]> git.gir.st - LegacyFox.git/blob - legacy/BootstrapLoader.jsm
fix setting softDisabled state
[LegacyFox.git] / legacy / BootstrapLoader.jsm
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 "use strict";
6
7 var EXPORTED_SYMBOLS = ["BootstrapLoader"];
8
9 const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
10 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
11
12 XPCOMUtils.defineLazyModuleGetters(this, {
13 AddonInternal: "resource://gre/modules/addons/XPIDatabase.jsm",
14 InstallRDF: "resource://legacy/RDFManifestConverter.jsm",
15 });
16 const Services = globalThis.Services || ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
17
18 (ChromeUtils.defineLazyGetter||XPCOMUtils.defineLazyGetter)(this, "BOOTSTRAP_REASONS", () => {
19 const {XPIProvider} = ChromeUtils.import("resource://gre/modules/addons/XPIProvider.jsm");
20 return XPIProvider.BOOTSTRAP_REASONS;
21 });
22
23 var logger = console.createInstance({ prefix: "addons.bootstrap" });
24
25 /**
26 * Valid IDs fit this pattern.
27 */
28 var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
29
30 // Properties that exist in the install manifest
31 const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL",
32 "optionsURL", "optionsType", "aboutURL", "iconURL"];
33 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
34 const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
35
36 // Map new string type identifiers to old style nsIUpdateItem types.
37 // Retired values:
38 // 32 = multipackage xpi file
39 // 8 = locale
40 // 256 = apiextension
41 // 128 = experiment
42 // theme = 4
43 const TYPES = {
44 extension: 2,
45 dictionary: 64,
46 };
47
48 const COMPATIBLE_BY_DEFAULT_TYPES = {
49 extension: true,
50 dictionary: true,
51 };
52
53 const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
54
55 function isXPI(filename) {
56 let ext = filename.slice(-4).toLowerCase();
57 return ext === ".xpi" || ext === ".zip";
58 }
59
60 /**
61 * Gets an nsIURI for a file within another file, either a directory or an XPI
62 * file. If aFile is a directory then this will return a file: URI, if it is an
63 * XPI file then it will return a jar: URI.
64 *
65 * @param {nsIFile} aFile
66 * The file containing the resources, must be either a directory or an
67 * XPI file
68 * @param {string} aPath
69 * The path to find the resource at, "/" separated. If aPath is empty
70 * then the uri to the root of the contained files will be returned
71 * @returns {nsIURI}
72 * An nsIURI pointing at the resource
73 */
74 function getURIForResourceInFile(aFile, aPath) {
75 if (!isXPI(aFile.leafName)) {
76 let resource = aFile.clone();
77 if (aPath)
78 aPath.split("/").forEach(part => resource.append(part));
79
80 return Services.io.newFileURI(resource);
81 }
82
83 return buildJarURI(aFile, aPath);
84 }
85
86 /**
87 * Creates a jar: URI for a file inside a ZIP file.
88 *
89 * @param {nsIFile} aJarfile
90 * The ZIP file as an nsIFile
91 * @param {string} aPath
92 * The path inside the ZIP file
93 * @returns {nsIURI}
94 * An nsIURI for the file
95 */
96 function buildJarURI(aJarfile, aPath) {
97 let uri = Services.io.newFileURI(aJarfile);
98 uri = "jar:" + uri.spec + "!/" + aPath;
99 return Services.io.newURI(uri);
100 }
101
102 var BootstrapLoader = {
103 name: "bootstrap",
104 manifestFile: "install.rdf",
105 async loadManifest(pkg) {
106 /**
107 * Reads locale properties from either the main install manifest root or
108 * an em:localized section in the install manifest.
109 *
110 * @param {Object} aSource
111 * The resource to read the properties from.
112 * @param {boolean} isDefault
113 * True if the locale is to be read from the main install manifest
114 * root
115 * @param {string[]} aSeenLocales
116 * An array of locale names already seen for this install manifest.
117 * Any locale names seen as a part of this function will be added to
118 * this array
119 * @returns {Object}
120 * an object containing the locale properties
121 */
122 function readLocale(aSource, isDefault, aSeenLocales) {
123 let locale = {};
124 if (!isDefault) {
125 locale.locales = [];
126 for (let localeName of aSource.locales || []) {
127 if (!localeName) {
128 logger.warn("Ignoring empty locale in localized properties");
129 continue;
130 }
131 if (aSeenLocales.includes(localeName)) {
132 logger.warn("Ignoring duplicate locale in localized properties");
133 continue;
134 }
135 aSeenLocales.push(localeName);
136 locale.locales.push(localeName);
137 }
138
139 if (locale.locales.length == 0) {
140 logger.warn("Ignoring localized properties with no listed locales");
141 return null;
142 }
143 }
144
145 for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {
146 if (hasOwnProperty(aSource, prop)) {
147 locale[prop] = aSource[prop];
148 }
149 }
150
151 return locale;
152 }
153
154 let manifestData = await pkg.readString("install.rdf");
155 let manifest = InstallRDF.loadFromString(manifestData).decode();
156
157 let addon = new AddonInternal();
158 for (let prop of PROP_METADATA) {
159 if (hasOwnProperty(manifest, prop)) {
160 addon[prop] = manifest[prop];
161 }
162 }
163
164 if (!addon.type) {
165 addon.type = "extension";
166 } else {
167 let type = addon.type;
168 addon.type = null;
169 for (let name in TYPES) {
170 if (TYPES[name] == type) {
171 addon.type = name;
172 break;
173 }
174 }
175 }
176
177 if (!(addon.type in TYPES))
178 throw new Error("Install manifest specifies unknown type: " + addon.type);
179
180 if (!addon.id)
181 throw new Error("No ID in install manifest");
182 if (!gIDTest.test(addon.id))
183 throw new Error("Illegal add-on ID " + addon.id);
184 if (!addon.version)
185 throw new Error("No version in install manifest");
186
187 addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
188 manifest.strictCompatibility == "true");
189
190 // Only read these properties for extensions.
191 if (addon.type == "extension") {
192 if (manifest.bootstrap != "true") {
193 throw new Error("Non-restartless extensions no longer supported");
194 }
195
196 if (addon.optionsType &&
197 addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&
198 addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {
199 throw new Error("Install manifest specifies unknown optionsType: " + addon.optionsType);
200 }
201 } else {
202 // Convert legacy dictionaries into a format the WebExtension
203 // dictionary loader can process.
204 if (addon.type === "dictionary") {
205 addon.loader = null;
206 let dictionaries = {};
207 await pkg.iterFiles(({path}) => {
208 let match = /^dictionaries\/([^\/]+)\.dic$/.exec(path);
209 if (match) {
210 let lang = match[1].replace(/_/g, "-");
211 dictionaries[lang] = match[0];
212 }
213 });
214 addon.startupData = {dictionaries};
215 }
216
217 // Only extensions are allowed to provide an optionsURL, optionsType,
218 // optionsBrowserStyle, or aboutURL. For all other types they are silently ignored
219 addon.aboutURL = null;
220 addon.optionsBrowserStyle = null;
221 addon.optionsType = null;
222 addon.optionsURL = null;
223 }
224
225 addon.defaultLocale = readLocale(manifest, true);
226
227 let seenLocales = [];
228 addon.locales = [];
229 for (let localeData of manifest.localized || []) {
230 let locale = readLocale(localeData, false, seenLocales);
231 if (locale)
232 addon.locales.push(locale);
233 }
234
235 let dependencies = new Set(manifest.dependencies);
236 addon.dependencies = Object.freeze(Array.from(dependencies));
237
238 let seenApplications = [];
239 addon.targetApplications = [];
240 for (let targetApp of manifest.targetApplications || []) {
241 if (!targetApp.id || !targetApp.minVersion ||
242 !targetApp.maxVersion) {
243 logger.warn("Ignoring invalid targetApplication entry in install manifest");
244 continue;
245 }
246 if (seenApplications.includes(targetApp.id)) {
247 logger.warn("Ignoring duplicate targetApplication entry for " + targetApp.id +
248 " in install manifest");
249 continue;
250 }
251 seenApplications.push(targetApp.id);
252 addon.targetApplications.push(targetApp);
253 }
254
255 // Note that we don't need to check for duplicate targetPlatform entries since
256 // the RDF service coalesces them for us.
257 addon.targetPlatforms = [];
258 for (let targetPlatform of manifest.targetPlatforms || []) {
259 let platform = {
260 os: null,
261 abi: null,
262 };
263
264 let pos = targetPlatform.indexOf("_");
265 if (pos != -1) {
266 platform.os = targetPlatform.substring(0, pos);
267 platform.abi = targetPlatform.substring(pos + 1);
268 } else {
269 platform.os = targetPlatform;
270 }
271
272 addon.targetPlatforms.push(platform);
273 }
274
275 addon.userDisabled = false;
276 addon.softDisabled = addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
277 addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
278
279 addon.userPermissions = null;
280
281 addon.icons = {};
282 if (await pkg.hasResource("icon.png")) {
283 addon.icons[32] = "icon.png";
284 addon.icons[48] = "icon.png";
285 }
286
287 if (await pkg.hasResource("icon64.png")) {
288 addon.icons[64] = "icon64.png";
289 }
290
291 return addon;
292 },
293
294 loadScope(addon) {
295 let file = addon.file || addon._sourceBundle;
296 let uri = getURIForResourceInFile(file, "bootstrap.js").spec;
297 let principal = Services.scriptSecurityManager.getSystemPrincipal();
298
299 let sandbox = new Cu.Sandbox(principal, {
300 sandboxName: uri,
301 addonId: addon.id,
302 wantGlobalProperties: ["ChromeUtils"],
303 metadata: { addonID: addon.id, URI: uri },
304 });
305
306 try {
307 Object.assign(sandbox, BOOTSTRAP_REASONS);
308
309 (ChromeUtils.defineLazyGetter||XPCOMUtils.defineLazyGetter)(sandbox, "console", () =>
310 console.createInstance({ consoleID: `addon/${addon.id}` }));
311
312 Services.scriptloader.loadSubScript(uri, sandbox);
313 } catch (e) {
314 logger.warn(`Error loading bootstrap.js for ${addon.id}`, e);
315 }
316
317 function findMethod(name) {
318 if (sandbox[name]) {
319 return sandbox[name];
320 }
321
322 try {
323 let method = Cu.evalInSandbox(name, sandbox);
324 return method;
325 } catch (err) { }
326
327 return () => {
328 logger.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`);
329 };
330 }
331
332 let install = findMethod("install");
333 let uninstall = findMethod("uninstall");
334 let startup = findMethod("startup");
335 let shutdown = findMethod("shutdown");
336
337 return {
338 install: (...args) => install(...args),
339
340 uninstall(...args) {
341 uninstall(...args);
342 // Forget any cached files we might've had from this extension.
343 Services.obs.notifyObservers(null, "startupcache-invalidate");
344 },
345
346 startup(...args) {
347 if (addon.type == "extension") {
348 logger.debug(`Registering manifest for ${file.path}\n`);
349 Components.manager.addBootstrappedManifestLocation(file);
350 }
351 return startup(...args);
352 },
353
354 shutdown(data, reason) {
355 try {
356 return shutdown(data, reason);
357 } catch (err) {
358 throw err;
359 } finally {
360 if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
361 logger.debug(`Removing manifest for ${file.path}\n`);
362 Components.manager.removeBootstrappedManifestLocation(file);
363 }
364 }
365 },
366 };
367 },
368 };
369
Imprint / Impressum