diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 904491f5..5b337207 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -128,6 +128,8 @@ let jsmodules = { memoize(target || this, name, function (name) require(module)[name]); } }; +jsmodules.jsmodules = jsmodules; + let use = {}; let loaded = {}; let currentModule; diff --git a/common/modules/contexts.jsm b/common/modules/contexts.jsm index b109cc61..8a9ae42b 100644 --- a/common/modules/contexts.jsm +++ b/common/modules/contexts.jsm @@ -8,9 +8,12 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("contexts", { - exports: ["Contexts", "Group", "contexts"] + exports: ["Contexts", "Group", "contexts"], + require: ["services", "util"] }, this); +this.lazyRequire("overlay", ["overlay"]); + var Const = function Const(val) Class.Property({ enumerable: true, value: val }); var Group = Class("Group", { @@ -85,6 +88,17 @@ var Group = Class("Group", { }); var Contexts = Module("contexts", { + init: function () { + this.pluginModules = {}; + }, + + cleanup: function () { + for each (let module in this.pluginModules) + util.trapErrors("cleanup", module); + + this.pluginModules = {}; + }, + Local: function Local(dactyl, modules, window) ({ init: function () { const contexts = this; @@ -195,7 +209,7 @@ var Contexts = Module("contexts", { let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-") : file.leafName; - let id = name.replace(/\.[^.]*$/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase()); + let id = util.camelCase(name.replace(/\.[^.]*$/, "")); let contextPath = file.path; let self = Set.has(plugins, contextPath) && plugins.contexts[contextPath]; @@ -205,7 +219,7 @@ var Contexts = Module("contexts", { if (self) { if (Set.has(self, "onUnload")) - self.onUnload(); + util.trapErrors("onUnload", self); } else { self = args && !isArray(args) ? args : newContext.apply(null, args || [userContext]); @@ -216,10 +230,16 @@ var Contexts = Module("contexts", { CONTEXT: Const(self), + set isGlobalModule(val) { + // Hack. + if (val) + throw Contexts; + }, + unload: Const(function unload() { if (plugins[this.NAME] === this || plugins[this.PATH] === this) if (this.onUnload) - this.onUnload(); + util.trapErrors("onUnload", this); if (plugins[this.NAME] === this) delete plugins[this.NAME]; @@ -270,6 +290,74 @@ var Contexts = Module("contexts", { return this.Context(file, group, [this.modules.userContext, true]); }, + Module: function Module(uri, isPlugin) { + const { io, plugins } = this.modules; + + let canonical = uri.spec; + if (uri.scheme == "resource") + canonical = services["resource:"].resolveURI(uri); + + if (uri instanceof Ci.nsIFileURL) + var file = File(uri.file); + + let isPlugin = array.nth(io.getRuntimeDirectories("plugins"), + function (dir) dir.contains(file, true), + 0); + + let name = isPlugin && file && file.getRelativeDescriptor(isPlugin) + .replace(File.PATH_SEP, "-"); + let id = util.camelCase(name.replace(/\.[^.]*$/, "")); + + let self = Set.has(this.pluginModules, canonical) && this.pluginModules[canonical]; + + if (!self) { + self = Object.create(jsmodules); + + update(self, { + NAME: Const(id), + + PATH: Const(file && file.path), + + CONTEXT: Const(self), + + get isGlobalModule() true, + set isGlobalModule(val) { + util.assert(val, "Loading non-global module as global", + false); + }, + + unload: Const(function unload() { + if (contexts.pluginModules[canonical] == this) { + if (this.onUnload) + util.trapErrors("onUnload", this); + + delete contexts.pluginModules[canonical]; + } + + for each (let { plugins } in overlay.modules) + if (plugins[this.NAME] == this) + delete plugins[this.name]; + }) + }); + + JSMLoader.loadSubScript(uri.spec, self, File.defaultEncoding); + this.pluginModules[canonical] = self; + } + + // This belongs elsewhere + if (isPlugin) + Object.defineProperty(plugins, self.NAME, { + configurable: true, + enumerable: true, + get: function () self, + set: function (val) { + util.dactyl(val).reportError(FailedAssertion(_("plugin.notReplacingContext", self.NAME), 3, false), true); + } + }); + + return self; + }, + context: null, /** diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 0587ea40..85271b4d 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -16,6 +16,7 @@ defineModule("io", { }, this); this.lazyRequire("config", ["config"]); +this.lazyRequire("contexts", ["Contexts", "contexts"]); // TODO: why are we passing around strings rather than file objects? /** @@ -167,8 +168,14 @@ var IO = Module("io", { let uri = services.io.newFileURI(file); - // handle pure JavaScript files specially - if (/\.js$/.test(filename)) { + let sourceJSM = function sourceJSM() { + context = contexts.Module(uri); + dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime); + } + + if (/\.js,$/.test(filename)) + sourceJSM(); + else if (/\.js$/.test(filename)) { try { var context = contexts.Script(file, params.group); if (Set.has(this._scriptNames, file.path)) @@ -178,15 +185,21 @@ var IO = Module("io", { dactyl.triggerObserver("io.source", context, file, file.lastModifiedTime); } catch (e) { - if (e.fileName && !(e instanceof FailedAssertion)) - try { - e.fileName = util.fixURI(e.fileName); - if (e.fileName == uri.spec) - e.fileName = filename; - e.echoerr = <>{e.fileName}:{e.lineNumber}: {e}; - } - catch (e) {} - throw e; + if (e == Contexts) { // Hack; + context.unload(); + sourceJSM(); + } + else { + if (e.fileName && !(e instanceof FailedAssertion)) + try { + e.fileName = util.fixURI(e.fileName); + if (e.fileName == uri.spec) + e.fileName = filename; + e.echoerr = <>{e.fileName}:{e.lineNumber}: {e}; + } + catch (e) {} + throw e; + } } } else if (/\.css$/.test(filename))