Titanium の WebView が言うことを聞いてくれない、そしてまだ未解決

ことの始まりは qnyp@mochiz さんの発言でした。

webViewのloadイベントで処理実行したいのに、重複発火しまくるバグがあってぐぬぬ https://t.co/y0lu6Rgq #titaniumjp
@mochiz
Hiroshi Asakura/もち

どうやら WebView のイベントハンドラは不思議な挙動を見せるようで、気になったので検証を始めました。

何が起きるのか

WebView に定義されているイベントハンドラの中に “beforeload” と “load” というものがあります。期待したい動きは WebView の読み込みの開始前に “beforeload” イベントが発火し、読み込みの終了時に “load” イベントが発火することです。

これらのイベントハンドリングはそれぞれ1回ずつであることを期待したいのですが、どうやら WebView は期待に背いた動きを見せるようです。何からしらの状態の時に、 “beforeload” も “load” も複数回発火してしまうようです。

原因を考える

検証を始めたとき、検証用のコードでは

こんな形で Google を決め打ちにしていました。すると @yagi_ さんから「(.co.jp に) リダイレクトしてるからでは」という指摘をいただいたので調べてみると、確かにリダイレクトが発生するページでは複数回発火しています。

ただ、リダイレクトが発生しないページでも複数回発火することがあり、この条件を探ってみると、どうやらクロスドメインでリソースの取得を行う場合、このドメインの数だけイベントが発火していることが分かりました。

例えば、このブログは addthis というソーシャル連携プラグインをインストールしているので、 addthis からウィジェットの取得を行います。すると、その外部リソースを取得した分だけイベントが発火するわけです。

どう解決するのか

もしも WebView を

  1. 何かをクリックしたその場で Window を作って (new して)
  2. addEventListener 設定済みの WebView を Window に add して
  3. 開く

のであれば、 addEventListener の中に

とでもすれば良いでしょう。リスナを削除していても、閉じてまた開くときは Window から作り直してイベントリスナも設定し直しなので、開いたそのときに読み込まれるページがロードされたタイミングでの処理さえできれば良いわけです。

このコードであれば1度限りのイベント発火で済ませられます。しかし、 WebView を開いてページ遷移が行われるたびに “beforeload” や “load” イベントを発火させたいとなるとこのコードではダメです。削除したイベントを復帰できません。

evalJS を使った闇 () 解決手法を考える

ハンドリングする関数に入ってくるイベントオブジェクトのプロパティ url を使って何とかできないかとか、関数内の処理にフラグを付けてあげて、処理そのものをフラグで管理するとか考えたのですがことごとく失敗…。

じゃあ WebView の evalJS 使ってみっか…と、試行錯誤しています。

モノとしてはこんな形です。まだ成功していません。あとちょっと…です。多分。開いたページに “load” イベントハンドラを仕込んで Titanium 用のタグを仕込み、 WebView の “load” イベントハンドラ内で仕込んだタグの取得にチャレンジして、成功したら Titanium 側で行いたい処理を走らせるというものです。

実際に Google を開いてみると一度だけの処理に成功するのですが、ページ遷移にまだ対応できていません。あとは clearInterval もうまく動いてくれないときがあるような…。邪悪な解決手法としてのイメージですが、まだまだ実験が必要です。

それにしても、なんで WebView の “beforeload” と “load” はクロスドメインのリソース取得とリダイレクトで発火しちゃうんでしょうね? JIRA にもチケット切られていますが、記事書いている時点で最新の Continuous Builds でも未解決でした。

Code Strong :-)