better `tieto()`
[guerillascript.git] / main / init.js
blob9abe67dec80521fddbd2a7a703bb885df629fdcf
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(Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal), {
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, "jscodeUrl", {get: function () urlJS});
60 Object.defineProperty(gsbox, "contentUrl", {get: function () urlMain});
62 // `tieto` API: ties method to object, so it always has correct `this`
63 // you may specify additional arguments that will be always passed unchanged
64 function tieto (obj, method) {
65 let mt;
66 if (typeof(obj) === "undefined") throw new Error("object or `null` expected");
67 if (obj !== null && typeof(obj) !== "object") throw new Error("object or `null` expected");
68 switch (typeof(method)) {
69 case "string":
70 if (!obj) throw new Error("can't take method by name from `null` object");
71 if (!(method in obj)) throw new Error("method '"+method+"' doesn't exist in object '"+obj+"'");
72 mt = obj[method];
73 if (typeof(mt) !== "function") throw new Error("method '"+method+"' isn't a function in object '"+obj+"'");
74 break;
75 case "function":
76 if (!obj) obj = {};
77 mt = method;
78 break;
79 default:
80 throw new Error("`method` should be function or method name");
82 // hack: replace first array item; avoiding extra arrays
83 let rest = Array.prototype.splice.call(arguments, 1, arguments.length);
84 rest[0] = obj;
85 // now use `bind`
86 return mt.bind.apply(mt, rest);
89 const sb_tieto = tieto(gsbox, tieto);
90 Object.defineProperty(gsbox, "tieto", {get: function () sb_tieto});
92 const sb_Components = tieto(gsbox, function (c) c, Components);
93 const sb_Cu = tieto(gsbox, function (cu) cu, Cu);
94 const sb_Cc = tieto(gsbox, function (cc) cc, Cc);
95 const sb_Ci = tieto(gsbox, function (ci) ci, Ci);
96 const sb_Cr = tieto(gsbox, function (cr) cr, Cr);
98 Object.defineProperty(gsbox, "Components", {get: function () sb_Components()});
99 Object.defineProperty(gsbox, "Cu", {get: function () sb_Cu()});
100 Object.defineProperty(gsbox, "Cc", {get: function () sb_Cc()});
101 Object.defineProperty(gsbox, "Ci", {get: function () sb_Ci()});
102 Object.defineProperty(gsbox, "Cr", {get: function () sb_Cr()});
104 // console log functions
105 const oldlogerr = (isXPCShell ? global.logError : null);
106 function logErr (str) {
107 if (oldlogerr) oldlogerr(str); else Cu.reportError(str);
109 const sb_logError = tieto(gsbox, function (logErrMsg) {
110 if (arguments.length > 1) {
111 let s = "";
112 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
113 logErrMsg(s);
115 }, logErr);
116 Object.defineProperty(gsbox, "logError", {get: function () sb_logError});
118 const sb_logException = tieto(gsbox, function (logErrMsg, msg, e) {
119 if (typeof(e) === "undefined" && typeof(msg) === "object") {
120 e = msg;
121 msg = "SOME";
123 if (typeof(e) !== "undefined" /*&& e instanceof global.Error*/ && e) {
124 logErrMsg(""+msg+" ERROR: "+e.name+": "+e.message+" : "+e.lineNumber);
125 if (e.stack) logErrMsg(e.stack);
126 logErrMsg(e);
127 } else {
128 logErrMsg(""+msg);
129 if (typeof(e) !== "undefined" && e) logErrMsg("e="+e);
131 }, logErr);
132 Object.defineProperty(gsbox, "logException", {get: function () sb_logException});
134 // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIConsoleService#logStringMessage() - wstring / wide string
135 const conService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
136 const oldconlog = (isXPCShell ? global.conlog : null);
137 function logStr (str) {
138 if (oldconlog) oldconlog(str); else conService.logStringMessage(str);
141 const sb_conlog = tieto(gsbox, function (logStrMsg) {
142 if (arguments.length > 1) {
143 let le = ("addonOptions" in this ? this["addonOptions"].logEnabled : true);
144 if (!le) return;
145 let s = "";
146 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
147 logStrMsg(s);
149 }, logStr);
150 Object.defineProperty(gsbox, "conlog", {get: function () sb_conlog});
152 const sb_debuglog = tieto(gsbox, function (logStrMsg) {
153 if (arguments.length > 1) {
154 if ("addonOptions" in this) {
155 let ao = this["addonOptions"];
156 //if (("logEnabled" in ao) && !ao.logEnabled) return;
157 if (!("debugMode" in ao) || !ao.debugMode) return;
159 let s = "";
160 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
161 logStrMsg(s);
163 }, logStr);
164 Object.defineProperty(gsbox, "debuglog", {get: function () sb_debuglog});
166 const sb_print = tieto(gsbox, function (logStrMsg) {
167 if (arguments.length > 1) {
168 let s = "";
169 for (let idx = 1; idx < arguments.length; ++idx) {
170 let t = ""+arguments[idx];
171 if (t) s += " "+t;
173 logStrMsg(s.substr(1));
175 }, logStr);
176 Object.defineProperty(gsbox, "print", {get: function () sb_print});
178 // script loaders
179 const newURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newURI;
180 //const newFileURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newFileURI;
181 const loadSubScript = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
183 const specsSchemes = [
184 { re: /^mainjs:(.+)$/, pfx: urlJS},
185 { re: /^main:(.+)$/, pfx: urlMain}
187 function toFullUrl (scriptSpec, name) {
188 if (/^(?:chrome|resource|file):/.test(name)) return name;
189 for (let sp of specsSchemes) {
190 let mt = name.match(sp.re);
191 if (mt) return sp.pfx+mt[1];
193 return scriptSpec+name;
196 // include("incname")
197 const sb_include = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, name) {
198 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
199 if (!(/.\.js$/.test(name))) name += ".js";
200 let fullUrl = toFullUrl(scriptSpec, name);
201 if (isXPCShell) print("including '"+name+"' : ["+fullUrl+"]");
202 try {
203 let uri = newURI(fullUrl, null, null);
204 loadSubScript(uri.spec, gsbox, "UTF-8");
205 } catch (e) {
206 if (typeof(e) === "object") {
207 reportError("INCLUDE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
208 reportError(e);
209 } else {
210 reportError("INCLUDE ERROR '"+name+"': "+e);
212 throw e;
214 }, urlJS+"includes/", newURI, loadSubScript, Cu.reportError);
215 Object.defineProperty(gsbox, "include", {get: function () sb_include});
218 function loadTextContentsFromUrl (url, encoding) {
219 if (typeof(encoding) !== "string") encoding = "UTF-8"; else encoding = encoding||"UTF-8";
221 function toUnicode (text) {
222 if (typeof(text) === "string") {
223 if (text.length == 0) return "";
224 } else if (text instanceof ArrayBuffer) {
225 if (text.byteLength == 0) return "";
226 text = new Uint8Array(text);
227 return new TextDecoder(encoding).decode(text);
228 } else if ((text instanceof Uint8Array) || (text instanceof Int8Array)) {
229 if (text.length == 0) return "";
230 return new TextDecoder(encoding).decode(text);
231 } else {
232 return "";
234 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
235 converter.charset = encoding;
236 let res = converter.ConvertToUnicode(text);
237 if (res.length >= 3 && res.substr(0, 3) == "\u00EF\u00BB\u00BF") res = res.substr(3); // fuck BOM
238 return res;
241 // download from URL
242 let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
243 req.mozBackgroundRequest = true;
244 req.open("GET", url, false);
245 req.timeout = 30*1000;
246 const cirq = Ci.nsIRequest;
247 req.channel.loadFlags |= cirq.INHIBIT_CACHING|cirq.INHIBIT_PERSISTENT_CACHING|cirq.LOAD_BYPASS_CACHE|cirq.LOAD_ANONYMOUS;
248 req.responseType = "arraybuffer";
249 //if (sendCookies) addCookies(req, urlsend);
250 let error = false;
251 req.onerror = function () { error = true; }; // just in case
252 req.ontimeout = function () { error = true; }; // just in case
253 req.send(null);
254 if (!error) {
255 error = (req.status != 0 && Math.floor(req.status/100) != 2);
256 //(/^file:/.test(file.spec) ? (req.status != 0) : (Math.floor(req.status/100) != 2));
258 if (error) throw new Error("can't load URI contents: status="+req.status+"; error="+error);
259 return toUnicode(req.response, encoding);
263 let modules = {}; // list of loaded modules
265 // require("modname")
266 const sb_require = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, import_, modules, name) {
267 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
268 if (!(/.\.js$/.test(name))) name += ".js";
269 if (name in modules) return modules[name];
270 let fullUrl = toFullUrl(scriptSpec, name);
271 if (isXPCShell) print("requiring '"+name+"' : ["+fullUrl+"]");
272 //else sb_debuglog("requiring '", name, "' : [", fullUrl, "]");
273 //let scope = {};
274 let scope = Object.create(gsbox);
275 scope.exports = {};
276 scope.exportsGlobal = {};
278 try {
279 let uri = newURI(fullUrl, null, null);
281 let box = new Cu.Sandbox(Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal), {
282 wantComponents: false,
283 wantXrays: false,
284 wantXHRConstructor: true,
285 sandboxPrototype: scope,
288 // uses(name[, name]...);
289 let sc_uses = tieto(box, function (scope, import_) {
290 if (arguments.length > 3) {
291 for (let idx = 2; idx < arguments.length; ++idx) import_(arguments[idx], scope);
292 return null;
293 } else if (arguments.length == 3) {
294 return import_(arguments[2], scope);
296 }, scope, Cu.import);
297 Object.defineProperty(box, "uses", {get: function () sc_uses});
299 // include("incname")
300 let sc_include = tieto(box, function (scriptSpec, newURI, loadSubScript, reportError, box, name) {
301 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
302 if (!(/.\.js$/.test(name))) name += ".js";
303 let fullUrl = toFullUrl(scriptSpec, name);
304 if (isXPCShell) print("including '"+name+"' : ["+fullUrl+"]");
305 try {
306 let uri = newURI(fullUrl, null, null);
307 loadSubScript(uri.spec, box, "UTF-8");
308 } catch (e) {
309 if (typeof(e) === "object") {
310 reportError("INCLUDE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
311 reportError(e);
312 } else {
313 reportError("INCLUDE ERROR '"+name+"': "+e);
315 throw e;
317 }, urlJS+"includes/", newURI, loadSubScript, Cu.reportError, box);
318 Object.defineProperty(box, "include", {get: function () sc_include});
320 let text = loadTextContentsFromUrl(uri.spec);
321 Cu.evalInSandbox(text, box, "ECMAv5", name, 1);
322 } catch (e) {
323 if (typeof(e) === "object") {
324 reportError("REQUIRE ERROR'"+name+"': "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
325 reportError(e);
326 } else {
327 reportError("REQUIRE ERROR '"+name+"': "+e);
329 throw e;
332 modules[name] = scope.exports;
333 if (typeof(scope.exportsGlobal) === "object") {
334 for (let k in scope.exportsGlobal) {
335 if (k !== "__exposedProps__" && Object.prototype.hasOwnProperty.call(scope.exportsGlobal, k)) {
336 try { gsbox[k] = scope.exportsGlobal[k]; } catch (e) {}
340 return scope.exports;
341 }, urlJS+"modules/", newURI, loadSubScript, Cu.reportError, Cu.import, modules);
342 Object.defineProperty(gsbox, "require", {get: function () sb_require});
345 // alert("msg")
346 let alertfn;
347 if (isXPCShell) {
348 let sb_alert = tieto(this, function (msg) {
349 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
350 file.initWithPath("/usr/bin/xmessage");
351 let params = [""+msg];
352 let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
353 process.init(file);
354 process.run(true, params, params.length);
356 Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
357 } else {
358 let sb_alert = tieto(gsbox, function (promptSvc, addonName, msg) {
359 promptSvc.alert(null, addonName, ""+msg);
360 }, promptSvc, addonName);
361 Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
365 let startupHooks = [], shutdownHooks = [];
366 let winloadHooks = [], winunloadHooks = [];
368 let mainLoaded = false;
370 function runHooks (hooktype, list, rev, firsttime) {
371 let rest = Array.prototype.splice.call(arguments, 2, arguments.length);
372 let callmain = false;
373 if (firsttime) {
374 //Cu.reportError("runHooks ("+hooktype+"): first time");
375 if (!mainLoaded) {
376 //Cu.reportError("runHooks ("+hooktype+"): main not loaded");
377 // now load main file
378 mainLoaded = true;
379 let loadmain = ("__dontLoadMainJS" in global ? !global.__dontLoadMainJS : true);
380 if (loadmain) {
381 //Cu.reportError("runHooks ("+hooktype+"): loading main...");
382 //Cu.reportError("runHooks ("+hooktype+"): len="+list.length);
383 gsbox.include("mainjs:prefs/prefs.js"); // define default extension preferences
384 gsbox.require("mainjs:prefsengine.js");
385 callmain = true;
386 //Cu.reportError("runHooks ("+hooktype+"): len="+list.length);
387 gsbox.include("mainjs:main.js");
391 if (hooktype === "shutdown" && typeof(gsbox.main_preshutdown) === "function") {
392 try {
393 gsbox.main_preshutdown();
394 } catch (e) {
395 Cu.reportError("PRESHUTDOWN ERROR: "+e.name+": "+e.message+" : "+e.lineNumber);
396 if (e.stack) Cu.reportError(e.stack);
397 Cu.reportError(e);
400 let idx = (rev ? list.length : -1);
401 for (;;) {
402 if (rev) {
403 if (--idx < 0) break;
404 } else {
405 if (++idx >= list.length) break;
407 let h = list[idx];
408 try {
409 //conService.logStringMessage("running hook: '"+h.name+"'");
410 let args = Array.prototype.slice.call(rest);
411 //Array.prototype.push.apply(args, arguments);
412 h.cback.apply(gsbox, args);
413 } catch (e) {
414 callmain = false;
415 Cu.reportError("HOOK '"+hooktype+"' ("+h.name+") ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+(e.stack ? "\n"+e.stack : ""));
416 Cu.reportError(e);
417 if (e.stack) Cu.reportError(e.stack);
420 if (callmain && typeof(gsbox.main_postinit) === "function") gsbox.main_postinit();
423 function addHook (hooks, name, cback) {
424 if (typeof(cback) === "undefined") {
425 // register*Hook(hookfn)
426 cback = name;
427 name = null;
429 if (typeof(name) === "undefined") throw new Error("name or `null` expected");
430 if (!name) name = "";
431 if (typeof(name) !== "string") throw new Error("name or `null` expected");
432 if (typeof(cback) !== "function") throw new Error("function expected");
433 for (let obj in hooks) if (obj.cback === cback) return; // nothing to do
434 hooks.push({name:name, cback:cback});
437 const sb_rsth = tieto(gsbox, addHook, startupHooks);
438 const sb_rsuh = tieto(gsbox, addHook, shutdownHooks);
440 // registerStartupHook(name, hookfn)
441 // registerShutdownHook(name, hookfn)
442 Object.defineProperty(gsbox, "registerStartupHook", {get: function () sb_rsth});
443 Object.defineProperty(gsbox, "registerShutdownHook", {get: function () sb_rsuh});
445 const sb_rwlh = tieto(gsbox, addHook, winloadHooks);
446 const sb_rwuh = tieto(gsbox, addHook, winunloadHooks);
448 // registerWindowLoadHook(name, hookfn)
449 // registerWindowUnloadHook(name, hookfn)
450 Object.defineProperty(gsbox, "registerWindowLoadHook", {get: function () sb_rwlh});
451 Object.defineProperty(gsbox, "registerWindowUnloadHook", {get: function () sb_rwuh});
453 return {
454 sbox: gsbox,
455 tieto: tieto,
456 defProp: tieto(this, function (mainobj, name, ops) {
457 //Cu.reportError("GS: defining property '"+name+"'");
458 mainobj.defineProperty(gsbox, name, ops);
459 }, Object),
460 //onInstall: function () {},
461 //onUnistall: function () {},
462 onStartup: tieto(this, runHooks, "startup", startupHooks, false, true),
463 onShutdown: tieto(this, runHooks, "shutdown", shutdownHooks, true, false),
464 onWindowLoad: tieto(this, runHooks, "window opened", winloadHooks, true, false),
465 onWindowUnload: tieto(this, runHooks, "window closed", winunloadHooks, true, false),