📜 ⬆️ ⬇️

Creating a cross-browser shell for custom scripts

Hello, dear habrazhiteli. There were quite a few posts about user scripts (userscripts), however, they only showed how to use them. And in the work of userscripts there are a lot of cross-browser incompatibilities (as in any area of ​​browser js). Naturally, you can install various add-ons for different browsers, however, in the case of writing a script for the end user, you will have to accompany him with a huge readme for installing components to ensure its normal operation. What I personally, and you, too, I suppose, is not very happy with.

This article will cover three browsers: Mozilla Firefox (with installed GreaseMonkey), Google Chrome, Opera. The goal of the article is “procurement”, which will allow the user script to work in the same way in all the browsers listed. The GM API implementation will not be considered, since such already hundreds. It is assumed that the reader is already familiar with the general rules of writing userscripts (if not, I recommend you first read another article ).


And directly to the topic. Let's start with the directives. There are several incompatibilities here: first, only include is supported in opera, only match is supported in chrome. Firefox supports both. Accordingly, you need to specify both directives (suddenly). Secondly, to support unsafeWindow in firefox, you need the @unwrap directive. I will not specify that before using unsafeWindow you need to think hard about whether it is really so necessary. So, the initial part of our workpiece looks like this:
// ==UserScript== // @name script_name // @author author's name // @version 1.0 // @description example // @unwrap // @run-at document-end // @include http://example.com/* // @match http://example.com/* // ==/UserScript== 

')
Plus, in Chrome, the match directive has a blessing in it, so it’s useful to add a line like this to the beginning:
 if (location.hostname !== "example.com") return; 


Now let's talk about the scope. In Google Chrome, user scripts are always executed in a separate area of ​​view - while in opera, on the contrary, they are executed in the middle of the page, threatening to destroy all global variables. Therefore, for security, it would be nice to add an additional closure:
 // ==UserScript== // @name script_name // @author author's name // @version 1.0 // @description example // @unwrap // @run-at document-end // @include http://example.com/* // @match http://example.com/* // ==/UserScript== (function(){ if (location.hostname !== "example.com") return; })(); 


And, finally, such an important thing as access to global objects. In Mozilla Firefox, they are accessed through the unsafeWindow variable, in Opera the script is executed directly in the page's scope, therefore, global variables are accessible through the window, in Google Chrome there is no access to global objects at all (sadness). However, unsafeWindow there can be emulated by adding an object to the page, onclick of which the window returns to us. Actually, the code with comments:

 //   unsafeWindow    -   ,    , //      window var unsafeWindow= this.unsafeWindow; //  ,    unsafeWindow,     , //    (function(){ // ..,   ,      , //  unsafeWindow  chrome,   , ,   . var test_scr= document.createElement("script"); //       var tid= ("t" + Math.random() + +(new Date())).replace(/\./g, ""); //       . test_scr.text= "window."+tid+"=true"; document.querySelector("body").appendChild(test_scr); //      unsafeWindow, //         if (typeof(unsafeWindow) == "undefined" || !unsafeWindow[tid]) { if (window[tid]) { // ..   window -    window, //    unsafeWindow= window; } else { //   -    var scr= document.createElement("script"); scr.text= "(" + (function() { var el= document.createElement('unsafeWindow'); el.style.display= 'none'; el.onclick=function(){return window}; document.body.appendChild(el); }).toString() + ")()"; //   -   script      document.querySelector("body").appendChild(scr); this.unsafeWindow= document.querySelector("unsafeWindow").onclick(); //   ,     // onclick  unsafeWindow ,      unsafeWindow= this.unsafeWindow; }; } })(); //  


So, in the end, our stocking will look like this:
 // ==UserScript== // @name script_name // @author author's name // @version 1.0 // @description example // @unwrap // @run-at document-end // @include http://example.com/* // @match http://example.com/* // ==/UserScript== (function(){ if (location.hostname !== "example.com") return; var unsafeWindow= this.unsafeWindow; (function(){ var test_scr= document.createElement("script"); var tid= ("t" + Math.random() + +(new Date())).replace(/\./g, ""); test_scr.text= "window."+tid+"=true"; document.querySelector("body").appendChild(test_scr); if (typeof(unsafeWindow) == "undefined" || !unsafeWindow[tid]) { if (window[tid]) { unsafeWindow= window; } else { var scr= document.createElement("script"); scr.text= "(" + (function() { var el= document.createElement('unsafeWindow'); el.style.display= 'none'; el.onclick=function(){return window}; document.body.appendChild(el); }).toString() + ")()"; document.querySelector("body").appendChild(scr); this.unsafeWindow= document.querySelector("unsafeWindow").onclick(); unsafeWindow= window.unsafeWindow; }; } })(); //  ,    })(); 


Actually, that's all. I hope this article will help someone.

PS Oh yes. To debug userscripts in Firefox, you can wrap your script code in a try ... catch structure like this:
try {
// script code
} catch (e) {console.error (e)}
Facilitates life very much.

UPD: Thank you so much spmbt for such a nice addition to the article. Rather, the article is an addition to what he wrote ...

UPD2: The include directive will not allow the script to work in chrome on unnecessary sites, however, when installing the script, if you do not specify the match directive, it will be said that the script works on all pages. Therefore, it is better to specify the match directive.

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


All Articles