buuuump
[guerillascript.git] / main / startup.js
blobc362619149f449a3bf6ac6da26ce59e7181b1b39
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.
9 */
10 ////////////////////////////////////////////////////////////////////////////////
11 this.EXPORTED_SYMBOLS = [];
13 const {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);
23 switch (appInfo.ID) {
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,
36 wantXrays: false,
37 wantXHRConstructor: true,
38 });
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"]) {
48 if (name in global) {
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) {
61 let mt;
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)) {
65 case "string":
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+"'");
68 mt = obj[method];
69 if (typeof(mt) !== "function") throw new Error("method '"+method+"' isn't a function in object '"+obj+"'");
70 break;
71 case "function":
72 if (!obj) obj = {};
73 mt = method;
74 break;
75 default:
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);
80 rest[0] = obj;
81 // now use `bind`
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) {
104 let s = "";
105 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
106 logErrMsg(s);
108 }, logErr);
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") {
113 e = msg;
114 msg = "SOME";
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);
119 logErrMsg(e);
120 } else {
121 logErrMsg(""+msg);
122 if (typeof(e) !== "undefined" && e) logErrMsg("e="+e);
124 }, logErr);
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);
134 if (!le) return;
135 let s = "";
136 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
137 logStrMsg(s);
139 }, logStr);
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;
149 let s = "";
150 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
151 logStrMsg(s);
153 }, logStr);
154 Object.defineProperty(gsbox, "debuglog", {get: function () sb_debuglog});
156 const sb_print = tieto(gsbox, function (logStrMsg) {
157 if (arguments.length > 1) {
158 let s = "";
159 for (let idx = 1; idx < arguments.length; ++idx) {
160 let t = ""+arguments[idx];
161 if (t) s += " "+t;
163 logStrMsg(s.substr(1));
165 }, logStr);
166 Object.defineProperty(gsbox, "print", {get: function () sb_print});
168 // script loaders
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);
191 try {
192 let uri = newURI(fullUrl, null, null);
193 loadSubScript(uri.spec, gsbox, "UTF-8");
194 } catch (e) {
195 if (typeof(e) === "object") {
196 reportError("INCLUDE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
197 reportError(e);
198 } else {
199 reportError("INCLUDE ERROR '"+name+"': "+e);
201 throw 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);
220 } else {
221 return "";
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
227 return res;
230 // download from URL
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);
239 let error = false;
240 req.onerror = function () { error = true; }; // just in case
241 req.ontimeout = function () { error = true; }; // just in case
242 req.send(null);
243 if (!error) {
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, "]");
261 //let scope = {};
262 let scope = Object.create(gsbox);
263 scope.exports = {};
264 scope.exportsGlobal = {};
266 try {
267 let uri = newURI(fullUrl, null, null);
269 let box = new Cu.Sandbox(Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal), {
270 wantComponents: false,
271 wantXrays: 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);
280 return null;
281 } else if (arguments.length == 3) {
282 return import_(arguments[2], scope);
283 } else {
284 return null;
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);
294 try {
295 let uri = newURI(fullUrl, null, null);
296 loadSubScript(uri.spec, box, "UTF-8");
297 } catch (e) {
298 if (typeof(e) === "object") {
299 reportError("INCLUDE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
300 reportError(e);
301 } else {
302 reportError("INCLUDE ERROR '"+name+"': "+e);
304 throw 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);
311 } catch (e) {
312 if (typeof(e) === "object") {
313 reportError("REQUIRE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
314 reportError(e);
315 } else {
316 reportError("REQUIRE ERROR '"+name+"': "+e);
318 throw 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});
334 // alert("msg")
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;
349 if (firsttime) {
350 //Cu.reportError("runHooks ("+hooktype+"): first time");
351 if (!mainLoaded) {
352 //Cu.reportError("runHooks ("+hooktype+"): main not loaded");
353 // now load main file
354 mainLoaded = true;
355 let loadmain = ("__dontLoadMainJS" in global ? !global.__dontLoadMainJS : true);
356 if (loadmain) {
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");
361 callmain = true;
362 //Cu.reportError("runHooks ("+hooktype+"): len="+list.length);
363 gsbox.include("mainjs:main.js");
367 if (hooktype === "shutdown" && typeof(gsbox.main_preshutdown) === "function") {
368 try {
369 gsbox.main_preshutdown();
370 } catch (e) {
371 Cu.reportError("PRESHUTDOWN ERROR: "+e.name+": "+e.message+" : "+e.lineNumber);
372 if (e.stack) Cu.reportError(e.stack);
373 Cu.reportError(e);
376 let idx = (rev ? list.length : -1);
377 for (;;) {
378 if (rev) {
379 if (--idx < 0) break;
380 } else {
381 if (++idx >= list.length) break;
383 let h = list[idx];
384 try {
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);
389 } catch (e) {
390 callmain = false;
391 Cu.reportError("HOOK '"+hooktype+"' ("+h.name+") ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+(e.stack ? "\n"+e.stack : ""));
392 Cu.reportError(e);
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)
402 cback = name;
403 name = null;
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});
429 return {
430 sbox: gsbox,
431 tieto: tieto,
432 defProp: tieto(this, function (mainobj, name, ops) {
433 //Cu.reportError("GS: defining property '"+name+"'");
434 mainobj.defineProperty(gsbox, name, ops);
435 }, Object),
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 "/"
451 let addonobj = null;
454 ////////////////////////////////////////////////////////////////////////////////
455 function initAddon (global) {
456 try {
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();
461 } catch (e) {
462 if (addonobj) try { addonobj.onShutdown(); } catch (xx) {}
463 addonobj = false;
464 Cu.reportError(e.stack);
465 Cu.reportError(e);
466 throw e;
471 ////////////////////////////////////////////////////////////////////////////////
472 let windowCount = 0;
474 function newWindow (global, win) {
475 if (addonobj === null) initAddon(global);
476 if (addonobj) {
477 win.addEventListener("load", function () {
478 ++windowCount;
479 // setup unload hook
480 global.window.addEventListener("unload", function () {
481 --windowCount;
482 addonobj.onWindowUnload(win);
483 if (windowCount == 0) {
484 // no more registered windows --> shutdown
485 addonobj.onShutdown();
487 }, false);
488 addonobj.onWindowLoad(win);
489 }, false);
494 ////////////////////////////////////////////////////////////////////////////////
495 function getAddonObj () addonobj;