firefox add-on sdk 入門

Post on 14-Jul-2015

7.393 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Firefox Add-on SDK入門Pasta-K

📧pastak@kmc.gr.jp

こんにちは😺

こんにちは😺

で、誰?

🍣 KMCでの活動

KMC 37代目 広報

OSC Kyoto 2014 出展 etc

JavaScriptで世界平和2014

🍣 インターネット活動

! twitter.com/pastak id: Pasta-K pastak.hatenablog.com pastak-diary.hatenadiary.com " github.com/pastak

はてなインターン 2013

アルバイトエンジニア ←イマココ

最近では、はてなのインターンを経てウチへ来るというのが定番の

ようになっているんです !

休学して社員となった杉本氏をはじめ、現在5人いる学生アルバイトのメンバーも、そのほとんどが「京大マイコンクラブ」の所属。

アニメファン

🐾目次

• Mozilla Firefox について • ブラウザー拡張について • Firefox Add-on SDKでアドオンを作ってみる

" サンプルコード

https://github.com/pastak/firefox-addon-sdk-sample

Mozilla Firefoxについて

About Mozilla Firefoxhttps://www.mozilla.org/firefox/ Mozillaによって提供されているブラウザ

Win / Mac / Linux / Android 現行最新バージョン 36.0.1 (2015/03/05)

次回37は2015/03/31にリリース予定

Netscapeがうにゃむにゃみたいな歴史に興味がある人は自力でググッてください

ブラウザシェア (2015/02)

http://news.mynavi.jp/news/2015/03/04/084/

iceweasel

iceweasel

Iceweasel(アイスウィーズル)とは、ウェブブラウザ。またはその開発プロジェクト。Mozilla Firefox とほぼ同一のものであるが、商標の関係により同プロジェクトから独立したもの。 Debian によるものと、GNU によるもの(GNU IceWeasel)が存在したが、現在では GNU IceWeasel は GNU IceCat に名称変更されている。

http://ja.wikipedia.org/wiki/Iceweasel

ブラウザ拡張 現状確認

Google Chrome Opera

(Based Chromium)

Google Chrome / OperaJavaScript + HTML + CSS で書ける

Chromium由来の共通のAPIを利用する

→同じExtensionがそのまま動作する

Chrome ExtensionをOperaにインストールするための拡張

https://addons.opera.com/ja/extensions/details/download-chrome-extension-9 ちなみにOpera側のドキュメントの方が少しだけ充実してる

!ExtensionとAppの2種類がある

Safari

Safarihttps://extensions.apple.com/ Chromiumと同じくHTML + JS + CSSで書ける 大体同じ世界観で書ける ドキュメント https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/Introduction/Introduction.html !

Internet Explorer

Internet Explorer

MSDNにチュートリアルなどのページがある。(寧ろそれ以外どこにも載ってない) Browser Extensions Overviews and Tutorials https://msdn.microsoft.com/en-us/library/aa753616%28v=vs.85%29.aspx C#で書くっぽい。 あんまり書いてる人見たこと無い気がする

Mozilla Firefox

Mozilla Firefoxダウンロード https://addons.mozilla.org/ ドキュメントはとにかくMDN

https://developer.mozilla.org/ja/docs/Extensions 開発手法は2通り

XULベースのAdd-on

HTML + JS + CSSベースのAdd-on

Addon SDKで開発できるのは後者 このあと詳しく

Google Chrome VS

Mozilla Firefox

操作可能範囲

表示領域 右上ポップアップ オムニバー

表示領域 サイドバー ツールバー URLバー ポップアップ ステータスバー

表示領域 サイドバー ツールバー URLバー ポップアップ ステータスバーAdd-on SDKでステータスバーにボタンなど

を追加するためのwidgetモジュールがFirefox 29で削除

公開方法

https://chrome.google.com/webstore/

無審査 開発者登録に$5

https://addons.mozilla.org/

審査あり 開発者登録無料

審査あり 開発者登録無料

人が確認するのではない セキュリティ上の問題などを自動確認

💖

Firefox Add-on SDK について

改めてFirefoxのアドオンについて

XULベースの開発とAdd-on SDKでの開発

配布形式は xpi ( その正体はzip )

!

開発に関しては困ったらとにかくMDNを見る

https://developer.mozilla.org/ja/docs/Extensions

http://www.slideshare.net/skeevs/mozilla-firefox-extension-development

XULベースの開発についてこれ以降は特に触れない Firefoxの初期からあった伝統的な方

FirefoxアプリケーションのXULを上書きして機能を提供したり、XPCOM コンポーネントを通じて操作をしたりする。 !より詳細な比較は https://developer.mozilla.org/en-US/

Add-ons/SDK/Guides/SDK_vs_XUL を

2009年 Jetpackリリース

アドオンとして提供開始 当時JSだけでFirefoxのアドオンが作れると話題に

XULとか触らなくて良い

JSとHTMLとCSSでなんとかなる

jQueryも標準装備

Firebugでデバッグ出来る!

大喜び

– https://developer.mozilla.org/ja/docs/Jetpack

“Jetpack で、開発者は拡張機能を高速に作り出すことができると同時に、強化された体験を与えられたユーザーは、Web

とのふれあいが変わるでしょう。”

それから2010年 初頭

Jetpack(Prototype)から Jetpack Rebootへ

アドオンからSDKへ移行

開発環境もPythonで書かれたコマンドラインツールに

https://dev.mozilla.jp/2010/03/shifting-from-jetpack-ptototype-to-jetpack-reboot/ !

2010年末名称を Add-on SDK へ名称変更 🎉

Add-on SDKについて

FirefoxだけではなくThumderbird向けのアドオンも作れる 一部のAPIはAndroid向けのFirefoxにも対応

デバッグのためにUSBでインストール可能

Add-on SDKで扱えるAPI一覧

座学 ここまで

ここから 実践編

とにかく 数が多い

スライドに書いてあること一覧

ボタンを出す バッジを操作する パネルを出す サイドバーを出す 通信する その他諸々

🚀

Add-on SDKのインストールMac OSXの人はhomebrewで一発

!

Git cloneでも入手可

!

!

https://developer.mozilla.org/en-US/Add-ons/SDK/Tutorials/Installation

$ brew install mozilla-addon-sdk

$ git clone git@github.com:mozilla/addon-sdk.git

Add-on SDKのインストールhomebrew以外の方法で入れた場合は

./bin/ をPATHに追加する と使えるようになる。 SDK仮想環境をシェル上で起動することが一応推奨されているっぽい

$ bash ./bin/activate $ cfx --version Add-on SDK 1.17(12f7d53e8b5…)

cfxコマンドの基本的な使い方

Add-onのスケルトンを生成

!

Add-onをFirefox上で実行

!

Add-onをxpiとして生成

!

テストを実行

$ cfx init

$ cfx run

$ cfx xpi

$ cfx test

Add-on用のスケルトンを生成

!

!

!

!

!

この状態で      で実行可能 (ただし機能は何もない)

$ cfx init

. ├── data ├── lib │   └── main.js ├── package.json └── test └── test-main.js

$ cfx run

$ cfx init $ mkdir firefox-addon-sample // cfx initは空のディレクトリでのみ実行可能 $ cd firefox-addon-sample $ cfx init * lib directory created * data directory created * test directory created * generated jID automatically: ********* * package.json written * test/test-main.js written * lib/main.js written !Your sample add-on is now ready. Do "cfx test" to test it and "cfx run" to try it. Have fun!

これを実行するとFirefoxが立ち上がる

実行する度にprofileを生成する

普段の環境を汚さない

profileを指定する場合は

$ cfx run

$ cfx run -p <ProfilePath><ProfilePath> Mac OSXの場合は~/Library/Application Support/Firefox/Profiles/以下にある https://support.mozilla.org/ja/kb/profiles-where-firefox-stores-user-data

デバッグについて

JSで console.log すると標準出力に出る デバッガーも利用可能 about:addons で [デバッグ]ボタンを押す

配布用のxpiファイルを生成

ドラッグアンドドロップでインストール 自前サーバで配布する場合でもアップデートを配信することも出来る !!!!HTTPで配信する場合はこの方法では署名できない

詳細: Add-on SDKで作ったFirefox拡張をHTTPで配布、アップデートする方法 - Pastalablog in はてな

http://pastak.hatenablog.com/entry/2013/10/03/213040 !※AMO以外からは配布出来なくなる予定があるので注意(おまけ参照)

$ cfx xpi

$ cfx xpi \ --update-url=https://hogehoge.com/hoge.update.rdf \ --update-link=https://hogehoge.com/hoge.xpi

ファイルの紹介. ├── data <- 静的htmlや画像ファイルなど │ アイコンなどもここに置く ├── lib │   └── main.js <- エントリーポイント │ になるJSファイル ├── package.json <- 設定などを書く └── test <- ここに置いておけば │ cfx test で実行してくれる └── test-main.js

package.jsonAdd-onに関する情報を書いておくファイル。

$ cat package.json { "name": "sample", "title": "sample", "id": "jid1-ZaoDi2iCqJeZVg", "description": "a basic add-on", "author": "", "license": "MPL 2.0", "version": "0.1" }

package.jsonname: アドオンの名前を入れる。

ドット(.)やスペースを含むことが出来ない。

title や fullName があればそちらが利用される

version: アドオンのバージョンを書く。

記法は npm などと同じくsemver に準拠する

http://semver.org/

package.jsonpermissions: クロスドメインXHRを許可するホスト名の指定

private browsing mode での許可

icon, icon64: アイコンのパスを指定する

iconは48x48、icon64は64x64

無ければデフォルトアイコンを利用する

main.js を編集してみる

右上にボタンを追加してみる

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

モジュールを読み込む モジュールの実体は build時にxpiに含まれる

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

モジュールのパス

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

ボタンにidとか アイコンとか ハンドラを設定する。

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

相対パスで書くと ./data以下を参照する

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

ツールバー 18x18 (px) メニューパネル 32x32 (px)

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

ツールバー 18x18 (px) メニューパネル 32x32 (px)

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: "./icon-16.png", onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

省略可

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); }

クリック時に呼ばれる関数

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick }); !function handleClick(state) { tabs.open("http://www.mozilla.org/"); } 新しいタブを開く

badgeを出すNew feature for Firefox 36

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !function handleClick(state) { button.badge = state.badge + 1; }

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !function handleClick(state) { button.badge = state.badge + 1; }

変更箇所

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !function handleClick(state) { button.badge = state.badge + 1; }

badgeの値と色を指定 値: Number or String 色: CSS color value

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick, badge: "KMC!!", badgeColor: "#00AAAA" }); !function handleClick(state) { button.badge = state.badge + 1; }

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick, badge: "KMC!!", badgeColor: "blue" }); !function handleClick(state) { button.badge = state.badge + 1; }

var buttons = require('sdk/ui/button/action'); var tabs = require("sdk/tabs"); !var button = buttons.ActionButton({ id: "mozilla-link", label: "Visit Mozilla", icon: { "16": "./icon-16.png", "32": "./icon-32.png", "64": "./icon-64.png" }, onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !function handleClick(state) { button.badge = state.badge + 1; } badgeの値を変更

ボタンを押したらパネルが出る

var buttons = require('sdk/ui/button/action'); var panels = require("sdk/panel"); !var button = buttons.ActionButton({ id: "panel-sample", label: "Panel Button", icon: "./icon-16.png", onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); } }); !function handleClick(state) { if (!state.checked) { panel.show({ position: button }); } button.state('window', {checked: !state.checked}); }

panelモジュール

main.js

var buttons = require('sdk/ui/button/action'); var panels = require("sdk/panel"); !var button = buttons.ActionButton({ id: "panel-sample", label: "Panel Button", icon: "./icon-16.png", onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); } }); !function handleClick(state) { if (!state.checked) { panel.show({ position: button }); } button.state('window', {checked: !state.checked}); }

パネルを記述

main.js

var buttons = require('sdk/ui/button/action'); var panels = require("sdk/panel"); !var button = buttons.ActionButton({ id: "panel-sample", label: "Panel Button", icon: "./icon-16.png", onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); } }); !function handleClick(state) { if (!state.checked) { panel.show({ position: button }); } button.state('window', {checked: !state.checked}); }

width, height 大きさ contextMenu コンテキストメニューの表示の許可 onShow / onHide / onMessage 各々のイベントハンドラー main.js

var buttons = require('sdk/ui/button/action'); var panels = require("sdk/panel"); !var button = buttons.ActionButton({ id: "panel-sample", label: "Panel Button", icon: "./icon-16.png", onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); } }); !function handleClick(state) { if (!state.checked) { panel.show({ position: button }); } button.state('window', {checked: !state.checked}); }

表示するHTMLファイルを指定

main.js

var buttons = require('sdk/ui/button/action'); var panels = require("sdk/panel"); !var button = buttons.ActionButton({ id: "panel-sample", label: "Panel Button", icon: "./icon-16.png", onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); } }); !function handleClick(state) { if (!state.checked) { panel.show({ position: button }); } button.state('window', {checked: !state.checked}); }

パネルを表示

main.js

var buttons = require('sdk/ui/button/action'); var panels = require("sdk/panel"); !var button = buttons.ActionButton({ id: "panel-sample", label: "Panel Button", icon: "./icon-16.png", onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); } }); !function handleClick(state) { if (!state.checked) { panel.show({ position: button }); } button.state('window', {checked: !state.checked}); }

object top / bottom / left / right

main.js

var buttons = require('sdk/ui/button/action'); var panels = require("sdk/panel"); !var button = buttons.ActionButton({ id: "panel-sample", label: "Panel Button", icon: "./icon-16.png", onClick: handleClick, badge: 0, badgeColor: "#00AAAA" }); !var panel = panels.Panel({ contentURL: "./panel.html", position: button, onHide: function(){ button.state('window', {checked: false}); } }); !function handleClick(state) { if (!state.checked) { panel.show(); } button.state('window', {checked: !state.checked}); }

ここで設定しておいてもOK

main.js

パネル内のイベントを掴む

パネル内のHTML上のボタンが押されたらbadgeの内容をアップデートする

Messaging(1) - port emit(name, message) on(name, handler) removeListener(name, handler) once(name, handler) 一度だけmessageを受ける

事前にmessageに対してhandlerを設定しておく

https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts/using_port

Messaging(2) - postMessage

messageイベントを発火させる

context-menuモジュールなどはportがサポートされていないのでこちらを使うしかない panelなどには onMessage があるので、こっちを使っておくと吉

https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts/using_postMessage

!!var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); }, onMessage: function(){ button.badge = button.badge + 1; } }); !

main.js(抜粋)

!!var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); }, onMessage: function(){ button.badge = button.badge + 1; } }); !

メッセージを受取ったら badgeを++

main.js(抜粋)

<h1>Panel!!!!!!!</h1> <button id='increament'>++</button> <script> !!!</script>

var btn = document.getElementById('increament'); btn.addEventListener('click', function(){ addon.postMessage("increament"); })

./data/panel.html

この場合はpostMessageは addonのメソッド JSをcontentScriptとして 読み込ませた場合はselfのメソッドになる

var btn = document.getElementById('increament'); btn.addEventListener('click', function(){ addon.postMessage({ type: 'increament' }); }) var color = document.getElementById('color'); color.addEventListener('change', function(){ addon.postMessage({ type: 'color', value: color.value }); })

<h1>Panel!!!!!!!</h1> <button id='increament'>++</button> <input type='color' id='color'/> <script> !!!!!!!!!!!!</script>

./data/panel.html

input[type=color]を使って badgeの色を変える

var btn = document.getElementById('increament'); btn.addEventListener('click', function(){ addon.postMessage({ type: 'increament' }); }) var color = document.getElementById('color'); color.addEventListener('change', function(){ addon.postMessage({ type: 'color', value: color.value }); })

<h1>Panel!!!!!!!</h1> <button id='increament'>++</button> <input type='color' id='color'/> <script> !!!!!!!!!!!!</script>

./data/panel.html

messageはJSONserializeされる

var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); }, onMessage: function(message){ switch(message.type){ case 'increament': button.badge = button.badge + 1; break; case 'color': button.badgeColor = message.value; break; } } });

main.js(抜粋)

message.typeで挙動を切替

サイドバーを表示する

サイドバー

Chrome Extensionだとこれが出来ない

扱い方の要領はpanelと同じ

sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html' })

main.js

sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html' })

main.js

main.js

sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ worker.port.emit('ping', ''); } })

main.js

sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ worker.port.emit('ping', ''); } }) サイドバーが表示されたら

messageを送る

<script> !!!</script>

addon.port.on('ping', function(){ alert('pong'); })

./data/sidebar.html

<script> !!!</script>

addon.port.on('ping', function(){ alert('pong'); })

./data/sidebar.html

alertを出す

サイドバーでAjaxを実装する

サイドバーからはCross-Originの制約でXHRなどで外部に通信できない panelなどはJSをファイルに分けて、

contentScriptFileなどで読込めばXHR出来る

→main.jsで代わりに通信して結果を

 messageで送信する

package.jsonに追記通信先のプロトコルとドメインを記述する Wildcardは使えないので注意

"permissions": { "cross-domain-content": [ "http://example.org/", "https://example.com/" ] }

Request = require("sdk/request").Request; sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ Request({ url: "http://api.openweathermap.org/data/2.5/weather?q=Kyoto,jp", // 天気情報を取得するAPIを利用してみる onComplete: function(response){ worker.port.emit('ping', response.json) } }).get() } })

main.js

Request = require("sdk/request").Request; sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ Request({ url: "http://api.openweathermap.org/data/2.5/weather? q=Kyoto,jp", // 天気情報を取得するAPIを利用してみる onComplete: function(response){ worker.port.emit('ping', response.json) } }).get() } })

main.js

外部と通信するために Requestモジュールを利用

RequestXMLHttpRequestをベースにしたオブジェクト

!

option: url: 通信先のURL

onComplete: 通信成功後に呼ぶ関数

content: GETやHEADのquery、POSTやPUTのbody

contentType: request headerのContent-Typeの内容

header: request header

require("sdk/request").Request(option)

Request methods

例: GET

!

get() head() post() put() delete()

require("sdk/request").Request(option).get()

Request = require("sdk/request").Request; sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ Request({ url: "http://api.openweathermap.org/data/2.5/weather? q=Kyoto,jp", // 天気情報を取得するAPIを利用してみる onComplete: function(response){ worker.port.emit('ping', response.json) } }).get() } })

main.js

成功したらportで サイドバー内のJSに送る

Request = require("sdk/request").Request; sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ Request({ url: "http://api.openweathermap.org/data/2.5/weather? q=Kyoto,jp", // 天気情報を取得するAPIを利用してみる onComplete: function(response){ worker.port.emit('ping', response.json) } }).get() } })

main.js

通信結果を保持しているオブジェクト text: plain text json: JSON.parse()の結果 status: status code ( ex: 200 ) statusText: headers: HTTP response headerのK/Vオブジェクト

<script> !!!!!!!!!!!</script>

addon.port.on('ping', function(data){ var weather = data.weather[0]; document.body.innerHTML = ` <ul> <li>Weather: ${weather.main}</li> <li>Description: ${weather.description}</li> <li>Sunrise: ${Date(data.sys.sunrise)} </li> <li>Sunset: ${Date(data.sys.sunset)}</li> </ul> `; })

./data/sidebar.html

<script> !!!!!!!!!!!</script>

addon.port.on('ping', function(data){ var weather = data.weather[0]; document.body.innerHTML = ` <ul> <li>Weather: ${weather.main}</li> <li>Description: ${weather.description}</li> <li>Sunrise: ${Date(data.sys.sunrise)} </li> <li>Sunset: ${Date(data.sys.sunset)}</li> </ul> `; })

./data/sidebar.html

メッセージを受け取って HTMLとして出力

諸々モジュール紹介

clipboard, context-menu, hotkeys, notification, page-mod, simple-prefs …

clipboard

var clipboard = require("sdk/clipboard"); clipboard.set("KMC is great!"); var contents = clipboard.get(); //set some HTML clipboard.set("<marquee>KMC KMC KMC</marquee>", "html");

context-menu

コンテキストメニューを表示する Chromeと違って細やかに設定できる

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

メニューアイテム を生成

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

表示するテキスト

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

アイコンを出すことも 出来る

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

表示するコンテキストを 指定する

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

こういう指定も出来る

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

PreficateContextは 引数の関数がtrueを返す場合に メニューアイテムを表示する

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

PreficateContextは 引数の関数がtrueを返す場合に メニューアイテムを表示する

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

documentType text/html, image/jpeg documentURL ページのURL targetName タグ名 targetID id属性 isEditable contenteditable? selectionText 選択中の文字列 srcURL <img>のsrc属性 linkURL <a>のhref属性 value <input><textarea>のvalue属性

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

MenuはItemを 子に持つことが出来る

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

ページ上のあらゆる箇所

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

child, sp, child2を 子メニューとして持つ

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

セパレータを生成

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' }); 親メニューの無いアイテム

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' });

メニューを表示している状態で このキーを押すと選択したことになる。 キーの組み合わせ不可

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' }); URLでマッチさせる

var cm = require("sdk/context-menu"); var sp = cm.Separator(); var child = cm.Item({ label: "Child Item 2 show only on <img>", image: require("sdk/self").data.url("icon-16.png") }); child.context.add(cm.SelectorContext('img')); var child2 = cm.Item({ label: "This item show only on some context", context: cm.PredicateContext(function(context){ return ( (context.documentURL.match(/kmc/)) || (context.selectionText && context.selectionText.length > 3) || (context.srcURL && context.srcURL.match(/png$/)) ) }) }); var menu = cm.Menu({ label: "sample menu", context: cm.PageContext(), items: [child, sp, child2] }) var menu2 = cm.Item({ label: "Welcome KMC", accesskey: '1', context: cm.URLContext("*.kmc.gr.jp"), contentScriptFile: './welcome-kmc.js' }); 選択された際に実行されるJS

var { Hotkey } = require("sdk/hotkeys"); !var myPanel = require("sdk/panel").Panel({ ... }); !var showHotKey = Hotkey({ combo: "accel-shift-o", onPress: function() { myPanel.show(); } });

hotkeys

hotkeys

alt: [alt]、Macでは [option] キー

meta: [Meta] ([#] キー)、Macでは [⌘]

accel: [ctrl]、Macでは[⌘]

shift, control, pageup, pagedown

notificationvar notifications = require("sdk/notifications"); !notifications.notify({ title: "Mailer", text: "Got new 2 mails!", onClick: function(){ console.log('clicked') }, iconURL: "./myIcon.png" });

page-mod

user.jsなどのように閲覧しているページ内にJSを埋め込む。

content script と呼ばれることが多い

page-modvar pageMod = require("sdk/page-mod"); !pageMod.PageMod({ include: "*", exclude: "*.kmc.gr.jp", contentScriptFile: [ "./jquery-1.7.min.js", "./my-script.js" ] });

page-mod

content script 内で port や postMessage を使う場合は window.self のメソッドとして利用できる

simple-prefs

設定画面にボタンなどを配置する package.json に記述しておく

"preferences": [{ "name": "somePreference", "title": "Some preference title", "description": "description text", "type": "string", "value": "this is the default string value" }]

simple-prefstype: 種別を指定(後述)

name: JS内で利用するための名前

   (propaty name として valid な必要がある)

title: 設定画面でラベルに使われる

description: 設定に関する説明

hidden: 非表示にする ( boolean )

value: デフォルト値

simple-prefs値を取り出す時は name を使う

"preferences": [{ "name": "somePreference", "type": "string", "value": "this is the default string value" }]var preferences = require("sdk/simple-prefs").prefs; // Get console.log(preferences.somePreference); // Update preferences.somePreference = "this is a new value";

package.json

main.js

type (1)bool: <input type='checkbox'> boolint: checkbox が表示されるが値はT/Fではなくて on off の値が利用される

"preferences": [{ "type": "boolint", "on": "1", // value for true must be string "off": "2", // value for false must be string "value": 1 }]

type (2)

integer: <input type='number'> string: <input type='text'> color: <input type='color'> file: <input type='file'> フルパスを得る

directory: ディレクトリのパスを得る

type (3)menulist: ドロップダウンリストが表示される

radio: <input type="radio">{ "type": "menulist", // or "radio" "options": [ { "value": "0", //must be string "label": "nona" }, { "value": "1", "label": "prime" } ] }

type (4)control: <button> {

"type": "control", "label": "Click me!", "name": "sayHello", "title": "Say Hello" }

var sp = require("sdk/simple-prefs"); sp.on("sayHello", function() { console.log("hello"); });

package.json

main.js

type (4)control: <button> {

"type": "control", "label": "Click me!", "name": "sayHello", "title": "Say Hello" }

var sp = require("sdk/simple-prefs"); sp.on("sayHello", function() { console.log("hello"); });

package.json

main.js

type (4)control: <button> {

"type": "control", "label": "Click me!", "name": "sayHello", "title": "Say Hello" }

var sp = require("sdk/simple-prefs"); sp.on("", function() { console.log("hello"); });

package.json

main.js

空にすると全てのcontrollの clickを受け取る

まとめズサッと一気に Add-on SDK の世界観を紹介しました。

サンプルコードも用意したので、MDNなどと合わせて

上手く活用して良いAdd-onを作って下さい。

Firefoxをより便利にして 最高のインターネットライフを 手に入れよう😍😍😍😍

Do you have any questions?🙉

おまけ※時間が余った時など用

野良アドオンについて

公式配布サイト以外に関する対応Chrome 2014/05 から Windows 向けの Stable と

Beta チャンネルのみ外部サイトからインストールした Extension は無効化

Firefox AMO以外で配布する場合は要署名

Nightly と Developer Edition は除外http://googledevjp.blogspot.jp/2014/03/chrome-chrome.html https://dev.mozilla.jp/2015/02/extension-signing-safer-experience/

Firefoxにおける影響

アドオンのテストを行う場合はDeveloper Edition、Nightly あるいはいずれかのノーブランド版で行う必要がある。 AMOで配布しない場合も一度署名のために AMO へ拡張機能ファイルをアップロードするという必須手順が導入される。

top related