]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/main.jsm
41ef16cf570cfcf40a260d4abbb934bf158bcbe6
[dactyl.git] / common / modules / main.jsm
1 // Copyright (c) 2009-2012 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) {
69         let args = Array.slice(arguments);
70
71         var base = ModuleBase;
72         if (callable(args[1]))
73             base = args.splice(1, 1)[0];
74
75         let [, prototype, classProperties, moduleInit] = args;
76         prototype._metaInit_ = function () {
77             delete module.prototype._metaInit_;
78             Class.replaceProperty(modules, module.className, this);
79         };
80         const module = Class(name, base, prototype, classProperties);
81
82         module.INIT = moduleInit || {};
83         module.modules = modules;
84         module.prototype.INIT = module.INIT;
85         module.requires = prototype.requires || [];
86         Module.list.push(module);
87         Module.constructors[name] = module;
88         return module;
89     }
90     Module.list = [];
91     Module.constructors = {};
92
93     const create = window.Object.create.bind(window.Object);
94
95
96     const BASES = [BASE, "resource://dactyl-local-content/"];
97
98     jsmodules = Cu.createObjectIn(window);
99     jsmodules.NAME = "jsmodules";
100     const modules = update(create(jsmodules), {
101         yes_i_know_i_should_not_report_errors_in_these_branches_thanks: [],
102
103         jsmodules: jsmodules,
104
105         get content() this.config.browser.contentWindow || window.content,
106
107         window: window,
108
109         Module: Module,
110
111         load: function load(script) {
112             for (let [i, base] in Iterator(BASES)) {
113                 try {
114                     JSMLoader.loadSubScript(base + script + ".js", modules, "UTF-8");
115                     return;
116                 }
117                 catch (e) {
118                     if (typeof e !== "string") {
119                         util.dump("Trying: " + (base + script + ".js") + ":");
120                         util.reportError(e);
121                     }
122                 }
123             }
124             try {
125                 require(script, jsmodules);
126             }
127             catch (e) {
128                 util.dump("Loading script " + script + ":");
129                 util.reportError(e);
130             }
131         },
132
133         newContext: function newContext(proto, normal, name) {
134             if (normal)
135                 return create(proto);
136
137             if (services.has("dactyl") && services.dactyl.createGlobal)
138                 var sandbox = services.dactyl.createGlobal();
139             else
140                 sandbox = Components.utils.Sandbox(window, { sandboxPrototype: proto || modules,
141                                                              sandboxName: name || ("Dactyl Sandbox " + ++_id),
142                                                              wantXrays: false });
143
144             // Hack:
145             // sandbox.Object = jsmodules.Object;
146             sandbox.File = jsmodules.File;
147             sandbox.Math = jsmodules.Math;
148             sandbox.__proto__ = proto || modules;
149             return sandbox;
150         },
151
152         get ownPropertyValues() array.compact(
153                 Object.getOwnPropertyNames(this)
154                       .map(function (name) Object.getOwnPropertyDescriptor(this, name).value, this)),
155
156         get moduleList() this.ownPropertyValues.filter(function (mod) mod instanceof this.ModuleBase || mod.isLocalModule, this)
157     });
158
159     modules.plugins = create(modules);
160     modules.modules = modules;
161     return modules;
162 }
163
164 config.loadStyles();
165
166 overlay.overlayWindow(Object.keys(config.overlays), function _overlay(window) ({
167     ready: function onInit(document) {
168         const modules = Modules(window);
169         modules.moduleManager = this;
170         this.modules = modules;
171
172         window.dactyl = { modules: modules };
173
174         defineModule.time("load", null, function _load() {
175             config.modules.global
176                   .forEach(function (name) {
177                       if (!isArray(name))
178                           defineModule.time("load", name, require, null, name, modules.jsmodules);
179                       else
180                           lazyRequire(name[0], name.slice(1), modules.jsmodules);
181                   });
182
183             config.modules.window
184                   .forEach(function (name) defineModule.time("load", name, modules.load, modules, name));
185         }, this);
186     },
187
188     load: function onLoad(document) {
189         let self = this;
190
191         var { modules, Module } = this.modules;
192         delete window.dactyl;
193
194         this.startTime = Date.now();
195         this.deferredInit = { load: {} };
196         this.seen = {};
197         this.loaded = {};
198         modules.loaded = this.loaded;
199
200         this.modules = modules;
201
202         this.scanModules();
203         this.initDependencies("init");
204
205         modules.config.scripts.forEach(modules.load);
206
207         this.scanModules();
208
209         defineModule.modules.forEach(function defModule({ lazyInit, constructor: { className } }) {
210             if (!lazyInit) {
211                 Class.replaceProperty(modules, className, modules[className]);
212                 this.initDependencies(className);
213             }
214             else
215                 modules.__defineGetter__(className, function () {
216                     let module = modules.jsmodules[className];
217                     Class.replaceProperty(modules, className, module);
218                     if (module.reallyInit)
219                         module.reallyInit(); // :(
220
221                     if (!module.lazyDepends)
222                         self.initDependencies(className);
223                     return module;
224                 });
225         }, this);
226     },
227
228     cleanup: function cleanup(window) {
229         overlay.windows = overlay.windows.filter(function (w) w != window);
230     },
231
232     unload: function unload(window) {
233         for each (let mod in this.modules.moduleList.reverse()) {
234             mod.stale = true;
235
236             if ("destroy" in mod)
237                 util.trapErrors("destroy", mod);
238         }
239     },
240
241     visible: function visible(window) {
242         // Module.list.forEach(load);
243         this.initDependencies("load");
244         this.modules.times = update({}, defineModule.times);
245
246         defineModule.loadLog.push("Loaded in " + (Date.now() - this.startTime) + "ms");
247
248         overlay.windows = array.uniq(overlay.windows.concat(window), true);
249     },
250
251     loadModule: function loadModule(module, prereq, frame) {
252         let { loaded, seen } = this;
253         let { Module, modules } = this.modules;
254
255         if (isString(module)) {
256             if (!Module.constructors.hasOwnProperty(module))
257                 modules.load(module);
258             module = Module.constructors[module];
259         }
260
261         try {
262             if (Set.has(loaded, module.className))
263                 return;
264
265             if (Set.add(seen, module.className))
266                 throw Error("Module dependency loop.");
267
268             for (let dep in values(module.requires))
269                 this.loadModule(Module.constructors[dep], module.className);
270
271             defineModule.loadLog.push(
272                 "Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ")
273                     + module.className);
274
275             if (frame && frame.filename)
276                 defineModule.loadLog.push("  from: " + util.fixURI(frame.filename) + ":" + frame.lineNumber);
277
278             let obj = defineModule.time(module.className, "init", module);
279             Class.replaceProperty(modules, module.className, obj);
280
281             Set.add(loaded, module.className);
282
283             if (loaded.dactyl && obj.signals)
284                 modules.dactyl.registerObservers(obj);
285
286             if (!module.lazyDepends)
287                 this.initDependencies(module.className);
288         }
289         catch (e) {
290             util.dump("Loading " + (module && module.className) + ":");
291             util.reportError(e);
292         }
293         return modules[module.className];
294     },
295
296     deferInit: function deferInit(name, INIT, mod) {
297         let { modules } = this.modules;
298
299         let init = this.deferredInit[name] || {};
300         this.deferredInit[name] = init;
301
302         let className = mod.className || mod.constructor.className;
303
304         if (!Set.has(init, className)) {
305             init[className] = function callee() {
306                 function finish() {
307                     this.currentDependency = className;
308                     defineModule.time(className, name, INIT[name], mod,
309                                       modules.dactyl, modules, window);
310                 }
311                 if (!callee.frobbed) {
312                     callee.frobbed = true;
313                     if (modules[name] instanceof Class)
314                         modules[name].withSavedValues(["currentDependency"], finish);
315                     else
316                         finish.call({});
317                 }
318             };
319
320             INIT[name].require = function (name) { init[name](); };
321         }
322     },
323
324     scanModules: function scanModules() {
325         let self = this;
326         let { Module, modules } = this.modules;
327
328         defineModule.modules.forEach(function defModule(mod) {
329             let names = Set(Object.keys(mod.INIT));
330             if ("init" in mod.INIT)
331                 Set.add(names, "init");
332
333             keys(names).forEach(function (name) { self.deferInit(name, mod.INIT, mod); });
334         });
335
336         Module.list.forEach(function frobModule(mod) {
337             if (!mod.frobbed) {
338                 modules.__defineGetter__(mod.className, function () {
339                     delete modules[mod.className];
340                     return self.loadModule(mod.className, null, Components.stack.caller);
341                 });
342                 Object.keys(mod.prototype.INIT)
343                       .forEach(function (name) { self.deferInit(name, mod.prototype.INIT, mod); });
344             }
345             mod.frobbed = true;
346         });
347     },
348
349     initDependencies: function initDependencies(name, parents) {
350         for (let [k, v] in Iterator(this.deferredInit[name] || {}))
351             if (!parents || ~parents.indexOf(k))
352                 util.trapErrors(v);
353     }
354 }));
355
356 endModule();
357
358 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
359
360 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: