зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to autoland
This commit is contained in:
Коммит
961aa85243
|
@ -655,7 +655,7 @@ skip-if = (toolkit == 'android') # Android: Bug 775227
|
|||
[test_innersize_scrollport.html]
|
||||
[test_integer_attr_with_leading_zero.html]
|
||||
[test_intersectionobservers.html]
|
||||
skip-if = (os == "android") # Timing issues
|
||||
skip-if = true # Track Bug 1320704
|
||||
[test_link_prefetch.html]
|
||||
skip-if = !e10s # Track Bug 1281415
|
||||
[test_link_stylesheet.html]
|
||||
|
|
|
@ -615,9 +615,9 @@ var interfaceNamesInGlobalScope =
|
|||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"InstallTrigger",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"IntersectionObserver",
|
||||
{name: "IntersectionObserver", disabled: true},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"IntersectionObserverEntry",
|
||||
{name: "IntersectionObserverEntry", disabled: true},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"KeyEvent",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
|
|
@ -5556,4 +5556,3 @@ pref("prompts.authentication_dialog_abuse_limit", 3);
|
|||
// To enable the DOM implementation, turn on "dom.storageManager.enabled"
|
||||
pref("browser.storageManager.enabled", false);
|
||||
|
||||
pref("dom.IntersectionObserver.enabled", true);
|
||||
|
|
|
@ -16,7 +16,13 @@ module.exports = { // eslint-disable-line no-undef
|
|||
"Extension": true,
|
||||
"ExtensionManagement": true,
|
||||
"extensions": true,
|
||||
"getContainerForCookieStoreId": true,
|
||||
"getCookieStoreIdForContainer": true,
|
||||
"global": true,
|
||||
"isContainerCookieStoreId": true,
|
||||
"isDefaultCookieStoreId": true,
|
||||
"isPrivateCookieStoreId": true,
|
||||
"isValidCookieStoreId": true,
|
||||
"NetUtil": true,
|
||||
"openOptionsPage": true,
|
||||
"require": false,
|
||||
|
|
|
@ -429,10 +429,21 @@ this.ExtensionData = class {
|
|||
// Errors are handled by the type checks above.
|
||||
}
|
||||
|
||||
let containersEnabled = true;
|
||||
try {
|
||||
containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
|
||||
} catch (e) {
|
||||
// If we fail here, we are in some xpcshell test.
|
||||
}
|
||||
|
||||
let permissions = this.manifest.permissions || [];
|
||||
|
||||
let whitelist = [];
|
||||
for (let perm of permissions) {
|
||||
if (perm == "contextualIdentities" && !containersEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.permissions.add(perm);
|
||||
|
||||
let match = /^(\w+)(?:\.(\w+)(?:\.\w+)*)?$/.exec(perm);
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
"use strict";
|
||||
|
||||
const {interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
|
||||
"resource://gre/modules/ContextualIdentityService.jsm");
|
||||
|
||||
function convert(identity) {
|
||||
let result = {
|
||||
name: ContextualIdentityService.getUserContextLabel(identity.userContextId),
|
||||
icon: identity.icon,
|
||||
color: identity.color,
|
||||
cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
extensions.registerSchemaAPI("contextualIdentities", "addon_parent", context => {
|
||||
let self = {
|
||||
contextualIdentities: {
|
||||
get(cookieStoreId) {
|
||||
let containerId = getContainerForCookieStoreId(cookieStoreId);
|
||||
if (!containerId) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let identity = ContextualIdentityService.getIdentityFromId(containerId);
|
||||
return Promise.resolve(convert(identity));
|
||||
},
|
||||
|
||||
query(details) {
|
||||
let identities = [];
|
||||
ContextualIdentityService.getIdentities().forEach(identity => {
|
||||
if (details.name &&
|
||||
ContextualIdentityService.getUserContextLabel(identity.userContextId) != details.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
identities.push(convert(identity));
|
||||
});
|
||||
|
||||
return Promise.resolve(identities);
|
||||
},
|
||||
|
||||
create(details) {
|
||||
let identity = ContextualIdentityService.create(details.name,
|
||||
details.icon,
|
||||
details.color);
|
||||
return Promise.resolve(convert(identity));
|
||||
},
|
||||
|
||||
update(cookieStoreId, details) {
|
||||
let containerId = getContainerForCookieStoreId(cookieStoreId);
|
||||
if (!containerId) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let identity = ContextualIdentityService.getIdentityFromId(containerId);
|
||||
if (!identity) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (details.name !== null) {
|
||||
identity.name = details.name;
|
||||
}
|
||||
|
||||
if (details.color !== null) {
|
||||
identity.color = details.color;
|
||||
}
|
||||
|
||||
if (details.icon !== null) {
|
||||
identity.icon = details.icon;
|
||||
}
|
||||
|
||||
if (!ContextualIdentityService.update(identity.userContextId,
|
||||
identity.name, identity.icon,
|
||||
identity.color)) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return Promise.resolve(convert(identity));
|
||||
},
|
||||
|
||||
remove(cookieStoreId) {
|
||||
let containerId = getContainerForCookieStoreId(cookieStoreId);
|
||||
if (!containerId) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let identity = ContextualIdentityService.getIdentityFromId(containerId);
|
||||
if (!identity) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// We have to create the identity object before removing it.
|
||||
let convertedIdentity = convert(identity);
|
||||
|
||||
if (!ContextualIdentityService.remove(identity.userContextId)) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return Promise.resolve(convertedIdentity);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return self;
|
||||
});
|
|
@ -22,7 +22,7 @@ global.getCookieStoreIdForTab = function(data, tab) {
|
|||
}
|
||||
|
||||
if (tab.userContextId) {
|
||||
return CONTAINER_STORE + tab.userContextId;
|
||||
return getCookieStoreIdForContainer(tab.userContextId);
|
||||
}
|
||||
|
||||
return DEFAULT_STORE;
|
||||
|
@ -40,8 +40,12 @@ global.isContainerCookieStoreId = function(storeId) {
|
|||
return storeId !== null && storeId.startsWith(CONTAINER_STORE);
|
||||
};
|
||||
|
||||
global.getCookieStoreIdForContainer = function(containerId) {
|
||||
return CONTAINER_STORE + containerId;
|
||||
};
|
||||
|
||||
global.getContainerForCookieStoreId = function(storeId) {
|
||||
if (!global.isContainerCookieStoreId(storeId)) {
|
||||
if (!isContainerCookieStoreId(storeId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -54,9 +58,9 @@ global.getContainerForCookieStoreId = function(storeId) {
|
|||
};
|
||||
|
||||
global.isValidCookieStoreId = function(storeId) {
|
||||
return global.isDefaultCookieStoreId(storeId) ||
|
||||
global.isPrivateCookieStoreId(storeId) ||
|
||||
global.isContainerCookieStoreId(storeId);
|
||||
return isDefaultCookieStoreId(storeId) ||
|
||||
isPrivateCookieStoreId(storeId) ||
|
||||
isContainerCookieStoreId(storeId);
|
||||
};
|
||||
|
||||
function convert({cookie, isPrivate}) {
|
||||
|
@ -76,7 +80,7 @@ function convert({cookie, isPrivate}) {
|
|||
}
|
||||
|
||||
if (cookie.originAttributes.userContextId) {
|
||||
result.storeId = CONTAINER_STORE + cookie.originAttributes.userContextId;
|
||||
result.storeId = getCookieStoreIdForContainer(cookie.originAttributes.userContextId);
|
||||
} else if (cookie.originAttributes.privateBrowsingId || isPrivate) {
|
||||
result.storeId = PRIVATE_STORE;
|
||||
} else {
|
||||
|
@ -197,17 +201,17 @@ function* query(detailsIn, props, context) {
|
|||
let userContextId = 0;
|
||||
let isPrivate = context.incognito;
|
||||
if (details.storeId) {
|
||||
if (!global.isValidCookieStoreId(details.storeId)) {
|
||||
if (!isValidCookieStoreId(details.storeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (global.isDefaultCookieStoreId(details.storeId)) {
|
||||
if (isDefaultCookieStoreId(details.storeId)) {
|
||||
isPrivate = false;
|
||||
} else if (global.isPrivateCookieStoreId(details.storeId)) {
|
||||
} else if (isPrivateCookieStoreId(details.storeId)) {
|
||||
isPrivate = true;
|
||||
} else if (global.isContainerCookieStoreId(details.storeId)) {
|
||||
} else if (isContainerCookieStoreId(details.storeId)) {
|
||||
isPrivate = false;
|
||||
userContextId = global.getContainerForCookieStoreId(details.storeId);
|
||||
userContextId = getContainerForCookieStoreId(details.storeId);
|
||||
if (!userContextId) {
|
||||
return;
|
||||
}
|
||||
|
@ -368,12 +372,12 @@ extensions.registerSchemaAPI("cookies", "addon_parent", context => {
|
|||
let expiry = isSession ? Number.MAX_SAFE_INTEGER : details.expirationDate;
|
||||
let isPrivate = context.incognito;
|
||||
let userContextId = 0;
|
||||
if (global.isDefaultCookieStoreId(details.storeId)) {
|
||||
if (isDefaultCookieStoreId(details.storeId)) {
|
||||
isPrivate = false;
|
||||
} else if (global.isPrivateCookieStoreId(details.storeId)) {
|
||||
} else if (isPrivateCookieStoreId(details.storeId)) {
|
||||
isPrivate = true;
|
||||
} else if (global.isContainerCookieStoreId(details.storeId)) {
|
||||
let containerId = global.getContainerForCookieStoreId(details.storeId);
|
||||
} else if (isContainerCookieStoreId(details.storeId)) {
|
||||
let containerId = getContainerForCookieStoreId(details.storeId);
|
||||
if (containerId === null) {
|
||||
return Promise.reject({message: `Illegal storeId: ${details.storeId}`});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# scripts
|
||||
category webextension-scripts alarms chrome://extensions/content/ext-alarms.js
|
||||
category webextension-scripts backgroundPage chrome://extensions/content/ext-backgroundPage.js
|
||||
category webextension-scripts contextualIdentities chrome://extensions/content/ext-contextualIdentities.js
|
||||
category webextension-scripts cookies chrome://extensions/content/ext-cookies.js
|
||||
category webextension-scripts downloads chrome://extensions/content/ext-downloads.js
|
||||
category webextension-scripts management chrome://extensions/content/ext-management.js
|
||||
|
@ -41,6 +42,7 @@ category webextension-scripts-addon storage chrome://extensions/content/ext-c-st
|
|||
|
||||
# schemas
|
||||
category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json
|
||||
category webextension-schemas contextualIdentities chrome://extensions/content/schemas/contextual_identities.json
|
||||
category webextension-schemas cookies chrome://extensions/content/schemas/cookies.json
|
||||
category webextension-schemas downloads chrome://extensions/content/schemas/downloads.json
|
||||
category webextension-schemas events chrome://extensions/content/schemas/events.json
|
||||
|
|
|
@ -7,6 +7,7 @@ toolkit.jar:
|
|||
content/extensions/ext-alarms.js
|
||||
content/extensions/ext-backgroundPage.js
|
||||
content/extensions/ext-browser-content.js
|
||||
content/extensions/ext-contextualIdentities.js
|
||||
content/extensions/ext-cookies.js
|
||||
content/extensions/ext-downloads.js
|
||||
content/extensions/ext-management.js
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
[
|
||||
{
|
||||
"namespace": "manifest",
|
||||
"types": [
|
||||
{
|
||||
"$extend": "Permission",
|
||||
"choices": [{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"contextualIdentities"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "contextualIdentities",
|
||||
"description": "Use the <code>browser.contextualIdentities</code> API to query and modify contextual identity, also called as containers.",
|
||||
"permissions": ["contextualIdentities"],
|
||||
"types": [
|
||||
{
|
||||
"id": "ContextualIdentity",
|
||||
"type": "object",
|
||||
"description": "Represents information about a contextual identity.",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "The name of the contextual identity."},
|
||||
"icon": {"type": "string", "description": "The icon of the contextual identity."},
|
||||
"color": {"type": "string", "description": "The color of the contextual identity."},
|
||||
"cookieStoreId": {"type": "string", "description": "The cookie store ID of the contextual identity."}
|
||||
}
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "get",
|
||||
"type": "function",
|
||||
"description": "Retrieves information about a single contextual identity.",
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "cookieStoreId",
|
||||
"description": "The ID of the contextual identity cookie store. "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"type": "function",
|
||||
"description": "Retrieves all contextual identities",
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "object",
|
||||
"name": "details",
|
||||
"description": "Information to filter the contextual identities being retrieved.",
|
||||
"properties": {
|
||||
"name": {"type": "string", "optional": true, "description": "Filters the contextual identity by name."}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "create",
|
||||
"type": "function",
|
||||
"description": "Creates a contextual identity with the given data.",
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "object",
|
||||
"name": "details",
|
||||
"description": "Details about the contextual identity being created.",
|
||||
"properties": {
|
||||
"name": {"type": "string", "optional": false, "description": "The name of the contextual identity." },
|
||||
"color": {"type": "string", "optional": false, "description": "The color of the contextual identity." },
|
||||
"icon": {"type": "string", "optional": false, "description": "The icon of the contextual identity." }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "update",
|
||||
"type": "function",
|
||||
"description": "Updates a contextual identity with the given data.",
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "cookieStoreId",
|
||||
"description": "The ID of the contextual identity cookie store. "
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"name": "details",
|
||||
"description": "Details about the contextual identity being created.",
|
||||
"properties": {
|
||||
"name": {"type": "string", "optional": true, "description": "The name of the contextual identity." },
|
||||
"color": {"type": "string", "optional": true, "description": "The color of the contextual identity." },
|
||||
"icon": {"type": "string", "optional": true, "description": "The icon of the contextual identity." }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"type": "function",
|
||||
"description": "Deletes a contetual identity by its cookie Store ID.",
|
||||
"async": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "cookieStoreId",
|
||||
"description": "The ID of the contextual identity cookie store. "
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -5,6 +5,7 @@
|
|||
toolkit.jar:
|
||||
% content extensions %content/extensions/
|
||||
content/extensions/schemas/alarms.json
|
||||
content/extensions/schemas/contextual_identities.json
|
||||
content/extensions/schemas/cookies.json
|
||||
content/extensions/schemas/downloads.json
|
||||
content/extensions/schemas/events.json
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
"use strict";
|
||||
|
||||
do_get_profile();
|
||||
|
||||
add_task(function* test_contextualIdentities_without_permissions() {
|
||||
function backgroundScript() {
|
||||
browser.test.assertTrue(!browser.contextualIdentities,
|
||||
"contextualIdentities API is not available when the contextualIdentities permission is not required");
|
||||
browser.test.notifyPass("contextualIdentities_permission");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: [],
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextualIdentities_permission");
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_contextualIdentity_no_containers() {
|
||||
function backgroundScript() {
|
||||
browser.test.assertTrue(!browser.contextualIdentities,
|
||||
"contextualIdentities API is not available when the containers are disabled");
|
||||
browser.test.notifyPass("contextualIdentities_pref");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["contextualIdentities"],
|
||||
},
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref("privacy.userContext.enabled", false);
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextualIdentities_pref");
|
||||
yield extension.unload();
|
||||
|
||||
Services.prefs.clearUserPref("privacy.userContext.enabled");
|
||||
});
|
||||
|
||||
add_task(function* test_contextualIdentity_with_permissions() {
|
||||
async function backgroundScript() {
|
||||
let ci = await browser.contextualIdentities.get("foobar");
|
||||
browser.test.assertEq(null, ci, "No identity should be returned here");
|
||||
|
||||
ci = await browser.contextualIdentities.get("firefox-container-1");
|
||||
browser.test.assertTrue(!!ci, "We have an identity");
|
||||
browser.test.assertTrue("name" in ci, "We have an identity.name");
|
||||
browser.test.assertTrue("color" in ci, "We have an identity.color");
|
||||
browser.test.assertTrue("icon" in ci, "We have an identity.icon");
|
||||
browser.test.assertEq("Personal", ci.name, "identity.name is correct");
|
||||
browser.test.assertEq("firefox-container-1", ci.cookieStoreId, "identity.cookieStoreId is correct");
|
||||
|
||||
let cis = await browser.contextualIdentities.query({});
|
||||
browser.test.assertEq(4, cis.length, "by default we should have 4 containers");
|
||||
|
||||
cis = await browser.contextualIdentities.query({name: "Personal"});
|
||||
browser.test.assertEq(1, cis.length, "by default we should have 1 container called Personal");
|
||||
|
||||
cis = await browser.contextualIdentities.query({name: "foobar"});
|
||||
browser.test.assertEq(0, cis.length, "by default we should have 0 container called foobar");
|
||||
|
||||
ci = await browser.contextualIdentities.create({name: "foobar", color: "red", icon: "icon"});
|
||||
browser.test.assertTrue(!!ci, "We have an identity");
|
||||
browser.test.assertEq("foobar", ci.name, "identity.name is correct");
|
||||
browser.test.assertEq("red", ci.color, "identity.color is correct");
|
||||
browser.test.assertEq("icon", ci.icon, "identity.icon is correct");
|
||||
browser.test.assertTrue(!!ci.cookieStoreId, "identity.cookieStoreId is correct");
|
||||
|
||||
ci = await browser.contextualIdentities.get(ci.cookieStoreId);
|
||||
browser.test.assertTrue(!!ci, "We have an identity");
|
||||
browser.test.assertEq("foobar", ci.name, "identity.name is correct");
|
||||
browser.test.assertEq("red", ci.color, "identity.color is correct");
|
||||
browser.test.assertEq("icon", ci.icon, "identity.icon is correct");
|
||||
|
||||
cis = await browser.contextualIdentities.query({});
|
||||
browser.test.assertEq(5, cis.length, "now we have 5 identities");
|
||||
|
||||
ci = await browser.contextualIdentities.update(ci.cookieStoreId, {name: "barfoo", color: "blue", icon: "icon icon"});
|
||||
browser.test.assertTrue(!!ci, "We have an identity");
|
||||
browser.test.assertEq("barfoo", ci.name, "identity.name is correct");
|
||||
browser.test.assertEq("blue", ci.color, "identity.color is correct");
|
||||
browser.test.assertEq("icon icon", ci.icon, "identity.icon is correct");
|
||||
|
||||
ci = await browser.contextualIdentities.get(ci.cookieStoreId);
|
||||
browser.test.assertTrue(!!ci, "We have an identity");
|
||||
browser.test.assertEq("barfoo", ci.name, "identity.name is correct");
|
||||
browser.test.assertEq("blue", ci.color, "identity.color is correct");
|
||||
browser.test.assertEq("icon icon", ci.icon, "identity.icon is correct");
|
||||
|
||||
ci = await browser.contextualIdentities.remove(ci.cookieStoreId);
|
||||
browser.test.assertTrue(!!ci, "We have an identity");
|
||||
browser.test.assertEq("barfoo", ci.name, "identity.name is correct");
|
||||
browser.test.assertEq("blue", ci.color, "identity.color is correct");
|
||||
browser.test.assertEq("icon icon", ci.icon, "identity.icon is correct");
|
||||
|
||||
cis = await browser.contextualIdentities.query({});
|
||||
browser.test.assertEq(4, cis.length, "we are back to 4 identities");
|
||||
|
||||
browser.test.notifyPass("contextualIdentities");
|
||||
}
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["contextualIdentities"],
|
||||
},
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref("privacy.userContext.enabled", true);
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contextualIdentities");
|
||||
yield extension.unload();
|
||||
|
||||
Services.prefs.clearUserPref("privacy.userContext.enabled");
|
||||
});
|
|
@ -25,6 +25,8 @@ skip-if = os == "android" # Android does not use Places for history.
|
|||
[test_ext_background_window_properties.js]
|
||||
skip-if = os == "android"
|
||||
[test_ext_contexts.js]
|
||||
[test_ext_contextual_identities.js]
|
||||
skip-if = os == "android" # Containers are not exposed to android.
|
||||
[test_ext_downloads.js]
|
||||
[test_ext_downloads_download.js]
|
||||
skip-if = os == "android"
|
||||
|
|
Загрузка…
Ссылка в новой задаче