グーグルアドオン開発 backgroundの作成(event_page化)

マニフェストのbackgroundに設定することで、バックグラウンド処理が機能する。
スクリプトは複数登録することができる。

ここのスクリプトにjQueryを入れているアドオンをよく見るけどどうだろうか?
開発コストを下げるためには必要だろうが、個人的にかっこ悪い気がする。(容量も気になる)

persistentオプションでスクリプトの永続化が設定できる。
永続化Trueで常にbackgroundのスクリプトをメモリに乗せる。
永続化False(イベントページ化)で必要な時のみbackgroundのスクリプトをメモリに乗せる。(Google推奨)
このオプションで少し戸惑ったが、アドオンの性質で
ON/OFFを切り替えたほうがいいだろう。

※メモリに乗るかどうかが変わるだけで、background.jsの処理が流れる回数は変わらない。
(persistentのON/OFFでプログラムの機能は変わらない)
ただ、特定の条件時にメモリを使用し、それ以外はスワップするだけ。

問題はこの特定の条件が大切。
0.初期開始時、まぁこれは無難(ブラウザ再開時、ホームは適応されないかも?)
1.ページからgetBackgroundPageが呼ばれた場合、
そのページ(Option、Popup等)が閉じるまでメモリに乗る。
2.メッセージが送付された場合、リスナーイベントが終了するまで。
3.ポートが確立された場合、ポートがクローズするまで。
(0~3は微妙にメモリ解放までにラグが有る。)

以上の点を踏まえて、処理能率の高いプログラムを作るための注意点。
1.アドオン機能のページ(OptionやPopup)にへgetBackgroundPageの利用タイミングに注意。
よくアドオンのソースを覗いていると画面の初期表示時や共通処理にグローバル変数にgetBackgroundPageの値を格納し、
処理のどこでもバックグラウンドの処理を呼び出せるようにしていることがある。
場合によりナンセンスなので注意。
2.妥当
3.ポート確立してる間はbackgroundがメモリに乗る、content_scriptからポートコネクトする場合は
タイミングを見計らったほうがいい。
コンテンツを開くたびに必ずポート確立していると、せっかくのpersistent(永続化OFF)が無駄になる。

■補足
アドオンの機能(background)が頻繁に呼び出されるのであれば、故意に永続化する価値はある。

	

グーグルアドオン開発 オプション画面の作成

マニフェストのオプションページに設定することで、拡張機能のオプション機能が活性化する。

作成するのは、オプションページのHTMLと処理のJS。
オプションの情報を保存するには、ストレージ機能を利用。(マニフェストに権限を追加)


	
	

chrome.storage.sync.set({ data1 : "test1", data2 : "test2" }
	, function() {
		console.debug("保存");
});

chrome.storage.sync.get(["data1", "data2"]
	, function(result) {
	console.debug("読み込み", result);
});
{
    "manifest_version": 2,
    "name": "Google Addon Devel",
    "description": "Develop",
    "version": "1",
    "icons": {
        "32": "icon.png"
    },
    "permissions": ["storage"],
    "options_page": "options.html"
}

データの保存はchrome.storage.sync.setメソッド
第一引数に保存するデータオブジェクト(Key、Value)
第二引数に保存後のコールバック関数
※コールバック関数に引数は無い。

データの読み込みはchrome.storage.sync.getメソッド
第一引数に読み込むデータのKey配列(またはKey文字列)
第二引数に保存後のコールバック関数
コールバック関数の第一引数に読み込んだストレージのオブジェクト変数が渡される。
※オブジェクトはKey、Valueの形で渡される。
※ストレージには文字列だけでなくオブジェクト形式で保存できる。

■storage.syncとstorage.localの違い。
ローカルはブラウザ単体別に保存される。
シンクはログイン登録されているブラウザで同期される。
「※機密ユーザー情報は保存しないでください!記憶域は暗号化されていません。」とのこと。

グーグルアドオン開発 content_scripts.jsからクロームアドオン処理呼び出し(page_action/browser_action)


※やりたいこと
Webページで範囲選択時にクロームアドオンのページアクションを活性化させる。

※大事なこと
content_scriptsについて。
コンテントスクリプトはマッチしたWebページにscriptファイルを追加読込させる。
そのためそのスクリプト内ではもとのWebページのスクリプトのスコープ(windowとかも)を参照可能。
コンテントスクリプト内でchromeオブジェクトは以下の処理が可能(chrome.runtime.idが取得できるよ。)
・i18n
・storage
・runtime:
connect
getManifest
getURL
id
onConnect
onMessage
sendMessage
ここのruntimeにgetBackgorundが無いのがすべての戦犯。(並列に呼ばれる場合があるから取れないんだろうね)
chrome変数の中身はアドオン毎にフォーカスがあるもよう。※別物になるよう制御されてるよ。
※なのでDevelConsoleのTopからchromeを直接参照すると最後に呼び出されたアドオンのchrome変数が参照されるので注意(1敗した)
※フォーカスをExtensionに変えること!

コンテントスクリプトからアドオン処理を呼び出すにはCHROME開発ドキュメントを参照した。


ケース1.メッセージの送信

コンテントページからバックグラウンドに対して単一的に呼び出す場合はメッセージ送信を利用。
コンテント側メソッド、chrome.runtime.sendMessage
第一引数、アドオンID
第二引数、送信するメッセージ(オブジェクトまたは変数)
第三引数、コールバック(レスポンスを受け取れる)

document.addEventListener("click", function (){
  //クリックイベントからセレクションが対応されるまでラグが有るので
  //トリガー式にして回避
  window.setTimeout(function (){
    var str = document.getSelection().toString();
    var flag = str.length > 0;

    chrome.runtime.sendMessage(chrome.runtime.id,
      {pageAction: flag},
      function(response) {
        //console.log(response);
    });
  });
});

バックグラウンド側メソッド、chrome.runtime.onMessage.addListener
第一引数、リスナー関数
リスナー関数の第一引数、受信メッセージ(sendMessageの第二引数)
リスナー関数の第二引数、送信元の情報(コンテントページの情報)
リスナー関数の第三引数、返答関数(呼び出すことでsendMessageのresuponseに渡される)

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    //console.log(request, sender, sendResponse);
    if(request.pageAction == true)
      chrome.pageAction.show(sender.tab.id/*,CallBack*/);
    else if(request.pageAction == false)
      chrome.pageAction.hide(sender.tab.id/*,CallBack*/);
  }
);

※ページアクションを活性化させたいのでsender変数から呼び出し元のTabIDを割り出しページアクションメソッドを実行することで解決。
■補足
メッセージリスナーは複数登録していくとかなり厄介になる。
今回はrequest.pageActionの判定をTrueとFalseにし、undefinedの場合は何もしないようにして回避。
content_scripts側のonMessageのキックの仕方が不明。できないんじゃないか疑惑。
backgroudからcontent_scriptsをキックする場合はケース2のコネクション確立を用いる。
ブラウザアクションの場合はchrome.browserAction.disableを利用。


ケース2.コネクションの確立

コンテントページからバックグラウンドに対して複数回に渡り呼び出したり、
バックグラウンドからコンテントページを呼び出す場合(双方向通信)はコネクションポートを利用。

■2.1 バックグラウンド側からポート確立

//バックグラウンド側からポート作成
chrome.runtime.onConnect.addListener(function(port) {
	//受信イベントに処理を追加
	port.onMessage.addListener(function(msg) {
		//受信したメッセージを表示
		console.debug(this, msg);
		alert(msg);
	});
});
var ports = {};
//ページ表示時、ページアクションを活性化(開発)
chrome.tabs.onUpdated.addListener(function(tabId) {
	chrome.pageAction.show(tabId);
	var port = chrome.tabs.connect(tabId, {name : "connect1"});

});
//ページアクション押下イベントに処理を追加
chrome.pageAction.onClicked.addListener(function (tab){
	var port = chrome.tabs.connect(tab.id, {name : "connect1"});
	port.postMessage("Push PageAction");
});

※バックグラウンド側からであれば同名ポートをコネクトすれば同一のポートが開ける(利点)

■2.2 コンテント側からポート確立(できないこともない おすすめしない)

//コンテント側からポート作成
var port1 = chrome.runtime.connect(
		chrome.runtime.id
		,{name : "test"});
//受信イベントに処理を追加
port1.onMessage.addListener(function(msg) {
	//受信したメッセージを表示
	console.debug(this, msg);
	alert(msg);
});
//メッセージを送信
//port1.postMessage("test");
var ports = {};
//ページ表示時、ページアクションを活性化(開発)
chrome.tabs.onUpdated.addListener(function(tabId) {
	chrome.pageAction.show(tabId);
});
//コネクトイベントに処理を追加
chrome.runtime.onConnect.addListener(function(port) {
	ports[port.sender.tab.id] = port;

	//コネクション破棄イベントに処理を追加
	port.onDisconnect.addListener(function (port){
		//コネクション破棄時処理
		//ページ遷移やタブ削除でも呼ばれる
		delete ports[port.sender.tab.id];
		console.debug("コネクションが破棄されました")
	});

	//メッセージ受信イベントに処理を追加
	port.onMessage.addListener(function(msg) {
		//コネクション受信時処理
		console.debug("メッセージを受信しました", msg);
	});
});
//ページアクション押下イベントに処理を追加
chrome.pageAction.onClicked.addListener(function (tab){
	if(ports[tab.id]){
		var port = ports[tab.id];
		port.postMessage("Push PageAction")
	}
});

ポートの中身のpostMessage関数で送信、onMessageイベントで受信制御で相互通信ができる。
問題はbackground側でポートをどうやって保存しておくか。
※Exsampleには結局 コネクション開始時に返答するしかサンプルがない。戦犯!
とりあえずports変数で制御。(今回はコネクションが1種だが、複数の場合はポート名も制御すること)
どうせバックグラウンドからキックするならバックグラウンド側からポート作成したほうが楽そう。(2.1)
※注意 コンテンツ側からポート確立する場合、同名であっても別ポートが開かれる(かも)
(Runtimeがポートを管理するためだと思う)