Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Firefox Add-on SDK 入門

14,909 views

Published on

京大マイコンクラブ春合宿2015での発表資料です

Published in: Technology
  • Be the first to comment

Firefox Add-on SDK 入門

  1. 1. Firefox Add-on SDK入門 Pasta-K 📧pastak@kmc.gr.jp
  2. 2. こんにちは😺
  3. 3. こんにちは😺 で、誰?
  4. 4. 🍣 KMCでの活動 KMC 37代目 広報 OSC Kyoto 2014 出展 etc JavaScriptで世界平和2014
  5. 5. 🍣 インターネット活動 ! twitter.com/pastak id: Pasta-K pastak.hatenablog.com pastak-diary.hatenadiary.com " github.com/pastak
  6. 6. はてなインターン 2013 アルバイトエンジニア ←イマココ
  7. 7. 最近では、はてなのインターンを 経てウチへ来るというのが定番の ようになっているんです ! 休学して社員となった杉本氏をは じめ、現在5人いる学生アルバイ トのメンバーも、そのほとんどが 「京大マイコンクラブ」の所属。
  8. 8. アニメファン
  9. 9. 🐾目次 • Mozilla Firefox について • ブラウザー拡張について • Firefox Add-on SDKでアドオンを作ってみる
  10. 10. " サンプルコード https://github.com/pastak/ firefox-addon-sdk-sample
  11. 11. Mozilla Firefox について
  12. 12. About Mozilla Firefox https://www.mozilla.org/firefox/ Mozillaによって提供されているブラウザ Win / Mac / Linux / Android 現行最新バージョン 36.0.1 (2015/03/05) 次回37は2015/03/31にリリース予定 Netscapeがうにゃむにゃみたいな歴史に興味 がある人は自力でググッてください
  13. 13. ブラウザシェア (2015/02) http://news.mynavi.jp/news/2015/03/04/084/
  14. 14. ?
  15. 15. iceweasel
  16. 16. iceweasel Iceweasel(アイスウィーズル)とは、ウェブブラウザ。または その開発プロジェクト。Mozilla Firefox とほぼ同一のものであ るが、商標の関係により同プロジェクトから独立したもの。 Debian によるものと、GNU によるもの(GNU IceWeasel)が 存在したが、現在では GNU IceWeasel は GNU IceCat に名称 変更されている。 http://ja.wikipedia.org/wiki/Iceweasel
  17. 17. ブラウザ拡張 現状確認
  18. 18. Google Chrome Opera (Based Chromium)
  19. 19. Google Chrome / Opera JavaScript + HTML + CSS で書ける Chromium由来の共通のAPIを利用する →同じExtensionがそのまま動作する Chrome ExtensionをOperaにインストールするための拡張 https://addons.opera.com/ja/extensions/details/download- chrome-extension-9 ちなみにOpera側のドキュメントの方が少しだけ充実してる ! ExtensionとAppの2種類がある
  20. 20. Safari
  21. 21. Safari https://extensions.apple.com/ Chromiumと同じくHTML + JS + CSSで書ける 大体同じ世界観で書ける ドキュメント https://developer.apple.com/library/safari/ documentation/Tools/Conceptual/ SafariExtensionGuide/Introduction/Introduction.html !
  22. 22. Internet Explorer
  23. 23. Internet Explorer MSDNにチュートリアルなどのページがある。 (寧ろそれ以外どこにも載ってない) Browser Extensions Overviews and Tutorials https://msdn.microsoft.com/en-us/library/ aa753616%28v=vs.85%29.aspx C#で書くっぽい。 あんまり書いてる人見たこと無い気がする
  24. 24. Mozilla Firefox
  25. 25. 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で開発できるのは後者 このあと詳しく
  26. 26. Google Chrome VS Mozilla Firefox
  27. 27. 操作可能範囲
  28. 28. 表示領域 右上ポップアップ オムニバー
  29. 29. 表示領域 サイドバー ツールバー URLバー ポップアップ ステータスバー
  30. 30. 表示領域 サイドバー ツールバー URLバー ポップアップ ステータスバー Add-on SDKでステータスバーにボタンなど を追加するためのwidgetモジュールがFirefox 29で削除
  31. 31. 公開方法
  32. 32. https://chrome.google.com/webstore/
  33. 33. 無審査 開発者登録に$5
  34. 34. https://addons.mozilla.org/
  35. 35. 審査あり 開発者登録無料
  36. 36. 審査あり 開発者登録無料 人が確認するのではない セキュリティ上の問題などを自動確認
  37. 37. 💖
  38. 38. Firefox Add-on SDK について
  39. 39. 改めてFirefoxのアドオンについて XULベースの開発とAdd-on SDKでの開発 配布形式は xpi ( その正体はzip ) ! 開発に関しては困ったらとにかくMDNを見る https://developer.mozilla.org/ja/docs/Extensions
  40. 40. http://www.slideshare.net/skeevs/mozilla-firefox-extension-development
  41. 41. XULベースの開発について これ以降は特に触れない Firefoxの初期からあった伝統的な方 FirefoxアプリケーションのXULを上書きして機能を提供し たり、XPCOM コンポーネントを通じて操作をしたりす る。 ! より詳細な比較は https://developer.mozilla.org/en-US/ Add-ons/SDK/Guides/SDK_vs_XUL を
  42. 42. 2009年 Jetpackリリース アドオンとして提供開始 当時JSだけでFirefoxのアドオンが作れると話題に XULとか触らなくて良い JSとHTMLとCSSでなんとかなる jQueryも標準装備 Firebugでデバッグ出来る!
  43. 43. 大喜び
  44. 44. – https://developer.mozilla.org/ja/docs/Jetpack “Jetpack で、開発者は拡張機能を高速に 作り出すことができると同時に、強化さ れた体験を与えられたユーザーは、Web とのふれあいが変わるでしょう。”
  45. 45. それから 2010年 初頭 Jetpack(Prototype)から Jetpack Rebootへ アドオンからSDKへ移行 開発環境もPythonで書かれたコマンドラインツールに https://dev.mozilla.jp/2010/03/shifting-from-jetpack- ptototype-to-jetpack-reboot/ ! 2010年末名称を Add-on SDK へ名称変更 🎉
  46. 46. Add-on SDKについて FirefoxだけではなくThumderbird向けのアド オンも作れる 一部のAPIはAndroid向けのFirefoxにも対応 デバッグのためにUSBでインストール可能
  47. 47. Add-on SDKで扱えるAPI一覧
  48. 48. 座学 ここまで
  49. 49. ここから 実践編
  50. 50. とにかく 数が多い
  51. 51. スライドに書いて あること一覧
  52. 52. ボタンを出す バッジを操作する パネルを出す サイドバーを出す 通信する その他諸々
  53. 53. 🚀
  54. 54. 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
  55. 55. Add-on SDKのインストール homebrew以外の方法で入れた場合は ./bin/ をPATHに追加する と使えるようになる。 SDK仮想環境をシェル上で起動することが一 応推奨されているっぽい $ bash ./bin/activate $ cfx --version Add-on SDK 1.17(12f7d53e8b5…)
  56. 56. cfxコマンドの基本的な使い方 Add-onのスケルトンを生成 ! Add-onをFirefox上で実行 ! Add-onをxpiとして生成 ! テストを実行 $ cfx init $ cfx run $ cfx xpi $ cfx test
  57. 57. Add-on用のスケルトンを生成 ! ! ! ! ! この状態で      で実行可能 (ただし機能は何もない) $ cfx init . ├── data ├── lib │   └── main.js ├── package.json └── test └── test-main.js $ cfx run
  58. 58. $ 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!
  59. 59. これを実行すると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
  60. 60. デバッグについて JSで console.log すると標準出力に出る デバッガーも利用可能 about:addons で [デバッグ]ボタンを押す
  61. 61. 配布用の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
  62. 62. ファイルの紹介 . ├── data <- 静的htmlや画像ファイルなど │ アイコンなどもここに置く ├── lib │   └── main.js <- エントリーポイント │ になるJSファイル ├── package.json <- 設定などを書く └── test <- ここに置いておけば │ cfx test で実行してくれる └── test-main.js
  63. 63. package.json Add-onに関する情報を書いておくファイル。 $ cat package.json { "name": "sample", "title": "sample", "id": "jid1-ZaoDi2iCqJeZVg", "description": "a basic add-on", "author": "", "license": "MPL 2.0", "version": "0.1" }
  64. 64. package.json name: アドオンの名前を入れる。 ドット(.)やスペースを含むことが出来ない。 title や fullName があればそちらが利用される version: アドオンのバージョンを書く。 記法は npm などと同じくsemver に準拠する http://semver.org/
  65. 65. package.json permissions: クロスドメインXHRを許可するホスト名の指定 private browsing mode での許可 icon, icon64: アイコンのパスを指定する iconは48x48、icon64は64x64 無ければデフォルトアイコンを利用する
  66. 66. main.js を編集してみる 右上にボタンを追加してみる
  67. 67. 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/"); }
  68. 68. 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に含まれる
  69. 69. 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/"); } モジュールのパス
  70. 70. 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とか アイコンとか ハンドラを設定する。
  71. 71. 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以下を参照する
  72. 72. 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)
  73. 73. 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)
  74. 74. 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/"); } 省略可
  75. 75. 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/"); } クリック時に呼ばれる関数
  76. 76. 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/"); } 新しいタブを開く
  77. 77. badgeを出す New feature for Firefox 36
  78. 78. 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; }
  79. 79. 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; } 変更箇所
  80. 80. 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
  81. 81. 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; }
  82. 82. 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; }
  83. 83. 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の値を変更
  84. 84. ボタンを押したらパネルが出る
  85. 85. 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
  86. 86. 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
  87. 87. 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
  88. 88. 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
  89. 89. 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
  90. 90. 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
  91. 91. 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
  92. 92. パネル内のイベントを掴む パネル内のHTML上のボタンが押されたら badgeの内容をアップデートする
  93. 93. 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
  94. 94. Messaging(2) - postMessage messageイベントを発火させる context-menuモジュールなどはportがサポートさ れていないのでこちらを使うしかない panelなどには onMessage があるので、こっちを 使っておくと吉 https://developer.mozilla.org/en-US/Add-ons/SDK/Guides/Content_Scripts/using_postMessage
  95. 95. ! ! var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); }, onMessage: function(){ button.badge = button.badge + 1; } }); ! main.js(抜粋)
  96. 96. ! ! var panel = panels.Panel({ contentURL: "./panel.html", onHide: function(){ button.state('window', {checked: false}); }, onMessage: function(){ button.badge = button.badge + 1; } }); ! メッセージを受取ったら badgeを++ main.js(抜粋)
  97. 97. <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のメソッドになる
  98. 98. 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の色を変える
  99. 99. 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される
  100. 100. 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で挙動を切替
  101. 101. サイドバーを表示する
  102. 102. サイドバー Chrome Extensionだとこれが出来ない 扱い方の要領はpanelと同じ
  103. 103. sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html' }) main.js
  104. 104. sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html' }) main.js
  105. 105. main.js sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ worker.port.emit('ping', ''); } })
  106. 106. main.js sideBar = require("sdk/ui/sidebar").Sidebar({ id: 'sample-sidebar', title: 'MiracleSidebar', url: './sidebar.html', onAttach: function(worker){ worker.port.emit('ping', ''); } }) サイドバーが表示されたら messageを送る
  107. 107. <script> ! ! ! </script> addon.port.on('ping', function(){ alert('pong'); }) ./data/sidebar.html
  108. 108. <script> ! ! ! </script> addon.port.on('ping', function(){ alert('pong'); }) ./data/sidebar.html alertを出す
  109. 109. サイドバーでAjaxを実装する サイドバーからはCross-Originの制約でXHRなどで 外部に通信できない panelなどはJSをファイルに分けて、 contentScriptFileなどで読込めばXHR出来る →main.jsで代わりに通信して結果を  messageで送信する
  110. 110. package.jsonに追記 通信先のプロトコルとドメインを記述する Wildcardは使えないので注意 "permissions": { "cross-domain-content": [ "http://example.org/", "https://example.com/" ] }
  111. 111. 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
  112. 112. 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モジュールを利用
  113. 113. Request XMLHttpRequestをベースにしたオブジェクト ! option: url: 通信先のURL onComplete: 通信成功後に呼ぶ関数 content: GETやHEADのquery、POSTやPUTのbody contentType: request headerのContent-Typeの内容 header: request header require("sdk/request").Request(option)
  114. 114. Request methods 例: GET ! get() head() post() put() delete() require("sdk/request").Request(option).get()
  115. 115. 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に送る
  116. 116. 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オブジェクト
  117. 117. <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
  118. 118. <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として出力
  119. 119. 諸々モジュール紹介 clipboard, context-menu, hotkeys, notification, page-mod, simple-prefs …
  120. 120. 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");
  121. 121. context-menu コンテキストメニューを表示する Chromeと違って細やかに設定できる
  122. 122. 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' });
  123. 123. 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' }); メニューアイテム を生成
  124. 124. 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' }); 表示するテキスト
  125. 125. 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' }); アイコンを出すことも 出来る
  126. 126. 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' }); 表示するコンテキストを 指定する
  127. 127. 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' }); こういう指定も出来る
  128. 128. 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を返す場合に メニューアイテムを表示する
  129. 129. 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を返す場合に メニューアイテムを表示する
  130. 130. 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属性
  131. 131. 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を 子に持つことが出来る
  132. 132. 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' }); ページ上のあらゆる箇所
  133. 133. 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を 子メニューとして持つ
  134. 134. 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' }); セパレータを生成
  135. 135. 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' }); 親メニューの無いアイテム
  136. 136. 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' }); メニューを表示している状態で このキーを押すと選択したことになる。 キーの組み合わせ不可
  137. 137. 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でマッチさせる
  138. 138. 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
  139. 139. var { Hotkey } = require("sdk/hotkeys"); ! var myPanel = require("sdk/panel").Panel({ ... }); ! var showHotKey = Hotkey({ combo: "accel-shift-o", onPress: function() { myPanel.show(); } }); hotkeys
  140. 140. hotkeys alt: [alt]、Macでは [option] キー meta: [Meta] ([#] キー)、Macでは [⌘] accel: [ctrl]、Macでは[⌘] shift, control, pageup, pagedown
  141. 141. notification var notifications = require("sdk/notifications"); ! notifications.notify({ title: "Mailer", text: "Got new 2 mails!", onClick: function(){ console.log('clicked') }, iconURL: "./myIcon.png" });
  142. 142. page-mod user.jsなどのように閲覧しているページ内に JSを埋め込む。 content script と呼ばれることが多い
  143. 143. page-mod var pageMod = require("sdk/page-mod"); ! pageMod.PageMod({ include: "*", exclude: "*.kmc.gr.jp", contentScriptFile: [ "./jquery-1.7.min.js", "./my-script.js" ] });
  144. 144. page-mod content script 内で port や postMessage を使 う場合は window.self のメソッドとして利用 できる
  145. 145. simple-prefs 設定画面にボタンなどを配置する package.json に記述しておく "preferences": [{ "name": "somePreference", "title": "Some preference title", "description": "description text", "type": "string", "value": "this is the default string value" }]
  146. 146. simple-prefs type: 種別を指定(後述) name: JS内で利用するための名前    (propaty name として valid な必要がある) title: 設定画面でラベルに使われる description: 設定に関する説明 hidden: 非表示にする ( boolean ) value: デフォルト値
  147. 147. 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
  148. 148. 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 }]
  149. 149. type (2) integer: <input type='number'> string: <input type='text'> color: <input type='color'> file: <input type='file'> フルパスを得る directory: ディレクトリのパスを得る
  150. 150. type (3) menulist: ドロップダウンリストが表示される radio: <input type="radio"> { "type": "menulist", // or "radio" "options": [ { "value": "0", //must be string "label": "nona" }, { "value": "1", "label": "prime" } ] }
  151. 151. 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
  152. 152. 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
  153. 153. 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を受け取る
  154. 154. まとめ ズサッと一気に Add-on SDK の世界観を紹介しました。 サンプルコードも用意したので、MDNなどと合わせて 上手く活用して良いAdd-onを作って下さい。 Firefoxをより便利にして 最高のインターネットライフを 手に入れよう😍😍😍😍
  155. 155. Do you have any questions?🙉
  156. 156. おまけ ※時間が余った時など用
  157. 157. 野良アドオン について
  158. 158. 公式配布サイト以外に関する対応 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/
  159. 159. Firefoxにおける影響 アドオンのテストを行う場合はDeveloper Edition、 Nightly あるいはいずれかのノーブランド版で行う必 要がある。 AMOで配布しない場合も一度署名のために AMO へ 拡張機能ファイルをアップロードするという必須手 順が導入される。

×