]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/main.jsm
New upstream version 1.0+hg6948
[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(function (name) Object.getOwnPropertyDescriptor(this, name).value, this)),
153
154         get moduleList() this.ownPropertyValues.filter(function (mod) mod instanceof this.ModuleBase || mod.isLocalModule, this)
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), function _overlay(window) ({
165     ready: function onInit(document) {
166         const modules = Modules(window);
167         modules.moduleManager = this;
168         this.modules = modules;
169
170         window.dactyl = { modules: modules };
171
172         defineModule.time("load", null, function _load() {
173             config.modules.global
174                   .forEach(function (name) {
175                       if (!isArray(name))
176                           defineModule.time("load", name, require, null, name, modules.jsmodules);
177                       else
178                           lazyRequire(name[0], name.slice(1), modules.jsmodules);
179                   });
180
181             config.modules.window
182                   .forEach(function (name) defineModule.time("load", name, modules.load, modules, name));
183         }, this);
184     },
185
186     load: function onLoad(document) {
187         let self = this;
188
189         var { modules, Module } = this.modules;
190         delete window.dactyl;
191
192         this.startTime = Date.now();
193         this.deferredInit = { load: {} };
194         this.seen = {};
195         this.loaded = {};
196         modules.loaded = this.loaded;
197
198         this.modules = modules;
199
200         this.scanModules();
201         this.initDependencies("init");
202
203         modules.config.scripts.forEach(modules.load);
204
205         this.scanModules();
206
207         defineModule.modules.forEach(function defModule({ lazyInit, constructor: { className } }) {
208             if (!lazyInit) {
209                 Class.replaceProperty(modules, className, modules[className]);
210                 this.initDependencies(className);
211             }
212             else
213                 modules.__defineGetter__(className, function () {
214                     let module = modules.jsmodules[className];
215                     Class.replaceProperty(modules, className, module);
216                     if (module.reallyInit)
217                         module.reallyInit(); // :(
218
219                     if (!module.lazyDepends)
220                         self.initDependencies(className);
221                     return module;
222                 });
223         }, this);
224     },
225
226     cleanup: function cleanup(window) {
227         overlay.windows = overlay.windows.filter(function (w) w != window);
228     },
229
230     unload: function unload(window) {
231         for each (let mod in this.modules.moduleList.reverse()) {
232             mod.stale = true;
233
234             if ("destroy" in mod)
235                 util.trapErrors("destroy", mod);
236         }
237     },
238
239     visible: function visible(window) {
240         // Module.list.forEach(load);
241         this.initDependencies("load");
242         this.modules.times = update({}, defineModule.times);
243
244         defineModule.loadLog.push("Loaded in " + (Date.now() - this.startTime) + "ms");
245
246         overlay.windows = array.uniq(overlay.windows.concat(window), true);
247     },
248
249     loadModule: function loadModule(module, prereq, frame) {
250         let { loaded, seen } = this;
251         let { Module, modules } = this.modules;
252
253         if (isString(module)) {
254             if (!Module.constructors.hasOwnProperty(module))
255                 modules.load(module);
256             module = Module.constructors[module];
257         }
258
259         try {
260             if (Set.has(loaded, module.className))
261                 return;
262
263             if (Set.add(seen, module.className))
264                 throw Error("Module dependency loop.");
265
266             for (let dep in values(module.requires))
267                 this.loadModule(Module.constructors[dep], module.className);
268
269             defineModule.loadLog.push(
270                 "Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ")
271                     + module.className);
272
273             if (frame && frame.filename)
274                 defineModule.loadLog.push("  from: " + util.fixURI(frame.filename) + ":" + frame.lineNumber);
275
276             let obj = defineModule.time(module.className, "init", module);
277             Class.replaceProperty(modules, module.className, obj);
278
279             Set.add(loaded, module.className);
280
281             if (loaded.dactyl && obj.signals)
282                 modules.dactyl.registerObservers(obj);
283
284             if (!module.lazyDepends)
285                 this.initDependencies(module.className);
286         }
287         catch (e) {
288             util.dump("Loading " + (module && module.className) + ":");
289             util.reportError(e);
290         }
291         return modules[module.className];
292     },
293
294     deferInit: function deferInit(name, INIT, mod) {
295         let { modules } = this.modules;
296
297         let init = this.deferredInit[name] || {};
298         this.deferredInit[name] = init;
299
300         let className = mod.className || mod.constructor.className;
301
302         if (!Set.has(init, className)) {
303             init[className] = function callee() {
304                 function finish() {
305                     this.currentDependency = className;
306                     defineModule.time(className, name, INIT[name], mod,
307                                       modules.dactyl, modules, window);
308                 }
309                 if (!callee.frobbed) {
310                     callee.frobbed = true;
311                     if (modules[name] instanceof Class)
312                         modules[name].withSavedValues(["currentDependency"], finish);
313                     else
314                         finish.call({});
315                 }
316             };
317
318             INIT[name].require = function (name) { init[name](); };
319         }
320     },
321
322     scanModules: function scanModules() {
323         let { Module, modules } = this.modules;
324
325         defineModule.modules.forEach((mod) => {
326             let names = Set(Object.keys(mod.INIT));
327             if ("init" in mod.INIT)
328                 Set.add(names, "init");
329
330             keys(names).forEach((name) => { this.deferInit(name, mod.INIT, mod); });
331         });
332
333         Module.list.forEach((mod) => {
334             if (!mod.frobbed) {
335                 modules.__defineGetter__(mod.className, () => {
336                     delete modules[mod.className];
337                     return this.loadModule(mod.className, null, Components.stack.caller);
338                 });
339                 Object.keys(mod.prototype.INIT)
340                       .forEach((name) => { this.deferInit(name, mod.prototype.INIT, mod); });
341             }
342             mod.frobbed = true;
343         });
344     },
345
346     initDependencies: function initDependencies(name, parents) {
347         for (let [k, v] in Iterator(this.deferredInit[name] || {}))
348             if (!parents || ~parents.indexOf(k))
349                 util.trapErrors(v);
350     }
351 }));
352
353 endModule();
354
355 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
356
357 // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: