はじめに
ブラウザー拡張機能のコンテンツスクリプトからは、HTML要素のすべてのプロパティーにアクセスできるわけではありません。
拡張機能の開発中にこの仕様でハマったので、この問題が発生する条件や解決策について解説します。
何をしようとしていたか
私はTwitterの拡張機能を開発するために、TwitterのWebクライアントのHTML要素からReact Propsを取得するコードを書いていました。
仕組みとしては、まずObject.getOwnPropertyNames()
を使って対象の要素のプロパティー名をすべて取得します。そして、その中からReact Propsのプロパティー名を探し、そのプロパティーにアクセスするという仕組みです。
しかし、このコードは開発者ツールのコンソールで実行すると正しく動作するものの、拡張機能にすると動作しませんでした。
そこで、挙動を詳しく調査したところ、拡張機能にするとObject.getOwnPropertyNames()
が空の配列を返すことが分かりました。まったく同じ要素に対してコンソールで実行した場合は、中身のある配列が返ってきます。
なぜ動作しないのか
では、なぜObject.getOwnPropertyNames()
が空の配列を返すのでしょうか。この問題に数日間悩んでいましたが、MDNのドキュメントに次の記述を見つけました。
コンテンツスクリプトは、普通のページスクリプトと同様に、ページの DOM にアクセスして修正できます。ページスクリプトにてなされた DOM の変更を見ることもできます。
しかし、コンテンツスクリプトは DOM の「きれいな」見た目を取得します。すなわち、
- コンテンツスクリプトはページスクリプトにて定義された JavaScript 変数を見ることができない
- ページスクリプトが組み込み DOM プロパティを再定義した場合、コンテンツスクリプトはそのプロパティの(再定義後でなく)オリジナル値を見ている
つまり、コンテンツスクリプトからHTML要素の通常のプロパティーにはアクセスできますが、ページスクリプトによって作成されたり再定義されたりしたプロパティーにはアクセスできないということです。
今回はこの仕様に該当するため、拡張機能のコード(コンテンツスクリプト)からReact Propsを参照できなかったわけです。
解決策
コンテンツスクリプトからは、ページスクリプトによって定義されたプロパティーにアクセスできないことが分かりました。この仕様はプロパティーへのアクセスを必要とするコードを、コンテンツスクリプトではない場所に記述すれば回避できます。
私の場合は、次のような対策をとりました。
- プロパティーへのアクセスを必要とするコードを、コンテンツスクリプトとは別のファイルに記述する
- 1のファイルをmanifest.jsonでWeb Accessible Resourceに登録する
- コンテンツスクリプトで動的にscriptタグを生成し、1のファイルを読み込む
こうすることで、プロパティーへのアクセスを必要とするコードはWebページで使用されている通常のスクリプトと同じように扱われるため、前述の仕様を回避できます。
まとめ
この記事では、拡張機能のコンテンツスクリプトからはHTML要素の一部のプロパティーにアクセスできないことと、その対策について解説しました。
この仕様はもともと、拡張機能のプログラムが他のプログラムと競合しないようにするためのものです。
通常はこの仕様が問題になることはありません。しかし、React Propsを参照したい場合などには、該当するコードをコンテンツスクリプトではない場所に記述する必要があります。もしもコンテンツスクリプトに記述してしまうと、プロパティーにアクセスできないということが発生するため、注意しましょう。