cosmetix
[guerillascript.git] / main / init.js
blob56bb919e8ec214a5673a98571e2bbeef484ea94a
1 /* coded by Ketmar // Invisible Vector (psyc://ketmar.no-ip.org/~Ketmar)
2  * Understanding is not required. Only obedience.
3  *
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, sandboxName, 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;
26     }
27   }
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: sandboxName,
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;
43   }
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];
49     }
50   }
52   //TODO: add "XMLHttpRequest" for xpcshell
54   // add some common API
55   Object.defineProperty(gsbox, "isXPCShell", {get: function () isXPCShell});
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 there
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");
77     }
78     let me = obj;
79     let rest = Array.prototype.splice.call(arguments, 2, arguments.length);
80     /*
81     let stk;
82     try { throw new Error("!"); } catch (e) {
83       //print("TIETO!\n"+e.stack);
84       stk = e.stack.split("\n")[1];
85     }
86     */
87     return function () {
88       //print(stk);
89       // `rest` is reused for each invocation, so copy it, and append new arguments to copy
90       let args = Array.prototype.slice.call(rest);
91       Array.prototype.push.apply(args, arguments);
92       return mt.apply(me, args);
93     };
94   }
95   const sb_tieto = tieto(gsbox, tieto);
96   Object.defineProperty(gsbox, "tieto", {get: function () sb_tieto});
98   const sb_Components = tieto(gsbox, function (c) c, Components);
99   const sb_Cu = tieto(gsbox, function (cu) cu, Cu);
100   const sb_Cc = tieto(gsbox, function (cc) cc, Cc);
101   const sb_Ci = tieto(gsbox, function (ci) ci, Ci);
102   const sb_Cr = tieto(gsbox, function (cr) cr, Cr);
104   Object.defineProperty(gsbox, "Components", {get: function () sb_Components()});
105   Object.defineProperty(gsbox, "Cu", {get: function () sb_Cu()});
106   Object.defineProperty(gsbox, "Cc", {get: function () sb_Cc()});
107   Object.defineProperty(gsbox, "Ci", {get: function () sb_Ci()});
108   Object.defineProperty(gsbox, "Cr", {get: function () sb_Cr()});
110   // console log functions
111   const oldlogerr = (typeof(global.logError) == "function" ? global.logError : null);
112   function logErr (str) {
113     if (oldlogerr) oldlogerr(str); else Cu.reportError(str);
114   }
115   const sb_logError = tieto(gsbox, function (logErrMsg) {
116     if (arguments.length > 1) {
117       let s = "";
118       for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
119       logErrMsg(s);
120     }
121   }, logErr);
122   Object.defineProperty(gsbox, "logError", {get: function () sb_logError});
124   const sb_logException = tieto(gsbox, function (logErrMsg, msg, e) {
125     if (typeof(e) !== "undefined" && e instanceof this.Error) {
126       logErrMsg(""+msg+" ERROR: "+e.name+": "+e.message+" : "+e.lineNumber)
127       if (e.stack) logErrMsg(e.stack);
128       logErrMsg(e);
129     } else {
130       logErrMsg(""+msg);
131     }
132   }, logErr);
133   Object.defineProperty(gsbox, "logException", {get: function () sb_logException});
135   //let conlogEnabled = true;
136   //Object.defineProperty(gsbox, "conlogEnabled", {get: tieto(this, function () this.conlogEnabled)});
138   // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIConsoleService#logStringMessage() - wstring / wide string
139   const conService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
140   const oldconlog = (typeof(global.conlog) == "function" ? global.conlog : null);
141   function logStr (str) {
142     if (oldconlog) oldconlog(str); else conService.logStringMessage(str);
143   }
145   const sb_conlog = tieto(gsbox, function (logStrMsg) {
146     if (arguments.length > 1) {
147       let le = ("addonOptions" in this ? this["addonOptions"].logEnabled : true);
148       if (!le) return;
149       let s = "";
150       for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
151       logStrMsg(s);
152     }
153   }, logStr);
154   Object.defineProperty(gsbox, "conlog", {get: function () sb_conlog});
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;
162       }
163       logStrMsg(s.substr(1));
164     }
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   function toFullUrl (scriptSpec, name) {
174     if (/^(?:chrome|resource|file):/.test(name)) return name;
175     if (isXPCShell) {
176       let mt = name.match(/^xpcemu:(.+)$/);
177       if (mt) return urlMain+mt[1];
178     }
179     {
180       let mt = name.match(/^mainjs:(.+)$/);
181       if (mt) return urlJS+mt[1];
182     }
183     {
184       let mt = name.match(/^main:(.+)$/);
185       if (mt) return urlMain+mt[1];
186     }
187     return scriptSpec+name;
188   }
190   // include("incname")
191   const sb_include = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, name) {
192     if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
193     if (!(/.\.js$/.test(name))) name += ".js";
194     let fullURL = toFullUrl(scriptSpec, name);
195     if (isXPCShell) print("including '"+name+"' : ["+fullURL+"]");
196     try {
197       let uri = newURI(fullURL, null, null);
198       loadSubScript(uri.spec, gsbox, "UTF-8");
199     } catch (e) {
200       reportError("INCLUDE ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
201       reportError(e);
202       throw e;
203     }
204   }, urlJS+"includes/", newURI, loadSubScript, Cu.reportError);
205   Object.defineProperty(gsbox, "include", {get: function () sb_include});
208   let modules = {}; // list of loaded modules
210   // require("modname")
211   const sb_require = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, import_, modules, name) {
212     if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
213     if (!(/.\.js$/.test(name))) name += ".js";
214     if (name in modules) return modules[name];
215     let fullURL = toFullUrl(scriptSpec, name);
216     if (isXPCShell) print("requiring '"+name+"' : ["+fullURL+"]");
217     //let scope = {};
218     let scope = Object.create(gsbox);
219     // uses(name[, name]...);
220     let sc_uses = tieto(gsbox, function (scope, import_) {
221       if (arguments.length > 3) {
222         for (let idx = 2; idx < arguments.length; ++idx) import_(arguments[idx], scope);
223         return null;
224       } else if (arguments.length == 3) {
225         return import_(arguments[2], scope);
226       }
227     }, scope, import_);
228     Object.defineProperty(scope, "uses", {get: function () sc_uses});
229     scope.exports = {};
230     scope.exportsGlobal = {};
231     /*
232     for (let k in gsbox) {
233       print("k=["+k+"]");
234       if (k !== "__exposedProps__" && !(k in scope) && Object.prototype.hasOwnProperty.call(gsbox, k)) {
235         print(" EXPORT: k=["+k+"]");
236         try { scope[k] = gsbox[k]; } catch (e) {}
237       }
238     }
239     */
240     try {
241       let uri = newURI(fullURL, null, null);
242       loadSubScript(uri.spec, scope, "UTF-8");
243     } catch (e) {
244       reportError("REQUIRE ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
245       reportError(e);
246       throw e;
247     }
248     modules[name] = scope.exports;
249     if (typeof(scope.exportsGlobal) === "object") {
250       for (let k in scope.exportsGlobal) {
251         if (k !== "__exposedProps__" && Object.prototype.hasOwnProperty.call(scope.exportsGlobal, k)) {
252           try { gsbox[k] = scope.exportsGlobal[k]; } catch (e) {}
253         }
254       }
255     }
256     return scope.exports;
257   }, urlJS+"modules/", newURI, loadSubScript, Cu.reportError, Cu.import, modules);
258   Object.defineProperty(gsbox, "require", {get: function () sb_require});
261   // alert("msg")
262   let alertfn;
263   if (isXPCShell) {
264     let sb_alert = tieto(this, function (msg) {
265       let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
266       file.initWithPath("/usr/bin/xmessage");
267       let params = [""+msg];
268       let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
269       process.init(file);
270       process.run(true, params, params.length);
271     });
272     Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
273   } else {
274     let sb_alert = tieto(gsbox, function (promptSvc, addonName, msg) {
275       promptSvc.alert(null, addonName, ""+msg);
276     }, promptSvc, addonName);
277     Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
278   }
281   let startupHooks = [], shutdownHooks = [];
283   function runHooks (list, rev) {
284     let rest = Array.prototype.splice.call(arguments, 2, arguments.length);
285     // hack: if (rev === false): this is "onStartup"
286     if (rev === false) {
287       // now load main file
288       let loadmain = ("__dontLoadMainJS" in global ? !global.__dontLoadMainJS : true);
289       if (loadmain) gsbox.include(urlJS+"main.js");
290     }
291     let idx = (rev ? list.length : -1);
292     for (;;) {
293       if (rev) {
294         if (--idx < 0) break;
295       } else {
296         if (++idx >= list.length) break;
297       }
298       let h = list[idx];
299       try {
300         //conService.logStringMessage("running hook: '"+h.name+"'");
301         let args = Array.prototype.slice.call(rest);
302         //Array.prototype.push.apply(args, arguments);
303         h.cback.apply(gsbox, args);
304       } catch (e) {
305         Cu.reportError("HOOK ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
306         Cu.reportError(e);
307       }
308     }
309   }
311   function addHook (hooks, name, cback) {
312     if (typeof(cback) === "undefined") {
313       // register*Hook(hookfn)
314       cback = name;
315       name = null;
316     }
317     if (typeof(name) === "undefined") throw new Error("name or `null` expected");
318     if (!name) name = "";
319     if (typeof(name) !== "string") throw new Error("name or `null` expected");
320     if (typeof(cback) !== "function") throw new Error("function expected");
321     hooks.push({name:name, cback:cback});
322   }
324   const sb_rsth = tieto(gsbox, addHook, startupHooks);
325   const sb_rsuh = tieto(gsbox, addHook, shutdownHooks);
327   // registerStartupHook(name, hookfn)
328   Object.defineProperty(gsbox, "registerStartupHook", {get: function () sb_rsth});
329   // registerShutdownHook(name, hookfn)
330   Object.defineProperty(gsbox, "registerShutdownHook", {get: function () sb_rsuh});
332   return {
333     sbox: gsbox,
334     tieto: tieto,
335     defProp: tieto(this, function (mainobj, name, ops) {
336       //Cu.reportError("GS: defining property '"+name+"'");
337       mainobj.defineProperty(gsbox, name, ops);
338     }, Object),
339     onInstall: function () {},
340     onUnistall: function () {},
341     onStartup: tieto(this, runHooks, startupHooks, false),
342     onShutdown: tieto(this, runHooks, shutdownHooks, true),
343   };