プログラミング」カテゴリーアーカイブ

導入から公開まで13時間!Chrome拡張機能の開発[6of6]

最初の記事に書いた苦労したポイントについて補足したいと思います。

苦労したポイント

  • 生のjavascriptではなく、jQueryを使えるようにするにはどうすればいい?
  • 対象ページの内容を変更する方法は?contentscriptによる方法とexecuteScriptがある?
  • backgroundを含む拡張部分とcontentscriptとはDOMを共有しないので変数や関数が互いに参照できない。その場合の通信方法はどうするの?
  • デバッグはどうすればいい?background.jsを含む拡張部分。contentscript部分。

生のjavascriptではなく、jQueryを使えるようにするにはどうすればいい?

これは「manifest.json」に明記すればよいです。例外として「popup.html」を見ていただければ分かるようにHTML中に記述しています。

対象ページの内容を変更する方法は?contentscriptによる方法とexecuteScriptがある?

「manifest.json」にcontentscriptとして宣言すれば「対象ページ」の内容を変更できます。

jQuery使いなら「contentscript.js」でもjavascriptそのものではなくjQueryを使いたいはず。その際は「manifest.json」にcontentscript宣言に「jQuery.js」等を追加してあげればよいです。「対象ページ」でjQueryが使われている場合でも版数の違いなどを気にすることなく宣言できるそうです。

バックグラウンドコードからexecuteScript関数を使って対象ページの内容を変更する方法もありますが、複雑な処理には向かないような印象でした。具体例については参照リンクを見ていただければと思います。

backgroundを含む拡張部分とcontentscriptとはDOMを共有しないので変数や関数が互いに参照できない。その場合の通信方法はどうするの?

リクエスト(メッセージ)のやりとりで実現します。拡張部分のコードとcontentscriptではリクエスト(メッセージ)のやりとりの際の引数が違います。

デバッグ方法はどうすればいい?background.jsを含む拡張部分とcontentscript部分

コンテントスクリプト部分については、対象ページを表示した状態でいつものデバッグツールを起動すればよいです。バックグラウンド部分では、Chromeの「設定」「拡張機能」画面にある「page_backgound.html」をクリックした状態でいつものデバッグツールを起動すればよいです。ポップアップ部分についてはHTMLにスナップショットを表示するdivタグを用意しておいてデバッグ内容を表示するようにすればよいような。

Chrome拡張は、作る楽しさ、使う楽しさが満載な仕組み

以上、これまで各コードについてひとつずつソースコードを提示してきましたが、各コード(ファイル)の役割は次のようになると思います。

  • background.js:ハブ的役割を持つコード。
  • popup.html, popup.js:利用者にユーザインタフェースを提供するコード。
  • contentscript.js:対象ページを処理するコード。
  • manifest.json:役割定義

Chrome拡張開発によりいろいろできそうに思いました。もちろん既に誰かが作成した便利なものを探して使うのが、もっともコストがかからない便利な方法だと思います。

でも、自分のニーズや悩みにぴったり来るものがない場合には、javascriptやHTMLなどをちょっとかじっていさえすれば、とても便利な、世界でひとつしかないブラウザ環境が手に入ります。

例えば、ページの文字が小さくてみえずらい場合には、ブラウザの機能を使って少しだけ大きな文字にすることもできますが、こうした拡張を作って好きなだけ拡大してみる。特定の英単語のみ拡大するような拡張機能も簡単に作成できます。

とにかくChrome拡張は、作る楽しさ、使う楽しさが満載な仕組みだと思います。

以上です。

関連する記事

導入から公開まで13時間!Chrome拡張機能の開発[1of6]
導入から公開まで13時間!Chrome拡張機能の開発[2of6]
導入から公開まで13時間!Chrome拡張機能の開発[3of6]
導入から公開まで13時間!Chrome拡張機能の開発[4of6]
導入から公開まで13時間!Chrome拡張機能の開発[5of6]

お世話になったリンク

Chrome Extensions API リファレンス
Sample Extensions – Google Chrome Extensions
ぷりんすの開発メモ: Chrome拡張機能のマニフェストVesrsion2対応についてのメモ
ChromeExtensionを作ってみる | ぼんぼるにっき
Chrome Extension でデバッグを行う – 日頃の行い
ハローワークの「求人情報検索」ページ

以上です。ありがとうございました。

導入から公開まで13時間!Chrome拡張機能の開発[4of6]

Chrome拡張のコードのうち、対象となるページの状態を直接読んだり変更したりできるものがコンテントスクリプトです。
だから「manifest.json」で特別に宣言するんでしょうね。

contentscript.jsのソースコード

/*
* コンテントスクリプト
*/

// パソコンに保存する情報を蓄えるオブジェクトを定義する
var condlist = {"cond":[]};

$(function(){

  var regex = /求人情報検索/;

  if (regex.test(document.body.innerText)) {
  // regexに設定した文字列が対象URL内のBodyタグ内に見つかれば、
  // バックグラウンドに通知する
    chrome.extension.sendRequest({}, function(response) {});
  } else {
  // 見つからない場合
  }


  // バックグラウンドやポップアップ等の拡張機能から
  // コンテントスクリプトへのリクエストをモニタして、
  // リクエストがきたら関数(onRequest)を実行する
  //
  // ポップアップの3種類のボタンをクリックしたとき
  // ポップアップからのリクエストを経由して呼ばれる
  chrome.extension.onRequest.addListener(onRequest);

});

function clearConditions(){
// ポップアップの「クリア」ボタンをクリックしたとき
// ポップアップからのリクエストを経由して呼ばれる
//
// パソコン内に保存していた情報を初期状態にする

  condlist = {"cond":[]};
  // condlistに溜まった情報を文字列化してパソコンに保存する。
  // これでパソコン内に保存していた情報が初期状態になる
  localStorage["condlist"] = JSON.stringify(condlist);
}

function saveConditions(){
// ポップアップの「書き出し」ボタンをクリックしたとき
// ポップアップからのリクエストを経由して呼ばれる
//
// URLページに入力された情報を一旦オブジェクトに集めてパソコン内に保存する

  // 処理前にcondlistを初期化(そうしないとどんどん溜まる)
  condlist = {"cond":[]};
  condcnt = 0;

  $("input").each(function(){
  // 「ラジオボタン」「チェックボックス」「テキスト(文字列や数字列)」
  // で設定する情報をcondlistに追加する
    str = $(this).attr("id");
    str_v = $(this).val();
    str_t = $(this).attr("type");
    if( typeof str != 'undefined' ){

	if(str_t == "radio"){
	// 「ラジオボタン」で設定する情報をcondlistに追加する
		if(typeof $(this).attr("checked") != 'undefined'){
			str_v = 1;
		}else{
			str_v = 0;
		}
		obj = {"id":str,"type":str_t,"val":str_v};
		condlist['cond'][condcnt++] = obj;

	}else if(str_t == "checkbox"){
	// 「チェックボックス」で設定する情報をcondlistに追加する
		if(typeof $(this).attr("checked") != 'undefined'){
			str_v = 1;
		}else{
			str_v = 0;
		}
		obj = {"id":str,"type":str_t,"val":str_v};
		condlist['cond'][condcnt++] = obj;

	}else if(str_t == "text"){
	// 「テキスト」で設定する情報をcondlistに追加する
		obj = {"id":str,"type":str_t,"val":str_v};
		condlist['cond'][condcnt++] = obj;

	}
    }
  });

  $("select").each(function(){
  // 「選択」で設定する情報をcondlistに追加する
    str = $(this).attr("id");
    str_v = $(this).val();
    str_t = "select";
    if( typeof str != 'undefined' ){
	obj = {"id":str,"type":str_t,"val":str_v};
        console.log("obj:"+obj);
	console.log("id="+str+" val:"+str_v+" type:"+str_t);
	condlist['cond'][condcnt++] = obj;
    }
  });

// condlistに溜まった情報を文字列化してパソコンに保存する
  localStorage["condlist"] = JSON.stringify(condlist);
}

function restoreConditions(){
// ポップアップの「読み込み」ボタンをクリックしたとき
// ポップアップからのリクエストを経由して呼ばれる
//
// パソコン内に保存されていた情報を、URLページの入力・選択フィールドに
// 設定する

  // パソコン内に保存されていた情報を、処理し易いようにオプジェクト
  // に変換してcondlistに設定する
  condlist = JSON.parse(localStorage["condlist"]);

  // まずラジオボタンやチェックボックスを全部オフしておく
  $('input').removeAttr("checked");

  // 入力・選択フィールドの種類に従い情報を設定する
  for(var i=0; i < condlist['cond'].length; i++){
    var cond = condlist['cond'][i];
    var itag = $("#"+cond.id);

    if(cond.type == "checkbox"){
      if(cond.val != 0) itag.attr("checked","checked");
    }else if(cond.type == "radio"){
      if(cond.val != 0) itag.attr("checked","checked");
    }else if(cond.type == "text"){
      itag.val(cond.val);
    }else if(cond.type == "select"){
      itag.val(cond.val);
    }
  }
}

function onRequest(request, sender, sendResponse) {
// ポップアップの3種類のボタンをクリックしたとき
// ポップアップからのリクエストを経由して呼ばれる
//
// パソコン内に保存されていた情報を、URLページの入力・選択フィールドに
// 設定する

  if(request.greeting == "restore"){
  // リクエストのメッセージ内容に従い処理を分ける
    restoreConditions();
  }else if(request.greeting == "save"){
  // リクエストのメッセージ内容に従い処理を分ける
    saveConditions();
  }else if(request.greeting == "clear"){
  // リクエストのメッセージ内容に従い処理を分ける
    clearConditions();
  }
  sendResponse({});
};

(つづく>>>)

関連する記事

導入から公開まで13時間!Chrome拡張機能の開発[1of6]
導入から公開まで13時間!Chrome拡張機能の開発[2of6]
導入から公開まで13時間!Chrome拡張機能の開発[3of6]
導入から公開まで13時間!Chrome拡張機能の開発[4of6]
導入から公開まで13時間!Chrome拡張機能の開発[5of6]
導入から公開まで13時間!Chrome拡張機能の開発[6of6]

お世話になったリンク

Chrome Extensions API リファレンス
Sample Extensions - Google Chrome Extensions
ぷりんすの開発メモ: Chrome拡張機能のマニフェストVesrsion2対応についてのメモ
ChromeExtensionを作ってみる | ぼんぼるにっき
Chrome Extension でデバッグを行う - 日頃の行い
ハローワークの「求人情報検索」ページ

導入から公開まで13時間!Chrome拡張機能の開発[3of6]

バックグラウンドというのは「背景」とか「裏」とかいう意味ですが、Chrome拡張におけるバックグラウンドというのは、拡張機能におけるコアとかメイン機能とかハブ機能を置く場所になるでしょうか。MVC(Model、View、Control)でいえばModel部分ですかね。Googleのコードサンプルを見るとこのバックグラウンドだけのサンプルもあり、必須のコードのようです。

本拡張機能においては主にリクエスト(メッセージ)のやりとりとアイコンをクリックしたときのイベントを掴むところに使っています。対象ページの読み書きおよびパソコンへの書き出し(保存)や読み込みはコンテントスクリプトに、UIはポップアップに任せています。

パソコンへの書き出しや読み込みは、本来このバックグラウンドで行い、コンテントスクリプトとはリクエスト(メッセージ)のやりとりで情報交換すべきだったのかも知れません。簡単な機能なので今回はオプションに対応しませんでしたが、例えばオプションで、パソコンへ保存した情報をリストアップさせて、「削除」「削除しないが対象外にする」の処理させる場合、やはりハブ的な場所に保存されている方が便利だからです。

backgroud.jsのソースコード

/*
* バックグラウンド
*/

$(function(){

// コンテントスクリプトからバックグラウンドへのリクエストをモニタして、
// リクエストがきたら関数(onRequest)を実行する
chrome.extension.onRequest.addListener(onRequest);

// オムニバー右端のアイコンをクリックをモニタして、クリックがあったら
// 関数(onSendRequest)を実行する
chrome.pageAction.onClicked.addListener(onSendRequest);

});

function onRequest(request, sender, sendResponse) {
//  console.log(sender.tab ?
//		"from a content script:" + sender.tab.url :
//		"from the extension");
  if(sender.tab){
    chrome.pageAction.show(sender.tab.id);
  }
  // Return nothing to let the connection be cleaned up.
  sendResponse({});
};

/*
* helloというメッセージを現在選択中のタブ(対象URLを表示しているタブ)に送信する
*/
function onSendRequest() {
  chrome.tabs.getSelected(null, function(tab) {
    chrome.tabs.sendRequest(tab.id, {greeting: "hello"}, function(response) {
//      console.log(response.farewell);
    });
  });
};

(つづく>>>)

関連する記事

導入から公開まで13時間!Chrome拡張機能の開発[1of6]
導入から公開まで13時間!Chrome拡張機能の開発[2of6]
導入から公開まで13時間!Chrome拡張機能の開発[3of6]
導入から公開まで13時間!Chrome拡張機能の開発[4of6]
導入から公開まで13時間!Chrome拡張機能の開発[5of6]
導入から公開まで13時間!Chrome拡張機能の開発[6of6]

お世話になったリンク

Chrome Extensions API リファレンス
Sample Extensions – Google Chrome Extensions
ぷりんすの開発メモ: Chrome拡張機能のマニフェストVesrsion2対応についてのメモ
ChromeExtensionを作ってみる | ぼんぼるにっき
Chrome Extension でデバッグを行う – 日頃の行い
ハローワークの「求人情報検索」ページ

導入から公開まで13時間!Chrome拡張機能の開発[2of6]

今回作成公開したChrome拡張のコード(ファイル)の構成を以下に示します。

Chrome拡張のコードの構成

HWISCooker
-	manifest.json
-	background.js
-	popup.js
-	popup.html
-	images
	-	HW_19.png
	-	HW_48.png
	-	HW_128.png
-	contentscript.js
-	jquery.js

となっています。

各ファイル(js, html, png)の役割はそれぞれのファイル名が示す通りですが、実際には「manifest.json」が全てを管理しており、役割とファイル名が関連付けられています。

Manifest.jsonのソースコード

{
  "name": "HWISCooker",
  "version": "1.0",
  "description": "Tired of putting your info. eternaly? Use HWISCooker!",
  "icons": {
    "19" : "images/HW_19.png",
    "48" : "images/HW_48.png",
    "128" : "images/HW_128.png"
  },
  "page_action": {
    "default_icon": "images/HW_19.png",
    "default_title": "HWISCooker",
    "default_popup": "popup.html"
  },
  "background": {
    "scripts": ["jquery.js", "background.js"]
  },
  "content_scripts": [ {
    "matches": ["https://www.hellowork.go.jp/servicef/130020.do*"],
    "js": ["jquery.js", "contentscript.js"],
    "run_at" : "document_idle",
    "all_frames": false
  }],
  "manifest_version": 2
}

上記構成で示したファイル名の内、「manifest.json」に明記されないのは「popup.js」だけですが、これはpopup.html内で参照されているからOKなのでしょう。また、jQuery等の補助スクリプトを使いたい場合には、「manifest.json」に明記するようです。

(つづく>>>)

関連する記事

導入から公開まで13時間!Chrome拡張機能の開発[1of6]
導入から公開まで13時間!Chrome拡張機能の開発[2of6]
導入から公開まで13時間!Chrome拡張機能の開発[3of6]
導入から公開まで13時間!Chrome拡張機能の開発[4of6]
導入から公開まで13時間!Chrome拡張機能の開発[5of6]
導入から公開まで13時間!Chrome拡張機能の開発[6of6]

お世話になったリンク

Chrome Extensions API リファレンス
Sample Extensions – Google Chrome Extensions
ぷりんすの開発メモ: Chrome拡張機能のマニフェストVesrsion2対応についてのメモ
ChromeExtensionを作ってみる | ぼんぼるにっき
Chrome Extension でデバッグを行う – 日頃の行い
ハローワークの「求人情報検索」ページ

(つづく)

結構簡単でした!?スマホでのGoogleAnalyticsのOpt-out

[追記しました@20131117]
[追記しました@20131019]

最近、ちょっとブックマークレットやChrome拡張にハマッてますが、ついでにひとつ便利ツール(?)を作成しましたので、ここで公開します。

ブログやサイトの運営者の悩みはここから始まる!?

全世界のブログやサイトの運営者は、最近のビッグデータの話題もあり、自分のブログやサイトの利用分析をしています。逆に利用分析をしていないブログやサイトは、利用者のメリットを最大化できる強力なデータを取得できるにもかかわらず有効活用していないということでしょうか。

ブログやサイトの利用分析の最も強力なもののひとつがグーグルのGoogleAnalytics(以下GA)です。GAはサイト訪問の頻度や利用者のブラウザ種類などをサイト運営者にレポートしてくれます。サイト運営者はそのレポートを元に利用者のメリットを最大化するための施策を打ちます。

自サイトへの運営者や運営スタッフのアクセスはカウントしたくない!

こういった利用目的があるので、サイト訪問の頻度などはできるだけ正確に知りたいですよね。そうすると自サイトへの運営者や運営スタッフのアクセスはできるだけ無視できるようにしたい。一方でサイト運営するためのメンテナンスは大変なもので、結構な頻度でアクセスするしかありません。

サイト管理者ならご存知の方が殆どだと推察しますが、PCに特殊な設定を施すだけでそのPCからサイトへのアクセス情報をGAが無視してくれます。例えば、GoogleAnalytics Opt-Outというプラグインがあります(ChromeならChrome拡張)。

スマホ対応のブラウザにはOpt-Outプラグインが適用できない!

ところが、iPhoneやAndroidといったスマホにプレインストールされている、もしくは購入後にインストールして使うブラウザには、こうしたプラグインが使えない場合が殆どです。実際私が主に使っているiOS用ChromeもChrome for mobileも例外ではなくOpt-Out拡張機能がスマホでは設定できません

これで何が困るかというと、スマホで自サイトにアクセスしてしまうとGAのアクセス対象になってしまい、それが分析結果に影響し、効果的な対策が打ちにくくなる、ということです。

そこで、スマホでもこうしたことへの解決策はないか調べ、コードを実装し、検証してみました

解決のヒントはGoogleのサイトに!

するとありました。まるで、コロンブスの卵ですね。

最初、上記の(Chrome拡張や)プラグインと同じようなOpt-Out用コードを準備しておき、AndroidのWebVIewを使って、ターゲットのサイトを呼び出す寸前にコード挿入してやる、なんてややこしいことを考えていましたが、実はもっと簡単に実現できることがわかりました。

ヒントは、
Tracking Basics (Asynchronous Syntax) – Google Analytics — Google Developers
にありました。

たったひとつのWindow変数をGAのコードの実行前に設定すればいい、というものです。

具体的には、

window['ga-disable-UA-123456-1'] = true;

を設定する。これだけです。

具体的には集計対象ページに挿入していた従来のGA用コードを

  var gaProperty = 'UA-123456-1';

  var disableStr = 'ga-disable-' + gaProperty;
  if (document.cookie.indexOf(disableStr + '=true') > -1) {
    window[disableStr] = true;
  }

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-123456-1']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

に書き換えます。

このコードの後半部分はサイト管理者であれば馴染み深いものだと思います。前半部分にある、window変数の設定が重要なのですが、ここにはサイトやブログのコードをいちいち変えずにcookieの設定だけでwindow変数の設定を有効にしたり無効にしたりするテクニックが仕込まれています。

有効/無効のコントロールコマンドはブックマークレットに登録

Opt-Outを有効にするブックマークレットの例

javascript:(function(){document.cookie=disableStr+'=true;expires=Tue,1-Jan-2030 00:00:00 UTC;path=/';window[disableStr]=true;})()

Opt-Outを無効にするブックマークレットの例

javascript:(function(){document.cookie=disableStr+'=true;expires=Fri,31-Dec-1999 23:59:59 UTC;path=/';window[disableStr]=false;})()

これらをスマホのブックマークバー等に登録しておけばスマホ毎に簡単に「有効/無効」を切り替えることができ、元々のサイトのコードには触る必要がありません。

これで思う存分スマホで自サイトをいろいろいじくることができます

[追記:20131019]

AndroidアプリのWebView内での実装には、Cookieを有効にして変数をセットしてあげる必要がありますね。

また、iPhoneやiPadなどiOS系のSafariブラウザでも本ブックマークレットにより制御は応用可能です。ただし、ブックマーク時に「ホーム画面に追加」で作成すると、同じURLにアクセスするものでもSafariブラウザとは異なるCookieになるようです。「ホーム画面に追加」されたアイコン起動の場合ブックマークレットが使えない(ような?)ので、ご注意ください。現在のところ解決策や回避策は不明です。

[追記:20131117]

上記ブックマークレットを使用する場合、GoogleのChromeブラウザの場合、PC版だとターゲットなるページを出しておき登録したブックマークレットを選択すれば済みますが、AndroidOS系やiOS系のスマホのChromeブラウザではそう簡単ではないようです。ターゲットなるページを出したまま登録したブックマークレットを選択する芸当が使えないのですね。その場合には、オムニバー(URLを指定するところ)に登録ブックマークレット名を入れるとopt-out用のブックマークが現れますのでそれを選択すればよいようです。

お世話になったリンク

Tracking Basics (Asynchronous Syntax) – Google Analytics — Google Developers

以上です。