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