// Copyright (c) 2009-2015 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. "use strict"; try { defineModule("main", { exports: ["ModuleBase"], require: ["config", "overlay", "services", "util"] }); var BASE = "resource://dactyl-content/"; var global = this; /** * @class ModuleBase * The base class for all modules. */ var ModuleBase = Class("ModuleBase", { /** * @property {[string]} A list of module prerequisites which * must be initialized before this module is loaded. */ requires: [], toString: function () { return "[module " + this.constructor.className + "]"; } }); var _id = 0; var Modules = function Modules(window) { /** * @constructor Module * * Constructs a new ModuleBase class and makes arrangements for its * initialization. Arguments marked as optional must be either * entirely elided, or they must have the exact type specified. * Loading semantics are as follows: * * - A module is guaranteed not to be initialized before any of its * prerequisites as listed in its {@see ModuleBase#requires} member. * - A module is considered initialized once it's been instantiated, * its {@see Class#init} method has been called, and its * instance has been installed into the top-level {@see modules} * object. * - Once the module has been initialized, its module-dependent * initialization functions will be called as described hereafter. * @param {string} name The module's name as it will appear in the * top-level {@see modules} object. * @param {ModuleBase} base The base class for this module. * @optional * @param {Object} prototype The prototype for instances of this * object. The object itself is copied and not used as a prototype * directly. * @param {Object} classProperties The class properties for the new * module constructor. * @optional * @param {Object} moduleInit The module initialization functions * for the new module. Each function is called as soon as the * named module has been initialized. The constructors are * guaranteed to be called in the same order that the dependent * modules were initialized. * @optional * * @returns {function} The constructor for the resulting module. */ function Module(name, ...args) { var base = ModuleBase; if (callable(args[0])) base = args.shift(); let [prototype, classProperties, moduleInit] = args; prototype._metaInit_ = function () { delete module.prototype._metaInit_; Class.replaceProperty(modules, module.className, this); }; const module = Class(name, base, prototype, classProperties); module.INIT = moduleInit || {}; module.modules = modules; module.prototype.INIT = module.INIT; module.requires = prototype.requires || []; Module.list.push(module); Module.constructors[name] = module; return module; } Module.list = []; Module.constructors = {}; function newContext(proto, normal, name) { if (normal) return create(proto); let sandbox = Components.utils.Sandbox(Cu.getObjectPrincipal(global), { sandboxPrototype: proto || modules, sandboxName: name || ("Dactyl Sandbox " + ++_id), addonId: config.addon.id, wantXrays: true, metadata: { addonID: config.addon.id } }); loadPolyfill(sandbox); // Hack: // sandbox.Object = jsmodules.Object; sandbox.File = global.File; sandbox.Math = global.Math; sandbox.Set = global.Set; return sandbox; }; const BASES = [BASE, "resource://dactyl-local-content/"]; let proxyCache = {}; var proxy = new Proxy(window, { get: function window_get(target, prop) { // `in`, not `hasOwnProperty`, because we want to return // unbound methods in `Object.prototype` if (prop in proxyCache) return proxyCache[prop]; let p = target[prop]; if (callable(p)) return proxyCache[prop] = p.bind(target); return p; }, set: function window_set(target, prop, val, receiver) { if (receiver !== target) Object.defineProperty(receiver, prop, { value: val, enumerable: true, configurable: true, writable: true }); else target[prop] = val; return true; } }); var jsmodules = newContext(proxy, false, "Dactyl `jsmodules`"); jsmodules.NAME = "jsmodules"; const create = bind("create", jsmodules.Object); const modules = update(create(jsmodules), { Symbol: Symbol, yes_i_know_i_should_not_report_errors_in_these_branches_thanks: [], jsmodules: jsmodules, Module: Module, load: function load(script) { for (let base of BASES) { try { JSMLoader.loadSubScript(base + script + ".js", modules, "UTF-8"); return; } catch (e) { if (typeof e !== "string") { util.dump("Trying: " + (base + script + ".js") + ":"); util.reportError(e); } } } try { require(script, jsmodules); } catch (e) { util.dump("Loading script " + script + ":"); util.reportError(e); } }, newContext: newContext, get ownPropertyValues() { return Ary.compact( Object.getOwnPropertyNames(this) .map(name => Object.getOwnPropertyDescriptor(this, name).value)); }, get moduleList() { return this.ownPropertyValues.filter(mod => (mod instanceof this.ModuleBase || mod.isLocalModule)); } }); modules.plugins = create(modules); modules.modules = modules; return modules; }; config.loadStyles(); overlay.overlayWindow(Object.keys(config.overlays), function _overlay(window) { return { ready: function onInit(document) { const modules = Modules(window); modules.moduleManager = this; this.modules = modules; this.jsmodules = modules.jsmodules; window.dactyl = { modules: modules }; defineModule.time("load", null, function _load() { config.modules.global .forEach(function (name) { if (!isArray(name)) defineModule.time("load", name, require, null, name, modules.jsmodules); else lazyRequire(name[0], name.slice(1), modules.jsmodules); }); config.modules.window .forEach(name => { defineModule.time("load", name, modules.load, modules, name); }); }, this); }, load: function onLoad(document) { let self = this; var { modules } = this.modules; delete window.dactyl; this.startTime = Date.now(); this.deferredInit = { load: {} }; this.seen = new RealSet; this.loaded = new RealSet; modules.loaded = this.loaded; this.modules = modules; this.scanModules(); this.initDependencies("init"); modules.config.scripts.forEach(modules.load); this.scanModules(); defineModule.modules.forEach(function defModule({ lazyInit, constructor: { className } }) { if (!lazyInit) { Class.replaceProperty(modules, className, modules[className]); this.initDependencies(className); } else Object.defineProperty(modules, className, { get: () => { let module = modules.jsmodules[className]; Class.replaceProperty(modules, className, module); if (module.reallyInit) module.reallyInit(); // :( if (!module.lazyDepends) this.initDependencies(className); return module; }, enumerable: true, configurable: true, }); }, this); }, cleanup: function cleanup(window) { overlay.windows.delete(window); JSMLoader.atexit(() => { Cu.nukeSandbox(this.jsmodules); }); }, unload: function unload(window) { for (let mod of this.modules.moduleList.reverse()) { mod.stale = true; if ("destroy" in mod) util.trapErrors("destroy", mod); } }, visible: function visible(window) { // Module.list.forEach(load); this.initDependencies("load"); this.modules.times = update({}, defineModule.times); defineModule.loadLog.push("Loaded in " + (Date.now() - this.startTime) + "ms"); overlay.windows.add(window); }, loadModule: function loadModule(module, prereq, frame) { let { loaded, seen } = this; let { Module, modules } = this.modules; if (isString(module)) { if (!Module.constructors.hasOwnProperty(module)) modules.load(module); module = Module.constructors[module]; } try { if (loaded.has(module.className)) return; if (seen.add(module.className)) throw Error("Module dependency loop."); for (let dep of module.requires) this.loadModule(Module.constructors[dep], module.className); defineModule.loadLog.push( "Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ") + module.className); if (frame && frame.filename) defineModule.loadLog.push(" from: " + util.fixURI(frame.filename) + ":" + frame.lineNumber); let obj = defineModule.time(module.className, "init", module); Class.replaceProperty(modules, module.className, obj); loaded.add(module.className); if (loaded.has("dactyl") && obj.signals) modules.dactyl.registerObservers(obj); if (!module.lazyDepends) this.initDependencies(module.className); } catch (e) { util.dump("Loading " + (module && module.className) + ":"); util.reportError(e); } return modules[module.className]; }, deferInit: function deferInit(name, INIT, mod) { let { modules } = this.modules; let init = this.deferredInit[name] || {}; this.deferredInit[name] = init; let className = mod.className || mod.constructor.className; if (!hasOwnProp(init, className)) { init[className] = function callee() { function finish() { this.currentDependency = className; defineModule.time(className, name, INIT[name], mod, modules.dactyl, modules, window); } if (!callee.frobbed) { callee.frobbed = true; if (modules[name] instanceof Class) modules[name].withSavedValues(["currentDependency"], finish); else finish.call({}); } }; INIT[name].require = name => { init[name](); }; } }, scanModules: function scanModules() { let { Module, modules } = this.modules; defineModule.modules.forEach(mod => { let names = new RealSet(Object.keys(mod.INIT)); if ("init" in mod.INIT) names.add("init"); for (let name of names) this.deferInit(name, mod.INIT, mod); }); Module.list.forEach(mod => { if (!mod.frobbed) { Object.defineProperty(modules, mod.className, { get: () => { return this.loadModule(mod.className, null, Components.stack.caller); }, enumerable: true, configurable: true }); Object.keys(mod.prototype.INIT) .forEach(name => { this.deferInit(name, mod.prototype.INIT, mod); }); } mod.frobbed = true; }); }, initDependencies: function initDependencies(name, parents) { for (let [k, v] of iter(this.deferredInit[name] || {})) if (!parents || ~parents.indexOf(k)) util.trapErrors(v); } }; }); endModule(); } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: