Upload
shoot-morii
View
7.392
Download
0
Embed Size (px)
Citation preview
こんにちは😺
こんにちは😺
で、誰?
🍣 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 [email protected]: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
配布用の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 へ拡張機能ファイルをアップロードするという必須手順が導入される。