]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/overlay.jsm
ea626b4c5c08c85cc4a4090ffef0770664904820
[dactyl.git] / common / modules / overlay.jsm
1 // Copyright (c) 2009-2011 by 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 Components.utils.import("resource://dactyl/bootstrap.jsm");
10 defineModule("overlay", {
11     exports: ["ModuleBase"],
12     require: ["config", "services", "util"]
13 }, this);
14
15 /**
16  * @class ModuleBase
17  * The base class for all modules.
18  */
19 var ModuleBase = Class("ModuleBase", {
20     /**
21      * @property {[string]} A list of module prerequisites which
22      * must be initialized before this module is loaded.
23      */
24     requires: [],
25
26     toString: function () "[module " + this.constructor.className + "]"
27 });
28
29 var Overlay = Module("Overlay", {
30     init: function init() {
31         services["dactyl:"]; // Hack. Force module initialization.
32
33         config.loadStyles();
34
35         util.overlayWindow(config.overlayChrome, function overlay(window) ({
36             init: function onInit(document) {
37                 /**
38                  * @constructor Module
39                  *
40                  * Constructs a new ModuleBase class and makes arrangements for its
41                  * initialization. Arguments marked as optional must be either
42                  * entirely elided, or they must have the exact type specified.
43                  * Loading semantics are as follows:
44                  *
45                  *  - A module is guaranteed not to be initialized before any of its
46                  *    prerequisites as listed in its {@see ModuleBase#requires} member.
47                  *  - A module is considered initialized once it's been instantiated,
48                  *    its {@see Class#init} method has been called, and its
49                  *    instance has been installed into the top-level {@see modules}
50                  *    object.
51                  *  - Once the module has been initialized, its module-dependent
52                  *    initialization functions will be called as described hereafter.
53                  * @param {string} name The module's name as it will appear in the
54                  *     top-level {@see modules} object.
55                  * @param {ModuleBase} base The base class for this module.
56                  *     @optional
57                  * @param {Object} prototype The prototype for instances of this
58                  *     object. The object itself is copied and not used as a prototype
59                  *     directly.
60                  * @param {Object} classProperties The class properties for the new
61                  *     module constructor.
62                  *     @optional
63                  * @param {Object} moduleInit The module initialization functions
64                  *     for the new module. Each function is called as soon as the named module
65                  *     has been initialized, but after the module itself. The constructors are
66                  *     guaranteed to be called in the same order that the dependent modules
67                  *     were initialized.
68                  *     @optional
69                  *
70                  * @returns {function} The constructor for the resulting module.
71                  */
72                 function Module(name) {
73                     let args = Array.slice(arguments);
74
75                     var base = ModuleBase;
76                     if (callable(args[1]))
77                         base = args.splice(1, 1)[0];
78                     let [, prototype, classProperties, moduleInit] = args;
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 BASE = "resource://dactyl-content/";
93
94                 const create = window.Object.create || (function () {
95                     window.__dactyl_eval_string = "(function (proto) ({ __proto__: proto }))";
96                     JSMLoader.loadSubScript(BASE + "eval.js", window);
97
98                     let res = window.__dactyl_eval_result;
99                     delete window.__dactyl_eval_string;
100                     delete window.__dactyl_eval_result;
101                     return res;
102                 })();
103
104                 const jsmodules = { NAME: "jsmodules" };
105                 const modules = update(create(jsmodules), {
106                     yes_i_know_i_should_not_report_errors_in_these_branches_thanks: [],
107
108                     jsmodules: jsmodules,
109
110                     get content() this.config.browser.contentWindow || window.content,
111
112                     window: window,
113
114                     Module: Module,
115
116                     load: function load(script) {
117                         for (let [i, base] in Iterator(prefix)) {
118                             try {
119                                 JSMLoader.loadSubScript(base + script + ".js", modules, "UTF-8");
120                                 return;
121                             }
122                             catch (e) {
123                                 if (typeof e !== "string") {
124                                     util.dump("Trying: " + (base + script + ".js") + ":");
125                                     util.reportError(e);
126                                 }
127                             }
128                         }
129                         try {
130                             require(jsmodules, script);
131                         }
132                         catch (e) {
133                             util.dump("Loading script " + script + ":");
134                             util.reportError(e);
135                         }
136                     },
137
138                     newContext: function newContext(proto, normal) {
139                         if (normal)
140                             return create(proto);
141                         let sandbox = Components.utils.Sandbox(window, { sandboxPrototype: proto || modules, wantXrays: false });
142                         // Hack:
143                         sandbox.Object = jsmodules.Object;
144                         sandbox.Math = jsmodules.Math;
145                         sandbox.__proto__ = proto || modules;
146                         return sandbox;
147                     },
148
149                     get ownPropertyValues() array.compact(
150                             Object.getOwnPropertyNames(this)
151                                   .map(function (name) Object.getOwnPropertyDescriptor(this, name).value, this)),
152
153                     get moduleList() this.ownPropertyValues.filter(function (mod) mod instanceof this.ModuleBase || mod.isLocalModule, this)
154                 });
155                 modules.plugins = create(modules);
156                 modules.modules = modules;
157                 window.dactyl = { modules: modules };
158
159                 let prefix = [BASE, "resource://dactyl-local-content/"];
160
161                 defineModule.time("load", null, function _load() {
162                     ["addons",
163                      "base",
164                      "io",
165                      "commands",
166                      "completion",
167                      "config",
168                      "contexts",
169                      "downloads",
170                      "finder",
171                      "highlight",
172                      "javascript",
173                      "messages",
174                      "options",
175                      "overlay",
176                      "prefs",
177                      "sanitizer",
178                      "services",
179                      "storage",
180                      "styles",
181                      "template",
182                      "util"
183                     ].forEach(function (name) defineModule.time("load", name, require, null, jsmodules, name));
184
185                     ["dactyl",
186                      "modes",
187                      "commandline",
188                      "abbreviations",
189                      "autocommands",
190                      "buffer",
191                      "editor",
192                      "events",
193                      "hints",
194                      "mappings",
195                      "marks",
196                      "mow",
197                      "statusline"
198                      ].forEach(function (name) defineModule.time("load", name, modules.load, modules, name));
199                 }, this);
200             },
201             load: function onLoad(document) {
202                 // This is getting to be horrible. --Kris
203
204                 var { modules, Module } = window.dactyl.modules;
205                 delete window.dactyl;
206
207                 const start = Date.now();
208                 const deferredInit = { load: {} };
209                 const seen = set();
210                 const loaded = set();
211                 modules.loaded = loaded;
212
213                 function load(module, prereq, frame) {
214                     if (isString(module)) {
215                         if (!Module.constructors.hasOwnProperty(module))
216                             modules.load(module);
217                         module = Module.constructors[module];
218                     }
219
220                     try {
221                         if (module.className in loaded)
222                             return;
223                         if (module.className in seen)
224                             throw Error("Module dependency loop.");
225                         set.add(seen, module.className);
226
227                         for (let dep in values(module.requires))
228                             load(Module.constructors[dep], module.className);
229
230                         defineModule.loadLog.push("Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ") + module.className);
231                         if (frame && frame.filename)
232                             defineModule.loadLog.push(" from: " + util.fixURI(frame.filename) + ":" + frame.lineNumber);
233
234                         let obj = defineModule.time(module.className, "init", module);
235                         Class.replaceProperty(modules, module.className, obj);
236                         loaded[module.className] = true;
237
238                         if (loaded.dactyl && obj.signals)
239                             modules.dactyl.registerObservers(obj);
240
241                         frob(module.className);
242                     }
243                     catch (e) {
244                         util.dump("Loading " + (module && module.className) + ":");
245                         util.reportError(e);
246                     }
247                     return modules[module.className];
248                 }
249
250                 function deferInit(name, INIT, mod) {
251                     let init = deferredInit[name] = deferredInit[name] || {};
252                     let className = mod.className || mod.constructor.className;
253
254                     init[className] = function callee() {
255                         if (!callee.frobbed)
256                             defineModule.time(className, name, INIT[name], mod,
257                                               modules.dactyl, modules, window);
258                         callee.frobbed = true;
259                     };
260
261                     INIT[name].require = function (name) { init[name](); };
262                 }
263
264                 function frobModules() {
265                     Module.list.forEach(function frobModule(mod) {
266                         if (!mod.frobbed) {
267                             modules.__defineGetter__(mod.className, function () {
268                                 delete modules[mod.className];
269                                 return load(mod.className, null, Components.stack.caller);
270                             });
271                             Object.keys(mod.prototype.INIT)
272                                   .forEach(function (name) { deferInit(name, mod.prototype.INIT, mod); });
273                         }
274                         mod.frobbed = true;
275                     });
276                 }
277                 defineModule.modules.forEach(function defModule(mod) {
278                     let names = set(Object.keys(mod.INIT));
279                     if ("init" in mod.INIT)
280                         set.add(names, "init");
281
282                     keys(names).forEach(function (name) { deferInit(name, mod.INIT, mod); });
283                 });
284
285                 function frob(name) { values(deferredInit[name] || {}).forEach(call); }
286
287                 frobModules();
288                 frob("init");
289                 modules.config.scripts.forEach(modules.load);
290                 frobModules();
291
292                 defineModule.modules.forEach(function defModule({ lazyInit, constructor: { className } }) {
293                     if (!lazyInit) {
294                         frob(className);
295                         Class.replaceProperty(modules, className, modules[className]);
296                     }
297                     else
298                         modules.__defineGetter__(className, function () {
299                             delete modules[className];
300                             frob(className);
301                             return modules[className] = modules[className];
302                         });
303                 });
304
305                 // Module.list.forEach(load);
306                 frob("load");
307                 modules.times = update({}, defineModule.times);
308
309                 defineModule.loadLog.push("Loaded in " + (Date.now() - start) + "ms");
310
311                 modules.events.listen(window, "unload", function onUnload() {
312                     window.removeEventListener("unload", onUnload.wrapped, false);
313
314                     for each (let mod in modules.moduleList.reverse()) {
315                         mod.stale = true;
316
317                         if ("destroy" in mod)
318                             util.trapErrors("destroy", mod);
319                     }
320                 }, false);
321             }
322         }));
323     }
324 });
325
326 endModule();
327
328 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
329
330 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: