📜 ⬆️ ⬇️

Getting cross-domain data in Google Chrome via user scripts (bypassing a bug)

In Chrome and Chromium for 2.5 years, there is a bug of the lack of cross-domain access to another frame from the context script (user script). What works normally in a regular page script, for example, intersite data transfer using postMessage and that works without problems in other browsers, is sometimes considered a “security restriction” in Chrome, but in fact it is a common and recognized bug noted with 4 th version.

It is known that such problems are solved in Chrome extensions, when they are assigned access rights, but the crux of the matter is that additional access rights for such ordinary tasks are not required, all that is needed is to bypass the bug in one browser. And then we can write the extension working in all browsers that support users scripts in one file. An example of such a task that cannot be solved by ordinary scripts is getting data on the number of “likes” from the Google Plus button (without special authorization in Google Apps and without server technologies ). For such and similar tasks, in which the “vendor” does not provide an API, a user script is necessary (necessary), and it does not have to be individual for each browser, as required by the extension.

An example of getting the number of “likes” from the Google+ button in userscript, which works in all browsers (except IE), is available in HabrAjax . There, the value of “likes” is read inside the frame by the embedded script and is brought out for a more compact display right on the button surface. Footnote for 4-5-digit numbers about 50 pixels wide. hiding styles, which greatly saves space on the button. Below is a screenshot.


How to get data from a cross-domain frame in other browsers


The data is obtained quite simply using the postMessage method supported in Firefox 3+, IE8 +, Safari4 +, Opera 9.5+, Chrome1 +. For older browsers use hacks like Iframe hash, Iframe name. In the receiver window, put the event listener " message ":
window.addEventListener("message", /*Function*/receiveMessage, false); 

In the window (or frame) source send a text message:
 (target_window).postMessage(/*Srting*/data, /*Srting*/domainTarget); 

The source context must point to the target window, and in the second argument, it is recommended to explicitly indicate the receiver's domain for security purposes, rather than writing a string containing an asterisk (in the sense, “any domain”). It is then that a bug pops up in Chrome in context scripts - the browser must provide an understanding of the object (target window) .postMessage , and it happens in ordinary scripts in it. For users, Chrome has an object ambiguity bug (target window) .postMessage if the window contains a different domain - other than the source of the message. Therefore, a “crutch” is required for a sufficiently well-shaped message transfer scheme.
')

Addition of cross-domain transfer for Chrome


On the receiver side, there are no additions, because the problem is not in it. In the source, we use script loading into the window environment.
In the embedded script in another frame we read, waiting for the appearance, we need the data.
 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'); } }); } 

And upon detection of the required data (the number of likes), a part of the above code starts, starting with “try {”. For all browsers except Chrome, create a postMessage () event in one line. For Chrome, the bug is performed as described in the winEval () function.
 /** * 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); }; 

That's all the wisdom of bypassing the bug. As you can see, an additional 30-40 lines are required. There is one consolation that these lines are functions that can be useful in other places of user scripts.

If you need to transport data to the other side and you also need a user-script (if you can’t write a regular script on the page), the same addition will be needed for the second domain. It is logical to use the same procedure and the same script, adding its second domain to the meta-directive.

Frame or Script Format Requirements


The postMessage method requires knowledge of the domain and the path to the target window. Therefore, it is required to transfer these 2 parameters to the user script in some way. They must be specified either explicitly (for example, parent ), or taken from an accessible environment. Since the script runs in a cross-domain frame (or window) and does not have (for Chrome) access to other windows, in practice they indicate the domain in the frame URL (this is done, for example, in the Google+ button). Therefore, suppose that the target window's domain name is written in URl in the format of /. in frame with URL:
http://some-widget-site.com/abcd.php?a=1&b=2&parent=http://some-my-site.com/some-path .
Or the same, but the anchor will be added:
http://some-widget-site.com/abcd.php?a=1&b=2#parent=http://some-my-site.com/some-path .
If there is no such possibility to set the domain of the target, the script should be rewritten to specify the domain explicitly or in another way (as an option, use the same postMessage from the top window in the user script to accept the domain name). In the following, it is assumed that the domain is specified in the parent parameter. So, in particular, done in the Google+ button.

Note that this complex mechanism will work in all browsers, but it requires more resources, because eval () is implicitly executed, so loading the script in the [window scope] should only be done where necessary, namely, in the Chrome browser, as long as bug 20773 exists. Therefore, in the working script, a branch is made that checks postMessage crossdomain frame and perform a bug bypass if the method is not available.

The script has a couple of useful functions that are needed in other places of the user script and could be taken as library functions:
1) loading programs into the [window scope] ([global_scope]) environment;
2) tracking the emergence of independent asynchronous data using a slow down timer.

The second procedure is not the best solution, but sometimes there is no other way to learn about the conditions independent of its script, as in the case of checking the results of a third-party widget. Reading the number of “likes” in the Google+ frame just applies to this occasion.

Ps . Admins, rename the blog GreaseMonkey, please, finally, in "User scripts".

Source: https://habr.com/ru/post/142862/


All Articles