extensionSystem: Move extension loading into ExtensionManager

Now that extension loading and the extensions map are no longer shared
between the gnome-shell and gnome-shell-extension-prefs processes, we
can move both into the ExtensionManager which makes much more sense
conceptually.

https://bugzilla.gnome.org/show_bug.cgi?id=789852
This commit is contained in:
Florian Müllner 2019-07-07 23:38:27 +02:00 committed by Florian Müllner
parent 9928125e7d
commit 1d6ddf060b
6 changed files with 111 additions and 117 deletions

View File

@ -7,10 +7,8 @@ const { Gio, GLib } = imports.gi;
const Gettext = imports.gettext; const Gettext = imports.gettext;
const Lang = imports.lang; const Lang = imports.lang;
const Signals = imports.signals;
const Config = imports.misc.config; const Config = imports.misc.config;
const FileUtils = imports.misc.fileUtils;
var ExtensionType = { var ExtensionType = {
SYSTEM: 1, SYSTEM: 1,
@ -32,9 +30,6 @@ var ExtensionState = {
const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs', 'canChange']; const SERIALIZED_PROPERTIES = ['type', 'state', 'path', 'error', 'hasPrefs', 'canChange'];
// Maps uuid -> metadata object
var extensions = {};
/** /**
* getCurrentExtension: * getCurrentExtension:
* *
@ -65,13 +60,17 @@ function getCurrentExtension() {
if (!match) if (!match)
return null; return null;
// local import, as the module is used from outside the gnome-shell process
// as well (not this function though)
let extensionManager = imports.ui.main.extensionManager;
let path = match[1]; let path = match[1];
let file = Gio.File.new_for_path(path); let file = Gio.File.new_for_path(path);
// Walk up the directory tree, looking for an extension with // Walk up the directory tree, looking for an extension with
// the same UUID as a directory name. // the same UUID as a directory name.
while (file != null) { while (file != null) {
let extension = extensions[file.get_basename()]; let extension = extensionManager.extensions[file.get_basename()];
if (extension !== undefined) if (extension !== undefined)
return extension; return extension;
file = file.get_parent(); file = file.get_parent();
@ -177,55 +176,6 @@ function isOutOfDate(extension) {
return false; return false;
} }
function createExtensionObject(uuid, dir, type) {
let metadataFile = dir.get_child('metadata.json');
if (!metadataFile.query_exists(null)) {
throw new Error('Missing metadata.json');
}
let metadataContents, success, tag;
try {
[success, metadataContents, tag] = metadataFile.load_contents(null);
if (metadataContents instanceof Uint8Array)
metadataContents = imports.byteArray.toString(metadataContents);
} catch (e) {
throw new Error(`Failed to load metadata.json: ${e}`);
}
let meta;
try {
meta = JSON.parse(metadataContents);
} catch (e) {
throw new Error(`Failed to parse metadata.json: ${e}`);
}
let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
for (let i = 0; i < requiredProperties.length; i++) {
let prop = requiredProperties[i];
if (!meta[prop]) {
throw new Error(`missing "${prop}" property in metadata.json`);
}
}
if (uuid != meta.uuid) {
throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
}
let extension = {};
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);
extension.canChange = false;
extensions[uuid] = extension;
return extension;
}
function serializeExtension(extension) { function serializeExtension(extension) {
let obj = {}; let obj = {};
Lang.copyProperties(extension.metadata, obj); Lang.copyProperties(extension.metadata, obj);
@ -280,36 +230,3 @@ function installImporter(extension) {
extension.imports = imports[extension.uuid]; extension.imports = imports[extension.uuid];
imports.searchPath = oldSearchPath; imports.searchPath = oldSearchPath;
} }
var ExtensionFinder = class {
_loadExtension(extensionDir, info, perUserDir) {
let fileType = info.get_file_type();
if (fileType != Gio.FileType.DIRECTORY)
return;
let uuid = info.get_name();
let existing = extensions[uuid];
if (existing) {
log('Extension %s already installed in %s. %s will not be loaded'.format(uuid, existing.path, extensionDir.get_path()));
return;
}
let extension;
let type = extensionDir.has_prefix(perUserDir) ? ExtensionType.PER_USER
: ExtensionType.SYSTEM;
try {
extension = createExtensionObject(uuid, extensionDir, type);
} catch (e) {
logError(e, 'Could not load extension %s'.format(uuid));
return;
}
this.emit('extension-found', extension);
}
scanExtensions() {
let perUserDir = Gio.File.new_for_path(global.userdatadir);
FileUtils.collectFromDatadirs('extensions', true, (dir, info) => {
this._loadExtension(dir, info, perUserDir);
});
}
};
Signals.addSignalMethods(ExtensionFinder.prototype);

View File

@ -44,7 +44,7 @@ function installExtension(uuid, invocation) {
} }
function uninstallExtension(uuid) { function uninstallExtension(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = Main.extensionManager.extensions[uuid];
if (!extension) if (!extension)
return false; return false;
@ -113,7 +113,7 @@ function updateExtension(uuid) {
_httpSession.queue_message(message, (session, message) => { _httpSession.queue_message(message, (session, message) => {
gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => { gotExtensionZipFile(session, message, uuid, newExtensionTmpDir, () => {
let oldExtension = ExtensionUtils.extensions[uuid]; let oldExtension = Main.extensionManager.extensions[uuid];
let extensionDir = oldExtension.dir; let extensionDir = oldExtension.dir;
if (!Main.extensionManager.unloadExtension(oldExtension)) if (!Main.extensionManager.unloadExtension(oldExtension))
@ -125,7 +125,7 @@ function updateExtension(uuid) {
let extension = null; let extension = null;
try { try {
extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER); extension = Main.extensionManager.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER);
Main.extensionManager.loadExtension(extension); Main.extensionManager.loadExtension(extension);
} catch (e) { } catch (e) {
if (extension) if (extension)
@ -151,8 +151,8 @@ function updateExtension(uuid) {
function checkForUpdates() { function checkForUpdates() {
let metadatas = {}; let metadatas = {};
for (let uuid in ExtensionUtils.extensions) { for (let uuid in Main.extensionManager.extensions) {
metadatas[uuid] = ExtensionUtils.extensions[uuid].metadata; metadatas[uuid] = Main.extensionManager.extensions[uuid].metadata;
} }
let params = { shell_version: Config.PACKAGE_VERSION, let params = { shell_version: Config.PACKAGE_VERSION,
@ -225,7 +225,7 @@ class InstallExtensionDialog extends ModalDialog.ModalDialog {
function callback() { function callback() {
try { try {
let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); let extension = Main.extensionManager.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
Main.extensionManager.loadExtension(extension); Main.extensionManager.loadExtension(extension);
if (!Main.extensionManager.enableExtension(uuid)) if (!Main.extensionManager.enableExtension(uuid))
throw new Error(`Cannot add ${uuid} to enabled extensions gsettings key`); throw new Error(`Cannot add ${uuid} to enabled extensions gsettings key`);

View File

@ -4,9 +4,10 @@ const { Gio, St } = imports.gi;
const Signals = imports.signals; const Signals = imports.signals;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main; const Main = imports.ui.main;
const { ExtensionState } = ExtensionUtils; const { ExtensionState, ExtensionType } = ExtensionUtils;
const ENABLED_EXTENSIONS_KEY = 'enabled-extensions'; const ENABLED_EXTENSIONS_KEY = 'enabled-extensions';
const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions'; const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions';
@ -17,15 +18,23 @@ var ExtensionManager = class {
this._initted = false; this._initted = false;
this._enabled = false; this._enabled = false;
this._extensions = {};
this._enabledExtensions = []; this._enabledExtensions = [];
this._extensionOrder = []; this._extensionOrder = [];
Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
}
init() {
this._sessionUpdated(); this._sessionUpdated();
} }
get extensions() {
return this._extensions;
}
_callExtensionDisable(uuid) { _callExtensionDisable(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = this._extensions[uuid];
if (!extension) if (!extension)
return; return;
@ -47,7 +56,7 @@ var ExtensionManager = class {
for (let i = 0; i < orderReversed.length; i++) { for (let i = 0; i < orderReversed.length; i++) {
let uuid = orderReversed[i]; let uuid = orderReversed[i];
try { try {
ExtensionUtils.extensions[uuid].stateObj.disable(); this._extensions[uuid].stateObj.disable();
} catch (e) { } catch (e) {
this.logExtensionError(uuid, e); this.logExtensionError(uuid, e);
} }
@ -68,7 +77,7 @@ var ExtensionManager = class {
for (let i = 0; i < order.length; i++) { for (let i = 0; i < order.length; i++) {
let uuid = order[i]; let uuid = order[i];
try { try {
ExtensionUtils.extensions[uuid].stateObj.enable(); this._extensions[uuid].stateObj.enable();
} catch (e) { } catch (e) {
this.logExtensionError(uuid, e); this.logExtensionError(uuid, e);
} }
@ -83,7 +92,7 @@ var ExtensionManager = class {
} }
_callExtensionEnable(uuid) { _callExtensionEnable(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = this._extensions[uuid];
if (!extension) if (!extension)
return; return;
@ -127,7 +136,7 @@ var ExtensionManager = class {
} }
enableExtension(uuid) { enableExtension(uuid) {
if (!ExtensionUtils.extensions[uuid]) if (!this._extensions[uuid])
return false; return false;
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
@ -140,7 +149,7 @@ var ExtensionManager = class {
} }
disableExtension(uuid) { disableExtension(uuid) {
if (!ExtensionUtils.extensions[uuid]) if (!this._extensions[uuid])
return false; return false;
let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
@ -153,7 +162,7 @@ var ExtensionManager = class {
} }
logExtensionError(uuid, error) { logExtensionError(uuid, error) {
let extension = ExtensionUtils.extensions[uuid]; let extension = this._extensions[uuid];
if (!extension) if (!extension)
return; return;
@ -169,6 +178,54 @@ var ExtensionManager = class {
this.emit('extension-state-changed', extension); this.emit('extension-state-changed', extension);
} }
createExtensionObject(uuid, dir, type) {
let metadataFile = dir.get_child('metadata.json');
if (!metadataFile.query_exists(null)) {
throw new Error('Missing metadata.json');
}
let metadataContents, success;
try {
[success, metadataContents] = metadataFile.load_contents(null);
if (metadataContents instanceof Uint8Array)
metadataContents = imports.byteArray.toString(metadataContents);
} catch (e) {
throw new Error(`Failed to load metadata.json: ${e}`);
}
let meta;
try {
meta = JSON.parse(metadataContents);
} catch (e) {
throw new Error(`Failed to parse metadata.json: ${e}`);
}
let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
for (let i = 0; i < requiredProperties.length; i++) {
let prop = requiredProperties[i];
if (!meta[prop]) {
throw new Error(`missing "${prop}" property in metadata.json`);
}
}
if (uuid != meta.uuid) {
throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
}
let extension = {
metadata: meta,
uuid: meta.uuid,
type,
dir,
path: dir.get_path(),
error: '',
hasPrefs: dir.get_child('prefs.js').query_exists(null),
canChange: false
};
this._extensions[uuid] = extension;
return extension;
}
loadExtension(extension) { loadExtension(extension) {
// Default to error, we set success as the last step // Default to error, we set success as the last step
extension.state = ExtensionState.ERROR; extension.state = ExtensionState.ERROR;
@ -202,7 +259,7 @@ var ExtensionManager = class {
extension.state = ExtensionState.UNINSTALLED; extension.state = ExtensionState.UNINSTALLED;
this.emit('extension-state-changed', extension); this.emit('extension-state-changed', extension);
delete ExtensionUtils.extensions[extension.uuid]; delete this._extensions[extension.uuid];
return true; return true;
} }
@ -217,7 +274,7 @@ var ExtensionManager = class {
// Now, recreate the extension and load it. // Now, recreate the extension and load it.
let newExtension; let newExtension;
try { try {
newExtension = ExtensionUtils.createExtensionObject(uuid, dir, type); newExtension = this.createExtensionObject(uuid, dir, type);
} catch (e) { } catch (e) {
this.logExtensionError(uuid, e); this.logExtensionError(uuid, e);
return; return;
@ -227,7 +284,7 @@ var ExtensionManager = class {
} }
_callExtensionInit(uuid) { _callExtensionInit(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = this._extensions[uuid];
let dir = extension.dir; let dir = extension.dir;
if (!extension) if (!extension)
@ -328,7 +385,7 @@ var ExtensionManager = class {
} }
_onSettingsWritableChanged() { _onSettingsWritableChanged() {
for (let uuid in ExtensionUtils.extensions) { for (let uuid in this._extensions) {
let extension = ExtensionUtils.extensions[uuid]; let extension = ExtensionUtils.extensions[uuid];
this._updateCanChange(extension); this._updateCanChange(extension);
this.emit('extension-state-changed', extension); this.emit('extension-state-changed', extension);
@ -340,8 +397,8 @@ var ExtensionManager = class {
// extensions when allowed by the sessionMode, so // extensions when allowed by the sessionMode, so
// temporarily disable them all // temporarily disable them all
this._enabledExtensions = []; this._enabledExtensions = [];
for (let uuid in ExtensionUtils.extensions) for (let uuid in this._extensions)
this.reloadExtension(ExtensionUtils.extensions[uuid]); this.reloadExtension(this._extensions[uuid]);
this._enabledExtensions = this._getEnabledExtensions(); this._enabledExtensions = this._getEnabledExtensions();
if (Main.sessionMode.allowExtensions) { if (Main.sessionMode.allowExtensions) {
@ -363,11 +420,30 @@ var ExtensionManager = class {
this._enabledExtensions = this._getEnabledExtensions(); this._enabledExtensions = this._getEnabledExtensions();
let finder = new ExtensionUtils.ExtensionFinder(); let perUserDir = Gio.File.new_for_path(global.userdatadir);
finder.connect('extension-found', (finder, extension) => { FileUtils.collectFromDatadirs('extensions', true, (dir, info) => {
let fileType = info.get_file_type();
if (fileType != Gio.FileType.DIRECTORY)
return;
let uuid = info.get_name();
let existing = this._extensions[uuid];
if (existing) {
log(`Extension ${uuid} already installed in ${existing.path}. ${dir.get_path()} will not be loaded`);
return;
}
let extension;
let type = dir.has_prefix(perUserDir)
? ExtensionType.PER_USER
: ExtensionType.SYSTEM;
try {
extension = this.createExtensionObject(uuid, dir, type);
} catch (e) {
logError(e, `Could not load extension ${uuid}`);
return;
}
this.loadExtension(extension); this.loadExtension(extension);
}); });
finder.scanExtensions();
} }
_enableAllExtensions() { _enableAllExtensions() {

View File

@ -624,7 +624,7 @@ var Extensions = class Extensions {
this._extensionsList.add(this._noExtensions); this._extensionsList.add(this._noExtensions);
this.actor.add(this._extensionsList); this.actor.add(this._extensionsList);
for (let uuid in ExtensionUtils.extensions) for (let uuid in Main.extensionManager.extensions)
this._loadExtension(null, uuid); this._loadExtension(null, uuid);
Main.extensionManager.connect('extension-loaded', Main.extensionManager.connect('extension-loaded',
@ -632,7 +632,7 @@ var Extensions = class Extensions {
} }
_loadExtension(o, uuid) { _loadExtension(o, uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = Main.extensionManager.extensions[uuid];
// There can be cases where we create dummy extension metadata // There can be cases where we create dummy extension metadata
// that's not really a proper extension. Don't bother with these. // that's not really a proper extension. Don't bother with these.
if (!extension.metadata.name) if (!extension.metadata.name)

View File

@ -228,6 +228,7 @@ function _initializeUI() {
ExtensionDownloader.init(); ExtensionDownloader.init();
extensionManager = new ExtensionSystem.ExtensionManager(); extensionManager = new ExtensionSystem.ExtensionManager();
extensionManager.init();
if (sessionMode.isGreeter && screenShield) { if (sessionMode.isGreeter && screenShield) {
layoutManager.connect('startup-prepared', () => { layoutManager.connect('startup-prepared', () => {

View File

@ -255,7 +255,7 @@ var GnomeShellExtensions = class {
ListExtensions() { ListExtensions() {
let out = {}; let out = {};
for (let uuid in ExtensionUtils.extensions) { for (let uuid in Main.extensionManager.extensions) {
let dbusObj = this.GetExtensionInfo(uuid); let dbusObj = this.GetExtensionInfo(uuid);
out[uuid] = dbusObj; out[uuid] = dbusObj;
} }
@ -263,12 +263,12 @@ var GnomeShellExtensions = class {
} }
GetExtensionInfo(uuid) { GetExtensionInfo(uuid) {
let extension = ExtensionUtils.extensions[uuid] || {}; let extension = Main.extensionManager.extensions[uuid] || {};
return ExtensionUtils.serializeExtension(extension); return ExtensionUtils.serializeExtension(extension);
} }
GetExtensionErrors(uuid) { GetExtensionErrors(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = Main.extensionManager.extensions[uuid];
if (!extension) if (!extension)
return []; return [];
@ -304,7 +304,7 @@ var GnomeShellExtensions = class {
} }
ReloadExtension(uuid) { ReloadExtension(uuid) {
let extension = ExtensionUtils.extensions[uuid]; let extension = Main.extensionManager.extensions[uuid];
if (!extension) if (!extension)
return; return;