「以前のバージョンでは使えていた機能が新バージョンでは使えなくなった」時の原因の調べ方


[この記事は会社のブログとのクロスポストです](https://www.clear-code.com/blog/2019/5/14.html)。

恐らくあまり知られていませんが、Firefoxはインストール先の `browser/chrome/icons/default/main-window.ico` の位置（Firefoxのインストール先が `C:\Program Files\Mozilla Firefox\` であれば、 `C:\Program Files\Mozilla Firefox\browser\chrome\icons\default\main-window.ico`）にICO形式のアイコン画像を置くと、それがメインウィンドウのアイコンになるという[機能](https://developer.mozilla.org/ja/docs/Window_icons)がありました[^icon]。

[^icon]: 正確には、`(id文字列).ico` という名前でファイルを置いておくと、ウィンドウの内部的なID（XULの`window`要素の`id`属性の値）が一致するファイルが自動的に使われるという事になっています。

![（実際にアイコンを変えた様子）](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61387/6b030d37-59ab-596d-b23f-0f05fcdde64f.png)

この機能について、次のESR版であるESR68でも有効なのかどうかを確かめようとNightly 68で試してみたところ、アイコンが変化しないという結果を得られました。

ということは、Firefox ESR68では機能が削除されたのだな……と決めつけるのは早計です。開発版のNightlyで駄目だったからといって、そのバージョンのFirefoxが正式リリースされた後も駄目だとは限りません[^nightly-specific]。たまたまNightlyでは機能していないだけなのか、それともこの方法が使えなくなってしまったのか、きちんと裏付けを取らないとはっきりした事は言えません。

[^nightly-specific]: その逆に、Nightlyでは使えていた機能が同じバージョンのリリース版Firefoxで使えなくなる場合もあります。例えば `xpinstall.signatures.required` という設定はビルド時のオプションによって機能するかどうかが切り替わるようになっており、NightlyとDeveloper Editionでは使えますが、ベータ版および正式リリース版のFirefoxでは機能しないようになっています。

この記事では、フリーソフトウェア・OSSで遭遇したこのような挙動の変化の経緯を実装を辿って調べる方法について、Firefoxでの事例を示します。


# 調査の取っかかりを探す

この事例では「ウィンドウのアイコン」がポイントになります。Windows上で動くFirefoxはWindowsのアプリケーションなので、ウィンドウのタイトルバーのアイコンを変えるには当然Windowsのアプリケーションの流儀に則った方法を使っているはずです。

`Windows window icon win32` といったキーワードで検索してみたところ、Win32アプリケーションでウィンドウのアイコンを変える伝統的なやり方に [`WM_SETICON` という定数で定められたメッセージを使う方法](https://docs.microsoft.com/en-us/windows/desktop/winmsg/wm-seticon) があるという事が分かりました。

# 機能が動いているバージョンでどう実装されているかを調べる

確認してみたところ、前述の方法でのアイコン変更はFirefox ESR60やFirefox 66では機能していました。そこで、[Firefox ESR60のソースコード中で `SM_SETICON` という定数が使われている箇所を検索](https://dxr.mozilla.org/mozilla-esr60/search?q=WM_SETICON)してみたところ、[クロスプラットフォームな実装ではなくWindows固有の実装の部分で、この定数が使われている箇所が見つかりました](https://hg.mozilla.org/releases/mozilla-esr60/annotate/3d80c100ff930e796422d166cf80599e84f7d657/widget/windows/nsWindow.cpp#l3483)。

このメソッドの[冒頭で呼ばれている](https://hg.mozilla.org/releases/mozilla-esr60/annotate/3d80c100ff930e796422d166cf80599e84f7d657/widget/windows/nsWindow.cpp#l3462)`ResolveIconName()`という[メソッドの定義](https://hg.mozilla.org/releases/mozilla-esr60/annotate/3d80c100ff930e796422d166cf80599e84f7d657/widget/nsBaseWidget.cpp#l1537)を辿り、さらに[その中で呼ばれている `ResolveIconNameHelper()` というメソッドの定義](https://hg.mozilla.org/releases/mozilla-esr60/annotate/3d80c100ff930e796422d166cf80599e84f7d657/widget/nsBaseWidget.cpp#l1520)まで辿ってみたところ、「与えられたファイルハンドラから`icons`、`default`、とディレクトリを辿り、与えられたファイル名のアイコンファイルに対応するアファイルハンドラを得る」という処理が行われている様子がうかがえました。これはまさしく、前述の「特定の位置に置かれたアイコンファイルを参照する」という処理そのものに見えます。

この箇所は `nsWindow` クラスの `SetIcon()` メソッドに含まれていますので、今度は[このメソッドを呼んでいる箇所を探しました](https://dxr.mozilla.org/mozilla-esr60/search?q=%22-%3ESetIcon\(%22&redirect=false)。すると、[XULの`window`要素の`id`属性の値を参照してアイコンを設定しているらしい箇所](https://hg.mozilla.org/releases/mozilla-esr60/annotate/3d80c100ff930e796422d166cf80599e84f7d657/xpfe/appshell/nsXULWindow.cpp#l1473)が見つかりました。

```cpp
  // "id" attribute for icon
  windowElement->GetAttribute(NS_LITERAL_STRING("id"), attr);
  if (attr.IsEmpty()) {
    attr.AssignLiteral("default");
  }
  mWindow->SetIcon(attr);
```

ということで、先の方法でウィンドウのアイコンを変更するという機能は、この一連の箇所によって実現されている事が分かりました。

# 機能が動いていないバージョンでどう実装されているかを調べる

機能が動いているバージョンでの実装が分かったので、今度は対応する部分のNightly 68での実装を見てみました。

先ほど見ていった実装のそれぞれについてNightly 68での状況を見ていくと、ほとんどの箇所には変更が見られませんでしたが、[`nsWindow` クラスの `SetIcon()` メソッドを呼んでいる箇所](https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/xpfe/appshell/nsXULWindow.cpp#1523)に違いがありました。こちらでは、XULの`window`要素の`icon`属性の値を参照して、値があった時にだけアイコンを設定するというコードになっています。

```cpp
  // "icon" attribute
  windowElement->GetAttribute(NS_LITERAL_STRING("icon"), attr);
  if (!attr.IsEmpty()) {
    mWindow->SetIcon(attr);

    NS_ENSURE_TRUE_VOID(mWindow);
  }
```

Firefoxのソースコード検索システムでは、そのまま続けてMercurialのblameの結果を見る事ができます。そこから当該箇所の変更が行われたコミットの情報を見ると、バグトラッカー上での対応するbugの番号が記載されており、[Bug 1531836 - Each new xul window does a stat call to look for non-existant window specific icons](https://bugzilla.mozilla.org/show_bug.cgi?id=1531836)に辿り着きました。

bugの内容を読むと、どうやら起動処理の高速化の一環として、「まず存在しないファイルを探しに行く」のではなく「明示的にアイコンが指定されている時だけファイルを探しに行く」という方針に改める変更が行われ、その一環として前述の機能が削除されたという事のようです。


## まとめ

以上、フリーソフトウェア・OSSで「以前のバージョンでは使えていた機能が新バージョンでは使えなくなった」という事態に遭遇した時の原因の調べ方の、Firefoxでの事例をご紹介しました。

ユーザーの目に触れやすい機能の変更はリリースノートに記載されることが多いですし、また、開発者向けに影響の大きな変更は技術情報が別途まとめられている事もあります。しかし、この例のように影響度の小さい変更は、そのような変更があったという事自体は特に告知されないままになっている事があります。そういった場合でも、フリーソフトウェア・OSSではソースコードやバージョン管理システムの変更履歴を辿って、原因を特定したり回避方法を見つけたりする事ができます。

皆さんも是非、開発に使用しているフレームワークやライブラリの挙動の変化に気付いた時には、公開の情報から詳細を調べてみて下さい。