1 /* coded by Ketmar // Invisible Vector (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software. It comes without any warranty, to
5 * the extent permitted by applicable law. You can redistribute it
6 * and/or modify it under the terms of the Do What The Fuck You Want
7 * To Public License, Version 2, as published by Sam Hocevar. See
8 * http://www.wtfpl.net/txt/copying/ for more details.
10 ////////////////////////////////////////////////////////////////////////////////
11 this.EXPORTED_SYMBOLS = [];
13 let {utils:Cu, classes:Cc, interfaces:Ci, results:Cr} = Components;
16 ////////////////////////////////////////////////////////////////////////////////
17 // addonName: used in `alert()`
18 function initAddonInternal (global, addonName, urlMain, urlJS) {
19 const {utils:Cu, classes:Cc, interfaces:Ci, results:Cr} = Components;
21 const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
22 const appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
24 case "{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}": break;
25 case "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": promptSvc.alert(null, addonName, "Ff is not supported"); return null;
26 case "{3550f703-e582-4d05-9a08-453d09bdfdc6}": promptSvc.alert(null, addonName, "Thunderbird is not supported"); return null;
27 case "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": promptSvc.alert(null, addonName, "Seamonkey is not supported"); return null;
28 default: promptSvc.alert(null, addonName, "Unknown application "+appInfo.ID+" is not supported"); return null;
31 // bootstrapped addons has no `window`
32 //const principal = (typeof(window) !== "undefined" ? window : Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal));
33 let gsbox = new Cu.Sandbox(Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal), {
34 sandboxName: addonName+" Internal Sandbox",
35 wantComponents: false,
37 wantXHRConstructor: true,
39 Cu.import("resource://gre/modules/Services.jsm", gsbox);
41 // bootstrapped addons has no `window`
42 if (typeof(window) !== "undefined") {
43 gsbox.window = window;
44 gsbox.document = window.document;
47 for (let name of ["CSS","indexedDB","XMLHttpRequest","TextEncoder","TextDecoder","URL","URLSearchParams","atob","btoa","Blob","File"]) {
49 //Components.utils.reportError("exporting '"+name+"'");
50 gsbox[name] = global[name];
54 // add some common API
55 Object.defineProperty(gsbox, "jscodeUrl", {get: function () urlJS});
56 Object.defineProperty(gsbox, "contentUrl", {get: function () urlMain});
58 // `tieto` API: ties method to object, so it always has correct `this`
59 // you may specify additional arguments that will be always passed unchanged
60 function tieto (obj, method) {
62 if (typeof(obj) === "undefined") throw new Error("object or `null` expected");
63 if (obj !== null && typeof(obj) !== "object") throw new Error("object or `null` expected");
64 switch (typeof(method)) {
66 if (!obj) throw new Error("can't take method by name from `null` object");
67 if (!(method in obj)) throw new Error("method '"+method+"' doesn't exist in object '"+obj+"'");
69 if (typeof(mt) !== "function") throw new Error("method '"+method+"' isn't a function in object '"+obj+"'");
76 throw new Error("`method` should be function or method name");
78 // hack: replace first array item; avoiding extra arrays
79 let rest = Array.prototype.splice.call(arguments, 1, arguments.length);
82 return mt.bind.apply(mt, rest);
85 const sb_tieto = tieto(gsbox, tieto);
86 Object.defineProperty(gsbox, "tieto", {get: function () sb_tieto});
88 const sb_Components = tieto(gsbox, function (c) c, Components);
89 const sb_Cu = tieto(gsbox, function (cu) cu, Cu);
90 const sb_Cc = tieto(gsbox, function (cc) cc, Cc);
91 const sb_Ci = tieto(gsbox, function (ci) ci, Ci);
92 const sb_Cr = tieto(gsbox, function (cr) cr, Cr);
94 Object.defineProperty(gsbox, "Components", {get: function () sb_Components()});
95 Object.defineProperty(gsbox, "Cu", {get: function () sb_Cu()});
96 Object.defineProperty(gsbox, "Cc", {get: function () sb_Cc()});
97 Object.defineProperty(gsbox, "Ci", {get: function () sb_Ci()});
98 Object.defineProperty(gsbox, "Cr", {get: function () sb_Cr()});
100 // console log functions
101 function logErr (str) Cu.reportError(str);
102 const sb_logError = tieto(gsbox, function (logErrMsg) {
103 if (arguments.length > 1) {
105 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
109 Object.defineProperty(gsbox, "logError", {get: function () sb_logError});
111 const sb_logException = tieto(gsbox, function (logErrMsg, msg, e) {
112 if (typeof(e) === "undefined" && typeof(msg) === "object") {
116 if (typeof(e) !== "undefined" /*&& e instanceof global.Error*/ && e) {
117 logErrMsg(""+msg+" ERROR: "+e.name+": "+e.message+" : "+e.lineNumber);
118 if (e.stack) logErrMsg(e.stack);
122 if (typeof(e) !== "undefined" && e) logErrMsg("e="+e);
125 Object.defineProperty(gsbox, "logException", {get: function () sb_logException});
127 // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIConsoleService#logStringMessage() - wstring / wide string
128 const conService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
129 function logStr (str) conService.logStringMessage(str);
131 const sb_conlog = tieto(gsbox, function (logStrMsg) {
132 if (arguments.length > 1) {
133 let le = ("addonOptions" in this ? this["addonOptions"].logEnabled : true);
136 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
140 Object.defineProperty(gsbox, "conlog", {get: function () sb_conlog});
142 const sb_debuglog = tieto(gsbox, function (logStrMsg) {
143 if (arguments.length > 1) {
144 if ("addonOptions" in this) {
145 let ao = this["addonOptions"];
146 //if (("logEnabled" in ao) && !ao.logEnabled) return;
147 if (!("debugMode" in ao) || !ao.debugMode) return;
150 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
154 Object.defineProperty(gsbox, "debuglog", {get: function () sb_debuglog});
156 const sb_print = tieto(gsbox, function (logStrMsg) {
157 if (arguments.length > 1) {
159 for (let idx = 1; idx < arguments.length; ++idx) {
160 let t = ""+arguments[idx];
163 logStrMsg(s.substr(1));
166 Object.defineProperty(gsbox, "print", {get: function () sb_print});
169 const newURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newURI;
170 //const newFileURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newFileURI;
171 const loadSubScript = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
173 const specsSchemes = [
174 { re: /^mainjs:(.+)$/, pfx: urlJS},
175 { re: /^main:(.+)$/, pfx: urlMain}
177 function toFullUrl (scriptSpec, name) {
178 if (/^(?:chrome|resource|file):/.test(name)) return name;
179 for (let sp of specsSchemes) {
180 let mt = name.match(sp.re);
181 if (mt) return sp.pfx+mt[1];
183 return scriptSpec+name;
186 // include("incname")
187 const sb_include = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, name) {
188 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
189 if (!(/.\.js$/.test(name))) name += ".js";
190 let fullUrl = toFullUrl(scriptSpec, name);
192 let uri = newURI(fullUrl, null, null);
193 loadSubScript(uri.spec, gsbox, "UTF-8");
195 if (typeof(e) === "object") {
196 reportError("INCLUDE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
199 reportError("INCLUDE ERROR '"+name+"': "+e);
203 }, urlJS+"includes/", newURI, loadSubScript, Cu.reportError);
204 Object.defineProperty(gsbox, "include", {get: function () sb_include});
207 function loadTextContentsFromUrl (url, encoding) {
208 if (typeof(encoding) !== "string") encoding = "UTF-8"; else encoding = encoding||"UTF-8";
210 function toUnicode (text) {
211 if (typeof(text) === "string") {
212 if (text.length == 0) return "";
213 } else if (text instanceof ArrayBuffer) {
214 if (text.byteLength == 0) return "";
215 text = new Uint8Array(text);
216 return new TextDecoder(encoding).decode(text);
217 } else if ((text instanceof Uint8Array) || (text instanceof Int8Array)) {
218 if (text.length == 0) return "";
219 return new TextDecoder(encoding).decode(text);
223 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
224 converter.charset = encoding;
225 let res = converter.ConvertToUnicode(text);
226 if (res.length >= 3 && res.substr(0, 3) == "\u00EF\u00BB\u00BF") res = res.substr(3); // fuck BOM
231 let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
232 req.mozBackgroundRequest = true;
233 req.open("GET", url, false);
234 req.timeout = 30*1000;
235 const cirq = Ci.nsIRequest;
236 req.channel.loadFlags |= cirq.INHIBIT_CACHING|cirq.INHIBIT_PERSISTENT_CACHING|cirq.LOAD_BYPASS_CACHE|cirq.LOAD_ANONYMOUS;
237 req.responseType = "arraybuffer";
238 //if (sendCookies) addCookies(req, urlsend);
240 req.onerror = function () { error = true; }; // just in case
241 req.ontimeout = function () { error = true; }; // just in case
244 error = (req.status != 0 && Math.floor(req.status/100) != 2);
245 //(/^file:/.test(file.spec) ? (req.status != 0) : (Math.floor(req.status/100) != 2));
247 if (error) throw new Error("can't load URI contents: status="+req.status+"; error="+error);
248 return toUnicode(req.response, encoding);
252 let modules = {}; // list of loaded modules
254 // require("modname")
255 const sb_require = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, import_, modules, name) {
256 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
257 if (!(/.\.js$/.test(name))) name += ".js";
258 if (name in modules) return modules[name];
259 let fullUrl = toFullUrl(scriptSpec, name);
260 //else sb_debuglog("requiring '", name, "' : [", fullUrl, "]");
262 let scope = Object.create(gsbox);
264 scope.exportsGlobal = {};
267 let uri = newURI(fullUrl, null, null);
269 let box = new Cu.Sandbox(Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal), {
270 wantComponents: false,
272 wantXHRConstructor: true,
273 sandboxPrototype: scope,
276 // uses(name[, name]...);
277 let sc_uses = tieto(box, function (scope, import_) {
278 if (arguments.length > 3) {
279 for (let idx = 2; idx < arguments.length; ++idx) import_(arguments[idx], scope);
281 } else if (arguments.length == 3) {
282 return import_(arguments[2], scope);
286 }, scope, Cu.import);
287 Object.defineProperty(box, "uses", {get: function () sc_uses});
289 // include("incname")
290 let sc_include = tieto(box, function (scriptSpec, newURI, loadSubScript, reportError, box, name) {
291 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
292 if (!(/.\.js$/.test(name))) name += ".js";
293 let fullUrl = toFullUrl(scriptSpec, name);
295 let uri = newURI(fullUrl, null, null);
296 loadSubScript(uri.spec, box, "UTF-8");
298 if (typeof(e) === "object") {
299 reportError("INCLUDE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
302 reportError("INCLUDE ERROR '"+name+"': "+e);
306 }, urlJS+"includes/", newURI, loadSubScript, Cu.reportError, box);
307 Object.defineProperty(box, "include", {get: function () sc_include});
309 let text = loadTextContentsFromUrl(uri.spec);
310 Cu.evalInSandbox(text, box, "ECMAv5", name, 1);
312 if (typeof(e) === "object") {
313 reportError("REQUIRE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
316 reportError("REQUIRE ERROR '"+name+"': "+e);
321 modules[name] = scope.exports;
322 if (typeof(scope.exportsGlobal) === "object") {
323 for (let k in scope.exportsGlobal) {
324 if (k !== "__exposedProps__" && Object.prototype.hasOwnProperty.call(scope.exportsGlobal, k)) {
325 try { gsbox[k] = scope.exportsGlobal[k]; } catch (e) {}
329 return scope.exports;
330 }, urlJS+"modules/", newURI, loadSubScript, Cu.reportError, Cu.import, modules);
331 Object.defineProperty(gsbox, "require", {get: function () sb_require});
335 let sb_alert = tieto(gsbox, function (promptSvc, addonName, msg) {
336 promptSvc.alert(null, addonName, ""+msg);
337 }, promptSvc, addonName);
338 Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
341 let startupHooks = [], shutdownHooks = [];
342 let winloadHooks = [], winunloadHooks = [];
344 let mainLoaded = false;
346 function runHooks (hooktype, list, rev, firsttime) {
347 let rest = Array.prototype.splice.call(arguments, 4, arguments.length);
348 let callmain = false;
350 //Cu.reportError("runHooks ("+hooktype+"): first time");
352 //Cu.reportError("runHooks ("+hooktype+"): main not loaded");
353 // now load main file
355 let loadmain = ("__dontLoadMainJS" in global ? !global.__dontLoadMainJS : true);
357 //Cu.reportError("runHooks ("+hooktype+"): loading main...");
358 //Cu.reportError("runHooks ("+hooktype+"): len="+list.length);
359 gsbox.include("mainjs:prefs/prefs.js"); // define default extension preferences
360 gsbox.require("mainjs:prefsengine.js");
362 //Cu.reportError("runHooks ("+hooktype+"): len="+list.length);
363 gsbox.include("mainjs:main.js");
367 if (hooktype === "shutdown" && typeof(gsbox.main_preshutdown) === "function") {
369 gsbox.main_preshutdown();
371 Cu.reportError("PRESHUTDOWN ERROR: "+e.name+": "+e.message+" : "+e.lineNumber);
372 if (e.stack) Cu.reportError(e.stack);
376 let idx = (rev ? list.length : -1);
379 if (--idx < 0) break;
381 if (++idx >= list.length) break;
385 //conService.logStringMessage("running hook: '"+h.name+"'");
386 let args = Array.prototype.slice.call(rest);
387 //Array.prototype.push.apply(args, arguments);
388 h.cback.apply(gsbox, args);
391 Cu.reportError("HOOK '"+hooktype+"' ("+h.name+") ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+(e.stack ? "\n"+e.stack : ""));
393 if (e.stack) Cu.reportError(e.stack);
396 if (callmain && typeof(gsbox.main_postinit) === "function") gsbox.main_postinit();
399 function addHook (hooks, name, cback) {
400 if (typeof(cback) === "undefined") {
401 // register*Hook(hookfn)
405 if (typeof(name) === "undefined") throw new Error("name or `null` expected");
406 if (!name) name = "";
407 if (typeof(name) !== "string") throw new Error("name or `null` expected");
408 if (typeof(cback) !== "function") throw new Error("function expected");
409 for (let obj in hooks) if (obj.cback === cback) return; // nothing to do
410 hooks.push({name:name, cback:cback});
413 const sb_rsth = tieto(gsbox, addHook, startupHooks);
414 const sb_rsuh = tieto(gsbox, addHook, shutdownHooks);
416 // registerStartupHook(name, hookfn)
417 // registerShutdownHook(name, hookfn)
418 Object.defineProperty(gsbox, "registerStartupHook", {get: function () sb_rsth});
419 Object.defineProperty(gsbox, "registerShutdownHook", {get: function () sb_rsuh});
421 const sb_rwlh = tieto(gsbox, addHook, winloadHooks);
422 const sb_rwuh = tieto(gsbox, addHook, winunloadHooks);
424 // registerWindowLoadHook(name, hookfn)
425 // registerWindowUnloadHook(name, hookfn)
426 Object.defineProperty(gsbox, "registerWindowLoadHook", {get: function () sb_rwlh});
427 Object.defineProperty(gsbox, "registerWindowUnloadHook", {get: function () sb_rwuh});
432 defProp: tieto(this, function (mainobj, name, ops) {
433 //Cu.reportError("GS: defining property '"+name+"'");
434 mainobj.defineProperty(gsbox, name, ops);
436 //onInstall: function () {},
437 //onUnistall: function () {},
438 onStartup: tieto(this, runHooks, "startup", startupHooks, false, true),
439 onShutdown: tieto(this, runHooks, "shutdown", shutdownHooks, true, false),
440 onWindowLoad: tieto(this, runHooks, "window opened", winloadHooks, true, false),
441 onWindowUnload: tieto(this, runHooks, "window closed", winunloadHooks, true, false),
446 ////////////////////////////////////////////////////////////////////////////////
447 const addonName = "Guerilla Script"; // addon name, used in `alert()`
448 const addonContentUrl = "chrome://guerilla-script/content/"; // content URL, must end with "/"
449 const addonJSUrl = "chrome://guerilla-script-jscode/content/"; // addon js code URL, must end with "/"
454 ////////////////////////////////////////////////////////////////////////////////
455 function initAddon (global) {
457 addonobj = initAddonInternal(global, addonName, addonContentUrl, addonJSUrl);
458 if (!addonobj) throw new Error("can't init addon '"+addonName+"'"); // alas, something is wrong
459 addonobj.defProp("gsdoxUrl", {get: function () "chrome://guerilla-script-dox/content/"});
460 addonobj.onStartup();
462 if (addonobj) try { addonobj.onShutdown(); } catch (xx) {}
464 Cu.reportError(e.stack);
471 ////////////////////////////////////////////////////////////////////////////////
474 function newWindow (global, win) {
475 if (addonobj === null) initAddon(global);
477 win.addEventListener("load", function () {
480 global.window.addEventListener("unload", function () {
482 addonobj.onWindowUnload(win);
483 if (windowCount == 0) {
484 // no more registered windows --> shutdown
485 addonobj.onShutdown();
488 addonobj.onWindowLoad(win);
494 ////////////////////////////////////////////////////////////////////////////////
495 function getAddonObj () addonobj;