前回は、
Google I/OのChrome Web Store関連トピック
- アプリ内課金
- Google I/
Oにて、 Chrome Web Storeでのアプリ内課金について発表されました。アプリ内課金の手数料は5%とのことで、 ほかのプラットフォームと比べて非常に低価格になっています。これによって、 Webアプリの中から電子書籍や音楽などのコンテンツを購入することができるようになります。 - 日本語化と各国言語へのローカライズ
- Chrome Web Storeの日本語化がおこなわれ、
日本語を含む41の言語にローカライズされました。これによって、 Chromeの全ユーザーである1億6000万人がChrome Web Storeへアクセス可能になったとのことです。 - Chromebook
- Chrome OS搭載のノートブックであるChromebookが米国で6月に販売開始となるとのことです。Chrome OS上で動作するアプリは、
基本的にはChrome Web StoreからWebアプリとしてダウンロードされるものになります。
Chrome Web Storeとそれを取り巻く環境はますます大きく広がっています。日本では、

追加機能の概要
ここからは、
Webアプリの構成
Webアプリを構成するファイルにbackground.

{
"name": "Odometer",
"description": "距離計",
"version": "0.2",
"app": {
"launch": {
"local_path": "main.html"
}
},
"icons": {
"16": "icon_16.png",
"48": "icon_48.png",
"128": "icon_128.png"
},
"background_page": "background.html",
"permissions": [
"geolocation",
"notifications",
“background”
]
}
“background_

Background Pages
Background Pagesは、
バックグラウンドで動作させるHTMLファイルはマニフェストファイルで指定します。ここで指定したバックグラウンドページは、
Hosted Appsの場合、
バックグラウンド動作させる処理
バックグラウンドページはHTMLファイルですので、
今回のOdometerへの機能追加では、
if (navigator.geolocation) {
//現在地の位置情報取得
navigator.geolocation.getCurrentPosition(
init, //成功時コールバック
onError, //失敗時コールバック
geoOptions //オプション
);
} else {
return;
}
/*
* 初期表示
*/
function init(position){
//地図作成
createMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
//現在地を保持
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
}
/*
* 初期表示
*/
function init(position){
//地図作成
createMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script src="./js/background.js"></script>
</head>
<body>
</body>
</html>
/*
* 現在地を取得する
*/
function getPosition(sendResponse){
//現在地の位置情報取得
navigator.geolocation.getCurrentPosition(
//成功
function(position){
//現在地を保持
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
//現在地を返す
sendResponse({
type: "position",
position: position
});
},
//失敗
function(e){
sendResponse({
type: "error",
error: e
});
},
//オプション
geoOptions
);
}
引数やコールバックに若干見慣れない記述がありますが、
バックグラウンドページとの通信
Webアプリをフロント側のページとバックグラウンド側のページに分けたことによって、
//バックグラウンドページのWindowオブジェクトを取得
var bp = chrome.extension.getBackgroundPage();
//位置情報を取得
bp.getPosition(function(data){
//地図作成と現在地情報表示
init(data.position);
});
逆に、
//位置情報を取得する処理など
//WebアプリのタブページのWindowオブジェクトをすべて取得
var views = chrome.extension.getViews();
for ( var i = 0, len = views.length; i < len; i++ ) {
//地図作成と現在地情報を表示
views[i].init(position);
}
ただし、
メソッド | 説明 |
---|---|
getBackgroundPage() | バックグラウンドページのWindowオブジェクトを取得する |
getViews() | WebアプリのタブページのWindowsオブジェクトをすべて取得する |
Message Passing
Message Passingとは、
Odometerでは、
メッセージの送受信
タブページからバックグラウンドページへの送受信
単純なメッセージの送受信を記述してみます。タブページ側からバックグラウンドページへ送信する場合、
/*
* Background Pageへ現在地の取得をリクエスト
*/
chrome.extension.sendRequest({ action: 'get_position' }, function(response) {
if ( response.type ) {
if ( response.type == 'position' ) {
init(response.position);
} else if ( response.type == 'error' ) {
onError(response.error);
}
} else {
alert('予期せぬエラーです');
}
});
Odometerでは、
/*
* メッセージを受信し、各処理へ振り分ける
*/
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
switch ( request.action ) {
case 'get_position':
getPosition(sendResponse);
break;
case 'set_destination':
setDistination(request.lat, request.lng, sendResponse);
break;
case 'start_watch_position':
startWatchPosition(sender, sendResponse);
break;
case 'stop_watch_position':
stopWatchPosition(sendResponse);
break;
default:
sendResponse({});
break;
}
}
);
/*
* 現在地を取得する
*/
function getPosition(sendResponse){
//現在地の位置情報取得
navigator.geolocation.getCurrentPosition(
//成功
function(position){
//現在地を保持
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
//現在地を返す
sendResponse({
type: "position",
position: position
});
},
//失敗
function(e){
sendResponse({
type: "error",
error: e
});
},
//オプション
geoOptions
);
}
バックグラウンドページでメッセージを受信するには、
バックグラウンドページからタブページへの送受信
逆にバックグラウンドページからタブページ側へメッセージを送信する場合には、
//更新情報を送る
chrome.tabs.sendRequest(
sender.tab.id,
{
action: 'refresh',
position: position,
distance: distance
},
function(response) {}
);
ここで指定しているsender.
/*
* Background Pageから現在地の自動更新があった場合に、再描画する
*/
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
if ( request.action && request.action == 'refresh' ) {
//地図更新
updateMap(request.position.coords.latitude, request.position.coords.longitude);
//現在地情報表示
showCurrentPosition(request.position);
//目的地までの距離表示
showDistance(request.distance);
}
sendResponse({});
}
);
受け取り側のイベントは、
これで、
メソッド/ | 説明 |
---|---|
sendRequest(request, responseCallback) | バックグラウンドページへメッセージを送信する |
onRequest | メッセージの受信イベント。addListenerメソッドでコールバックを登録する |
メソッド/ | 説明 |
---|---|
sendRequest(tabId, request, responseCallback) | タブページへメッセージを送信する |
メソッド/ | 説明 |
---|---|
id | WebアプリのID |
tab | タブオブジェクト |
メッセージチャンネルの設定
Odometerでは実装していませんが、
また、
//チャンネル設定の要求
var port = chrome.extension.connect({name: "geolocation"});
//メッセージを送信
port.postMessage({acton: "get_position"});
//メッセージを受信
port.onMessage.addListener(function(message) {
//受信したメッセージを処理
});
//チャンネル設定の受付
chrome.extension.onConnect.addListener(function(port) {
//メッセージを受信
port.onMessage.addListener(function(msg) {
//受信したメッセージを処理
});
});
メソッド/ | 説明 |
---|---|
connect (connectInfo) | チャンネルを設定する |
onConnect | チャンネルの設定イベント。addListenerメソッドでコールバックを登録する |
メソッド/ | 説明 |
---|---|
connect(tabId, connectInfo) | チャンネルを設定する |
メソッド/ | 説明 |
---|---|
name | 名前 |
onDisconnect | チャンネルの切断イベント |
onMessage | メッセージの受信イベント |
postMessage(message) | メッセージの送信 |
sender | 送信者オブジェクト |
まとめ
今回は、
- ※
- crxファイルはzipファイルですので、
右クリックからのダウンロード後に拡張子をzipに変えていただければ中身を参照できます。
また、
参考
document.addEventListener('DOMContentLoaded', function(){
//新しく立ち上げた際に前回の自動更新を停止する(リカバリは未対応)
chrome.extension.sendRequest({ action: 'stop_watch_position' }, function(response) {});
/*
* Background Pageへ現在地の取得をリクエスト
*/
chrome.extension.sendRequest({ action: 'get_position' }, function(response) {
if ( response.type ) {
if ( response.type == 'position' ) {
init(response.position);
} else if ( response.type == 'error' ) {
onError(response.error);
}
} else {
alert('予期せぬエラーです');
}
});
/*
* 初期表示
*/
function init(position){
//地図作成
createMap(position.coords.latitude, position.coords.longitude);
//現在地情報表示
showCurrentPosition(position);
}
/*
* エラーコールバック
*/
function onError(e) {
alert(e.message + '(' + e.code + ')');
}
/*
* 現在地情報表示
*/
function showCurrentPosition(position){
//現在地を表示
//緯度
document.getElementById('latitude').textContent =
position.coords.latitude;
//経度
document.getElementById('longitude').textContent =
position.coords.longitude;
//精度
document.getElementById('accuracy').textContent =
position.coords.accuracy;
//移動方向
document.getElementById('heading').textContent =
position.coords.heading;
//移動速度
document.getElementById('speed').textContent =
position.coords.speed;
//取得日時
var dt = new Date(position.timestamp);
document.getElementById('timestamp').textContent =
dt.getFullYear() + '年' + (dt.getMonth()+1) + '月' + dt.getDate() + '日' +
dt.getHours() + '時' + dt.getMinutes() + '分' + dt.getSeconds() + '秒';
}
/*
* 目的地情報表示
*/
function showDestinationPosition(lat, lng) {
//目的地を表示
document.getElementById('dest-latitude').textContent = lat;
document.getElementById('dest-longitude').textContent = lng;
}
/*
* 目的地までの距離表示
*/
function showDistance(distance){
//単位をkmに変換して表示
document.getElementById('distance').textContent = distance;
}
/*
* Background Pageへ自動更新の開始/終了をリクエスト
*/
document.getElementById('auto-update').addEventListener('click', function(){
var autoUpdateButton = this;
if ( autoUpdateButton.value == '自動更新開始') {
chrome.extension.sendRequest({ action: 'start_watch_position' }, function(response){
//目的地が設定されていない場合、メッセージが返る
if ( response.type && response.type == 'message') {
alert(response.message);
} else {
autoUpdateButton.value = '自動更新停止';
}
});
} else {
chrome.extension.sendRequest({ action: 'stop_watch_position' }, function(response){
autoUpdateButton.value = '自動更新開始';
});
}
});
/*
* Background Pageから現在地の自動更新があった場合に、再描画する
*/
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
if ( request.action && request.action == 'refresh' ) {
//地図更新
updateMap(request.position.coords.latitude, request.position.coords.longitude);
//現在地情報表示
showCurrentPosition(request.position);
//目的地までの距離表示
showDistance(request.distance);
}
sendResponse({});
}
);
/*
* Google Maps
*/
var map,
marker;
var mapOptions = {
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
/*
* 地図作成
*/
function createMap(lat, lng) {
//地図作成
var infowindow = new google.maps.InfoWindow(),
latLng = new google.maps.LatLng(lat, lng);
map = new google.maps.Map(document.getElementById("map"), mapOptions);
//マーカー作成
marker = new google.maps.Marker(
{
title: '現在地',
position: latLng,
map: map
}
);
/*
* クリックで目的地設定
*/
var destMarker = null;
google.maps.event.addListener(map, "click", function(event){
if ( destMarker ) {
destMarker.setMap(null);
}
destMarker = new google.maps.Marker(
{
title: '目的地',
position: event.latLng,
map: map
}
);
//目的地情報表示
showDestinationPosition(event.latLng.lat(), event.latLng.lng());
/*
* Background Pageで目的地を設定
*/
chrome.extension.sendRequest(
//リクエストデータ
{
action: 'set_destination',
lat: event.latLng.lat(),
lng: event.latLng.lng()
},
//レスポンスコールバック
function(response) {
if ( response.type && response.type == 'distance' ) {
//目的地までの距離を表示
showDistance(response.distance);
} else {
alert('目的地の設定に失敗しました');
}
}
);
});
map.setCenter(latLng);
infowindow.open(map);
}
/*
* 地図更新
*/
function updateMap(lat, lng) {
var latLng = new google.maps.LatLng(lat, lng);
//マーカー作成
if ( marker ) {
marker.setMap(null);
}
marker = new google.maps.Marker(
{
title: '現在地',
position: latLng,
map: map
}
);
map.setCenter(latLng);
}
/*
* 住所検索
*/
var geocoder = new google.maps.Geocoder();
document.getElementById('search').addEventListener('submit', function(event){
//デフォルトのsubmit動作をキャンセル
event.preventDefault();
var addr = document.getElementById('address').value;
if ( !addr ) {
return;
}
//Geocoding APIで住所から座標を取得する
geocoder.geocode({ 'address': addr}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
//最初の候補を表示する
map.setCenter(results[0].geometry.location);
} else {
alert('検索できませんでした');
}
});
}, false);
/*
* 目的地表示
*/
document.getElementById('move-dest').addEventListener('click', function(){
map.setCenter(new google.maps.LatLng(destPos.lat, destPos.lng));
}, false);
/*
* 現在地表示
*/
document.getElementById('move-current').addEventListener('click', function(){
map.setCenter(new google.maps.LatLng(currentPos.lat, currentPos.lng));
}, false);
}, false);
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<script src="./js/background.js"></script>
</head>
<body>
</body>
</html>
//現在地
var currentPos = {
lat: 0,
lng: 0
}
//目的地
var destPos = {
lat: 0,
lng: 0
}
//通知済みの距離を保持
var notified = {};
//オプション
var geoOptions = {
enableHighAccuracy: true, //高精度要求
timeout: 6000, //タイムアウト(ミリ秒)
maximumAge: 0 //キャッシュ有効期限(ミリ秒)
}
/*
* メッセージを受信し、各処理へ振り分ける
*/
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
switch ( request.action ) {
case 'get_position':
getPosition(sendResponse);
break;
case 'set_destination':
setDistination(request.lat, request.lng, sendResponse);
break;
case 'start_watch_position':
startWatchPosition(sender, sendResponse);
break;
case 'stop_watch_position':
stopWatchPosition(sendResponse);
break;
default:
sendResponse({});
break;
}
}
);
/*
* 現在地を取得する
*/
function getPosition(sendResponse){
//現在地の位置情報取得
navigator.geolocation.getCurrentPosition(
//成功
function(position){
//現在地を保持
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
//現在地を返す
sendResponse({
type: "position",
position: position
});
},
//失敗
function(e){
sendResponse({
type: "error",
error: e
});
},
//オプション
geoOptions
);
}
/*
* 目的地を設定する
*/
function setDistination(lat, lng, sendResponse){
//目的地を保持
destPos.lat = lat;
destPos.lng = lng;
//通知済みの距離をリセット
notified = {};
//目的地までの距離を返す
sendResponse({
type: "distance",
distance: getDistance(currentPos.lat, currentPos.lng, destPos.lat, destPos.lng)
});
}
/*
* 自動更新を開始する
*/
var watchId = 0; //自動更新停止用のID
function startWatchPosition(sender, sendResponse){
if ( watchId != 0 ) {
//以前の自動更新を停止する
navigator.geolocation.clearWatch(watchId);
watchId = 0;
}
if ( destPos.lat == 0 && destPos.lng == 0 ) {
sendResponse({
type: "message",
message: "目的地を設定してください"
});
return;
}
//通知済みの距離をリセット
notified = {};
//自動更新を開始する
watchId = navigator.geolocation.watchPosition(function(position){
//現在地を保持
currentPos.lat = position.coords.latitude;
currentPos.lng = position.coords.longitude;
//デスクトップに通知
//目的地までの距離を取得してkm単位に変換
var distance = getDistance(currentPos.lat, currentPos.lng, destPos.lat, destPos.lng);
//距離によってしきい値を変える
var threshold = 0;
if ( distance < 1 ) {
//1km未満は、200mごとに通知
threshold = 0.2;
} else if (distance < 10 ) {
//10km未満は、1kmごとに通知
threshold = 1;
} else {
//10km以上は、10kmごとに通知
threshold = 10;
}
//一度通知した距離は再通知しない
var notifiedKey = Math.floor(distance / threshold) * threshold;
if ( !notified[notifiedKey] ) {
notify('目的地までの距離', '約 ' + distance + ' km');
notified[notifiedKey] = true;
}
//更新情報を送る
chrome.tabs.sendRequest(
sender.tab.id,
{
action: 'refresh',
position: position,
distance: distance
},
function(response) {}
);
}, null, geoOptions);
sendResponse({});
}
/*
* 自動更新を停止する
*/
function stopWatchPosition(sendResponse){
navigator.geolocation.clearWatch(watchId);
watchId = 0;
sendResponse({});
}
/*
* 2点間距離計算(km)
*/
function getDistance(lat, lng, dLat, dLng){
//緯度1度あたり111km、経度1度あたり91kmの概算
var h = Math.abs(dLat - lat) * 111000;
var v = Math.abs(dLng - lng) * 91000;
return Math.round(Math.sqrt(Math.pow(h, 2) + Math.pow(v, 2))) / 1000;
}
/*
* デスクトップへ通知
*/
function notify(title, message){
//マニフェストファイルへの記述で許可されている
if ( webkitNotifications.checkPermission() == 0 ) {
var popup = webkitNotifications.createNotification('icon_48.png', title, message);
popup.ondisplay = function(){
setTimeout(function(){
popup.cancel();
}, 5000);
};
popup.show();
} else {
//デスクトップへの通知許可を要求する
webkitNotifications.requestPermission();
}
}