読者です 読者をやめる 読者になる 読者になる

はてなスクリーンショットへの長く険しい道のり

Firefox javascript はてなスクリーンショット拡張

はてなスクリーンショット拡張きちんと動かなくなるやつの修正に至るまでの経緯と、その続き。
# 長いよ

ここから始まった

重たい Firefox を何とかしたい

特にタブを閉じるときにぴたっと固まる。
多分、Firefox 単体の話じゃなくて、アンチウィルスソフトとかとの合わせ技(閉じたウィンドウの処理とかのファイルアクセスに引っ張られている)。

そっちはどうしようもないので、64bit 版だったら多少はマシになるかも、と思ったのが始まり。
未だβ版では 64bit 版が出てなかったので Developer Edition に手を出してみた。

はてなスクリーンショットが動かない

places.sqlite なんかをクリアしたりもして割りと快適に使っていたけれど、はてなスクリーンショットが動かないことに気が付く。

  DEV-42.0a2 β-41.0b7 安定-42.0 β-44.0b2 β-44.0b4(x86) 安定-43.0.3 安定-44.0
はてなスクリーンショット ×(*1) (?) ×(*2) × ×
fotolife お絵かき (?)
人力検索 YouTube 埋め込み (?)
英辞郎 右クリックメニュー × (?) × × ×
fotolife 一括アップロード (?) × × × × ×

  *1 : 「一部のみ」は反応しない。「画面全部」「表示部分のみ」は、ダイアログは表示されるが、アップロードされない
  *2 : ダイアログすら表示されない

fotolife 一括アップロードは、x86(32bit) だと動くので、Flash との関連かも。
  Firefox 44.0b4 + Flash 20.0r0

どうやら、Firefox 44 から動かなくなるっぽい。

戦い(その1)

まずは検索から

JavaScript
Breaking changes in let and const. Firefox 44 finally brings the let and const implementation up to standard, which also means some backward-incompatible changes had to be implemented. Read the linked blog post for the details.

https://blog.mozilla.org/addons/2015/12/29/compatibility-for-firefox-44/

Many add-ons are currently broken on Nightly (Firefox 44) due to some changes that were done in the way let and const behave. These changes were introduced to make Firefox compliant with the final ES6 standard. These changes can lead to JS errors that break your code entirely, so we suggest you test your add-ons extensively to make sure they continue to work.

https://blog.mozilla.org/addons/2015/10/14/breaking-changes-let-const-firefox-nightly-44/

let と const の挙動が ES6 標準と違ってたのを FF44 で修正したよ、ということらしい。

Legacy Semantics
グローバルスコープでの let は var と同じ。
const も、値を修正できないことを除いては、var と同じ。

  • グローバルな変数は、グローバルな this のプロパティとして参照できるが、let や const はそうじゃない
  • let や const は、再定義で例外
  • let や const の前に変数を参照すると例外
  • loadSubScript で読み込むスクリプトで、let や const の再定義も例外
  • Cu.import で読み込むスクリプトは、個別のグローバルスコープを持つ
  • Global lexical scope and custom scope

カスタムスコープの件まで行って、読むのに飽きた。
先に進もう。

アドオンのデバッグ

ピクリともしないのは、例外が出ているからなのか?
アドオンのデバッグって、どうやるんだ。

Enabling the Add-on Debugger
To enable the Add-on Debugger you need to check the "Enable chrome and addon debugging" and "Enable remote debugging" settings in Firefox.

https://developer.mozilla.org/en-US/Add-ons/Add-on_Debugger
  1. ctrl + shift + I で、開発ツールを表示
  2. 右上の歯車をクリック
  3. 「ブラウザとアドオンのデバッグ」と「リモートデバッガを有効」にチェック

これで、有効になるらしい。
Add-on Manager (about:addons) を開くと「デバッグ」のボタンが表示されている。
でも、肝心の「はてなスクリーンショット」や「英辞郎 on the WEB」には「デバッグ」のボタンが表示されてない。

The Add-on Debugger looks and behaves very much like the Browser Toolbox, except that while the scope of the Browser Toolbox is the whole browser, the Add-on Debugger is focused on the specific add-on for which you launched it.

https://developer.mozilla.org/en-US/Add-ons/Add-on_Debugger

Browser Toolbox との違いは、全部見えるか、該当のアドオンだけかの違いらしい。
じゃあ、ブラウザツールボックスで。

https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox
Browser Toolbox を開くのは、「メニュー」→「ツール」→「Web 開発」→「ブラウザツールボックス」か、ショートカットキーの ctrl + alt + shift + I 。

リモート接続確認のダイアログが表示されるので「OK」をクリック。

デバッガオプションから

  • 「キャッチした例外を無視」のチェックを外す
  • 「例外発生で停止」にチェック

駄目だ。
タブをドカドカ開いていると、あちこちのウィンドウでエラーが出まくってる。

セッションを保存して、全てのウィンドウを閉じる。
ブラウザツールボックスを開いて、スクリーンショットのボタンをクリック。
んー、開発ツールでの例外を捕捉してしまうので、一向に進まない。

「キャッチした例外を無視」のチェックを入れる。

ツールバースクリーンショットのボタンから「フォトライフにアップロード」→「一部のみ」をクリック。
デバッガが browser.xul で停止する。
一行目を刺しているが、スクリプトのコードではない。
F8 で再開して、コンソールを確認。

10:02:43.222 TypeError: hScreenshot.Manager is undefined            browser.xul:1:1

これか。
本来、作られていなければいけないオブジェクトができていないらしい。

尻尾は捕まえた

デバッガオプションで、「起動時にデバッガを開く」にチェックを入れて、一旦、Firefox を終了させる。
うーん、デバッガは起動されてこないし、セッションが復元されちゃうのか...

まあ良い。
ブラウザツールボックスのコンソールを確認。

10:04:54.168 TypeError: eow.uninstallObservers is undefined             eow.js:23:1
10:04:54.311 ReferenceError: PrefService is not defined                 53-Prefs.js:22:17

上記のエラーが 5回 記録されている。
ウィンドウの数だ。

extensions/screenshot@hatena.ne.jp/resources/modules/53-Prefs.js:22

    14   Prefs.prototype = {
    15       get prefs() {
    16           if (!this._prefs) {
    17               if (this._branch) {
    18                   this._prefs = PrefService.getBranch(this._branch);
    19                   // add QI
    20                   this._prefs.QueryInterface(Ci.nsIPrefBranch2);
    21               } else {
★  22                   this._prefs = PrefService;
    23               }
    24           }
    25           return this._prefs;
    26       },

resources/modules/00-utils.jsm

     41  const PrefService =
     42      getService("@mozilla.org/preferences-service;1", [Ci.nsIPrefService, Ci.nsIPrefBranch, Ci.nsIPrefBranch2]);
        ...
    111  PrefService.addObserver('', p, false);

PrefService は 00-utils.jsm で const 宣言されていて、そのソースの中で addObserver メソッドが呼ばれてる。
addObserver メソッドを呼ぶところで例外が出ていないということは、つまり、00-utils.jsm の中では PrefService は正しく取得できている、ということ。

これか。

Global lexical scope and the component loader

When loading components, such as via Cu.import, each component has its own global scope
and global lexical scope, so cross-script redeclaration issues do not arise.

Cu.import returns the global object of the imported component, so the main pitfall
is using let and const-declared bindings as properties on that scope.

https://blog.mozilla.org/addons/2015/10/14/breaking-changes-let-const-firefox-nightly-44/

英辞郎に浮気

尻尾は捕まえたような気がするので、英辞郎 on the WEB の方も確認。

エラーが出てるところ。
extensions/eow@alc.co.jp/chrome/content/eow.js

    20
    21  } catch(e) {}
    22
★  23  eow.uninstallObservers.add(eow.core.files.removeDataDir, eow.core.files);
    24
    25  }(eijiroOnTheWEB));

modules/uninstallObservers.js

    12  /**
    13   * アンインストール時の処理
    14   */
★  15  var uninstallObservers =
    16    (function () {

これが取れてない。
試しに、console.log を入れてみる。

    15  var uninstallObservers =
    16    (function () {
    17       Cu.import('resource://eow/lib/Observers.js');
    18
★  19       console.log("a-kuma3");
    20       let flags = {
    21         uninstalling: false
    21       };

アドオンの検証ができないとかで、無効化しやがった(まあ、signed は、そういうことだよな)。

about:config ページ) で xpinstall.signatures.required 設定の値を false に変更することで、
この設定を上書きし、署名の強制を無効にできます。

https://support.mozilla.org/ja/kb/add-on-signing-in-firefox?as=u&utm_source=inproduct

ちなみに、署名は META-INF にある。
extensions/screenshot@hatena.ne.jp/META-INF/

    manifest.mf ―― 構成ファイル毎のハッシュ値
    mozilla.rsa ―― 証明書?
    mozilla.sf ―― mozilla.rsa のハッシュ値?


そもそも、例外が出てるじゃんか。
console.log を入れた19行目まで行ってない。

TypeError: this.prefs is undefined
スタックトレース:
    dbg@resource://eow/core.js:135:3
    _log@resource://eow/core.js:167:6
    searchHistory._createTable@resource://eow/searchHistory.js:42:13
    searchHistory.init@resource://eow/searchHistory.js:39:5
    @resource://eow/searchHistory.js:198:1
    @resource://eow/lookup.js:6:1
    @chrome://eow/content/eow.js:15:3
    @chrome://eow/content/eow.js:3:1

modules/core.js

    134  let dbg = function() {
★  135    return this.prefs.get('debug');
    136  };
    137  let consoleOpened = false;
    138  let openconsole = function() {
    139    let w = utils.getFrontWindow();
★  140    if(this.prefs.get('debug.open_console') && !consoleOpened) {
    141      w.Application && w.Application.console.open();
    142      consoleOpened = true;
    143    }
    144  };

135行目と、140行目の this.prefs が駄目だ。
prefs は let で宣言されているので this ではアクセスできない。

    134  let dbg = function() {
★  135    return prefs.get('debug');
    136  };
    137  let consoleOpened = false;
    138  let openconsole = function() {
    139    let w = utils.getFrontWindow();
★  140    if(prefs.get('debug.open_console') && !consoleOpened) {
    141      w.Application && w.Application.console.open();
    142      consoleOpened = true;
    143    }
    144  };

よし、英辞郎の方は直った。
まさか、デバッグ用のコードで動かなくなっていたとは。

そもそも、modules/uninstallObservers.js で宣言されてる uninstallObservers が chrome/content/eow.js で参照できるかというと、こいつのおかげらしい。
chrome/content/eow.js

     8  try {
     9    let Cu = Components.utils;
    10
★  11    Cu.import('resource://eow/core.js', eow.core);
    12    Cu.import('resource://eow/utils.js', eow.utils);

モジュールが普通の JavaScript を使って、関数、オブジェクト、定数、その他あらゆる JavaScript の型のオブジェクトを生成していることに注目してください。また、モジュールは EXPORTED_SYMBOLS という名前の特別な Array を定義します。 EXPORTED_SYMBOLS 内で命名されたすべての JavaScript オブジェクトは、モジュールからエクスポートされてインポート先のスコープ内で使用可能となります。

https://developer.mozilla.org/ja/docs/Mozilla/JavaScript_code_modules/Using

構文
Components.utils.import(url [, scope]);

引数
url
  読み込まれるスクリプトの URL の文字列。URL は、ディスク上のファイルを指さなくてはなりません。JAR ファイル内を指すことがあります。
scope
  スクリプト上にインポートされる任意のオブジェクト。省略した場合、グローバルオブジェクトが使用されます。

https://developer.mozilla.org/ja/docs/Components.utils.import

第2引数の scope は、import するスクリプトの中で EXPORTED_SYMBOLS = [...] した変数が
その scope のプロパティとして参照できる(export される)。


未署名の問題は残るにしても、はてなスクリーンショットの方も直せそうな気がしてきた。

はてなスクリーンショットに戻る

はてなスクリーンショットに戻る。

resources/modules/00-utils.jsm

    215  var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
    216                            if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];

const が this のプロパティじゃなくなったことによって、EXPORT_SYMBOLS に var 宣言したものしか入ってない。
なので、参照できるはずのグローバルなシンボルが undefined になっているんだ。

他のソースを確認。

> find . -type f -print | xargs grep EXPORTED_SYMBOLS
./resources/modules/00-utils.jsm:var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
./resources/modules/00-utils.jsm:                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
./resources/modules/00-utils.jsm:Application.console.log(EXPORTED_SYMBOLS);
./resources/modules/00-utils.jsm:EXPORTED_SYMBOLS.push.apply(EXPORTED_SYMBOLS,
./resources/modules/10-event.jsm:const EXPORTED_SYMBOLS = ["EventService"];
./resources/modules/52-utils.js:var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
./resources/modules/52-utils.js:                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
./resources/modules/53-Prefs.js:var   EXPORTED_SYMBOLS = ['Prefs'];
./resources/modules/70-User.js:var   EXPORTED_SYMBOLS = ["User"];

同様の書き方は、52-utils.js にも見受けられるが、52-utils.js にはグローバルなスコープを持つ let や const がない。

気が利いた技を開発したぜ、というのが地雷になっているのは、ありがちなケースだ(人はそれを、「小賢しい」という:経験あり)。


resources/modules/00-utils.jsm の末尾に以下の 38行を追加。

    225  /*
    226     for Firefox 44 or later.
    227
    228     c.f.
    229     https://blog.mozilla.org/addons/2015/10/14/breaking-changes-let-const-firefox-nightly-44/
    230     let and const bindings, unlike their legacy counterparts, are no longer properties on the global object.
    231   */
    232  if (! EXPORTED_SYMBOLS.includes('Cc')) {   // Firefox 44 or later ?
    233     EXPORTED_SYMBOLS.push(
    234         'Cc',
    235         'Ci',
    236         'Cr',
    237         'Cu',
    238         'OS_TARGET',
    239         'IS_WIN',
    240         'IS_MAC',
    241         'IS_OSX',
    242         'Application',
    243         'PrefetchService',
    244         'DirectoryService',
    245         'ObserverService',
    246         'StorageService',
    247         'IOService',
    248         'HistoryService',
    249         'BookmarksService',
    250         'PrefService',
    251         'CookieManager',
    252         'CookieService',
    253         'PromptService',
    254         'CryptoHash',
    255         'XUL_NS',
    256         'XBL_NS',
    257         'XHTML_NS',
    258         'XML_NS',
    259         'XMLNS_NS',
    260         'getService'
    261     );
    262  }

動いた!

とりあえず、はてなスクリーンショット英辞郎は復活。
フォトライフの一括アップロードは、Flash の問題臭い。


ちなみに、.jsm の中では console が未定義。
他の箇所に、Application.console.log という書き方があったので、それを流用したが、Application も、この resources/modules/00-utils.jsm で定義されている。

    24  const Application =
    25      getService("@mozilla.org/fuel/application;1", Ci.fuelIApplication);

署名の問題

署名については、ここを読めば分かるのかな?
https://developer.mozilla.org/en-US/Add-ons/Distribution

Extension Signing

The wiki page on Extension Signing has information about the timeline, as well as responses to some frequently asked questions. The current plan is to remove the signing override preference in Firefox 46 (updated from the previous deadline of Firefox 44).
|

https://blog.mozilla.org/addons/2016/01/27/add-ons-update-76/

うわ、FF46 で署名検証をスキップする設定がなくなりそうじゃんか X-|

https://wiki.mozilla.org/Electrolysis
46βは 2016-3-7 頃なのかな。
aurora は 47.0a1 まで行ってるけど、Developer Edition なら署名の検証をスキップできないはずがない。
https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central-l10n/



ちなみに、人力検索の質問の方は id:meefla さんが、はてなブックマーク拡張の FF44 対応を見つけて回答。
http://rockridge.hatenablog.com/entry/2016/01/27/075604

let と const を全て var に置き換えるという力技。
変更点を見る感じ、はてなスクリーンショットとと同様に resources/modules/00-utils.jsm の修正だけで対応できそうだ。
https://github.com/gifnksm/hatena-bookmark-xul/commit/f1176453a4f6ee7ef5b8377ae715ffc9957c7e26

公式の修正も、同じ修正方法。
https://github.com/hatena/hatena-bookmark-xul/commit/53b044150af04ddc561d5c5695a2246fe54d0afa

let を var にするのはまだしも、const を var にしちゃ駄目だろうよ :-|


「ファイルに保存」の方

はてなスクリーンショット」の「ファイルに保存」は、元々、きちんと動作しない。

投稿者: eyoshida0330 - July 2, 2015 ・ 固定リンク ・ 翻訳
Firefox ver. 38.0.5では、クリップボードへのコピーは問題なく可能ですが、ファイルへの出力ができません。

はてなスクリーンショット拡張 :: Add-ons for Firefox

こっちも、コンソールにエラーが出てるんだな。

    01:44:48.334 NS_ERROR_XPC_NOT_ENOUGH_ARGS: Not enough arguments [nsIWebBrowserPersist.saveURI] 10-Manager.js:273:0

chrome/content/browser/10-Manager.js

    250  Manager.Save = extend({}, Manager.Base);
    251  extend(Manager.Save, {
    252      capture: function(method, finish) {
        ...
    267                  // 第 7 引数は関連するウィンドウやドキュメントから引き出されるコンテキスト
    268                  // プライベート情報が流出しないようにするために使われる
    269                  var privacyContext =
    270                          window.QueryInterface(Ci.nsIInterfaceRequestor)
    271                                .getInterface(Ci.nsIWebNavigation)
    272                                .QueryInterface(Ci.nsILoadContext);
★  273                  wbp.saveURI(uri, null, null, null, null, filePicker.file, privacyContext);
    274              }
    275              finish();
    276          });
    277      },
    278  });

void saveURI(
in nsIURI aURI,
in nsISupports aCacheKey,
in nsIURI aReferrer,
in long aReferrerPolicy,
in nsIInputStream aPostData,
in string aExtraHeaders,
in nsISupports aFile,
in nsILoadContext aPrivacyContext
);
|

https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWebBrowserPersist#saveURI%28%29

ああ、引数の数が合ってないじゃん(って、ログのメッセージのまんま)。
aReferrerPolicy Requires Gecko 36.0 とある第4引数が怪しい。
ってか、developer.mozilla.org にあるサンプルコードも、引数が 7つしかない。

https://addons.mozilla.org/ja/thunderbird/addon/abduction/reviews/
Firefox 37 で非互換があった模様。

https://github.com/dafizilla/firefox-viewsourcewith/issues/2
やっぱり、第4引数が怪しい。
ここでは、Firefox 36 から非互換があったようだ、とあるけれど。

273 行目の saveURI メソッドに引数を足してみる。
chrome/content/browser/10-Manager.js

    273  //              wbp.saveURI(uri, null, null, null, null, filePicker.file, privacyContext);
★  274                  wbp.saveURI(uri, null, null, null, null, null, filePicker.file, privacyContext);


反映には Firefox の再起動が必要。

とりあえず、お腹はいっぱいになった

「お問い合わせ」は、割りときちんと返事してもらえるので、一応、正式ルートでお願いしてみる。署名の問題もあるし。
http://d.hatena.ne.jp/a-kuma3/20160129/hatena_screenshot_patch

ただ、ファイルに保存のやつがログを見たら一発なのに、一年近く放置されてるからなあ。


英辞郎のお問い合わせフォームは、ここ。
https://www.alc.co.jp/inquiry/eow/

スクリーンショットの方が落ち着いたら、こっちも修正のお願いをしてみよう。


戦い(その2)

次なる戦いの予感

id:Kityo さんのお問い合わせに対する返信。

2016/02/23 | 07:37PM JST はてなサポート窓口 ( cs@hatena.ne.jp )
 ...
今後のサービス開発計画から、ブラウザ拡張類に関して、アップデートに合わせて
継続的に改修を行っていくことが難しいとの判断に至りました。

http://q.hatena.ne.jp/1453897063#c286888

予想はしてた。
最近のはてなは、公開サービスは「はてなブログ」と「はてなブックマーク」にしか向いてないのは、誰が見ても明らかだし、はてなブックマーク拡張の Firefox 44 対応の修正を見た感じでも開発リソースが十分にあるとは言えないのは分かってた。

お問い合わせした後に、署名検証のスキップが次の版でも使えるかなって、45βを試したときに、スクリーンショット拡張のメニューをクリックすると別のサイトに飛ばされるとか不思議な挙動をしてた。英辞郎の方も似たような感じ。
他にも問題は抱えて良そうだな、という感じ。

この年度末の何かと忙しい時期なので、見て見ないふりをしていたのだけれど……

Developer Edition

しばらくぶりに Developer Edition を起動。
xpinstall.signatures.required を true → false に変更して、再起動。
最新のものにアップデート。
  42.0a2 → 45.0a2 → 46.0a2

はてなスクリーンショットの改変をコピー

  • extensions/screenshot@hatena.ne.jp/resources/modules/00-utils.jsm
  • extensions/screenshot@hatena.ne.jp/chrome/content/browser/10-Manager.js

で、再起動。

んー、Stylish がちゃんと動いてないぞ。
プロファイルを普段使ってるものを Developer Edition ように複製して、AppData/Roaming/Mozilla/Firefox/profiles.ini を変更。
また、再起動。
Stylish がちゃんと動いてる。

一息入れてから、はてなスクリーンショットの確認。

「一部のみ」を「フォトライフにアップロード」
動かないじゃないか。

画面がグレーアウトしない。
「画面全部」、「表示部分のみ」も駄目だ。
「ファイルに保存」も駄目だなあ。

第二章の幕開け

一応、エラーの内容を確認しておこう。

14:59:51.319 TypeError: hScreenshot.Manager is undefined            browser.xul:1:1

また、こいつだ。

よく見てみると、こんなのが出てる。

17:38:26.159 expression closures are deprecated                     autoloader.js:20:36

chrome://hatenascreenshot/content/autoloader.js

    16  hScreenshot.load = function (uri) {
    17      if (uri.charAt(uri.length - 1) === "/") {
    18          var load = arguments.callee;
    19          load.getScriptURIs(uri)
★  20              .forEach(function (uri) load.call(this, uri), this);
    21          return;
    22      }

他にも...
resource://eow/lib/StringBundle.js:98:54
resource://eow/lib/Observers.js:94:46
resource://hatenascreenshot/modules/00-utils.jsm:11:35
resource://hatenascreenshot/modules/53-Prefs.jsm:28:17

"deprecated" か。もう、使えないということなんだね。
また、検索だ。

概要
JavaScript 1.8で導入された 式クロージャ 構文が廃止予定となり、近い将来削除されることとなりました。代わりに ECMAScript 6 アロー関数 構文を使用してください。

https://www.fxsitecompat.com/ja/docs/2015/expression-closures-are-now-deprecated/

なるほど。

    .forEach(function (uri) load.call(this, uri), this);

は、

    .forEach((uri) => load.call(this, uri), this);

とするか、

    .forEach(function (uri) { return load.call(this, uri); }, this);

としなきゃいけないんだ。

とりあえず、問題の所在は分かった。

長い戦い

「式クロージャ」は、まだまだある。
resource://hatenascreenshot/modules/00-utils.jsm:162:35
chrome://hatenascreenshot/content/autoloader.js:30:43
resource://hatenascreenshot/modules/00-utils.jsm:171:44

目で探すのも面倒なので、エラーを確認しては該当箇所をつぶす、というのを繰り返す。


他には、こんなエラーも出てる。

17:38:26.164 SyntaxError: missing ] after element list          00-utils.jsm:215:26
    215  var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
    216                            if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];

怪しい書き方だなあと思ってた。
どうせ、この EXPORTED_SYMBOLS は const や let の挙動が変わっててまともに動かないのだから、コメントアウトしちまおう。


うーん、このエラーが。

22:27:11.463 TypeError: hScreenshot.loadModules is not a function           autoloader.js:58:5
22:27:11.481 ReferenceError: loadPrecedingModules is not defined            53-Prefs.js:2:1

loadPrecedingModules は modules/00-utils.jsm で定義されている。


実は、こんなのを見つけてた。
http://www.ne.jp/asahi/nanto/moon/2009/11/12/hatebu-ext-impl.html

はてなブックマーク拡張の話らしい。
ファイル名で読み込みの順番を制御しているとか :-|
参考になるような、ならないような、とは思ってたのだけれど、まさか、読み込みの後先の挙動が変わってるとか?

うーん、面倒。




EXPORTED_SYMBOS の処理をコメントアウトしたのがまずいんだ。
var で宣言されているものもある。
EXPORTED_SYMBOS に、以下を追加。

'nowDebug',
'XMLHttpRequest',
'BuiltInTimer',
'p',
'log',
'UIEncodeText',
'shared',
'decodeJSON',
'encodeJSON',
'loadModules',
'loadPrecedingModules',
'extend',
'XPCOMUtils'

loadPrecedingModules の reference error は消えた。

52-utils.js でも 00-utils.jsm と同じエラーが。

SyntaxError: missing ] after element list           52-utils.js:139:26

単純にコメントアウトするんじゃなくて、素直に for 文で代入しよう。

//var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
//                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
var EXPORTED_SYMBOLS = [];
for (m in new Iterator(this, true)) {
    if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS") {
        EXPORTED_SYMBOLS.push(m);
    }
}

modules/00-utils.jsm も、同じ処理にした方が良いな。



まだまだ、式クロージャのエラーが出る。

modules/10-event.jsm:87:58
modules/52-utils.js:20:46
modules/70-User.js:65:15

70-User.js は、他にも 6行ほど式クロージャがある。
52-utils.js にも、まだある。


こんなエラーが出だした。

00:31:36.212 TypeError: this._http is not a function            52-utils.js:143:5
    139  net.get = (url, callback, errorback, async, query, headers) =>
    140      this._http(url, callback, errorback, async, query, headers, 'GET');
    141
    142  net.post = (url, callback, errorback, async, query, headers) =>
★  143      this._http(url, callback, errorback, async, query, headers, 'POST');

なぜ、140 行目でエラーが出てなくて、143 行目なのかが分からんが、アロー関数だと、this の扱いが変わってくるのは想像に難くない。
素直に、無名関数にする。

//net.get = function net_get (url, callback, errorback, async, query, headers)
//    this._http(url, callback, errorback, async, query, headers, 'GET');
net.get = function (url, callback, errorback, async, query, headers) {
        return this._http(url, callback, errorback, async, query, headers, 'GET');
    };

お、やっと起動時のエラーが消えた。

戦いの終焉

メニューから「一部のみ」を実行。
まだ、駄目だなあ。

00:38:21.687 unsafe CPOW usage                                                                  10-Manager.js:80:0
00:38:21.689 unsafe CPOW usage                                                                  10-Capture.js:26:42
00:38:21.690 unsafe CPOW usage                                                                  10-Capture.js:27:12
00:38:21.697 unsafe CPOW usage                                                                  10-Capture.js:28:8
00:38:21.698 unsafe CPOW usage                                                                  10-Capture.js:29:12
00:38:21.699 unsafe CPOW usage                                                                  10-Capture.js:30:12
00:38:21.699 unsafe CPOW usage                                                                  10-Capture.js:31:8
00:38:21.702 unsafe CPOW usage                                                                  10-Capture.js:106:8
00:38:21.729 unsafe CPOW usage                                                                  <不明>
00:38:21.731 TypeError: Argument 1 of Node.appendChild does not implement interface Node.       <不明>
    76  Manager.Base = {
    77      createFinish: function() {
    78          let sketch;
    79          if (window.content &&
★  80              (sketch = Manager.sketch.getSketckById(window.content.__sketch_switch_sid__))) {
    81              if (sketch.shownMenu) {
    82                  sketch.hideMenu();
    83
    84                  return function() {
    85                      sketch.showMenu();
    86                      p('capture finish (show menu)');
    87                  }
    88              }
    89          }

なんだ「unsafe CPOW usage」って。
window.content がアクセスできないのか...

“CPOW” stands for “Cross-process Object Wrapper”1, and is part of the glue that has allowed e10s to be enabled on Nightly without requiring a full re-write of the front-end code.
  ...
In single-process Firefox, easy and synchronous access to the DOM of web content was more or less assumed. For example, in browser code, one could do this from the scope of a browser window:

let doc = gBrowser.selectedBrowser.contentDocument;
let contentBody = doc.body;
https://mikeconley.ca/blog/2015/02/17/on-unsafe-cpow-usage-in-firefox-desktop-and-why-is-my-nightly-so-sluggish-with-e10s-enabled/

確かに、マルチプロセスが有効になってる >Nightly
オフにしてみると...




マルチプロセスに対応するとしたら...

With a CPOW, one can synchronously access and manipulate these items, just as if they were in the same process. We expose a CPOW for the content document in a remote browser with contentDocumentAsCPOW, so the above could be rewritten as:

let doc = gBrowser.selectedBrowser.contentDocumentAsCPOW;
let contentBody = doc.body;
https://mikeconley.ca/blog/2015/02/17/on-unsafe-cpow-usage-in-firefox-desktop-and-why-is-my-nightly-so-sluggish-with-e10s-enabled/

他にも、
https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Cross_Process_Object_Wrappers
https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts

うーん、よく分からん。
よくは分からんが、とりあえず動くところまでは持って行けたので、今日のところはこれで善しとするか。

ここまで...

なんだかんだで、7つもファイルをいじってしまった。

  • chrome/content/autoloader.js
  • chrome/content/browser/10-Manager.js
  • resources/modules/00-utils.jsm
  • resources/modules/10-event.jsm
  • resources/modules/52-utils.js
  • resources/modules/53-Prefs.js
  • resources/modules/70-User.js
diff -wu "original/extensions/screenshot@hatena.ne.jp" "update/extensions/screenshot@hatena.ne.jp"

diff -rwu original/extensions/screenshot@hatena.ne.jp/chrome/content/autoloader.js update/extensions/screenshot@hatena.ne.jp/chrome/content/autoloader.js
--- original/extensions/screenshot@hatena.ne.jp/chrome/content/autoloader.js    Sun Feb 28 01:46:29 2016
+++ update/extensions/screenshot@hatena.ne.jp/chrome/content/autoloader.js  Sun Feb 28 01:27:17 2016
@@ -14,7 +14,8 @@
     if (uri.charAt(uri.length - 1) === "/") {
         var load = arguments.callee;
         load.getScriptURIs(uri)
-            .forEach(function (uri) load.call(this, uri), this);
+//          .forEach(function (uri) load.call(this, uri), this);
+            .forEach(uri => load.call(this, uri), this);
         return;
     }

@@ -23,7 +24,8 @@
     var env = { __proto__: this };
     loader.loadSubScript(uri, env);
     if (env.EXPORT)
-        env.EXPORT.forEach(function (name) this[name] = env[name], this);
+//      env.EXPORT.forEach(function (name) this[name] = env[name], this);
+        env.EXPORT.forEach(name => this[name] = env[name], this);
 };

 hScreenshot.load.getScriptURIs = function (dirURI) {
diff -rwu original/extensions/screenshot@hatena.ne.jp/chrome/content/browser/10-Manager.js update/extensions/screenshot@hatena.ne.jp/chrome/content/browser/10-Manager.js
--- original/extensions/screenshot@hatena.ne.jp/chrome/content/browser/10-Manager.js    Sun Feb 28 01:46:29 2016
+++ update/extensions/screenshot@hatena.ne.jp/chrome/content/browser/10-Manager.js  Sat Feb 27 16:56:19 2016
@@ -270,7 +270,8 @@
                         window.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebNavigation)
                               .QueryInterface(Ci.nsILoadContext);
-                wbp.saveURI(uri, null, null, null, null, filePicker.file, privacyContext);
+//              wbp.saveURI(uri, null, null, null, null, filePicker.file, privacyContext);
+                wbp.saveURI(uri, null, null, null, null, null, filePicker.file, privacyContext);
             }
             finish();
         });
diff -rwu original/extensions/screenshot@hatena.ne.jp/resources/modules/00-utils.jsm update/extensions/screenshot@hatena.ne.jp/resources/modules/00-utils.jsm
--- original/extensions/screenshot@hatena.ne.jp/resources/modules/00-utils.jsm  Sun Feb 28 01:46:29 2016
+++ update/extensions/screenshot@hatena.ne.jp/resources/modules/00-utils.jsm    Sun Feb 28 01:23:52 2016
@@ -8,7 +8,8 @@
 let getService = function getService(name, i) {
     let interfaces = Array.concat(i);
     let service = Cc[name].getService(interfaces.shift());
-    interfaces.forEach(function(i) service.QueryInterface(i));
+//  interfaces.forEach(function(i) service.QueryInterface(i));
+    interfaces.forEach(i => service.QueryInterface(i));
     return service;
 };

@@ -158,7 +159,8 @@

 function loadModules() {
     var uris = _getModuleURIs();
-    uris.forEach(function (uri) Cu.import(uri, this), this);
+//  uris.forEach(function (uri) Cu.import(uri, this), this);
+    uris.forEach(uri => Cu.import(uri, this), this);
 }

 function loadPrecedingModules() {
@@ -166,7 +168,8 @@
     var self = _MODULE_BASE_URI + this.__LOCATION__.leafName;
     var i = uris.indexOf(self);
     if (i === -1) return;
-    uris.slice(0, i).forEach(function (uri) Cu.import(uri, this), this);
+//  uris.slice(0, i).forEach(function (uri) Cu.import(uri, this), this);
+    uris.slice(0, i).forEach(uri => Cu.import(uri, this), this);
 }

 function _getModuleURIs() {
@@ -212,11 +215,59 @@
     return target;
 }

-var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
-                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
+//var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
+//                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
+var EXPORTED_SYMBOLS = [];
+for (m in new Iterator(this, true)) {
+    if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS") {
+       EXPORTED_SYMBOLS.push(m);
+   }
+}
+//Application.console.log("PPP");
+//Application.console.log(EXPORTED_SYMBOLS);

 /* Debug
 EXPORTED_SYMBOLS.push.apply(EXPORTED_SYMBOLS,
                             [m for (m in new Iterator(this, true))
                                if (m[0] === "_")]);
 //*/
+
+
+/*
+   for Firefox 44 or later.
+
+   c.f.
+   https://blog.mozilla.org/addons/2015/10/14/breaking-changes-let-const-firefox-nightly-44/
+   let and const bindings, unlike their legacy counterparts, are no longer properties on the global object.
+ */
+if (! EXPORTED_SYMBOLS.includes('Cc')) {   // Firefox 44 or later ?
+   EXPORTED_SYMBOLS.push(
+       'Cc',
+       'Ci',
+       'Cr',
+       'Cu',
+       'OS_TARGET',
+       'IS_WIN',
+       'IS_MAC',
+       'IS_OSX',
+       'Application',
+       'PrefetchService',
+       'DirectoryService',
+       'ObserverService',
+       'StorageService',
+       'IOService',
+       'HistoryService',
+       'BookmarksService',
+       'PrefService',
+       'CookieManager',
+       'CookieService',
+       'PromptService',
+       'CryptoHash',
+       'XUL_NS',
+       'XBL_NS',
+       'XHTML_NS',
+       'XML_NS',
+       'XMLNS_NS',
+       'getService'
+   );
+}
diff -rwu original/extensions/screenshot@hatena.ne.jp/resources/modules/10-event.jsm update/extensions/screenshot@hatena.ne.jp/resources/modules/10-event.jsm
--- original/extensions/screenshot@hatena.ne.jp/resources/modules/10-event.jsm  Sun Feb 28 01:46:29 2016
+++ update/extensions/screenshot@hatena.ne.jp/resources/modules/10-event.jsm    Sun Feb 28 00:26:25 2016
@@ -84,7 +84,8 @@
         disposableEntries.push(entry);
         window.addEventListener("unload", function ES_dispose() {
             p("unlisten " + entry.listeners.length + " listeners");
-            entry.listeners.concat().forEach(function (l) l.unlisten());
+//          entry.listeners.concat().forEach(function (l) l.unlisten());
+            entry.listeners.concat().forEach((l) => l.unlisten());
             disposableEntries.splice(disposableEntries.indexOf(entry), 1);
             window.removeEventListener("unload", ES_dispose, false);
         }, false);
diff -rwu original/extensions/screenshot@hatena.ne.jp/resources/modules/52-utils.js update/extensions/screenshot@hatena.ne.jp/resources/modules/52-utils.js
--- original/extensions/screenshot@hatena.ne.jp/resources/modules/52-utils.js   Sun Feb 28 01:46:29 2016
+++ update/extensions/screenshot@hatena.ne.jp/resources/modules/52-utils.js Sun Feb 28 01:25:17 2016
@@ -17,7 +17,8 @@

 var sprintf = function (str) {
     let args = Array.slice(arguments, 1);
-    return str.replace(/%[sdf]/g, function(m) _SPRINTF_HASH[m](args.shift()));
+//  return str.replace(/%[sdf]/g, function(m) _SPRINTF_HASH[m](args.shift()));
+    return str.replace(/%[sdf]/g, (m) => _SPRINTF_HASH[m](args.shift()));
 };

 /*
@@ -129,12 +130,23 @@
     return xhr;
 };

-net.get = function net_get (url, callback, errorback, async, query, headers)
-    this._http(url, callback, errorback, async, query, headers, 'GET');
+//net.get = function net_get (url, callback, errorback, async, query, headers)
+//    this._http(url, callback, errorback, async, query, headers, 'GET');
+net.get = function (url, callback, errorback, async, query, headers) {
+        return this._http(url, callback, errorback, async, query, headers, 'GET');
+   };

-net.post = function net_post (url, callback, errorback, async, query, headers)
+//net.post = function net_post (url, callback, errorback, async, query, headers)
+//    this._http(url, callback, errorback, async, query, headers, 'POST');
+net.post = function (url, callback, errorback, async, query, headers) {
     this._http(url, callback, errorback, async, query, headers, 'POST');
-
+   };

-var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
-                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
+//var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
+//                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
+var EXPORTED_SYMBOLS = [];
+for (m in new Iterator(this, true)) {
+    if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS") {
+       EXPORTED_SYMBOLS.push(m);
+   }
+}
diff -rwu original/extensions/screenshot@hatena.ne.jp/resources/modules/53-Prefs.js update/extensions/screenshot@hatena.ne.jp/resources/modules/53-Prefs.js
--- original/extensions/screenshot@hatena.ne.jp/resources/modules/53-Prefs.js   Sun Feb 28 01:46:29 2016
+++ update/extensions/screenshot@hatena.ne.jp/resources/modules/53-Prefs.js Sat Feb 27 22:08:29 2016
@@ -25,7 +25,10 @@
         return this._prefs;
     },

-    get branch() this._branch,
+//  get branch() this._branch,
+    get branch() {
+       return this._branch;
+   },

     get: function Prefs_get(name) {
         let prefs = this.prefs;
diff -rwu original/extensions/screenshot@hatena.ne.jp/resources/modules/70-User.js update/extensions/screenshot@hatena.ne.jp/resources/modules/70-User.js
--- original/extensions/screenshot@hatena.ne.jp/resources/modules/70-User.js    Sun Feb 28 01:46:29 2016
+++ update/extensions/screenshot@hatena.ne.jp/resources/modules/70-User.js  Sun Feb 28 01:11:12 2016
@@ -62,10 +62,14 @@
 });

 User.prototype = {
-    get name() this._name,
-    get rk() User.rk,
-    get rks() this.options.rks,
-    get rkm() this.options.rkm,
+//  get name() this._name,
+//  get rk() User.rk,
+//  get rks() this.options.rks,
+//  get rkm() this.options.rkm,
+    get name() { return this._name; },
+    get rk() { return User.rk; },
+    get rks() { return this.options.rks; },
+    get rkm() { return this.options.rkm; },
     get info() {
         // XXX: キャッシュクリアをどうする?
         if (!this._info) {
@@ -90,8 +94,10 @@
         }
     },

-    get infoAPI() this.getPermalink('api/info?mode=detail'),
-    get haikuAPI() this.getPermalink('haiku'),
+//  get infoAPI() this.getPermalink('api/info?mode=detail'),
+//  get haikuAPI() this.getPermalink('haiku'),
+    get infoAPI() { return this.getPermalink('api/info?mode=detail'); },
+    get haikuAPI() { return this.getPermalink('haiku'); },

     getPermalink: function(url) {
         return 'http://f.hatena.ne.jp/' + this.name + '/' + (url || '');

で、結局...

  • let や const が this の属性で拾えなくなった
  • WebBrowserPersist#saveURI の引数が増えてた
  • クロージャをアロー関数にする
  • アロー関数での this の扱い
  • 古い配列内包

まとめると 5行で説明できる :-)


戦いは終わってない

FUEL is deprecated

DEPRECATION WARNING: FUEL is deprecated, you should use the add-on SDK instead.
You may find more details about this deprecation at: https://developer.mozilla.org/Add-ons/SDK/
resource://app/components/fuelApplication.js 1459 Application
resource://app/components/fuelApplication.js 726 af_ci
resource://hatenascreenshot/modules/00-utils.jsm 10 getService
resource://hatenascreenshot/modules/00-utils.jsm 26 null
chrome://hatenascreenshot/content/autoloader.js 4 null

また deprecated !

https://developer.mozilla.org/ja/Add-ons/SDK
いや、こんなに大きな話なの?

Application は、console と prefs しか使ってなくて、00-utils.jsm の外でも利用してない。
それぞれの代替を探せば、とりあえずは凌げるか。

SDK の Low Level API
https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs
https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/console_plain-text
https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/preferences_service

00-utils.jsm のコメントにあるように、Services.jsm を使う方が賢いか?
https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Services.jsm
https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIConsoleService
https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPrefService


署名の問題

  • AMO で配布していないアドオンの署名を取得するにはどうすればよいですか?
    • AMO アカウントを作成して、アドオンを提出する必要があります。アドオンの提出時に、このアドオンを AMO 非登録にするオプションを選択できるので、AMO サイトで公開しないアドオンファイルも提出できます。詳細については、Distribution Policy を参照してください。
  • 非登録アドオンの署名プロセスについて教えてください。
    • 非登録アドオンの場合、署名の申請用にファイルを提出すると、そのファイルが自動審査プロセスに掛けられます。審査を通過したアドオンは自動的に署名され、ダウンロードリンクが開発者に送付されます。このプロセスは、通常、数秒で完了します。ファイルが審査を通過しなかった場合、開発者は手動審査を要求することができ、その場合も、2 日未満で審査が完了します。これは、現在の AMO アドオンに対して実施されている審査とは異なるプロセスであり、AMO アドオンよりも迅速に処理が行われます。
アドオン/拡張機能への署名 | Mozilla Developer Street (modest)

やれば良いだけ、のような気はする。

マルチプロセス対応

元々は、Firefox が重たいところから 64bit 版に手を出して、安定版よりも進んだバージョンに手を出して嵌ったんだった。
マルチプロセスの方が軽いよなあ、普通に考えて。

けど、ちょろっと変更して対応できた、というわけにはいかないんだろうな……
https://developer.mozilla.org/ja/Firefox/Multiprocess_Firefox
https://developer.mozilla.org/ja/docs/User:wbamberg/e10s/Working_with_multiprocess_Firefox
http://piro.sakura.ne.jp/latest/blosxom/mozilla/xul/2014-11-13_e10s.htm
http://www.asukaze.net/etc/jetpack/electrolysis.html

蛇足

はてなブックマーク

https://github.com/hatena/hatena-bookmark-xul/blob/master/resources/modules/00-utils.jsm
https://github.com/hatena/hatena-bookmark-xul/blob/master/resources/modules/53-Prefs.js
メンテされてる「はてなブックマーク拡張」でも、同じような実装になってる。
クロージャも使っているし、こっちも動かなくなるよな。
ま、使ってないけど。

Firefox のリリース予定

Future branch dates

quarter merge date central aurora beta release date release ESR
Q1 2016-03-07 Firefox 48 Firefox 47 Firefox 46 2016-03-08 Firefox 45 Firefox 38.7; 45.0
Q2 2016-04-18 Firefox 49 Firefox 48 Firefox 47 2016-04-19 Firefox 46 Firefox 38.8; 45.1
Q2 2016-06-06 Firefox 50 Firefox 49 Firefox 48 2016-06-07 Firefox 47 Firefox 45.2
https://wiki.mozilla.org/RapidRelease/Calendar

むう、すぐそこまで迫ってる...


追記

配列内包 ――― Edit @2016-3-1

    215  var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
    216                            if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];

怪しい書き方だなあと思ってた。
どうせ、この EXPORTED_SYMBOLS は const や let の挙動が変わっててまともに動かないのだから、コメントアウトしちまおう。

「配列内包」というんだそうな。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Array_comprehensions

削除されたのは「古い配列内包(legacy array/generator comprehension)」?
https://dev.mozilla.jp/2016/02/firefox-46-addon-compatibility/
https://bugzilla.mozilla.org/show_bug.cgi?id=1220564

こういう書き方だったら OK だ。

    215  //var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
    216  //                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
    217  var EXPORTED_SYMBOLS = [for (m of new Iterator(this, true))
    218                            if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS") m];

でも、非標準なんだね。
標準の範囲で書くと、こうなるか。

    215  //var EXPORTED_SYMBOLS = [m for (m in new Iterator(this, true))
    216  //                          if (m[0] !== "_" && m !== "EXPORTED_SYMBOLS")];
    217  var EXPORTED_SYMBOLS = Array.from(new Iterator(this, true))
    218                             .filter(m => m[0] !== "_" && m !== "EXPORTED_SYMBOLS");