diff --git a/js/extensionPrefs/main.js b/js/extensionPrefs/main.js index 82b67b4b1..bf5e41e91 100644 --- a/js/extensionPrefs/main.js +++ b/js/extensionPrefs/main.js @@ -43,7 +43,6 @@ const Application = new Lang.Class({ this.application.connect('command-line', Lang.bind(this, this._onCommandLine)); this.application.connect('startup', Lang.bind(this, this._onStartup)); - this._extensionMetas = {}; this._extensionPrefsModules = {}; this._extensionIters = {}; @@ -55,15 +54,15 @@ const Application = new Lang.Class({ }, _extensionAvailable: function(uuid) { - let meta = this._extensionMetas[uuid]; + let extension = ExtensionUtils.extensions[uuid]; - if (!meta) + if (!extension) return false; - if (ExtensionUtils.isOutOfDate(meta)) + if (ExtensionUtils.isOutOfDate(extension)) return false; - if (!meta.dir.get_child('prefs.js').query_exists(null)) + if (!extension.dir.get_child('prefs.js').query_exists(null)) return false; return true; @@ -75,16 +74,18 @@ const Application = new Lang.Class({ cell.set_sensitive(false); }, - _getExtensionPrefsModule: function(meta) { - if (this._extensionPrefsModules.hasOwnProperty(meta.uuid)) - return this._extensionPrefsModules[meta.uuid]; + _getExtensionPrefsModule: function(extension) { + let uuid = extension.metadata.uuid; - ExtensionUtils.installImporter(meta); + if (this._extensionPrefsModules.hasOwnProperty(uuid)) + return this._extensionPrefsModules[uuid]; - let prefsModule = meta.importer.prefs; - prefsModule.init(meta); + ExtensionUtils.installImporter(extension); - this._extensionPrefsModules[meta.uuid] = prefsModule; + let prefsModule = extension.imports.prefs; + prefsModule.init(extension.metadata); + + this._extensionPrefsModules[uuid] = prefsModule; return prefsModule; }, @@ -92,14 +93,14 @@ const Application = new Lang.Class({ if (!this._extensionAvailable(uuid)) return; - let meta = this._extensionMetas[uuid]; + let extension = ExtensionUtils.extensions[uuid]; let widget; try { - let prefsModule = this._getExtensionPrefsModule(meta); - widget = prefsModule.buildPrefsWidget(meta); + let prefsModule = this._getExtensionPrefsModule(extension); + widget = prefsModule.buildPrefsWidget(); } catch (e) { - widget = this._buildErrorUI(meta, e); + widget = this._buildErrorUI(extension, e); } // Destroy the current prefs widget, if it exists @@ -119,10 +120,10 @@ const Application = new Lang.Class({ this._selectExtension(uuid); }, - _buildErrorUI: function(meta, exc) { + _buildErrorUI: function(extension, exc) { let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let label = new Gtk.Label({ - label: _("There was an error loading the preferences dialog for %s:").format(meta.name) + label: _("There was an error loading the preferences dialog for %s:").format(extension.metadata.name) }); box.add(label); @@ -194,7 +195,7 @@ const Application = new Lang.Class({ this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell'); this._shellProxy.connectSignal('ExtensionStatusChanged', Lang.bind(this, function(proxy, senderName, [uuid, state, error]) { - if (!this._extensionMetas.hasOwnProperty(uuid)) + if (ExtensionUtils.extensions[uuid] !== undefined) this._scanExtensions(); })); @@ -203,21 +204,19 @@ const Application = new Lang.Class({ _scanExtensions: function() { ExtensionUtils.scanExtensions(Lang.bind(this, function(uuid, dir, type) { - if (this._extensionMetas.hasOwnProperty(uuid)) + if (ExtensionUtils.extensions[uuid] !== undefined) return; - let meta; + let extension; try { - meta = ExtensionUtils.loadMetadata(uuid, dir, type); + extension = ExtensionUtils.createExtensionObject(uuid, dir, type); } catch(e) { global.logError('' + e); return; } - this._extensionMetas[uuid] = meta; - let iter = this._model.append(); - this._model.set(iter, [0, 1], [uuid, meta.name]); + this._model.set(iter, [0, 1], [uuid, extension.metadata.name]); this._extensionIters[uuid] = iter; })); }, diff --git a/js/misc/extensionUtils.js b/js/misc/extensionUtils.js index 7c2120bb5..1697c4609 100644 --- a/js/misc/extensionUtils.js +++ b/js/misc/extensionUtils.js @@ -17,6 +17,38 @@ const ExtensionType = { // GFile for user extensions var userExtensionsDir = null; +// Maps uuid -> metadata object +const extensions = {}; + +function getCurrentExtension() { + let stack = (new Error()).stack; + + // Assuming we're importing this directly from an extension (and we shouldn't + // ever not be), its UUID should be directly in the path here. + let extensionStackLine = stack.split('\n')[1]; + if (!extensionStackLine) + throw new Error('Could not find current extension'); + + // The stack line is like: + // init([object Object])@/home/user/data/gnome-shell/extensions/u@u.id/prefs.js:8 + // + // In the case that we're importing from + // module scope, the first field is blank: + // @/home/user/data/gnome-shell/extensions/u@u.id/prefs.js:8 + let match = new RegExp('@(.+):\\d+').exec(extensionStackLine); + if (!match) + throw new Error('Could not find current extension'); + + let path = match[1]; + let uuid = GLib.path_get_basename(GLib.path_get_dirname(path)); + + let extension = extensions[uuid]; + if (extension === undefined) + throw new Error('Could not find current extension'); + + return extension; +} + /** * versionCheck: * @required: an array of versions we're compatible with @@ -47,17 +79,17 @@ function versionCheck(required, current) { return false; } -function isOutOfDate(meta) { - if (!versionCheck(meta['shell-version'], Config.PACKAGE_VERSION)) +function isOutOfDate(extension) { + if (!versionCheck(extension.metadata['shell-version'], Config.PACKAGE_VERSION)) return true; - if (meta['js-version'] && !versionCheck(meta['js-version'], Config.GJS_VERSION)) + if (extension.metadata['js-version'] && !versionCheck(extension.metadata['js-version'], Config.GJS_VERSION)) return true; return false; } -function loadMetadata(uuid, dir, type) { +function createExtensionObject(uuid, dir, type) { let info; let metadataFile = dir.get_child('metadata.json'); @@ -95,21 +127,27 @@ function loadMetadata(uuid, dir, type) { throw new Error('uuid "' + meta.uuid + '" from metadata.json does not match directory name "' + uuid + '"'); } - meta.type = type; - meta.dir = dir; - meta.path = dir.get_path(); - meta.error = ''; - meta.hasPrefs = dir.get_child('prefs.js').query_exists(null); + let extension = {}; - return meta; + extension.metadata = meta; + extension.uuid = meta.uuid; + extension.type = type; + extension.dir = dir; + extension.path = dir.get_path(); + extension.error = ''; + extension.hasPrefs = dir.get_child('prefs.js').query_exists(null); + + extensions[uuid] = extension; + + return extension; } -var _meta = null; +var _extension = null; -function installImporter(meta) { - _meta = meta; - ShellJS.add_extension_importer('imports.misc.extensionUtils._meta', 'importer', meta.path); - _meta = null; +function installImporter(extension) { + _extension = extension; + ShellJS.add_extension_importer('imports.misc.extensionUtils._extension', 'imports', extension.path); + _extension = null; } function init() { diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js index d2b25a002..8e0294f7a 100644 --- a/js/ui/extensionSystem.js +++ b/js/ui/extensionSystem.js @@ -53,15 +53,10 @@ function _getCertFile() { _httpSession.ssl_ca_file = _getCertFile(); -// Maps uuid -> metadata object -const extensionMeta = {}; -// Maps uuid -> extension state object (returned from init()) -const extensionStateObjs = {}; -// Contains the order that extensions were enabled in. -const extensionOrder = []; - // Arrays of uuids var enabledExtensions; +// Contains the order that extensions were enabled in. +const extensionOrder = []; // We don't really have a class to add signals on. So, create // a simple dummy object, add the signal methods, and export those @@ -72,9 +67,6 @@ Signals.addSignalMethods(_signals); const connect = Lang.bind(_signals, _signals.connect); const disconnect = Lang.bind(_signals, _signals.disconnect); -// UUID => Array of error messages -var errors = {}; - const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; function installExtensionFromUUID(uuid, version_tag) { @@ -94,8 +86,8 @@ function installExtensionFromUUID(uuid, version_tag) { } function uninstallExtensionFromUUID(uuid) { - let meta = extensionMeta[uuid]; - if (!meta) + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) return false; // Try to disable it -- if it's ERROR'd, we can't guarantee that, @@ -104,17 +96,17 @@ function uninstallExtensionFromUUID(uuid) { disableExtension(uuid); // Don't try to uninstall system extensions - if (meta.type != ExtensionUtils.ExtensionType.PER_USER) + if (extension.type != ExtensionUtils.ExtensionType.PER_USER) return false; - meta.state = ExtensionState.UNINSTALLED; - _signals.emit('extension-state-changed', meta); + extension.state = ExtensionState.UNINSTALLED; + _signals.emit('extension-state-changed', extension); - delete extensionMeta[uuid]; + delete ExtensionUtils.extensions[uuid]; delete extensionStateObjs[uuid]; delete errors[uuid]; - FileUtils.recursivelyDeleteDir(Gio.file_new_for_path(meta.path)); + FileUtils.recursivelyDeleteDir(Gio.file_new_for_path(extension.path)); return true; } @@ -164,11 +156,11 @@ function gotExtensionZipFile(session, message, uuid) { } function disableExtension(uuid) { - let meta = extensionMeta[uuid]; - if (!meta) + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) return; - if (meta.state != ExtensionState.ENABLED) + if (extension.state != ExtensionState.ENABLED) return; let extensionState = extensionStateObjs[uuid]; @@ -212,41 +204,45 @@ function disableExtension(uuid) { extensionOrder.splice(orderIdx, 1); - meta.state = ExtensionState.DISABLED; - _signals.emit('extension-state-changed', meta); + extension.state = ExtensionState.DISABLED; + _signals.emit('extension-state-changed', extension); } function enableExtension(uuid) { - let meta = extensionMeta[uuid]; - if (!meta) + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) return; - if (meta.state == ExtensionState.INITIALIZED) { - loadExtension(meta.dir, meta.type, true); + if (extension.state == ExtensionState.INITIALIZED) { + loadExtension(extension.dir, extension.type, true); return; } - if (meta.state != ExtensionState.DISABLED) + if (extension.state != ExtensionState.DISABLED) return; - let extensionState = extensionStateObjs[uuid]; - extensionOrder.push(uuid); try { - extensionState.enable(); + extension.stateObj.enable(); } catch(e) { logExtensionError(uuid, e.toString()); return; } - meta.state = ExtensionState.ENABLED; - _signals.emit('extension-state-changed', meta); + extension.state = ExtensionState.ENABLED; + _signals.emit('extension-state-changed', extension); } function logExtensionError(uuid, message, state) { - if (!errors[uuid]) errors[uuid] = []; - errors[uuid].push(message); + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) + return; + + if (!extension.errors) + extension.errors = []; + + extension.errors.push(message); global.logError('Extension "%s" had error: %s'.format(uuid, message)); state = state || ExtensionState.ERROR; _signals.emit('extension-state-changed', { uuid: uuid, @@ -256,32 +252,30 @@ function logExtensionError(uuid, message, state) { function loadExtension(dir, type, enabled) { let uuid = dir.get_basename(); - let meta; + let extension; - if (extensionMeta[uuid] != undefined) { + if (ExtensionUtils.extensions[uuid] != undefined) { throw new Error('extension already loaded'); } try { - meta = ExtensionUtils.loadMetadata(uuid, dir, type); + extension = ExtensionUtils.createExtensionObject(uuid, dir, type); } catch(e) { logExtensionError(uuid, e.message); return; } - extensionMeta[uuid] = meta; - // Default to error, we set success as the last step - meta.state = ExtensionState.ERROR; + extension.state = ExtensionState.ERROR; - if (ExtensionUtils.isOutOfDate(meta)) { + if (ExtensionUtils.isOutOfDate(extension)) { logExtensionError(uuid, 'extension is not compatible with current GNOME Shell and/or GJS version', ExtensionState.OUT_OF_DATE); - meta.state = ExtensionState.OUT_OF_DATE; + extension.state = ExtensionState.OUT_OF_DATE; return; } if (!enabled) { - meta.state = ExtensionState.INITIALIZED; + extension.state = ExtensionState.INITIALIZED; return; } @@ -306,8 +300,8 @@ function loadExtension(dir, type, enabled) { let extensionModule; let extensionState = null; try { - ExtensionUtils.installImporter(meta); - extensionModule = meta.importer.extension; + ExtensionUtils.installImporter(extension); + extensionModule = extension.imports.extension; } catch (e) { if (stylesheetPath != null) theme.unload_stylesheet(stylesheetPath); @@ -321,7 +315,7 @@ function loadExtension(dir, type, enabled) { } try { - extensionState = extensionModule.init(meta); + extensionState = extensionModule.init(extension); } catch (e) { if (stylesheetPath != null) theme.unload_stylesheet(stylesheetPath); @@ -331,7 +325,7 @@ function loadExtension(dir, type, enabled) { if (!extensionState) extensionState = extensionModule; - extensionStateObjs[uuid] = extensionState; + extension.stateObj = extensionState; if (!extensionState.enable) { logExtensionError(uuid, 'missing \'enable\' function'); @@ -342,13 +336,13 @@ function loadExtension(dir, type, enabled) { return; } - meta.state = ExtensionState.DISABLED; + extension.state = ExtensionState.DISABLED; enableExtension(uuid); - _signals.emit('extension-loaded', meta.uuid); - _signals.emit('extension-state-changed', meta); - global.log('Loaded extension ' + meta.uuid); + _signals.emit('extension-loaded', uuid); + _signals.emit('extension-state-changed', extension); + global.log('Loaded extension ' + uuid); } function onEnabledExtensionsChanged() { @@ -430,13 +424,13 @@ const InstallExtensionDialog = new Lang.Class({ }, _onInstallButtonPressed: function(button, event) { - let meta = { uuid: this._uuid, - state: ExtensionState.DOWNLOADING, - error: '' }; + let extension = { uuid: this._uuid, + state: ExtensionState.DOWNLOADING, + error: '' }; - extensionMeta[this._uuid] = meta; + ExtensionUtils.extensions[this._uuid] = extension; - _signals.emit('extension-state-changed', meta); + _signals.emit('extension-state-changed', extension); let params = { version_tag: this._version_tag, shell_version: Config.PACKAGE_VERSION, diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index c7fb4c344..d88ead0dc 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -15,6 +15,7 @@ const Mainloop = imports.mainloop; const History = imports.misc.history; const ExtensionSystem = imports.ui.extensionSystem; +const ExtensionUtils = imports.misc.extensionUtils; const Link = imports.ui.link; const ShellEntry = imports.ui.shellEntry; const Tweener = imports.ui.tweener; @@ -728,7 +729,7 @@ const Extensions = new Lang.Class({ this._extensionsList.add(this._noExtensions); this.actor.add(this._extensionsList); - for (let uuid in ExtensionSystem.extensionMeta) + for (let uuid in ExtensionUtils.extensions) this._loadExtension(null, uuid); ExtensionSystem.connect('extension-loaded', @@ -736,10 +737,10 @@ const Extensions = new Lang.Class({ }, _loadExtension: function(o, uuid) { - let extension = ExtensionSystem.extensionMeta[uuid]; + let extension = ExtensionUtils.extensions[uuid]; // There can be cases where we create dummy extension metadata // that's not really a proper extension. Don't bother with these. - if (!extension.name) + if (!extension.metadata.name) return; let extensionDisplay = this._createExtensionDisplay(extension); @@ -751,25 +752,24 @@ const Extensions = new Lang.Class({ }, _onViewSource: function (actor) { - let meta = actor._extensionMeta; - let file = Gio.file_new_for_path(meta.path); - let uri = file.get_uri(); + let extension = actor._extension; + let uri = extension.dir.get_uri(); Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context()); Main.lookingGlass.close(); }, _onWebPage: function (actor) { - let meta = actor._extensionMeta; - Gio.app_info_launch_default_for_uri(meta.url, global.create_app_launch_context()); + let extension = actor._extension; + Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context()); Main.lookingGlass.close(); }, _onViewErrors: function (actor) { - let meta = actor._extensionMeta; + let extension = actor._extension; let shouldShow = !actor._isShowing; if (shouldShow) { - let errors = ExtensionSystem.errors[meta.uuid]; + let errors = extension.errors; let errorDisplay = new St.BoxLayout({ vertical: true }); if (errors && errors.length) { for (let i = 0; i < errors.length; i ++) @@ -809,36 +809,36 @@ const Extensions = new Lang.Class({ return 'Unknown'; // Not translated, shouldn't appear }, - _createExtensionDisplay: function(meta) { + _createExtensionDisplay: function(extension) { let box = new St.BoxLayout({ style_class: 'lg-extension', vertical: true }); let name = new St.Label({ style_class: 'lg-extension-name', - text: meta.name }); + text: extension.metadata.name }); box.add(name, { expand: true }); let description = new St.Label({ style_class: 'lg-extension-description', - text: meta.description || 'No description' }); + text: extension.metadata.description || 'No description' }); box.add(description, { expand: true }); let metaBox = new St.BoxLayout({ style_class: 'lg-extension-meta' }); box.add(metaBox); - let stateString = this._stateToString(meta.state); + let stateString = this._stateToString(extension.state); let state = new St.Label({ style_class: 'lg-extension-state', - text: this._stateToString(meta.state) }); + text: this._stateToString(extension.state) }); metaBox.add(state); let viewsource = new Link.Link({ label: _("View Source") }); - viewsource.actor._extensionMeta = meta; + viewsource.actor._extension = extension; viewsource.actor.connect('clicked', Lang.bind(this, this._onViewSource)); metaBox.add(viewsource.actor); - if (meta.url) { + if (extension.metadata.url) { let webpage = new Link.Link({ label: _("Web Page") }); - webpage.actor._extensionMeta = meta; + webpage.actor._extension = extension; webpage.actor.connect('clicked', Lang.bind(this, this._onWebPage)); metaBox.add(webpage.actor); } let viewerrors = new Link.Link({ label: _("Show Errors") }); - viewerrors.actor._extensionMeta = meta; + viewerrors.actor._extension = extension; viewerrors.actor._parentBox = box; viewerrors.actor._isShowing = false; viewerrors.actor.connect('clicked', Lang.bind(this, this._onViewErrors)); diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js index d3daf4b96..f74e8220b 100644 --- a/js/ui/shellDBus.js +++ b/js/ui/shellDBus.js @@ -7,6 +7,7 @@ const Shell = imports.gi.Shell; const Config = imports.misc.config; const ExtensionSystem = imports.ui.extensionSystem; +const ExtensionUtils = imports.misc.extensionUtils; const Flashspot = imports.ui.flashspot; const Main = imports.ui.main; @@ -182,7 +183,7 @@ const GnomeShell = new Lang.Class({ ListExtensions: function() { let out = {}; - for (let uuid in ExtensionSystem.extensionMeta) { + for (let uuid in ExtensionUtils.extensions) { let dbusObj = this.GetExtensionInfo(uuid); out[uuid] = dbusObj; } @@ -190,10 +191,23 @@ const GnomeShell = new Lang.Class({ }, GetExtensionInfo: function(uuid) { - let meta = ExtensionSystem.extensionMeta[uuid] || {}; + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) + return {}; + + let obj = {}; + Lang.copyProperties(extension.metadata, obj); + + // Only serialize the properties that we actually need. + const serializedProperties = ["type", "state", "path", "error", "hasPrefs"]; + + serializedProperties.forEach(function(prop) { + obj[prop] = extension[prop]; + }); + let out = {}; - for (let key in meta) { - let val = meta[key]; + for (let key in obj) { + let val = obj[key]; let type; switch (typeof val) { case 'string': @@ -210,11 +224,19 @@ const GnomeShell = new Lang.Class({ } out[key] = GLib.Variant.new(type, val); } + return out; }, GetExtensionErrors: function(uuid) { - return ExtensionSystem.errors[uuid] || []; + let extension = ExtensionUtils.extensions[uuid]; + if (!extension) + return []; + + if (!extension.errors) + return []; + + return extension.errors; }, EnableExtension: function(uuid) {