株式会社クリアコード > ククログ

ククログ

タグ:

GeckoエンジンにmacOSでprintToFileの機能を実装してみた話

はじめに

Geckoエンジンはクロスプラットフォームを標榜して作成されています。また、できるだけそのプラットフォームの特長を生かすため、そのプラットフォーム特有のAPIを使用するように作られている箇所もあります。印刷に関連するGeckoのコードももちろんプラットフォームごとに異なるAPIを呼ぶようになっており、通常使用する限りにおいてはどのプラットフォームも一様に印刷の機能を使用することができます。

今回の記事は nsIWebBrowserPrint に紐づいているprintという印刷のためのAPIにmacOSではPDFを印刷するのに必要な情報が渡っていなかった問題を解決した、という題材について書きます。

Geckoでの印刷の流れ

Geckoでは印刷をするときにプラットフォーム固有のAPIを使用する箇所とプラットフォーム非依存のAPIの箇所があります。

プラットフォームに依存するモジュールはサービス化されており、実行時に適切なContract ID(CID)を持ったモジュールがロードされる仕組みとなっています。このCIDはJavaScript側からも見え、アドオンやXULアプリケーション上でもこのCIDを用いて適宜必要なモジュールをロードしていきます。

印刷の流れを追うと以下のようになります。

nsDocumentViewer::Print(...)
  -> nsPrintEngine::Print(...) 
    -> nsPrintEngine::DoCommonPrint(...) 
      -> printDeviceContext->InitForPrinting(aDevice, ...) // プラットフォームごとに別々のDeviceContextが作成されるのでそれを用いる
        -> aDevice->MakePrintTarget(..) 
    -> nsPrintEngine::DoCommonPrint(...) //戻ってきたら印刷プレビューか印刷ジョブを行う

macOSのCocoaのAPIでの実装はwidget/cocoa以下に配置されています。

macOSでprintToFileが動かない問題のBug

macOSではnsIPrintSettingsServiceの関数のprintToFileへtrueを渡してもPDFヘ出力されないというBugが長年に渡って未解決です。詳細はMozillaのBugzillaのprintToFile is busted on Mac | Mozilla Bugzillaを参照してください。

macOSでGeckoのレンダリング結果をPDFへ出力できなかったのはなぜか

macOSでPDFへの出力をさせるのに必要な情報が上記Bugのパッチがコミットされる前のコードで設定されているかどうかを見ます。

macOSでCocoaのAPIからPDF印刷を行うには | ククログ にあるように、NSPrintSaveJobNSPrintJobSavingURL のような値が設定されているかを探します。

ここで、macOSでPDF印刷ができないのはnsIPrintSettingsServiceのCIDを持つ https://hg.mozilla.org/mozilla-central/file/0ca553b86af3/widget/cocoa/nsPrintSettingsX.mm にこれらの値がないかどうかを調べれば原因が分かります。

探すと見事にそのようなことをしている箇所はありませんでした。 前の記事を元にGeckoに向けたパッチを書く必要があります。

GeckoのXPCOMで生成されたクラスのAPIの振る舞いをプラットフォーム固有にする

GeckoはXPCOMの技術を用いており、インターフェースはidlファイルで定義されています。 ここではnsIPrintSettingsのidlは https://dxr.mozilla.org/mozilla-central/source/widget/nsIPrintSettings.idl です。

GeckoはC++で書かれているため、nsPrintSettings クラスに定義されているメソッドで必要なものをoverrideしてやれば目的は達成できます。

そのため、SetToFileName(const char16_t *aToFileName) をoverrideします。

diff --git a/widget/cocoa/nsPrintSettingsX.h b/widget/cocoa/nsPrintSettingsX.h
--- a/widget/cocoa/nsPrintSettingsX.h
+++ b/widget/cocoa/nsPrintSettingsX.h
@@ -51,8 +51,10 @@ public:
   void SetInchesScale(float aWidthScale, float aHeightScale);
   void GetInchesScale(float *aWidthScale, float *aHeightScale);

+  NS_IMETHOD SetToFileName(const char16_t *aToFileName) override;
+
   void SetAdjustedPaperSize(double aWidth, double aHeight);
   void GetAdjustedPaperSize(double *aWidth, double *aHeight);

diff --git a/widget/cocoa/nsPrintSettingsX.mm b/widget/cocoa/nsPrintSettingsX.mm
--- a/widget/cocoa/nsPrintSettingsX.mm
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -270,3 +275,30 @@ void nsPrintSettingsX::GetAdjustedPaperS
   *aWidth = mAdjustedPaperWidth;
   *aHeight = mAdjustedPaperHeight;
 }
+
+NS_IMETHODIMP
+nsPrintSettingsX::SetToFileName(const char16_t *aToFileName)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
+  nsString filename = nsDependentString(aToFileName);
+
+  NSURL* jobSavingURL =
+      [NSURL fileURLWithPath: nsCocoaUtils::ToNSString(filename)];
+  if (jobSavingURL) {
+    [printInfoDict setObject: NSPrintSaveJob forKey: NSPrintJobDisposition];
+    [printInfoDict setObject: jobSavingURL forKey: NSPrintJobSavingURL];
+  }
+  NSPrintInfo* newPrintInfo =
+      [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
+  if (NS_WARN_IF(!newPrintInfo)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  SetCocoaPrintInfo(newPrintInfo);
+  [newPrintInfo release];
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}

ここまででPDFに出力するための関数の実装です。

まだmacOS向けにはoverrideする必要がある関数が残っています:

  • 用紙の単位
    • NS_IMETHOD SetPaperSizeUnit(int16_t aPaperSizeUnit) override;
  • 拡大縮小倍率
    • NS_IMETHOD SetScaling(double aScaling) override;
  • 縦横
    • NS_IMETHOD GetOrientation(int32_t *aOrientation) override;
    • NS_IMETHOD SetOrientation(int32_t aOrientation) override;
  • 余白(GeckoではMerginに加えてUnwritaebleMerginという設定値が存在する)
    • NS_IMETHOD SetUnwriteableMarginTop(double aUnwriteableMarginTop) override;
    • NS_IMETHOD SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) override;
    • NS_IMETHOD SetUnwriteableMarginBottom(double aUnwriteableMarginBottom) override;
    • NS_IMETHOD SetUnwriteableMarginRight(double aUnwriteableMarginRight) override;

これらと、印刷用紙の単位変換を行なう処理を追加したものがこちらです。

このmozilla-centralのパッチではGeckoが印刷に用いている二つの単位系の対応も入っています。

  • インチ
    • kPaperSizeInches
  • ミリメーター
    • kPaperSizeMillimeters

の両方の単位系がGeckoでの印刷ではサポートされています。

そのため、

  • Inches -> Twips -> Pixels
  • Millimeters -> Twips -> Pixels

の両方を取り扱う必要があります。これは

diff --git a/widget/cocoa/nsPrintSettingsX.mm b/widget/cocoa/nsPrintSettingsX.mm
--- a/widget/cocoa/nsPrintSettingsX.mm
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -254,8 +254,13 @@ NS_IMETHODIMP nsPrintSettingsX::SetPaper
 NS_IMETHODIMP
 nsPrintSettingsX::GetEffectivePageSize(double *aWidth, double *aHeight)
 {
-  *aWidth  = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
-  *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+  if (kPaperSizeInches == GetCocoaUnit(mPaperSizeUnit)) {
+    *aWidth  = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
+    *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+  } else {
+    *aWidth  = NS_MILLIMETERS_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
+    *aHeight = NS_MILLIMETERS_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+  }
   return NS_OK;
 }

diff --git a/widget/cocoa/nsDeviceContextSpecX.mm b/widget/cocoa/nsDeviceContextSpecX.mm
--- a/widget/cocoa/nsDeviceContextSpecX.mm
+++ b/widget/cocoa/nsDeviceContextSpecX.mm
@@ -49,6 +49,19 @@ NS_IMETHODIMP nsDeviceContextSpecX::Init
   if (!settings)
     return NS_ERROR_NO_INTERFACE;

+  bool toFile;
+  settings->GetPrintToFile(&toFile);
+
+  bool toPrinter = !toFile && !aIsPrintPreview;
+  if (!toPrinter) {
+    double width, height;
+    settings->GetEffectivePageSize(&width, &height);
+    width /= TWIPS_PER_POINT_FLOAT;
+    height /= TWIPS_PER_POINT_FLOAT;
+
+    settings->SetCocoaPaperSize(width, height);
+  }
+
   mPrintSession = settings->GetPMPrintSession();
   ::PMRetain(mPrintSession);
   mPageFormat = settings->GetPMPageFormat();

のコードにより取り扱うことができます。

ここではGetCocoaUnitkPaperSizeMillimeterskPaperSizeInches を返すprotectedメソッドです。また、SetCocoaPaperSizeは印刷用紙の大きさを設定するメソッドです。

まとめ

macOSでもXULアプリケーションなどから printToFile = true を設定したときにPDFへ出力するパッチについて解説しました。 このBugのレポート日時は 2011-08-01 12:35 PDT なので、このパッチで5年半越しの問題が解決されました。 mozilla-centralの該当コミットを見るとBug番号がまだ6桁であり、周辺のコミットに紐づいたBug番号は7桁なので感慨深いですね。

つづき: 2017-03-10
タグ: Mozilla
2017-02-22

Firefox 45ESRとFirefox 47以降で、法人利用に影響のある変更が入ります

Bug 1267567が修正され、Firefox 45ESRの次のリリースとFirefox 47以降のバージョンにおいて、法人利用に影響を及ぼし得る変更が入りました。

現在Firefox 45ESR、Firefox 46、およびThunderbird 45に対してMCDの集中管理用設定ファイルを使用している環境では、同じ設定ファイルを次以降のリリースのFirefoxまたはThunderbirdでそのまま使うと、期待通りの結果を得られない可能性があります。 説明を参考に、設定ファイルの修正を行ってください。

以下、詳細と対応方法について詳しくご説明します。

影響範囲

先に「次以降のリリースのFirefoxまたはThunderbirdで」と述べましたが、実際の所、Bug 1267567はFirefox 40からFirefox 45にかけて発生していた後退バグに関する物です。 つまり正確に言うと、以下に列挙したバージョンのFirefoxには、集中管理用設定ファイルの取り扱いに問題があるということになります。

  • 40.0
  • 40.0.1
  • 40.0.2
  • 41.0
  • 41.0.1
  • 41.0.2
  • 42.0
  • 43.0
  • 43.0.1
  • 43.0.2
  • 43.0.3
  • 43.0.4
  • 44.0
  • 44.0.1
  • 44.0.2
  • 45.0 / 45.0ESR
  • 45.0.1 / 45.0.1ESR
  • 45.0.2 / 45.0.2ESR
  • 45.1.0 / 45.1.0ESR
  • 45.1.1 / 45.1.1ESR
  • 46.0
  • 46.0.1

ここに列挙したいずれかのバージョンのFirefox、およびバージョン番号が一致しているThunderbird向けに作成されたMCDの集中管理用設定ファイルは、これら以外のバージョンでは正常に読み込まれない可能性があります。

問題の概要

集中管理用設定ファイルは、日本語の文字を含める場合はUTF-8でエンコーディングする必要がありました。 しかしながら上記のバージョンでは、MCD用設定ファイル全体をUTF-8でエンコーディングしていても、文字列型の設定値に非ASCII文字(例えば、ひらがな・カタカナ・漢字など)が含まれていると、値が正常に読み込まれず空文字になってしまうという問題が起こります。

これは、Bug 1137799の修正の結果、ファイルの読み込み後の内容がUTF-8バイト列そのままで扱われず、Unicode文字列に変換されるようになったためです。

Firefoxの文字列型設定を読み書きする内部APIは、Unicode文字列ではなくUTF-8バイト列を入出力に使用する設計になっています。 Firefox 38ESRおよびFirefox 39までのバージョンでは、設定ファイル全体がUTF-8バイト列として読み込まれたまま解釈されていたため、ファイル内にUTF-8バイト列として記述された文字列がそのまま内部APIに渡される形となり、結果として文字列型の設定値が正しく読み書きされるという状況でした。

ところが、Bug 1137799の修正によりファイル全体が一旦Unicode文字列に変換されるようになった結果、文字列型設定を読み書きする内部APIに対してUTF-8バイト列ではなくUnicode文字列が渡されるようになってしまいました。 その結果、上記のバージョンでは文字列値が期待通りに読み書きされなくなってしまっていました。

この問題を回避する方法として、設定ファイル内で文字列値をlockPref()などの関数に渡す前に、unescape(encodeURIComponent("文字列"));と変換する、という方法があります。 例えば以下の要領です。

function lockStringPref(aKey, aValue) {
  aValue = unescape(encodeURIComponent(aValue));
  lockPref(aKey, aValue);
}

lockStringPref("_test.string.non-ASCII", "日本語");

これにより、Unicode文字列をUTF-8バイト列にしてから文字列型設定値を保存するAPIに引き渡す事になるため、非ASCII文字の設定値も期待通りに認識されるようになります。

しかしながら、Bug 1267567の修正により、文字列型の設定値については、Firefox自身が自動的にUnicode文字列からUTF-8バイト列への変換を行うようになりました(上記の回避策と同等の事をFirefox自身が行うようになりました)。

その結果、集中管理用設定ファイル内で上記の回避策を実施している場合には、回避策が二重に実施されるのと同じ結果となり、却って動作がおかしくなってしまうという状況が発生します。

設定ファイルの修正方法

以上のような経緯のため、今後リリースされるFirefoxでは、上記の例のような回避策が不要となります。 日本語の値であっても、設定はすべてlockPref()などの関数の値に直接記述できるようになり、結果として、Firefox 38ESRおよびFirefox 39までの挙動と同じに戻りました。 上記の回避策に類する変換を行っていた場合、変換を行なわないように設定ファイルを修正してください。 それにより、引き続き同じ設定ファイルをFirefox 47以降・Firefox 45ESR以降で使い続けられます。

まとめ

Firefox 45ESR、Firefox 47、およびThunderbird 45の今後のリリースで行われた変更のうち、法人利用への影響が懸念される変更点について、概要と対策をご紹介しました。 影響を受けるバージョンを使われる際には、上記の点にくれぐれもご注意下さい。

タグ: Mozilla
2016-05-10

FirefoxアドオンのWebExtensions移行についてMozilla Blogに寄稿しました

弊社メンバーが執筆した、従来からあるFirefox用アドオンを「WebExtensions」ベースに移行させた際の知見を解説した記事がMozilla Add-ons Blogに掲載されました。

これは、筆者がアドオンのWebExtensions移行のための調査を進める中で、アドオンを1つ移行できた事についてMozillaのアドオン開発者向けメーリングリスト上に投稿した所、他のアドオン開発者にその時の知見を共有するゲスト記事の執筆を勧められたために書いた物です。 草稿段階では「WebExtensionsへの移行」に直接は関係していない前段階の準備にあたる話が多く含まれていたため、全文は筆者個人のブログで公開することにして、Mozilla Add-ons Blogには前段階の話を省いた内容が抜粋して掲載されています。

Mozilla Add-ons Blogは英語のブログである上に、記事自体もいくつかの前提が端折られています。 ここでは、WebExtensionsそのものの背景事情も含めて上記の記事の内容を紹介します。

WebExtensionsとは

WebExtensionsは、Firefox用アドオンを開発するためのAPIであると同時に、アドオンの新しい形式そのものの名前でもあります。 現在はGoogle Chromeの拡張機能用のAPI群をFirefox上に再現する形で開発が進められており、未実装の機能の充実を急いでいる段階です。 また、Google ChromeのAPI群には含まれていない・Firefoxで独自に拡張したAPI群も追加される見込みで、既にそのようになっている部分もあります。

Firefoxアドオン向けのAPIを整備する試みは「FUEL」や「Add-on SDK(旧Jetpack)」など過去にもありました。 それらとWebExtensionsとの最大の違いは、「WebExtensionsの導入」と「既存のアドオンの仕組みの廃止」が強く紐付いているという点です。

Mozilla Firefoxはアドオンによって様々なカスタマイズができることが特徴です。 しかし、アドオンを開発するための基盤や互換性維持のための仕組みの整備が遅れていた事から、

  • Firefoxの更新に追従できず、動かなくなるアドオンが出てくる。
  • お気に入りのアドオンが動かなくなるので、ユーザーはFirefoxを更新したくなくなる。あるいは、お気に入りのアドオンが動かないということでFirefoxを使う動機が薄れ、Firefoxを使わなくなる。
  • ユーザー離れを防ぐために、アドオンに大きな影響を与える変更をFirefoxに対して入れにくくなる。Firefoxの根本的な設計を改良できない状態が続いてしまう。

といった具合に、Firefox自体の進歩にとってもユーザーにとっても望ましいとは言えない状態が発生してしまっています。

Add-on SDKはこのような状況の改善を目標の1つとしていましたが、現在は「アドオンを開発するための新しい推奨される方法」という位置付けに留まっており、すべてのアドオンをAdd-on SDKに移行させるには至りませんでした。 WebExtensionsではその反省もあってか、当初から「すべてのアドオンのWebExtensionsへの移行」と「現在のアドオンの仕組みの将来的な廃止」を前面に打ち出しています。 またそれと同時に、どのようなAPIが必要なのかをアドオン作者にヒアリングするという事も進められています。 これらの事から、過去の類似の取り組みとは腰の据え方が違うという印象を受けます。

従来型のFirefoxアドオンをWebExtensionsに移行する際のポイント

冒頭に挙げた記事では、Popup ALT AttributeというアドオンのWebExtensions移行の顛末を解説しています。

従来型のアドオンをWebExtensionsに移行する時の最も重要な点は、「今までのアドオン開発のノウハウは一旦すべて忘れて、ゼロベースで考えること」の一言に尽きます。

従来型のFirefox用アドオンは、本質的には「Firefox本体に動的に反映されるパッチ」と言うことができます。 そのため、機能性やメンテナンス性を強く意識して開発していくと、「Firefox本体の処理の中で期待通りの結果になっていない部分をピンポイントで書き換える」という、その時点でのFirefox本体の設計に強く依存した設計になってしまうことがあります。

Add-on SDKではいくつかの「抜け道」があり、それを使って「SDK以前の、今までのアドオン開発のノウハウ」を一部取り入れた開発」が可能となっていました。 それに対して、WebExtensionsにはそのような抜け道は一切用意されず、厳密にすべての要素がWebExtensionsの枠組みの中で実装される必要があります。 移行元のアドオンがFirefox本体の設計に強く依存した設計である場合、WebExtensionsへの移行は「同じ結果を得るアドオンを新規に再開発する」のに近くなります。 Popup ALT Attributesは、初出が2002年(※Firefox 1.0は2004年にリリースされたので、Firefoxより前からある事になる)と非常に歴史の古いアドオンなので、その典型的な例です。

実際の所、Popup ALT AttributeのWebExtensions移行は以下の3段階を経て実現されました。

  1. 再起動が必要なアドオンから、再起動不要なアドオン(Bootstrapped Extensions)への移行(2011年)
  2. シングルプロセス前提の設計から、マルチプロセス(e10s)対応の設計への移行(2016年2月)
  3. 従来のアドオンの枠組みから、WebExtensionsへの移行(2016年4月)

Bootstrapped Extensionsへの移行では、「動的な関数の書き換え」や「XULオーバーレイ」を廃止しました。 マルチプロセス(e10s)前提の設計への移行では、「Firefoxの内部的な処理に割り込んで任意の処理を行う設計」から、「Firefox内部の処理とは別の所で完結する設計」へ変更しました。 冒頭に挙げたMozilla Add-ons Blogに掲載された縮約版は、その後の最後のステップである、「アドオンを従来の形式からWebExtensionsの形式にパッケージングし直す」という工程にフォーカスした内容になっています。

ここでは3つの段階として書いていますが、実際には、1から3までが必ずこの順で行われる必要はありません。 例えば筆者が開発した別のアドオンでは、「1(再起動不要なアドオンへの移行)はできないが2(マルチプロセス対応)はできている」という物がいくつかあります。 既存のアドオンをWebExtensionsに移行できるかどうかは、1と2を両方とも実現できていて、且つ、必要なAPIがWebExtensionsで既に提供されていることが鍵となります。 Popup ALT Attributesはそのすべての条件を満たしていたため、つつがなくWebExtensionsに移行することができました。

まとめ

筆者が行ったFirefox用アドオンのWebExtensionsへの移行の顛末を記したブログ記事が、Mozilla Add-ons Blogに掲載されました。

いくつかの条件を満たしているアドオンであれば、WebExtensionsへの移行はそう困難ではありません。 しかし、現状のWebExtensionsにAPIが不足している事は事実です。

Mozilla自身がレガシーなアドオンの廃止とWebExtensionsへの移行の推進に本気で取り組もうとしている今のタイミングであれば、必要なAPIのWebExtensionsへの追加提案も不可能ではないと考えられます。 アドオン作者の人はぜひ自作のアドオンのWebExtensions移行を試みて、躓きやすい箇所・APIが不足している箇所を早めに明らかにし、dev-addons MLBugzilla上でコンポーネントが「WebExtensions」と明示されているBugなどを通じてMozillaにフィードバックしてみて下さい。

つづき: 2017-01-05
タグ: Mozilla
2016-05-02

第1回 法人向けFirefox導入セミナーで発表を行いました

2016年3月16日、Mozilla Japan主催の法人向けFirefox導入セミナーでFirefoxの法人利用についての紹介を行いました。

発表資料はGitHubの発表資料のリポジトリにてPDFを公開しています。 また、PDF版ではリンクになっていない参考情報などについてはMarkdown形式の元ファイルをご参照下さい。

Firefoxの法人向けカスタマイズメニューについて

発表の中で、法人利用で頻出の設定項目を一覧にしたカスタマイズメニューをご紹介しました。

Firefoxはカスタマイズ性の高さが特長の製品ですが、「なんでもできます」と言われても普通は途方に暮れてしまいますし、実際に法人で利用する場面では、行いたいカスタマイズの内容はある程度パターン化されています。 そこでクリアコードでは、業務の効率化を兼ねて、カテゴリ別にカスタマイズ項目とその実現方法を整理したカスタマイズメニューを作成・利用しています。

こちらも発表資料と同様にGitHubで公開しており、全設定項目の一覧はOpenDocument Formatのファイルとしてダウンロードしていただけます。 また、発表では触れませんでしたが、各カスタマイズ項目の検証手順のドキュメント(※現状では未完成)や、検証用のテストケース群もあります。

設定項目の一覧のODSファイルには、以下の3つのシートが含まれています。

  • 全般的な設定項目の一覧(メタインストーラの設定を含む)
  • キーボードショートカットやメニュー項目のON/OFFに関する設定の一覧
  • その他のUI項目の非表示化に関する設定の一覧

それぞれの設定項目には、設定項目の識別用のIDと共に、簡単な説明と、各選択肢の具体的な反映方法を記してあります。 ここに書かれている内容に従ってMCD用の集中管理用設定ファイルglobalChrome.css用のスタイルシートファイルを作成すれば、それらのカスタマイズが反映された状態のFirefoxを迷い無く作る事ができます。

リポジトリにあるファイルは一般的な内容になっており、実際の業務では、ここからお客様ごとのニーズに合った選択肢を選び出して使用しています。 現在はちょうどFirefox 38ESRからFirefox 45ESRへの更新が発生している時期なので、その過程で得られた知見を随時フィードバックしつつ、各項目や対応する検証手順をアップデートしているという段階です。

検証が完了していない箇所については、内容に間違いや見落としがある可能性がありますので、その場合はもし宜しければissueでの指摘やプルリクエストを頂けましたら幸いです。

カスタマイズのデモンストレーション用メタインストーラについて

発表の中で、実際のメタインストーラを実行してカスタマイズ済みのFirefoxをインストールする様子をご紹介しました。 こちらの実際のファイルもGitHubリポジトリのリリースの一部として公開しています

ただし、Firefoxのインストーラそのものの再配布はできないため、実際には「メタインストーラ作成キット一式」の状態の物をアップロードしています。 こちらのZIP形式のファイルを展開すると「FxDemoInstaller-source」というフォルダが取り出されます。この中の「resources」フォルダにFirefox 45ESRのインストーラの実行ファイルをコピーして、「FxDemoInstaller.bat」というバッチファイルを実行する事で、最終的なインストーラの実行ファイル「FxDemoInstaller-45.0.exe」が作成されます。 この「FxDemoInstaller-45.0.exe」をWindowsクライアントにコピーして実行することで、カスタマイズ済みのFirefoxが実際にインストールされます(それ以外のファイルはコピーしなくても大丈夫です)。

このデモ用メタインストーラで行われているカスタマイズ内容は、上記のカスタマイズメニューからいくつか項目をピックアップして作成しています。 カスタマイズの適用方法の例として参考にして下さい。

カスタマイズメニューとメタインストーラのご利用上の注意

上記のカスタマイズメニューとメタインストーラは、自社内での展開用などにそのままお使いいただいて問題ありません。 ただし、内容については無保証となります。

期待通りの結果を得られないなどのトラブルへの対応、個別の環境に合わせるための調査・修正、既存の項目でカバーされていないカスタマイズのご相談などについては、クリアコードのMozillaサポートサービス(有償)のご利用をご検討下さい。

次回のセミナーについて

この法人向けセミナーは毎月第3水曜日の定期開催となっており、次回は4月20日を予定しています。 参加登録はDoorkeeperのMozillaグループから行うことができ、開催会によっては事前登録時のアンケートで、あらかじめセミナーで聞きたい内容をリクエストすることもできるようになる予定です。 Firefoxの法人利用についてピンポイントで不明な点がある場合には、その旨をお伝えいただければ幸いです。

また、次回開催時には15時から16時までと17時から18時までを個別相談の時間として会場を開放し、セミナーは16時から17時の開催とする予定です。 セミナーの質疑応答で大々的には質問できないことでお困りの場合には、個別相談の時間にご相談頂くこともご検討ください。

以上、Mozilla Japanの第1回法人向けFirefox導入セミナーの発表内容の補足と、次回セミナーのご案内でした。

つづき: 2017-01-05
タグ: Mozilla
2016-03-17

Firefox・Thunderbirdを、検証用にクリーンな環境で起動する方法

FirefoxやThunderbirdはアドオンや設定によるカスタマイズが可能ですが、アドオンを多数インストールして様々な設定を変更した状態だと、何か問題が起こった場合にその原因がどこにあるのかを特定しにくくなります。

とりあえずの対応方法としては、「カスタマイズ内容をすべてリセットしてまっさらの状態に戻す」ことによって問題の解消を図るという方法があります。 FirefoxでもThunderbirdでも、「ヘルプ」メニューから「アドオンを無効化して再起動」を選択したり、Firefoxであれば「トラブルシューティング情報」のタブから「Firefoxをリセット」という操作を行ったりすると、アドオンがインストールされていない状態にすることができます。 ただ、問題の原因究明には繋がりませんし、普段使いの環境を失う事になるため、問題の深刻度によっては「そこまでしなくてもよい」と思う場合もあるでしょう。

このような場合には、一時的な空のプロファイルを使って、普段使いの環境を残したままでクリーンな状態のFirefoxやThunderbirdを並行して動かすことができます。 これを使って問題の原因を調査し、本質的な解決を図った上で改めて普段使いの環境にフィードバックすれば、うまくいけば何も失わずに問題を解消できます。

以下はFirefoxの場合の解説ですが、Thunderbirdの場合も同様ですので、適宜読み替えて読んでいただければ幸いです。

プロファイルとは?

Firefoxのユーザー設定は、インストール先とは別の「ユーザープロファイル」という場所にまとめて保存されています。 これは、WindowsであればC:\Users\(ユーザー名)\Application Data\Roaming配下に保存されています。 「トラブルシューティング情報」の「プロファイルフォルダ」欄で「ディレクトリを開く」ボタンを押すと、実際に今起動しているFirefoxのプロファイルフォルダを開くこともできます。

ユーザープロファイルは通常は上記の位置にある物が使われますが、フォルダのパスをコマンドラインオプションの-profileの値として与えると、そのフォルダをユーザープロファイルの置き場所としてFirefoxを起動することができます。 例えばF:\data\firefoxの内容をユーザープロファイルとしてFirefoxを起動する場合は、以下のようにします。

C:\> cd "C:\Program Files (x86)\Mozilla Firefox"
C:\> firefox.exe -profile "F:\data\firefox"

OS X版のFirefoxも、ターミナルからオプションを指定して起動すれば同様のことができます。

$ cd /Applications/Firefox.app/Contents/MacOS
$ ./firefox-bin -profile "/path/to/blank/directory"

この方法で空のフォルダを指定して起動すると、普段使いの環境とは全く別に、アドオンがインストールされておらず、履歴や設定も何も保存されていない、クリーンな状態なFirefoxを起動することができます。 この状態であれば、使い込まれた状態の環境では容易に試すことができないアドオンのインストールやアンインストール、複数を組み合わせた状態での動作検証などを容易に行えます。

検証を行う機会が多いのであれば、firefox.exeへのショートカットを作成し、プロパティを開いて「リンク先」の欄に以下のような形でコマンドラインオプションを追記しておくことで、コマンドプロンプトを開かずにそのプロファイルでFirefoxを起動することもできます。

"C:\Program Files (x86)\Mozilla Firefox\firefox.exe" -profile "F:\data\firefox"

ユーザープロファイルのパス自体に空白文字が含まれる場合、それ自体を一続きの文字列として示すために二重引用符で囲う必要があることに注意して下さい。

なお、Windows版とOS X版のFirefox 38およびThunderbird 38以降のバージョンでは、-profileで指定するパスの位置に実際にフォルダが存在しない場合は、フォルダが自動的に作成されるようになっています。 Linux版のFirefoxやThunderbirdでは、フォルダが存在しないとエラーになります(1136620 – -profile option does not create a new directory on linux if the directory does not exist)。

普段使いの環境のFirefoxと、新規プロファイルのFirefoxを並行して動作させる

すでにFirefoxが起動している状態で上記のコマンドを実行しても、既に起動しているFirefoxの別ウィンドウが開かれるだけだったり、タブが1つ開かれるだけだったりという結果になります。 これは、コマンドラインから起動されたFirefoxが既に起動しているFirefoxを認識して、既に起動しているFirefoxに処理を委譲し(新しいタブを開く、または新しいウィンドウを開く)、新たに起動された方のFirefox自身はすぐに終了してしまうために起こる現象です。

普段使いの環境でインターネット上の情報を検索しながら、クリーンな環境で検証を進める、というような使い方をしたい場合には-no-remoteオプションを追加で指定する必要があります。

C:\> cd "C:\Program Files (x86)\Mozilla Firefox"
C:\> firefox.exe -profile "F:\data\firefox" -no-remote

このオプションが指定されていると、新たに起動されたFirefoxは既に起動しているFirefoxのことを認識せず、両者を並行して起動できるようになります。 このオプションは、firefox.exeへのショートカットのリンク先にも指定できます。

普段使いの環境のFirefoxと、違うバージョンのFirefoxを並行して動作させる

上記の-profileオプションと-no-remoteオプションの組み合わせを使うと、普段使いのFirefoxとは別のバージョンのFirefoxを併用することも可能となります。

例えば、普段はリリース版のFirefox 43を使っていて、それとは別にFirefox 45のベータ版の動作を検証したくなることがあるでしょう。 このような場合、Firefox 45のベータ版の方を普段のインストール先とは別の場所にインストールして、これらのオプションを指定して起動すれば、普段使いのFirefox 43で調べ物をしながらFirefox 45のベータ版を試すことができます。

C:\> cd "C:\Program Files (x86)\Mozilla Firefox 45Beta"
C:\> firefox.exe -profile "F:\data\firefox-beta" -no-remote

まとめ

FirefoxやThunderbirdについて、普段使いの環境には影響を与えずに、クリーンな環境や別のバージョンを並行して起動させる方法をご紹介しました。

様々なカスタマイズを行った状態の普段使いの環境で不具合と思われる現象に遭遇して、それをBugzillaやアドオンの作者に報告しても、Firefoxの開発者やアドオンの開発者の環境では問題が再現しないという場合には、原因究明が難しかったり、時には解決を諦めないといけなかったりという事になる場合もあります。

問題の原因をきちんと切り分けて、再現条件を特定するためにも、不具合に遭遇した時はこの記事で紹介している手順でクリーンな環境のFirefoxを起動して、そこから問題が再現する状態を整えるまでの最短の手順を探るようにしましょう。

つづき: 2016-01-06
タグ: Mozilla
2015-12-25

Cert Importer 1.4の再公開と、Firefox ESR版での署名要求の無効化設定について

先日、Firefoxアドオンの署名義務化に伴っていくつかのアドオンのMozilla Add-onsのWebサイト上で配布しないように切り替えた旨をお伝えしましたが、その際、Cert Importer(証明書インポータ)だけは、その特性上サイドローディング形式でのインストールが可能な権限を設定して貰えない(本審査が棄却されていた)ために、Firefox 44以降のバージョンでは一切利用できない状態のままとなっておりました。

この度、本件について進展がありましたので、この場にてご報告いたします。

Cert Importer 1.4のリリース

Firefoxに新たに認証局証明書をインポートする際には、その証明書を信頼するかどうかの確認が表示されます。 Cert Importer 1.3までのバージョンは、この確認をスキップして証明書を完全に自動的にインポートする設計となっていましたが、この「確認をスキップする」という点が問題視されていたがために、本アドオンは本審査が棄却される結果となっていました。

これを受けて、Cert Importer 1.4では当該機能を削除し、証明書のインポート時にはユーザ自身の手による明示的な許可の操作を必要とするように仕様を変更しました。 既に、署名済みのXPIパッケージはCert ImporterのGitHubリポジトリのリリース一覧のページからダウンロードできるようになっています。

利便性の点では後退する結果となりましたが、悪意ある攻撃者が攻撃用の証明書を完全に自動でインストールするというような悪用のされ方を防ぐためには避けられない変更ですので、何卒ご容赦下さい。

Firefox 45ESRでの署名要求無効化について

Firefox 44以降のバージョンではサイドローディングであるかどうかに関わらず、アドオンにはMozillaによる署名が必須となります。 アドオンのXPIパッケージへの署名申請を自動化する仕組みは既に用意されており、大抵の場合はこれにより署名申請の手間を軽減できるため、大きな混乱は無いと予想されますが、それでも解決不可能な問題が残ります。 それは、社外秘の情報を含むアドオンの取り扱いについてです。

というのも、現在アドオンに有効な署名を施すことができるのはMozilla Corporationのみであるため、署名を施してもらうためにはアドオンのインストール用パッケージをMozilla Corporationに引き渡す必要があります。 社内の取り決めや他社とのNDAの都合などからファイルを社外に持ち出せないアドオンの場合、署名を施せないため、やはりFirefox 44以降のバージョンでは利用できないという事態が発生してしまいます。

現在リリースされているFirefox 43までのバージョンや、開発者向けの検証用という位置付けの「Aurora」および「Nightly」では、about:configなどを使って隠し設定のxpinstall.signatures.requiredの値をfalseに変更することで、署名要求を無効化することができます。 Firefox 44以降のリリース版およびベータ版ではこの設定自体が機能しなくなり、署名要求を無効化できなくなることが予告されています。

これについて、まだオフィシャルな場所では情報が公開されていませんが、企業利用向けの長期サポート版であるESR版(Firefox 45ESRおよびそれ以降のバージョン)では、署名要求を無効化する隠し設定を引き続き利用できる状態とする方針である旨、Mozilla Japanから連絡を頂いております。 社外に持ち出せないアドオンを必要とする場合には、この設定を伴ってESR版Firefoxを使うという運用を選択するようにして下さい。

ただしこの措置は、ESR版の運用は技術的な知識のあるシステム管理者がアドオンの安全性を確認した上で行う事が想定されるためということになります。 一般のユーザがESR版でこの設定を使用し未署名のアドオンを使用することは推奨されませんので、ご注意下さい。

つづき: 2016-01-06
タグ: Mozilla
2015-12-22

Firefox用アドオンのデジタル署名の自動化

このククログでも既に何度か触れていますが、Firefox 44以降ではアドオンのインストール用パッケージ(XPIパッケージ)について、Mozillaによるデジタル署名が施されていない物はインストールできないようになります。

この変更は、一般の利用者にとっては使い勝手の面であまり変化はありませんが、アドオンを開発・公開する側にとっては大きな影響があります。 その1つとして、アドオンをMozilla Add-ons以外の方法で頒布する場合に必要なステップが1つ増えるという事が挙げられます。

というのも、Mozillaによるデジタル署名が施された状態のXPIパッケージを入手するには、Mozilla Add-onsのWebサイトにXPIパッケージをアップロードし、場合によってはエディターによる承認を得た上で、署名済みのXPIパッケージを改めてダウンロードしなくてはなりません。 この作業自体は簡単なのですが、「Webサイトを訪問して、ログインして、フォームを使ってファイルを送信して、署名済みのファイルをダウンロードする」という手作業が必要でした。 このような定型的な作業を毎回人の手で行うのは非効率であるのみならず、ミスの発生原因になりうるので、可能であれば自動化したいところです。

このような状況を受けて、Mozilla Add-onsのWebサイト側にSigning APIという物が実装され、上記の作業のある程度の自動化が可能になりました。

上記リンク先の記事では具体的な手順が案内されていますが、「このようにすればコマンド一発で可能」というものではないため、自分でやるには若干ハードルが高いです。 そのため、アドオンSDKに含まれるコマンドラインツールのjpmに署名APIを使ってXPIに署名するためのサブコマンドが追加されました。 SDKを使ってアドオンを開発する場合は、これを使うことで署名の申請までを自動化することができます。

それとは別のものとして、SDKを使わないでアドオンを開発する場合向けに、XPIパッケージの署名申請を支援するBashスクリプトを作成しました。 この記事では、jpmではなくこのスクリプトを用いてXPIパッケージへのデジタル署名を申請する手順を解説します。

※2015年12月21日追記:上記のjpmコマンドは、SDKを導入しなくてもjpmというNPMパッケージ単体の導入により利用可能になるとのことです。 よって、基本的にはアドオンのXPIパッケージへの署名にはjpmのサブコマンドsignを使うことをお薦めします。 以下で紹介するスクリプトは、jpm signではカバーできない範囲のニーズを満たす必要がある場合にのみ使うようにして下さい。

スクリプトの動作準備

署名申請の支援スクリプトは、Ubuntu 14.04LTS上で動作を確認済みです。 スクリプトそのものはBashスクリプトですが、APIへのアクセスにcurlを使用します。 また、Node.js用のJWTライブラリを使うため、nodejsコマンドおよびnpmコマンドが利用可能になっている必要があります。

以下の説明では、例としてUbuntuでの操作手順を示します。 他の環境で使う場合は、環境依存の記述を適宜読み替えて下さい。

署名申請にの支援スクリプトは複数に分かれているため、スクリプトは単体でダウンロードするのではなく、スクリプト群一式を作業ディレクトリに保存するようにして下さい。 例えば以下の要領です。

$ mkdir -p /tmp/signxpi
$ cd /tmp/signxpi
$ git clone https://github.com/piroor/makexpi.git

APIキーの生成

署名の申請を自動化するにあたっては、JSON Web Token(JWT)用のAPIキーが必要です。 これは、Mozilla Add-onsの開発者センターのAPIキー管理画面で生成できます。 本記事執筆時点では用途別の複数APIキーの使い分けは考慮されていないようで、生成できるAPIキーは一種類のみとなっています。 既にAPIキーを生成済みの場合、APIキーを生成し直すと古いAPIキーは無効になる模様ですのでご注意下さい。

APIキーは、「JWT発行者(JWT issuer)」と「JWT秘密鍵(JWT secret)」の2つの文字列のペアとして生成されます。 APIキーを準備できたら、次のステップに進みます。

署名申請の送信

署名申請を行うスクリプトは、リポジトリ内にあるsign_xpi.shです。 以下のようにオプションを指定して実行します。

$ makexpi/sign_xpi.sh -p "XPIファイルのパス" \
                      -o "署名済みファイルの保存先ディレクトリのパス" \
                      -k "JWT発行者" \
                      -s "JWT秘密鍵" \
                      -d

例えば、XPIファイルがカレントディレクトリにある「myaddon.xpi」で、署名済みのファイルを同じくカレントディレクトリに保存するのであれば、以下のようになります。

$ JWT_ISSUER=user:xxxxxx:xxx
$ JWT_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
$ makexpi/sign_xpi.sh -p "./myaddon.xpi" \
                      -o "./" \
                      -k "$JWT_ISSUER" \
                      -s "$JWT_SECRET" \
                      -d
The file will be uploaded for signing.

The file will be uploaded for signing.という出力を得られたでしょうか? 実は、この時点ではまだファイルは送信されていません。 -dはいわゆるdry runを行うためのオプションで、このオプションが指定された場合、実際のファイル送信は行われないようになっています。 dry runに成功したら、改めて-dオプションを外し、実際にファイルを署名申請します。 (慣れてきたら、dry runでの確認はスキップして構いません。)

$ makexpi/sign_xpi.sh -p "./myaddon.xpi" \
                      -o "./" \
                      -k "$JWT_ISSUER" \
                      -s "$JWT_SECRET"

申請に成功し、署名済みのファイルが得られた場合、署名済みのファイルがカレントディレクトリに保存されます。

なお、署名APIは、「アドオンの新しいバージョンをアップロードするためのAPI」という形をとっているため、この時点で、署名申請を行ったバージョンは当該アドオンの新バージョンとして登録されることになります。 この時、当該バージョンは詳細情報が入力されていない状態になりますので、公開用の(listedな)アドオンの場合は、別途バージョン一覧から詳細情報を入力する必要があります。

後から署名済みのファイルをダウンロードする

レビュー待ちなどの理由で署名済みのファイルをすぐには入手できなかった場合、Not signed yet. You must retry downloading after signed.というメッセージが表示されてエラーとなります。 この場合、レビューが完了して署名済みファイルをダウンロードできるようになった段階で再挑戦する必要があります。 また、署名済みのファイルを紛失してしまった場合にも、再度ダウンロードしなくてはなりません。

このようなケースでは、download_signed_xpi.shという別のスクリプトを使います。

$ makexpi/download_signed_xpi.sh -i "アドオンの内部ID" \
                                 -v "署名済みファイルをダウンロードするバージョン" \
                                 -o "署名済みファイルの保存先ディレクトリのパス" \
                                 -k "JWT発行者" \
                                 -s "JWT秘密鍵"

例えば、アドオンのIDが「myaddon@example.com」で、「バージョン1.5」の署名済みのファイルをカレントディレクトリに保存するのであれば、以下のようになります。

$ JWT_ISSUER=user:xxxxxx:xxx
$ JWT_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
$ makexpi/download_signed_xpi.sh -i "myaddon@example.com" \
                                 -v "1.5" \
                                 -o "./" \
                                 -k "$JWT_ISSUER" \
                                 -s "$JWT_SECRET"

まとめ

De-coupling Reviews from Signing Unlisted Add-ons | Mozilla Add-ons Blog私的翻訳)によると、非公開(unlisted)のアドオンについては、人力のレビューを経ずに即座に署名済みのXPIパッケージを入手できるようになっている模様です。

ただ、人力レビューが必要な場合であっても、この署名APIによって「新バージョンリリース時のファイルのアップロード」までは自動化できます。 署名義務化の発表以降、アドオンの開発や提供、検証にあたって手間が増えた部分はいくつかありますが、こういった部分でささやかながらリリースの自動化をしやすくなってくれたことは、素直に有り難いことだと言えるでしょう。

タグ: Mozilla
2015-12-17

Thunderbirdのエラーコンソールの出力結果を効率よく収集する方法

FirefoxやThunderbirdには、内部で発生したエラーの情報やデバッグ用の情報を表示するためのコンソールが内蔵されています。 FirefoxでもThunderbirdでも、「Ctrl-Shift-J」というキーボードショートカットでこのコンソールを開く事ができます。

FirefoxのエラーコンソールはWeb開発ツール由来の物となっており、表示された内容を簡単に選択してクリップボードにコピーできます。 しかしながら、Thunderbirdのエラーコンソールは設計上の都合から、1つ1つのログをコピーする事しかできず、また表示可能なログの最大件数も250件に制限されています。 大量のメッセージが発生する状況ではすぐにログが流れてしまって、有効な情報を収集することができません。

以下は、この制限を実証するための「300件のエラーを報告する」というサンプルコードです。

for (var i = 1; i <= 300; i++) { Components.utils.reportError('Error '+i); }; 

これをエラーコンソール内の「コード」欄にコピー&ペーストして「コードを評価」ボタンをクリックして実行すると、それまで表示されていたログや新規のログの300件のうち最初の50件までは消えてしまい、「Error 51」から「Error 300」までの250件だけが見えるという結果になることが分かります。 実際の運用上で問題が発生している時に、エラーコンソール内をすさまじい速度でログが流れていってしまうと、原因の切り分けのための調査自体もはかどりません。

エラーコンソールの最大表示ログ件数を増やす

幸い、Thunderbirdのエラーコンソールでは任意のコードを実行できます。 この機能を使えば、エラーコンソール自身の最大表示件数を変える事ができます。

以下のコードをエラーコンソール内の「コード」欄にコピー&ペーストし、「コードを評価」ボタンをクリックして実行すると、ログの最大件数が99999件に拡大されます。

Components.utils.import('resource://gre/modules/Services.jsm');var box = Services.wm.getMostRecentWindow('global:console').document.getElementById('ConsoleBox'); var descriptor = { value: 99999 }; Object.defineProperty(box, 'limit', descriptor); Object.defineProperty(box, 'fieldMaxLength', descriptor);

実際に、続けて以下のようなコードを実行してみると、それまでのログと併せてすべてのログが表示されることを確認できます。

for (var i = 1; i <= 300; i++) { Components.utils.reportError('New Error '+i); }; 

この方法は、エラーコンソールを開いた時点より後に出力されるログに対してしか利用できません。 (エラーコンソールを開く前に大量にログが出ている場合、それらは既に制限を超えて流れてしまっているために、この方法では見る事ができません。) とはいえ、エラーコンソールを開いた後の任意のタイミングで再現試験を行える状況であれば十分に有用でしょう。

なお、この変更は「エラーコンソール」のウィンドウを閉じるまでの間のみ有効で、エラーコンソールを開き直したりすると最初の状態(最大250件のみ表示)に戻ります。

エラーコンソールの内容をクリップボードにコピーする

また、以下のコードを実行すれば、その時点でコンソールに表示されているすべてのログをまとめてクリップボードにコピーできます。

Components.utils.import('resource://gre/modules/Services.jsm');var text=[];for (var row of Services.wm.getMostRecentWindow('global:console').document.getElementById('ConsoleBox').mConsoleRowBox.children) { if (row.boxObject.height > 0) { text.push(row.toString()); } }; Components.classes['@mozilla.org/widget/clipboardhelper;1'].getService(Components.interfaces.nsIClipboardHelper).copyString(text.join('\n---\n'));

これなら、ログの件数が多い場合でも容易にログを保存することができます。 アドオンのエラー情報を作者に伝える場合や、バグトラッキングシステムに問題を報告する時などにご活用下さい。

つづき: 2016-01-06
タグ: Mozilla
2015-12-15

Firefoxを意図的にクラッシュさせる方法

Firefoxの導入時の要件として、クラッシュ時のレポートを送信しないようにするという設定を行う事があります。 この設定が意図通りに反映されているかどうかを確認するために、Firefoxが実際にクラッシュした時の様子を観察したい場合があります。

Firefoxには既知で且つ未修正のクラッシュバグがいくつかあるため、それを突くようなコードを実行すればFirefoxをクラッシュさせる事ができます。 しかし、クラッシュバグは再現性が低い物もありますし、そもそも新しいバージョンのFirefoxではクラッシュしないように修正されていることも多いです。 安定してFirefoxをクラッシュさせる方法を把握しておけば、Firefoxをクラッシュさせるための方法をその都度あれこれ調べなくても済みます。

Firefoxにはjs-ctypesという、JavaScriptからC言語製の共有ライブラリの機能を直接呼び出す機能が含まれており、これを使えば、比較的簡単にFirefoxをクラッシュさせられます。 例えば以下のようなコードを実行すれば、使用中のメモリ領域を強制的に解放してメモリ破壊を引き起こし、Firefoxをクラッシュさせる事ができます。

(function(){
Components.utils.import('resource://gre/modules/ctypes.jsm');
// Firefoxの構成ファイルの1つであるnss3.dll(nss3.so)をオープンする。
var library = ctypes.open(ctypes.libraryName('nss3'));
// 指定したアドレスのメモリを解放する関数を取得する。
var PR_Free = library.declare(
  'PR_Free',
  ctypes.default_abi,
  ctypes.void_t,
  ctypes.voidptr_t
);
// 適当なアドレスを指定してメモリを解放する。
var ptr = new ctypes.voidptr_t(1);
PR_Free(ptr);
})();

上記のスクリプトを実行するには、js-ctypesを利用する権限(chrome権限)が必要です。 「スクラッチパッド」を使って実行する場合は以下の手順となります。

  1. Firefoxを起動する。
  2. about:configを開き、devtools.chrome.enabledの値をtrueに設定する。
  3. Shift-F4を押すか、「開発ツール」メニューから「スクラッチパッド」を選択してスクラッチパッドを起動する。
  4. 「実行環境」メニューから「ブラウザ」を選択する。
  5. 上記のスクリプトを貼り付けて、ツールバー上の「実行」をクリックする。

なお、メモリ破壊を引き起こしてFirefoxをクラッシュさせると履歴や設定などのデータが破損する場合があります。 この方法を実際に試す際は、データが壊れてもよいテスト用のプロファイルを使う事を強くお勧めします。

つづき: 2016-01-06
タグ: Mozilla
2015-12-14

__noSuchMethod__をES6 Proxyで代替する方法

FirefoxのJavaScript実行エンジンであるSpiderMonkeyでは、ECMAScriptの仕様にはないSpiderMonkey固有の拡張機能をいくつか利用できますが、その中の1つとして__noSuchMethod__があります。

これはRubyなどでいう「メソッドミッシング」を実現するための仕組みで、あるオブジェクトに__noSuchMethod__という名前のメソッドを定義しておくと、呼び出されたメソッドが存在しなかった時に代わりに__noSuchMethod__メソッドが呼ばれるというものです。 具体的には以下のように使います。

// インスタンスに直接定義する場合
var object = {};
object.__noSuchMethod__ = function(name, args) {
  alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
object.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]

// クラスの一部として定義する場合
function MyClass() {
}
MyClass.prototype.__noSuchMethod__ = function(name, args) {
  alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};

var instance = new MyClass();
instance.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]

こういった機能はいわゆるメタプログラミングにあたり、普段の開発で頻繁に利用する物ではありませんが、フレームワーク的な物を開発する場面では重宝します。 Ruby on Railsなどの「設定より規約」というルールでよく見られる「このような名前でパラメータを与えておけば自動的に、与えた名前に基づくこのような名前のメソッドが利用可能になる」という仕組みを、より自然な形で実現させられます。

ただ、前述の通りこれはSpiderMonkey固有の機能なので他のJSエンジンでは利用できませんし、SpiderMonkeyにおいてもFirefox 44で廃止されました。 代わりとして、より汎用的なProxyを使う事が推奨されています。

とはいうものの、Proxy__noSuchMethod__とは全く違う様式を取っており、しかも機能が豊富なので、単純に「__noSuchMethod__と同じ事だけをしたい」という場合にどうすればよいのか分かりにくいです。 また、__noSuchMethod__を使っていた箇所の設計を見直してProxyを適切に使うようにするとしても、それなりに規模が大きいコードの場合、フレームワーク的な基盤部分の設計を大きく変えてしまうと変更の影響範囲が大きくなって後が大変です。

結論を述べると、先のような例であれば、以下のようにしてProxy__noSuchMethod__を代替できます。

// インスタンスに直接定義する場合
var object = {};
object.__noSuchMethod__ = function(name, args) {
  alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};
  // 追加箇所:ここから
object = (function(source) {
  var cache = {};
  return new Proxy(source, {
    get: function(target, name) {
      if (name in target)
        return target[name];
      return cache[name] || cache[name] = function(...args) {
        return target.__noSuchMethod__.call(this, name, args);
      };
    }
  });
})(object);
  // 追加箇所:ここまで
object.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]

// クラスの一部として定義する場合
function MyClass() {
  // 追加箇所:ここから
  var cache = {};
  return new Proxy(this, {
    get: function(target, name) {
      if (name in target)
        return target[name];
      return cache[name] || cache[name] = function(...args) {
        return target.__noSuchMethod__.call(this, name, args);
      };
    }
  });
  // 追加箇所:ここまで
}
MyClass.prototype.__noSuchMethod__ = function(name, args) {
  alert('NO SUCH METHOD: '+name+' , '+JSON.stringify(args));
};

var instance = new MyClass();
instance.toStringg('a', 'b'); // NO SUCH METHOD: toStringg , ["a","b"]

これまで__noSuchMethod__を使っていたコードに対して、例で「追加箇所」と記した部分を付け加えることで、同等の結果を得られるようになります。

ただし、厳密には全く同一というわけではありません。 __noSuchMethod__はメソッド呼び出しのみが対象になりますが、Proxyを使用したバージョンでは「未定義のプロパティにアクセスされたら、__noSuchMethod__を実行する関数オブジェクトを返す」という形になっているので、undefinedが返される事を期待して単純にvar hasProperty = !!instance.something;のようにした場合にも影響が出てしまいます。 この例であれば、"something" in instanceのような判別方法を使うという風に、「未定義のプロパティにアクセスするとundefinedだけでなく関数が返ってくる事もある」という前提で対象オブジェクトを処理するように気をつける必要がありますので、ご注意下さい。

また、例をよく読むと分かりますが、最初からProxyを使って書くのであればさらに無駄のない書き方もできます。 ここではあくまで、これまで__noSuchMethod__を使っていた既存のそれなりの規模があるコードに対して、最小限の変更で同様の結果を得られるようにするという目的に特化しているために、このようになっています。 このようなアドホックな対応で済ませるよりは、ProxyのAPI設計に即した使い方になるようにコードの設計を見直す方が基本的には望ましいと言えますので、実行に移すかどうかはメリットとデメリットをよく考えてから判断しましょう。

タグ: Mozilla
2015-12-03

2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|
タグ:
RubyKaigi 2015 sponsor RubyKaigi 2015 speaker RubyKaigi 2015 committer RubyKaigi 2014 official-sponsor RubyKaigi 2014 speaker RubyKaigi 2014 committer RubyKaigi 2013 OfficialSponsor RubyKaigi 2013 Speaker RubyKaigi 2013 Committer SapporoRubyKaigi 2012 OfficialSponsor SapporoRubyKaigi 2012 Speaker RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer badge_speaker.gif RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer
SapporoRubyKaigi02Sponsor
SapporoRubyKaigi02Speaker
RubyKaigi2009Sponsor
RubyKaigi2009Speaker
RubyKaigi2008Speaker