diff --git a/scripts/download-import-schema-from-gecko-dev b/scripts/download-import-schema-from-gecko-dev index 6bee0b19..60ef8e4a 100755 --- a/scripts/download-import-schema-from-gecko-dev +++ b/scripts/download-import-schema-from-gecko-dev @@ -19,7 +19,7 @@ const GIT_CONFIG = { 'browser/config/version_display.txt', 'toolkit/components/extensions/schemas', 'browser/components/extensions/schemas', - 'mobile/android/components/extensions/schemas', + 'mobile/shared/components/extensions/schemas', ], partialCloneOptions: '--depth 1 --filter=blob:none --no-checkout', tmpDir: tmp.dirSync({ diff --git a/src/parsers/manifestjson.js b/src/parsers/manifestjson.js index 1e9e0290..f6729646 100644 --- a/src/parsers/manifestjson.js +++ b/src/parsers/manifestjson.js @@ -13,7 +13,6 @@ import { validateAddon, validateDictionary, validateLangPack, - validateSitePermission, validateStaticTheme, } from 'schema/validator'; import { @@ -125,7 +124,6 @@ export default class ManifestJSONParser extends JSONParser { this.isStaticTheme = false; this.isLanguagePack = false; this.isDictionary = false; - this.isSitePermission = false; // Keep the addon type detection in sync with the most updated logic // used on the Firefox side, as defined in ExtensionData parseManifest @@ -136,8 +134,6 @@ export default class ManifestJSONParser extends JSONParser { this.isLanguagePack = true; } else if (hasManifestKey('dictionaries')) { this.isDictionary = true; - } else if (hasManifestKey('site_permissions')) { - this.isSitePermission = true; } this.io = io; @@ -461,8 +457,6 @@ export default class ManifestJSONParser extends JSONParser { validate = validateLangPack; } else if (this.isDictionary) { validate = validateDictionary; - } else if (this.isSitePermission) { - validate = validateSitePermission; } this.isValid = validate(this.parsedJSON, this.schemaValidatorOptions); diff --git a/src/schema/imported/chrome_settings_overrides.json b/src/schema/imported/chrome_settings_overrides.json index eb1cbe3c..66deea05 100644 --- a/src/schema/imported/chrome_settings_overrides.json +++ b/src/schema/imported/chrome_settings_overrides.json @@ -105,7 +105,8 @@ "type": "string", "format": "url", "pattern": "^(https://|http://(localhost|127\\.0\\.0\\.1|\\[::1\\])(:\\d*)?(/|$)).*$", - "preprocess": "localize" + "preprocess": "localize", + "deprecated": "Unsupported on Firefox at this time." }, "alternate_urls": { "type": "array", diff --git a/src/schema/imported/commands.json b/src/schema/imported/commands.json index bfcd9e4b..07b50178 100644 --- a/src/schema/imported/commands.json +++ b/src/schema/imported/commands.json @@ -17,7 +17,7 @@ { "allOf": [ { - "$ref": "tags#/types/Tab" + "$ref": "tabs#/types/Tab" }, { "name": "tab", diff --git a/src/schema/imported/content_scripts.json b/src/schema/imported/content_scripts.json index 4dd84f5e..a9a9874d 100644 --- a/src/schema/imported/content_scripts.json +++ b/src/schema/imported/content_scripts.json @@ -74,7 +74,11 @@ }, "matchAboutBlank": { "type": "boolean", - "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 false." + "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. Ignored if matchOriginAsFallback is specified. By default it is false." + }, + "matchOriginAsFallback": { + "type": "boolean", + "description": "If matchOriginAsFallback is true, then the code is also injected in about:, data:, blob: when their origin matches the pattern in 'matches', even if the actual document origin is opaque (due to the use of CSP sandbox or iframe sandbox). Match patterns in 'matches' must specify a wildcard path glob. By default it is false." }, "runAt": { "allOf": [ @@ -86,6 +90,16 @@ } ] }, + "world": { + "allOf": [ + { + "$ref": "extensionTypes#/types/ExecutionWorld" + }, + { + "description": "The JavaScript world for a script to execute within. Defaults to \"ISOLATED\"." + } + ] + }, "cookieStoreId": { "anyOf": [ { diff --git a/src/schema/imported/declarative_net_request.json b/src/schema/imported/declarative_net_request.json index 3157000f..6efdb4d6 100644 --- a/src/schema/imported/declarative_net_request.json +++ b/src/schema/imported/declarative_net_request.json @@ -98,7 +98,7 @@ { "name": "updateEnabledRulesets", "type": "function", - "description": "Returns the ids for the current set of enabled static rulesets.", + "description": "Modifies the static rulesets enabled/disabled state.", "async": "callback", "parameters": [ { @@ -128,6 +128,45 @@ } ] }, + { + "name": "updateStaticRules", + "type": "function", + "description": "Modified individual static rules enabled/disabled state. Changes to rules belonging to a disabled ruleset will take effect when the ruleset becomes enabled.", + "async": "callback", + "parameters": [ + { + "name": "options", + "type": "object", + "properties": { + "rulesetId": { + "type": "string" + }, + "disableRuleIds": { + "type": "array", + "items": { + "type": "integer" + }, + "default": [] + }, + "enableRuleIds": { + "type": "array", + "items": { + "type": "integer" + }, + "default": [] + } + }, + "required": [ + "rulesetId" + ] + }, + { + "name": "callback", + "type": "function", + "parameters": [] + } + ] + }, { "name": "getAvailableStaticRuleCount", "type": "function", @@ -146,6 +185,39 @@ } ] }, + { + "name": "getDisabledRuleIds", + "type": "function", + "description": "Returns the list of individual disabled static rules from a given static ruleset id.", + "async": "callback", + "parameters": [ + { + "name": "options", + "type": "object", + "properties": { + "rulesetId": { + "type": "string" + } + }, + "required": [ + "rulesetId" + ] + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "disabledRuleIds", + "type": "array", + "items": { + "type": "integer" + } + } + ] + } + ] + }, { "name": "getDynamicRules", "type": "function", @@ -371,13 +443,25 @@ "type": "number", "description": "The maximum number of static Rulesets an extension can specify as part of the rule_resources manifest key." }, + "MAX_NUMBER_OF_DISABLED_STATIC_RULES": { + "type": "number", + "description": "The maximum number of static rules that can be disabled on each static ruleset." + }, "MAX_NUMBER_OF_ENABLED_STATIC_RULESETS": { "type": "number", "description": "The maximum number of static Rulesets an extension can enable at any one time." }, "MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES": { "type": "number", - "description": "The maximum number of dynamic and session rules an extension can add. NOTE: in the Firefox we are enforcing this limit to the session and dynamic rules count separately, instead of enforcing it to the rules count for both combined as the Chrome implementation does." + "description": "Deprecated property returning the maximum number of dynamic and session rules an extension can add, replaced by MAX_NUMBER_OF_DYNAMIC_RULES/MAX_NUMBER_OF_SESSION_RULES." + }, + "MAX_NUMBER_OF_DYNAMIC_RULES": { + "type": "number", + "description": "The maximum number of dynamic session rules an extension can add." + }, + "MAX_NUMBER_OF_SESSION_RULES": { + "type": "number", + "description": "The maximum number of dynamic session rules an extension can add." }, "MAX_NUMBER_OF_REGEX_RULES": { "type": "number", diff --git a/src/schema/imported/extension_types.json b/src/schema/imported/extension_types.json index 96f24d61..749c46d6 100644 --- a/src/schema/imported/extension_types.json +++ b/src/schema/imported/extension_types.json @@ -75,6 +75,14 @@ ], "description": "The soonest that the JavaScript or CSS will be injected into the tab." }, + "ExecutionWorld": { + "type": "string", + "enum": [ + "ISOLATED", + "MAIN" + ], + "description": "The JavaScript world for a script to execute within. ISOLATED is the default execution environment of content scripts, MAIN is the web page's execution environment." + }, "CSSOrigin": { "type": "string", "enum": [ diff --git a/src/schema/imported/manifest.json b/src/schema/imported/manifest.json index 44714f25..e0e42d50 100644 --- a/src/schema/imported/manifest.json +++ b/src/schema/imported/manifest.json @@ -314,6 +314,21 @@ }, "default": [] }, + "optional_host_permissions": { + "min_manifest_version": 3, + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/types/MatchPattern" + }, + { + "onError": "warn" + } + ] + }, + "default": [] + }, "optional_permissions": { "type": "array", "items": { @@ -567,39 +582,6 @@ } } }, - "WebExtensionSitePermissionsManifest": { - "$merge": { - "source": { - "$ref": "manifest#/types/ManifestBase" - }, - "with": { - "type": "object", - "description": "Represents a WebExtension site permissions manifest.json file", - "properties": { - "site_permissions": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/types/SitePermission" - } - }, - "install_origins": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "items": { - "type": "string", - "format": "origin" - } - } - }, - "required": [ - "site_permissions", - "install_origins" - ] - } - } - }, "ThemeIcons": { "type": "object", "properties": { @@ -835,17 +817,6 @@ } ] }, - "SitePermission": { - "anyOf": [ - { - "type": "string", - "enum": [ - "midi", - "midi-sysex" - ] - } - ] - }, "HttpURL": { "type": "string", "format": "url", @@ -901,8 +872,7 @@ "pattern": "^[0-9]{1,3}(\\.[a-z0-9*]+)+$" }, "admin_install_only": { - "type": "boolean", - "optional": true + "type": "boolean" } } }, @@ -919,8 +889,7 @@ "description": "Maximum version of Gecko to support.", "pattern": "^[0-9]{1,3}(\\.[a-z0-9*]+)+$" } - }, - "additionalProperties": {} + } }, "DeprecatedApplications": { "type": "object", @@ -1041,7 +1010,11 @@ }, "match_about_blank": { "type": "boolean", - "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 false." + "description": "If match_about_blank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Ignored if match_origin_as_fallback is specified. By default it is false." + }, + "match_origin_as_fallback": { + "type": "boolean", + "description": "If match_origin_as_fallback is true, then the code is also injected in about:, data:, blob: when their origin matches the pattern in 'matches', even if the actual document origin is opaque (due to the use of CSP sandbox or iframe sandbox). Match patterns in 'matches' must specify a wildcard path glob. By default it is false." }, "run_at": { "allOf": [ @@ -1053,6 +1026,17 @@ "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"." } ] + }, + "world": { + "allOf": [ + { + "$ref": "extensionTypes#/types/ExecutionWorld" + }, + { + "default": "ISOLATED", + "description": "The JavaScript world for a script to execute within. Defaults to \"ISOLATED\"." + } + ] } }, "required": [ diff --git a/src/schema/imported/proxy.json b/src/schema/imported/proxy.json index 313e93e4..6c1394f3 100644 --- a/src/schema/imported/proxy.json +++ b/src/schema/imported/proxy.json @@ -236,7 +236,7 @@ }, "proxyDNS": { "type": "boolean", - "description": "Proxy DNS when using SOCKS v5." + "description": "Proxy DNS when using SOCKS. DNS queries get leaked to the network when set to false. True by default for SOCKS v5. False by default for SOCKS v4." }, "respectBeConservative": { "type": "boolean", diff --git a/src/schema/imported/scripting.json b/src/schema/imported/scripting.json index 9d479400..01e8f5d6 100644 --- a/src/schema/imported/scripting.json +++ b/src/schema/imported/scripting.json @@ -364,9 +364,10 @@ "ExecutionWorld": { "type": "string", "enum": [ - "ISOLATED" + "ISOLATED", + "MAIN" ], - "description": "The JavaScript world for a script to execute within. We currently only support the 'ISOLATED' world." + "description": "The JavaScript world for a script to execute within. ISOLATED is the default execution environment of content scripts, MAIN is the web page's execution environment." }, "RegisteredContentScript": { "type": "object", @@ -400,6 +401,10 @@ "type": "string" } }, + "matchOriginAsFallback": { + "type": "boolean", + "description": "If matchOriginAsFallback is true, then the code is also injected in about:, data:, blob: when their origin matches the pattern in 'matches', even if the actual document origin is opaque (due to the use of CSP sandbox or iframe sandbox). Match patterns in 'matches' must specify a wildcard path glob. By default it is false." + }, "runAt": { "allOf": [ { @@ -410,6 +415,16 @@ } ] }, + "world": { + "allOf": [ + { + "$ref": "#/types/ExecutionWorld" + }, + { + "description": "The JavaScript world for a script to execute within. Defaults to \"ISOLATED\"." + } + ] + }, "persistAcrossSessions": { "type": "boolean", "default": true, diff --git a/src/schema/imported/web_request.json b/src/schema/imported/web_request.json index b73f346a..32735ae6 100644 --- a/src/schema/imported/web_request.json +++ b/src/schema/imported/web_request.json @@ -769,7 +769,7 @@ { "type": "function", "optional": true, - "name": "callback", + "name": "asyncCallback", "parameters": [ { "allOf": [ @@ -800,6 +800,7 @@ "type": "array", "optional": true, "name": "extraInfoSpec", + "postprocess": "mutuallyExclusiveBlockingOrAsyncBlocking", "description": "Array of extra information that should be passed to the listener function.", "items": { "$ref": "#/types/OnAuthRequiredOptions" diff --git a/src/schema/validator.js b/src/schema/validator.js index 5027a4e3..7719a789 100644 --- a/src/schema/validator.js +++ b/src/schema/validator.js @@ -388,34 +388,6 @@ export class SchemaValidator { return this._localeValidator; } - get validateSitePermission() { - this._lazyInit(); - - if (!this._sitepermissionValidator) { - // Like with langpacks, we don't want additional properties in dictionaries, - // and there is no separate schema file. - // Uses ``deepPatch`` (instead of deepmerge) because we're patching a - // complicated schema instead of simply merging them together. - this._sitepermissionValidator = this._validator.compile({ - ...deepPatch(this.schemaObject, { - types: { - WebExtensionSitePermissionsManifest: { - $merge: { - with: { - additionalProperties: false, - }, - }, - }, - }, - }), - $id: 'sitepermission-manifest', - $ref: '#/types/WebExtensionSitePermissionsManifest', - }); - } - - return this._sitepermissionValidator; - } - _compileAddonValidator(validator) { const { minimum, maximum } = this.allowedManifestVersionsRange; const manifestVersion = this.addonManifestVersion; @@ -808,15 +780,6 @@ export const validateDictionary = (manifestData, validatorOptions = {}) => { return isValid; }; -export const validateSitePermission = (manifestData, validatorOptions = {}) => { - const validator = getValidator(validatorOptions); - let isValid = validator.validateSitePermission(manifestData); - const errors = filterErrors(validator.validateSitePermission.errors); - isValid = errors?.length > 0 ? isValid : true; - validateSitePermission.errors = errors; - return isValid; -}; - export const validateLocaleMessages = ( localeMessagesData, validatorOptions = {} diff --git a/tests/unit/helpers.js b/tests/unit/helpers.js index fc22b64a..6c6159aa 100644 --- a/tests/unit/helpers.js +++ b/tests/unit/helpers.js @@ -209,22 +209,6 @@ export function validLocaleMessagesJSON() { }); } -export function validSitePermissionManifestJSON(extra) { - return JSON.stringify({ - manifest_version: 2, - name: 'My SitePermission Addon', - version: '1.0a1', - site_permissions: ['midi'], - install_origins: ['http://mozilla.org'], - browser_specific_settings: { - gecko: { - id: '@my-exaple-sitepermission', - }, - }, - ...extra, - }); -} - function isMatch(target, expected) { return isMatchWith(target, expected, (tVal, eVal) => { if (eVal instanceof RegExp) { diff --git a/tests/unit/parsers/test.manifestjson.js b/tests/unit/parsers/test.manifestjson.js index cea32e1f..e92b742a 100644 --- a/tests/unit/parsers/test.manifestjson.js +++ b/tests/unit/parsers/test.manifestjson.js @@ -17,7 +17,6 @@ import { validManifestJSON, validDictionaryManifestJSON, validLangpackManifestJSON, - validSitePermissionManifestJSON, validStaticThemeManifestJSON, getStreamableIO, EMPTY_PNG, @@ -4231,52 +4230,6 @@ describe('ManifestJSONParser', () => { }); }); - describe('sitepermission', () => { - it('supports simple valid sitepermission', () => { - const linter = new Linter({ _: ['bar'] }); - const json = validSitePermissionManifestJSON(); - const manifestJSONParser = new ManifestJSONParser(json, linter.collector); - expect(manifestJSONParser.isValid).toEqual(true); - }); - - it('detects invalid sitepermission (more than one install_origin)', () => { - const linter = new Linter({ _: ['bar'] }); - const json = validSitePermissionManifestJSON({ - install_origins: ['https://website1.com', 'https://website2.com'], - }); - const manifestJSONParser = new ManifestJSONParser(json, linter.collector); - expect(linter.collector.errors).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: messages.JSON_INVALID.code, - message: '"/install_origins" must NOT have more than 1 items', - instancePath: '/install_origins', - }), - ]) - ); - expect(manifestJSONParser.isValid).toEqual(false); - }); - - it('detects invalid sitepermission (invalid site_permissions value)', () => { - const linter = new Linter({ _: ['bar'] }); - const json = validSitePermissionManifestJSON({ - site_permissions: ['midi', 'not_a_valid_webapi_name'], - }); - const manifestJSONParser = new ManifestJSONParser(json, linter.collector); - expect(linter.collector.errors).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: messages.JSON_INVALID.code, - message: - '"/site_permissions/1" is not a valid key or has invalid extra properties', - instancePath: '/site_permissions/1', - }), - ]) - ); - expect(manifestJSONParser.isValid).toEqual(false); - }); - }); - describe('homepage_url', () => { function testHomepageUrl(homepage_url, expectValid) { const addonLinter = new Linter({ _: ['bar'] });