Now browsing the archives for the 'ScrapBook' category.

contenteditable 属性

position: absolute; な要素に対して Firefox 3 で導入された contenteditable="true" 属性を付加すると、内容の編集以外にもその要素をドラッグ&ドロップで位置を変更したり、リサイズしたりすることが可能。
テストケース

Firefox 3 でのセキュリティに関する変更 (Web ページからの chrome コンテントへのアクセスがデフォルトで制限される) 影響で、 ScrapBook の付箋アノテーション機能が壊れていた。 chrome.manifest で contentaccessible フラグを使えばこの制限を解除できるが、せっかくなのでこの機会に前から考えていた contenteditable 属性を使って付箋アノテーション機能を改善してみた。

TOP

nsIWebBrowserPersist で gzip 圧縮されたデータをダウンロードする

HTTPでダウンロードする際は nsIWebBrowserPersist::saveURI や saveChannel が便利だ。
しかし、gzip圧縮形式で転送されたデータ(つまりWebサーバが「Content-Encoding: gzip」ヘッダを付加した場合)をダウンロードすると、圧縮されたままの状態でファイルへと保存してしまう。

非圧縮状態でファイルへ保存するためには、 nsIWebBrowserPersist::persistFlags プロパティに PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION フラグを立てる。
あえて圧縮状態で保存する理由が思いつかないので、基本的にこのフラグは必須と考えてよさそう。

参考

nsIWebBrowserPersist.idl

余談

ScrapBook でgzip圧縮されたページを保存すると画像がすべて壊れて表示されなかったり、 Amazon や はてな のページを保存すると、ツリーの favicon が消滅してしまう、といった問題はすべて上記の問題によるものだった。さきほど修正して新しいバージョンをリリースした。

なぜこんな簡単なことに今まで気付かなかったのだろうか…。
しかも気付く前に Bugzilla にバグを立ててしまった。
Bug 416817 – nsIWebBrowserPersist should decompress data if server responses with "Content-Encoding: gzip" header

TOP

canvas要素によるWebページのスクリーンショット保存機能

今年4月に行なわれたMozillaParty7.0において、いくつか有用な情報を得ることができたが、中でもcanvas 要素の toDataURL メソッドで取得した data:URL をファイルへ保存するという Taken さんの情報は、 ScrapBook で保存したWebページのコレクションをサムネイル画像によって一望するというプランを一気に実現へと近づけることができるありがたいものであった。その具体的な方法はTaken SPC : Mozilla Party JP 7.0 に行ってきましたのポストでも説明されているが、これを利用して現在ブラウザに表示されているWebページのスクリーンショット(今回はサムネイルではなく、原寸大のスクリーンショット)をPNG画像として保存する機能を実装してみる。

(1) XUL

html:canvas要素を chrome://browser/content/browser.xul へオーバーレイする。オーバーレイする位置はどこでも構わないが、下記の例ではステータスバー内にオーバーレイしている。また、サイズの大きな canvas 要素を擬似的に隠すために、scrollbox 要素内に押し込んで表示させている。これは PearlCrescent PageSaver で用いられているテクニックである。

<overlay id="myOverlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml">

    <statusbar id="status-bar">
        <scrollbox width="1" height="1">
            <html:canvas id="myCanvas" display="none" />
        </scrollbox>
    </statusbar>

</overlay>

(2) JavaScript

まずは対象となるWebページのWindowオブジェクトと、Webページのサイズを取得する。ただし、documentElement.clientHeightは、Quirks(後方互換)モードではWebページ全体の高さとなるが、 Standards Compliant(標準準拠)モードでは実際に見えている部分のみの高さとなる。PearlCrescent PageSaver では、どちらのモードにも対応した GetWindowWidth, GetWindowHeight 関数というのを自前で実装している。

var win = window._content;
var w = win.document.documentElement.clientWidth;
var h = win.document.documentElement.clientHeight;

var w = win.document.width;
var h = win.document.height;

「display: none;」となっていたcanvas要素を一時的に表示させ、そのサイズを先ほど取得したWebページのサイズと一致させる。canvas要素は scrollbox 内に押し込まれているので、見かけ上は1×1ピクセルとして表示される。

var canvas = document.getElementById("myCanvas");
canvas.style.display = "inline";
canvas.width = w;
canvas.height = h;

いよいよ、canvas要素へWebページを描画する。

var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.scale(1.0, 1.0);    // 1.0なら原寸大
ctx.drawWindow(win, 0, 0, w, h, "rgb(255,255,255)");
ctx.restore();

続いて、PNG画像データをBASE64エンコードしたdata:URLを取得する。これは Firefox 2.0 以外では例外となるので、 try~catch する。さらに、取得した string型の data:URL から nsIURI オブジェクトを生成する。

try {
    var url = canvas.toDataURL("image/png");
} catch(ex) {
    return alert("This feature requires Firefox 2.0.
" + ex);
}
const IO_SERVICE = Components.classes['@mozilla.org/network/io-service;1']
                   .getService(Components.interfaces.nsIIOService);
url = IO_SERVICE.newURI(url, null, null);

ファイルピッカーを使って保存先ファイルを決定する。

var fp = Components.classes['@mozilla.org/filepicker;1']
          .createInstance(Components.interfaces.nsIFilePicker);
fp.init(window, "Save Screenshot As", fp.modeSave);
fp.appendFilters(fp.filterImages);
fp.defaultExtension = "png";
fp.defaultString = "screenshot.png";
if ( fp.show() == fp.returnCancel || !fp.file ) return;

nsIWebBrowserPersist を使って data:URL をファイルへ保存する。
nsIWebBrowserPersist はURIがhttp:プロトコルであるインターネット上のデータをダウンロードするためによく使われるが、data:プロトコルに対しても有効であるというのがミソ。

var wbp = Components.classes['@mozilla.org/embedding/browser/nsWebBrowserPersist;1']
          .createInstance(Components.interfaces.nsIWebBrowserPersist);
wbp.saveURI(url, null, null, null, null, fp.file);

最後に、一時的に表示されたキャンバスを非表示にして後始末する。

canvas.style.display = "none";
canvas.width = 1;
canvas.height = 1;

以上のような処理でおおよそスクリーンショットを保存する機能を実装することができた。あとは、適当なUIを加えれば、 PearlCrescent PageSaver Basic 相当の拡張機能がいとも簡単に実装できてしまう。

しかし、ひとつ厄介な問題がある。スクリーンショットを撮る対象のWebページのサイズが非常に大きい場合、一時的に膨大なメモリを食ってしまうということである。例えば ScrapBook の「リクエスト+バグ」のページのスクリーンショットを保存すると、一時的にメモリ使用量が50MBほど上昇する。PCに搭載されたメモリサイズに依存するが、最悪PCの動作が不安定になり、Firefox を強制終了するしかなくなることもある。原因はもちろん data:URL を取得して string型の変数へ格納した際に発生するのだが、対策は難しいだろう。

TOP

Rethinking ScrapBook’s Browser Context Menu :: Part 2

Japanese version of this post is also available.

The 2nd topic in the series, “Rethinking ScrapBook’s Browser Context Menu”.
This time, I kick around the [Capture Frame] menu which apprears with [Capture Page] when we click in a frame / inner-frame. In the Firefox’s standart context menu, all menus related to frame are submenus inside the [This Frame] menu. Hence [Capture Frame] should be inside the [This Frame] menu in a similar way.

Contorary to that, the standpatters might have such viewpoints:
Additional menus of extensions should not scatter and should be placed in one place.
It is easier to choice between [Capture Page] and [Capture Frame].

As a developer, I have a following viewpoint. [Capture Frame] inside [This Frame] is surely sensible, however, I’m afraid some people mistakenly thought that [Capture Frame] was gone!

Please let me hear your thoghts or comments.

current

Current: [Capture Frame] and [Capture Page] are in parallel position.

alternative

Alternative: [Capture Frame] should be the submenus inside [This Frame].

TOP

Rethinking ScrapBook’s Browser Context Menu :: Part 1

Japanese version of this post is also available.

The 1st topic in the series, “Rethinking ScrapBook’s Browser Context Menu”.
First time, I kick around the most popular [Capture Page] and [Capture Page As…] menus.
Today we have these menus at the bottom of the browser context menu. However, the feature of [Capture Page (As…)] is similar to Firefox’s [Save Page As…] one, so they should be ‘neighborhood’ each other. At least, both should be placed in a same category between two separators.

Contorary to that, the standpatters might have such a viewpoint: additional menus of extensions are below Firefox’s standard menus in rank.

Please let me hear your thoghts or comments.

current

Current
We have [Capture Page (As…)] at the bottom.

alternative

Alternative
[Capture Page (As…)] and [Save Page As…] should be placed in close position.

TOP

ScrapBook ブラウザコンテキストメニューの見直し :: その2

English version of this post is also available.

ScrapBookのブラウザ上のコンテキストメニューについて見直すシリーズ、その2。
今回はフレーム上で右クリックしたときに「ページの取り込み」とともに表示される「フレームの取り込み」メニューについての比較検討である。Firefoxの標準のコンテキストメニューでは、フレームに対する操作はすべて「このフレーム」というメニューのサブメニューとして表示される。したがって、「フレームの取り込み」メニューは「このフレーム」メニューのサブメニューであるべきだと言える。
逆に、現状維持派の言い分としては、「ページの取り込み」と「フレームの取り込み」が並んでいる方が直感的にわかりやすく、二者択一しやすい、あるいは、拡張機能による付加的なメニューはあちこちに分散すべきでない、一箇所にまとめるべきだ、といった見解がだろうか。
また、開発者の言い分としては、確かに代替案の方が理にかなっていると思うが、今頃になって突如メニューの配置を変えるとなると、それに気付かずに「フレームの取り込み」機能が無くなってしまったのかと勘違いされるのが嫌だ、というのが一番大きい。

現状

現状 「フレームの取り込み」は「ページの取り込み」と並列関係にある。
代替案 「フレームの取り込み」は「このフレーム」のサブメニューとすべき。

代替案

TOP

ScrapBook ブラウザコンテキストメニューの見直し :: その1

English version of this post is also available.

ScrapBookのブラウザ上のコンテキストメニューについて見直すシリーズ、その1。
まずはもっともポピュラーなメニューである「ページの取り込み」「ページの詳細な取り込み」の位置についてである。現状では、これらのメニューはコンテキストメニューの最下部に表示される。しかし、ScrapBookの「ページの取り込み」機能はFirefoxの「名前を付けてページを保存…」の類似機能であることを考慮すると、これらのメニューは近い位置、少なくともセパレータで区切られた同一のカテゴリ内にあるべきだと言える。
逆に、現状維持派の言い分としては、拡張機能による付加的なメニューは標準で備わるメニューよりも位が低いのでより下の位置にあるべきだ、といった見解だろうか。
ぜひともScrapBookユーザの方々の意見をいただきたいと思います。

現状

現状
「ページの取り込み」は最下部にある。

代替案

代替案
「ページの取り込み」は「名前を付けてページを保存…」に近い位置であるべき。

TOP

parseInt の落とし穴 - 8月発生の時限式バグ

何気なくScrapBookのbackupフォルダを覗いてみたところ、なぜか一番新しいバックアップファイルが7月31日に生成されたもので、8月以降およそ2週間まったくバックアップファイルが生成されていなかった。

これは何か怪しいと思い、バックアップ処理を細かく調べたところ、バックアップファイル名の日付が何日前かをチェックする処理において、なぜか本日生成されたばかりのバックアップファイルが200日以上前のものだと判断され、即座に削除の対象にされていた。
ScrapBook では Firefox 起動時に本日付のバックアップファイルが存在するかを確認し、もしなければ本日付のバックアップファイルを生成し、なおかつ古いバックアップファイルの削除処理を行うという仕様になっている。したがって、 Firefox を起動する度に本日付のバックアップファイル生成→本日付のバックアップファイル削除、という処理が8月以降ずっと繰り返されていたというわけだ。

ではなぜ8月以降、バックアップファイルが何日前かを算出する処理がうまくいかなかったのだろうか。OSの設定が狂った、夏時間特有の問題、2つのDateオブジェクトの減算で型変換がうまくいっていない等の原因を考えたが、色々追求した結果、parseInt(“08”) が 0 になることが原因だと判明した。

alert( parseInt("08") );    // 「0」が表示される

今まで parseInt というJavaScript の組み込みグローバル関数は、string 型で表現された整数を、number 型に変換するだけの単純なものだと思っていたが、実は string 型で表現された小数や8進数や16進数や文字列も変換可能である。その際、引数として渡した string 型の値をどのような形式で変換するのかは自動で識別されるため、先頭に0をつけている場合は8進数とみなされ、”08″ が 0 に変換されるのである。また、第二引数に基数 10 を指定することで思い通りに10進数として変換させることが可能である。

alert( parseInt("08", 10) );    // 「8」が表示される

この parseInt(“08”) が原因となるバックアップ処理に関するバグは、8月になると突如発生する時限式バグといえよう。運良く2週間ほどでバグに気づけたことが不幸中の幸いである。このバグを修正したバージョン (1.1.0.2) はすでにリリース済みである。

ところで、ScrapBook を世に公開して間もない、非常に初期のバージョンにて、2004年10月になると突如取り込んだデータが上書きされ続けるという大変恐ろしい時限式バグが発生したが、このときの原因も今回のバグの原因と通ずるものがあり、年・月・日を加算するときに型の自動変換に頼っていたためにバグが引き起こされていた。

var y = 2004;    // number
var m = "09";    // string
var d = 30;    // number
var ymd = y + m + d;
alert(ymd);    // 「20040930」が表示される

9月30日までは、数値 9 を 文字列 “09” にして加算をしていたため、 2004 + “09” は “200409” になってうまくいっていた。

var y = 2004;    // number
var m = 10;    // number
var d = "01";    // string
var ymd = y + m + d;
alert(ymd);    // 「201401」が表示される

しかし、10月1日になると、数値 10 は文字列に変換せずに加算していたため、 2004 + 10 は 2014 になってしまった。

var y = 2004;    // number
var m = 10;    // number
var d = "01";    // string
var ymd = y.toString() + m.toString() + d.toString();
alert(ymd);    // 「20041001」が表示される

数値か文字列かわからないような値同士を加算するときには、必ず toString や parseInt を使って正しい型に変換してから加算をしなければならない。

parseInt のリファレンス:
Core JavaScript 1.5 Reference:Global Functions:parseInt – MDC

TOP

designMode

designModeとは多くのWebブラウザに搭載される簡易HTML編集機能であり、おもにブログ投稿やWebメール作成のためのリッチテキストエディタとして使用される。

javascript:void(document.designMode='on')

ScrapBookで保存したページを閲覧時、このようなブックマークレットを用いてデザインモードを有効にすると、ScrapBookの編集機能ではどうしても不可能だった編集ができるようになる。たとえば、ある文章の一部を改変したり、段落を一つ増やして自分のコメントを文中に挿入したり、ブロック要素の幅を変えたり、他のページからコピーした画像や文章の断片をそのままページ中に貼り付けたりできるようになる。
こういった編集をしたいというユーザからの要望に対しては、designModeのブックマークレットを薦めてきたが、いっそのことScrapBookのステータスバーアイコンのメニューなどからdesignModeのオン/オフができるようにしたら面白いかもと思った。しかしdesignModeで色々試しているうちに、色々とおかしな現象が多発したので、Bugzillaを調べてみたところかなりバグがあるようである。
Bug 287707 – After page had designmode on, there are still several issues
Bug 304994 – iframe with designMode=on breaks when you navigate to another loaded page and then back
特にBug 287707で報告されているように、次のページへ遷移しても何も表示されないというバグが致命的なので、designMode切り替えメニューの搭載は当面先送りにした。

TOP

宿題

ScrapBookのページ保存機能ではCSSから参照される画像は無条件でダウンロードされる。たとえば、CSS中に

#hoge { background-image: url('foobar.png'); }

というルールがあれば、たとえ現在のページ中に id=”hoge” が存在しないとしても foobar.png はダウンロードされる。
この仕様だと確かに保存機能の精度は高くなるが、場合によっては無駄なファイルが大量にダウンロードされる。特に最近はCSSを利用したデザインが多い傾向にあるようで、不要な画像ファイルをダウンロードすることによるデータサイズの肥大がかなり気になってきた。
そこで、CSSからは参照されているが、実際のページ(HTML)からは参照されない画像をダウンロードしないための方法を考えてみようと思う。

ロジック:

  • CSSのルールから参照される各画像について、それがHTMLから参照されているかどうか判別する
  • 現在表示しているHTMLから「実際に」参照されている画像をリストアップし、CSSから参照されている画像のリストと突き合わせて不要ファイルを検出する
    →現在表示しているHTMLから「実際に」参照されている画像のリストアップは「ページ情報」の「メディア」に相当するので、これが参考になるかも?

ユーザインタフェース:

  • [設定]の[取り込み]タブに、試験的なオプションを追加する
    →十分なデバッグの後、正式採用する
  • 編集ツールバーに[不要ファイル削除]機能を追加する(ボタンまたはメニュー)
    →DOMイレーサーなどでページから削除された(=参照されなくなった)画像を物理的に削除する機能としても使える

TOP