UserGestureとは
こんな経験をしたことはありませんか?
これらの挙動の違いはバグではなく、主にモバイル端末向けにWebKitの内部設定が変更されているためです。実はモバイルに限らず、ブラウザではユーザ操作(UserGesture)による呼び出しか否かでスクリプトの挙動が変わります。そして、その変わり方がブラウザによって違います。
このあたりのWebKitにおける実装を調べてみました。
- PCのChromeではJavaScriptからwindow.openでポップアップウィンドウが開けるのに、AndroidのChromeでは開けない。けど、onclickイベントのハンドラ内だったらAndroidでもwindow.openが動く
- PCのSafariでは動画の自動再生(document.onloadのタイミングでvideo要素のplayを呼ぶ)ができるのに、iPhoneのSafariでは出来ない
これらの挙動の違いはバグではなく、主にモバイル端末向けにWebKitの内部設定が変更されているためです。実はモバイルに限らず、ブラウザではユーザ操作(UserGesture)による呼び出しか否かでスクリプトの挙動が変わります。そして、その変わり方がブラウザによって違います。
このあたりのWebKitにおける実装を調べてみました。
UserGestureによる主な挙動の違い
下記の操作はクリックやタッチと言ったユーザ操作(UserGesture)起因でなければ実行できません。例えばonloadイベント等では動きません。
その他細かい相違は下記があります。
もしかすると他にもあるかもしれません。
「モバイルのみ該当」と書いたものは、WebKitの内部設定で変更できるもので一般的にモバイルではそうなっているというだけです。違う動きをするアプリも有り得ます。
- 新しいウィンドウ(ポップアップやタブ)を開くこと (モバイルのみ該当)
- window.open
- media要素の再生制御、全画面制御 (モバイルのみ該当)
- video要素やaudio要素
- ただしUserGestureによってloadが一度行われれば、以降いつでもplay可能
- androidのWebViewではWebSettings.setMediaPlaybackRequiresUserGestureで無効に出来る(デフォルトは有効)
- フォームのサブミット
- ただし、targetが指定されていればサブミット可(要検証)
- ファイル選択ダイアログを開くこと
- input type=file
- フルスクリーンAPI
その他細かい相違は下記があります。
- UserGesture起因ではなく、ページロード完了前にページ遷移が発生した場合、バックフォワードリスト(履歴)を更新しない
- リダイレクト扱い、という解釈かな
もしかすると他にもあるかもしれません。
「モバイルのみ該当」と書いたものは、WebKitの内部設定で変更できるもので一般的にモバイルではそうなっているというだけです。違う動きをするアプリも有り得ます。
何をUserGestureとするか
以下のイベントをUserGestureとしています。
もちろん、本当にOSから受け取って作られたイベントのみです。JavaScriptによって生成されたタッチイベントをdispatchしてもUserGestureにはなりません。
さらに、UserGestureによって登録されたタイマーイベントも以下の条件でUserGesture起因とします。
つまり、clickイベントでsetTimeoutによって0.5秒後に発火するように登録した関数はUserGestureと見なして実行されます。けど、その関数からタイマーイベントを登録してもそこから呼び出された関数はそうなりません。
- タッチイベント全般
- マウスのクリックイベント(カーソル移動やホイールは含まない)
- キーイベント全般
もちろん、本当にOSから受け取って作られたイベントのみです。JavaScriptによって生成されたタッチイベントをdispatchしてもUserGestureにはなりません。
さらに、UserGestureによって登録されたタイマーイベントも以下の条件でUserGesture起因とします。
- 1秒以内に発火すること
- UserGestureによって実行されたスクリプトから直接登録されたこと
つまり、clickイベントでsetTimeoutによって0.5秒後に発火するように登録した関数はUserGestureと見なして実行されます。けど、その関数からタイマーイベントを登録してもそこから呼び出された関数はそうなりません。
なぜ必要なのか
多くはセキュリティ上好ましくないからと思われます。
モバイル版だけ一部特別扱いしているのが不思議ですが、media周りは電力や通信量の削減のために自動再生を封じているのかもしれません。window.openはモバイルではそもそもポップアップが開きませんし(タブとして開かれるのが通例)、自動で開くポップアップと言えば広告が殆どですから使い勝手を考えてそうなっているのかもしれません。
mediaに関しては、ここには「いきなり自動再生されるとうざいからAppleが勝手に仕様を変更した」と書かれてますね *0 。
このあたり全部推測です。誰か詳しい方、教えてください!
モバイル版だけ一部特別扱いしているのが不思議ですが、media周りは電力や通信量の削減のために自動再生を封じているのかもしれません。window.openはモバイルではそもそもポップアップが開きませんし(タブとして開かれるのが通例)、自動で開くポップアップと言えば広告が殆どですから使い勝手を考えてそうなっているのかもしれません。
mediaに関しては、ここには「いきなり自動再生されるとうざいからAppleが勝手に仕様を変更した」と書かれてますね *0 。
このあたり全部推測です。誰か詳しい方、教えてください!
WebKitの調べ方など
このあたりの実装もversionによって挙動も変わっていく可能性が高いです(実際変わってるし)。いざという時のために自分で調べられると便利です。
調べる上で肝となるのは、UserGestureIndicatorです。
このインスタンスが生きている間は、staticなUserGestureIndicator::processingUserGestureがtrueになって(正確にはctorの引数次第ですが)、各種機能が有効になると思えばok。
ということで、UserGestureIndicatorでgrepすれば、EventHandler.cpp内で幾つかヒットして、どのイベントがUserGestureに相当するかが分かります。
あとgrepでヒットするところは単純に操作を有効にするか否かの条件に使っているだけなので難しくないと思います。例外としては、DOMTimer.cppでUserGestureのforwardingをしているのと、HTMLMediaElement.cppでload時にUserGestureかどうかで状態が変わるのは少し特殊かも。
Settingsで関係するのは、setJavaScriptCanOpenWindowsAutomaticallyとsetMediaPlaybackRequiresUserGestureだけの様に見えます。
なお、Android4.2.2のWebKitで主に調べました。最新のWebKit(r149931)と比べると、これに関してandroidはifdefが若干入っていたりするのですが大筋は変わらないです。とはいえ最新のWebKitの方が制約は若干増えてますが。ChromeはWebKitからBlinkに変わりましたが、このあたりはWebKitとまだ違いは無さそう。
androidはversionによって挙動が変わったり、このあたりも色々と流動的ですね。古いandroidだと頑張ればvideoの自動再生できたりするし(例えばICSだとvideoに関してUserGestureの制限は無い。しかし別のバグを踏んでいてそれを回避しないと再生できないという罠があり、一見UserGestureのせいなのか何なのか区別がつかなかったりする)
調べる上で肝となるのは、UserGestureIndicatorです。
このインスタンスが生きている間は、staticなUserGestureIndicator::processingUserGestureがtrueになって(正確にはctorの引数次第ですが)、各種機能が有効になると思えばok。
ということで、UserGestureIndicatorでgrepすれば、EventHandler.cpp内で幾つかヒットして、どのイベントがUserGestureに相当するかが分かります。
あとgrepでヒットするところは単純に操作を有効にするか否かの条件に使っているだけなので難しくないと思います。例外としては、DOMTimer.cppでUserGestureのforwardingをしているのと、HTMLMediaElement.cppでload時にUserGestureかどうかで状態が変わるのは少し特殊かも。
Settingsで関係するのは、setJavaScriptCanOpenWindowsAutomaticallyとsetMediaPlaybackRequiresUserGestureだけの様に見えます。
なお、Android4.2.2のWebKitで主に調べました。最新のWebKit(r149931)と比べると、これに関してandroidはifdefが若干入っていたりするのですが大筋は変わらないです。とはいえ最新のWebKitの方が制約は若干増えてますが。ChromeはWebKitからBlinkに変わりましたが、このあたりはWebKitとまだ違いは無さそう。
androidはversionによって挙動が変わったり、このあたりも色々と流動的ですね。古いandroidだと頑張ればvideoの自動再生できたりするし(例えばICSだとvideoに関してUserGestureの制限は無い。しかし別のバグを踏んでいてそれを回避しないと再生できないという罠があり、一見UserGestureのせいなのか何なのか区別がつかなかったりする)
*0 : その後は「勝手にHTML5の仕様をネジ曲げやがって、Mobile Safariはマジでクソ。HTML5の理念は死んだんだ、ネイティブアプリ開発に戻ろうぜ」と書いてて笑った。しかし現実はiOS独自仕様が事実上標準になり、そのうち本当の標準になるパターンが多いんだよね。現実は理想によって成るのではなく、強者によって作られる。