MDN wants to learn about developers like you: https://qsurvey.mozilla.com/s3/MDN-dev-survey

重要: 僅 Firefox OS 2.5+ 支援附加元件功能。

「附加元件」的概念在 Web 瀏覽器的世界中眾人皆知,而我們也將這個概念帶進 Firefox OS。Firefox OS 的附加元件可以僅僅作用於單一 App 上,也可以指定作用於多個、甚至所有 App 上。本文帶您一步步撰寫自己的 Firefox OS 附加元件,同時提供一些秘訣及其他應該了解的資訊。

附註:本文僅重點概略翻譯。

附註:Firefox OS 附加元件採用 WebExtensions 擴充模式。此模式大部份源自於 Chrome/Blink 擴充套件機制,讓附加元件在開發時能擁有許多互通性與功能上的優勢。詳情可見持續編寫中的 WebExtensions 文件集

開發附加元件

Firefox OS 附加元件其實也是個內含 JavaScript / CSS / 其他必備檔案的 App,但不是拿來獨立運作,而是在描述檔中增加特別的說明來定義要在哪些 App 中使用這個附加元件。When apps are launched on a Firefox OS device that has an add-on installed, the add-on is injected into any app that matches the pattern specifed in the filter.

Firefox OS add-ons use the same syntax and structure for their code as the new school of Firefox add-ons developed using the WebExtensions API, which is itself based on the Chrome extensions model.

簡單範例

以下我們以一個簡單的例子說明 Firefox OS 附加元件的程式基礎。這個附加元件會在 system app 放上一塊看板,使用者可以點擊關閉。

firefox os screenshot showing add-on banner

這個附加元件很簡單,但用來作為入門倒是相當足夠了。你可以在 GitHub 上下載範例程式,而後用 WebIDE 裝到你的 Firefox OS 裝置上(參考 Testing your add-on using WebIDE 一節)。往後你也可以透過 Firefox Marketplace 來發表自己的附加元件。

Be aware that Firefox OS add-ons can do a lot more than what we've got listed here. The WebExtensions documentation will have more information added as time goes on.

解析 Firefox OS add-on

In this section we'll walkthrough the contents of the sample add-on repo, explaining each piece of content. 目錄結構看起來像這樣:

  • simple-addon/
    • manifest.json
    • update.webapp
    • css/
      • style.css
    • js/
      • index.js
    • icons/
      • 128.png
    • extension.zip

manifest.json

你應該發現了:在這個範例附加元件目錄中,有兩種類似的描述檔。第一個「manifest.json」的結構類似 Chrome 擴充套件,與 CSS、JavaScript 及其他檔案一起放在 extensions.zip 當中。It can contain a large variety of instructions (see Chrome Manifest File Format), but for now we're just going to concentrate on a simple subset:

{
  "manifest_version": 1,
  "name": "Add-on banner",
  "description": "Firefox OS add-on example",
  "version": "1.0",
  "author": "Chris Mills",
  "content_scripts": [{
    "matches": ["app://system.gaiamobile.org/index.html"],
    "css": ["css/style.css"],
    "js": ["js/index.js"]
  }],
  "icons": {
    "128": "/icons/128.png"
  }
}

Most of these fields are pretty self-explanatory, but we'll cover the last few.

首先以 content_scripts 指定要植入 app 的檔案 — 你可以看到這邊以 cssjs 兩欄分別指定 CSS 樣式檔及 JavaScript 程式檔。matches 欄位則是用以指定該把檔案放入哪些 app 裡,這個欄位的內容形式很多元(參考 Chrome Match Patterns),但我們先簡單指定為 app://system.gaiamobile.org/index.html,讓附加元件僅影響 system app。若想影響所有 app,可以將此欄位寫為 app://*/*

Note: You can reference multiple scripts and stylesheets by simply including multiple items in the arrays, for example "css": ["css/style.css", "css/more.css"].

Note: Firefox OS does not currently support the Chrome <all_urls> keyword.

At the bottom of the manifest we've included the icons field; see the next section for more info on this.

update.webapp

update.webapp 則是 Firefox OS 式的描述檔,基本上就是跟打包 app 時用的描述檔格式相同。(參考 Self-publishing packaged apps。)

Our update.webapp file looks like so:

{
  "name" : "Add-on banner",
  "description": "Firefox OS add-on example",
  "developer": { "name": "Chris Mills" },
  "package_path": "extension.zip",
  "icons": {
    "128": "/icons/128.png"
  }
}

Again, most of this is fairly self-explanatory.

這邊最重要的欄位該是 package_path,用以指定內含附加元件程式的包裝檔位置。

You'll notice that the icons field is included here, the same as it is in manifest.jsonupdate.webapp is the only place you need to have the icons information at the moment, but we'd recommend you include it in both places for now, just in case things change. The icons field points to the add-on icon so it can be used inside the Gaia Settings app, and the Firefox Marketplace when it starts to host add-ons.

指定圖示

你必須在描述檔中至少指定一個圖示,否則描述檔無效。詳情可參考 Manifest 參考文件:圖示 一節。

CSS

There is nothing special about the CSS included in the example. The only thing to bear in mind is that you should make sure your add-on classnames and selectors do not conflict with any of the existing CSS in the app(s) it is applied to.

For example, we wrapped our example banner in a <div> with class fxos-banner. But you could even consider using some kind of unique code for your classname.

JavaScript

Again, the JavaScript file that powers the add-on doesn't have any special functionality inside it (see the JavaScript source on Github.) It is injected into the apps it is applied to along with any CSS specified in the manifest.json file.

Note: Add-on code is injected every time an app is launched and the match specified in manifest.json pattern matches that app. It is also injected whenever add-ons are enabled. When an add-on is injected into an app because the app is launching, all add-on files are injected into the app before anything in the app is initialized, including the DOM. It is up to the add-on developer to handle the different launch cases cases (immediate injection vs. injection on launch); there is more info on this below.

Other main things to note are covered below.

The window object

Add-ons only share a proxied version of the content window. As a result, anything that is written to the window object from an add-on is unavailable to the app code. However, anything on the window object that is set by app code is available to add-ons. Similarly, the DOM is accessible as usual.

Injecting code at the correct time

You must be careful to properly handle cases where an add-on is injected into an app after the app has been loaded. Such a scenario can occur when an app is already running and an add-on that targets it is enabled.

in such a case, a window.onload handler won't work because the DOMContentLoaded event has already occured.

There's no good solution to this problem right now. In the interim, we recommend to check whether or not the DOM has been loaded before setting a DOMContentLoaded callback. This pattern is used in the demo:

// If injecting into an app that was already running at the time
// the app was enabled, simply initialize it.
if (document.documentElement) {
  initialize();
}

// Otherwise, we need to wait for the DOM to be ready before
// starting initialization since add-ons are usually (always?)
// injected *before* `document.documentElement` is defined.
else {
  window.addEventListener('DOMContentLoaded', initialize);
}

function initialize() {
  // ...
}

避免重複植入

為避免附加元件的程式碼多次重複植入到同一 App 中,您必須檢查附加元件是否曾經植入過,例如這樣

you should check whether your add-on is already present, like this:

function initialize() {
  if (document.querySelector('.fxos-banner')) {
    // Already injected, abort.
    return;
  } else {
    var body = document.querySelector('body');
    var fxosBanner = document.createElement('div');
    fxosBanner.classList.add('fxos-banner');
    var bannerText = document.createElement('p');
    var closeBtn = document.createElement('button');

    fxosBanner.appendChild(bannerText);
    fxosBanner.appendChild(closeBtn);
    body.appendChild(fxosBanner);

    closeBtn.textContent = 'X';
    bannerText.textContent = 'Wow, you have an extension installed!';

    closeBtn.onclick = function() {
      fxosBanner.parentNode.removeChild(fxosBanner);
    }
  }
}

So here we are using if (document.querySelector('.fxos-banner')) to check whether the example banner already exists. If so, then we return out of the function. If not, then the querySelector() method returns null, and we run the code block that creates the banner.

App management functions in add-ons

All Apps and Mgmt functions work on add-ons just like they do on apps. Be aware however that the latter are only available to add-ons when they are injected into a certified app that has the webapps-manager permission specified in the manifest.

In addition to these functions, an onenabledstatechange callback is exposed for add-ons being enabled and disabled. This event is fired for all add-ons, so you will have to check which add-on was enabled/disabled before performing initialization or cleanup.

navigator.mozApps.mgmt.onenabledstatechange = function(event) {
  var app = event.application;
  if (app.manifestURL === 'https://origin.of.manifest/manifest.webapp') {
    var wasEnabled = app.enabled;
    // do something with this information
  }
};

extension.zip

Note: The extension.zip file has been left in the demo repo mainly for illustrative purposes, so it is clear how the system works. You actually don't need to include the zip in your directory, as WebIDE will generate it for you when you install the add-on. The Firefox Marketplace is likely to do the same when it starts to list add-ons.

The extension.zip archive contains the code for the extension, and is referenced in the update.webapp package_path field — This is how Gecko finds the code to be installed. Archived inside you'll find:

  • css/
    • style.css
  • js/
    • index.js
  • icons/
    • 128.png
  • manifest.json

So the manifest.json file sits inside the archive, and serves to reference the files to be injected and specify which apps to affect.

Testing your add-on using WebIDE

Mozilla's WebIDE tool is available in Firefox desktop by default. To use it for installing add-ons on your phone, follow the steps listed below:

  1. Make sure you have Firefox 43 or above installed (this was Nightly at the time of writing), as add-ons are not supported in WebIDE below this version.
  2. Open your browser and open the WebIDE tool (press the WebIDE button, or choose Tools > Web Developer > WebIDE from the menu.)
  3. Make sure your phone has remote debugging enabled (Settings App > Developer > Set the "Debugging via USB " selection to "ADB and DevTools".)
  4. Connect your phone to your computer via a USB cable. Make sure you don't have other phones connected at the same time.
  5. In the WebIDE UI, press the Select Runtime option, and select your phone, which should be listed under USB Devices.
  6. At this point, your phone should be showing an Allow USB debugging connection? prompt. Choose Allow.
  7. Select the Open App option, then choose Open Packaged App...
  8. In the resulting file chooser, navigate to the directory that contains your update.webapp manifest file, and press Open.
  9. Providing there are no warnings or errors reported, you can install your add-on on the device using the "Play" button in the center (Install and Run.)
  10. To see the add-on in action, enable it by choosing Settings app > Add-ons > Add-on example > toggle the checkbox at the top.

Add-on settings

You can control the add-ons on your phone by going to Settings app > Add-ons; in here you'll find a list of your installed add-ons, and you can tap each entry to see more information about each add-on.

firefox os screenshot showing a list of installed add-ons in the settings appinformation screen for an individual addon, with a list of apps this add-on affects, and controls to disable and delete the add-on

Enabling/disabling and deleting add-ons

By default, add-ons will be enabled after installation when they are installed form the Firefox Marketplace. When installed via WebIDE however they will be disabled by default.

You can manually enable/disable add-ons via the checkbox at the top of each individual add-on's page (found under Settings app > Add-ons), or programmatically using the navigator.mozApps.mgmt.setEnabled() function (see this setEnabled() usage example on Github.)

You can delete an add-on entirely by tapping the Delete button found on individual app pages.

Apps affected by add-ons

You'll also notice that in the page for each individual add-on there is a section that lists which apps are affected by that add-on. Currently this doesn't seem to work (it always lists This add-on does not affect any installed apps.) This should be fixed soon (see bug 1196386 for progress.)

權限

附加元件的權限繼承自其所植入的 App,在描述檔中想多要一點權限是行不通的,也無法以此方式增加其植入對象的權限。

文件標籤與貢獻者

 此頁面的貢獻者: chrisdavidmills, BobChao
 最近更新: chrisdavidmills,