aboutsummaryrefslogtreecommitdiff
path: root/files/fa/mozilla/add-ons/webextensions/your_second_webextension/index.html
blob: aff7ba025912082bd75499fa22c73529688f3ee5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
---
title: اکستنشن دوم شما
slug: Mozilla/Add-ons/WebExtensions/Your_second_WebExtension
translation_of: Mozilla/Add-ons/WebExtensions/Your_second_WebExtension
---
<div>
<p>{{AddonSidebar}}</p>

<p dir="rtl">اگر شما مقاله <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension">اولین اکستنشن خود</a> را خوانده اید، حالا ایده ای از چگونگی نوشتن یک اکستنشن به شما داده می شود. در این مقاله ما اکستنشن کمی پیچیده تری را که در ان از تعدادی از API ها استفاده شده، می نویسیم.</p>

<p>The extension adds a new button to the Firefox toolbar. When the user clicks the button, we display a popup enabling them to choose an animal. Once they choose an animal, we'll replace the current page's content with a picture of the chosen animal.</p>

<p>To implement this, we will:</p>

<ul>
 <li><strong>define a <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_action">browser action</a>, which is a button attached to the Firefox toolbar</strong>.<br>
  For the button we'll supply:
  <ul>
   <li>an icon, called "beasts-32.png"</li>
   <li>a popup to open when the button is pressed. The popup will include HTML, CSS, and JavaScript.</li>
  </ul>
 </li>
 <li><strong>define an icon for the extension</strong>, called "beasts-48.png". This will be shown in the Add-ons Manager.</li>
 <li><strong>write a content script, "beastify.js" that will be injected into web pages</strong>.<br>
  This is the code that will actually modify the pages.</li>
 <li><strong>package some images of the animals, to replace images in the web page</strong>.<br>
  We'll make the images "web accessible resources" so the web page can refer to them.</li>
</ul>

<p>You could visualise the overall structure of the extension like this:</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/13671/Untitled-1.png" style="display: block; height: 1200px; margin-left: auto; margin-right: auto; width: 860px;"></p>

<p>It's a simple extension, but shows many of the basic concepts of the WebExtensions API:</p>

<ul>
 <li>adding a button to the toolbar</li>
 <li>defining a popup panel using HTML, CSS, and JavaScript</li>
 <li>injecting content scripts into web pages</li>
 <li>communicating between content scripts and the rest of the extension</li>
 <li>packaging resources with your extension that can be used by web pages</li>
</ul>

<p>You can find <a href="https://github.com/mdn/webextensions-examples/tree/master/beastify">complete source code for the extension on GitHub</a>.</p>

<p>To write this extension, you'll need Firefox 45 or newer.</p>

<h2 id="Writing_the_extension">Writing the extension</h2>

<p>Create a new directory and navigate to it:</p>

<pre class="brush: bash">mkdir beastify
cd beastify</pre>

<h3 id="manifest.json">manifest.json</h3>

<p>Now create a new file called "manifest.json", and give it the following contents:</p>

<pre class="brush: json">{

  "manifest_version": 2,
  "name": "Beastify",
  "version": "1.0",

  "description": "Adds a browser action icon to the toolbar. Click the button to choose a beast. The active tab's body content is then replaced with a picture of the chosen beast. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#beastify",
  "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/beastify",
  "icons": {
    "48": "icons/beasts-48.png"
  },

  "permissions": [
    "activeTab"
  ],

  "browser_action": {
    "default_icon": "icons/beasts-32.png",
    "default_title": "Beastify",
    "default_popup": "popup/choose_beast.html"
  },

  "web_accessible_resources": [
    "beasts/frog.jpg",
    "beasts/turtle.jpg",
    "beasts/snake.jpg"
  ]

}
</pre>

<ul>
 <li>The first three keys: <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/manifest_version">manifest_version</a></code>, <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/name">name</a></code>, and <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version">version</a></code>, are mandatory and contain basic metadata for the extension.</li>
 <li><code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/description">description</a></code> and <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/homepage_url">homepage_url</a></code> are optional, but recommended: they provide useful information about the extension.</li>
 <li><code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/icons">icons</a></code> is optional, but recommended: it allows you to specify an icon for the extension, that will be shown in the Add-ons Manager.</li>
 <li><code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions">permissions</a></code> lists permissions the extension needs. We're just asking for the <a href="/en-US/Add-ons/WebExtensions/manifest.json/permissions#activeTab_permission"><code>activeTab</code> permission</a> here.</li>
 <li><code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_action">browser_action</a></code> specifies the toolbar button. We're supplying three pieces of information here:
  <ul>
   <li><code>default_icon</code> is mandatory, and points to the icon for the button</li>
   <li><code>default_title</code> is optional, and will be shown in a tooltip</li>
   <li><code>default_popup</code> is used if you want a popup to be shown when the user clicks the button. We do, so we've included this key and made it point to an HTML file included with the extension.</li>
  </ul>
 </li>
 <li><code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/web_accessible_resources">web_accessible_resources</a></code> lists files that we want to make accessible to web pages. Since the extension replaces the content in the page with images we've packaged with the extension, we need to make these images accessible to the page.</li>
</ul>

<p>Note that all paths given are relative to manifest.json itself.</p>

<h3 id="The_icon">The icon</h3>

<p>The extension should have an icon. This will be shown next to the extension's listing in the Add-ons Manager (you can open this by visiting the URL "about:addons"). Our manifest.json promised that we would have an icon for the toolbar at "icons/beasts-48.png".</p>

<p>Create the "icons" directory and save an icon there named "beasts-48.png".  You could use <a href="https://github.com/mdn/webextensions-examples/blob/master/beastify/icons/beasts-48.png">the one from our example</a>, which is taken from the <a href="https://www.iconfinder.com/iconsets/free-retina-icon-set">Aha-Soft’s Free Retina iconset</a>, and used under the terms of its <a href="http://www.aha-soft.com/free-icons/free-retina-icon-set/">license</a>.</p>

<p>If you choose to supply your own icon, It should be 48x48 pixels. You could also supply a 96x96 pixel icon, for high-resolution displays, and if you do this it will be specified as the <code>96</code> property of the <code>icons</code> object in manifest.json:</p>

<pre class="brush: json line-numbers  language-json"><code class="language-json"><span class="key token">"icons":</span> <span class="punctuation token">{</span>
  <span class="key token">"48":</span> <span class="string token">"icons/beasts-48.png"</span><span class="punctuation token">,</span>
  <span class="key token">"96":</span> <span class="string token">"icons/beasts-96.png"</span>
<span class="punctuation token">}</span></code></pre>

<h3 id="The_toolbar_button">The toolbar button</h3>

<p>The toolbar button also needs an icon, and our manifest.json promised that we would have an icon for the toolbar at "icons/beasts-32.png".</p>

<p>Save an icon named "beasts-32.png" in the "icons" directory. You could use <a href="https://github.com/mdn/webextensions-examples/blob/master/beastify/icons/beasts-32.png">the one from our example</a>, which is taken from the <a href="http://www.iconbeast.com/free">IconBeast Lite icon set</a> and used under the terms of its <a href="http://www.iconbeast.com/faq/">license</a>.</p>

<p>If you don't supply a popup, then a click event is dispatched to your extension when the user clicks the button. If you do supply a popup, the click event is not dispatched, but instead, the popup is opened. We want a popup, so let's create that next.</p>

<h3 id="The_popup">The popup</h3>

<p>The function of the popup is to enable the user to choose one of three beasts.</p>

<p>Create a new directory called "popup" under the extension root. This is where we'll keep the code for the popup. The popup will consist of three files:</p>

<ul>
 <li><strong><code>choose_beast.html</code></strong> defines the content of the panel</li>
 <li><strong><code>choose_beast.css</code></strong> styles the content</li>
 <li><strong><code>choose_beast.js</code></strong> handles the user's choice by running a content script in the active tab</li>
</ul>

<pre class="brush: bash">mkdir popup
cd popup
touch choose_beast.html choose_beast.css choose_beast.js
</pre>

<h4 id="choose_beast.html">choose_beast.html</h4>

<p>The HTML file looks like this:</p>

<pre class="brush: html">&lt;!DOCTYPE html&gt;

&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;link rel="stylesheet" href="choose_beast.css"/&gt;
  &lt;/head&gt;

&lt;body&gt;
  &lt;div id="popup-content"&gt;
    &lt;div class="button beast"&gt;Frog&lt;/div&gt;
    &lt;div class="button beast"&gt;Turtle&lt;/div&gt;
    &lt;div class="button beast"&gt;Snake&lt;/div&gt;
    &lt;div class="button reset"&gt;Reset&lt;/div&gt;
  &lt;/div&gt;
  &lt;div id="error-content" class="hidden"&gt;
    &lt;p&gt;Can't beastify this web page.&lt;/p&gt;&lt;p&gt;Try a different page.&lt;/p&gt;
  &lt;/div&gt;
  &lt;script src="choose_beast.js"&gt;&lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</pre>

<p>We have a <code><a href="/en-US/docs/Web/HTML/Element/div">&lt;div&gt;</a></code> element with an ID of <code>"popup-content"</code> that contains an element for each animal choice. We have another <code>&lt;div&gt;</code> with an ID of <code>"error-content"</code> and a class <code>"hidden"</code>. We'll use that in case there's a problem initializing the popup.</p>

<p>Note that we include the CSS and JS files from this file, just like a web page.</p>

<h4 id="choose_beast.css">choose_beast.css</h4>

<p>The CSS fixes the size of the popup, ensures that the three choices fill the space, and gives them some basic styling. It also hides elements with <code>class="hidden"</code>: this means that our <code>"error-content"</code> <code>&lt;div&gt;</code> will be hidden by default.</p>

<pre class="brush: css">html, body {
  width: 100px;
}

.hidden {
  display: none;
}

.button {
  margin: 3% auto;
  padding: 4px;
  text-align: center;
  font-size: 1.5em;
  cursor: pointer;
}

.beast:hover {
  background-color: #CFF2F2;
}

.beast {
  background-color: #E5F2F2;
}

.reset {
  background-color: #FBFBC9;
}

.reset:hover {
  background-color: #EAEA9D;
}

</pre>

<h4 id="choose_beast.js">choose_beast.js</h4>

<p>Here's the JavaScript for the popup:</p>

<pre class="brush: js">/**
 * CSS to hide everything on the page,
 * except for elements that have the "beastify-image" class.
 */
const hidePage = `body &gt; :not(.beastify-image) {
                    display: none;
                  }`;

/**
 * Listen for clicks on the buttons, and send the appropriate message to
 * the content script in the page.
 */
function listenForClicks() {
  document.addEventListener("click", (e) =&gt; {

    /**
     * Given the name of a beast, get the URL to the corresponding image.
     */
    function beastNameToURL(beastName) {
      switch (beastName) {
        case "Frog":
          return browser.extension.getURL("beasts/frog.jpg");
        case "Snake":
          return browser.extension.getURL("beasts/snake.jpg");
        case "Turtle":
          return browser.extension.getURL("beasts/turtle.jpg");
      }
    }

    /**
     * Insert the page-hiding CSS into the active tab,
     * then get the beast URL and
     * send a "beastify" message to the content script in the active tab.
     */
    function beastify(tabs) {
      browser.tabs.insertCSS({code: hidePage}).then(() =&gt; {
        let url = beastNameToURL(e.target.textContent);
        browser.tabs.sendMessage(tabs[0].id, {
          command: "beastify",
          beastURL: url
        });
      });
    }

    /**
     * Remove the page-hiding CSS from the active tab,
     * send a "reset" message to the content script in the active tab.
     */
    function reset(tabs) {
      browser.tabs.removeCSS({code: hidePage}).then(() =&gt; {
        browser.tabs.sendMessage(tabs[0].id, {
          command: "reset",
        });
      });
    }

    /**
     * Just log the error to the console.
     */
    function reportError(error) {
      console.error(`Could not beastify: ${error}`);
    }

    /**
     * Get the active tab,
     * then call "beastify()" or "reset()" as appropriate.
     */
    if (e.target.classList.contains("beast")) {
      browser.tabs.query({active: true, currentWindow: true})
        .then(beastify)
        .catch(reportError);
    }
    else if (e.target.classList.contains("reset")) {
      browser.tabs.query({active: true, currentWindow: true})
        .then(reset)
        .catch(reportError);
    }
  });
}

/**
 * There was an error executing the script.
 * Display the popup's error message, and hide the normal UI.
 */
function reportExecuteScriptError(error) {
  document.querySelector("#popup-content").classList.add("hidden");
  document.querySelector("#error-content").classList.remove("hidden");
  console.error(`Failed to execute beastify content script: ${error.message}`);
}

/**
 * When the popup loads, inject a content script into the active tab,
 * and add a click handler.
 * If we couldn't inject the script, handle the error.
 */
browser.tabs.executeScript({file: "/content_scripts/beastify.js"})
.then(listenForClicks)
.catch(reportExecuteScriptError);

</pre>

<p>The place to start here is line 96. The popup script executes a content script in the active tab as soon as the popup is loaded, using the <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/executeScript">browser.tabs.executeScript()</a></code> API. If executing the content script is successful, then the content script will stay loaded in the page until the tab is closed or the user navigates to a different page.</p>

<p>A common reason the <code>browser.tabs.executeScript()</code> call might fail is that you can't execute content scripts in all pages. For example, you can't execute them in privileged browser pages like about:debugging, and you can't execute them on pages in the <a href="https://addons.mozilla.org/">addons.mozilla.org</a> domain. If it does fail, <code>reportExecuteScriptError()</code> will hide the <code>"popup-content"</code> <code>&lt;div&gt;</code>, show the <code>"error-content"</code> <code>&lt;div&gt;</code>, and log an error to the <a href="/en-US/Add-ons/WebExtensions/Debugging">console</a>.</p>

<p>If executing the content script is successful, we call <code>listenForClicks()</code>. This listens for clicks on the popup.</p>

<ul>
 <li>If the click was on a button with <code>class="beast"</code>, then we call <code>beastify()</code>.</li>
 <li>If the click was on a button with <code>class="reset"</code>, then we call <code>reset()</code>.</li>
</ul>

<p>The <code>beastify()</code> function does three things:</p>

<ul>
 <li>map the button clicked to a URL pointing to an image of a particular beast</li>
 <li>hide the page's whole content by injecting some CSS, using the <code><a href="/en-US/Add-ons/WebExtensions/API/tabs/insertCSS">browser.tabs.insertCSS()</a></code> API</li>
 <li>send a "beastify" message to the content script using the <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/sendMessage">browser.tabs.sendMessage()</a></code> API, asking it to beastify the page, and passing it the URL to the beast image.</li>
</ul>

<p>The <code>reset()</code> function essentially undoes a beastify:</p>

<ul>
 <li>remove the CSS we added, using the <code><a href="/en-US/Add-ons/WebExtensions/API/tabs/removeCSS">browser.tabs.removeCSS()</a></code> API</li>
 <li>send a "reset" message to the content script asking it to reset the page.</li>
</ul>

<h3 id="The_content_script">The content script</h3>

<p>Create a new directory, under the extension root, called "content_scripts" and create a new file in it called "beastify.js", with the following contents:</p>

<pre class="brush: js">(function() {
  /**
   * Check and set a global guard variable.
   * If this content script is injected into the same page again,
   * it will do nothing next time.
   */
  if (window.hasRun) {
    return;
  }
  window.hasRun = true;

  /**
   * Given a URL to a beast image, remove all existing beasts, then
   * create and style an IMG node pointing to
   * that image, then insert the node into the document.
   */
  function insertBeast(beastURL) {
    removeExistingBeasts();
    let beastImage = document.createElement("img");
    beastImage.setAttribute("src", beastURL);
    beastImage.style.height = "100vh";
    beastImage.className = "beastify-image";
    document.body.appendChild(beastImage);
  }

  /**
   * Remove every beast from the page.
   */
  function removeExistingBeasts() {
    let existingBeasts = document.querySelectorAll(".beastify-image");
    for (let beast of existingBeasts) {
      beast.remove();
    }
  }

  /**
   * Listen for messages from the background script.
   * Call "beastify()" or "reset()".
  */
  browser.runtime.onMessage.addListener((message) =&gt; {
    if (message.command === "beastify") {
      insertBeast(message.beastURL);
    } else if (message.command === "reset") {
      removeExistingBeasts();
    }
  });

})();
</pre>

<p>The first thing the content script does is to check for a global variable <code>window.hasRun</code>: if it's set the script returns early, otherwise it sets <code>window.hasRun</code> and continues. The reason we do this is that every time the user opens the popup, the popup executes a content script in the active tab, so we could have multiple instances of the script running in a single tab. If this happens, we need to make sure that only the first instance is actually going to do anything.</p>

<p>After that, the place to start is line 40, where the content script listens for messages from the popup, using the <code><a href="/en-US/Add-ons/WebExtensions/API/runtime/onMessage">browser.runtime.onMessage</a></code> API. We saw above that the popup script can send two different sorts of messages: "beastify" and "reset".</p>

<ul>
 <li>if the message is "beastify", we expect it to contain a URL pointing to a beast image. We remove any beasts that might have been added by previous "beastify" calls, then construct and append an <code><a href="/en-US/docs/Web/HTML/Element/img">&lt;img&gt;</a></code> element whose <code>src</code> attribute is set to the beast URL.</li>
 <li>if the message is "reset", we just remove any beasts that might have been added.</li>
</ul>

<h3 id="The_beasts">The beasts</h3>

<p>Finally, we need to include the images of the beasts.</p>

<p>Create a new directory called "beasts", and add the three images in that directory, with the appropriate names. You can get the images from <a href="https://github.com/mdn/webextensions-examples/tree/master/beastify/beasts">the GitHub repository</a>, or from here:</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/11459/frog.jpg" style="display: inline-block; height: 200px; margin: 20px; width: 200px;"><img alt="" src="https://mdn.mozillademos.org/files/11461/snake.jpg" style="display: inline-block; height: 200px; margin: 20px; width: 200px;"><img alt="" src="https://mdn.mozillademos.org/files/11463/turtle.jpg" style="display: inline-block; height: 200px; margin: 20px; width: 200px;"></p>

<h2 id="Testing_it_out">Testing it out</h2>

<p>First, double check that you have the right files in the right places:</p>

<pre>beastify/

    beasts/
        frog.jpg
        snake.jpg
        turtle.jpg

    content_scripts/
        beastify.js

    icons/
        beasts-32.png
        beasts-48.png

    popup/
        choose_beast.css
        choose_beast.html
        choose_beast.js

    manifest.json</pre>

<p>Starting in Firefox 45, you can install extensions temporarily from disk.</p>

<p>Open "about:debugging" in Firefox, click "Load Temporary Add-on", and select your manifest.json file. You should then see the extension's icon appear in the Firefox toolbar:</p>

<p>{{EmbedYouTube("sAM78GU4P34")}}</p>

<p>Open a web page, then click the icon, select a beast, and see the web page change:</p>

<p>{{EmbedYouTube("YMQXyAQSiE8")}}</p>

<h2 id="Developing_from_the_command_line">Developing from the command line</h2>

<p>You can automate the temporary installation step by using the <a href="/en-US/Add-ons/WebExtensions/Getting_started_with_web-ext">web-ext</a> tool. Try this:</p>

<pre class="brush: bash">cd beastify
web-ext run</pre>
</div>