better directory layout; unification with non-bootstrapping addons
[guerillascript.git] / main / init.js
blob122a2f151ca31d8fb98b7e2085582b7b46f0e316
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 (addonName, sandboxName, urlMain, urlJS) {
13 const {utils:Cu, classes:Cc, interfaces:Ci, results:Cr} = Components;
15 // bootstrapped addons has no `window`
16 const principal = (typeof(window) !== "undefined" ? window : Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal));
17 let gsbox = new Cu.Sandbox(principal, {
18 sandboxName: sandboxName,
19 wantComponents: false,
20 wantXrays: false,
21 wantXHRConstructor: true,
22 });
23 Cu.import("resource://gre/modules/Services.jsm", gsbox);
25 // bootstrapped addons has no `window`
26 if (typeof(window) !== "undefined") {
27 gsbox.window = window;
28 gsbox.document = window.document;
31 // add some common API
32 Object.defineProperty(gsbox, "isXPCShell", {get: function () false});
33 Object.defineProperty(gsbox, "contentURL", {get: function () urlMain});
35 // `tieto` API: ties method to object, so it always has correct `this`
36 // you may specify additional arguments that will be always there
37 function tieto (obj, method) {
38 let mt;
39 if (typeof(obj) === "undefined") throw new Error("object or `null` expected");
40 if (obj !== null && typeof(obj) !== "object") throw new Error("object or `null` expected");
41 switch (typeof(method)) {
42 case "string":
43 if (!obj) throw new Error("can't take method by name from `null` object");
44 if (!(method in obj)) throw new Error("method '"+method+"' doesn't exist in object '"+obj+"'");
45 mt = obj[method];
46 if (typeof(mt) !== "function") throw new Error("method '"+method+"' isn't a function in object '"+obj+"'");
47 break;
48 case "function":
49 if (!obj) obj = {};
50 mt = method;
51 break;
52 default:
53 throw new Error("`method` should be function or method name");
55 let me = obj;
56 let rest = Array.prototype.splice.call(arguments, 2, arguments.length);
58 let stk;
59 try { throw new Error("!"); } catch (e) {
60 //print("TIETO!\n"+e.stack);
61 stk = e.stack.split("\n")[1];
64 return function () {
65 //print(stk);
66 // `rest` is reused for each invocation, so copy it, and append new arguments to copy
67 let args = Array.prototype.slice.call(rest);
68 Array.prototype.push.apply(args, arguments);
69 return mt.apply(me, args);
72 const sb_tieto = tieto(gsbox, tieto);
73 Object.defineProperty(gsbox, "tieto", {get: function () sb_tieto});
75 const sb_Components = tieto(gsbox, function (c) c, Components);
76 const sb_Cu = tieto(gsbox, function (cu) cu, Cu);
77 const sb_Cc = tieto(gsbox, function (cc) cc, Cc);
78 const sb_Ci = tieto(gsbox, function (ci) ci, Ci);
79 const sb_Cr = tieto(gsbox, function (cr) cr, Cr);
81 Object.defineProperty(gsbox, "Components", {get: function () sb_Components()});
82 Object.defineProperty(gsbox, "Cu", {get: function () sb_Cu()});
83 Object.defineProperty(gsbox, "Cc", {get: function () sb_Cc()});
84 Object.defineProperty(gsbox, "Ci", {get: function () sb_Ci()});
85 Object.defineProperty(gsbox, "Cr", {get: function () sb_Cr()});
87 // console log functions
88 const oldlogerr = (typeof(conlog) == "function" ? logError : null);
89 function logErr (str) {
90 if (oldlogerr) oldlogerr(str); else Cu.reportError(str);
92 const sb_logError = tieto(gsbox, function (logErrMsg) {
93 if (arguments.length) {
94 let s = "";
95 for (let idx = 0; idx < arguments.length; ++idx) s += ""+arguments[idx];
96 logErrMsg(s);
98 }, logErr);
99 Object.defineProperty(gsbox, "logError", {get: function () sb_logError});
101 const sb_logException = tieto(gsbox, function (logErrMsg) {
102 if (e instanceof this.Error) {
103 logErrMsg(""+msg+" ERROR: "+e.name+": "+e.message+" : "+e.lineNumber)
104 if (e.stack) logErrMsg(e.stack);
105 logErrMsg(e);
106 } else {
107 logErrMsg(""+msg);
109 }, logErr);
110 Object.defineProperty(gsbox, "logException", {get: function () sb_logException});
112 //let conlogEnabled = true;
113 //Object.defineProperty(gsbox, "conlogEnabled", {get: tieto(this, function () this.conlogEnabled)});
115 // https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIConsoleService#logStringMessage() - wstring / wide string
116 const conService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
117 const oldconlog = (typeof(conlog) == "function" ? conlog : null);
118 function logStr (str) {
119 if (oldconlog) oldconlog(str); else conService.logStringMessage(str);
122 const sb_conlog = tieto(gsbox, function (logStrMsg) {
123 if (arguments.length > 1) {
124 let le = ("guerillaOptions" in this ? this["guerillaOptions"].logEnabled : true);
125 if (!le) return;
126 let s = "";
127 for (let idx = 1; idx < arguments.length; ++idx) s += ""+arguments[idx];
128 logStrMsg(s);
130 }, logStr);
131 Object.defineProperty(gsbox, "conlog", {get: function () sb_conlog});
133 const sb_print = tieto(gsbox, function (logStrMsg) {
134 if (arguments.length > 1) {
135 let le = ("guerillaOptions" in this ? this["guerillaOptions"].logEnabled : true);
136 if (!le) return;
137 let s = "";
138 let wasPrint = false;
139 for (let idx = 1; idx < arguments.length; ++idx) {
140 let t = ""+arguments[idx];
141 if (t) s += " "+t;
143 logStrMsg(s.substr(1));
145 }, logStr);
146 Object.defineProperty(gsbox, "print", {get: function () sb_print});
148 // script loaders
149 const newURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newURI;
150 //const newFileURI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService).newFileURI;
151 const loadSubScript = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
153 // include("incname")
154 const sb_include = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, name) {
155 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
156 if (!(/.\.js$/.test(name))) name += ".js";
157 let fullURL = (/^(?:chrome|resource):/.test(name) ? name : scriptSpec+name); // hack
158 //reportError("including '"+name+"' : ["+fullURL+"]");
159 try {
160 let uri = newURI(fullURL, null, null);
161 loadSubScript(uri.spec, gsbox, "UTF-8");
162 } catch (e) {
163 reportError("INCLUDE ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
164 reportError(e);
165 throw e;
167 }, urlJS+"includes/", newURI, loadSubScript, Cu.reportError);
168 Object.defineProperty(gsbox, "include", {get: function () sb_include});
171 let modules = {}; // list of loaded modules
173 // require("modname")
174 const sb_require = tieto(gsbox, function (scriptSpec, newURI, loadSubScript, reportError, import_, modules, name) {
175 if (name.length == 0) throw new Error("invalid include name: '"+name+"'");
176 if (!(/.\.js$/.test(name))) name += ".js";
177 if (name in modules) return modules[name];
178 let fullURL = (/^(?:chrome|resource):/.test(name) ? name : scriptSpec+name); // hack
179 //reportError("requiring '"+name+"' : ["+fullURL+"]");
180 //let scope = {};
181 let scope = Object.create(gsbox);
182 // uses(name[, name]...);
183 let sc_uses = tieto(gsbox, function (scope, import_) {
184 if (arguments.length > 3) {
185 for (let idx = 2; idx < arguments.length; ++idx) import_(arguments[idx], scope);
186 return null;
187 } else if (arguments.length == 3) {
188 return import_(arguments[2], scope);
190 }, scope, import_);
191 Object.defineProperty(scope, "uses", {get: function () sc_uses});
192 scope.exports = {};
193 scope.exportsGlobal = {};
195 for (let k in gsbox) {
196 print("k=["+k+"]");
197 if (k !== "__exposedProps__" && !(k in scope) && Object.prototype.hasOwnProperty.call(gsbox, k)) {
198 print(" EXPORT: k=["+k+"]");
199 try { scope[k] = gsbox[k]; } catch (e) {}
203 try {
204 let uri = newURI(fullURL, null, null);
205 loadSubScript(uri.spec, scope, "UTF-8");
206 } catch (e) {
207 reportError("REQUIRE ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
208 reportError(e);
209 throw e;
211 modules[name] = scope.exports;
212 if (typeof(scope.exportsGlobal) === "object") {
213 for (let k in scope.exportsGlobal) {
214 if (k !== "__exposedProps__" && Object.prototype.hasOwnProperty.call(scope.exportsGlobal, k)) {
215 try { gsbox[k] = scope.exportsGlobal[k]; } catch (e) {}
219 return scope.exports;
220 }, urlJS+"modules/", newURI, loadSubScript, Cu.reportError, Cu.import, modules);
221 Object.defineProperty(gsbox, "require", {get: function () sb_require});
224 const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
225 // alert("msg")
226 const sb_alert = tieto(gsbox, function (promptSvc, addonName, msg) {
227 promptSvc.alert(null, addonName, ""+msg);
228 }, promptSvc, addonName);
229 Object.defineProperty(gsbox, "alert", {get: function () sb_alert});
232 let startupHooks = [], shutdownHooks = [];
234 function runHooks (list, rev) {
235 let rest = Array.prototype.splice.call(arguments, 2, arguments.length);
236 // hack: if (rev === false): this is "onStartup"
237 if (rev === false) {
238 // now load main file
239 gsbox.include(urlJS+"main.js");
241 let idx = (rev ? list.length : -1);
242 for (;;) {
243 if (rev) {
244 if (--idx < 0) break;
245 } else {
246 if (++idx >= list.length) break;
248 let h = list[idx];
249 try {
250 //conService.logStringMessage("running hook: '"+h.name+"'");
251 let args = Array.prototype.slice.call(rest);
252 //Array.prototype.push.apply(args, arguments);
253 h.cback.apply(gsbox, args);
254 } catch (e) {
255 Cu.reportError("HOOK ERROR: "+e.name+": "+e.message+" : "+e.lineNumber+"\n"+e.stack);
256 Cu.reportError(e);
261 function addHook (hooks, name, cback) {
262 if (typeof(cback) === "undefined") {
263 // register*Hook(hookfn)
264 cback = name;
265 name = null;
267 if (typeof(name) === "undefined") throw new Error("name or `null` expected");
268 if (!name) name = "";
269 if (typeof(name) !== "string") throw new Error("name or `null` expected");
270 if (typeof(cback) !== "function") throw new Error("function expected");
271 hooks.push({name:name, cback:cback});
274 const sb_rsth = tieto(gsbox, addHook, startupHooks);
275 const sb_rsuh = tieto(gsbox, addHook, shutdownHooks);
277 // registerStartupHook(name, hookfn)
278 Object.defineProperty(gsbox, "registerStartupHook", {get: function () sb_rsth});
279 // registerShutdownHook(name, hookfn)
280 Object.defineProperty(gsbox, "registerShutdownHook", {get: function () sb_rsuh});
282 return {
283 sbox: gsbox,
284 tieto: tieto,
285 defProp: tieto(this, function (mainobj, name, ops) {
286 //Cu.reportError("GS: defining property '"+name+"'");
287 mainobj.defineProperty(gsbox, name, ops);
288 }, Object),
289 onInstall: function () {},
290 onUnistall: function () {},
291 onStartup: tieto(this, runHooks, startupHooks, false),
292 onShutdown: tieto(this, runHooks, shutdownHooks, true),