mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1225715: Part 5 - Add schema for extension manifests. r=billm
This currently forbids unknown top-level schema properties, and unknown permissions. In the future, I'd like to make those warnings rather than errors, for compatibility purposes, but I think errors are fine for now. --HG-- extra : commitid : 9jGEwCU9AhR extra : rebase_source : db16f1e5f9962fb7b24c0e52c05832ae646a57c2
This commit is contained in:
parent
7223a1a63e
commit
278a332b02
@ -3,6 +3,20 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"bookmarks"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "bookmarks",
|
||||
"description": "Use the <code>browser.bookmarks</code> API to create, organize, and otherwise manipulate bookmarks. Also see $(topic:override)[Override Pages], which you can use to create a custom Bookmark Manager page.",
|
||||
|
@ -3,6 +3,25 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "WebExtensionManifest",
|
||||
"properties": {
|
||||
"browser_action": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default_title": { "type": "string", "optional": true },
|
||||
"default_icon": { "$ref": "IconPath", "optional": true },
|
||||
"default_popup": { "type": "string", "format": "relativeUrl", "optional": true }
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "browserAction",
|
||||
"description": "Use browser actions to put icons in the main browser toolbar, to the right of the address bar. In addition to its icon, a browser action can also have a tooltip, a badge, and a popup.",
|
||||
|
@ -3,6 +3,20 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"contextMenus"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "contextMenus",
|
||||
"description": "Use the <code>browser.contextMenus</code> API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
|
||||
|
@ -3,6 +3,25 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "WebExtensionManifest",
|
||||
"properties": {
|
||||
"page_action": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default_title": { "type": "string", "optional": true },
|
||||
"default_icon": { "$ref": "IconPath", "optional": true },
|
||||
"default_popup": { "type": "string", "format": "relativeUrl", "optional": true }
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "pageAction",
|
||||
"description": "Use the <code>browser.pageAction</code> API to put icons inside the address bar. Page actions represent actions that can be taken on the current page, but that aren't applicable to all pages.",
|
||||
|
@ -3,6 +3,21 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"activeTab",
|
||||
"tabs"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "tabs",
|
||||
"description": "Use the <code>browser.tabs</code> API to interact with the browser's tab system. You can use this API to create, modify, and rearrange tabs in the browser.",
|
||||
|
@ -3,6 +3,20 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"windows"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "windows",
|
||||
"description": "Use the <code>browser.windows</code> API to interact with browser windows. You can use this API to create, modify, and rearrange windows in the browser.",
|
||||
|
@ -374,50 +374,49 @@ add_task(function* testSecureURLsDenied() {
|
||||
|
||||
yield extension.awaitFinish("setIcon security tests");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
|
||||
add_task(function* testSecureManifestURLsDenied() {
|
||||
// Test URLs included in the manifest.
|
||||
|
||||
let urls = ["chrome://browser/content/browser.xul",
|
||||
"javascript:true"];
|
||||
|
||||
let matchURLForbidden = url => ({
|
||||
message: new RegExp(`Loading extension.*Invalid icon data: NS_ERROR_DOM_BAD_URI`),
|
||||
});
|
||||
let apis = ["browser_action", "page_action"];
|
||||
|
||||
// Because the underlying method throws an error on invalid data,
|
||||
// only the first invalid URL of each component will be logged.
|
||||
let messages = [matchURLForbidden(urls[0]),
|
||||
matchURLForbidden(urls[1])];
|
||||
for (let url of urls) {
|
||||
for (let api of apis) {
|
||||
info(`TEST ${api} icon url: ${url}`);
|
||||
|
||||
let waitForConsole = new Promise(resolve => {
|
||||
// Not necessary in browser-chrome tests, but monitorConsole gripes
|
||||
// if we don't call it.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
let matchURLForbidden = url => ({
|
||||
message: new RegExp(`String "${url}" must be a relative URL`),
|
||||
});
|
||||
|
||||
SimpleTest.monitorConsole(resolve, messages);
|
||||
});
|
||||
let messages = [matchURLForbidden(url)];
|
||||
|
||||
extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
"19": urls[0],
|
||||
"38": urls[1],
|
||||
let waitForConsole = new Promise(resolve => {
|
||||
// Not necessary in browser-chrome tests, but monitorConsole gripes
|
||||
// if we don't call it.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SimpleTest.monitorConsole(resolve, messages);
|
||||
});
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
[api]: {
|
||||
"default_icon": url,
|
||||
},
|
||||
},
|
||||
},
|
||||
"page_action": {
|
||||
"default_icon": {
|
||||
"19": urls[1],
|
||||
"38": urls[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.unload();
|
||||
yield Assert.rejects(extension.startup(),
|
||||
null,
|
||||
"Manifest rejected");
|
||||
|
||||
SimpleTest.endMonitorConsole();
|
||||
yield waitForConsole;
|
||||
SimpleTest.endMonitorConsole();
|
||||
yield waitForConsole;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -148,47 +148,34 @@ add_task(function* testPageActionPopup() {
|
||||
add_task(function* testPageActionSecurity() {
|
||||
const URL = "chrome://browser/content/browser.xul";
|
||||
|
||||
let messages = [/Access to restricted URI denied/,
|
||||
/Access to restricted URI denied/];
|
||||
let apis = ["browser_action", "page_action"];
|
||||
|
||||
let waitForConsole = new Promise(resolve => {
|
||||
// Not necessary in browser-chrome tests, but monitorConsole gripes
|
||||
// if we don't call it.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
for (let api of apis) {
|
||||
info(`TEST ${api} icon url: ${URL}`);
|
||||
|
||||
SimpleTest.monitorConsole(resolve, messages);
|
||||
});
|
||||
let messages = [/Access to restricted URI denied/];
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"browser_action": { "default_popup": URL },
|
||||
"page_action": { "default_popup": URL },
|
||||
},
|
||||
let waitForConsole = new Promise(resolve => {
|
||||
// Not necessary in browser-chrome tests, but monitorConsole gripes
|
||||
// if we don't call it.
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
background: function() {
|
||||
browser.tabs.query({ active: true, currentWindow: true }, tabs => {
|
||||
let tabId = tabs[0].id;
|
||||
SimpleTest.monitorConsole(resolve, messages);
|
||||
});
|
||||
|
||||
browser.pageAction.show(tabId);
|
||||
browser.test.sendMessage("ready");
|
||||
});
|
||||
},
|
||||
});
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
[api]: { "default_popup": URL },
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
yield Assert.rejects(extension.startup(),
|
||||
null,
|
||||
"Manifest rejected");
|
||||
|
||||
yield clickBrowserAction(extension);
|
||||
yield clickPageAction(extension);
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
let pageActionId = makeWidgetId(extension.id) + "-page-action";
|
||||
let node = document.getElementById(pageActionId);
|
||||
is(node, null, "pageAction image removed from document");
|
||||
|
||||
SimpleTest.endMonitorConsole();
|
||||
yield waitForConsole;
|
||||
SimpleTest.endMonitorConsole();
|
||||
yield waitForConsole;
|
||||
}
|
||||
});
|
||||
|
||||
add_task(forceGC);
|
||||
|
@ -1889,6 +1889,10 @@ SpecialPowersAPI.prototype = {
|
||||
});
|
||||
let unloadPromise = new Promise(resolve => { resolveUnload = resolve; });
|
||||
|
||||
startupPromise.catch(() => {
|
||||
this._removeMessageListener("SPExtensionMessage", listener);
|
||||
});
|
||||
|
||||
handler = Cu.waiveXrays(handler);
|
||||
ext = Cu.waiveXrays(ext);
|
||||
|
||||
|
@ -70,6 +70,8 @@ ExtensionManagement.registerScript("chrome://extensions/content/ext-webRequest.j
|
||||
ExtensionManagement.registerScript("chrome://extensions/content/ext-storage.js");
|
||||
ExtensionManagement.registerScript("chrome://extensions/content/ext-test.js");
|
||||
|
||||
const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
|
||||
|
||||
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/cookies.json");
|
||||
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/extension.json");
|
||||
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/extension_types.json");
|
||||
@ -111,10 +113,15 @@ var Management = {
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let schema of ExtensionManagement.getSchemas()) {
|
||||
promises.push(Schemas.load(schema));
|
||||
}
|
||||
// Load order matters here. The base manifest defines types which are
|
||||
// extended by other schemas, so needs to be loaded first.
|
||||
let promise = Schemas.load(BASE_SCHEMA).then(() => {
|
||||
let promises = [];
|
||||
for (let schema of ExtensionManagement.getSchemas()) {
|
||||
promises.push(Schemas.load(schema));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
});
|
||||
|
||||
for (let script of ExtensionManagement.getScripts()) {
|
||||
let scope = {extensions: this,
|
||||
@ -127,7 +134,7 @@ var Management = {
|
||||
this.scopes.push(scope);
|
||||
}
|
||||
|
||||
this.initialized = Promise.all(promises);
|
||||
this.initialized = promise;
|
||||
return this.initialized;
|
||||
},
|
||||
|
||||
@ -551,20 +558,31 @@ ExtensionData.prototype = {
|
||||
// Reads the extension's |manifest.json| file, and stores its
|
||||
// parsed contents in |this.manifest|.
|
||||
readManifest() {
|
||||
return this.readJSON("manifest.json").then(manifest => {
|
||||
this.manifest = manifest;
|
||||
return Promise.all([
|
||||
this.readJSON("manifest.json"),
|
||||
Management.lazyInit(),
|
||||
]).then(([manifest]) => {
|
||||
let context = {
|
||||
url: (this.baseURI || this.rootURI).spec,
|
||||
|
||||
principal: this.principal,
|
||||
};
|
||||
|
||||
let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
|
||||
if (normalized.error) {
|
||||
this.manifestError(normalized.error);
|
||||
this.manifest = manifest;
|
||||
} else {
|
||||
this.manifest = normalized.value;
|
||||
}
|
||||
|
||||
try {
|
||||
this.id = this.manifest.applications.gecko.id;
|
||||
} catch (e) {
|
||||
// Errors are handled by the type check below.
|
||||
// Errors are handled by the type checks above.
|
||||
}
|
||||
|
||||
if (typeof this.id != "string") {
|
||||
this.manifestError("Missing required `applications.gecko.id` property");
|
||||
}
|
||||
|
||||
return manifest;
|
||||
return this.manifest;
|
||||
});
|
||||
},
|
||||
|
||||
@ -579,7 +597,7 @@ ExtensionData.prototype = {
|
||||
// If a "default_locale" is specified in that manifest, returns it
|
||||
// as a Gecko-compatible locale string. Otherwise, returns null.
|
||||
get defaultLocale() {
|
||||
if ("default_locale" in this.manifest) {
|
||||
if (this.manifest.default_locale != null) {
|
||||
return this.normalizeLocaleCode(this.manifest.default_locale);
|
||||
}
|
||||
|
||||
@ -975,7 +993,9 @@ Extension.prototype = extend(Object.create(ExtensionData.prototype), {
|
||||
this.webAccessibleResources = resources;
|
||||
|
||||
for (let directive in manifest) {
|
||||
Management.emit("manifest_" + directive, directive, this, manifest);
|
||||
if (manifest[directive] !== null) {
|
||||
Management.emit("manifest_" + directive, directive, this, manifest);
|
||||
}
|
||||
}
|
||||
|
||||
let data = Services.ppmm.initialProcessData;
|
||||
@ -1027,13 +1047,19 @@ Extension.prototype = extend(Object.create(ExtensionData.prototype), {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
let lazyInit = Management.lazyInit();
|
||||
return this.readManifest().then(() => {
|
||||
if (!this.hasShutdown) {
|
||||
return this.initLocale();
|
||||
}
|
||||
}).then(() => {
|
||||
if (this.errors.length) {
|
||||
// b2g add-ons generate manifest errors that we've silently
|
||||
// ignoring prior to adding this check.
|
||||
if (!this.rootURI.schemeIs("app")) {
|
||||
return Promise.reject({errors: this.errors});
|
||||
}
|
||||
}
|
||||
|
||||
return lazyInit.then(() => {
|
||||
return this.readManifest();
|
||||
}).then(() => {
|
||||
return this.initLocale();
|
||||
}).then(() => {
|
||||
if (this.hasShutdown) {
|
||||
return;
|
||||
}
|
||||
@ -1046,6 +1072,11 @@ Extension.prototype = extend(Object.create(ExtensionData.prototype), {
|
||||
}).catch(e => {
|
||||
dump(`Extension error: ${e} ${e.filename || e.fileName}:${e.lineNumber}\n`);
|
||||
Cu.reportError(e);
|
||||
|
||||
ExtensionManagement.shutdownExtension(this.uuid);
|
||||
|
||||
this.cleanupGeneratedFile();
|
||||
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
@ -1072,6 +1103,9 @@ Extension.prototype = extend(Object.create(ExtensionData.prototype), {
|
||||
shutdown() {
|
||||
this.hasShutdown = true;
|
||||
if (!this.manifest) {
|
||||
ExtensionManagement.shutdownExtension(this.uuid);
|
||||
|
||||
this.cleanupGeneratedFile();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1093,7 +1127,6 @@ Extension.prototype = extend(Object.create(ExtensionData.prototype), {
|
||||
|
||||
ExtensionManagement.shutdownExtension(this.uuid);
|
||||
|
||||
// Clean up a generated file.
|
||||
this.cleanupGeneratedFile();
|
||||
},
|
||||
|
||||
|
@ -9,6 +9,8 @@ const Cc = Components.classes;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
var {
|
||||
instanceOf,
|
||||
@ -147,14 +149,18 @@ const FORMATS = {
|
||||
url(string, context) {
|
||||
let url = new URL(string).href;
|
||||
|
||||
context.checkLoadURL(url);
|
||||
if (!context.checkLoadURL(url)) {
|
||||
throw new Error(`Access denied for URL ${url}`);
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
relativeUrl(string, context) {
|
||||
let url = new URL(string, context.url).href;
|
||||
|
||||
context.checkLoadURL(url);
|
||||
if (!context.checkLoadURL(url)) {
|
||||
throw new Error(`Access denied for URL ${url}`);
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
@ -945,7 +951,7 @@ this.Schemas = {
|
||||
}
|
||||
|
||||
let additionalProperties = null;
|
||||
if ("additionalProperties" in type) {
|
||||
if (type.additionalProperties) {
|
||||
additionalProperties = this.parseType(namespaceName, type.additionalProperties);
|
||||
}
|
||||
|
||||
@ -1106,6 +1112,18 @@ this.Schemas = {
|
||||
for (let [name, entry] of ns) {
|
||||
entry.inject(name, obj, new Context(wrapperFuncs));
|
||||
}
|
||||
|
||||
if (!Object.keys(obj).length) {
|
||||
delete dest[namespace];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
normalize(obj, typeName, context) {
|
||||
let [namespaceName, prop] = typeName.split(".");
|
||||
let ns = this.namespaces.get(namespaceName);
|
||||
let type = ns.get(prop);
|
||||
|
||||
return type.normalize(obj, new Context(context));
|
||||
},
|
||||
};
|
||||
|
@ -3,6 +3,20 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cookies"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "cookies",
|
||||
"description": "Use the <code>browser.cookies</code> API to query and modify cookies, and to be notified when they change.",
|
||||
|
@ -3,6 +3,20 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "WebExtensionManifest",
|
||||
"properties": {
|
||||
"default_locale": {
|
||||
"type": "string",
|
||||
"optional": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "i18n",
|
||||
"description": "Use the <code>browser.i18n</code> infrastructure to implement internationalization across your whole app or extension.",
|
||||
|
@ -9,6 +9,7 @@ toolkit.jar:
|
||||
content/extensions/schemas/extension_types.json
|
||||
content/extensions/schemas/i18n.json
|
||||
content/extensions/schemas/idle.json
|
||||
content/extensions/schemas/manifest.json
|
||||
content/extensions/schemas/runtime.json
|
||||
content/extensions/schemas/web_navigation.json
|
||||
content/extensions/schemas/web_request.json
|
||||
|
229
toolkit/components/extensions/schemas/manifest.json
Normal file
229
toolkit/components/extensions/schemas/manifest.json
Normal file
@ -0,0 +1,229 @@
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"id": "WebExtensionManifest",
|
||||
"type": "object",
|
||||
"description": "Represents a WebExtension manifest.json file",
|
||||
"properties": {
|
||||
"manifest_version": {
|
||||
"type": "integer",
|
||||
"minimum": 2,
|
||||
"maximum": 2
|
||||
},
|
||||
|
||||
"applications": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gecko": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "$ref": "ExtensionID" },
|
||||
|
||||
"update_url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"optional": true
|
||||
},
|
||||
|
||||
"strict_min_version": {
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
|
||||
"strict_max_version": {
|
||||
"type": "string",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"name": {
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "string",
|
||||
"optional": true
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"optional": false
|
||||
},
|
||||
|
||||
"icons": {
|
||||
"type": "object",
|
||||
"optional": true,
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": { "type": "string" }
|
||||
}
|
||||
},
|
||||
|
||||
"background": {
|
||||
"choices": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": { "$ref": "ExtensionURL" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scripts": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "ExtensionURL" }
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"optional": true
|
||||
},
|
||||
|
||||
"content_scripts": {
|
||||
"type": "array",
|
||||
"optional": true,
|
||||
"items": { "$ref": "ContentScript" }
|
||||
},
|
||||
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "Permission" },
|
||||
"optional": true
|
||||
},
|
||||
|
||||
"web_accessible_resources": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Permission",
|
||||
"choices": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"alarms",
|
||||
"idle",
|
||||
"notifications",
|
||||
"storage"
|
||||
]
|
||||
},
|
||||
{ "$ref": "MatchPattern" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ExtensionURL",
|
||||
"type": "string",
|
||||
"format": "strictRelativeUrl"
|
||||
},
|
||||
{
|
||||
"id": "ExtensionID",
|
||||
"choices": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "(?i)^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "(?i)^[a-z0-9-._]*@[a-z0-9-._]+$"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "MatchPattern",
|
||||
"choices": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["<all_urls>"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^(https?|file|ftp|app|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+)/.*$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^file:///.*$"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ContentScript",
|
||||
"type": "object",
|
||||
"description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time. Based on InjectDetails, but using underscore rather than camel case naming conventions.",
|
||||
"properties": {
|
||||
"matches": {
|
||||
"type": "array",
|
||||
"optional": false,
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "MatchPattern" }
|
||||
},
|
||||
"exclude_matches": {
|
||||
"type": "array",
|
||||
"optional": true,
|
||||
"minItems": 1,
|
||||
"items": { "$ref": "MatchPattern" }
|
||||
},
|
||||
"css": {
|
||||
"type": "array",
|
||||
"optional": true,
|
||||
"description": "The list of CSS files to inject",
|
||||
"items": { "$ref": "ExtensionURL" }
|
||||
},
|
||||
"js": {
|
||||
"type": "array",
|
||||
"optional": true,
|
||||
"description": "The list of CSS files to inject",
|
||||
"items": { "$ref": "ExtensionURL" }
|
||||
},
|
||||
"all_frames": {"type": "boolean", "optional": true, "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."},
|
||||
"match_about_blank": {"type": "boolean", "optional": true, "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is <code>false</code>."},
|
||||
"run_at": {
|
||||
"$ref": "extensionTypes.RunAt",
|
||||
"optional": true,
|
||||
"description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "IconPath",
|
||||
"choices": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": { "$ref": "ExtensionURL" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{ "$ref": "ExtensionURL" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "IconImageData",
|
||||
"choices": [
|
||||
{
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": {
|
||||
"type": "object",
|
||||
"isInstanceOf": "ImageData"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"isInstanceOf": "ImageData"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -3,6 +3,20 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"webNavigation"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "webNavigation",
|
||||
"description": "Use the <code>browser.webNavigation</code> API to receive notifications about the status of navigation requests in-flight.",
|
||||
|
@ -3,6 +3,21 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"webRequest",
|
||||
"webRequestBlocking"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "webRequest",
|
||||
"description": "Use the <code>browser.webRequest</code> API to observe and analyze traffic and to intercept, block, or modify requests in-flight.",
|
||||
|
@ -24,6 +24,7 @@ support-files =
|
||||
file_permission_xhr.html
|
||||
|
||||
[test_ext_simple.html]
|
||||
[test_ext_schema.html]
|
||||
[test_ext_geturl.html]
|
||||
[test_ext_contentscript.html]
|
||||
skip-if = buildapp == 'b2g' # runat != document_idle is not supported.
|
||||
|
@ -0,0 +1,36 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for schema API creation</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
add_task(function* testSchema() {
|
||||
function background() {
|
||||
browser.test.assertTrue(!("manifest" in browser), "browser.manifest is not defined");
|
||||
browser.test.notifyPass("schema");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${background})()`,
|
||||
});
|
||||
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
yield extension.awaitFinish("schema");
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -279,10 +279,7 @@ let wrapper = {
|
||||
url: "moz-extension://b66e3509-cdb3-44f6-8eb8-c8b39b3a1d27/",
|
||||
|
||||
checkLoadURL(url) {
|
||||
if (url.startsWith("chrome:")) {
|
||||
throw new Error("Access denied");
|
||||
}
|
||||
return url;
|
||||
return !url.startsWith("chrome:");
|
||||
},
|
||||
|
||||
callFunction(ns, name, args) {
|
||||
|
@ -135,7 +135,7 @@ add_task(function* checkUpdateMetadata() {
|
||||
addon: {
|
||||
manifest: {
|
||||
version: "1.0",
|
||||
application: { gecko: { strict_max_version: "45" } },
|
||||
applications: { gecko: { strict_max_version: "45" } },
|
||||
}
|
||||
},
|
||||
updates: {
|
||||
@ -240,7 +240,7 @@ add_task(function* checkIllegalUpdateURL() {
|
||||
});
|
||||
});
|
||||
|
||||
ok(messages.some(msg => /nsIScriptSecurityManager.checkLoadURIStrWithPrincipal/.test(msg)),
|
||||
ok(messages.some(msg => /Access denied for URL|may not load or link to|is not a valid URL/.test(msg)),
|
||||
"Got checkLoadURI error");
|
||||
}
|
||||
});
|
||||
|
@ -73,47 +73,6 @@ add_task(function*() {
|
||||
yield promiseRestartManager();
|
||||
});
|
||||
|
||||
// Test filtering invalid icon sizes
|
||||
add_task(function*() {
|
||||
writeWebManifestForExtension({
|
||||
name: "Web Extension Name",
|
||||
version: "1.0",
|
||||
manifest_version: 2,
|
||||
applications: {
|
||||
gecko: {
|
||||
id: ID
|
||||
}
|
||||
},
|
||||
icons: {
|
||||
32: "icon32.png",
|
||||
banana: "bananana.png",
|
||||
"20.5": "icon20.5.png",
|
||||
"20.0": "also invalid",
|
||||
"123banana": "123banana.png",
|
||||
64: "icon64.png"
|
||||
}
|
||||
}, profileDir);
|
||||
|
||||
yield promiseRestartManager();
|
||||
|
||||
let addon = yield promiseAddonByID(ID);
|
||||
do_check_neq(addon, null);
|
||||
|
||||
let uri = do_get_addon_root_uri(profileDir, ID);
|
||||
|
||||
deepEqual(addon.icons, {
|
||||
32: uri + "icon32.png",
|
||||
64: uri + "icon64.png"
|
||||
});
|
||||
|
||||
equal(addon.iconURL, uri + "icon64.png");
|
||||
equal(addon.icon64URL, uri + "icon64.png");
|
||||
|
||||
addon.uninstall();
|
||||
|
||||
yield promiseRestartManager();
|
||||
});
|
||||
|
||||
// Test AddonManager.getPreferredIconURL for retina screen sizes
|
||||
add_task(function*() {
|
||||
writeWebManifestForExtension({
|
||||
|
Loading…
Reference in New Issue
Block a user