]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/main.jsm
Imported Upstream version 1.1+hg7904
[dactyl.git] / common / modules / main.jsm
1 // Copyright (c) 2009-2014 Kris Maglione <maglione.k@gmail.com>
2 //
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
5 "use strict";
6
7 try {
8
9 defineModule("main", {
10     exports: ["ModuleBase"],
11     require: ["config", "overlay", "services", "util"]
12 });
13
14 var BASE = "resource://dactyl-content/";
15
16 var global = this;
17
18 /**
19  * @class ModuleBase
20  * The base class for all modules.
21  */
22 var ModuleBase = Class("ModuleBase", {
23     /**
24      * @property {[string]} A list of module prerequisites which
25      * must be initialized before this module is loaded.
26      */
27     requires: [],
28
29     toString: function () "[module " + this.constructor.className + "]"
30 });
31
32 var _id = 0;
33
34 var Modules = function Modules(window) {
35     /**
36      * @constructor Module
37      *
38      * Constructs a new ModuleBase class and makes arrangements for its
39      * initialization. Arguments marked as optional must be either
40      * entirely elided, or they must have the exact type specified.
41      * Loading semantics are as follows:
42      *
43      *  - A module is guaranteed not to be initialized before any of its
44      *    prerequisites as listed in its {@see ModuleBase#requires} member.
45      *  - A module is considered initialized once it's been instantiated,
46      *    its {@see Class#init} method has been called, and its
47      *    instance has been installed into the top-level {@see modules}
48      *    object.
49      *  - Once the module has been initialized, its module-dependent
50      *    initialization functions will be called as described hereafter.
51      * @param {string} name The module's name as it will appear in the
52      *     top-level {@see modules} object.
53      * @param {ModuleBase} base The base class for this module.
54      *     @optional
55      * @param {Object} prototype The prototype for instances of this
56      *     object. The object itself is copied and not used as a prototype
57      *     directly.
58      * @param {Object} classProperties The class properties for the new
59      *     module constructor.
60      *     @optional
61      * @param {Object} moduleInit The module initialization functions
62      *     for the new module. Each function is called as soon as the
63      *     named module has been initialized. The constructors are
64      *     guaranteed to be called in the same order that the dependent
65      *     modules were initialized.
66      *     @optional
67      *
68      * @returns {function} The constructor for the resulting module.
69      */
70     function Module(name, ...args) {
71
72         var base = ModuleBase;
73         if (callable(args[0]))
74             base = args.shift();
75
76         let [prototype, classProperties, moduleInit] = args;
77         prototype._metaInit_ = function () {
78             delete module.prototype._metaInit_;
79             Class.replaceProperty(modules, module.className, this);
80         };
81         const module = Class(name, base, prototype, classProperties);
82
83         module.INIT = moduleInit || {};
84         module.modules = modules;
85         module.prototype.INIT = module.INIT;
86         module.requires = prototype.requires || [];
87         Module.list.push(module);
88         Module.constructors[name] = module;
89         return module;
90     }
91     Module.list = [];
92     Module.constructors = {};
93
94     function newContext(proto, normal, name) {
95         if (normal)
96             return create(proto);
97
98         sandbox = Components.utils.Sandbox(window, { sandboxPrototype: proto || modules,
99                                                      sandboxName: name || ("Dactyl Sandbox " + ++_id),
100                                                      wantXrays: true });
101
102         // Hack:
103         // sandbox.Object = jsmodules.Object;
104         sandbox.File = global.File;
105         sandbox.Math = global.Math;
106         sandbox.Set  = global.Set;
107         return sandbox;
108     };
109
110
111     const BASES = [BASE, "resource://dactyl-local-content/"];
112
113     let proxyCache = {};
114     if (config.haveGecko(29))
115         var proxy = new Proxy(window, {
116             get: function window_get(target, prop) {
117                 // `in`, not `hasOwnProperty`, because we want to return
118                 // unbound methods in `Object.prototype`
119                 if (prop in proxyCache)
120                     return proxyCache[prop];
121
122                 let p = target[prop];
123                 if (callable(p))
124                     return proxyCache[prop] = p.bind(target);
125
126                 return p;
127             },
128
129             set: function window_set(target, prop, val) {
130                 return target[prop] = val;
131             }
132         });
133     else {
134         // Bug 814892
135         let o = {};
136         // Oh, the brokenness... See bug 793210
137         Object.preventExtensions(o);
138         proxy = new Proxy(o, {
139             get: function window_get(target, prop) {
140                 // `in`, not `hasOwnProperty`, because we want to return
141                 // unbound methods in `Object.prototype`
142                 if (prop in proxyCache)
143                     return proxyCache[prop];
144
145                 let p = window[prop];
146                 if (callable(p))
147                     return proxyCache[prop] = p.bind(window);
148
149                 return p;
150             },
151
152             set: function window_set(target, prop, val) {
153                 return window[prop] = val;
154             },
155
156             getOwnPropertyDescriptor: function (target, prop) Object.getOwnPropertyDescriptor(window, prop),
157             getOwnPropertyNames: function (target, prop) Object.getOwnPropertyNames(window),
158             defineProperty: function (target, prop, desc) Object.defineProperty(window, prop, desc),
159             deleteProperty: function (target, prop) { delete window[prop]; },
160             has: function (target, prop) prop in window,
161             hasOwn: function (target, prop) hasOwnProperty(window, prop),
162             enumerate: function (target) (p for (p in window)),
163             iterate: function (target) (p for (p of window))
164         });
165     }
166
167     var jsmodules = newContext(proxy, false, "Dactyl `jsmodules`");
168     jsmodules.NAME = "jsmodules";
169
170     const create = bind("create", jsmodules.Object);
171
172     const modules = update(create(jsmodules), {
173         yes_i_know_i_should_not_report_errors_in_these_branches_thanks: [],
174
175         jsmodules: jsmodules,
176
177         Module: Module,
178
179         load: function load(script) {
180             for (let [i, base] in Iterator(BASES)) {
181                 try {
182                     JSMLoader.loadSubScript(base + script + ".js", modules, "UTF-8");
183                     return;
184                 }
185                 catch (e) {
186                     if (typeof e !== "string") {
187                         util.dump("Trying: " + (base + script + ".js") + ":");
188                         util.reportError(e);
189                     }
190                 }
191             }
192             try {
193                 require(script, jsmodules);
194             }
195             catch (e) {
196                 util.dump("Loading script " + script + ":");
197                 util.reportError(e);
198             }
199         },
200
201         newContext: newContext,
202
203         get ownPropertyValues() array.compact(
204                 Object.getOwnPropertyNames(this)
205                       .map(name => Object.getOwnPropertyDescriptor(this, name).value)),
206
207         get moduleList() this.ownPropertyValues.filter(mod => (mod instanceof this.ModuleBase || mod.isLocalModule))
208     });
209
210     modules.plugins = create(modules);
211     modules.modules = modules;
212     return modules;
213 }
214
215 config.loadStyles();
216
217 overlay.overlayWindow(Object.keys(config.overlays),
218                       function _overlay(window) ({
219     ready: function onInit(document) {
220         const modules = Modules(window);
221         modules.moduleManager = this;
222         this.modules = modules;
223         this.jsmodules = modules.jsmodules;
224
225         window.dactyl = { modules: modules };
226
227         defineModule.time("load", null, function _load() {
228             config.modules.global
229                   .forEach(function (name) {
230                       if (!isArray(name))
231                           defineModule.time("load", name, require, null, name, modules.jsmodules);
232                       else
233                           lazyRequire(name[0], name.slice(1), modules.jsmodules);
234                   });
235
236             config.modules.window
237                   .forEach(name => { defineModule.time("load", name, modules.load, modules, name); });
238         }, this);
239     },
240
241     load: function onLoad(document) {
242         let self = this;
243
244         var { modules, Module } = this.modules;
245         delete window.dactyl;
246
247         this.startTime = Date.now();
248         this.deferredInit = { load: {} };
249         this.seen = RealSet();
250         this.loaded = RealSet();
251         modules.loaded = this.loaded;
252
253         this.modules = modules;
254
255         this.scanModules();
256         this.initDependencies("init");
257
258         modules.config.scripts.forEach(modules.load);
259
260         this.scanModules();
261
262         defineModule.modules.forEach(function defModule({ lazyInit, constructor: { className } }) {
263             if (!lazyInit) {
264                 Class.replaceProperty(modules, className, modules[className]);
265                 this.initDependencies(className);
266             }
267             else
268                 modules.__defineGetter__(className, function () {
269                     let module = modules.jsmodules[className];
270                     Class.replaceProperty(modules, className, module);
271                     if (module.reallyInit)
272                         module.reallyInit(); // :(
273
274                     if (!module.lazyDepends)
275                         self.initDependencies(className);
276                     return module;
277                 });
278         }, this);
279     },
280
281     cleanup: function cleanup(window) {
282         overlay.windows.delete(window);
283
284         Cu.nukeSandbox(this.jsmodules);
285     },
286
287     unload: function unload(window) {
288         for (let mod of this.modules.moduleList.reverse()) {
289             mod.stale = true;
290
291             if ("destroy" in mod)
292                 util.trapErrors("destroy", mod);
293         }
294     },
295
296     visible: function visible(window) {
297         // Module.list.forEach(load);
298         this.initDependencies("load");
299         this.modules.times = update({}, defineModule.times);
300
301         defineModule.loadLog.push("Loaded in " + (Date.now() - this.startTime) + "ms");
302
303         overlay.windows.add(window);
304     },
305
306     loadModule: function loadModule(module, prereq, frame) {
307         let { loaded, seen } = this;
308         let { Module, modules } = this.modules;
309
310         if (isString(module)) {
311             if (!Module.constructors.hasOwnProperty(module))
312                 modules.load(module);
313             module = Module.constructors[module];
314         }
315
316         try {
317             if (loaded.has(module.className))
318                 return;
319
320             if (seen.add(module.className))
321                 throw Error("Module dependency loop.");
322
323             for (let dep in values(module.requires))
324                 this.loadModule(Module.constructors[dep], module.className);
325
326             defineModule.loadLog.push(
327                 "Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ")
328                     + module.className);
329
330             if (frame && frame.filename)
331                 defineModule.loadLog.push("  from: " + util.fixURI(frame.filename) + ":" + frame.lineNumber);
332
333             let obj = defineModule.time(module.className, "init", module);
334             Class.replaceProperty(modules, module.className, obj);
335
336             loaded.add(module.className);
337
338             if (loaded.has("dactyl") && obj.signals)
339                 modules.dactyl.registerObservers(obj);
340
341             if (!module.lazyDepends)
342                 this.initDependencies(module.className);
343         }
344         catch (e) {
345             util.dump("Loading " + (module && module.className) + ":");
346             util.reportError(e);
347         }
348         return modules[module.className];
349     },
350
351     deferInit: function deferInit(name, INIT, mod) {
352         let { modules } = this.modules;
353
354         let init = this.deferredInit[name] || {};
355         this.deferredInit[name] = init;
356
357         let className = mod.className || mod.constructor.className;
358
359         if (!hasOwnProperty(init, className)) {
360             init[className] = function callee() {
361                 function finish() {
362                     this.currentDependency = className;
363                     defineModule.time(className, name, INIT[name], mod,
364                                       modules.dactyl, modules, window);
365                 }
366                 if (!callee.frobbed) {
367                     callee.frobbed = true;
368                     if (modules[name] instanceof Class)
369                         modules[name].withSavedValues(["currentDependency"], finish);
370                     else
371                         finish.call({});
372                 }
373             };
374
375             INIT[name].require = function (name) { init[name](); };
376         }
377     },
378
379     scanModules: function scanModules() {
380         let { Module, modules } = this.modules;
381
382         defineModule.modules.forEach((mod) => {
383             let names = RealSet(Object.keys(mod.INIT));
384             if ("init" in mod.INIT)
385                 names.add("init");
386
387             for (let name of names)
388                 this.deferInit(name, mod.INIT, mod);
389         });
390
391         Module.list.forEach((mod) => {
392             if (!mod.frobbed) {
393                 modules.__defineGetter__(mod.className, () => {
394                     delete modules[mod.className];
395                     return this.loadModule(mod.className, null, Components.stack.caller);
396                 });
397                 Object.keys(mod.prototype.INIT)
398                       .forEach((name) => { this.deferInit(name, mod.prototype.INIT, mod); });
399             }
400             mod.frobbed = true;
401         });
402     },
403
404     initDependencies: function initDependencies(name, parents) {
405         for (let [k, v] in Iterator(this.deferredInit[name] || {}))
406             if (!parents || ~parents.indexOf(k))
407                 util.trapErrors(v);
408     }
409 }));
410
411 endModule();
412
413 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
414
415 // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: