ユーザースクリプトを介してGoogle Chromeでクロスドメインデータを取得する(バグバイパス)

ChromeとChromiumには2.5年間、コンテキストスクリプト(ユーザースクリプト)から別のフレームへのクロスドメインアクセスがないというバグがありました。 通常、通常のページスクリプトで機能するもの、たとえばpostMessageを使用したクロスサイトデータ転送、他のブラウザーで問題なく機能するものは、Chromeで「セキュリティ制限」と見なされる場合がありますが、実際には、番目のバージョン。



これらの問題は、Chrome拡張機能でアクセス権が割り当てられると解決されることが知られていますが、問題の本質は、このような通常のタスクに追加のアクセス権が必要ないことです。1つのブラウザーでバグをバイパスするだけです。 そして、ユーザースクリプトをサポートするすべてのブラウザーで機能する単一のファイル拡張子を作成できます。 従来のスクリプトでは解決できないこのようなタスクの例は、Google Plusボタンから「いいね」の数に関するデータを取得することです(Google Appsでの特別な許可サーバーテクノロジーなし)。 「ベンダー」がAPIを提供しないようなタスクおよび同様のタスクの場合、ユーザースクリプトが必要(必要)であり、拡張機能が必要とするため、ブラウザーごとに個別にする必要はありません。



すべてのブラウザー(IEを除く)で動作するユーザースクリプトのGoogle+ボタンから「いいね」の数を取得する例は、 HabrAjaxで入手できます。 そこでは、「いいね」の値がフレーム内で埋め込まれたスクリプトによって読み取られ、ボタンの表面でよりコンパクトな表示のために取り出されます。 幅が約50ピクセルの4〜5桁の数値のサイドフッター。 スタイルを非表示にして、ボタンのスペースを大幅に節約します。 以下はスクリーンショットです。





他のブラウザでクロスドメインフレームからデータを取得する方法



データは、Firefox 3 +、IE8 +、Safari4 +、Opera 9.5 +、Chrome1 +でサポートされているpostMessageメソッドを使用して非常に簡単に受信されます。 古いブラウザの場合、Iframeハッシュ、Iframe名などのハックを使用します。 受信ウィンドウに、イベント「 メッセージ 」のリスナーを配置します

window.addEventListener("message", /*Function*/receiveMessage, false);
      
      





ウィンドウ(またはフレーム)ソースでは、テキストメッセージが送信されます。

 (target_window).postMessage(/*Srting*/data, /*Srting*/domainTarget);
      
      





ソースコンテキストはターゲットウィンドウを指している必要があります。2番目の引数では、アスタリスクを含む行を記述するのではなく、セキュリティ上の目的で受信者のドメインを明示的に指定することをお勧めします。 ここで、Chromeでは、コンテキストスクリプトにバグが表示されます。ブラウザはオブジェクト(ターゲットウィンドウ).postMessageを理解する必要があり、これは通常のスクリプトで発生することです。 ユーザースクリプトの場合、ウィンドウにメッセージソース以外の別のドメインが含まれていると、Chromeはオブジェクト不確実性バグ(ターゲットウィンドウ).postMessageに遭遇します。 したがって、かなり適切に設計されたメッセージ送信スキームには「松葉杖」が必要です。



Chromeのクロスドメイン転送手順の追加



受信側では-問題はその中にないため、追加はありません。 ソースでは、スクリプトを使用してウィンドウ環境にロードします。

外部フレームに埋め込まれたスクリプトで、必要なデータを読み取り、表示を待機します。

 if(gPlusFrame){ /** * check occurrence of third-party event with growing interval * @constructor * @param{Number} t start period of check * @param{Number} i number of checks * @param{Number} m multiplier of period increment * @param{Function} checkOccur event handler * @param{Function} check event condition */ var Tout = function(h){ var th = this; (function(){ if((h.dat = h.check() )) //data place h.occur(); else if(hi-- >0) th.ww = setTimeout(arguments.callee, (ht *= hm) ); })(); }; new Tout({t:320, i:6, m: 1.6 ,occur: function(){ var id = location.hash.match(/(\?|#|&)id=([^&]+)/) //frame id [or name] , w = win; id = id && id.length && id[2]; var s = w.JSON && w.JSON.stringify && w.JSON.stringify( //must supported earlier {likes: this.dat.innerHTML, frme: id}) //data format , pHost = (function(a){ //host extract from parameter (#|&)parent if(!a.match(/^https?\:\/\//)) return''; var b = document.createElement('a'); b.href = a; b.pathname = b.search = b.hash =''; return b.href.replace(/\/\??\#?$/,'') })( decodeURIComponent( (w.location.href.match(/.*(\?|#|&)parent=([^&]+)/) ||[])[2] ||'') ); try{ //'s'.wcl(s) if(!isChrome || w.parent && w.parent.postMessage){ s && w.parent.postMessage(s, pHost); //all browsers except Chrome //wcl('postpost') }else if(s) winEval(function(args){ var w = window , p1 = arguments[0] , p2 = arguments[1]; if(w.postMessage && p1 && w != w.parent){ function wpm(){ w.parent.postMessage(p1, p2); //msg with a glance Chrome bug } w.document.all ? w.setTimeout(wpm, 0) : wpm(); } }, [s, pHost]); }catch(er){wcl(er)} } ,check: function(){ return document && document.querySelector('#aggregateCount'); } }); }
      
      





そして、必要なデータ(「いいね!」の数)が検出されると、指定されたコードの一部が「try {」から始まります。 Chromeを除くすべてのブラウザーの場合、postMessage()イベントは1行で作成されます。 Chromeの場合、バグバイパスはwinEval()関数で説明されています。

 /** * evaluate script in window scope * @param{Function} fs function or string is body of function * @param{String|Array} s string or array of strings for arguments * @param{Boolean} noOnce not delete script after exec */ var winEval = function(fs, s, noOnce){ //exec function/text in other scope s = (s ||[]) instanceof Array? s ||[] : [s]; //wrap by array var fs2 = typeof fs=='function' ? (fs +'').replace(/(^\s*function\s*\([^\)]*\)\s*\{\s*|\s*\}\s*$)/g,'') //clean wrapper : fs , as =''; for(var i =0, sL =s.length; i < sL; i++) //sequential array as += (i?',':'') +"'"+ s[i].replace(/'/g,"\\'").replace(/(\r\n|\r|\n)/g,"\\\n") +"'"; fs = '(function(){'+ fs2 +'}).apply(window,['+ as +']);'; //'fs'.wcl(fs, fs2) var d = document , scr = d.createElement('script'); scr.setAttribute('type','application/javascript'); scr.textContent = fs; var dPlace = d.body || d.getElementsByTagName('head') && d.getElementsByTagName('head')[0]; dPlace.appendChild(scr); if(!noOnce) dPlace.removeChild(scr); };
      
      





それがバグを回避するすべての知恵です。 明らかに、追加の30〜40行が必要です。 これらの行は、ユーザースクリプトの他の場所で役立つ可能性がある関数であるという1つの慰めがあります。



データを他の方向に転送する必要があり、ユーザースクリプトも必要な場合(ページに通常のスクリプトを記述できない場合)、2番目のドメインにもまったく同じ追加が必要になります。 2番目のドメインをmetaディレクティブに追加することにより、同じ手順と同じスクリプトを使用することは論理的です。



フレームまたはスクリプト形式の要件



postMessageメソッドには、ドメインとターゲットウィンドウへのパスの知識が必要です。 したがって、何らかの方法でこれら2つのパラメーターをユーザースクリプトに渡す必要があります。 それらは、明示的に(例えば、 parent )指定するか、アクセス可能な環境から取得する必要があります。 スクリプトはクロスドメインフレーム(またはウィンドウ)で実行され、(Chromeの場合)他のウィンドウにアクセスする機能がないため、実際にはフレームURLでドメインを示します(これは、たとえばGoogle+ボタンで行われます)。 したがって、ターゲットウィンドウのドメイン名が/.*(\?|#|&)parent=([^&†+)/の形式でURlに書き込まれていると仮定します(これはパラメーターが認識するreg式です。たとえば、ウィジェットが呼び出されますURLのあるフレーム内:

http://some-widget-site.com/abcd.php?a=1&b=2&parent=http://some-my-site.com/some-path

または同じですが、アンカーが追加されます:

http://some-widget-site.com/abcd.php?a=1&b=2#parent=http://some-my-site.com/some-path

ターゲットドメインを設定する可能性がない場合は、スクリプトを書き換えて、明示的にまたは別の方法でドメインを設定する必要があります(オプションとして、トップウィンドウから同じpostMessageを介してドメインのユーザー名を受け入れます)。 さらに、ドメインがパラメーターで指定されていると想定されます。 したがって、特に、Google +ボタンで行われます。



この複雑なメカニズムはすべてのブラウザで機能しますが、より多くのリソースが必要になることに注意してください。 eval()は暗黙的に実行されるため、バグ20773が存在する限り、[window scope]でのスクリプトのロードは必要な場合、つまりChromeブラウザでのみ行う必要があります。メソッドが利用できない場合、クロスドメインフレームとバグトラバーサル。



このスクリプトには、ユーザースクリプトの他の場所で必要ないくつかの便利な機能があり、ライブラリー機能として使用できます。

1)プログラムを環境にロードする[ウィンドウスコープ]([global_scope]);

2)減速タイマーを使用して、独立した非同期データの出現を追跡します。



2番目の手順は最善の解決策ではありませんが、サードパーティのウィジェットの結果を確認する場合のように、スクリプトに依存しない条件の発生を確認する他の方法がない場合があります。 これは、Google +フレームで「いいね」の数を読み取る場合に当てはまります。



PS 管理者、GreaseMonkeyブログの名前を「Userscripts」に変更してください。



All Articles