chrome extensionsの基本とデザインパターン
TRANSCRIPT
Chrome Extensionsの基本とデザインパターン
Chrome+HTML5 Developers Live Japan #1
Chrome Extension使ってますか?
Chrome Extensionで何ができるの?
Browser Actions
Page Actions
Context Menus
Desktop Notifications
Options Pages
Options Pages
APIsalarms, bookmarks, browserAction, browsingData, commands, contentSettings, contextMenus, cookies, debugger, declarativeWebRequest, devtools.network, devtools.inspectedWindow, devtools.panels, downloads, events, extension, fileBrowserHandler, fontSettings, history, i18n, idle, input.ime, management, omnibox, pageAction, pageCapture, permissions, privacy, proxy, pushMessaging, runtime, scriptBadge, storage, tabs, topSites, tts, ttsEngine, types, webNavigation, webRequest, webstore, windows
APIs
Chrome Extensionは何でできてるの?
Technologies
Web pages + JavaScript API
Structureマニフェストファイル(manifest.json)1つ以上の HTMLファイル
(Optional) 1つ以上の JavaScriptファイル(Optional) 必要となる他のファイル - 画像ファイルなど
これらを zipファイルにまとめて Chromeウェブストアにアップロードする
Minimum Extensionmanifest.json
{ "manifest_version": 2, "name": "Minimum Extension", "version": "0.0.1", "browser_action": { "default_popup": "popup.html" }}
Minimum Extensionpopup.html
<!DOCTYPE html><html> <head></head> <body> <div>Hello, Chrome extension!</div> </body></html>
Minimum Extension
Minimum Extension
Minimum Extension
Minimum Extension
Minimum Extension
オレ流Chrome Extensionデザインパターン
Structure
manifest.json analytics.json background.js
options.htmloptions.js
popup.htmlpopup.js
My design pattern for Chrome Extension
manifest.json
{ ... "version": "バージョン番号 (XX.XX.XX形式 )", "browser_action": { "default_icon": "./icon_**.png (**はサイズ )", "default_popup": "./popup.html", ... }, "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'", "background": { "scripts": [ "./jquery-min.js", "./background.js" ], "persistent": false, ... }, "options_page": "./options.html", "default_locale": "en", ... }
{ ... "version": "バージョン番号 (XX.XX.XX形式 )", "browser_action": { "default_icon": "./icon_**.png (**はサイズ )", "default_popup": "./popup.html", ... }, "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'", "background": { "scripts": [ "./jquery-min.js", "./background.js" ], "persist": false, ... }, "options_page": "./options.html", "default_locale": "en", ... }
Browser/Page actionのコンテンツは「 popup.html」というファイル名にする
{ ... "version": "バージョン番号 (XX.XX.XX形式 )", "browser_action": { "default_icon": "./icon_**.png (**はサイズ )", "default_popup": "./popup.html", ... }, "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'", "background": { "scripts": ["./jquery-min.js","./background.js" ], "persistent": false, ... }, "options_page": "./options.html", "default_locale": "en", ... }
Background Pageは必ず作成し「 background.js」というファイル名とする
軽い ExtensionとするためにEvent pageとする
{ ... "version": "バージョン番号 (XX.XX.XX形式 )", "browser_action": { "default_icon": "./icon_**.png (**はサイズ )", "default_popup": "./popup.html", ... }, "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'", "background": { "scripts": [ "./jquery-min.js", "./background.js" ], "persistent": false, ... }, "options_page": "./options.html", "default_locale": "en", ... }
設定ページが必要な場合は「 options.html」というファイル名で作成する
{ ... "version": "バージョン番号 (XX.XX.XX形式 )", "browser_action": { "default_icon": "./icon_**.png (**はサイズ )", "default_popup": "./popup.html", ... }, "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'", "background": { "scripts": [ "./jquery-min.js", "./background.js" ], "persistent": false, ... }, "options_page": "./options.html", "default_locale": "en", ... }
どんなに小さな Extensionでも国際化しておく最低限 enと jaをサポートする
{ ... "version": "バージョン番号 (XX.XX.XX形式 )", "browser_action": { "default_icon": "./icon_**.png (**はサイズ )", "default_popup": "./popup.html", ... }, "content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'", "background": { "scripts": [ "./jquery-min.js", "./background.js" ], "persistent": false, ... }, "options_page": "./options.html", "default_locale": "en", ... }
利用状況を把握するためにGoogle Analyticsを仕込んでおく
Google Analyticsサーバと通信するために CSPに記載しておく
Google Analyticsポリシー:
「 Google Analyticsを使っていることを開示し、トラッキングデータの収集のためにCookieが使われていることも明記する」
Chromeウェブストアのページや Extension内にてちゃんとユーザに説明しましょう。
My design pattern for Chrome Extension
background.js
popup.htmlpopup.js
options.htmloptions.js
background.js Backend server
UI処理に専念
UI処理に専念
Ajaxでの通信
設定値出し入れ
イベント処理
var Background = function() { this.assignEventHandlers();};Background.prototype = { assignEventHandlers: function() {...}, load***: function(callbacks) {...}, get***Config: function() {...}, set***Config: function() {...} ...};var bg = new Background();
var Background = function() { this.assignEventHandlers();};Background.prototype = { assignEventHandlers: function() {...}, load***: function(callbacks) {...}, get***Config: function() {...}, set***Config: function() {...} ...};var bg = new Background();
各種イベントハンドラを登録する処理を「 assignEventHandlers()」関数にまとめる
var Background = function() { this.assignEventHandlers();};Background.prototype = { assignEventHandlers: function() {...}, load***: function(callbacks) {...}, get***Config: function() {...}, set***Config: function() {...} ...};var bg = new Background();
assignEventHandlers: function() { // タブの変更監視 chrome.tab.onActivated.addListener( function(activeInfo) { ... } ); // Chromeの起動監視 chrome.runtime.onStartup.addListener( function() { // Google Analyticsに記録など _gaq.push(["_trackEvent", "..", ".."]); } ); ...},
var Background = function() { this.assignEventHandlers();};Background.prototype = { assignEventHandlers: function() {...}, load***: function(callbacks) {...}, get***Config: function() {...}, set***Config: function() {...} ...};var bg = new Background();
Ajax処理は「 load***()」という関数名にする
Ajax処理を background.jsに集めておく( Debugしやすくなるため)
var Background = function() { this.assignEventHandlers();};Background.prototype = { assignEventHandlers: function() {...}, load***: function(callbacks) {...}, get***Config: function() {...}, set***Config: function() {...} ...};var bg = new Background();
load***: function(callbacks) { $.ajax({ url: this.getServerUrl + "ajax/get_***" }) .done(function(data) { callbacks.onSuccess(data); });},getServerUrl: function() { return "http://backend.server.name/";},
var Background = function() { this.assignEventHandlers();};Background.prototype = { assignEventHandlers: function() {...}, load***: function(callbacks) {...}, get***Config: function() {...}, set***Config: function() {...} ...};var bg = new Background();設定ページで設定されるような動
的な設定値の格納と取得処理もbackground.jsにまとめておく
var Background = function() { this.assignEventHandlers();};Background.prototype = { assignEventHandlers: function() {...}, load***: function(callbacks) {...}, get***Config: function() {...}, set***Config: function() {...} ...};var bg = new Background();
get***Config: function() { var value = localStorage["***"]; if (value) { return value; } else { return "初期値 "; }},set***Config: function() { localStorage["***"] = value;},
popup.htmlpopup.js
options.htmloptions.js
background.js Backend server
chrome.runtime.getBackgroundPage()でアクセス可能
Chrome DevTools
My design pattern for Chrome Extension
options.html
Options Pages
<!DOCTYPE html><html> <head> <script type="text/javascript" src="./jquery-min.js"> <script type="text/javascript" src="./options.js"> </head> <body> <script type="text/javascript" src="./analytics.js"> ... <div><span id="opt***"></span></div> <table> <tr> <td> <input type="checkbox" id="***" /> </td> <td> <span id="opt***"></span> </td> </tr> ... </table> ...
<!DOCTYPE html><html> <head> <script type="text/javascript" src="./jquery-min.js"> <script type="text/javascript" src="./options.js"> </head> <body> <script type="text/javascript" src="./analytics.js"> ... <div><span id="opt***"></span></div> <table> <tr> <td> <input type="checkbox" id="***" /> </td> <td> <span id="opt***"></span> </td> </tr> ... </table> ...
Google Analyticsで設定ページへのアクセスを計測できるようにする
var _gaq = _gaq || [];_gaq.push(['_setAccount', 'UA-xxxxxx-xx']);_gaq.push(['_trackPageview']);(function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = 'https://ssl.google-analytics.com/ga.js'; var s = document.getElementByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();
<!DOCTYPE html><html> <head> <script type="text/javascript" src="./jquery-min.js"> <script type="text/javascript" src="./options.js"> </head> <body> <script type="text/javascript" src="./analytics.js"> ... <div><span id="opt***"></span></div> <table> <tr> <td> <input type="checkbox" id="***" /> </td> <td> <span id="opt***"></span> </td> </tr> ... </table> ...
表示文字列は htmlに一切記載しない
id属性はしっかり書いておく
My design pattern for Chrome Extension
options.js
(function() { var Options = function() { this.assignMessages(); this.assignEventHandlers(); this.restoreConfigurations(); }; Options.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, restoreConfigurations: function() { ... }, }; window.addEventListener( "load", function(evt) { new Options(); });})();
(function() { var Options = function() { this.assignMessages(); this.assignEventHandlers(); this.restoreConfigurations(); }; Options.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, restoreConfigurations: function() { ... }, }; window.addEventListener( "load", function(evt) { new Options(); });})();
onloadイベント発生に応じて初期化処理が動くようにしておく
(function() { var Options = function() { this.assignMessages(); this.assignEventHandlers(); this.restoreConfigurations(); }; Options.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, restoreConfigurations: function() { ... }, }; window.addEventListener( "load", function(evt) { new Options(); });})();
各種初期化処理を呼び出す他の jsと関数名を揃えておく
(function() { var Options = function() { this.assignMessages(); this.assignEventHandlers(); this.restoreConfigurations(); }); }; Options.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, restoreConfigurations: function() { ... }, }; window.addEventListener( "load", function(evt) { new Options(); });})();
assignMessages: function() { var hash = { "opt***" : "opt***", ... }; for (var key in hash) { $("#" + key).text( chrome.i18n.getMessage(hash[key])); }},
HTMLの各プレースホルダ (id属性を振った要素 )に対して、メッセージリソースから得た国際化文字列をセットする
(function() { var Options = function() { this.assignMessages(); this.assignEventHandlers(); this.restoreConfigurations(); }); }; Options.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, restoreConfigurations: function() { ... }, }; window.addEventListener( "load", function(evt) { new Options(); });})();
restoreConfigurations: function() { chrome.runtime.getBackgroundPage( function(bg) { $("#***").val(bg.get***Config()); ... } );}; Background pageの設定値取得
関数を使って、せっせと UIに値をセットしていく
(function() { var Options = function() { this.assignMessages(); this.assignEventHandlers(); this.restoreConfigurations(); }); }; Options.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, restoreConfigurations: function() { ... }, }; window.addEventListener( "load", function(evt) { new Options(); });})();
assignEventHandlers: function() { $("#***").on("click", $.proxy( function(evt) { this.onClick***(evt); }, this ));},onClick***: function(evt) { chrome.runtime.getBackgroundPage( function(bg) { var value = $("#***").val(); bg.set***Config(value); } );}, Background pageの設定値格納
関数を使って設定値を保存する
(function() { var Options = function() { this.assignMessages(); this.assignEventHandlers(); this.restoreConfigurations(); }); }; Options.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, restoreConfigurations: function() { ... }, }; window.addEventListener( "load", function(evt) { new Options(); });})();
assignEventHandlers: function() { $("#***").on("click", $.proxy( function(evt) { this.onClick***(evt); }, this ));},onClick***: function(evt) { chrome.runtime.getBackgroundPage( function(bg) { var value = $("#***").val(); bg.set***Config(value); } );},
イベントハンドラの名前は「 on+イベント種別 +UI項目名」とする
My design pattern for Chrome Extension
popup.html
<!DOCTYPE html><html> <head> <script type="text/javascript" src="./jquery-min.js"> <script type="text/javascript" src="./popup.js"> </head> <body> <script type="text/javascript" src="./analytics.js"> <span id="***"></span> ...
options.jsとほぼ一緒
My design pattern for Chrome Extension
popup.js
(function() { var Popup = function() { this.assignMessages(); this.assignEventHandlers(); }; Popup.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, ... }; window.addEventListener( "load", function(evt) { new Popup(); });})();
options.jsとほぼ一緒
(function() { var Popup = function() { this.assignMessages(); this.assignEventHandlers(); }; Popup.prototype = { assignMessages: function() { ... }, assignEventHandlers: function() { ... }, ... }; window.addEventListener( "load", function(evt) { new Popup(); });})();
assignEventHandlers: function() { $("#***").on("click", $.proxy(function(evt) { this.onClick***(evt); }, this));},onClick***: function(evt) { chrome.runtime.getBackgroundPage( function(bg) { // 設定値を取得 var config =bg.get***Config(); // Ajax通信 bg.load***({ onSuccess: function(data) { ... } }); } );},
Background pageが提供する関数を利用
My design patternロジックを整理&集中させる - Background pageをうまく使うこと
統一感を作り出す - assign***() , onClick***() , set/get***Config()
国際化を徹底させる - id属性 , assignMessages()
利用者が増えるのを楽しむには?
http://developer.chrome.com/extensions/