Firefox は Mozilla プロジェクトの中で最も成功した製品かもしれません。Firefox は2004年に最初にリリースされ、そして最も一般的な Web ブラウザーである MSIE (Microsoft® Internet Explorer) の開発があまり進まず、また深刻なセキュリティー問題に苦しんでいる時に劇的に人気が高まりました。こうした Firefox の人気に対抗することを主な目的として、Microsoft は MSIE に大量のリソースを再び投入しましたが、それによってこの新しい競合の着実な成長を遅らせることはできませんでした。Mozilla Foundation によれば、Firefox のユーザーは全世界に約 2 億人いますが、この人気の一端は Firefox が 45 ヵ国の言語で利用できる点にあります。さらに Mozilla は、「24 時間内に最も多くダウンロードされたソフトウェア」というギネス世界記録の新しいカテゴリーで記録を作ることで、新たな話題を呼ぼうとしています。このギネス記録を作るためのターゲットとなるソフトウェアが Firefox 3.0 です。Firefox 3.0 はこのブラウザーの新たなメジャー・バージョンであり、2008年の夏にリリースが予定されています。Firefox 3.0 は既にリリース候補の形で入手することができ、これは次のリリースが完全な最終版の 3.0 であると確定していることを意味しています。Firefox 3 によって Web 開発がより一層楽しいものになり、またオフラインのサポートが追加されたことにより、ブラウザーは今まで以上に幅広い状況で使い得る汎用のアプリケーション・プラットフォームとなることができます。この記事は、Firefox 3.0 のこうした新たな変更を最大限に利用するためのガイドです。
Firefox 3.0 に関する期待が大きく高まっている理由は、Web ユーザーから Web 開発者まですべての人々に対して、Firefox 3.0 が驚くほど多種多様な改善を約束しているためです。Web 開発者は (クロスブラウザーの互換性のために最終的には妥協が必要なことを知っていても) 自分たちの好みの開発プラットフォームとして Firefox を使うことが多かったため、Firefox 3.0 のリリースは重要かつ画期的な出来事です。Firefox が開発者の間で人気がある理由は、コミュニティーが活発であり、標準を厳密にサポートしており、そしてプラットフォームが常に革新的であるためです。プラットフォームが革新的であることによって、開発者は Web でのトレンドに先んじることができます。また標準をサポートしているということは、そうした最先端のトレンドに関してさえ Firefox はその機能を公開しており、それによって採用が加速され、また互換性が改善されるということを意味します。しかも Firefox 3 は、さらに多くのものを提供します。Firefox 3 のリリース・ノートには次のように書かれています。
Firefox 3 は過去 33 カ月にわたって開発されてきた Gecko 1.9 Web レンダリング・プラットフォームに基づいています。前回のリリースを元に構築された Gecko 1.9 には主要部分の一部の再設計を含めて 14,000 件を超える変更が行われており、パフォーマンスや安定性、レンダリングの正確性が改善され、またコードの単純化と維持管理性の向上が図られています。Firefox 3 はこの新しいプラットフォームの上に構築されており、そのためよりセキュアで使いやすく、よりパーソナライズしやすい製品となっており、しかも Web サイトや Firefox アドオンの開発者のために、さらに多くの機能が提供されています。
「14,000 件」という数字は、あっと言わせるために使われている数字のひとつかもしれませんが、しかし実際に驚くほどの多数の有用な変更が行われており、その多くは深く掘り下げて調べる価値のあるものです。
終了スクリプトの脆弱性からフィッシングに対する保護に至るまで、セキュリティーに関する機能強化がいくつか含まれていますが、それらの大部分は、悪意のあるコードを作成するのでない限り皆さんの作業に影響することはありません。他の変更はユーザーにとっての使いやすさを向上するためのものです。例えばダウンロード・マネージャーやパスワード・マネージャーが改善され、ページをズームできる便利な機能が備わり、また Mac や Windows®、Linux® での動作が、それぞれの OS に合わせて、より自然なものになっています。新しいブックマークは非常に強力であり、厳密な階層構造ではなく複数のタグを使うことができます。履歴機能は Places と呼ばれるものを提供することで一層機能豊富になっており、この履歴機能をブックマークと組み合わせることで Web をパーソナルな方法で管理し、検索することができます。
Firefox 3 では、HTML5 のワーキング・ドラフトに基づき、オフラインであってもユーザーが作業を継続できるようになっています。オンラインの状態でユーザーは静的な Web ページを何通りかの方法で持つことができます。その方法には、ブラウザーのキャッシュを使う方法、ローカル・キャッシュのプロキシーを持つ方法、さらには単に Web コンテンツをディスクにダウンロードする方法などがあります。私達が Web で行う内容はますます動的なものになっており、例えば Web メールや Web フィードを読む、お気に入りの店をブラウズする、あるいはお気に入りのソーシャル・ネットワークを使う、などがあります。最も標準的な生産性アプリケーションでさえ、オンライン版のワード・プロセッサーやスプレッドシート、プレゼンテーション・ツールにより Web で行えるようになっており、それはこうしたアプリケーションの新しいカテゴリーであるウィキなどと同様です。ネットワークが利用できない場合でもこれらのアプリケーションを使い続けることができれば、そのアプリケーションは一層貴重なものになります。Firefox 3 を利用することにより、この機能を皆さんの Web アプリケーションで実現できるのです。では Web 開発者にとって最も興味深い、この新機能を詳しく調べてみましょう。
オンライン/オフラインのパズルを解くためには、まず Web アプリケーションがユーザーのブラウザーに対して、オフラインで利用できるローカル・キャッシュにアプリケーションが必要とするリソースを保存するように指示できる必要があります (このローカル・キャッシュはアプリケーション・キャッシュと呼ばれます)。このキャッシュは通常、メインの Web ページやスタイルシート、スクリプト・ファイルやその他のファイルなど、あまり頻繁に変更されないものに対して使われます。アプリケーションの URI を登録すると、その URI によってリソースのバンドルが管理され、Firefox はそれらのリソースを、対応するオンラインのリソースと同期するように管理します。また文書要素 (例えば html) の manifest 属性を使うことでアプリケーションのキャッシュを確立します。
パズルを解くための 2 番目の要素は、ユーザーがアプリケーション・キャッシュの自動管理でカバーされない作業を行えるように、いつユーザーがオンラインになり、いつオフラインになるのかを知ることです。Firefox 2 では、navigator.onLine というブール値のスクリプト・プロパティーが導入されました。このプロパティーは、ユーザーが (通常は Firefox のメニューによって) 手動でオフライン・モードに切り替えるたびに更新されます。Firefox 3 以降では、このプロパティーは、ネットワークを利用できないことをブラウザーが自動的に検出した場合にも更新されます。さらに、オンラインの状態が変化するとスクリプト・イベントが呼び出されます。このスクリプト・イベントをリッスンすることで、オフラインの状況でのアプリケーションの振る舞いを完全に制御することができます。
Firefox 3 のアルファ版以前のバージョンでオフライン・サポートが利用できるようになるとすぐに、Mozilla 開発者である Mark Finkle が、開発者のために便利で簡単なアプリケーション例を作り上げました。このアプリケーションは ToDo リスト・ツールであり、スクリプトを使うことによって、アプリケーション・キャッシュに保存されたメインのサービスの Web ページの中にユーザー入力を埋め込むことができます。またこのアプリケーションは ToDo リストをサーバー・サイドに保存することもできます。この方法を使うことによって、ユーザーがオンラインであれオフラインであれ、このページを利用することができ、またユーザーのデータを維持管理することができます。ここでは Mark の許可を得て、この記事のために彼のサンプル・アプリケーションを変更しています。リスト 1 (todo.html) は、このアプリケーションのメインの HTML ファイルです。
リスト 1. ToDo リスト・アプリケーションのメインの HTML (todo.js)
<html manifest="todo.manifest">
<head>
<title>Todo tool</title>
<script type="text/javascript" src="json2.js"></script>
<script type="text/javascript" src="todo.js"></script>
<style type="text/css">
body { font-family: verdana,tahoma, arial; }
div#container { width: 300px; }
div#title { font-size: 120%; }
div#subtitle { font-size: 80%; }
div#tasklist { margin-bottom: .5em; }
div#log { font-size: 90%; background-color: lightgray; margin-top: 1em;
white-space: pre; }
</style>
</head>
<body onload="loaded();">
<div id="container">
<div id="title">Todo tool <span id="status">online*</span></div>
<div id="subtitle">Simple online/offline demo for Firefox 3</div>
<hr />
<div id="tasklist">
</div>
<input type="text" id="data" size="35" />
<input type="button" value="Add" onclick="addItem();"/>
<hr />
<input type="button" value="Remove" onclick="removeItems();"/>
<input type="button" value="Complete" onclick="completeItems();"/>
<div id="log"><strong>Event Log</strong>
</div>
</div>
</body>
</html>
|
最初の行にある属性、manifest="todo.manifest" に注目してください。この値は相対 URL であり、これを HTML ページのベース URL と組み合わせてマニフェストの URL を指定しています。このマニフェストの内容がリスト 2 です。
リスト 2. ToDo リスト・アプリケーションのリソース・キャッシュのマニフェスト
CACHE MANIFEST
# v1
todo.html
json2.js
|
このフォーマットは非常に単純です。ここで重要なものとして注目して欲しいものは v1 コメントです。マニフェストの中のどのファイルを変更する場合も、このテキストを (例えば v2 などに) 変更する必要があります。ブラウザーはマニフェスト・ファイルが変更されたことに気付くと、(変更されていないファイルも含めて) リストアップされたすべてのファイルの新しいバージョンを自動的に取得します。リスト 1 に戻ると、ToDo リストの項目は <div id="tasklist"></div> の中に表現されており、スクリプトによって動的に更新されます。ロードされるスクリプトの 1 つである json2.js は、よく使われるライブラリーであり、JSON 処理のための便利なコードを提供しています。しかしこれに関しては、後でもう少し説明します。2 番目のスクリプト (リスト 3) には、オフラインを処理するためのビットを含めて、ToDo リスト・ツールのための専用の処理が含まれています。
リスト 3. ToDo リスト・アプリケーションのスクリプト (todo.js)
//Uses offline features from WHAT Web applications draft specification
//(Firefox 3-specific at present)
//JSON form of the task list items, for direct transport purposes
var taskStorage = "[]";
//Web app's domain is used as a key to the application cache data (globalStorage)
var storageDomain = location.hostname;
//Invoked when the browser page is loaded (i.e. onLoad attribute on the body)
function loaded() {
//Load the to-do list data from app cache or web app, depending on offline status
updateOnlineStatus("initial load", false);
//Set up listeners to handle online/offline transitions
document.body.addEventListener("offline",
function () { updateOnlineStatus("offline", true) }, false);
document.body.addEventListener("online",
function () { updateOnlineStatus("online", true) }, false);
//Load initial to-do list data saved in the application cache, if available
//This will cause the browser to prompt the user for permission
if (typeof globalStorage != "undefined") {
var storage = globalStorage[storageDomain];
if (storage && storage.taskStorage) {
taskStorage = storage.taskStorage;
}
}
//See if we can load an updated task list
fetchList();
}
//Invoked when the user's online/offline status changes
function updateOnlineStatus(msg, allowUpdate) {
//Update the online status indicator in the subtitle
var status = document.getElementById("status");
status.innerHTML = (navigator.onLine ? "[online]" : "[offline]");
//Record the change in the log area of the Web page
var log = document.getElementById("log");
log.appendChild(document.createTextNode("Event: " + msg + "\n"));
//If online, try to push any task list changes back to the server
if (navigator.onLine && allowUpdate) {
update();
log.appendChild(document.createTextNode("Updated server\n"));
}
}
//Execute HTTP request to the server, either to get the task list or to push updates
function httpRequest(type, data, callback) {
var httpreq = new XMLHttpRequest();
httpreq.onreadystatechange = function() {
if (httpreq.readyState == 4)
callback(httpreq.readyState, httpreq.status, httpreq.responseText);
}; //close function()
httpreq.open(type, "/todo-app", true);
if (type == "POST") {
httpreq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
httpreq.send(data);
}
//Put updated task list from the server on the Web page, and in the application cache
function loadList(readyState, status, responseText) {
if (readyState == 4) {
if (status == 200) {
taskStorage = responseText;
var tasks = JSON.parse(taskStorage);
var html = "";
//Update the fields on the Web page
for (var i=0; i<tasks.length; i++) {
html += "<input type='checkbox' id='" + tasks[i].name + "'/><label for='"
html += tasks[i].name + "'>" + tasks[i].data + "</label><br/>";
}
document.getElementById("tasklist").innerHTML = html;
//Update the application cache
if (typeof globalStorage != "undefined") {
globalStorage[storageDomain].taskStorage = taskStorage;
}
}
}
}
//Load an updated task list either from the server or from application cache
function fetchList() {
if (navigator.onLine) {
httpRequest("GET", null, loadList);
}
else {
loadList(4, 200, taskStorage);
}
}
//Invoked when the user adds a new task list item
function addItem() {
var data = document.getElementById("data").value;
document.getElementById("data").value = "";
//Turn the stored task list into a JavaScript list
//Use present timestamp as a simple ID ("name")
var tasks = JSON.parse(taskStorage);
tasks.push({"name": Date.now(), "data": data });
//Convert back to JSON and update the task list variable
taskStorage = JSON.stringify(tasks);
//Try to push any task list changes back to the server
update();
}
//Invoked when the user removes a task list item
function removeItems() {
var tasks = JSON.parse(taskStorage);
var newTasks = [];
//See which boxes are checked and only copy back those that are not
var items = document.getElementById("tasklist").getElementsByTagName("input");
for (var i=0; i<items.length; i++) {
if (items[i].checked == false) {
newTasks.push(tasks[i]);
}
}
taskStorage = JSON.stringify(newTasks);
update();
}
//Invoked when the user marks new task list item as completed
function completeItems() {
var tasks = JSON.parse(taskStorage);
//See which boxes are checked add strikeout tags to those that are
var items = document.getElementById("tasklist").getElementsByTagName("input");
for (var i=0; i<items.length; i++) {
if (items[i].checked) {
var task = tasks[i].data;
if (task.indexOf("<strike>") != -1) {
task = task.replace("<strike>", "");
task = task.replace("</strike>", "");
}
else {
task = "<strike>" + task + "</strike>";
}
tasks[i].data = task;
}
}
taskStorage = JSON.stringify(tasks);
update();
}
//Try to push any task list changes back to the server
function update() {
if (navigator.onLine) {
var post = "action=update&tododata=" + encodeURIComponent(taskStorage);
httpRequest("POST", post, function(readyState, status, json) { fetchList(); });
}
else {
loadList(4, 200, taskStorage);
}
}
|
私はコードに大量のコメントを付けておきましたが、このコードに関する一般的な注意をいくつか挙げておきます。ToDo リストのデータはページの中に埋め込まれていますが、このデータを利用する際には taskStorage 変数の中に入れられ、JSON 形式で受け渡しが行われます。この Web アプリケーションのページを最初にロードすると、Firefox はこのデータをアプリケーション・キャッシュからロードしようとします。そのアプリケーション用のエントリーがない場合には、Firefox はそのエントリーをセットアップしてよいかどうか、ユーザーに許可を促します。これを示したものが図 1 です。
図 1. ToDo ツールを最初にロードしたところ
ユーザーが allow をクリックすると、Firefox はアプリケーション・キャッシュをセットアップします。開発者はおそらく、Firefoxがもう 1 度許可を要求するように、さまざまなものをリセットする必要があるでしょう。これを行うのは、「環境設定」ウィンドウ (Mac の場合) または「オプション」ウィンドウ (Windows の場合) で、具体的には「詳細」タブ内の「ネットワーク」タブで行います。図 2 は Mac OS X での、該当ウィンドウ部分を示しています。これを見ると、ローカル・ホストに対してオフラインの動作を許可するエントリーがあることがわかります。このエントリーをリセットするためには、このエントリーを選択し、Remove... をクリックします。
図 2. オフライン作業用データの保存を管理する
ユーザーが Web ページを開くと、ユーザーはエントリーの追加や削除を行うことができ、また完了したものにはエントリーの上に取消線を重ねて表示することで、エントリーをリストに表示したまま、完了したものとして印を付けることができます。図 3 は、オンラインの間にテスト用のエントリーをいくつか追加した後のページの様子を示しています。
図 3. ToDo ツールがオンラインの状態 (いくつかのエントリーが追加されています)
オンラインの間に ToDo リストが更新されると、スクリプトは必ずサーバーに対して更新内容を送信します。私はこれを処理する簡単なサーバーのためのコードを Python で作成しました。Mark Finkle も、この状況を処理するためのコードを PHP で作成しています。もし彼のコードの方がお好みであれば、彼によるオリジナルのコードへのリンクを「参考文献」に挙げてあります。ただし彼のコードを使う場合もやはり、Firefox 3 のリリース候補あるいは最終バージョンですべてが動作するようにするためには、リスト 3 に示す更新されたスクリプトを使う必要があります。リスト 4 (todohandler.py) は Python によるサーバー・コードです。
Lリスト 4. ToDo リスト・アプリケーションのサーバー・コード (todohandler.py)
import os, sys
import cherrypy #http://www.cherrypy.org/
from webob import Request, Response #http://pythonpaste.org/webob/
#For now just use a single file for all sessions
DATAFILE = 'test.json'
#The handler function
def todo_handler_application(environ, start_response):
req = Request(environ)
if req.POST.get("action") == "update":
#Handle POSTs to update the to-do list entries. Write to the file
f = open(DATAFILE, 'w')
f.write(req.POST["tododata"])
f.close()
resp = Response(body='', content_type='application/json')
else:
#Handle GETs to pull the to-do list entries. Read from the file
f = open(DATAFILE, 'r')
data = f.read()
f.close()
resp = Response(body=data, content_type='application/json')
#Send the response
return resp(environ, start_response)
#Configure the server to handle the Web form in todo.html
cherrypy.tree.graft(todo_handler_application, '/todo-app')
#Configure the server to serve up the regular, static files from the current directory
server_config = {
'/': {'tools.staticdir.on': True, 'tools.staticdir.dir': os.getcwd() }
}
#Create a CherryPy handler. Doesn't do anything because the meat is in the WSGI app
class DummyHandler: pass
cherrypy.quickstart(DummyHandler(), '/', config=server_config)
|
この場合もコードには大量のコメントを付けてあります。1 つだけ注意しておきたい点として、例を簡単にするために、この場合のコードはアプリケーションのすべてのユーザーに対して 1 つのファイル (DATAFILE と指定されています) を使っています。しかしこれは当然ながら、実際のアプリケーションでは変更する必要があります。つまりユーザーごとに別のファイルまたは別のデータベース行を使う必要があります。私はこのデータを、リスト 5 に示すファイル (test.json) を使って作り出しています。
リスト 5. ToDo リスト・アプリケーションのスクリプト (test.json)
[{"name":1171640861226,"data":"<strike>Example entry</strike>"},
{"name":1212604738536,"data":"Say \"Hello\""},{"name":1212604795352,"data":"
<strike>Say \"Good night\"</strike>"}]
|
これでオンラインの振る舞いを少し示すことができたので、今度はオフラインの振る舞いを調べてみましょう。Firefox でオフラインにするためには、Firefox のメニューを利用します。タイトルが即座に「Todo tool [offline]」に変更されますが、これはオンライン/オフラインの切り替えのためのスクリプト・ハンドラーのおかげです。ユーザーは相変わらず通常通りアプリケーションを使用することができます。オフラインで少し操作を行った後 (この場合は作業完了としてタスクに印 (取り消し線) を付けました)、再度切り替えてオンラインに戻すと、表示は図 4 のようになります。
図 4. オフラインの操作を行った後オンラインに戻った ToDo ツール
注記として、リスト 3 での JSON 処理について説明したいと思います。私は Douglas Crockford による json2.js ライブラリーを使っていますが、これは JavaScript への機能強化として彼が推奨した API をベースにしており、この API は多くの人に支持されているようです。リスト 3 は、この API の JSON.stringify と JSON.parse を使っています。Firefox 3 では新しいハイパフォーマンスの JSON 処理である nsIJSON が追加されていますが、これは今のところ、ブラウザーのデフォルトの設定で使用できる形ではまだ公開されていません。おそらく nsIJSON は近々 Firefox に組み込まれると思われ、また nsIJSON と json2.js は互換性があるはずです。この処理が使えるようになると、パフォーマンスが高まり、また別の JavaScript ファイルが必要なくなることによって、外部への依存を 1 つ減らすことができます。
Firefox の他の新機能も、いくつか試してみてください。Web ベースのプロトコル・ハンドラーを利用すると、組み込みの URI タイプ (例えば http: や mailto: など) 以外の新しい URI タイプを定義することができます。XML 開発者の立場で見ると、Firefox 3 には EXSLT 拡張機能のサポートが数多く追加されており、これによって XSLT 変換が非常に強力になります。またXML ベースのベクター・グラフィックスの標準である SVG のサポートが改善されています。これについて、また XML 処理に関する他の改善に関しては、別の記事で詳しく説明することにします。Firefox 3 によって Web 開発がより一層楽しいものになり、またオフラインのサポートが追加されたことにより、ブラウザーは今まで以上に幅広い状況で使い得る汎用のアプリケーション・プラットフォームとしての可能性が広がります。もちろん、Firefox 3 の新機能の大部分は、まだ他のブラウザーでは使用することができません。これは残念なことですが、少なくともこれらの新機能の大部分は標準に基づいているため、他の Web ソフトウェアも間もなく追従してくるものと十分に期待することができます。
学ぶために
- この記事で紹介したコードの大部分は、Mark Finkle によるアプリケーション例、Firefox 3 - Offline App Demo から引用しています。彼はこの記事の Python コードと同じようなサーバーを PHP で作成しています。
-
オフライン機能や nsIJSON など、Firefox 3.0 で開発者向けに変更された機能を調べてみてください。一般的な情報に関してはリリース・ノートを見てください。
- オフラインのサポートについて学ぶために、John Resig による Offline Events を読んでください。
- HTML 5 について学ぶために、「HTMLの将来、パート1:WHATWG」(developerWorks、2005年12月) と「HTML 5 の新要素」(developerWorks、2007年8月) を読んでください。
- XML 開発者にとっての Firefox に焦点を当てた他の記事として、次のようなものがあります。
- 「Firefox 1.5でのXML、第1回:XML機能の概要」(developerWorks、2005年9月)
- 「Firefox 1.5でのXML、第2回:基本的なXML処理」(developerWorks、2006年3月)
- 「XML in Firefox 1.5, Part 3: JavaScript meets XML in Firefox」(developerWorks、2006年8月)
- 「XML 的思索: Firefox 2.0 と XML」(developerWorks、2007年10月)
-
developerWorks technical events and webcasts で最新情報を入手してください。
-
developerWorks の Web development ゾーンには Web 技術を中心とした記事やチュートリアルが豊富に用意されています。これらを利用して Web 開発のスキルを磨いてください。
製品や技術を入手するために
-
Firefox を入手してください。Firefox は Mozilla ベースの Web ブラウザーであり、標準への準拠、パフォーマンス、セキュリティー、そして確実な XML 機能を実現しています。Download Day のページを調べ、Firefox 3.0 でギネス世界記録を達成する計画について学んでください。
- この記事のコードを利用するためには Douglas Crockford による json2.js が必要です。John Resig がこの json2.js を彼のブログ The State of JSON の中で取り上げており、また Firefox 3 で JSON をネイティブでサポートするために json2.js を進化させるための方法について解説しています。
議論するために
-
developerWorks blogs から developerWorks のコミュニティーに加わってください。

Uche Ogbuji は次世代の Web 技術に特化してソリューションを提供する会社 Zepheira, LLC のパートナーです。Ogbuji 氏は、XML、RDF、およびナレッジ・マネージメント・アプリケーション用のオープンソース・プラットフォームである 4Suite や、チームによる Web 開発のための Jacqard アジャイル手法、そして Versa RDF 問い合わせ言語などの開発リーダーでもあります。彼はナイジェリア出身のコンピューター・エンジニア兼ライターとして米国コロラド州ボルダーに住み、そこで働いています。彼に関して詳しくは、彼のブログである Copia を見てください。