correct working with multiple windows
[guerillascript.git] / main / init.js
blobd7a8c7ccbddfbf4c130b622a2f4ae92f2802e8bf
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 // addonName: used in `alert()`
12 (function (global, addonName, urlMain, urlJS) {
13 const {utils:Cu, classes:Cc, interfaces:Ci, results:Cr} = Components;
15 const isXPCShell = ("__scriptpath__" in global);
17 if (!isXPCShell) {
18 const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
19 const appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
20 switch (appInfo.ID) {
21 case "{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}": break;
22 case "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": promptSvc.alert(null, addonName, "Ff is not supported"); return null;
23 case "{3550f703-e582-4d05-9a08-453d09bdfdc6}": promptSvc.alert(null, addonName, "Thunderbird is not supported"); return null;
24 case "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": promptSvc.alert(null, addonName, "Seamonkey is not supported"); return null;
25 default: promptSvc.alert(null, addonName, "Unknown application "+appInfo.ID+" is not supported"); return null;
29 // bootstrapped addons has no `window`
30 const principal = (typeof(window) !== "undefined" ? window : Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal));
31 let gsbox = new Cu.Sandbox(principal, {
32 sandboxName: addonName+" Internal Sandbox",
33 wantComponents: false,
34 wantXrays: false,
35 wantXHRConstructor: true,
36 });
37 Cu.import("resource://gre/modules/Services.jsm", gsbox);
39 // bootstrapped addons has no `window`
40 if (typeof(window) !== "undefined") {
41 gsbox.window = window;
42 gsbox.document = window.document;
45 for (let name of ["CSS","indexedDB","XMLHttpRequest","TextEncoder","TextDecoder","URL","URLSearchParams","atob","btoa","Blob","File"]) {
46 if (name in global) {
47 //Components.utils.reportError("exporting '"+name+"'");
48 gsbox[name] = global[name];
52 // add some common API
53 if (isXPCShell) {
54 Object.defineProperty(gsbox, "isXPCShell", {get: function () true});
55 } else {
56 Object.defineProperty(gsbox, "isXPCShell", {get: function () false});
59 Object.defineProperty(gsbox, "contentURL", {get: function () urlMain});
61 // `tieto` API: ties method to object, so it always has correct `this`
62 // you may specify additional arguments that will be always passed unchanged
63 function tieto (obj, method) {
64 let mt;
65 if (typeof(obj) === "undefined") throw new Error("object or `null` expected");
66 if (obj !== null && typeof(obj) !== "object") throw new Error("object or `null` expected");
67 switch (typeof(method)) {
68 case "string":
69 if (!obj) throw new Error("can't take method by name from `null` object");
70 if (!(method in obj)) throw new Error("method '"+method+"' doesn't exist in object '"+obj+"'");
71 mt = obj[method];
72 if (typeof(mt) !== "function") throw new Error("method '"+method+"' isn't a function in object '"+obj+"'");
73 break;
74 case "function":
75 if (!obj) obj = {};
76 mt = method;
77 break;
78 default:
79 throw new Error("`method` should be function or method name");
81 let me = obj;
82 let rest = Array.prototype.splice.call(arguments, 2, arguments.length);
84 let stk;
85 try { throw new Error("!"); } catch (e) {
86 //print("TIETO!\n"+e.stack);
87 stk = e.stack.split("\n")[1];
90 return function () {
91 //print(stk);
92 // `rest` is reused for each invocation, so copy it, and append new arguments to copy
93 let args = Array.prototype.slice.call(rest);
94 Array.prototype.push.apply(args, arguments);
95 return mt.apply(me, args);
98 const sb_tieto = tieto(gsbox, tieto);
99 Object.defineProperty(gsbox, "tieto", {get: function () sb_tieto});
101 const sb_Components = tieto(gsbox, function (c) c, Components);
102 const sb_Cu = tieto(gsbox, function (cu) cu, Cu);
103 const sb_Cc = tieto(gsbox, function (cc) cc, Cc);
104 const sb_Ci = tieto(gsbox, function (ci) ci, Ci);
105 const sb_Cr = tieto(gsbox, function (cr) cr, Cr);
107 Object.defineProperty(gsbox, "Components", {get: function () sb_Components()});
108 Object.defineProperty(gsbox, "Cu", {get: function () sb_Cu()});
109 Object.defineProperty(gsbox, "Cc", {get: function () sb_Cc()});
110 Object.defineProperty(gsbox, "Ci", {get: function () sb_Ci()});
111 Object.defineProperty(gsbox, "Cr", {get: function () sb_Cr()});
113 // console log functions
114 const oldlogerr = (isXPCShell ? global.logError : null);
115 function logErr (str) {
116 if (oldlogerr) oldlogerr(str); else Cu.reportError(str);
118 const sb_logError = tieto(gsbox, function (logErrMsg) {
119 if (arguments.length > 1) {
120 let s = "";
121 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
122 logErrMsg(s);
124 }, logErr);
125 Object.defineProperty(gsbox, "logError", {get: function () sb_logError});
127 const sb_logException = tieto(gsbox, function (logErrMsg, msg, e) {
128 if (typeof(e) !== "undefined" /*&& e instanceof global.Error*/ && e) {
129 logErrMsg(""+msg+" ERROR: "+e.name+": "+e.message+" : "+e.lineNumber)
130 if (e.stack) logErrMsg(e.stack);
131 logErrMsg(e);
132 } else {
133 logErrMsg(""+msg);
134 if (typeof(e) !== "undefined" && e) logErrMsg("e="+e);
136 }, logErr);
137 Object.defineProperty(gsbox, "logException", {get: function () sb_logException});
139 // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIConsoleService#logStringMessage() - wstring / wide string
140 const conService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
141 const oldconlog = (isXPCShell ? global.conlog : null);
142 function logStr (str) {
143 if (oldconlog) oldconlog(str); else conService.logStringMessage(str);
146 const sb_conlog = tieto(gsbox, function (logStrMsg) {
147 if (arguments.length > 1) {
148 let le = ("addonOptions" in this ? this["addonOptions"].logEnabled : true);
149 if (!le) return;
150 let s = "";
151 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
152 logStrMsg(s);
154 }, logStr);
155 Object.defineProperty(gsbox, "conlog", {get: function () sb_conlog});
157 const sb_print = tieto(gsbox, function (logStrMsg) {
158 if (arguments.length > 1) {
159 let s = "";
160 for (let idx = 1; idx < arguments.length; ++idx) {
161 let t = ""+arguments[idx];
162 if (t) s += " "+t;
164 logStrMsg(s.substr(1));
166 }, logStr);
167 Object.defineProperty(gsbox, "print", {get: function () sb_print});
169 // script loaders
170 const newURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newURI;
171 //const newFileURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newFileURI;
172 const loadSubScript = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
174 const specsSchemes = [
175 { re: /^mainjs:(.+)$/, pfx: urlJS},
176 { re: /^main:(.+)$/, pfx: urlMain}
178 function toFullUrl (scriptSpec, name) {
179 if (/^(?:chrome|resource|file):/.test(name)) return name;
180 for (let sp of specsSchemes) {
181 let mt = name.match(sp.re);
182 if (mt) return sp.pfx+mt[1];
184 return scriptSpec+name;
187 // include("incname")
188 const sb_include = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, name) {
189 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
190 if (!(/.\.js$/.test(name))) name += ".js";
191 let fullURL = toFullUrl(scriptSpec, name);
192 if (isXPCShell) print("including '"+name+"' : ["+fullURL+"]");
193 try {
194 let uri = newURI(fullURL, null, null);
195 loadSubScript(uri.spec, gsbox, "UTF-8");
196 } catch (e) {
197 reportError("INCLUDE ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
198 reportError(e);
199 throw e;
201 }, urlJS+"includes/", newURI, loadSubScript, Cu.reportError);
202 Object.defineProperty(gsbox, "include", {get: function () sb_include});
205 let modules = {}; // list of loaded modules
207 // require("modname")
208 const sb_require = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, import_, modules, name) {
209 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
210 if (!(/.\.js$/.test(name))) name += ".js";
211 if (name in modules) return modules[name];
212 let fullURL = toFullUrl(scriptSpec, name);
213 if (isXPCShell) print("requiring '"+name+"' : ["+fullURL+"]");
214 //let scope = {};
215 let scope = Object.create(gsbox);
216 // uses(name[, name]...);
217 let sc_uses = tieto(gsbox, function (scope, import_) {
218 if (arguments.length > 3) {
219 for (let idx = 2; idx < arguments.length; ++idx) import_(arguments[idx], scope);
220 return null;
221 } else if (arguments.length == 3) {
222 return import_(arguments[2], scope);
224 }, scope, import_);
225 Object.defineProperty(scope, "uses", {get: function () sc_uses});
226 scope.exports = {};
227 scope.exportsGlobal = {};
229 for (let k in gsbox) {
230 print("k=["+k+"]");
231 if (k !== "__exposedProps__" && !(k in scope) && Object.prototype.hasOwnProperty.call(gsbox, k)) {
232 print(" EXPORT: k=["+k+"]");
233 try { scope[k] = gsbox[k]; } catch (e) {}
237 try {
238 let uri = newURI(fullURL, null, null);
239 loadSubScript(uri.spec, scope, "UTF-8");
240 } catch (e) {
241 reportError("REQUIRE ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
242 reportError(e);
243 throw e;
245 modules[name] = scope.exports;
246 if (typeof(scope.exportsGlobal) === "object") {
247 for (let k in scope.exportsGlobal) {
248 if (k !== "__exposedProps__" && Object.prototype.hasOwnProperty.call(scope.exportsGlobal, k)) {
249 try { gsbox[k] = scope.exportsGlobal[k]; } catch (e) {}
253 return scope.exports;
254 }, urlJS+"modules/", newURI, loadSubScript, Cu.reportError, Cu.import, modules);
255 Object.defineProperty(gsbox, "require", {get: function () sb_require});
258 // alert("msg")
259 let alertfn;
260 if (isXPCShell) {
261 let sb_alert = tieto(this, function (msg) {
262 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
263 file.initWithPath("/usr/bin/xmessage");
264 let params = [""+msg];
265 let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
266 process.init(file);
267 process.run(true, params, params.length);
269 Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
270 } else {
271 let sb_alert = tieto(gsbox, function (promptSvc, addonName, msg) {
272 promptSvc.alert(null, addonName, ""+msg);
273 }, promptSvc, addonName);
274 Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
278 let startupHooks = [], shutdownHooks = [];
279 let winloadHooks = [], winunloadHooks = [];
281 let mainLoaded = false;
283 function runHooks (list, rev, firsttime) {
284 let rest = Array.prototype.splice.call(arguments, 2, arguments.length);
285 // hack: if (rev === false): this is "onStartup"
286 if (firsttime) {
287 if (!mainLoaded) {
288 // now load main file
289 mainLoaded = true;
290 let loadmain = ("__dontLoadMainJS" in global ? !global.__dontLoadMainJS : true);
291 if (loadmain) {
292 gsbox.include("mainjs:prefs/prefs.js"); // define default extension preferences
293 gsbox.require("mainjs:prefsengine.js");
294 gsbox.include("mainjs:main.js");
298 let idx = (rev ? list.length : -1);
299 for (;;) {
300 if (rev) {
301 if (--idx < 0) break;
302 } else {
303 if (++idx >= list.length) break;
305 let h = list[idx];
306 try {
307 //conService.logStringMessage("running hook: '"+h.name+"'");
308 let args = Array.prototype.slice.call(rest);
309 //Array.prototype.push.apply(args, arguments);
310 h.cback.apply(gsbox, args);
311 } catch (e) {
312 Cu.reportError("HOOK ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
313 Cu.reportError(e);
318 function addHook (hooks, name, cback) {
319 if (typeof(cback) === "undefined") {
320 // register*Hook(hookfn)
321 cback = name;
322 name = null;
324 if (typeof(name) === "undefined") throw new Error("name or `null` expected");
325 if (!name) name = "";
326 if (typeof(name) !== "string") throw new Error("name or `null` expected");
327 if (typeof(cback) !== "function") throw new Error("function expected");
328 hooks.push({name:name, cback:cback});
331 const sb_rsth = tieto(gsbox, addHook, startupHooks);
332 const sb_rsuh = tieto(gsbox, addHook, shutdownHooks);
334 // registerStartupHook(name, hookfn)
335 // registerShutdownHook(name, hookfn)
336 Object.defineProperty(gsbox, "registerStartupHook", {get: function () sb_rsth});
337 Object.defineProperty(gsbox, "registerShutdownHook", {get: function () sb_rsuh});
339 const sb_rwlh = tieto(gsbox, addHook, winloadHooks);
340 const sb_rwuh = tieto(gsbox, addHook, winunloadHooks);
342 // registerWindowLoadHook(name, hookfn)
343 // registerWindowUnloadHook(name, hookfn)
344 Object.defineProperty(gsbox, "registerWindowLoadHook", {get: function () sb_rwlh});
345 Object.defineProperty(gsbox, "registerWindowUnloadHook", {get: function () sb_rwuh});
347 return {
348 sbox: gsbox,
349 tieto: tieto,
350 defProp: tieto(this, function (mainobj, name, ops) {
351 //Cu.reportError("GS: defining property '"+name+"'");
352 mainobj.defineProperty(gsbox, name, ops);
353 }, Object),
354 //onInstall: function () {},
355 //onUnistall: function () {},
356 onStartup: tieto(this, runHooks, startupHooks, false, true),
357 onShutdown: tieto(this, runHooks, shutdownHooks, true, false),
358 onWindowLoad: tieto(this, runHooks, winloadHooks, true, false),
359 onWindowUnload: tieto(this, runHooks, winunloadHooks, true, false),