зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
5444611211
|
@ -196,8 +196,6 @@ module.exports = {
|
|||
"modules/libpref/test/unit/test_dirtyPrefs.js",
|
||||
"toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js",
|
||||
"toolkit/mozapps/update/tests/unit_aus_update/testConstants.js",
|
||||
"xpcom/tests/unit/test_hidden_files.js",
|
||||
"xpcom/tests/unit/test_localfile.js",
|
||||
|
||||
// These are more complicated bugs which may require some in-depth
|
||||
// investigation or different solutions. They are also likely to be
|
||||
|
|
|
@ -429,6 +429,14 @@ pref("browser.urlbar.quicksuggest.remoteSettings.enabled", true);
|
|||
// suggestions.
|
||||
pref("browser.urlbar.quicksuggest.allowPositionInSuggestions", true);
|
||||
|
||||
// Whether non-sponsored quick suggest results are subject to impression
|
||||
// frequency caps.
|
||||
pref("browser.urlbar.quicksuggest.impressionCaps.nonSponsoredEnabled", false);
|
||||
|
||||
// Whether sponsored quick suggest results are subject to impression frequency
|
||||
// caps.
|
||||
pref("browser.urlbar.quicksuggest.impressionCaps.sponsoredEnabled", false);
|
||||
|
||||
// Whether unit conversion is enabled.
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("browser.urlbar.unitConversion.enabled", true);
|
||||
|
@ -2701,3 +2709,8 @@ pref("browser.snapshots.score.InNavigation", 3);
|
|||
pref("browser.snapshots.score.IsOverlappingVisit", 3);
|
||||
pref("browser.snapshots.score.IsUserPersisted", 1);
|
||||
pref("browser.snapshots.score.IsUsedRemoved", -10);
|
||||
|
||||
// Expiration days for snapshots.
|
||||
pref("browser.places.snapshots.expiration.days", 210);
|
||||
// For user managed snapshots we use more than a year, to support yearly tasks.
|
||||
pref("browser.places.snapshots.expiration.userManaged.days", 420);
|
||||
|
|
|
@ -2053,62 +2053,6 @@ var BookmarkingUI = {
|
|||
aEvent.target.removeEventListener("ViewHiding", this);
|
||||
},
|
||||
|
||||
toggleMenuButtonInToolbar(triggerNode) {
|
||||
let placement = CustomizableUI.getPlacementOfWidget(
|
||||
this.BOOKMARK_BUTTON_ID
|
||||
);
|
||||
const area = CustomizableUI.AREA_NAVBAR;
|
||||
if (!placement) {
|
||||
// Button is in the palette, so we can move it to the navbar.
|
||||
let pos;
|
||||
let widgetIDs = CustomizableUI.getWidgetIdsInArea(
|
||||
CustomizableUI.AREA_NAVBAR
|
||||
);
|
||||
// If there's a spring inside the navbar, find it and use that as the
|
||||
// placement marker.
|
||||
let lastSpringID = null;
|
||||
for (let i = widgetIDs.length - 1; i >= 0; --i) {
|
||||
let id = widgetIDs[i];
|
||||
if (CustomizableUI.isSpecialWidget(id) && /spring/.test(id)) {
|
||||
lastSpringID = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastSpringID) {
|
||||
pos = CustomizableUI.getPlacementOfWidget(lastSpringID).position + 1;
|
||||
} else {
|
||||
// Next alternative is to use the searchbar as the placement marker.
|
||||
const searchWidgetID = "search-container";
|
||||
if (widgetIDs.includes(searchWidgetID)) {
|
||||
pos =
|
||||
CustomizableUI.getPlacementOfWidget(searchWidgetID).position + 1;
|
||||
} else {
|
||||
// Last alternative is to use the navbar as the placement marker.
|
||||
pos =
|
||||
CustomizableUI.getPlacementOfWidget("urlbar-container").position +
|
||||
1;
|
||||
}
|
||||
}
|
||||
|
||||
CustomizableUI.addWidgetToArea(this.BOOKMARK_BUTTON_ID, area, pos);
|
||||
BrowserUsageTelemetry.recordWidgetChange(
|
||||
this.BOOKMARK_BUTTON_ID,
|
||||
area,
|
||||
"bookmark-tools"
|
||||
);
|
||||
} else {
|
||||
// Move it back to the palette.
|
||||
CustomizableUI.removeWidgetFromArea(this.BOOKMARK_BUTTON_ID);
|
||||
BrowserUsageTelemetry.recordWidgetChange(
|
||||
this.BOOKMARK_BUTTON_ID,
|
||||
null,
|
||||
"bookmark-tools"
|
||||
);
|
||||
}
|
||||
triggerNode.setAttribute("checked", !placement);
|
||||
updateToggleControlLabel(triggerNode);
|
||||
},
|
||||
|
||||
handlePlacesEvents(aEvents) {
|
||||
let isStarUpdateNeeded = false;
|
||||
let affectsOtherBookmarksFolder = false;
|
||||
|
|
|
@ -2093,7 +2093,10 @@
|
|||
// and make it ready to use. We only do this if the tab is selected
|
||||
// because otherwise, callers might end up unintentionally binding the
|
||||
// browser for lazy background tabs.
|
||||
if (aTab.selected) {
|
||||
if (!aTab.linkedPanel) {
|
||||
if (!aTab.selected) {
|
||||
return null;
|
||||
}
|
||||
gBrowser._insertBrowser(aTab);
|
||||
}
|
||||
return document.getElementById(aTab.linkedPanel);
|
||||
|
|
|
@ -236,6 +236,7 @@ https_first_disabled = true
|
|||
skip-if = apple_silicon && !debug # Bug 1724711
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
[browser_minimize.js]
|
||||
skip-if = apple_silicon && !debug # Bug 1725756
|
||||
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
|
||||
fail-if = (os == 'linux' && os_version == '18.04') # Bug 1600177
|
||||
[browser_modifiedclick_inherit_principal.js]
|
||||
|
|
|
@ -93,3 +93,7 @@ body > section {
|
|||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
login-intro {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
prefs =
|
||||
browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json'
|
||||
support-files =
|
||||
../head.js
|
||||
config_disable_developer_tools.json
|
||||
|
||||
[browser_policy_disable_developer_tools.js]
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* import-globals-from ../head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { EnterprisePolicyTesting } = ChromeUtils.import(
|
||||
"resource://testing-common/EnterprisePolicyTesting.jsm"
|
||||
);
|
||||
var updateService = Cc["@mozilla.org/updates/update-service;1"].getService(
|
||||
Ci.nsIApplicationUpdateService
|
||||
);
|
||||
|
@ -58,3 +60,28 @@ add_task(async function test_updates_post_policy() {
|
|||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
// Copied from ../head.js. head.js was never intended to be used with tests
|
||||
// that use a JSON file versus calling setupPolicyEngineWithJson so I have
|
||||
// to copy this function here versus including it.
|
||||
async function testPageBlockedByPolicy(page, policyJSON) {
|
||||
if (policyJSON) {
|
||||
await EnterprisePolicyTesting.setupPolicyEngineWithJson(policyJSON);
|
||||
}
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:blank" },
|
||||
async browser => {
|
||||
BrowserTestUtils.loadURI(browser, page);
|
||||
await BrowserTestUtils.browserLoaded(browser, false, page, true);
|
||||
await SpecialPowers.spawn(browser, [page], async function(innerPage) {
|
||||
ok(
|
||||
content.document.documentURI.startsWith(
|
||||
"about:neterror?e=blockedByPolicy"
|
||||
),
|
||||
content.document.documentURI +
|
||||
" should start with about:neterror?e=blockedByPolicy"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -173,10 +173,6 @@ async function check_homepage({
|
|||
}
|
||||
|
||||
add_setup(async function policies_headjs_startWithCleanSlate() {
|
||||
if (Services.prefs.getPrefType("browser.policies.alternatePath")) {
|
||||
// This allows tests that use a JSON File to use head.js
|
||||
return;
|
||||
}
|
||||
if (Services.policies.status != Ci.nsIEnterprisePolicies.INACTIVE) {
|
||||
await setupPolicyEngineWithJson("");
|
||||
}
|
||||
|
|
|
@ -118,6 +118,8 @@
|
|||
"url": "chrome://browser/content/parent/ext-menus.js",
|
||||
"schema": "chrome://browser/content/schemas/menus.json",
|
||||
"scopes": ["addon_parent"],
|
||||
"events": ["startup"],
|
||||
"permissions": ["menus", "contextMenus"],
|
||||
"paths": [
|
||||
["contextMenus"],
|
||||
["menus"],
|
||||
|
|
|
@ -24,7 +24,7 @@ var { ExtensionParent } = ChromeUtils.import(
|
|||
"resource://gre/modules/ExtensionParent.jsm"
|
||||
);
|
||||
|
||||
var { IconDetails } = ExtensionParent;
|
||||
var { IconDetails, StartupCache } = ExtensionParent;
|
||||
|
||||
const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
|
||||
|
||||
|
@ -33,6 +33,12 @@ const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
|
|||
// this cannot be a weak map.
|
||||
var gMenuMap = new Map();
|
||||
|
||||
// Map[Extension -> Map[ID -> MenuCreateProperties]]
|
||||
// The map object for each extension is a reference to the same
|
||||
// object in StartupCache.menus. This provides a non-async
|
||||
// getter for that object.
|
||||
var gStartupCache = new Map();
|
||||
|
||||
// Map[Extension -> MenuItem]
|
||||
var gRootItems = new Map();
|
||||
|
||||
|
@ -711,38 +717,42 @@ function addMenuEventInfo(info, contextData, extension, includeSensitiveData) {
|
|||
}
|
||||
}
|
||||
|
||||
function MenuItem(extension, createProperties, isRoot = false) {
|
||||
this.extension = extension;
|
||||
this.children = [];
|
||||
this.parent = null;
|
||||
this.tabManager = extension.tabManager;
|
||||
class MenuItem {
|
||||
constructor(extension, createProperties, isRoot = false) {
|
||||
this.extension = extension;
|
||||
this.children = [];
|
||||
this.parent = null;
|
||||
this.tabManager = extension.tabManager;
|
||||
|
||||
this.setDefaults();
|
||||
this.setProps(createProperties);
|
||||
this.setDefaults();
|
||||
this.setProps(createProperties);
|
||||
|
||||
if (!this.hasOwnProperty("_id")) {
|
||||
this.id = gNextMenuItemID++;
|
||||
if (!this.hasOwnProperty("_id")) {
|
||||
this.id = gNextMenuItemID++;
|
||||
}
|
||||
// If the item is not the root and has no parent
|
||||
// it must be a child of the root.
|
||||
if (!isRoot && !this.parent) {
|
||||
this.root.addChild(this);
|
||||
}
|
||||
}
|
||||
// If the item is not the root and has no parent
|
||||
// it must be a child of the root.
|
||||
if (!isRoot && !this.parent) {
|
||||
this.root.addChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem.prototype = {
|
||||
setProps(createProperties) {
|
||||
for (let propName in createProperties) {
|
||||
if (createProperties[propName] === null) {
|
||||
static mergeProps(obj, properties) {
|
||||
for (let propName in properties) {
|
||||
if (properties[propName] === null) {
|
||||
// Omitted optional argument.
|
||||
continue;
|
||||
}
|
||||
this[propName] = createProperties[propName];
|
||||
obj[propName] = properties[propName];
|
||||
}
|
||||
|
||||
if ("icons" in createProperties && createProperties.icons === null) {
|
||||
this.icons = null;
|
||||
if ("icons" in properties && properties.icons === null && obj.icons) {
|
||||
obj.icons = null;
|
||||
}
|
||||
}
|
||||
|
||||
setProps(createProperties) {
|
||||
MenuItem.mergeProps(this, createProperties);
|
||||
|
||||
if (createProperties.documentUrlPatterns != null) {
|
||||
this.documentUrlMatchPattern = parseMatchPatterns(
|
||||
|
@ -766,7 +776,7 @@ MenuItem.prototype = {
|
|||
if (createProperties.parentId && !createProperties.contexts) {
|
||||
this.contexts = this.parent.contexts;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
setDefaults() {
|
||||
this.setProps({
|
||||
|
@ -776,7 +786,7 @@ MenuItem.prototype = {
|
|||
enabled: true,
|
||||
visible: true,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
set id(id) {
|
||||
if (this.hasOwnProperty("_id")) {
|
||||
|
@ -787,11 +797,11 @@ MenuItem.prototype = {
|
|||
throw new ExtensionError(`ID already exists: ${id}`);
|
||||
}
|
||||
this._id = id;
|
||||
},
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
},
|
||||
}
|
||||
|
||||
get elementId() {
|
||||
let id = this.id;
|
||||
|
@ -803,7 +813,7 @@ MenuItem.prototype = {
|
|||
id = `_${id}`;
|
||||
}
|
||||
return `${makeWidgetId(this.extension.id)}-menuitem-${id}`;
|
||||
},
|
||||
}
|
||||
|
||||
ensureValidParentId(parentId) {
|
||||
if (parentId === undefined) {
|
||||
|
@ -822,7 +832,25 @@ MenuItem.prototype = {
|
|||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* When updating menu properties we need to ensure parents exist
|
||||
* in the cache map before children. That allows the menus to be
|
||||
* created in the correct sequence on startup. This reparents the
|
||||
* tree starting from this instance of MenuItem.
|
||||
*/
|
||||
reparentInCache() {
|
||||
let { id, extension } = this;
|
||||
let cachedMap = gStartupCache.get(extension);
|
||||
let createProperties = cachedMap.get(id);
|
||||
cachedMap.delete(id);
|
||||
cachedMap.set(id, createProperties);
|
||||
|
||||
for (let child of this.children) {
|
||||
child.reparentInCache();
|
||||
}
|
||||
}
|
||||
|
||||
set parentId(parentId) {
|
||||
this.ensureValidParentId(parentId);
|
||||
|
@ -837,11 +865,11 @@ MenuItem.prototype = {
|
|||
let menuMap = gMenuMap.get(this.extension);
|
||||
menuMap.get(parentId).addChild(this);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
get parentId() {
|
||||
return this.parent ? this.parent.id : undefined;
|
||||
},
|
||||
}
|
||||
|
||||
addChild(child) {
|
||||
if (child.parent) {
|
||||
|
@ -849,7 +877,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
this.children.push(child);
|
||||
child.parent = this;
|
||||
},
|
||||
}
|
||||
|
||||
detachChild(child) {
|
||||
let idx = this.children.indexOf(child);
|
||||
|
@ -858,7 +886,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
this.children.splice(idx, 1);
|
||||
child.parent = null;
|
||||
},
|
||||
}
|
||||
|
||||
get root() {
|
||||
let extension = this.extension;
|
||||
|
@ -872,7 +900,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
|
||||
return gRootItems.get(extension);
|
||||
},
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.parent) {
|
||||
|
@ -885,10 +913,14 @@ MenuItem.prototype = {
|
|||
|
||||
let menuMap = gMenuMap.get(this.extension);
|
||||
menuMap.delete(this.id);
|
||||
// Menu items are saved if !extension.persistentBackground.
|
||||
if (gStartupCache.get(this.extension)?.delete(this.id)) {
|
||||
StartupCache.save();
|
||||
}
|
||||
if (this.root == this) {
|
||||
gRootItems.delete(this.extension);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getClickInfo(contextData, wasChecked) {
|
||||
let info = {
|
||||
|
@ -906,7 +938,7 @@ MenuItem.prototype = {
|
|||
}
|
||||
|
||||
return info;
|
||||
},
|
||||
}
|
||||
|
||||
enabledForContext(contextData) {
|
||||
if (!this.visible) {
|
||||
|
@ -968,8 +1000,8 @@ MenuItem.prototype = {
|
|||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// windowTracker only looks as browser windows, but we're also interested in
|
||||
// the Library window. Helper for menuTracker below.
|
||||
|
@ -1199,6 +1231,38 @@ this.menusInternal = class extends ExtensionAPIPersistent {
|
|||
gMenuMap.set(extension, new Map());
|
||||
}
|
||||
|
||||
restoreFromCache() {
|
||||
let { extension } = this;
|
||||
// ensure extension has not shutdown
|
||||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
for (let createProperties of gStartupCache.get(extension).values()) {
|
||||
// The order of menu creation is significant, see reparentInCache.
|
||||
let menuItem = new MenuItem(extension, createProperties);
|
||||
gMenuMap.get(extension).set(menuItem.id, menuItem);
|
||||
}
|
||||
// Used for testing
|
||||
extension.emit("webext-menus-created", gMenuMap.get(extension));
|
||||
}
|
||||
|
||||
async onStartup() {
|
||||
let { extension } = this;
|
||||
if (extension.persistentBackground) {
|
||||
return;
|
||||
}
|
||||
// Using the map retains insertion order.
|
||||
let cachedMenus = await StartupCache.menus.get(extension.id, () => {
|
||||
return new Map();
|
||||
});
|
||||
gStartupCache.set(extension, cachedMenus);
|
||||
if (!cachedMenus.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.restoreFromCache();
|
||||
}
|
||||
|
||||
onShutdown() {
|
||||
let { extension } = this;
|
||||
|
||||
|
@ -1206,6 +1270,7 @@ this.menusInternal = class extends ExtensionAPIPersistent {
|
|||
gMenuMap.delete(extension);
|
||||
gRootItems.delete(extension);
|
||||
gShownMenuItems.delete(extension);
|
||||
gStartupCache.delete(extension);
|
||||
gOnShownSubscribers.delete(extension);
|
||||
if (!gMenuMap.size) {
|
||||
menuTracker.unregister();
|
||||
|
@ -1333,7 +1398,7 @@ this.menusInternal = class extends ExtensionAPIPersistent {
|
|||
contextMenus: menus,
|
||||
menus,
|
||||
menusInternal: {
|
||||
create: function(createProperties) {
|
||||
create(createProperties) {
|
||||
// event pages require id
|
||||
if (!extension.persistentBackground) {
|
||||
if (!createProperties.id) {
|
||||
|
@ -1349,31 +1414,63 @@ this.menusInternal = class extends ExtensionAPIPersistent {
|
|||
}
|
||||
|
||||
// Note that the id is required by the schema. If the addon did not set
|
||||
// it, the implementation of menus.create in the child should
|
||||
// have added it.
|
||||
// it, the implementation of menus.create in the child will add it for
|
||||
// extensions with persistent backgrounds, but not otherwise.
|
||||
let menuItem = new MenuItem(extension, createProperties);
|
||||
gMenuMap.get(extension).set(menuItem.id, menuItem);
|
||||
},
|
||||
|
||||
update: function(id, updateProperties) {
|
||||
let menuItem = gMenuMap.get(extension).get(id);
|
||||
if (menuItem) {
|
||||
menuItem.setProps(updateProperties);
|
||||
if (!extension.persistentBackground) {
|
||||
// Only cache properties that are necessary.
|
||||
let cached = {};
|
||||
MenuItem.mergeProps(cached, createProperties);
|
||||
gStartupCache.get(extension).set(menuItem.id, cached);
|
||||
StartupCache.save();
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
update(id, updateProperties) {
|
||||
let menuItem = gMenuMap.get(extension).get(id);
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
menuItem.setProps(updateProperties);
|
||||
|
||||
// Update the startup cache for non-persistent extensions.
|
||||
if (extension.persistentBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cached = gStartupCache.get(extension).get(id);
|
||||
let reparent =
|
||||
updateProperties.parentId != null &&
|
||||
cached.parentId != updateProperties.parentId;
|
||||
MenuItem.mergeProps(cached, updateProperties);
|
||||
if (reparent) {
|
||||
// The order of menu creation is significant, see reparentInCache.
|
||||
menuItem.reparentInCache();
|
||||
}
|
||||
StartupCache.save();
|
||||
},
|
||||
|
||||
remove(id) {
|
||||
let menuItem = gMenuMap.get(extension).get(id);
|
||||
if (menuItem) {
|
||||
menuItem.remove();
|
||||
}
|
||||
},
|
||||
|
||||
removeAll: function() {
|
||||
removeAll() {
|
||||
let root = gRootItems.get(extension);
|
||||
if (root) {
|
||||
root.remove();
|
||||
}
|
||||
// Should be empty, just extra assurance.
|
||||
if (!extension.persistentBackground) {
|
||||
let cached = gStartupCache.get(extension);
|
||||
if (cached.size) {
|
||||
cached.clear();
|
||||
StartupCache.save();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onClicked: new EventManager({
|
||||
|
|
|
@ -0,0 +1,439 @@
|
|||
"use strict";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ExtensionParent",
|
||||
"resource://gre/modules/ExtensionParent.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Management",
|
||||
"resource://gre/modules/Extension.jsm"
|
||||
);
|
||||
|
||||
const { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
AddonTestUtils.init(this);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo(
|
||||
"xpcshell@tests.mozilla.org",
|
||||
"XPCShell",
|
||||
"42",
|
||||
"42"
|
||||
);
|
||||
|
||||
Services.prefs.setBoolPref("extensions.eventPages.enabled", true);
|
||||
|
||||
function getExtension(id, background, useAddonManager) {
|
||||
return ExtensionTestUtils.loadExtension({
|
||||
useAddonManager,
|
||||
manifest: {
|
||||
applications: { gecko: { id } },
|
||||
permissions: ["menus"],
|
||||
background: { persistent: false },
|
||||
},
|
||||
background,
|
||||
});
|
||||
}
|
||||
|
||||
async function expectCached(extension, expect) {
|
||||
let { StartupCache } = ExtensionParent;
|
||||
let cached = await StartupCache.menus.get(extension.id);
|
||||
let createProperties = Array.from(cached.values());
|
||||
equal(cached.size, expect.length, "menus saved in cache");
|
||||
// The menus startupCache is a map and the order is significant
|
||||
// for recreating menus on startup. Ensure that they are in
|
||||
// the expected order. We only verify specific keys here rather
|
||||
// than all menu properties.
|
||||
for (let i in createProperties) {
|
||||
Assert.deepEqual(
|
||||
createProperties[i],
|
||||
expect[i],
|
||||
"expected cached properties exist"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function promiseExtensionEvent(wrapper, event) {
|
||||
return new Promise(resolve => {
|
||||
wrapper.extension.once(event, (kind, data) => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
add_setup(async () => {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
});
|
||||
|
||||
add_task(async function test_menu_onInstalled() {
|
||||
async function background() {
|
||||
browser.runtime.onInstalled.addListener(async () => {
|
||||
const parentId = browser.menus.create({
|
||||
contexts: ["all"],
|
||||
title: "parent",
|
||||
id: "test-parent",
|
||||
});
|
||||
browser.menus.create({
|
||||
parentId,
|
||||
title: "click A",
|
||||
id: "test-click-a",
|
||||
});
|
||||
browser.menus.create(
|
||||
{
|
||||
parentId,
|
||||
title: "click B",
|
||||
id: "test-click-b",
|
||||
},
|
||||
() => {
|
||||
browser.test.sendMessage("onInstalled");
|
||||
}
|
||||
);
|
||||
});
|
||||
browser.menus.create(
|
||||
{
|
||||
contexts: ["tab"],
|
||||
title: "top-level",
|
||||
id: "test-top-level",
|
||||
},
|
||||
() => {
|
||||
browser.test.sendMessage("create", browser.runtime.lastError?.message);
|
||||
}
|
||||
);
|
||||
|
||||
browser.test.onMessage.addListener(async msg => {
|
||||
browser.test.log(`onMessage ${msg}`);
|
||||
if (msg == "updatemenu") {
|
||||
await browser.menus.update("test-click-a", { title: "click updated" });
|
||||
} else if (msg == "removemenu") {
|
||||
await browser.menus.remove("test-click-b");
|
||||
} else if (msg == "removeall") {
|
||||
await browser.menus.removeAll();
|
||||
}
|
||||
browser.test.sendMessage("updated");
|
||||
});
|
||||
}
|
||||
|
||||
const extension = getExtension(
|
||||
"test-persist@mochitest",
|
||||
background,
|
||||
"permanent"
|
||||
);
|
||||
|
||||
await extension.startup();
|
||||
let lastError = await extension.awaitMessage("create");
|
||||
Assert.equal(lastError, undefined, "no error creating menu");
|
||||
await extension.awaitMessage("onInstalled");
|
||||
await extension.terminateBackground();
|
||||
|
||||
await expectCached(extension, [
|
||||
{
|
||||
contexts: ["tab"],
|
||||
id: "test-top-level",
|
||||
title: "top-level",
|
||||
},
|
||||
{ contexts: ["all"], id: "test-parent", title: "parent" },
|
||||
{
|
||||
id: "test-click-a",
|
||||
parentId: "test-parent",
|
||||
title: "click A",
|
||||
},
|
||||
{
|
||||
id: "test-click-b",
|
||||
parentId: "test-parent",
|
||||
title: "click B",
|
||||
},
|
||||
]);
|
||||
|
||||
await extension.wakeupBackground();
|
||||
lastError = await extension.awaitMessage("create");
|
||||
Assert.equal(
|
||||
lastError,
|
||||
"The menu id test-top-level already exists in menus.create.",
|
||||
"correct error creating menu"
|
||||
);
|
||||
|
||||
await AddonTestUtils.promiseRestartManager();
|
||||
await extension.awaitStartup();
|
||||
|
||||
// verify the startupcache
|
||||
await expectCached(extension, [
|
||||
{
|
||||
contexts: ["tab"],
|
||||
id: "test-top-level",
|
||||
title: "top-level",
|
||||
},
|
||||
{ contexts: ["all"], id: "test-parent", title: "parent" },
|
||||
{
|
||||
id: "test-click-a",
|
||||
parentId: "test-parent",
|
||||
title: "click A",
|
||||
},
|
||||
{
|
||||
id: "test-click-b",
|
||||
parentId: "test-parent",
|
||||
title: "click B",
|
||||
},
|
||||
]);
|
||||
|
||||
equal(
|
||||
extension.extension.backgroundState,
|
||||
"stopped",
|
||||
"background is not running"
|
||||
);
|
||||
await extension.wakeupBackground();
|
||||
lastError = await extension.awaitMessage("create");
|
||||
Assert.equal(
|
||||
lastError,
|
||||
"The menu id test-top-level already exists in menus.create.",
|
||||
"correct error creating menu"
|
||||
);
|
||||
|
||||
extension.sendMessage("updatemenu");
|
||||
await extension.awaitMessage("updated");
|
||||
await extension.terminateBackground();
|
||||
|
||||
// Title change is cached
|
||||
await expectCached(extension, [
|
||||
{
|
||||
contexts: ["tab"],
|
||||
id: "test-top-level",
|
||||
title: "top-level",
|
||||
},
|
||||
{ contexts: ["all"], id: "test-parent", title: "parent" },
|
||||
{
|
||||
id: "test-click-a",
|
||||
parentId: "test-parent",
|
||||
title: "click updated",
|
||||
},
|
||||
{
|
||||
id: "test-click-b",
|
||||
parentId: "test-parent",
|
||||
title: "click B",
|
||||
},
|
||||
]);
|
||||
|
||||
await extension.wakeupBackground();
|
||||
lastError = await extension.awaitMessage("create");
|
||||
Assert.equal(
|
||||
lastError,
|
||||
"The menu id test-top-level already exists in menus.create.",
|
||||
"correct error creating menu"
|
||||
);
|
||||
|
||||
extension.sendMessage("removemenu");
|
||||
await extension.awaitMessage("updated");
|
||||
await extension.terminateBackground();
|
||||
|
||||
// menu removed
|
||||
await expectCached(extension, [
|
||||
{
|
||||
contexts: ["tab"],
|
||||
id: "test-top-level",
|
||||
title: "top-level",
|
||||
},
|
||||
{ contexts: ["all"], id: "test-parent", title: "parent" },
|
||||
{
|
||||
id: "test-click-a",
|
||||
parentId: "test-parent",
|
||||
title: "click updated",
|
||||
},
|
||||
]);
|
||||
|
||||
await extension.wakeupBackground();
|
||||
lastError = await extension.awaitMessage("create");
|
||||
Assert.equal(
|
||||
lastError,
|
||||
"The menu id test-top-level already exists in menus.create.",
|
||||
"correct error creating menu"
|
||||
);
|
||||
|
||||
extension.sendMessage("removeall");
|
||||
await extension.awaitMessage("updated");
|
||||
await extension.terminateBackground();
|
||||
|
||||
// menus removed
|
||||
await expectCached(extension, []);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_menu_nested() {
|
||||
async function background() {
|
||||
browser.test.onMessage.addListener(async (action, properties) => {
|
||||
browser.test.log(`onMessage ${action}`);
|
||||
switch (action) {
|
||||
case "create":
|
||||
await new Promise(resolve => {
|
||||
browser.menus.create(properties, resolve);
|
||||
});
|
||||
break;
|
||||
case "update":
|
||||
{
|
||||
let { id, ...update } = properties;
|
||||
await browser.menus.update(id, update);
|
||||
}
|
||||
break;
|
||||
case "remove":
|
||||
{
|
||||
let { id } = properties;
|
||||
await browser.menus.remove(id);
|
||||
}
|
||||
break;
|
||||
case "removeAll":
|
||||
await browser.menus.removeAll();
|
||||
break;
|
||||
}
|
||||
browser.test.sendMessage("updated");
|
||||
});
|
||||
}
|
||||
|
||||
const extension = getExtension(
|
||||
"test-nesting@mochitest",
|
||||
background,
|
||||
"permanent"
|
||||
);
|
||||
await extension.startup();
|
||||
|
||||
extension.sendMessage("create", {
|
||||
id: "first",
|
||||
contexts: ["all"],
|
||||
title: "first",
|
||||
});
|
||||
await extension.awaitMessage("updated");
|
||||
await expectCached(extension, [
|
||||
{ contexts: ["all"], id: "first", title: "first" },
|
||||
]);
|
||||
|
||||
extension.sendMessage("create", {
|
||||
id: "second",
|
||||
contexts: ["all"],
|
||||
title: "second",
|
||||
});
|
||||
await extension.awaitMessage("updated");
|
||||
await expectCached(extension, [
|
||||
{ contexts: ["all"], id: "first", title: "first" },
|
||||
{ contexts: ["all"], id: "second", title: "second" },
|
||||
]);
|
||||
|
||||
extension.sendMessage("create", {
|
||||
id: "third",
|
||||
contexts: ["all"],
|
||||
title: "third",
|
||||
parentId: "first",
|
||||
});
|
||||
await extension.awaitMessage("updated");
|
||||
await expectCached(extension, [
|
||||
{ contexts: ["all"], id: "first", title: "first" },
|
||||
{ contexts: ["all"], id: "second", title: "second" },
|
||||
{
|
||||
contexts: ["all"],
|
||||
id: "third",
|
||||
parentId: "first",
|
||||
title: "third",
|
||||
},
|
||||
]);
|
||||
|
||||
extension.sendMessage("create", {
|
||||
id: "fourth",
|
||||
contexts: ["all"],
|
||||
title: "fourth",
|
||||
});
|
||||
await extension.awaitMessage("updated");
|
||||
await expectCached(extension, [
|
||||
{ contexts: ["all"], id: "first", title: "first" },
|
||||
{ contexts: ["all"], id: "second", title: "second" },
|
||||
{
|
||||
contexts: ["all"],
|
||||
id: "third",
|
||||
parentId: "first",
|
||||
title: "third",
|
||||
},
|
||||
{ contexts: ["all"], id: "fourth", title: "fourth" },
|
||||
]);
|
||||
|
||||
extension.sendMessage("update", {
|
||||
id: "first",
|
||||
parentId: "second",
|
||||
});
|
||||
await extension.awaitMessage("updated");
|
||||
await expectCached(extension, [
|
||||
{ contexts: ["all"], id: "second", title: "second" },
|
||||
{ contexts: ["all"], id: "fourth", title: "fourth" },
|
||||
{
|
||||
contexts: ["all"],
|
||||
id: "first",
|
||||
title: "first",
|
||||
parentId: "second",
|
||||
},
|
||||
{
|
||||
contexts: ["all"],
|
||||
id: "third",
|
||||
parentId: "first",
|
||||
title: "third",
|
||||
},
|
||||
]);
|
||||
|
||||
await AddonTestUtils.promiseShutdownManager();
|
||||
// We need to attach an event listener before the
|
||||
// startup event is emitted. Fortunately, we
|
||||
// emit via Management before emitting on extension.
|
||||
let promiseMenus;
|
||||
Management.once("startup", (kind, ext) => {
|
||||
info(`management ${kind} ${ext.id}`);
|
||||
promiseMenus = promiseExtensionEvent(
|
||||
{ extension: ext },
|
||||
"webext-menus-created"
|
||||
);
|
||||
});
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
await extension.awaitStartup();
|
||||
await extension.wakeupBackground();
|
||||
|
||||
await expectCached(extension, [
|
||||
{ contexts: ["all"], id: "second", title: "second" },
|
||||
{ contexts: ["all"], id: "fourth", title: "fourth" },
|
||||
{
|
||||
contexts: ["all"],
|
||||
id: "first",
|
||||
title: "first",
|
||||
parentId: "second",
|
||||
},
|
||||
{
|
||||
contexts: ["all"],
|
||||
id: "third",
|
||||
parentId: "first",
|
||||
title: "third",
|
||||
},
|
||||
]);
|
||||
// validate nesting
|
||||
let menus = await promiseMenus;
|
||||
equal(menus.get("first").parentId, "second", "menuitem parent is correct");
|
||||
equal(
|
||||
menus.get("second").children.length,
|
||||
1,
|
||||
"menuitem parent has correct number of children"
|
||||
);
|
||||
equal(
|
||||
menus.get("second").root.children.length,
|
||||
2, // second and forth
|
||||
"menuitem root has correct number of children"
|
||||
);
|
||||
|
||||
extension.sendMessage("remove", {
|
||||
id: "second",
|
||||
});
|
||||
await extension.awaitMessage("updated");
|
||||
await expectCached(extension, [
|
||||
{ contexts: ["all"], id: "fourth", title: "fourth" },
|
||||
]);
|
||||
|
||||
extension.sendMessage("removeAll");
|
||||
await extension.awaitMessage("updated");
|
||||
await expectCached(extension, []);
|
||||
|
||||
await extension.unload();
|
||||
});
|
|
@ -20,6 +20,7 @@ skip-if = tsan # Times out, bug 1612707
|
|||
[test_ext_manifest_omnibox.js]
|
||||
[test_ext_manifest_permissions.js]
|
||||
[test_ext_menu_caller.js]
|
||||
[test_ext_menu_startup.js]
|
||||
[test_ext_normandyAddonStudy.js]
|
||||
[test_ext_pageAction_shutdown.js]
|
||||
[test_ext_pkcs11_management.js]
|
||||
|
|
|
@ -142,6 +142,17 @@ body[lwt-newtab-brighttext] {
|
|||
min-height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.onboardingContainer .screen:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) .brand-logo {
|
||||
margin-top: 120px;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.onboardingContainer .screen:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) .brand-logo {
|
||||
background-image: url("chrome://activity-stream/content/data/content/assets/heart.svg") !important;
|
||||
}
|
||||
}
|
||||
.onboardingContainer .screen:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) .no-steps {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.onboardingContainer .screen.light-text {
|
||||
--in-content-page-color: rgb(251, 251, 254);
|
||||
--in-content-primary-button-text-color: rgb(43, 42, 51);
|
||||
|
@ -233,9 +244,7 @@ body[lwt-newtab-brighttext] {
|
|||
.onboardingContainer .welcome-text.fancy h1 {
|
||||
background-image: linear-gradient(90deg, #9059FF, #FF4AA2, #FF8C00, #FF4AA2, #9059FF);
|
||||
background-size: 400% auto;
|
||||
color: #000;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
animation: shine 50s linear infinite;
|
||||
}
|
||||
@media (prefers-contrast: no-preference) {
|
||||
|
@ -669,6 +678,7 @@ body[lwt-newtab-brighttext] {
|
|||
background-size: contain;
|
||||
background-position: center;
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
}
|
||||
.onboardingContainer .mobile-download-buttons li:not(:first-child) {
|
||||
margin-inline: 5px 0;
|
||||
|
|
|
@ -147,6 +147,21 @@ body {
|
|||
min-height: 500px;
|
||||
overflow: hidden;
|
||||
|
||||
&:is(.UPGRADE_PIN_FIREFOX, .UPGRADE_ONLY_DEFAULT, .UPGRADE_GET_STARTED) {
|
||||
.brand-logo {
|
||||
margin-top: 120px;
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
// sass-lint:disable-block no-important
|
||||
background-image: url('chrome://activity-stream/content/data/content/assets/heart.svg') !important;
|
||||
}
|
||||
}
|
||||
|
||||
.no-steps {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&.light-text {
|
||||
--in-content-page-color: rgb(251, 251, 254);
|
||||
--in-content-primary-button-text-color: rgb(43, 42, 51);
|
||||
|
@ -257,9 +272,7 @@ body {
|
|||
h1 {
|
||||
background-image: linear-gradient(90deg, #9059FF, #FF4AA2, #FF8C00, #FF4AA2, #9059FF);
|
||||
background-size: 400% auto;
|
||||
color: #000;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
animation: shine 50s linear infinite;
|
||||
@media (prefers-contrast: no-preference) {
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
@ -826,6 +839,7 @@ body {
|
|||
background-size: contain;
|
||||
background-position: center;
|
||||
box-shadow: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
|
|
Двоичные данные
browser/components/newtab/data/content/assets/heart.gif
Двоичные данные
browser/components/newtab/data/content/assets/heart.gif
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 70 KiB |
|
@ -0,0 +1,48 @@
|
|||
<!-- 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/. -->
|
||||
<svg width="83" height="67" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.675 6.774A21.93 21.93 0 0 0 60.989 0h-.18a21.902 21.902 0 0 0-15.592 6.438L27.549 24.126a7.533 7.533 0 0 0 0 10.658l3.276 3.269a7.568 7.568 0 0 0 10.68 0l-8.591-8.505L50.56 11.86a14.355 14.355 0 0 1 10.25-4.242h.136a14.356 14.356 0 0 1 10.278 4.427c5.43 5.63 5.15 14.9-.615 20.672L45.467 57.931a5.544 5.544 0 0 1-7.868 0l4.041 4.048.651.658a6.602 6.602 0 0 0 8.805.45l24.82-24.991c8.748-8.676 9.041-22.724.759-31.322Z" fill="url(#a)"/>
|
||||
<path d="M70.58 32.724 45.439 57.938a5.543 5.543 0 0 1-7.868 0l4.07 4.041.651.658a6.602 6.602 0 0 0 8.805.45l24.82-24.991-5.335-5.372Z" fill="url(#b)"/>
|
||||
<path d="M41.533 38.032h-.036a7.56 7.56 0 0 1-10.672 0l5.344 5.336a7.548 7.548 0 0 0 10.671 0l8.584-8.584a7.567 7.567 0 0 0 0-10.679L37.806 6.481A21.923 21.923 0 0 0 22.213.043h-.207A21.945 21.945 0 0 0 6.32 6.773c-8.283 8.584-7.961 22.646.715 31.33L31.483 62.53a14.305 14.305 0 0 0 19.67.565 6.602 6.602 0 0 1-8.804-.45l-5.48-5.487-24.448-24.434C6.65 26.952 6.37 17.704 11.8 12.053a14.342 14.342 0 0 1 10.279-4.435h.107a14.355 14.355 0 0 1 10.235 4.234l17.66 17.575-8.533 8.583-.014.022Z" fill="url(#c)"/>
|
||||
<path d="M41.533 38.032h-.036a7.56 7.56 0 0 1-10.672 0l5.344 5.336a7.548 7.548 0 0 0 10.671 0l8.584-8.584a7.567 7.567 0 0 0 0-10.679L37.806 6.481A21.923 21.923 0 0 0 22.213.043h-.207A21.945 21.945 0 0 0 6.32 6.773c-8.283 8.584-7.961 22.646.715 31.33L31.483 62.53a14.305 14.305 0 0 0 19.67.565 6.602 6.602 0 0 1-8.804-.45l-5.48-5.487-24.448-24.434C6.65 26.952 6.37 17.704 11.8 12.053a14.342 14.342 0 0 1 10.279-4.435h.107a14.355 14.355 0 0 1 10.235 4.234l17.66 17.575-8.533 8.583-.014.022Z" fill="url(#d)"/>
|
||||
<path d="M45.188 6.48 27.55 24.127a7.533 7.533 0 0 0 0 10.658l3.276 3.269a7.568 7.568 0 0 0 10.68 0l-8.591-8.505L50.56 11.86l-5.372-5.378Z" fill="url(#e)"/>
|
||||
<defs>
|
||||
<linearGradient id="a" x1="54.008" y1="64.583" x2="54.008" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3A8EE6"/>
|
||||
<stop offset=".24" stop-color="#5C79F0"/>
|
||||
<stop offset=".63" stop-color="#9059FF"/>
|
||||
<stop offset="1" stop-color="#C139E6"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="75.959" y1="48.653" x2="37.613" y2="48.653" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".14" stop-color="#6A2BEA" stop-opacity="0"/>
|
||||
<stop offset=".34" stop-color="#642DE4" stop-opacity=".03"/>
|
||||
<stop offset=".55" stop-color="#5131D3" stop-opacity=".12"/>
|
||||
<stop offset=".76" stop-color="#3139B7" stop-opacity=".27"/>
|
||||
<stop offset=".98" stop-color="#054490" stop-opacity=".48"/>
|
||||
<stop offset="1" stop-color="#00458B" stop-opacity=".5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="43.421" y1="8.848" x2="16.942" y2="54.719" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF980E"/>
|
||||
<stop offset=".27" stop-color="#FF851B"/>
|
||||
<stop offset=".56" stop-color="#FF7F1F"/>
|
||||
<stop offset=".77" stop-color="#FF3750"/>
|
||||
<stop offset=".9" stop-color="#F92261"/>
|
||||
<stop offset="1" stop-color="#F5156C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="d" x1="43.421" y1="8.848" x2="16.942" y2="54.719" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFF261" stop-opacity=".8"/>
|
||||
<stop offset=".06" stop-color="#FFF261" stop-opacity=".68"/>
|
||||
<stop offset=".19" stop-color="#FFF261" stop-opacity=".48"/>
|
||||
<stop offset=".31" stop-color="#FFF261" stop-opacity=".31"/>
|
||||
<stop offset=".42" stop-color="#FFF261" stop-opacity=".17"/>
|
||||
<stop offset=".53" stop-color="#FFF261" stop-opacity=".08"/>
|
||||
<stop offset=".63" stop-color="#FFF261" stop-opacity=".02"/>
|
||||
<stop offset=".72" stop-color="#FFF261" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="e" x1="37.95" y1="40.285" x2="37.95" y2="6.481" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6E008B" stop-opacity=".5"/>
|
||||
<stop offset=".5" stop-color="#C846CB" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 4.1 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 98 KiB |
|
@ -1432,14 +1432,12 @@ class _ASRouter {
|
|||
}
|
||||
// Update storage
|
||||
this._storage.set("groupImpressions", newGroupImpressions);
|
||||
// The groups parameter below can be removed once this method has test coverage
|
||||
return this.setState(({ groups }) => ({
|
||||
groupImpressions: newGroupImpressions,
|
||||
}));
|
||||
}
|
||||
|
||||
// Until this method has test coverage, it should only be used for testing
|
||||
_resetMessageState() {
|
||||
resetMessageState() {
|
||||
const newMessageImpressions = {};
|
||||
for (let { id } of this.state.messages) {
|
||||
newMessageImpressions[id] = [];
|
||||
|
|
|
@ -60,7 +60,7 @@ const ONBOARDING_MESSAGES = () => [
|
|||
content: {
|
||||
logo: {
|
||||
imageURL:
|
||||
"chrome://activity-stream/content/data/content/assets/heart.gif",
|
||||
"chrome://activity-stream/content/data/content/assets/heart.webp",
|
||||
height: "73px",
|
||||
},
|
||||
has_noodles: true,
|
||||
|
|
|
@ -178,10 +178,6 @@ add_task(async function test_aboutwelcome_with_title_styles() {
|
|||
"font-weight": "276",
|
||||
"font-size": "36px",
|
||||
animation: "50s linear 0s infinite normal none running shine",
|
||||
},
|
||||
// Unexpected styles:
|
||||
{
|
||||
color: "rgb(21, 20, 26)",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -2868,4 +2868,46 @@ describe("ASRouter", () => {
|
|||
assert.notCalled(spy);
|
||||
});
|
||||
});
|
||||
describe("#resetMessageState", () => {
|
||||
it("should reset all message impressions", async () => {
|
||||
await Router.setState({
|
||||
messages: [{ id: "1" }, { id: "2" }],
|
||||
});
|
||||
await Router.setState({
|
||||
messageImpressions: { "1": [0, 1, 2], "2": [0, 1, 2] },
|
||||
}); // Add impressions for test messages
|
||||
let impressions = Object.values(Router.state.messageImpressions);
|
||||
assert.equal(impressions.filter(i => i.length).length, 2); // Both messages have impressions
|
||||
|
||||
Router.resetMessageState();
|
||||
impressions = Object.values(Router.state.messageImpressions);
|
||||
|
||||
assert.isEmpty(impressions.filter(i => i.length)); // Both messages now have zero impressions
|
||||
assert.calledWithExactly(Router._storage.set, "messageImpressions", {
|
||||
"1": [],
|
||||
"2": [],
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("#resetGroupsState", () => {
|
||||
it("should reset all group impressions", async () => {
|
||||
await Router.setState({
|
||||
groups: [{ id: "1" }, { id: "2" }],
|
||||
});
|
||||
await Router.setState({
|
||||
groupImpressions: { "1": [0, 1, 2], "2": [0, 1, 2] },
|
||||
}); // Add impressions for test groups
|
||||
let impressions = Object.values(Router.state.groupImpressions);
|
||||
assert.equal(impressions.filter(i => i.length).length, 2); // Both groups have impressions
|
||||
|
||||
Router.resetGroupsState();
|
||||
impressions = Object.values(Router.state.groupImpressions);
|
||||
|
||||
assert.isEmpty(impressions.filter(i => i.length)); // Both groups now have zero impressions
|
||||
assert.calledWithExactly(Router._storage.set, "groupImpressions", {
|
||||
"1": [],
|
||||
"2": [],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,16 +20,38 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"SNAPSHOT_ADDED_TIMER_DELAY",
|
||||
"browser.places.snapshot.monitorDelayAdded",
|
||||
"browser.places.snapshots.monitorDelayAdded",
|
||||
5000
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"SNAPSHOT_REMOVED_TIMER_DELAY",
|
||||
"browser.places.snapshot.monitorDelayRemoved",
|
||||
"browser.places.snapshots.monitorDelayRemoved",
|
||||
1000
|
||||
);
|
||||
|
||||
// Expiration days for automatic and user managed snapshots.
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"SNAPSHOT_EXPIRE_DAYS",
|
||||
"browser.places.snapshots.expiration.days",
|
||||
210
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"SNAPSHOT_USERMANAGED_EXPIRE_DAYS",
|
||||
"browser.places.snapshots.expiration.userManaged.days",
|
||||
420
|
||||
);
|
||||
// We expire on the next idle after a snapshot was added or removed, and
|
||||
// idle-daily, but we don't want to expire too often or rarely.
|
||||
// Thus we define both a mininum and maximum time in the session among which
|
||||
// we'll expire chunks of snapshots.
|
||||
const EXPIRE_EVERY_MIN_MS = 60 * 60000; // 1 Hour.
|
||||
const EXPIRE_EVERY_MAX_MS = 120 * 60000; // 2 Hours.
|
||||
// The number of snapshots to expire at once.
|
||||
const EXPIRE_CHUNK_SIZE = 10;
|
||||
|
||||
/**
|
||||
* Monitors changes in snapshots (additions, deletions, etc) and triggers
|
||||
* the snapshot group builders to run as necessary.
|
||||
|
@ -81,6 +103,15 @@ const SnapshotMonitor = new (class SnapshotMonitor {
|
|||
*/
|
||||
testGroupBuilders = null;
|
||||
|
||||
/**
|
||||
* The time of the last snapshots expiration.
|
||||
*/
|
||||
#lastExpirationTime = 0;
|
||||
/**
|
||||
* How many snapshots to expire per chunk.
|
||||
*/
|
||||
#expirationChunkSize = EXPIRE_CHUNK_SIZE;
|
||||
|
||||
/**
|
||||
* Internal getter to get the builders used.
|
||||
*
|
||||
|
@ -169,6 +200,81 @@ const SnapshotMonitor = new (class SnapshotMonitor {
|
|||
this.#removedUrls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers expiration of a chunk of snapshots.
|
||||
* We differentiate snapshots depending on whether they are user managed:
|
||||
* 1. manually created by the user
|
||||
* 2. part of a group
|
||||
* TODO: evaluate whether we want to consider user managed only snapshots
|
||||
* that are part of a user curated group, rather than any group.
|
||||
* User managed snapshots will expire if their last interaction is older than
|
||||
* browser.snapshots.expiration.userManaged.days, while others will expire
|
||||
* after browser.snapshots.expiration.days.
|
||||
* Snapshots that have a tombstone (removed_at is set) should not be expired.
|
||||
*
|
||||
* @param {boolean} onIdle
|
||||
* Whether this is running on idle. When it's false expiration is
|
||||
* rescheduled for the next idle.
|
||||
*/
|
||||
async #expireSnapshotsChunk(onIdle = false) {
|
||||
let now = Date.now();
|
||||
if (now - this.#lastExpirationTime < EXPIRE_EVERY_MIN_MS) {
|
||||
return;
|
||||
}
|
||||
let instance = (this._expireInstance = {});
|
||||
let skip = false;
|
||||
if (!onIdle) {
|
||||
// Wait for the next idle.
|
||||
skip = await new Promise(resolve =>
|
||||
ChromeUtils.idleDispatch(deadLine => {
|
||||
// Skip if we couldn't find an idle, unless we're over max waiting time.
|
||||
resolve(
|
||||
deadLine.didTimeout &&
|
||||
now - this.#lastExpirationTime < EXPIRE_EVERY_MAX_MS
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
if (skip || instance != this._expireInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#lastExpirationTime = now;
|
||||
let urls = (
|
||||
await Snapshots.query({
|
||||
includeUserPersisted: false,
|
||||
includeTombstones: false,
|
||||
group: null,
|
||||
lastInteractionBefore: now - SNAPSHOT_EXPIRE_DAYS * 86400000,
|
||||
limit: this.#expirationChunkSize,
|
||||
})
|
||||
).map(s => s.url);
|
||||
if (instance != this._expireInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (urls.length < this.#expirationChunkSize) {
|
||||
// If we couldn't find enough automatic snapshots, check if there's any
|
||||
// user managed ones we can expire.
|
||||
urls.push(
|
||||
...(
|
||||
await Snapshots.query({
|
||||
includeUserPersisted: true,
|
||||
includeTombstones: false,
|
||||
lastInteractionBefore:
|
||||
now - SNAPSHOT_USERMANAGED_EXPIRE_DAYS * 86400000,
|
||||
limit: this.#expirationChunkSize - urls.length,
|
||||
})
|
||||
).map(s => s.url)
|
||||
);
|
||||
}
|
||||
if (instance != this._expireInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Snapshots.delete([...new Set(urls)], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timer ensuring that if the new timeout would occur sooner than the
|
||||
* current target time, the timer is changed to the sooner time.
|
||||
|
@ -188,10 +294,10 @@ const SnapshotMonitor = new (class SnapshotMonitor {
|
|||
}
|
||||
|
||||
this.#currentTargetTime = targetTime;
|
||||
this.#timer = setTimeout(
|
||||
() => this.#triggerBuilders().catch(console.error),
|
||||
timeout
|
||||
);
|
||||
this.#timer = setTimeout(() => {
|
||||
this.#expireSnapshotsChunk().catch(console.error);
|
||||
this.#triggerBuilders().catch(console.error);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -203,12 +309,24 @@ const SnapshotMonitor = new (class SnapshotMonitor {
|
|||
* @param {nsISupports} data
|
||||
*/
|
||||
async observe(subject, topic, data) {
|
||||
if (topic == "places-snapshots-added") {
|
||||
this.#onSnapshotAdded(JSON.parse(data));
|
||||
} else if (topic == "places-snapshots-deleted") {
|
||||
this.#onSnapshotRemoved(JSON.parse(data));
|
||||
} else if (topic == "idle-daily") {
|
||||
await this.#triggerBuilders(true);
|
||||
switch (topic) {
|
||||
case "places-snapshots-added":
|
||||
this.#onSnapshotAdded(JSON.parse(data));
|
||||
break;
|
||||
case "places-snapshots-deleted":
|
||||
this.#onSnapshotRemoved(JSON.parse(data));
|
||||
break;
|
||||
case "idle-daily":
|
||||
await this.#expireSnapshotsChunk(true);
|
||||
await this.#triggerBuilders(true);
|
||||
break;
|
||||
case "test-expiration":
|
||||
this.#lastExpirationTime =
|
||||
subject.lastExpirationTime || this.#lastExpirationTime;
|
||||
this.#expirationChunkSize =
|
||||
subject.expirationChunkSize || this.#expirationChunkSize;
|
||||
await this.#expireSnapshotsChunk(subject.onIdle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -396,33 +396,58 @@ const Snapshots = new (class Snapshots {
|
|||
}
|
||||
|
||||
/**
|
||||
* Deletes a snapshot, creating a tombstone. Note, the caller is expected
|
||||
* to take account of the userPersisted value for a Snapshot when appropriate.
|
||||
* Deletes one or more snapshots.
|
||||
* By default this creates a tombstone rather than removing the entry, so that
|
||||
* heuristics can take into account user removed snapshots.
|
||||
* Note, the caller is expected to take account of the userPersisted value
|
||||
* for a Snapshot when appropriate.
|
||||
*
|
||||
* @param {string} url
|
||||
* The url of the snapshot to delete.
|
||||
* @param {string|Array<string>} urls
|
||||
* The url of the snapshot to delete, or an Array of urls.
|
||||
* @param {boolean} removeFromStore
|
||||
* Whether the snapshot should actually be removed rather than tombston-ed.
|
||||
*/
|
||||
async delete(url) {
|
||||
url = this.stripFragments(url);
|
||||
await PlacesUtils.withConnectionWrapper("Snapshots: delete", async db => {
|
||||
let placeId = (
|
||||
await db.executeCached(
|
||||
async delete(urls, removeFromStore = false) {
|
||||
if (!Array.isArray(urls)) {
|
||||
urls = [urls];
|
||||
}
|
||||
urls = urls.map(this.stripFragments);
|
||||
|
||||
let placeIdsSQLFragment = `
|
||||
SELECT id FROM moz_places
|
||||
WHERE url_hash IN (${PlacesUtils.sqlBindPlaceholders(
|
||||
urls,
|
||||
"hash(",
|
||||
")"
|
||||
)}) AND url IN (${PlacesUtils.sqlBindPlaceholders(urls)})`;
|
||||
let queryArgs = removeFromStore
|
||||
? [
|
||||
`DELETE FROM moz_places_metadata_snapshots
|
||||
WHERE place_id IN (${placeIdsSQLFragment})
|
||||
RETURNING place_id`,
|
||||
[...urls, ...urls],
|
||||
]
|
||||
: [
|
||||
`UPDATE moz_places_metadata_snapshots
|
||||
SET removed_at = :removedAt
|
||||
WHERE place_id = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url)
|
||||
RETURNING place_id`,
|
||||
{ removedAt: Date.now(), url }
|
||||
)
|
||||
)[0].getResultByName("place_id");
|
||||
SET removed_at = ?
|
||||
WHERE place_id IN (${placeIdsSQLFragment})
|
||||
RETURNING place_id`,
|
||||
[Date.now(), ...urls, ...urls],
|
||||
];
|
||||
|
||||
await PlacesUtils.withConnectionWrapper("Snapshots: delete", async db => {
|
||||
let placeIds = (await db.executeCached(...queryArgs)).map(r =>
|
||||
r.getResultByName("place_id")
|
||||
);
|
||||
// Remove orphan page data.
|
||||
await db.executeCached(
|
||||
`DELETE FROM moz_places_metadata_snapshots_extra
|
||||
WHERE place_id = :placeId`,
|
||||
{ placeId }
|
||||
WHERE place_id IN (${PlacesUtils.sqlBindPlaceholders(placeIds)})`,
|
||||
placeIds
|
||||
);
|
||||
});
|
||||
|
||||
this.#notify("places-snapshots-deleted", [url]);
|
||||
this.#notify("places-snapshots-deleted", urls);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -479,10 +504,15 @@ const Snapshots = new (class Snapshots {
|
|||
* @param {number} [options.type]
|
||||
* Restrict the snapshots to those with a particular type of page data available.
|
||||
* @param {number} [options.group]
|
||||
* Restrict the snapshots to those within a particular group.
|
||||
* Restrict the snapshots to those within a particular group. Pass null
|
||||
* to get all the snapshots that are not part of a group.
|
||||
* @param {boolean} [options.includeHiddenInGroup]
|
||||
* Only applies when querying a particular group. Pass true to include
|
||||
* snapshots that are hidden in the group.
|
||||
* @param {boolean} [options.includeUserPersisted]
|
||||
* Whether to include user persisted snapshots.
|
||||
* @param {number} [options.lastInteractionBefore]
|
||||
* Restrict to snaphots whose last interaction was before the given time.
|
||||
* @param {boolean} [options.sortDescending]
|
||||
* Whether or not to sortDescending. Defaults to true.
|
||||
* @param {string} [options.sortBy]
|
||||
|
@ -497,6 +527,8 @@ const Snapshots = new (class Snapshots {
|
|||
type = undefined,
|
||||
group = undefined,
|
||||
includeHiddenInGroup = false,
|
||||
includeUserPersisted = true,
|
||||
lastInteractionBefore = undefined,
|
||||
sortDescending = true,
|
||||
sortBy = "last_interaction_at",
|
||||
} = {}) {
|
||||
|
@ -511,12 +543,26 @@ const Snapshots = new (class Snapshots {
|
|||
clauses.push("removed_at IS NULL");
|
||||
}
|
||||
|
||||
if (!includeUserPersisted) {
|
||||
clauses.push("user_persisted = :user_persisted");
|
||||
bindings.user_persisted = this.USER_PERSISTED.NO;
|
||||
}
|
||||
if (lastInteractionBefore) {
|
||||
clauses.push("last_interaction_at < :last_interaction_before");
|
||||
bindings.last_interaction_before = lastInteractionBefore;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
clauses.push("type = :type");
|
||||
bindings.type = type;
|
||||
}
|
||||
|
||||
if (group) {
|
||||
if (group === null) {
|
||||
clauses.push("group_id IS NULL");
|
||||
joins.push(
|
||||
"LEFT JOIN moz_places_metadata_groups_to_snapshots g USING(place_id)"
|
||||
);
|
||||
} else if (group) {
|
||||
clauses.push("group_id = :group");
|
||||
if (!includeHiddenInGroup) {
|
||||
clauses.push("g.hidden = 0");
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests for the expiration of snapshots.
|
||||
*/
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"SNAPSHOT_EXPIRE_DAYS",
|
||||
"browser.places.snapshots.expiration.days",
|
||||
210
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"SNAPSHOT_USERMANAGED_EXPIRE_DAYS",
|
||||
"browser.places.snapshots.expiration.userManaged.days",
|
||||
420
|
||||
);
|
||||
|
||||
// For each snapshot define its url, whether it should be expired, whether it
|
||||
// should be a tomstone and a type.
|
||||
// The type may be:
|
||||
// - manual: user_persisted
|
||||
// - auto: created automatically by some heuristic
|
||||
// - group: part of a group
|
||||
let gSnapshots = [
|
||||
{
|
||||
url: "https://example.com/1",
|
||||
type: "manual",
|
||||
expired: true,
|
||||
tombstone: false,
|
||||
},
|
||||
{
|
||||
url: "https://example.com/2",
|
||||
type: "manual",
|
||||
expired: false,
|
||||
tombstone: false,
|
||||
},
|
||||
{
|
||||
url: "https://example.com/3",
|
||||
type: "group",
|
||||
expired: true,
|
||||
tombstone: false,
|
||||
},
|
||||
{
|
||||
url: "https://example.com/4",
|
||||
type: "auto",
|
||||
expired: true,
|
||||
tombstone: false,
|
||||
},
|
||||
{
|
||||
url: "https://example.com/5",
|
||||
type: "auto",
|
||||
expired: false,
|
||||
tombstone: false,
|
||||
},
|
||||
{
|
||||
url: "https://example.com/6",
|
||||
type: "auto",
|
||||
expired: true,
|
||||
tombstone: true,
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function setup() {
|
||||
let now = Date.now();
|
||||
let interactions = gSnapshots.map(s => {
|
||||
if (s.expired) {
|
||||
s.created_at =
|
||||
now -
|
||||
(1 + s.type != "auto"
|
||||
? SNAPSHOT_USERMANAGED_EXPIRE_DAYS
|
||||
: SNAPSHOT_EXPIRE_DAYS) *
|
||||
86400000;
|
||||
} else {
|
||||
s.created_at =
|
||||
now - (s.type != "auto" ? SNAPSHOT_EXPIRE_DAYS : 1) * 86400000;
|
||||
}
|
||||
return s;
|
||||
});
|
||||
await addInteractions(interactions);
|
||||
|
||||
let groupSerial = 1;
|
||||
for (let snapshot of gSnapshots) {
|
||||
if (snapshot.type == "manual") {
|
||||
snapshot.userPersisted = Snapshots.USER_PERSISTED.MANUAL;
|
||||
}
|
||||
await Snapshots.add(snapshot);
|
||||
if (snapshot.type == "group") {
|
||||
snapshot.group = await SnapshotGroups.add(
|
||||
{
|
||||
title: `test-group-${groupSerial++}`,
|
||||
builder: "test",
|
||||
},
|
||||
[snapshot.url]
|
||||
);
|
||||
}
|
||||
if (snapshot.tombstone) {
|
||||
await Snapshots.delete(snapshot.url);
|
||||
}
|
||||
}
|
||||
|
||||
Services.prefs.setBoolPref("browser.places.interactions.enabled", true);
|
||||
|
||||
SnapshotMonitor.init();
|
||||
});
|
||||
|
||||
add_task(async function test_idle_expiration() {
|
||||
await SnapshotMonitor.observe({ onIdle: true }, "test-expiration");
|
||||
|
||||
let remaining = await Snapshots.query({ includeTombstones: true });
|
||||
for (let snapshot of gSnapshots) {
|
||||
let index = remaining.findIndex(s => s.url == snapshot.url);
|
||||
if (!snapshot.expired || snapshot.tombstone) {
|
||||
Assert.greater(index, -1, `${snapshot.url} should not have been removed`);
|
||||
remaining.splice(index, 1);
|
||||
} else {
|
||||
Assert.equal(index, -1, `${snapshot.url} should have been removed`);
|
||||
}
|
||||
}
|
||||
Assert.ok(
|
||||
!remaining.length,
|
||||
`All the snapshots should be processed: ${JSON.stringify(remaining)}`
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_active_limited_expiration() {
|
||||
// Add 2 expirable snapshots.
|
||||
let now = Date.now();
|
||||
let expiredSnapshots = [
|
||||
{
|
||||
url: "https://example.com/7",
|
||||
created_at: now - (SNAPSHOT_USERMANAGED_EXPIRE_DAYS + 1) * 86400000,
|
||||
},
|
||||
{
|
||||
url: "https://example.com/8",
|
||||
created_at: now - (SNAPSHOT_USERMANAGED_EXPIRE_DAYS + 1) * 86400000,
|
||||
},
|
||||
];
|
||||
for (let snapshot of expiredSnapshots) {
|
||||
await addInteractions([snapshot]);
|
||||
await Snapshots.add(snapshot);
|
||||
}
|
||||
|
||||
let snapshots = await Snapshots.query({ includeTombstones: true });
|
||||
|
||||
info("expire again without setting lastExpirationTime, should be a no-op");
|
||||
let expirationChunkSize = 1;
|
||||
await SnapshotMonitor.observe(
|
||||
{
|
||||
expirationChunkSize,
|
||||
},
|
||||
"test-expiration"
|
||||
);
|
||||
|
||||
// Since expiration just ran, nothing should have been expired.
|
||||
Assert.equal(
|
||||
(await Snapshots.query({ includeTombstones: true })).length,
|
||||
snapshots.length,
|
||||
"No snapshot should have been expired."
|
||||
);
|
||||
|
||||
info("expire again, for real");
|
||||
await SnapshotMonitor.observe(
|
||||
{
|
||||
expirationChunkSize,
|
||||
lastExpirationTime: now - 24 * 86400000,
|
||||
},
|
||||
"test-expiration"
|
||||
);
|
||||
|
||||
let remaining = await Snapshots.query({ includeTombstones: true });
|
||||
|
||||
let count = 0;
|
||||
for (let snapshot of expiredSnapshots) {
|
||||
let index = remaining.findIndex(s => s.url == snapshot.url);
|
||||
if (index == -1) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Assert.equal(
|
||||
count,
|
||||
expiredSnapshots.length - expirationChunkSize,
|
||||
"Check the expected number of snapshots have been expired"
|
||||
);
|
||||
});
|
|
@ -20,6 +20,7 @@ skip-if = toolkit == 'android' # bug 1730213
|
|||
[test_snapshots_common_referrer_queries.js]
|
||||
[test_snapshots_create_allow_protocols.js]
|
||||
[test_snapshots_create_criteria.js]
|
||||
[test_snapshots_expiration.js]
|
||||
[test_snapshots_fragments.js]
|
||||
[test_snapshots_overlapping_queries.js]
|
||||
[test_snapshots_pagedata.js]
|
||||
|
|
|
@ -13,7 +13,7 @@ async function setupRegions(home, current) {
|
|||
}
|
||||
|
||||
add_task(async function test_focus_promo_in_allowed_region() {
|
||||
await ASRouter._resetMessageState();
|
||||
ASRouter.resetMessageState();
|
||||
|
||||
const allowedRegion = "ES"; // Spain
|
||||
setupRegions(allowedRegion, allowedRegion);
|
||||
|
@ -31,7 +31,7 @@ add_task(async function test_focus_promo_in_allowed_region() {
|
|||
});
|
||||
|
||||
add_task(async function test_focus_promo_in_disallowed_region() {
|
||||
await ASRouter._resetMessageState();
|
||||
ASRouter.resetMessageState();
|
||||
|
||||
const disallowedRegion = "CN"; // China
|
||||
setupRegions(disallowedRegion);
|
||||
|
|
|
@ -340,35 +340,38 @@ var SessionFileInternal = {
|
|||
return result;
|
||||
},
|
||||
|
||||
// Initialize SessionWriter.
|
||||
// This should be called _before_ any other methods on SessionWriter (see
|
||||
// `_callWriter()`).
|
||||
_initWriter() {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
}
|
||||
// Initialize SessionWriter and return it as a resolved promise.
|
||||
getWriter() {
|
||||
if (!this._initialized) {
|
||||
if (!this._readOrigin) {
|
||||
return Promise.reject(
|
||||
"SessionFileInternal.getWriter() called too early! Please read the session file from disk first."
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._readOrigin) {
|
||||
throw new Error(
|
||||
"_initWriter called too early! Please read the session file from disk first."
|
||||
this._initialized = true;
|
||||
SessionWriter.init(
|
||||
this._readOrigin,
|
||||
this._usingOldExtension,
|
||||
this.Paths,
|
||||
{
|
||||
maxUpgradeBackups: Services.prefs.getIntPref(
|
||||
PREF_MAX_UPGRADE_BACKUPS,
|
||||
3
|
||||
),
|
||||
maxSerializeBack: Services.prefs.getIntPref(
|
||||
PREF_MAX_SERIALIZE_BACK,
|
||||
10
|
||||
),
|
||||
maxSerializeForward: Services.prefs.getIntPref(
|
||||
PREF_MAX_SERIALIZE_FWD,
|
||||
-1
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this._initialized = true;
|
||||
SessionWriter.init(this._readOrigin, this._usingOldExtension, this.Paths, {
|
||||
maxUpgradeBackups: Services.prefs.getIntPref(PREF_MAX_UPGRADE_BACKUPS, 3),
|
||||
maxSerializeBack: Services.prefs.getIntPref(PREF_MAX_SERIALIZE_BACK, 10),
|
||||
maxSerializeForward: Services.prefs.getIntPref(
|
||||
PREF_MAX_SERIALIZE_FWD,
|
||||
-1
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
// Call a method of SessionWriter, making sure that it has been initialized first.
|
||||
_callWriter(method, args = []) {
|
||||
this._initWriter();
|
||||
return SessionWriter[method](...args);
|
||||
return Promise.resolve(SessionWriter);
|
||||
},
|
||||
|
||||
write(aData) {
|
||||
|
@ -388,7 +391,7 @@ var SessionFileInternal = {
|
|||
|
||||
this._attempts++;
|
||||
let options = { isFinalWrite, performShutdownCleanup };
|
||||
let promise = this._callWriter("write", [aData, options]);
|
||||
let promise = this.getWriter().then(writer => writer.write(aData, options));
|
||||
|
||||
// Wait until the write is done.
|
||||
promise = promise.then(
|
||||
|
@ -446,12 +449,12 @@ var SessionFileInternal = {
|
|||
});
|
||||
},
|
||||
|
||||
wipe() {
|
||||
return this._callWriter("wipe").then(() => {
|
||||
// After a wipe, we need to make sure to re-initialize upon the next read(),
|
||||
// because the state variables as sent to the writer have changed.
|
||||
this._initialized = false;
|
||||
});
|
||||
async wipe() {
|
||||
const writer = await this.getWriter();
|
||||
await writer.wipe();
|
||||
// After a wipe, we need to make sure to re-initialize upon the next read(),
|
||||
// because the state variables as sent to the writer have changed.
|
||||
this._initialized = false;
|
||||
},
|
||||
|
||||
_recordTelemetry(telemetry) {
|
||||
|
|
|
@ -228,6 +228,20 @@ const PREF_URLBAR_DEFAULTS = new Map([
|
|||
// sponsored and recommended results related to the user's search string.
|
||||
["quicksuggest.enabled", false],
|
||||
|
||||
// Whether non-sponsored quick suggest results are subject to impression
|
||||
// frequency caps. This pref is a fallback for the Nimbus variable
|
||||
// `quickSuggestImpressionCapsNonSponsoredEnabled`.
|
||||
["quicksuggest.impressionCaps.nonSponsoredEnabled", false],
|
||||
|
||||
// Whether sponsored quick suggest results are subject to impression frequency
|
||||
// caps. This pref is a fallback for the Nimbus variable
|
||||
// `quickSuggestImpressionCapsSponsoredEnabled`.
|
||||
["quicksuggest.impressionCaps.sponsoredEnabled", false],
|
||||
|
||||
// JSON'ed object of quick suggest impression stats. Used for implementing
|
||||
// impression frequency caps for quick suggest suggestions.
|
||||
["quicksuggest.impressionCaps.stats", ""],
|
||||
|
||||
// Whether to show QuickSuggest related logs.
|
||||
["quicksuggest.log", false],
|
||||
|
||||
|
|
|
@ -65,6 +65,21 @@ const TELEMETRY_SCALARS = {
|
|||
|
||||
const TELEMETRY_EVENT_CATEGORY = "contextservices.quicksuggest";
|
||||
|
||||
// This object maps impression stats object keys to their corresponding keys in
|
||||
// the `extra` object of impression cap telemetry events. The main reason this
|
||||
// is necessary is because the keys of the `extra` object are limited to 15
|
||||
// characters in length, which some stats object keys exceed. It also forces us
|
||||
// to be deliberate about keys we add to the `extra` object, since the `extra`
|
||||
// object is limited to 10 keys.
|
||||
let TELEMETRY_IMPRESSION_CAP_EXTRA_KEYS = {
|
||||
// stats object key -> `extra` telemetry event object key
|
||||
intervalSeconds: "intervalSeconds",
|
||||
startDateMs: "startDate",
|
||||
count: "count",
|
||||
maxCount: "maxCount",
|
||||
impressionDateMs: "impressionDate",
|
||||
};
|
||||
|
||||
// Identifies the source of the QuickSuggest suggestion.
|
||||
const QUICK_SUGGEST_SOURCE = {
|
||||
REMOTE_SETTINGS: "remote-settings",
|
||||
|
@ -78,9 +93,14 @@ const QUICK_SUGGEST_SOURCE = {
|
|||
class ProviderQuickSuggest extends UrlbarProvider {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this._updateExperimentState();
|
||||
|
||||
UrlbarQuickSuggest.init();
|
||||
UrlbarQuickSuggest.on("config-set", () => this._validateImpressionStats());
|
||||
|
||||
this._updateFeatureState();
|
||||
NimbusFeatures.urlbar.onUpdate(() => this._updateFeatureState());
|
||||
|
||||
UrlbarPrefs.addObserver(this);
|
||||
NimbusFeatures.urlbar.onUpdate(() => this._updateExperimentState());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,6 +214,16 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
promises.push(this._fetchMerinoSuggestions(queryContext, searchString));
|
||||
}
|
||||
|
||||
// While we're waiting on suggestions, opportunistically reset elapsed
|
||||
// impression counters and record "reset" telemetry as appropriate. If we
|
||||
// didn't need to record telemetry for periods with no impressions, then we
|
||||
// could simply reset elapsed counters on each impression instead of doing
|
||||
// it here. But since we do need to record telemetry for periods with no
|
||||
// impressions, we need to reset counters more often. Doing it here means no
|
||||
// telemetry will be recorded as long as the user doesn't do any searches,
|
||||
// but the alternative is to use one or more long-lived timers.
|
||||
this._resetElapsedImpressionCounters();
|
||||
|
||||
// Wait for both sources to finish before adding a suggestion.
|
||||
let allSuggestions = await Promise.all(promises);
|
||||
if (instance != this.queryInstance) {
|
||||
|
@ -421,7 +451,7 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
}
|
||||
this._addedResultInLastQuery = false;
|
||||
|
||||
// Per spec, we update telemetry only when the user picks a result, i.e.,
|
||||
// Per spec, we count impressions only when the user picks a result, i.e.,
|
||||
// when `state` is "engagement".
|
||||
if (state != "engagement") {
|
||||
return;
|
||||
|
@ -444,6 +474,9 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
result = queryContext.results[resultIndex];
|
||||
}
|
||||
|
||||
// Update impression stats.
|
||||
this._updateImpressionStats(result.payload.isSponsored);
|
||||
|
||||
// Record telemetry. We want to record the 1-based index of the result, so
|
||||
// add 1 to the 0-based resultIndex.
|
||||
let telemetryResultIndex = resultIndex + 1;
|
||||
|
@ -546,11 +579,17 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
onPrefChanged(pref) {
|
||||
switch (pref) {
|
||||
case "quickSuggest.blockedDigests":
|
||||
this.logger.debug(
|
||||
"browser.urlbar.quickSuggest.blockedDigests changed, loading digests"
|
||||
);
|
||||
this.logger.info("browser.urlbar.quickSuggest.blockedDigests changed");
|
||||
this._loadBlockedDigests();
|
||||
break;
|
||||
case "quicksuggest.impressionCaps.stats":
|
||||
if (!this._updatingImpressionStats) {
|
||||
this.logger.info(
|
||||
"browser.urlbar.quicksuggest.impressionCaps.stats changed"
|
||||
);
|
||||
this._loadImpressionStats();
|
||||
}
|
||||
break;
|
||||
case "quicksuggest.dataCollection.enabled":
|
||||
if (!UrlbarPrefs.updatingFirefoxSuggestScenario) {
|
||||
Services.telemetry.recordEvent(
|
||||
|
@ -841,18 +880,51 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
* provider itself should be active.
|
||||
*
|
||||
* @param {object} suggestion
|
||||
* A suggestion object fetched from UrlbarQuickSuggest.
|
||||
* @returns {boolean}
|
||||
* Whether the suggestion can be added.
|
||||
*/
|
||||
async _canAddSuggestion(suggestion) {
|
||||
return (
|
||||
((suggestion.is_sponsored &&
|
||||
UrlbarPrefs.get("suggest.quicksuggest.sponsored")) ||
|
||||
(!suggestion.is_sponsored &&
|
||||
UrlbarPrefs.get("suggest.quicksuggest.nonsponsored"))) &&
|
||||
!(await this.isSuggestionBlocked(suggestion.url))
|
||||
);
|
||||
this.logger.info("Checking if suggestion can be added");
|
||||
this.logger.debug(JSON.stringify({ suggestion }));
|
||||
|
||||
// Return false if suggestions are disabled.
|
||||
if (
|
||||
(suggestion.is_sponsored &&
|
||||
!UrlbarPrefs.get("suggest.quicksuggest.sponsored")) ||
|
||||
(!suggestion.is_sponsored &&
|
||||
!UrlbarPrefs.get("suggest.quicksuggest.nonsponsored"))
|
||||
) {
|
||||
this.logger.info("Suggestions disabled, not adding suggestion");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return false if an impression cap has been hit.
|
||||
if (
|
||||
(suggestion.is_sponsored &&
|
||||
UrlbarPrefs.get("quickSuggestImpressionCapsSponsoredEnabled")) ||
|
||||
(!suggestion.is_sponsored &&
|
||||
UrlbarPrefs.get("quickSuggestImpressionCapsNonSponsoredEnabled"))
|
||||
) {
|
||||
let type = suggestion.is_sponsored ? "sponsored" : "nonsponsored";
|
||||
let stats = this._impressionStats?.[type];
|
||||
if (stats) {
|
||||
let hitStats = stats.filter(s => s.maxCount <= s.count);
|
||||
if (hitStats.length) {
|
||||
this.logger.info("Impression cap(s) hit, not adding suggestion");
|
||||
this.logger.debug(JSON.stringify({ type, hitStats }));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return false if the suggestion is blocked.
|
||||
if (await this.isSuggestionBlocked(suggestion.url)) {
|
||||
this.logger.info("Suggestion blocked, not adding suggestion");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.info("Suggestion can be added");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -897,6 +969,322 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the user's impression stats counters for the given type of
|
||||
* suggestion. This should be called only when a suggestion impression is
|
||||
* recorded.
|
||||
*
|
||||
* @param {boolean} isSponsored
|
||||
* Whether the impression was recorded for a sponsored suggestion.
|
||||
*/
|
||||
_updateImpressionStats(isSponsored) {
|
||||
this.logger.info("Starting impression stats update");
|
||||
this.logger.debug(
|
||||
JSON.stringify({
|
||||
isSponsored,
|
||||
currentStats: this._impressionStats,
|
||||
impression_caps: UrlbarQuickSuggest.config.impression_caps,
|
||||
})
|
||||
);
|
||||
|
||||
// Don't bother recording anything if caps are disabled.
|
||||
if (
|
||||
(isSponsored &&
|
||||
!UrlbarPrefs.get("quickSuggestImpressionCapsSponsoredEnabled")) ||
|
||||
(!isSponsored &&
|
||||
!UrlbarPrefs.get("quickSuggestImpressionCapsNonSponsoredEnabled"))
|
||||
) {
|
||||
this.logger.info("Impression caps disabled, skipping update");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user's impression stats. Since stats are synced from caps, if the
|
||||
// stats don't exist then the caps don't exist, and don't bother recording
|
||||
// anything in that case.
|
||||
let type = isSponsored ? "sponsored" : "nonsponsored";
|
||||
let stats = this._impressionStats?.[type];
|
||||
if (!stats) {
|
||||
this.logger.info("Impression caps undefined, skipping update");
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment counters.
|
||||
for (let stat of stats) {
|
||||
stat.count++;
|
||||
stat.impressionDateMs = Date.now();
|
||||
|
||||
// Record a telemetry event for each newly hit cap.
|
||||
if (stat.count == stat.maxCount) {
|
||||
this.logger.info(`'${type}' impression cap hit`);
|
||||
this.logger.debug(JSON.stringify({ type, hitStat: stat }));
|
||||
this._recordImpressionCapEvent({
|
||||
stat,
|
||||
eventType: "hit",
|
||||
suggestionType: type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Save the stats.
|
||||
this._updatingImpressionStats = true;
|
||||
try {
|
||||
UrlbarPrefs.set(
|
||||
"quicksuggest.impressionCaps.stats",
|
||||
JSON.stringify(this._impressionStats)
|
||||
);
|
||||
} finally {
|
||||
this._updatingImpressionStats = false;
|
||||
}
|
||||
|
||||
this.logger.info("Finished impression stats update");
|
||||
this.logger.debug(JSON.stringify({ newStats: this._impressionStats }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and validates impression stats.
|
||||
*/
|
||||
_loadImpressionStats() {
|
||||
let json = UrlbarPrefs.get("quicksuggest.impressionCaps.stats");
|
||||
if (!json) {
|
||||
this._impressionStats = null;
|
||||
} else {
|
||||
try {
|
||||
this._impressionStats = JSON.parse(
|
||||
json,
|
||||
// Infinity, which is the `intervalSeconds` for the lifetime cap, is
|
||||
// stringified as `null` in the JSON, so convert it back to Infinity.
|
||||
(key, value) =>
|
||||
key == "intervalSeconds" && value === null ? Infinity : value
|
||||
);
|
||||
} catch (error) {}
|
||||
}
|
||||
this._validateImpressionStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates impression stats, which includes two things:
|
||||
*
|
||||
* - Type checks stats and discards any that are invalid. We do this because
|
||||
* stats are stored in prefs where anyone can modify them.
|
||||
* - Syncs stats with impression caps so that there is one stats object
|
||||
* corresponding to each impression cap. See the `_impressionStats` comment
|
||||
* for more info.
|
||||
*/
|
||||
_validateImpressionStats() {
|
||||
let { impression_caps } = UrlbarQuickSuggest.config;
|
||||
|
||||
this.logger.info("Validating impression stats");
|
||||
this.logger.debug(
|
||||
JSON.stringify({
|
||||
impression_caps,
|
||||
currentStats: this._impressionStats,
|
||||
})
|
||||
);
|
||||
|
||||
if (!this._impressionStats || typeof this._impressionStats != "object") {
|
||||
this._impressionStats = {};
|
||||
}
|
||||
|
||||
for (let [type, cap] of Object.entries(impression_caps || {})) {
|
||||
// Build a map from interval seconds to max counts in the caps.
|
||||
let maxCapCounts = (cap.custom || []).reduce(
|
||||
(map, { interval_s, max_count }) => {
|
||||
map.set(interval_s, max_count);
|
||||
return map;
|
||||
},
|
||||
new Map()
|
||||
);
|
||||
if (typeof cap.lifetime == "number") {
|
||||
maxCapCounts.set(Infinity, cap.lifetime);
|
||||
}
|
||||
|
||||
let stats = this._impressionStats[type];
|
||||
if (!Array.isArray(stats)) {
|
||||
stats = [];
|
||||
this._impressionStats[type] = stats;
|
||||
}
|
||||
|
||||
// Validate existing stats:
|
||||
//
|
||||
// * Discard stats with invalid properties.
|
||||
// * Collect and remove stats with intervals that aren't in the caps. This
|
||||
// should only happen when caps are changed or removed.
|
||||
// * For stats with intervals that are in the caps:
|
||||
// * Keep track of the max `stat.count` across all stats so we can
|
||||
// update the lifetime stat below.
|
||||
// * Set `stat.maxCount` to the max count in the corresponding cap.
|
||||
let orphanStats = [];
|
||||
let maxCountInStats = 0;
|
||||
for (let i = 0; i < stats.length; ) {
|
||||
let stat = stats[i];
|
||||
if (
|
||||
typeof stat.intervalSeconds != "number" ||
|
||||
typeof stat.startDateMs != "number" ||
|
||||
typeof stat.count != "number" ||
|
||||
typeof stat.maxCount != "number" ||
|
||||
typeof stat.impressionDateMs != "number"
|
||||
) {
|
||||
stats.splice(i, 1);
|
||||
} else {
|
||||
maxCountInStats = Math.max(maxCountInStats, stat.count);
|
||||
let maxCount = maxCapCounts.get(stat.intervalSeconds);
|
||||
if (maxCount === undefined) {
|
||||
stats.splice(i, 1);
|
||||
orphanStats.push(stat);
|
||||
} else {
|
||||
stat.maxCount = maxCount;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create stats for caps that don't already have corresponding stats.
|
||||
for (let [intervalSeconds, maxCount] of maxCapCounts.entries()) {
|
||||
if (!stats.some(s => s.intervalSeconds == intervalSeconds)) {
|
||||
stats.push({
|
||||
maxCount,
|
||||
intervalSeconds,
|
||||
startDateMs: Date.now(),
|
||||
count: 0,
|
||||
impressionDateMs: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Merge orphaned stats into other ones if possible. For each orphan, if
|
||||
// its interval is no bigger than an existing stat's interval, then the
|
||||
// orphan's count can contribute to the existing stat's count, so merge
|
||||
// the two.
|
||||
for (let orphan of orphanStats) {
|
||||
for (let stat of stats) {
|
||||
if (orphan.intervalSeconds <= stat.intervalSeconds) {
|
||||
stat.count = Math.max(stat.count, orphan.count);
|
||||
stat.startDateMs = Math.min(stat.startDateMs, orphan.startDateMs);
|
||||
stat.impressionDateMs = Math.max(
|
||||
stat.impressionDateMs,
|
||||
orphan.impressionDateMs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the lifetime stat exists, make its count the max count found above.
|
||||
// This is only necessary when the lifetime cap wasn't present before, but
|
||||
// it doesn't hurt to always do it.
|
||||
let lifetimeStat = stats.find(s => s.intervalSeconds == Infinity);
|
||||
if (lifetimeStat) {
|
||||
lifetimeStat.count = maxCountInStats;
|
||||
}
|
||||
|
||||
// Sort the stats by interval ascending. This isn't necessary except that
|
||||
// it guarantees an ordering for tests.
|
||||
stats.sort((a, b) => a.intervalSeconds - b.intervalSeconds);
|
||||
}
|
||||
|
||||
this.logger.debug(JSON.stringify({ newStats: this._impressionStats }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the counters of impression stats whose intervals have elapased.
|
||||
*/
|
||||
_resetElapsedImpressionCounters() {
|
||||
this.logger.info("Checking for elapsed impression cap intervals");
|
||||
this.logger.debug(
|
||||
JSON.stringify({
|
||||
currentStats: this._impressionStats,
|
||||
impression_caps: UrlbarQuickSuggest.config.impression_caps,
|
||||
})
|
||||
);
|
||||
|
||||
let now = Date.now();
|
||||
for (let [type, stats] of Object.entries(this._impressionStats)) {
|
||||
for (let stat of stats) {
|
||||
let elapsedMs = now - stat.startDateMs;
|
||||
let intervalMs = 1000 * stat.intervalSeconds;
|
||||
let elapsedIntervalCount = Math.floor(elapsedMs / intervalMs);
|
||||
if (elapsedIntervalCount) {
|
||||
this.logger.info(
|
||||
`Resetting impression counter for interval ${stat.intervalSeconds}s`
|
||||
);
|
||||
this.logger.debug(
|
||||
JSON.stringify({ type, stat, elapsedMs, elapsedIntervalCount })
|
||||
);
|
||||
|
||||
// Record a telemetry event for each elapsed interval period.
|
||||
let startDateMs = stat.startDateMs;
|
||||
for (let i = 0; i < elapsedIntervalCount; i++) {
|
||||
let endDateMs = startDateMs + intervalMs;
|
||||
this._recordImpressionCapEvent({
|
||||
eventType: "reset",
|
||||
suggestionType: type,
|
||||
eventDateMs: endDateMs,
|
||||
stat: {
|
||||
...stat,
|
||||
startDateMs,
|
||||
// There were `stat.count` impressions in the first elapsed
|
||||
// period and zero in all subsequent periods because if that
|
||||
// were not the case then we would have recorded telemetry for
|
||||
// the subsequent impression(s).
|
||||
count: i == 0 ? stat.count : 0,
|
||||
},
|
||||
});
|
||||
startDateMs += intervalMs;
|
||||
}
|
||||
|
||||
// Reset the stat.
|
||||
let remainderMs = elapsedMs - elapsedIntervalCount * intervalMs;
|
||||
stat.startDateMs = now - remainderMs;
|
||||
stat.count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug(JSON.stringify({ newStats: this._impressionStats }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an impression cap telemetry event.
|
||||
*
|
||||
* @param {string} eventType
|
||||
* One of: "hit", "reset"
|
||||
* @param {string} suggestionType
|
||||
* One of: "sponsored", "nonsponsored"
|
||||
* @param {object} stat
|
||||
* The stats object whose max count was hit or whose counter was reset.
|
||||
* @param {number} eventDateMs
|
||||
* The `eventDate` that should be recorded in the event's `extra` object.
|
||||
* We include this in `extra` even though events are timestamped because
|
||||
* "reset" events are batched during periods where the user doesn't perform
|
||||
* any searches and therefore impression counters are not reset.
|
||||
*/
|
||||
_recordImpressionCapEvent({
|
||||
eventType,
|
||||
suggestionType,
|
||||
stat,
|
||||
eventDateMs = Date.now(),
|
||||
}) {
|
||||
// All `extra` object values must be strings.
|
||||
let extra = {
|
||||
type: suggestionType,
|
||||
eventDate: String(eventDateMs),
|
||||
endDate: String(stat.startDateMs + 1000 * stat.intervalSeconds),
|
||||
};
|
||||
for (let [statKey, value] of Object.entries(stat)) {
|
||||
let extraKey = TELEMETRY_IMPRESSION_CAP_EXTRA_KEYS[statKey];
|
||||
if (!extraKey) {
|
||||
throw new Error("Unrecognized stats object key: " + statKey);
|
||||
}
|
||||
extra[extraKey] = String(value);
|
||||
}
|
||||
Services.telemetry.recordEvent(
|
||||
TELEMETRY_EVENT_CATEGORY,
|
||||
"impression_cap",
|
||||
eventType,
|
||||
"",
|
||||
extra
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads blocked suggestion digests from the pref into `_blockedDigests`.
|
||||
*/
|
||||
|
@ -940,27 +1328,86 @@ class ProviderQuickSuggest extends UrlbarProvider {
|
|||
|
||||
/**
|
||||
* Updates state based on the `browser.urlbar.quicksuggest.enabled` pref.
|
||||
* Enable/disable event telemetry and ensure QuickSuggest module is loaded
|
||||
* when enabled.
|
||||
*/
|
||||
_updateExperimentState() {
|
||||
_updateFeatureState() {
|
||||
let enabled = UrlbarPrefs.get("quickSuggestEnabled");
|
||||
if (enabled == this._quickSuggestEnabled) {
|
||||
// This method is a Nimbus `onUpdate()` callback, which means it's called
|
||||
// each time any pref is changed that is a fallback for a Nimbus variable.
|
||||
// We have many such prefs. The point of this method is to set up and tear
|
||||
// down state when quick suggest's enabled status changes, so ignore
|
||||
// updates that do not modify `quickSuggestEnabled`.
|
||||
return;
|
||||
}
|
||||
|
||||
this._quickSuggestEnabled = enabled;
|
||||
this.logger.info("Updating feature state, feature enabled: " + enabled);
|
||||
|
||||
Services.telemetry.setEventRecordingEnabled(
|
||||
TELEMETRY_EVENT_CATEGORY,
|
||||
UrlbarPrefs.get("quickSuggestEnabled")
|
||||
enabled
|
||||
);
|
||||
|
||||
// QuickSuggest is only loaded by the UrlBar on it's first query, however
|
||||
// there is work it can preload when idle instead of starting it on user
|
||||
// input. Referencing it here will trigger its import and init.
|
||||
if (UrlbarPrefs.get("quickSuggestEnabled")) {
|
||||
UrlbarQuickSuggest; // eslint-disable-line no-unused-expressions
|
||||
if (enabled) {
|
||||
this._loadImpressionStats();
|
||||
this._loadBlockedDigests();
|
||||
}
|
||||
}
|
||||
|
||||
// The most recently cached value of `UrlbarPrefs.get("quickSuggestEnabled")`.
|
||||
// The purpose of this property is only to detect changes in the feature's
|
||||
// enabled status. To determine the current status, call
|
||||
// `UrlbarPrefs.get("quickSuggestEnabled")` directly instead.
|
||||
_quickSuggestEnabled = false;
|
||||
|
||||
// Whether we added a result during the most recent query.
|
||||
_addedResultInLastQuery = false;
|
||||
|
||||
// An object that keeps track of impression stats per sponsored and
|
||||
// non-sponsored suggestion types. It looks like this:
|
||||
//
|
||||
// { sponsored: statsArray, nonsponsored: statsArray }
|
||||
//
|
||||
// The `statsArray` values are arrays of stats objects, one per impression
|
||||
// cap, which look like this:
|
||||
//
|
||||
// { intervalSeconds, startDateMs, count, maxCount, impressionDateMs }
|
||||
//
|
||||
// {number} intervalSeconds
|
||||
// The number of seconds in the corresponding cap's time interval.
|
||||
// {number} startDateMs
|
||||
// The timestamp at which the current interval period started and the
|
||||
// object's `count` was reset to zero. This is a value returned from
|
||||
// `Date.now()`. When the current date/time advances past `startDateMs +
|
||||
// 1000 * intervalSeconds`, a new interval period will start and `count`
|
||||
// will be reset to zero.
|
||||
// {number} count
|
||||
// The number of impressions during the current interval period.
|
||||
// {number} maxCount
|
||||
// The maximum number of impressions allowed during an interval period.
|
||||
// This value is the same as the `max_count` value in the corresponding
|
||||
// cap. It's stored in the stats object for convenience.
|
||||
// {number} impressionDateMs
|
||||
// The timestamp of the most recent impression, i.e., when `count` was
|
||||
// last incremented.
|
||||
//
|
||||
// There are two types of impression caps: interval and lifetime. Interval
|
||||
// caps are periodically reset, and lifetime caps are never reset. For stats
|
||||
// objects corresponding to interval caps, `intervalSeconds` will be the
|
||||
// `interval_s` value of the cap. For stats objects corresponding to lifetime
|
||||
// caps, `intervalSeconds` will be `Infinity`.
|
||||
//
|
||||
// `_impressionStats` is kept in sync with impression caps, and there is a
|
||||
// one-to-one relationship between stats objects and caps. A stats object's
|
||||
// corresponding cap is the one with the same suggestion type (sponsored or
|
||||
// non-sponsored) and interval. See `_validateImpressionStats()` for more.
|
||||
//
|
||||
// Impression caps are stored in the remote settings config. See
|
||||
// `UrlbarQuickSuggest.confg.impression_caps`.
|
||||
_impressionStats = null;
|
||||
|
||||
// Whether impression stats are currently being updated.
|
||||
_updatingImpressionStats = false;
|
||||
|
||||
// Set of digests of the original URLs of blocked suggestions. A suggestion's
|
||||
// "original URL" is its URL straight from the source with an unreplaced
|
||||
// timestamp template. For details on the digests, see `_getDigest()`.
|
||||
|
|
|
@ -12,6 +12,7 @@ const { XPCOMUtils } = ChromeUtils.import(
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
|
||||
EventEmitter: "resource://gre/modules/EventEmitter.jsm",
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
|
||||
QUICK_SUGGEST_SOURCE: "resource:///modules/UrlbarProviderQuickSuggest.jsm",
|
||||
RemoteSettings: "resource://services-settings/remote-settings.js",
|
||||
|
@ -66,8 +67,8 @@ const SUGGESTION_SCORE = 0.2;
|
|||
* Fetches the suggestions data from RemoteSettings and builds the structures
|
||||
* to provide suggestions for UrlbarProviderQuickSuggest.
|
||||
*/
|
||||
class Suggestions {
|
||||
constructor() {
|
||||
class QuickSuggest extends EventEmitter {
|
||||
init() {
|
||||
UrlbarPrefs.addObserver(this);
|
||||
NimbusFeatures.urlbar.onUpdate(() => this._queueSettingsSetup());
|
||||
|
||||
|
@ -102,11 +103,26 @@ class Suggestions {
|
|||
/**
|
||||
* @returns {object}
|
||||
* Global quick suggest configuration from remote settings:
|
||||
*
|
||||
* {
|
||||
* best_match: {
|
||||
* min_search_string_length,
|
||||
* blocked_suggestion_ids,
|
||||
* },
|
||||
* impression_caps: {
|
||||
* nonsponsored: {
|
||||
* lifetime,
|
||||
* custom: [
|
||||
* { interval_s, max_count },
|
||||
* ],
|
||||
* },
|
||||
* sponsored: {
|
||||
* lifetime,
|
||||
* custom: [
|
||||
* { interval_s, max_count },
|
||||
* ],
|
||||
* },
|
||||
* },
|
||||
* }
|
||||
*/
|
||||
get config() {
|
||||
|
@ -349,7 +365,7 @@ class Suggestions {
|
|||
// or initialization is ongoing; see `readyPromise`.
|
||||
_settingsTaskQueue = new TaskQueue();
|
||||
|
||||
// Configuration data synced from remote settings.
|
||||
// Configuration data synced from remote settings. See the `config` getter.
|
||||
_config = {};
|
||||
|
||||
// Maps from keywords to their corresponding results. Keywords are unique in
|
||||
|
@ -409,7 +425,7 @@ class Suggestions {
|
|||
]);
|
||||
|
||||
log.debug("Got configuration:", configArray);
|
||||
this._config = configArray?.[0]?.configuration || {};
|
||||
this._setConfig(configArray?.[0]?.configuration || {});
|
||||
|
||||
this._resultsByKeyword.clear();
|
||||
|
||||
|
@ -423,6 +439,16 @@ class Suggestions {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quick suggest config and emits a "config-set" event.
|
||||
*
|
||||
* @param {object} config
|
||||
*/
|
||||
_setConfig(config) {
|
||||
this._config = config || {};
|
||||
this.emit("config-set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list of result objects to the results map. This method is also used
|
||||
* by tests to set up mock suggestions.
|
||||
|
@ -460,4 +486,4 @@ class Suggestions {
|
|||
}
|
||||
}
|
||||
|
||||
let UrlbarQuickSuggest = new Suggestions();
|
||||
let UrlbarQuickSuggest = new QuickSuggest();
|
||||
|
|
|
@ -333,6 +333,52 @@ Changelog
|
|||
.. _1735976: https://bugzilla.mozilla.org/show_bug.cgi?id=1735976
|
||||
.. _1740965: https://bugzilla.mozilla.org/show_bug.cgi?id=1740965
|
||||
|
||||
contextservices.quicksuggest.impression_cap
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This event is recorded when an event related to an impression cap occurs. The
|
||||
event's objects are the following possible values:
|
||||
|
||||
:hit:
|
||||
Recorded when an impression cap is hit.
|
||||
:reset:
|
||||
Recorded when a cap's counter is reset because its interval period has
|
||||
elapsed.
|
||||
|
||||
The event's ``extra`` object value contains the following properties:
|
||||
|
||||
:count:
|
||||
The number of impressions during the cap's interval period.
|
||||
:endDate:
|
||||
The timestamp at which the cap's interval period will end (for "hit" events)
|
||||
or did end (for "reset" events), in number of milliseconds since Unix epoch.
|
||||
For lifetime caps, this value will be "Infinity".
|
||||
:eventDate:
|
||||
The event's timestamp, in number of milliseconds since Unix epoch. For "reset"
|
||||
events, this may be earlier than the timestamp on the event itself because the
|
||||
implementation sometimes batches and records these events at a later date.
|
||||
This ``eventDate`` value should be preferred over the timestamp on the event
|
||||
itself.
|
||||
:impressionDate:
|
||||
The timestamp of the most recent impression, in number of milliseconds since
|
||||
Unix epoch.
|
||||
:intervalSeconds:
|
||||
The number of seconds in the cap's interval period. For lifetime caps, this
|
||||
value will be "Infinity".
|
||||
:maxCount:
|
||||
The maximum number of impressions allowed in the cap's interval period.
|
||||
:startDate:
|
||||
The timestamp at which the cap's interval period started, in number of seconds
|
||||
since Unix epoch.
|
||||
:type:
|
||||
The type of cap, one of: "sponsored", "nonsponsored"
|
||||
|
||||
Changelog
|
||||
Firefox 101.0
|
||||
Introduced. [Bug 1761058_]
|
||||
|
||||
.. _1761058: https://bugzilla.mozilla.org/show_bug.cgi?id=1761058
|
||||
|
||||
contextservices.quicksuggest.opt_in_dialog
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -201,7 +201,7 @@ class QSTestUtils {
|
|||
* @param {object} config
|
||||
*/
|
||||
setConfig(config) {
|
||||
UrlbarQuickSuggest._config = config;
|
||||
UrlbarQuickSuggest._setConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,23 +25,23 @@ add_task(async function init() {
|
|||
await QuickSuggestTestUtils.ensureQuickSuggestInit();
|
||||
});
|
||||
|
||||
// Makes sure `UrlbarProviderQuickSuggest._updateExperimentState()` is called
|
||||
// Makes sure `UrlbarProviderQuickSuggest._updateFeatureState()` is called
|
||||
// when the `browser.urlbar.quicksuggest.enabled` pref is changed.
|
||||
add_task(async function test_updateExperimentState_pref() {
|
||||
add_task(async function test_updateFeatureState_pref() {
|
||||
Assert.ok(
|
||||
UrlbarPrefs.get("quicksuggest.enabled"),
|
||||
"Sanity check: quicksuggest.enabled is true by default"
|
||||
);
|
||||
|
||||
let sandbox = sinon.createSandbox();
|
||||
let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateExperimentState");
|
||||
let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateFeatureState");
|
||||
|
||||
UrlbarPrefs.set("quicksuggest.enabled", false);
|
||||
await UrlbarQuickSuggest.readyPromise;
|
||||
Assert.equal(
|
||||
spy.callCount,
|
||||
1,
|
||||
"_updateExperimentState called once after changing pref"
|
||||
"_updateFeatureState called once after changing pref"
|
||||
);
|
||||
|
||||
UrlbarPrefs.clear("quicksuggest.enabled");
|
||||
|
@ -49,24 +49,24 @@ add_task(async function test_updateExperimentState_pref() {
|
|||
Assert.equal(
|
||||
spy.callCount,
|
||||
2,
|
||||
"_updateExperimentState called again after clearing pref"
|
||||
"_updateFeatureState called again after clearing pref"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
// Makes sure `UrlbarProviderQuickSuggest._updateExperimentState()` is called
|
||||
// Makes sure `UrlbarProviderQuickSuggest._updateFeatureState()` is called
|
||||
// when a Nimbus experiment is installed and uninstalled.
|
||||
add_task(async function test_updateExperimentState_experiment() {
|
||||
add_task(async function test_updateFeatureState_experiment() {
|
||||
let sandbox = sinon.createSandbox();
|
||||
let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateExperimentState");
|
||||
let spy = sandbox.spy(UrlbarProviderQuickSuggest, "_updateFeatureState");
|
||||
|
||||
await QuickSuggestTestUtils.withExperiment({
|
||||
callback: () => {
|
||||
Assert.equal(
|
||||
spy.callCount,
|
||||
1,
|
||||
"_updateExperimentState called once after installing experiment"
|
||||
"_updateFeatureState called once after installing experiment"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -74,7 +74,7 @@ add_task(async function test_updateExperimentState_experiment() {
|
|||
Assert.equal(
|
||||
spy.callCount,
|
||||
2,
|
||||
"_updateExperimentState called again after uninstalling experiment"
|
||||
"_updateFeatureState called again after uninstalling experiment"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
|
||||
/* import-globals-from ../../unit/head.js */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
|
||||
UrlbarProviderQuickSuggest:
|
||||
"resource:///modules/UrlbarProviderQuickSuggest.jsm",
|
||||
UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm",
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests quick suggest prefs migrations.
|
||||
*
|
||||
|
|
|
@ -7,12 +7,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
UrlbarProviderQuickSuggest:
|
||||
"resource:///modules/UrlbarProviderQuickSuggest.jsm",
|
||||
UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm",
|
||||
});
|
||||
|
||||
const TELEMETRY_REMOTE_SETTINGS_LATENCY =
|
||||
"FX_URLBAR_QUICK_SUGGEST_REMOTE_SETTINGS_LATENCY_MS";
|
||||
|
||||
|
|
|
@ -6,11 +6,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
UrlbarProviderQuickSuggest:
|
||||
"resource:///modules/UrlbarProviderQuickSuggest.jsm",
|
||||
});
|
||||
|
||||
const MAX_RESULT_COUNT = UrlbarPrefs.get("maxRichResults");
|
||||
|
||||
// This search string length needs to be >= 4 to trigger its suggestion as a
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -8,9 +8,6 @@
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.jsm",
|
||||
UrlbarProviderQuickSuggest:
|
||||
"resource:///modules/UrlbarProviderQuickSuggest.jsm",
|
||||
UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm",
|
||||
});
|
||||
|
||||
// We set the Merino timeout to a large value to avoid intermittent failures in
|
||||
|
|
|
@ -11,8 +11,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
UrlbarProviderHeuristicFallback:
|
||||
"resource:///modules/UrlbarProviderHeuristicFallback.jsm",
|
||||
UrlbarProviderPlaces: "resource:///modules/UrlbarProviderPlaces.jsm",
|
||||
UrlbarProviderQuickSuggest:
|
||||
"resource:///modules/UrlbarProviderQuickSuggest.jsm",
|
||||
UrlbarProviderTabToSearch:
|
||||
"resource:///modules/UrlbarProviderTabToSearch.jsm",
|
||||
});
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
[DEFAULT]
|
||||
skip-if = toolkit == 'android' # bug 1730213
|
||||
head = head.js ../../unit/head.js
|
||||
head = ../../unit/head.js head.js
|
||||
firefox-appdir = browser
|
||||
|
||||
[test_quicksuggest.js]
|
||||
[test_quicksuggest_bestMatch.js]
|
||||
[test_quicksuggest_impressionCaps.js]
|
||||
[test_quicksuggest_merino.js]
|
||||
[test_quicksuggest_migrate_v1.js]
|
||||
[test_quicksuggest_migrate_v2.js]
|
||||
|
|
|
@ -349,14 +349,14 @@ p {
|
|||
top: -90px;
|
||||
}
|
||||
|
||||
.promo-dismiss:hover {
|
||||
background-color: var(--in-content-button-background) !important;
|
||||
}
|
||||
|
||||
@media not (prefers-contrast) {
|
||||
.promo-dismiss {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.promo-dismiss:hover {
|
||||
background-color: var(--in-content-button-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.promo-content {
|
||||
|
@ -383,6 +383,7 @@ p {
|
|||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--in-content-primary-button-background);
|
||||
}
|
||||
|
||||
.promo-cta .button:hover {
|
||||
|
|
|
@ -6180,7 +6180,6 @@ XPCOMService_GetSocketTransportService
|
|||
?BroadcastStringBundle@ContentParent@dom@mozilla@@SAXABVStringBundleDescriptor@23@@Z
|
||||
?Get@SharedStringMap@ipc@dom@mozilla@@QAE_NABV?$nsTString@D@@AAV?$nsTSubstring@_S@@@Z
|
||||
?ReinitForContent@ImageBridgeChild@layers@mozilla@@SA_N$$QAV?$Endpoint@VPImageBridgeChild@layers@mozilla@@@ipc@3@I@Z
|
||||
?RecvAudioSessionData@widget@mozilla@@YA?AW4nsresult@@ABUnsID@@ABV?$nsTString@_S@@1@Z
|
||||
?Release@WakeLock@dom@mozilla@@UAGKXZ
|
||||
?GetInstance@PowerManagerService@power@dom@mozilla@@SA?AU?$already_AddRefed@VPowerManagerService@power@dom@mozilla@@@@XZ
|
||||
?RegisterWakeLockObserver@hal@mozilla@@YAXPAV?$Observer@VWakeLockInformation@hal@mozilla@@@2@@Z
|
||||
|
|
|
@ -6464,7 +6464,6 @@ ZN11encoding_rs3mem17utf16_valid_up_to17h89dc8b8f218f8fa1E
|
|||
?BroadcastStringBundle@ContentParent@dom@mozilla@@SAXAEBVStringBundleDescriptor@23@@Z
|
||||
?Get@SharedStringMap@ipc@dom@mozilla@@QEAA_NAEBV?$nsTString@D@@AEAV?$nsTSubstring@_S@@@Z
|
||||
?GetAndResetReleaseFence@RenderCompositor@wr@mozilla@@UEAA?AVFileDescriptor@ipc@3@XZ
|
||||
?RecvAudioSessionData@widget@mozilla@@YA?AW4nsresult@@AEBUnsID@@AEBV?$nsTString@_S@@1@Z
|
||||
?Release@WakeLock@dom@mozilla@@UEAAKXZ
|
||||
?GetInstance@PowerManagerService@power@dom@mozilla@@SA?AU?$already_AddRefed@VPowerManagerService@power@dom@mozilla@@@@XZ
|
||||
?RegisterWakeLockObserver@hal@mozilla@@YAXPEAV?$Observer@VWakeLockInformation@hal@mozilla@@@2@@Z
|
||||
|
|
|
@ -51,6 +51,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1716622
|
|||
ok(ex.toString().includes("negative or greater than the allowed amount"),
|
||||
"Expected getImageData exception");
|
||||
|
||||
ex = null;
|
||||
try {
|
||||
new ImageData(23175, 23175);
|
||||
} catch (e) {
|
||||
ex = e;
|
||||
}
|
||||
ok(ex.toString().includes("negative or greater than the allowed amount"),
|
||||
"Expected ImageData constructor exception");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
go();
|
||||
|
|
|
@ -50,8 +50,11 @@ already_AddRefed<ImageData> ImageData::Constructor(const GlobalObject& aGlobal,
|
|||
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Restrict the typed array length to INT32_MAX because that's all we support
|
||||
// in dom::TypedArray::ComputeState.
|
||||
CheckedInt<uint32_t> length = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
|
||||
if (!length.isValid()) {
|
||||
if (!length.isValid() || length.value() > INT32_MAX) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -226,7 +226,6 @@
|
|||
# include <process.h>
|
||||
# define getpid _getpid
|
||||
# include "mozilla/WinDllServices.h"
|
||||
# include "mozilla/widget/AudioSession.h"
|
||||
# include "mozilla/widget/WinContentSystemParameters.h"
|
||||
#endif
|
||||
|
||||
|
@ -3092,10 +3091,6 @@ void ContentChild::ShutdownInternal() {
|
|||
os->NotifyObservers(ToSupports(this), "content-child-shutdown", nullptr);
|
||||
}
|
||||
|
||||
#if defined(XP_WIN)
|
||||
mozilla::widget::StopAudioSession();
|
||||
#endif
|
||||
|
||||
GetIPCChannel()->SetAbortOnError(false);
|
||||
|
||||
if (mProfilerController) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "mozilla/NotNull.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ProfilerLabels.h"
|
||||
#include "mozilla/ProfilerMarkers.h"
|
||||
#include "mozilla/ProfilerMarkerTypes.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
@ -2526,14 +2527,23 @@ void MediaDecoderStateMachine::DecodingState::MaybeStartBuffering() {
|
|||
}
|
||||
|
||||
// Note we could have a wait promise pending when playing non-MSE EME.
|
||||
if ((mMaster->OutOfDecodedAudio() && mMaster->IsWaitingAudioData()) ||
|
||||
(mMaster->OutOfDecodedVideo() && mMaster->IsWaitingVideoData())) {
|
||||
if (mMaster->OutOfDecodedAudio() && mMaster->IsWaitingAudioData()) {
|
||||
PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
|
||||
"OutOfDecodedAudio");
|
||||
SetState<BufferingState>();
|
||||
return;
|
||||
}
|
||||
if (mMaster->OutOfDecodedVideo() && mMaster->IsWaitingVideoData()) {
|
||||
PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
|
||||
"OutOfDecodedVideo");
|
||||
SetState<BufferingState>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Reader()->UseBufferingHeuristics() && mMaster->HasLowDecodedData() &&
|
||||
mMaster->HasLowBufferedData() && !mMaster->mCanPlayThrough) {
|
||||
PROFILER_MARKER_TEXT("MDSM::StartBuffering", MEDIA_PLAYBACK, {},
|
||||
"BufferingHeuristics");
|
||||
SetState<BufferingState>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
skip-if(Android) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)&&/^aarch64-msvc/.test(xulRuntime.XPCOMABI),0-136,0-427680) == vtt_update_display_after_removed_cue.html vtt_update_display_after_removed_cue_ref.html
|
||||
skip-if(Android) fuzzy-if(winWidget,0-170,0-170) == vtt_overlapping_time.html vtt_overlapping_time-ref.html
|
||||
skip-if(Android) != vtt_reflow_display.html vtt_reflow_display-ref.html
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<link rel="stylesheet" href="vtt_reflow_display.css">
|
||||
<body>
|
||||
<div class="video-player">
|
||||
<div class="video-layer">
|
||||
<video id="v1" autoplay controls></video>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
/**
|
||||
* Simply play and pause a video without any cues.
|
||||
*/
|
||||
async function testDisplayCueDuringFrequentReflowRef() {
|
||||
const video = document.getElementById("v1");
|
||||
video.src = "white.webm";
|
||||
video.onplay = _ => {
|
||||
video.onplay = null;
|
||||
video.pause();
|
||||
document.documentElement.removeAttribute('class');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("MozReftestInvalidate",
|
||||
testDisplayCueDuringFrequentReflowRef);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,33 @@
|
|||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-height: 100%;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
.video-player {
|
||||
display: flex;
|
||||
max-height: calc(100% - 400px);
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
height: 0;
|
||||
}
|
||||
.video-layer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
video {
|
||||
object-fit: contain;
|
||||
display: flex;
|
||||
flex: auto;
|
||||
max-width: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
</head>
|
||||
<link rel="stylesheet" href="vtt_reflow_display.css">
|
||||
<body>
|
||||
<div class="video-player">
|
||||
<div class="video-layer">
|
||||
<video id="v1" autoplay controls></video>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
/**
|
||||
* In bug 1733232, setting some CSS properties (from bug 1733232 comment17)
|
||||
* would cause video frame's reflow called very frequently, which crashed the
|
||||
* video control and caused no cue showing. We compare this test with another
|
||||
* white video without any cues, and they should NOT be equal.
|
||||
*/
|
||||
function testDisplayCueDuringFrequentReflow() {
|
||||
let video = document.getElementById("v1");
|
||||
video.src = "white.webm";
|
||||
let cue = new VTTCue(0, 4, "hello testing");
|
||||
cue.onenter = _ => {
|
||||
cue.onenter = null;
|
||||
video.pause();
|
||||
document.documentElement.removeAttribute('class');
|
||||
}
|
||||
let track = video.addTextTrack("captions");
|
||||
track.mode = "showing";
|
||||
track.addCue(cue);
|
||||
};
|
||||
|
||||
window.addEventListener("MozReftestInvalidate",
|
||||
testDisplayCueDuringFrequentReflow);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Двоичный файл не отображается.
|
@ -177,7 +177,7 @@
|
|||
* If shutdown takes this long, kill actors of a quota client, to avoid reaching
|
||||
* the crash timeout.
|
||||
*/
|
||||
#define SHUTDOWN_FORCE_KILL_TIMEOUT_MS 5000
|
||||
#define SHUTDOWN_KILL_ACTORS_TIMEOUT_MS 5000
|
||||
|
||||
/**
|
||||
* Automatically crash the browser if shutdown of a quota client takes this
|
||||
|
@ -189,7 +189,11 @@
|
|||
* not hide them. On the other hand this value is less than 60 seconds which is
|
||||
* used by nsTerminator to crash a hung main process.
|
||||
*/
|
||||
#define SHUTDOWN_FORCE_CRASH_TIMEOUT_MS 45000
|
||||
#define SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS 45000
|
||||
|
||||
static_assert(
|
||||
SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS > SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
|
||||
"The kill actors timeout must be shorter than the crash browser one.");
|
||||
|
||||
// profile-before-change, when we need to shut down quota manager
|
||||
#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm"
|
||||
|
@ -3700,13 +3704,6 @@ nsresult QuotaManager::Init() {
|
|||
nsCOMPtr<nsIThread>, MOZ_SELECT_OVERLOAD(NS_NewNamedThread),
|
||||
"QuotaManager IO"));
|
||||
|
||||
// Make a timer here to avoid potential failures later. We don't actually
|
||||
// initialize the timer until shutdown.
|
||||
nsCOMPtr shutdownTimer = NS_NewTimer();
|
||||
QM_TRY(OkIf(shutdownTimer), Err(NS_ERROR_FAILURE));
|
||||
|
||||
mShutdownTimer.init(WrapNotNullUnchecked(std::move(shutdownTimer)));
|
||||
|
||||
static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
|
||||
Client::LS == 3 && Client::TYPE_MAX == 4,
|
||||
"Fix the registration!");
|
||||
|
@ -3761,12 +3758,20 @@ void QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
|
|||
}
|
||||
}
|
||||
|
||||
void QuotaManager::RecordQuotaManagerShutdownStep(
|
||||
const nsACString& aStepDescription) {
|
||||
// Callable on any thread.
|
||||
MOZ_ASSERT(mShutdownStarted);
|
||||
|
||||
RecordShutdownStep(Nothing{}, aStepDescription);
|
||||
}
|
||||
|
||||
void QuotaManager::MaybeRecordQuotaManagerShutdownStep(
|
||||
const nsACString& aStepDescription) {
|
||||
// Callable on any thread.
|
||||
|
||||
if (ShutdownStarted()) {
|
||||
RecordShutdownStep(Nothing{}, aStepDescription);
|
||||
RecordQuotaManagerShutdownStep(aStepDescription);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3811,140 +3816,205 @@ void QuotaManager::Shutdown() {
|
|||
MOZ_ASSERT(!mShutdownStarted);
|
||||
MOZ_DIAGNOSTIC_ASSERT(!gShutdown);
|
||||
|
||||
// Setting this flag prevents the service from being recreated and prevents
|
||||
// further storagess from being created.
|
||||
gShutdown = true;
|
||||
// Define some local helper functions
|
||||
|
||||
auto flagShutdownStarted = [this]() {
|
||||
// Setting this flag prevents the service from being recreated and prevents
|
||||
// further storages from being created.
|
||||
// XXX: Harmonize QM shutdown flags, see bug 1726714
|
||||
gShutdown = true;
|
||||
|
||||
// StopIdleMaintenance used to happen before mShutdownStarted is set true
|
||||
// but it is just an internal flag for the recording of shutdown steps
|
||||
// and not evaluated elsewhere.
|
||||
|
||||
mShutdownStartedAt.init(TimeStamp::NowLoRes());
|
||||
mShutdownStarted = true;
|
||||
};
|
||||
|
||||
nsCOMPtr<nsITimer> crashBrowserTimer;
|
||||
|
||||
auto crashBrowserTimerCallback = [](nsITimer* aTimer, void* aClosure) {
|
||||
auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
|
||||
|
||||
nsCString annotation;
|
||||
|
||||
for (Client::Type type : quotaManager->AllClientTypes()) {
|
||||
auto& quotaClient = *(*quotaManager->mClients)[type];
|
||||
|
||||
if (!quotaClient.IsShutdownCompleted()) {
|
||||
annotation.AppendPrintf("%s: %s\nIntermediate steps:\n%s\n\n",
|
||||
Client::TypeToText(type).get(),
|
||||
quotaClient.GetShutdownStatus().get(),
|
||||
quotaManager->mShutdownSteps[type].get());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MutexAutoLock lock(quotaManager->mQuotaMutex);
|
||||
|
||||
annotation.AppendPrintf("QM: %zu normal origin ops pending\n",
|
||||
gNormalOriginOps->Length());
|
||||
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
|
||||
for (const auto& op : *gNormalOriginOps) {
|
||||
nsCString name;
|
||||
op->GetName(name);
|
||||
annotation.AppendPrintf("Op: %s pending\n", name.get());
|
||||
}
|
||||
#endif
|
||||
annotation.AppendPrintf("Intermediate steps:\n%s\n",
|
||||
quotaManager->mQuotaManagerShutdownSteps.get());
|
||||
}
|
||||
|
||||
CrashReporter::AnnotateCrashReport(
|
||||
CrashReporter::Annotation::QuotaManagerShutdownTimeout, annotation);
|
||||
|
||||
MOZ_CRASH("Quota manager shutdown timed out");
|
||||
};
|
||||
|
||||
auto startCrashBrowserTimer = [&]() {
|
||||
crashBrowserTimer = NS_NewTimer();
|
||||
MOZ_ASSERT(crashBrowserTimer);
|
||||
if (crashBrowserTimer) {
|
||||
RecordQuotaManagerShutdownStep("startCrashBrowserTimer"_ns);
|
||||
MOZ_ALWAYS_SUCCEEDS(crashBrowserTimer->InitWithNamedFuncCallback(
|
||||
crashBrowserTimerCallback, this, SHUTDOWN_CRASH_BROWSER_TIMEOUT_MS,
|
||||
nsITimer::TYPE_ONE_SHOT,
|
||||
"quota::QuotaManager::Shutdown::crashBrowserTimer"));
|
||||
}
|
||||
};
|
||||
|
||||
auto stopCrashBrowserTimer = [&]() {
|
||||
if (crashBrowserTimer) {
|
||||
RecordQuotaManagerShutdownStep("stopCrashBrowserTimer"_ns);
|
||||
QM_WARNONLY_TRY(QM_TO_RESULT(crashBrowserTimer->Cancel()));
|
||||
}
|
||||
};
|
||||
|
||||
auto initiateShutdownWorkThreads = [this]() {
|
||||
RecordQuotaManagerShutdownStep("initiateShutdownWorkThreads"_ns);
|
||||
bool needsToWait = false;
|
||||
for (Client::Type type : AllClientTypes()) {
|
||||
// Clients are supposed to also AbortAllOperations from this point on
|
||||
// to speed up shutdown, if possible. Thus pending operations
|
||||
// might not be executed anymore.
|
||||
needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
|
||||
}
|
||||
|
||||
return needsToWait;
|
||||
};
|
||||
|
||||
nsCOMPtr<nsITimer> killActorsTimer;
|
||||
|
||||
auto killActorsTimerCallback = [](nsITimer* aTimer, void* aClosure) {
|
||||
auto* const quotaManager = static_cast<QuotaManager*>(aClosure);
|
||||
|
||||
quotaManager->RecordQuotaManagerShutdownStep("killActorsTimerCallback"_ns);
|
||||
|
||||
// XXX: This abort is a workaround to unblock shutdown, which
|
||||
// ought to be removed by bug 1682326. We probably need more
|
||||
// checks to immediately abort new operations during
|
||||
// shutdown.
|
||||
quotaManager->GetClient(Client::IDB)->AbortAllOperations();
|
||||
|
||||
for (Client::Type type : quotaManager->AllClientTypes()) {
|
||||
quotaManager->GetClient(type)->ForceKillActors();
|
||||
}
|
||||
};
|
||||
|
||||
auto startKillActorsTimer = [&]() {
|
||||
killActorsTimer = NS_NewTimer();
|
||||
MOZ_ASSERT(killActorsTimer);
|
||||
if (killActorsTimer) {
|
||||
RecordQuotaManagerShutdownStep("startKillActorsTimer"_ns);
|
||||
MOZ_ALWAYS_SUCCEEDS(killActorsTimer->InitWithNamedFuncCallback(
|
||||
killActorsTimerCallback, this, SHUTDOWN_KILL_ACTORS_TIMEOUT_MS,
|
||||
nsITimer::TYPE_ONE_SHOT,
|
||||
"quota::QuotaManager::Shutdown::killActorsTimer"));
|
||||
}
|
||||
};
|
||||
|
||||
auto stopKillActorsTimer = [&]() {
|
||||
if (killActorsTimer) {
|
||||
RecordQuotaManagerShutdownStep("stopKillActorsTimer"_ns);
|
||||
QM_WARNONLY_TRY(QM_TO_RESULT(killActorsTimer->Cancel()));
|
||||
}
|
||||
};
|
||||
|
||||
auto isAllClientsShutdownComplete = [this] {
|
||||
return std::all_of(AllClientTypes().cbegin(), AllClientTypes().cend(),
|
||||
[&self = *this](const auto type) {
|
||||
return (*self.mClients)[type]->IsShutdownCompleted();
|
||||
});
|
||||
};
|
||||
|
||||
auto shutdownAndJoinWorkThreads = [this]() {
|
||||
RecordQuotaManagerShutdownStep("shutdownAndJoinWorkThreads"_ns);
|
||||
for (Client::Type type : AllClientTypes()) {
|
||||
(*mClients)[type]->FinalizeShutdownWorkThreads();
|
||||
}
|
||||
};
|
||||
|
||||
auto shutdownAndJoinIOThread = [this]() {
|
||||
RecordQuotaManagerShutdownStep("shutdownAndJoinIOThread"_ns);
|
||||
// NB: It's very important that runnable is destroyed on this thread
|
||||
// (i.e. after we join the IO thread) because we can't release the
|
||||
// QuotaManager on the IO thread. This should probably use
|
||||
// NewNonOwningRunnableMethod ...
|
||||
RefPtr<Runnable> runnable =
|
||||
NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
|
||||
&QuotaManager::ShutdownStorage);
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// Give clients a chance to cleanup IO thread only objects.
|
||||
QM_WARNONLY_TRY(
|
||||
QM_TO_RESULT((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL)));
|
||||
|
||||
// Make sure to join with our IO thread.
|
||||
QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
|
||||
};
|
||||
|
||||
auto invalidatePendingDirectoryLocks = [this]() {
|
||||
RecordQuotaManagerShutdownStep("invalidatePendingDirectoryLocks"_ns);
|
||||
for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
|
||||
lock->Invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
// Body of the function
|
||||
|
||||
flagShutdownStarted();
|
||||
|
||||
startCrashBrowserTimer();
|
||||
|
||||
// XXX: StopIdleMaintenance now just notifies all clients to abort any
|
||||
// maintenance work.
|
||||
// This could be done as part of QuotaClient::AbortAllOperations.
|
||||
StopIdleMaintenance();
|
||||
|
||||
mShutdownStartedAt.init(TimeStamp::NowLoRes());
|
||||
mShutdownStarted = true;
|
||||
|
||||
const auto& allClientTypes = AllClientTypes();
|
||||
|
||||
bool needsToWait = false;
|
||||
for (Client::Type type : allClientTypes) {
|
||||
needsToWait |= (*mClients)[type]->InitiateShutdownWorkThreads();
|
||||
}
|
||||
needsToWait |= static_cast<bool>(gNormalOriginOps);
|
||||
const bool needsToWait =
|
||||
initiateShutdownWorkThreads() | static_cast<bool>(gNormalOriginOps);
|
||||
|
||||
// If any clients cannot shutdown immediately, spin the event loop while we
|
||||
// wait on all the threads to close. Our timer may fire during that loop.
|
||||
// wait on all the threads to close.
|
||||
if (needsToWait) {
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
(*mShutdownTimer)
|
||||
->InitWithNamedFuncCallback(
|
||||
[](nsITimer* aTimer, void* aClosure) {
|
||||
auto* const quotaManager =
|
||||
static_cast<QuotaManager*>(aClosure);
|
||||
|
||||
for (Client::Type type : quotaManager->AllClientTypes()) {
|
||||
// XXX This is a workaround to unblock shutdown, which ought
|
||||
// to be removed by Bug 1682326.
|
||||
if (type == Client::IDB) {
|
||||
(*quotaManager->mClients)[type]->AbortAllOperations();
|
||||
}
|
||||
|
||||
(*quotaManager->mClients)[type]->ForceKillActors();
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(aTimer->InitWithNamedFuncCallback(
|
||||
[](nsITimer* aTimer, void* aClosure) {
|
||||
auto* const quotaManager =
|
||||
static_cast<QuotaManager*>(aClosure);
|
||||
|
||||
nsCString annotation;
|
||||
|
||||
{
|
||||
for (Client::Type type :
|
||||
quotaManager->AllClientTypes()) {
|
||||
auto& quotaClient =
|
||||
*(*quotaManager->mClients)[type];
|
||||
|
||||
if (!quotaClient.IsShutdownCompleted()) {
|
||||
annotation.AppendPrintf(
|
||||
"%s: %s\nIntermediate steps:\n%s\n\n",
|
||||
Client::TypeToText(type).get(),
|
||||
quotaClient.GetShutdownStatus().get(),
|
||||
quotaManager->mShutdownSteps[type].get());
|
||||
}
|
||||
}
|
||||
|
||||
if (gNormalOriginOps) {
|
||||
MutexAutoLock lock(quotaManager->mQuotaMutex);
|
||||
|
||||
annotation.AppendPrintf(
|
||||
"QM: %zu normal origin ops pending\n",
|
||||
gNormalOriginOps->Length());
|
||||
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
|
||||
for (const auto& op : *gNormalOriginOps) {
|
||||
nsCString name;
|
||||
op->GetName(name);
|
||||
annotation.AppendPrintf("Op: %s pending\n",
|
||||
name.get());
|
||||
}
|
||||
#endif
|
||||
annotation.AppendPrintf(
|
||||
"Intermediate steps:\n%s\n",
|
||||
quotaManager->mQuotaManagerShutdownSteps.get());
|
||||
}
|
||||
}
|
||||
|
||||
// We expect that at least one quota client didn't
|
||||
// complete its shutdown.
|
||||
MOZ_DIAGNOSTIC_ASSERT(!annotation.IsEmpty());
|
||||
|
||||
CrashReporter::AnnotateCrashReport(
|
||||
CrashReporter::Annotation::
|
||||
QuotaManagerShutdownTimeout,
|
||||
annotation);
|
||||
|
||||
MOZ_CRASH("Quota manager shutdown timed out");
|
||||
},
|
||||
aClosure, SHUTDOWN_FORCE_CRASH_TIMEOUT_MS,
|
||||
nsITimer::TYPE_ONE_SHOT,
|
||||
"quota::QuotaManager::ForceCrashTimer"));
|
||||
},
|
||||
this, SHUTDOWN_FORCE_KILL_TIMEOUT_MS, nsITimer::TYPE_ONE_SHOT,
|
||||
"quota::QuotaManager::ForceKillTimer"));
|
||||
startKillActorsTimer();
|
||||
|
||||
MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
|
||||
"QuotaManager::Shutdown"_ns, [this, &allClientTypes] {
|
||||
return !gNormalOriginOps &&
|
||||
std::all_of(
|
||||
allClientTypes.cbegin(), allClientTypes.cend(),
|
||||
[&self = *this](const auto type) {
|
||||
return (*self.mClients)[type]->IsShutdownCompleted();
|
||||
});
|
||||
"QuotaManager::Shutdown"_ns, [isAllClientsShutdownComplete]() {
|
||||
return !gNormalOriginOps && isAllClientsShutdownComplete();
|
||||
}));
|
||||
|
||||
stopKillActorsTimer();
|
||||
}
|
||||
|
||||
for (Client::Type type : allClientTypes) {
|
||||
(*mClients)[type]->FinalizeShutdownWorkThreads();
|
||||
}
|
||||
shutdownAndJoinWorkThreads();
|
||||
|
||||
// Cancel the timer regardless of whether it actually fired.
|
||||
QM_WARNONLY_TRY(QM_TO_RESULT((*mShutdownTimer)->Cancel()));
|
||||
shutdownAndJoinIOThread();
|
||||
|
||||
// NB: It's very important that runnable is destroyed on this thread
|
||||
// (i.e. after we join the IO thread) because we can't release the
|
||||
// QuotaManager on the IO thread. This should probably use
|
||||
// NewNonOwningRunnableMethod ...
|
||||
RefPtr<Runnable> runnable =
|
||||
NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
|
||||
&QuotaManager::ShutdownStorage);
|
||||
MOZ_ASSERT(runnable);
|
||||
invalidatePendingDirectoryLocks();
|
||||
|
||||
// Give clients a chance to cleanup IO thread only objects.
|
||||
QM_WARNONLY_TRY(
|
||||
QM_TO_RESULT((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL)));
|
||||
|
||||
// Make sure to join with our IO thread.
|
||||
QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
|
||||
|
||||
for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
|
||||
lock->Invalidate();
|
||||
}
|
||||
stopCrashBrowserTimer();
|
||||
}
|
||||
|
||||
void QuotaManager::InitQuotaForOrigin(
|
||||
|
|
|
@ -381,6 +381,9 @@ class QuotaManager final : public BackgroundThreadObject {
|
|||
static void SafeMaybeRecordQuotaClientShutdownStep(
|
||||
Client::Type aClientType, const nsACString& aStepDescription);
|
||||
|
||||
// Record a quota manager shutdown step, use only if shutdown is active.
|
||||
void RecordQuotaManagerShutdownStep(const nsACString& aStepDescription);
|
||||
|
||||
// Record a quota manager shutdown step, if shutting down.
|
||||
void MaybeRecordQuotaManagerShutdownStep(const nsACString& aStepDescription);
|
||||
|
||||
|
@ -587,9 +590,6 @@ class QuotaManager final : public BackgroundThreadObject {
|
|||
|
||||
nsCOMPtr<mozIStorageConnection> mStorageConnection;
|
||||
|
||||
// A timer that gets activated at shutdown to ensure we close all storages.
|
||||
LazyInitializedOnceNotNull<const nsCOMPtr<nsITimer>> mShutdownTimer;
|
||||
|
||||
EnumeratedArray<Client::Type, Client::TYPE_MAX, nsCString> mShutdownSteps;
|
||||
LazyInitializedOnce<const TimeStamp> mShutdownStartedAt;
|
||||
Atomic<bool> mShutdownStarted;
|
||||
|
|
|
@ -1038,12 +1038,12 @@ struct Chain
|
|||
goto skip;
|
||||
|
||||
if (reverse)
|
||||
_hb_ot_layout_reverse_graphemes (c->buffer);
|
||||
c->buffer->reverse ();
|
||||
|
||||
subtable->apply (c);
|
||||
|
||||
if (reverse)
|
||||
_hb_ot_layout_reverse_graphemes (c->buffer);
|
||||
c->buffer->reverse ();
|
||||
|
||||
(void) c->buffer->message (c->font, "end chainsubtable %d", c->lookup_index);
|
||||
|
||||
|
|
|
@ -359,12 +359,6 @@ _hb_grapheme_group_func (const hb_glyph_info_t& a HB_UNUSED,
|
|||
#define foreach_grapheme(buffer, start, end) \
|
||||
foreach_group (buffer, start, end, _hb_grapheme_group_func)
|
||||
|
||||
static inline void
|
||||
_hb_ot_layout_reverse_graphemes (hb_buffer_t *buffer)
|
||||
{
|
||||
buffer->reverse_groups (_hb_grapheme_group_func,
|
||||
buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
_hb_glyph_info_is_unicode_format (const hb_glyph_info_t *info)
|
||||
|
|
|
@ -628,7 +628,20 @@ hb_ensure_native_direction (hb_buffer_t *buffer)
|
|||
(HB_DIRECTION_IS_VERTICAL (direction) &&
|
||||
direction != HB_DIRECTION_TTB))
|
||||
{
|
||||
_hb_ot_layout_reverse_graphemes (buffer);
|
||||
|
||||
if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
|
||||
foreach_grapheme (buffer, start, end)
|
||||
{
|
||||
buffer->merge_clusters (start, end);
|
||||
buffer->reverse_range (start, end);
|
||||
}
|
||||
else
|
||||
foreach_grapheme (buffer, start, end)
|
||||
/* form_clusters() merged clusters already, we don't merge. */
|
||||
buffer->reverse_range (start, end);
|
||||
|
||||
buffer->reverse ();
|
||||
|
||||
buffer->props.direction = HB_DIRECTION_REVERSE (buffer->props.direction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,10 @@ PrintTargetPDF::~PrintTargetPDF() {
|
|||
/* static */
|
||||
already_AddRefed<PrintTargetPDF> PrintTargetPDF::CreateOrNull(
|
||||
nsIOutputStream* aStream, const IntSize& aSizeInPoints) {
|
||||
if (NS_WARN_IF(!aStream)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cairo_surface_t* surface = cairo_pdf_surface_create_for_stream(
|
||||
write_func, (void*)aStream, aSizeInPoints.width, aSizeInPoints.height);
|
||||
if (cairo_surface_status(surface)) {
|
||||
|
|
|
@ -4454,6 +4454,7 @@ impl PicturePrimitive {
|
|||
let tile_cache = tile_caches.get_mut(&slice_id).unwrap();
|
||||
let mut debug_info = SliceDebugInfo::new();
|
||||
let mut surface_render_tasks = FastHashMap::default();
|
||||
let mut surface_dirty_rects = Vec::new();
|
||||
let mut surface_local_dirty_rect = PictureRect::zero();
|
||||
let device_pixel_scale = frame_state
|
||||
.surfaces[surface_index.0]
|
||||
|
@ -4777,6 +4778,7 @@ impl PicturePrimitive {
|
|||
tile_key,
|
||||
render_task_id,
|
||||
);
|
||||
surface_dirty_rects.push(tile.local_dirty_rect);
|
||||
}
|
||||
|
||||
if frame_context.fb_config.testing {
|
||||
|
@ -4863,7 +4865,10 @@ impl PicturePrimitive {
|
|||
);
|
||||
}
|
||||
|
||||
let descriptor = SurfaceDescriptor::new_tiled(surface_render_tasks);
|
||||
let descriptor = SurfaceDescriptor::new_tiled(
|
||||
surface_render_tasks,
|
||||
surface_dirty_rects,
|
||||
);
|
||||
|
||||
frame_state.surface_builder.push_surface(
|
||||
surface_index,
|
||||
|
@ -4992,6 +4997,7 @@ impl PicturePrimitive {
|
|||
surface_descriptor = SurfaceDescriptor::new_chained(
|
||||
picture_task_id,
|
||||
blur_render_task_id,
|
||||
surface_rects.clipped_local,
|
||||
);
|
||||
}
|
||||
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
|
||||
|
@ -5056,6 +5062,7 @@ impl PicturePrimitive {
|
|||
surface_descriptor = SurfaceDescriptor::new_chained(
|
||||
picture_task_id,
|
||||
blur_render_task_id,
|
||||
surface_rects.clipped_local,
|
||||
);
|
||||
}
|
||||
PictureCompositeMode::MixBlend(mode) if BlendMode::from_mix_blend_mode(
|
||||
|
@ -5163,7 +5170,10 @@ impl PicturePrimitive {
|
|||
|
||||
primary_render_task_id = render_task_id;
|
||||
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(render_task_id);
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(
|
||||
render_task_id,
|
||||
surface_rects.clipped_local,
|
||||
);
|
||||
}
|
||||
PictureCompositeMode::Filter(..) => {
|
||||
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
|
||||
|
@ -5188,7 +5198,10 @@ impl PicturePrimitive {
|
|||
|
||||
primary_render_task_id = render_task_id;
|
||||
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(render_task_id);
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(
|
||||
render_task_id,
|
||||
surface_rects.clipped_local,
|
||||
);
|
||||
}
|
||||
PictureCompositeMode::ComponentTransferFilter(..) => {
|
||||
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
|
||||
|
@ -5213,7 +5226,10 @@ impl PicturePrimitive {
|
|||
|
||||
primary_render_task_id = render_task_id;
|
||||
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(render_task_id);
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(
|
||||
render_task_id,
|
||||
surface_rects.clipped_local,
|
||||
);
|
||||
}
|
||||
PictureCompositeMode::MixBlend(..) |
|
||||
PictureCompositeMode::Blit(_) => {
|
||||
|
@ -5239,7 +5255,10 @@ impl PicturePrimitive {
|
|||
|
||||
primary_render_task_id = render_task_id;
|
||||
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(render_task_id);
|
||||
surface_descriptor = SurfaceDescriptor::new_simple(
|
||||
render_task_id,
|
||||
surface_rects.clipped_local,
|
||||
);
|
||||
}
|
||||
PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
|
||||
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
|
||||
|
@ -5277,6 +5296,7 @@ impl PicturePrimitive {
|
|||
surface_descriptor = SurfaceDescriptor::new_chained(
|
||||
picture_task_id,
|
||||
filter_task_id,
|
||||
surface_rects.clipped_local,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,17 +40,20 @@ pub enum SurfaceDescriptorKind {
|
|||
// Describes how a surface is rendered
|
||||
pub struct SurfaceDescriptor {
|
||||
kind: SurfaceDescriptorKind,
|
||||
dirty_rects: Vec<PictureRect>,
|
||||
}
|
||||
|
||||
impl SurfaceDescriptor {
|
||||
// Create a picture cache tiled surface
|
||||
pub fn new_tiled(
|
||||
tiles: FastHashMap<TileKey, RenderTaskId>,
|
||||
dirty_rects: Vec<PictureRect>,
|
||||
) -> Self {
|
||||
SurfaceDescriptor {
|
||||
kind: SurfaceDescriptorKind::Tiled {
|
||||
tiles,
|
||||
},
|
||||
dirty_rects,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,23 +61,27 @@ impl SurfaceDescriptor {
|
|||
pub fn new_chained(
|
||||
render_task_id: RenderTaskId,
|
||||
root_task_id: RenderTaskId,
|
||||
dirty_rect: PictureRect,
|
||||
) -> Self {
|
||||
SurfaceDescriptor {
|
||||
kind: SurfaceDescriptorKind::Chained {
|
||||
render_task_id,
|
||||
root_task_id,
|
||||
},
|
||||
dirty_rects: vec![dirty_rect],
|
||||
}
|
||||
}
|
||||
|
||||
// Create a simple surface (e.g. opacity)
|
||||
pub fn new_simple(
|
||||
render_task_id: RenderTaskId,
|
||||
dirty_rect: PictureRect,
|
||||
) -> Self {
|
||||
SurfaceDescriptor {
|
||||
kind: SurfaceDescriptorKind::Simple {
|
||||
render_task_id,
|
||||
},
|
||||
dirty_rects: vec![dirty_rect],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,7 +190,7 @@ pub struct SurfaceBuilder {
|
|||
// Stack of surfaces that are parents to the current targets
|
||||
builder_stack: Vec<CommandBufferBuilder>,
|
||||
// Dirty rect stack used to reject adding primitives
|
||||
dirty_rect_stack: Vec<PictureRect>,
|
||||
dirty_rect_stack: Vec<Vec<PictureRect>>,
|
||||
}
|
||||
|
||||
impl SurfaceBuilder {
|
||||
|
@ -207,7 +214,7 @@ impl SurfaceBuilder {
|
|||
// Init the surface
|
||||
surfaces[surface_index.0].clipping_rect = clipping_rect;
|
||||
|
||||
self.dirty_rect_stack.push(clipping_rect);
|
||||
self.dirty_rect_stack.push(descriptor.dirty_rects);
|
||||
|
||||
let builder = match descriptor.kind {
|
||||
SurfaceDescriptorKind::Tiled { tiles } => {
|
||||
|
@ -273,7 +280,10 @@ impl SurfaceBuilder {
|
|||
self.dirty_rect_stack
|
||||
.last()
|
||||
.unwrap()
|
||||
.intersects(&vis.clip_chain.pic_coverage_rect)
|
||||
.iter()
|
||||
.any(|dirty_rect| {
|
||||
dirty_rect.intersects(&vis.clip_chain.pic_coverage_rect)
|
||||
})
|
||||
}
|
||||
VisibilityState::PassThrough => {
|
||||
true
|
||||
|
|
|
@ -47,10 +47,17 @@ BlobSurfaceProvider::~BlobSurfaceProvider() {
|
|||
/* static */ void BlobSurfaceProvider::DestroyKeys(
|
||||
const AutoTArray<BlobImageKeyData, 1>& aKeys) {
|
||||
for (const auto& entry : aKeys) {
|
||||
if (!entry.mManager->IsDestroyed()) {
|
||||
entry.mManager->GetRenderRootStateManager()->AddBlobImageKeyForDiscard(
|
||||
entry.mBlobKey);
|
||||
if (entry.mManager->IsDestroyed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WebRenderBridgeChild* wrBridge = entry.mManager->WrBridge();
|
||||
if (!wrBridge || !wrBridge->MatchesNamespace(entry.mBlobKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.mManager->GetRenderRootStateManager()->AddBlobImageKeyForDiscard(
|
||||
entry.mBlobKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"debug": true,
|
||||
"optimize": true,
|
||||
"configure-args": "--enable-record-tuple",
|
||||
"skip-tests": {
|
||||
"all": ["jittest", "jsapitests", "checks"]
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
windowtype="Toolkit:PictureInPicture"
|
||||
chromemargin="0,0,0,0">
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<head>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="chrome://mochikit/content/chrome-harness.js"></script>
|
||||
|
@ -92,7 +90,7 @@
|
|||
for (let dark of [true, false]) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.theme.toolbar-theme", 2],
|
||||
["layout.css.prefers-color-scheme.content-override", 2],
|
||||
["ui.systemUsesDarkTheme", dark ? 1 : 0],
|
||||
]
|
||||
});
|
||||
|
|
|
@ -162,6 +162,9 @@ nsIContent* nsVideoFrame::GetVideoControls() const {
|
|||
|
||||
void nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
||||
PostDestroyData& aPostDestroyData) {
|
||||
if (mReflowCallbackPosted) {
|
||||
PresShell()->CancelReflowCallback(this);
|
||||
}
|
||||
aPostDestroyData.AddAnonymousContent(mCaptionDiv.forget());
|
||||
aPostDestroyData.AddAnonymousContent(mPosterImage.forget());
|
||||
nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
|
||||
|
@ -169,19 +172,48 @@ void nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
|||
|
||||
class DispatchResizeEvent : public Runnable {
|
||||
public:
|
||||
explicit DispatchResizeEvent(nsIContent* aContent, const nsString& aName)
|
||||
: mozilla::Runnable("DispatchResizeEvent"),
|
||||
mContent(aContent),
|
||||
mName(aName) {}
|
||||
explicit DispatchResizeEvent(nsIContent* aContent,
|
||||
const nsLiteralString& aName)
|
||||
: Runnable("DispatchResizeEvent"), mContent(aContent), mName(aName) {}
|
||||
NS_IMETHOD Run() override {
|
||||
nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, mName,
|
||||
CanBubble::eNo, Cancelable::eNo);
|
||||
return NS_OK;
|
||||
}
|
||||
nsCOMPtr<nsIContent> mContent;
|
||||
nsString mName;
|
||||
const nsLiteralString mName;
|
||||
};
|
||||
|
||||
bool nsVideoFrame::ReflowFinished() {
|
||||
auto GetSize = [&](nsIContent* aContent) -> Maybe<nsSize> {
|
||||
if (!aContent) {
|
||||
return Nothing();
|
||||
}
|
||||
nsIFrame* f = aContent->GetPrimaryFrame();
|
||||
if (!f) {
|
||||
return Nothing();
|
||||
}
|
||||
return Some(f->GetSize());
|
||||
};
|
||||
|
||||
if (auto size = GetSize(mCaptionDiv)) {
|
||||
if (*size != mCaptionTrackedSize) {
|
||||
mCaptionTrackedSize = *size;
|
||||
nsContentUtils::AddScriptRunner(
|
||||
new DispatchResizeEvent(mCaptionDiv, u"resizecaption"_ns));
|
||||
}
|
||||
}
|
||||
nsIContent* controls = GetVideoControls();
|
||||
if (auto size = GetSize(controls)) {
|
||||
if (*size != mControlsTrackedSize) {
|
||||
mControlsTrackedSize = *size;
|
||||
nsContentUtils::AddScriptRunner(
|
||||
new DispatchResizeEvent(controls, u"resizevideocontrols"_ns));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus) {
|
||||
|
@ -286,12 +318,11 @@ void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
|||
ReflowChildFlags::Default);
|
||||
|
||||
if (child->GetSize() != oldChildSize) {
|
||||
const nsString name = child->GetContent() == videoControlsDiv
|
||||
? u"resizevideocontrols"_ns
|
||||
: u"resizecaption"_ns;
|
||||
RefPtr<Runnable> event =
|
||||
new DispatchResizeEvent(child->GetContent(), name);
|
||||
nsContentUtils::AddScriptRunner(event);
|
||||
MOZ_ASSERT(child->IsPrimaryFrame(),
|
||||
"We only look at the primary frame in ReflowFinished");
|
||||
if (!mReflowCallbackPosted) {
|
||||
PresShell()->PostReflowCallback(this);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NS_ERROR("Unexpected extra child frame in nsVideoFrame; skipping");
|
||||
|
|
|
@ -12,20 +12,15 @@
|
|||
#include "mozilla/Attributes.h"
|
||||
#include "nsContainerFrame.h"
|
||||
#include "nsIAnonymousContentCreator.h"
|
||||
#include "nsIReflowCallback.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
class Layer;
|
||||
class LayerManager;
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
class nsPresContext;
|
||||
class nsDisplayItem;
|
||||
|
||||
class nsVideoFrame final : public nsContainerFrame,
|
||||
public nsIReflowCallback,
|
||||
public nsIAnonymousContentCreator {
|
||||
public:
|
||||
template <typename T>
|
||||
|
@ -33,14 +28,14 @@ class nsVideoFrame final : public nsContainerFrame,
|
|||
using Nothing = mozilla::Nothing;
|
||||
using Visibility = mozilla::Visibility;
|
||||
|
||||
typedef mozilla::layers::Layer Layer;
|
||||
typedef mozilla::layers::LayerManager LayerManager;
|
||||
|
||||
explicit nsVideoFrame(ComputedStyle*, nsPresContext*);
|
||||
|
||||
NS_DECL_QUERYFRAME
|
||||
NS_DECL_FRAMEARENA_HELPERS(nsVideoFrame)
|
||||
|
||||
void ReflowCallbackCanceled() final { mReflowCallbackPosted = false; }
|
||||
bool ReflowFinished() final;
|
||||
|
||||
void BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
const nsDisplayListSet& aLists) override;
|
||||
|
||||
|
@ -125,6 +120,13 @@ class nsVideoFrame final : public nsContainerFrame,
|
|||
|
||||
// Anonymous child which is the text track caption display div.
|
||||
nsCOMPtr<nsIContent> mCaptionDiv;
|
||||
|
||||
// Some sizes tracked for notification purposes.
|
||||
// TODO: Maybe the calling code could be rewritten to use ResizeObserver for
|
||||
// this nowadays.
|
||||
nsSize mControlsTrackedSize{-1, -1};
|
||||
nsSize mCaptionTrackedSize{-1, -1};
|
||||
bool mReflowCallbackPosted = false;
|
||||
};
|
||||
|
||||
#endif /* nsVideoFrame_h___ */
|
||||
|
|
|
@ -12,11 +12,8 @@
|
|||
img {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
img.overflowingVertical {
|
||||
|
|
|
@ -8,20 +8,17 @@
|
|||
are top level (e.g. not iframes).
|
||||
*/
|
||||
|
||||
html, body {
|
||||
html {
|
||||
/* Fill the viewport height, so that our '-moz-user-focus' styling will
|
||||
disregard clicks in the whole background area (so the video element
|
||||
doesn't inadvertently lose focus from a stray click on the background). */
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-user-focus: ignore;
|
||||
}
|
||||
|
||||
video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
|
|
@ -6329,7 +6329,7 @@
|
|||
# Whether we record SVG images as blobs or not.
|
||||
- name: image.svg.blob-image
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Whether we attempt to decode WebP images or not.
|
||||
|
|
|
@ -93,11 +93,6 @@ Next, add that package and any new transitive dependencies (you'll see them adde
|
|||
* When considering manually vendoring a package, discuss the situation with
|
||||
the ``#build`` team to ensure that other, more maintainable options are exhausted.
|
||||
|
||||
* ``mach vendor python`` **MUST** be run on Linux with Python 3.6.
|
||||
This restriction will be lifted when
|
||||
`bug 1659593 <https://bugzilla.mozilla.org/show_bug.cgi?id=1659593>`_
|
||||
is resolved.
|
||||
|
||||
.. note::
|
||||
|
||||
We require that it is possible to build Firefox using only a checkout of the source,
|
||||
|
|
|
@ -224,9 +224,14 @@ class SubCommand(object):
|
|||
description=None,
|
||||
parser=None,
|
||||
metrics_path: Optional[str] = None,
|
||||
virtualenv_name: Optional[str] = None,
|
||||
):
|
||||
self._mach_command = _MachCommand(
|
||||
name=command, subcommand=subcommand, description=description, parser=parser
|
||||
name=command,
|
||||
subcommand=subcommand,
|
||||
description=description,
|
||||
parser=parser,
|
||||
virtualenv_name=virtualenv_name,
|
||||
)
|
||||
self._mach_command.decl_order = SubCommand.global_order
|
||||
SubCommand.global_order += 1
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
# 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/.
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytoml
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
|
||||
class PoetryLockfiles:
|
||||
def __init__(
|
||||
self,
|
||||
poetry_lockfile: Path,
|
||||
pip_lockfile: Path,
|
||||
):
|
||||
self.poetry_lockfile = poetry_lockfile
|
||||
self.pip_lockfile = pip_lockfile
|
||||
|
||||
|
||||
class PoetryHandle:
|
||||
def __init__(self, work_dir: Path):
|
||||
self._work_dir = work_dir
|
||||
self._dependencies = {}
|
||||
|
||||
def add_requirement(self, requirement: Requirement):
|
||||
self._dependencies[requirement.name] = str(requirement.specifier)
|
||||
|
||||
def add_requirements_in_file(self, requirements_in: Path):
|
||||
with open(requirements_in) as requirements_in:
|
||||
for line in requirements_in.readlines():
|
||||
if line.startswith("#"):
|
||||
continue
|
||||
|
||||
req = Requirement(line)
|
||||
self.add_requirement(req)
|
||||
|
||||
def reuse_existing_lockfile(self, lockfile_path: Path):
|
||||
"""Make minimal number of changes to the lockfile to satisfy new requirements"""
|
||||
shutil.copy(str(lockfile_path), str(self._work_dir / "poetry.lock"))
|
||||
|
||||
def generate_lockfiles(self, do_update):
|
||||
"""Generate pip-style lockfiles that satisfy provided requirements
|
||||
|
||||
One lockfile will be made for all mandatory requirements, and then an extra,
|
||||
compatible lockfile will be created for each optional requirement.
|
||||
|
||||
Args:
|
||||
do_update: if True, then implicitly upgrade the versions of transitive
|
||||
dependencies
|
||||
"""
|
||||
|
||||
poetry_config = {
|
||||
"name": "poetry-test",
|
||||
"description": "",
|
||||
"version": "0",
|
||||
"authors": [],
|
||||
"dependencies": {"python": "^3.6"},
|
||||
}
|
||||
poetry_config["dependencies"].update(self._dependencies)
|
||||
|
||||
pyproject = {"tool": {"poetry": poetry_config}}
|
||||
with open(self._work_dir / "pyproject.toml", "w") as pyproject_file:
|
||||
pytoml.dump(pyproject_file, pyproject)
|
||||
|
||||
self._run_poetry(["lock"] + (["--no-update"] if not do_update else []))
|
||||
self._run_poetry(["export", "-o", "requirements.txt"])
|
||||
|
||||
return PoetryLockfiles(
|
||||
self._work_dir / "poetry.lock",
|
||||
self._work_dir / "requirements.txt",
|
||||
)
|
||||
|
||||
def _run_poetry(self, args):
|
||||
subprocess.check_call(
|
||||
[sys.executable, "-m", "poetry"] + args, cwd=self._work_dir
|
||||
)
|
|
@ -66,8 +66,3 @@ skip-if = python == 2
|
|||
[test_util.py]
|
||||
[test_util_fileavoidwrite.py]
|
||||
[test_vendor.py]
|
||||
# Only run the test on Linux to ensure that the test is stable: depending
|
||||
# on packages, it can be system-dependent.
|
||||
skip-if =
|
||||
os == "win"
|
||||
os == "mac"
|
||||
|
|
|
@ -40,6 +40,7 @@ def test_up_to_date_vendor():
|
|||
os.path.join(topsrcdir, os.path.join("third_party", "python")),
|
||||
os.path.join(work_dir, os.path.join("third_party", "python")),
|
||||
"--exclude=__pycache__",
|
||||
"--strip-trailing-cr",
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -189,6 +189,7 @@ def vendor_rust(command_context, **kwargs):
|
|||
"Some extra files like docs and tests will automatically be excluded."
|
||||
"Installs the packages listed in third_party/python/requirements.in and "
|
||||
"their dependencies.",
|
||||
virtualenv_name="vendor",
|
||||
)
|
||||
@CommandArgument(
|
||||
"--keep-extra-files",
|
||||
|
@ -196,17 +197,8 @@ def vendor_rust(command_context, **kwargs):
|
|||
default=False,
|
||||
help="Keep all files, including tests and documentation.",
|
||||
)
|
||||
def vendor_python(command_context, **kwargs):
|
||||
def vendor_python(command_context, keep_extra_files):
|
||||
from mozbuild.vendor.vendor_python import VendorPython
|
||||
|
||||
if sys.version_info[:2] != (3, 6):
|
||||
print(
|
||||
"You must use Python 3.6 to vendor Python packages. If you don't "
|
||||
"have Python 3.6, you can request that your package be added by "
|
||||
"creating a bug: \n"
|
||||
"https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox%20Build%20System&component=Mach%20Core" # noqa F401
|
||||
)
|
||||
return 1
|
||||
|
||||
vendor_command = command_context._spawn(VendorPython)
|
||||
vendor_command.vendor(**kwargs)
|
||||
vendor_command.vendor(keep_extra_files)
|
||||
|
|
|
@ -11,7 +11,6 @@ import sys
|
|||
from pathlib import Path
|
||||
|
||||
import mozfile
|
||||
import mozpack.path as mozpath
|
||||
from mozbuild.base import MozbuildObject
|
||||
from mozfile import TemporaryDirectory
|
||||
from mozpack.files import FileFinder
|
||||
|
@ -19,47 +18,39 @@ from mozpack.files import FileFinder
|
|||
|
||||
class VendorPython(MozbuildObject):
|
||||
def vendor(self, keep_extra_files=False):
|
||||
from mach.python_lockfile import PoetryHandle
|
||||
|
||||
self.populate_logger()
|
||||
self.log_manager.enable_unstructured()
|
||||
|
||||
vendor_dir = mozpath.join(self.topsrcdir, os.path.join("third_party", "python"))
|
||||
spec = os.path.join(vendor_dir, "requirements.in")
|
||||
requirements = os.path.join(vendor_dir, "requirements.txt")
|
||||
vendor_dir = Path(self.topsrcdir) / "third_party" / "python"
|
||||
requirements_in = vendor_dir / "requirements.in"
|
||||
poetry_lockfile = vendor_dir / "poetry.lock"
|
||||
_sort_requirements_in(requirements_in)
|
||||
|
||||
with TemporaryDirectory() as spec_dir:
|
||||
tmpspec = "requirements-mach-vendor-python.in"
|
||||
tmpspec_absolute = os.path.join(spec_dir, tmpspec)
|
||||
shutil.copyfile(spec, tmpspec_absolute)
|
||||
self._update_packages(tmpspec_absolute)
|
||||
with TemporaryDirectory() as work_dir:
|
||||
work_dir = Path(work_dir)
|
||||
poetry = PoetryHandle(work_dir)
|
||||
poetry.add_requirements_in_file(requirements_in)
|
||||
poetry.reuse_existing_lockfile(poetry_lockfile)
|
||||
lockfiles = poetry.generate_lockfiles(do_update=False)
|
||||
|
||||
tmp_requirements_absolute = os.path.join(spec_dir, "requirements.txt")
|
||||
# Copy the existing "requirements.txt" file so that the versions
|
||||
# of transitive dependencies aren't implicitly changed.
|
||||
shutil.copy(requirements, tmp_requirements_absolute)
|
||||
|
||||
# resolve the dependencies and update requirements.txt.
|
||||
# "--allow-unsafe" is required to vendor pip and setuptools.
|
||||
subprocess.check_output(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"piptools",
|
||||
"compile",
|
||||
tmpspec,
|
||||
"--no-header",
|
||||
"--no-emit-index-url",
|
||||
"--output-file",
|
||||
tmp_requirements_absolute,
|
||||
"--generate-hashes",
|
||||
"--allow-unsafe",
|
||||
],
|
||||
# Run pip-compile from within the temporary directory so that the "via"
|
||||
# annotations don't have the non-deterministic temporary path in them.
|
||||
cwd=spec_dir,
|
||||
# Vendoring packages is only viable if it's possible to have a single
|
||||
# set of packages that work regardless of which environment they're used in.
|
||||
# So, we scrub environment markers, so that we essentially ask pip to
|
||||
# download "all dependencies for all environments". Pip will then either
|
||||
# fetch them as requested, or intelligently raise an error if that's not
|
||||
# possible (e.g.: if different versions of Python would result in different
|
||||
# packages/package versions).
|
||||
pip_lockfile_without_markers = work_dir / "requirements.no-markers.txt"
|
||||
shutil.copy(str(lockfiles.pip_lockfile), str(pip_lockfile_without_markers))
|
||||
remove_environment_markers_from_requirements_txt(
|
||||
pip_lockfile_without_markers
|
||||
)
|
||||
|
||||
with TemporaryDirectory() as tmp:
|
||||
# use requirements.txt to download archived source distributions of all packages
|
||||
# use requirements.txt to download archived source distributions of all
|
||||
# packages
|
||||
subprocess.check_call(
|
||||
[
|
||||
sys.executable,
|
||||
|
@ -67,7 +58,7 @@ class VendorPython(MozbuildObject):
|
|||
"pip",
|
||||
"download",
|
||||
"-r",
|
||||
tmp_requirements_absolute,
|
||||
str(pip_lockfile_without_markers),
|
||||
"--no-deps",
|
||||
"--dest",
|
||||
tmp,
|
||||
|
@ -80,29 +71,10 @@ class VendorPython(MozbuildObject):
|
|||
_purge_vendor_dir(vendor_dir)
|
||||
self._extract(tmp, vendor_dir, keep_extra_files)
|
||||
|
||||
shutil.copyfile(tmpspec_absolute, spec)
|
||||
shutil.copy(tmp_requirements_absolute, requirements)
|
||||
shutil.copy(lockfiles.pip_lockfile, str(vendor_dir / "requirements.txt"))
|
||||
shutil.copy(lockfiles.poetry_lockfile, str(poetry_lockfile))
|
||||
self.repository.add_remove_files(vendor_dir)
|
||||
|
||||
def _update_packages(self, spec):
|
||||
requirements = {}
|
||||
with open(spec, "r") as f:
|
||||
comments = []
|
||||
for line in f.readlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
comments.append(line)
|
||||
continue
|
||||
name, version = line.split("==")
|
||||
requirements[name] = version, comments
|
||||
comments = []
|
||||
|
||||
with open(spec, "w") as f:
|
||||
for name, (version, comments) in sorted(requirements.items()):
|
||||
if comments:
|
||||
f.write("{}\n".format("\n".join(comments)))
|
||||
f.write("{}=={}\n".format(name, version))
|
||||
|
||||
def _extract(self, src, dest, keep_extra_files=False):
|
||||
"""extract source distribution into vendor directory"""
|
||||
|
||||
|
@ -147,6 +119,43 @@ class VendorPython(MozbuildObject):
|
|||
_denormalize_symlinks(package_dir)
|
||||
|
||||
|
||||
def _sort_requirements_in(requirements_in: Path):
|
||||
requirements = {}
|
||||
with open(requirements_in) as f:
|
||||
comments = []
|
||||
for line in f.readlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
comments.append(line)
|
||||
continue
|
||||
name, version = line.split("==")
|
||||
requirements[name] = version, comments
|
||||
comments = []
|
||||
|
||||
with open(requirements_in, "w") as f:
|
||||
for name, (version, comments) in sorted(requirements.items()):
|
||||
if comments:
|
||||
f.write("{}\n".format("\n".join(comments)))
|
||||
f.write("{}=={}\n".format(name, version))
|
||||
|
||||
|
||||
def remove_environment_markers_from_requirements_txt(requirements_txt: Path):
|
||||
with open(requirements_txt) as f:
|
||||
lines = f.readlines()
|
||||
markerless_lines = []
|
||||
for line in lines:
|
||||
if not line.startswith(" ") and not line.startswith("#"):
|
||||
# The first line of each requirement looks something like:
|
||||
# package-name==X.Y; python_version>=3.7
|
||||
# We can scrub the environment marker by splitting on the
|
||||
# semicolon
|
||||
markerless_lines.append(line.split(";")[0])
|
||||
else:
|
||||
markerless_lines.append(line)
|
||||
with open(requirements_txt, "w") as f:
|
||||
f.writelines(markerless_lines)
|
||||
|
||||
|
||||
def _purge_vendor_dir(vendor_dir):
|
||||
excluded_packages = [
|
||||
# dlmanager's package on PyPI only has metadata, but is missing the code.
|
||||
|
@ -159,6 +168,7 @@ def _purge_vendor_dir(vendor_dir):
|
|||
"virtualenv",
|
||||
# The moz.build file isn't a vendored module, so don't delete it.
|
||||
"moz.build",
|
||||
"requirements.in",
|
||||
]
|
||||
|
||||
for child in Path(vendor_dir).iterdir():
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
vendored:third_party/python/glean_parser
|
||||
# Until bug 1724273 lands, testing code that has custom dependencies is not possible. Work around
|
||||
# this by addressing the only use case (so far) affected by this: the testing of vendored
|
||||
# dependencies.
|
||||
packages.txt:python/sites/vendor.txt
|
||||
pypi:pytest==7.0.1
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
pypi:poetry==1.2.0a2
|
||||
# Pin poetry-core so that the same one is used between Python versions.
|
||||
# Otherwise, different versions of poetry-core output different "requirements.txt" contents
|
||||
pypi:poetry-core==1.1.0a6
|
|
@ -1307,10 +1307,10 @@
|
|||
"PASS"
|
||||
],
|
||||
"Page removing and adding event handlers should correctly fire event handlers as they are added and then removed (page.spec.ts)": [
|
||||
"FAIL"
|
||||
"PASS"
|
||||
],
|
||||
"Page removing and adding event handlers should correctly added and removed request events (page.spec.ts)": [
|
||||
"FAIL"
|
||||
"PASS"
|
||||
],
|
||||
"Page Page.Events.error should throw when page crashes (page.spec.ts)": [
|
||||
"TIMEOUT"
|
||||
|
|
|
@ -127,14 +127,20 @@ describe('Page', function () {
|
|||
const { page, server } = getTestState();
|
||||
|
||||
const handler = sinon.spy();
|
||||
page.on('response', handler);
|
||||
const onResponse = (response) => {
|
||||
// Ignore default favicon requests.
|
||||
if (!response.url().endsWith('favicon.ico')) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
page.on('response', onResponse);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect(handler.callCount).toBe(1);
|
||||
page.off('response', handler);
|
||||
page.off('response', onResponse);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
// Still one because we removed the handler.
|
||||
expect(handler.callCount).toBe(1);
|
||||
page.on('response', handler);
|
||||
page.on('response', onResponse);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
// Two now because we added the handler back.
|
||||
expect(handler.callCount).toBe(2);
|
||||
|
@ -144,14 +150,21 @@ describe('Page', function () {
|
|||
const { page, server } = getTestState();
|
||||
|
||||
const handler = sinon.spy();
|
||||
page.on('request', handler);
|
||||
const onResponse = (response) => {
|
||||
// Ignore default favicon requests.
|
||||
if (!response.url().endsWith('favicon.ico')) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
||||
page.on('request', onResponse);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect(handler.callCount).toBe(1);
|
||||
page.off('request', handler);
|
||||
page.off('request', onResponse);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
// Still one because we removed the handler.
|
||||
expect(handler.callCount).toBe(1);
|
||||
page.on('request', handler);
|
||||
page.on('request', onResponse);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
// Two now because we added the handler back.
|
||||
expect(handler.callCount).toBe(2);
|
||||
|
|
|
@ -482,3 +482,23 @@ sm-gdb-linux64/debug:
|
|||
- linux64-minidump-stackwalk
|
||||
- linux64-llvm-symbolizer
|
||||
- sysroot-x86_64-linux-gnu
|
||||
|
||||
sm-rt-linux64/debug:
|
||||
description: "SpiderMonkey Records&Tuples"
|
||||
index:
|
||||
job-name: sm-rt-linux64-debug
|
||||
treeherder:
|
||||
platform: linux64/debug
|
||||
symbol: SM(rt)
|
||||
run:
|
||||
spidermonkey-variant: rtdebug
|
||||
fetches:
|
||||
toolchain:
|
||||
- linux64-clang
|
||||
- linux64-cbindgen
|
||||
- linux64-dump_syms
|
||||
- linux64-breakpad-injector
|
||||
- linux64-minidump-stackwalk
|
||||
- linux64-llvm-symbolizer
|
||||
- linux64-rust
|
||||
- sysroot-x86_64-linux-gnu
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -2,10 +2,6 @@ appdirs==1.4.4
|
|||
attrs==19.2.0
|
||||
blessings==1.7
|
||||
cbor2==4.0.1
|
||||
# Though we don't depend on colorama directly, we need to explicitly
|
||||
# define it here because it's needed by other dependencies on
|
||||
# Windows systems.
|
||||
colorama==0.4.4
|
||||
compare-locales==8.2.1
|
||||
cookies==2.2.1
|
||||
cram==0.7
|
||||
|
|
|
@ -1,498 +1,392 @@
|
|||
aiohttp==3.7.4.post0 \
|
||||
--hash=sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe \
|
||||
aiohttp==3.7.4.post0; python_version >= "3.6" \
|
||||
--hash=sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5 \
|
||||
--hash=sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8 \
|
||||
--hash=sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95 \
|
||||
--hash=sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290 \
|
||||
--hash=sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f \
|
||||
--hash=sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809 \
|
||||
--hash=sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe \
|
||||
--hash=sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5 \
|
||||
--hash=sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8 \
|
||||
--hash=sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd \
|
||||
--hash=sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb \
|
||||
--hash=sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c \
|
||||
--hash=sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287 \
|
||||
--hash=sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc \
|
||||
--hash=sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87 \
|
||||
--hash=sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0 \
|
||||
--hash=sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290 \
|
||||
--hash=sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5 \
|
||||
--hash=sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287 \
|
||||
--hash=sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde \
|
||||
--hash=sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf \
|
||||
--hash=sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8 \
|
||||
--hash=sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16 \
|
||||
--hash=sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf \
|
||||
--hash=sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809 \
|
||||
--hash=sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213 \
|
||||
--hash=sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f \
|
||||
--hash=sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013 \
|
||||
--hash=sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b \
|
||||
--hash=sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9 \
|
||||
--hash=sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5 \
|
||||
--hash=sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb \
|
||||
--hash=sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df \
|
||||
--hash=sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4 \
|
||||
--hash=sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439 \
|
||||
--hash=sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f \
|
||||
--hash=sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22 \
|
||||
--hash=sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f \
|
||||
--hash=sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5 \
|
||||
--hash=sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970 \
|
||||
--hash=sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f \
|
||||
--hash=sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde \
|
||||
--hash=sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c \
|
||||
--hash=sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8 \
|
||||
--hash=sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f \
|
||||
--hash=sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5 \
|
||||
--hash=sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf \
|
||||
--hash=sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df \
|
||||
--hash=sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213 \
|
||||
--hash=sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4 \
|
||||
--hash=sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009 \
|
||||
--hash=sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc \
|
||||
--hash=sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5 \
|
||||
--hash=sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013 \
|
||||
--hash=sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16 \
|
||||
--hash=sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5 \
|
||||
--hash=sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b \
|
||||
--hash=sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd \
|
||||
--hash=sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439 \
|
||||
--hash=sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22 \
|
||||
--hash=sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a \
|
||||
--hash=sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95
|
||||
# via taskcluster
|
||||
--hash=sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb \
|
||||
--hash=sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb \
|
||||
--hash=sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9 \
|
||||
--hash=sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe \
|
||||
--hash=sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf
|
||||
appdirs==1.4.4 \
|
||||
--hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 \
|
||||
--hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# glean-parser
|
||||
# taskcluster-taskgraph
|
||||
async-timeout==3.0.1 \
|
||||
--hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 \
|
||||
--hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41
|
||||
async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" \
|
||||
--hash=sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f \
|
||||
--hash=sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3
|
||||
# via
|
||||
# aiohttp
|
||||
# taskcluster
|
||||
attrs==19.2.0 \
|
||||
attrs==19.2.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
|
||||
--hash=sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2 \
|
||||
--hash=sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# aiohttp
|
||||
# jsonschema
|
||||
# mozilla-version
|
||||
# taskcluster-taskgraph
|
||||
blessings==1.7 \
|
||||
--hash=sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d \
|
||||
blessings==1.7; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
|
||||
--hash=sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e \
|
||||
--hash=sha256:b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3 \
|
||||
--hash=sha256:caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
--hash=sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d
|
||||
cbor2==4.0.1 \
|
||||
--hash=sha256:b0eb916c9ea226aa81e9091607737475d5b0e5c314fe8d5a87179fba449cd190 \
|
||||
--hash=sha256:cee0d01e520563b5a73c72eace5c428bb68aefb1b3f7aee5d692d3af6a1e5172
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
certifi==2018.4.16 \
|
||||
--hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7 \
|
||||
--hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0
|
||||
# via
|
||||
# requests
|
||||
# sentry-sdk
|
||||
chardet==4.0.0 \
|
||||
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
|
||||
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
|
||||
# via
|
||||
# aiohttp
|
||||
# requests
|
||||
click==7.1.2 \
|
||||
--hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
|
||||
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc
|
||||
# via
|
||||
# glean-parser
|
||||
# pip-tools
|
||||
colorama==0.4.4 \
|
||||
--hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b \
|
||||
--hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
compare-locales==8.2.1 \
|
||||
--hash=sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0 \
|
||||
--hash=sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7
|
||||
chardet==4.0.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" \
|
||||
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \
|
||||
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa
|
||||
click==7.1.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
|
||||
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
|
||||
--hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a
|
||||
colorama==0.4.4; python_version >= "2.7" and python_full_version < "3.0.0" and platform_system == "Windows" or python_full_version >= "3.5.0" and platform_system == "Windows" \
|
||||
--hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 \
|
||||
--hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b
|
||||
compare-locales==8.2.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") \
|
||||
--hash=sha256:470d50d96c68f8e147daa3d70f29a7b750adefea450c5fa07e0f666c8083d854 \
|
||||
--hash=sha256:e6a1610151d357e74ee6c1f5e944f1868e449f83e478c84d92f7b86132f721d7
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# fluent.migrate
|
||||
cookies==2.2.1 \
|
||||
--hash=sha256:15bee753002dff684987b8df8c235288eb8d45f8191ae056254812dfd42c81d3 \
|
||||
--hash=sha256:d6b698788cae4cfa4e62ef8643a9ca332b79bd96cb314294b864ae8d7eb3ee8e
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
cram==0.7 \
|
||||
--hash=sha256:008e4e8b4d325cf040964b5f62460535b004a7bc816d54f8527a4d299edfe4a3 \
|
||||
--hash=sha256:7da7445af2ce15b90aad5ec4792f857cef5786d71f14377e9eb994d8b8337f2f
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
diskcache==4.1.0 \
|
||||
--hash=sha256:69b253a6ffe95bb4bafb483b97c24fca3c2c6c47b82e92b36486969a7e80d47d \
|
||||
--hash=sha256:bcee5a59f9c264e2809e58d01be6569a3bbb1e36a1e0fb83f7ef9b2075f95ce0
|
||||
# via glean-parser
|
||||
distro==1.4.0 \
|
||||
--hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \
|
||||
--hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
ecdsa==0.15 \
|
||||
--hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 \
|
||||
--hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57
|
||||
ecdsa==0.15; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0") \
|
||||
--hash=sha256:867ec9cf6df0b03addc8ef66b56359643cb5d0c1dc329df76ba7ecfe256c8061 \
|
||||
--hash=sha256:8f12ac317f8a1318efa75757ef0a651abe12e51fc1af8838fb91079445227277
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
esprima==4.0.1 \
|
||||
--hash=sha256:08db1a876d3c2910db9cfaeb83108193af5411fc3a3a66ebefacd390d21323ee
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
fluent.migrate==0.11 \
|
||||
--hash=sha256:3b93fdba9cbc8702d160367ba3a0d5c120707fdde752af35aecf516ce80ed252
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
fluent.syntax==0.18.1 \
|
||||
--hash=sha256:0e63679fa4f1b3042565220a5127b4bab842424f07d6a13c12299e3b3835486a \
|
||||
--hash=sha256:3a55f5e605d1b029a65cc8b6492c86ec4608e15447e73db1495de11fd46c104f
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# compare-locales
|
||||
# fluent.migrate
|
||||
glean_parser==5.0.1 \
|
||||
--hash=sha256:309f36ed55f2f1ed82472ab8b18e1dd01edeb71060464e3f859c9b60e38b87d4 \
|
||||
--hash=sha256:7357aac9a857883db99121abb15b9fdb9bcb5437cecee28c9b8a09a2123408ed
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
idna-ssl==1.1.0 \
|
||||
glean-parser==5.0.1 \
|
||||
--hash=sha256:7357aac9a857883db99121abb15b9fdb9bcb5437cecee28c9b8a09a2123408ed \
|
||||
--hash=sha256:309f36ed55f2f1ed82472ab8b18e1dd01edeb71060464e3f859c9b60e38b87d4
|
||||
idna-ssl==1.1.0; python_version < "3.7" and python_version >= "3.6" \
|
||||
--hash=sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c
|
||||
# via aiohttp
|
||||
idna==2.10 \
|
||||
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
|
||||
--hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
|
||||
# via
|
||||
# idna-ssl
|
||||
# requests
|
||||
# yarl
|
||||
importlib-metadata==1.7.0 \
|
||||
--hash=sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83 \
|
||||
--hash=sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# jsonschema
|
||||
iso8601==0.1.14 \
|
||||
--hash=sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79 \
|
||||
--hash=sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004
|
||||
# via glean-parser
|
||||
jinja2==2.11.3 \
|
||||
idna==2.10; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.4.0" or python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" \
|
||||
--hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \
|
||||
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6
|
||||
importlib-metadata==1.7.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
|
||||
--hash=sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070 \
|
||||
--hash=sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83
|
||||
iso8601==0.1.14; python_version <= "3.6" \
|
||||
--hash=sha256:e7e1122f064d626e17d47cd5106bed2c620cb38fe464999e0ddae2b6d2de6004 \
|
||||
--hash=sha256:8aafd56fa0290496c5edbb13c311f78fa3a241f0853540da09d9363eae3ebd79
|
||||
jinja2==2.11.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
|
||||
--hash=sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419 \
|
||||
--hash=sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6
|
||||
# via glean-parser
|
||||
jsmin==2.1.0 \
|
||||
--hash=sha256:5d07bf0251a4128e5e8e8eef603849b6b5741c337bff087731a248f9cc774f56
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
json-e==2.7.0 \
|
||||
--hash=sha256:d8c1ec3f5bbc7728c3a504ebe58829f283c64eca230871e4eefe974b4cdaae4a
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# taskcluster-taskgraph
|
||||
jsonschema==3.2.0 \
|
||||
--hash=sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163 \
|
||||
--hash=sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a
|
||||
# via glean-parser
|
||||
markupsafe==1.1.1 \
|
||||
--hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
|
||||
markupsafe==1.1.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
|
||||
--hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \
|
||||
--hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \
|
||||
--hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \
|
||||
--hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \
|
||||
--hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
|
||||
--hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \
|
||||
--hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \
|
||||
--hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
|
||||
--hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \
|
||||
--hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
|
||||
--hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \
|
||||
--hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \
|
||||
--hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \
|
||||
--hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
|
||||
--hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
|
||||
--hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \
|
||||
--hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \
|
||||
--hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \
|
||||
--hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
|
||||
--hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \
|
||||
--hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
|
||||
--hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
|
||||
--hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \
|
||||
--hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
|
||||
--hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
|
||||
--hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \
|
||||
--hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
|
||||
--hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \
|
||||
--hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \
|
||||
--hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \
|
||||
--hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \
|
||||
--hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be
|
||||
# via jinja2
|
||||
--hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \
|
||||
--hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \
|
||||
--hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \
|
||||
--hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \
|
||||
--hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \
|
||||
--hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \
|
||||
--hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \
|
||||
--hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \
|
||||
--hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \
|
||||
--hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \
|
||||
--hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \
|
||||
--hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \
|
||||
--hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \
|
||||
--hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
|
||||
--hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
|
||||
--hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
|
||||
--hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \
|
||||
--hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
|
||||
--hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
|
||||
--hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \
|
||||
--hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \
|
||||
--hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \
|
||||
--hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
|
||||
--hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
|
||||
--hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
|
||||
--hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \
|
||||
--hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
|
||||
--hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
|
||||
--hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \
|
||||
--hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \
|
||||
--hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \
|
||||
--hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
|
||||
--hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
|
||||
--hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \
|
||||
--hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \
|
||||
--hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \
|
||||
--hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \
|
||||
--hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \
|
||||
--hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \
|
||||
--hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \
|
||||
--hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \
|
||||
--hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \
|
||||
--hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \
|
||||
--hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \
|
||||
--hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \
|
||||
--hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \
|
||||
--hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 \
|
||||
--hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \
|
||||
--hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \
|
||||
--hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b
|
||||
mohawk==0.3.4 \
|
||||
--hash=sha256:b3f85ffa93a5c7d2f9cc591246ef9f8ac4a9fa716bfd5bae0377699a2d89d78c \
|
||||
--hash=sha256:e98b331d9fa9ece7b8be26094cbe2d57613ae882133cc755167268a984bc0ab3
|
||||
# via taskcluster
|
||||
mozilla-version==0.3.4 \
|
||||
--hash=sha256:3ed4deb7a6fb25c83a5346ef4de08ddff9b2ddc4d16dd8fafb4a84978cc71255 \
|
||||
--hash=sha256:ce5741c2e7d12c30b53de9f79e30d6ac2a8bd4c93be711d30c7a7a08e32a094f
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
multidict==5.1.0 \
|
||||
--hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \
|
||||
--hash=sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93 \
|
||||
--hash=sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632 \
|
||||
--hash=sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656 \
|
||||
--hash=sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79 \
|
||||
--hash=sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7 \
|
||||
multidict==5.1.0; python_version >= "3.6" \
|
||||
--hash=sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f \
|
||||
--hash=sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf \
|
||||
--hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \
|
||||
--hash=sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d \
|
||||
--hash=sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5 \
|
||||
--hash=sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d \
|
||||
--hash=sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da \
|
||||
--hash=sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224 \
|
||||
--hash=sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26 \
|
||||
--hash=sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea \
|
||||
--hash=sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348 \
|
||||
--hash=sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6 \
|
||||
--hash=sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76 \
|
||||
--hash=sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1 \
|
||||
--hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \
|
||||
--hash=sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f \
|
||||
--hash=sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952 \
|
||||
--hash=sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a \
|
||||
--hash=sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37 \
|
||||
--hash=sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348 \
|
||||
--hash=sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93 \
|
||||
--hash=sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9 \
|
||||
--hash=sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359 \
|
||||
--hash=sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8 \
|
||||
--hash=sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da \
|
||||
--hash=sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3 \
|
||||
--hash=sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d \
|
||||
--hash=sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf \
|
||||
--hash=sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841 \
|
||||
--hash=sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d \
|
||||
--hash=sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93 \
|
||||
--hash=sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f \
|
||||
--hash=sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647 \
|
||||
--hash=sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635 \
|
||||
--hash=sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456 \
|
||||
--hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \
|
||||
--hash=sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37 \
|
||||
--hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \
|
||||
--hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \
|
||||
--hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
packaging==20.9 \
|
||||
--hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5 \
|
||||
--hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
pathspec==0.9.0 \
|
||||
--hash=sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632 \
|
||||
--hash=sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952 \
|
||||
--hash=sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79 \
|
||||
--hash=sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456 \
|
||||
--hash=sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7 \
|
||||
--hash=sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635 \
|
||||
--hash=sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a \
|
||||
--hash=sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea \
|
||||
--hash=sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656 \
|
||||
--hash=sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3 \
|
||||
--hash=sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93 \
|
||||
--hash=sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647 \
|
||||
--hash=sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d \
|
||||
--hash=sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8 \
|
||||
--hash=sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1 \
|
||||
--hash=sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841 \
|
||||
--hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \
|
||||
--hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80 \
|
||||
--hash=sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359 \
|
||||
--hash=sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5
|
||||
packaging==20.9; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
|
||||
--hash=sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a \
|
||||
--hash=sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5
|
||||
pathspec==0.9.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
|
||||
--hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
|
||||
--hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# yamllint
|
||||
pip-tools==5.5.0 \
|
||||
--hash=sha256:10841c1e56c234d610d0466447685b9ea4ee4a2c274f858c0ef3c33d9bd0d985 \
|
||||
--hash=sha256:cb0108391366b3ef336185097b3c2c0f3fa115b15098dafbda5e78aef70ea114
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
pip-tools==5.5.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
|
||||
--hash=sha256:cb0108391366b3ef336185097b3c2c0f3fa115b15098dafbda5e78aef70ea114 \
|
||||
--hash=sha256:10841c1e56c234d610d0466447685b9ea4ee4a2c274f858c0ef3c33d9bd0d985
|
||||
pip==21.2.4; python_version >= "3.6" \
|
||||
--hash=sha256:fa9ebb85d3fd607617c0c44aca302b1b45d87f9c2a1649b46c26167ca4296323 \
|
||||
--hash=sha256:0eb8a1516c3d138ae8689c0c1a60fde7143310832f9dc77e11d8a4bc62de193b
|
||||
ply==3.10 \
|
||||
--hash=sha256:96e94af7dd7031d8d6dd6e2a8e0de593b511c211a86e28a9c9621c275ac8bacb
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
pyasn1-modules==0.2.8 \
|
||||
--hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \
|
||||
--hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
--hash=sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199 \
|
||||
--hash=sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405 \
|
||||
--hash=sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb \
|
||||
--hash=sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8 \
|
||||
--hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 \
|
||||
--hash=sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d \
|
||||
--hash=sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45 \
|
||||
--hash=sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4 \
|
||||
--hash=sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811 \
|
||||
--hash=sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed \
|
||||
--hash=sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0 \
|
||||
--hash=sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd
|
||||
pyasn1==0.4.8 \
|
||||
--hash=sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3 \
|
||||
--hash=sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf \
|
||||
--hash=sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00 \
|
||||
--hash=sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8 \
|
||||
--hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \
|
||||
--hash=sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86 \
|
||||
--hash=sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7 \
|
||||
--hash=sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576 \
|
||||
--hash=sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12 \
|
||||
--hash=sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2 \
|
||||
--hash=sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359 \
|
||||
--hash=sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776 \
|
||||
--hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# pyasn1-modules
|
||||
# rsa
|
||||
pylru==1.0.9 \
|
||||
--hash=sha256:71376192671f0ad1690b2a7427d39a29b1df994c8469a9b46b03ed7e28c0172c
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
pyparsing==2.4.7 \
|
||||
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \
|
||||
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b
|
||||
# via packaging
|
||||
pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" \
|
||||
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \
|
||||
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1
|
||||
pyrsistent==0.16.0 \
|
||||
--hash=sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3
|
||||
# via jsonschema
|
||||
python-hglib==2.4 \
|
||||
--hash=sha256:693d6ed92a6566e78802c7a03c256cda33d08c63ad3f00fcfa11379b184b9462
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
pytoml==0.1.10 \
|
||||
--hash=sha256:98399eabd927cd3e12457525315b6abbc5abf9a6f392ab578cbcec327f73890c
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# compare-locales
|
||||
pyyaml==5.4.1 \
|
||||
--hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
|
||||
--hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
|
||||
--hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \
|
||||
--hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \
|
||||
pyyaml==5.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") \
|
||||
--hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \
|
||||
--hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \
|
||||
--hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \
|
||||
--hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \
|
||||
--hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \
|
||||
--hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \
|
||||
--hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \
|
||||
--hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e \
|
||||
--hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \
|
||||
--hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \
|
||||
--hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \
|
||||
--hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \
|
||||
--hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \
|
||||
--hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \
|
||||
--hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \
|
||||
--hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \
|
||||
--hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \
|
||||
--hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \
|
||||
--hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \
|
||||
--hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \
|
||||
--hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \
|
||||
--hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \
|
||||
--hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \
|
||||
--hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \
|
||||
--hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \
|
||||
--hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \
|
||||
--hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \
|
||||
--hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
|
||||
--hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \
|
||||
--hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \
|
||||
--hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \
|
||||
--hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 \
|
||||
--hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \
|
||||
--hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
|
||||
--hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \
|
||||
--hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \
|
||||
--hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \
|
||||
--hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \
|
||||
--hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
|
||||
--hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
|
||||
--hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \
|
||||
--hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \
|
||||
--hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \
|
||||
--hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \
|
||||
--hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# glean-parser
|
||||
# taskcluster-taskgraph
|
||||
# yamllint
|
||||
--hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \
|
||||
--hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \
|
||||
--hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e
|
||||
redo==2.0.3 \
|
||||
--hash=sha256:36784bf8ae766e14f9db0e377ccfa02835d648321d2007b6ae0bf4fd612c0f94 \
|
||||
--hash=sha256:71161cb0e928d824092a5f16203939bbc0867ce4c4685db263cf22c3ae7634a8
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# taskcluster-taskgraph
|
||||
requests-unixsocket==0.2.0 \
|
||||
--hash=sha256:014d07bfb66dc805a011a8b4b306cf4ec96d2eddb589f6b2b5765e626f0dc0cc \
|
||||
--hash=sha256:9e5c1a20afc3cf786197ae59c79bcdb0e7565f218f27df5f891307ee8817c1ea
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# taskcluster-taskgraph
|
||||
requests==2.25.1 \
|
||||
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
|
||||
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# requests-unixsocket
|
||||
# responses
|
||||
# taskcluster
|
||||
# taskcluster-taskgraph
|
||||
responses==0.10.6 \
|
||||
--hash=sha256:502d9c0c8008439cfcdef7e251f507fcfdd503b56e8c0c87c3c3e3393953f790 \
|
||||
--hash=sha256:97193c0183d63fba8cd3a041c75464e4b09ea0aff6328800d1546598567dde0b
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
--hash=sha256:9e5c1a20afc3cf786197ae59c79bcdb0e7565f218f27df5f891307ee8817c1ea \
|
||||
--hash=sha256:014d07bfb66dc805a011a8b4b306cf4ec96d2eddb589f6b2b5765e626f0dc0cc
|
||||
requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
|
||||
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \
|
||||
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804
|
||||
responses==0.10.6; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
|
||||
--hash=sha256:97193c0183d63fba8cd3a041c75464e4b09ea0aff6328800d1546598567dde0b \
|
||||
--hash=sha256:502d9c0c8008439cfcdef7e251f507fcfdd503b56e8c0c87c3c3e3393953f790
|
||||
rsa==3.1.4 \
|
||||
--hash=sha256:e2b0b05936c276b1edd2e1525553233b666df9e29b5c3ba223eed738277c82a0
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
sentry-sdk==0.14.3 \
|
||||
--hash=sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f \
|
||||
--hash=sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
six==1.13.0 \
|
||||
--hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \
|
||||
--hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# blessings
|
||||
# compare-locales
|
||||
# ecdsa
|
||||
# fluent.migrate
|
||||
# jsonschema
|
||||
# mohawk
|
||||
# pyrsistent
|
||||
# responses
|
||||
# taskcluster
|
||||
slugid==2.0.0 \
|
||||
--hash=sha256:a950d98b72691178bdd4d6c52743c4a2aa039207cf7a97d71060a111ff9ba297 \
|
||||
--hash=sha256:aec8b0e01c4ad32e38e12d609eab3ec912fd129aaf6b2ded0199b56a5f8fd67c
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# taskcluster
|
||||
# taskcluster-taskgraph
|
||||
taskcluster-taskgraph==1.2.0 \
|
||||
--hash=sha256:ac7b0345b5b8752c5b31c516c9d798d7168bfda8963774def972b0beee349f6e \
|
||||
--hash=sha256:bce1ef59f15b317256214da4d82f19d745bdc1b4497561d753436f4efa32ea39
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
taskcluster-urls==13.0.1 \
|
||||
--hash=sha256:5e25e7e6818e8877178b175ff43d2e6548afad72694aa125f404a7329ece0973 \
|
||||
--hash=sha256:b25e122ecec249c4299ac7b20b08db76e3e2025bdaeb699a9d444556de5fd367 \
|
||||
--hash=sha256:f66dcbd6572a6216ab65949f0fa0b91f2df647918028436c384e6af5cd12ae2b
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# taskcluster
|
||||
# taskcluster-taskgraph
|
||||
taskcluster==44.2.2 \
|
||||
--hash=sha256:0266a6a901e1a2ec838984a7f24e7adb6d58f9f2e221a7f613388f8f23f786fc \
|
||||
--hash=sha256:846d73c597f0f47dd8525c85c8d9bc41111d5200b090690d3f16b2f57c56a2e1 \
|
||||
--hash=sha256:c1b0e82be25b1ed17e07c90b24a382634b2bfce273fdf2682d94568abe10716c
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
tqdm==4.62.3 \
|
||||
--hash=sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c \
|
||||
--hash=sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
typing-extensions==3.10.0.0 \
|
||||
--hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \
|
||||
--hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342 \
|
||||
--hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84
|
||||
# via
|
||||
# aiohttp
|
||||
# yarl
|
||||
urllib3==1.26.0 \
|
||||
--hash=sha256:4849f132941d68144df0a3785ccc4fe423430ba5db0108d045c8cadbc90f517a \
|
||||
--hash=sha256:bad31cb622ceee0ab46c4c884cf61957def0ff2e644de0a7a093678844c9ccac
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# requests
|
||||
# requests-unixsocket
|
||||
# sentry-sdk
|
||||
voluptuous==0.12.1 \
|
||||
--hash=sha256:663572419281ddfaf4b4197fd4942d181630120fb39b333e3adad70aeb56444b \
|
||||
--hash=sha256:8ace33fcf9e6b1f59406bfaf6b8ec7bcc44266a9f29080b4deb4fe6ff2492386
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# taskcluster-taskgraph
|
||||
wheel==0.37.0 \
|
||||
--hash=sha256:21014b2bd93c6d0034b6ba5d35e4eb284340e09d63c59aef6fc14b0f346146fd \
|
||||
--hash=sha256:e2ef7239991699e3355d54f8e968a21bb940a1dbf34a4d226741e64462516fad
|
||||
# via -r requirements-mach-vendor-python.in
|
||||
yamllint==1.23 \
|
||||
--hash=sha256:0fa69bf8a86182b7fe14918bdd3a30354c869966bbc7cbfff176af71bda9c806 \
|
||||
--hash=sha256:59f3ff77f44e7f46be6aecdb985830f73a1c51e290b7082a7d38c2ae1940f4a9
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# glean-parser
|
||||
yarl==1.6.3 \
|
||||
--hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \
|
||||
--hash=sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434 \
|
||||
--hash=sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366 \
|
||||
--hash=sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3 \
|
||||
--hash=sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec \
|
||||
--hash=sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959 \
|
||||
--hash=sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e \
|
||||
--hash=sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c \
|
||||
--hash=sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6 \
|
||||
--hash=sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a \
|
||||
--hash=sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6 \
|
||||
--hash=sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424 \
|
||||
--hash=sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e \
|
||||
--hash=sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f \
|
||||
--hash=sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50 \
|
||||
--hash=sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2 \
|
||||
--hash=sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc \
|
||||
--hash=sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4 \
|
||||
--hash=sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970 \
|
||||
--hash=sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10 \
|
||||
--hash=sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0 \
|
||||
--hash=sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406 \
|
||||
--hash=sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896 \
|
||||
--hash=sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643 \
|
||||
--hash=sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721 \
|
||||
--hash=sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478 \
|
||||
--hash=sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724 \
|
||||
--hash=sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e \
|
||||
--hash=sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8 \
|
||||
--hash=sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96 \
|
||||
--hash=sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25 \
|
||||
--hash=sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76 \
|
||||
--hash=sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2 \
|
||||
--hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \
|
||||
--hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \
|
||||
--hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \
|
||||
--hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71
|
||||
# via aiohttp
|
||||
zipp==3.4.1 \
|
||||
--hash=sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76 \
|
||||
--hash=sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098
|
||||
# via importlib-metadata
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==21.2.4 \
|
||||
--hash=sha256:0eb8a1516c3d138ae8689c0c1a60fde7143310832f9dc77e11d8a4bc62de193b \
|
||||
--hash=sha256:fa9ebb85d3fd607617c0c44aca302b1b45d87f9c2a1649b46c26167ca4296323
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# pip-tools
|
||||
setuptools==51.2.0 \
|
||||
--hash=sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950 \
|
||||
--hash=sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f
|
||||
setuptools==51.2.0; python_version >= "3.6" \
|
||||
--hash=sha256:56948bf25c682e166cf2bfe7c1ad63e5745849b50d1ae7b0f8bff5decdcf34f2 \
|
||||
--hash=sha256:7ef59b1790b3491f8d321f531eccc11517a07a4d7637e498465cd834d80d4c2c
|
||||
# via
|
||||
# -r requirements-mach-vendor-python.in
|
||||
# jsonschema
|
||||
six==1.13.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.2.0") \
|
||||
--hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \
|
||||
--hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66
|
||||
slugid==2.0.0 \
|
||||
--hash=sha256:aec8b0e01c4ad32e38e12d609eab3ec912fd129aaf6b2ded0199b56a5f8fd67c \
|
||||
--hash=sha256:a950d98b72691178bdd4d6c52743c4a2aa039207cf7a97d71060a111ff9ba297
|
||||
taskcluster-taskgraph==1.2.0 \
|
||||
--hash=sha256:bce1ef59f15b317256214da4d82f19d745bdc1b4497561d753436f4efa32ea39 \
|
||||
--hash=sha256:ac7b0345b5b8752c5b31c516c9d798d7168bfda8963774def972b0beee349f6e
|
||||
taskcluster-urls==13.0.1 \
|
||||
--hash=sha256:b25e122ecec249c4299ac7b20b08db76e3e2025bdaeb699a9d444556de5fd367 \
|
||||
--hash=sha256:5e25e7e6818e8877178b175ff43d2e6548afad72694aa125f404a7329ece0973 \
|
||||
--hash=sha256:f66dcbd6572a6216ab65949f0fa0b91f2df647918028436c384e6af5cd12ae2b
|
||||
taskcluster==44.2.2 \
|
||||
--hash=sha256:c1b0e82be25b1ed17e07c90b24a382634b2bfce273fdf2682d94568abe10716c \
|
||||
--hash=sha256:846d73c597f0f47dd8525c85c8d9bc41111d5200b090690d3f16b2f57c56a2e1 \
|
||||
--hash=sha256:0266a6a901e1a2ec838984a7f24e7adb6d58f9f2e221a7f613388f8f23f786fc
|
||||
tqdm==4.62.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
|
||||
--hash=sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c \
|
||||
--hash=sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d
|
||||
typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" or python_version >= "3.6" \
|
||||
--hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \
|
||||
--hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \
|
||||
--hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342
|
||||
urllib3==1.26.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0" and python_version < "4") \
|
||||
--hash=sha256:bad31cb622ceee0ab46c4c884cf61957def0ff2e644de0a7a093678844c9ccac \
|
||||
--hash=sha256:4849f132941d68144df0a3785ccc4fe423430ba5db0108d045c8cadbc90f517a
|
||||
voluptuous==0.12.1 \
|
||||
--hash=sha256:8ace33fcf9e6b1f59406bfaf6b8ec7bcc44266a9f29080b4deb4fe6ff2492386 \
|
||||
--hash=sha256:663572419281ddfaf4b4197fd4942d181630120fb39b333e3adad70aeb56444b
|
||||
wheel==0.37.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
|
||||
--hash=sha256:21014b2bd93c6d0034b6ba5d35e4eb284340e09d63c59aef6fc14b0f346146fd \
|
||||
--hash=sha256:e2ef7239991699e3355d54f8e968a21bb940a1dbf34a4d226741e64462516fad
|
||||
yamllint==1.23.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
|
||||
--hash=sha256:0fa69bf8a86182b7fe14918bdd3a30354c869966bbc7cbfff176af71bda9c806 \
|
||||
--hash=sha256:59f3ff77f44e7f46be6aecdb985830f73a1c51e290b7082a7d38c2ae1940f4a9
|
||||
yarl==1.6.3; python_version >= "3.6" \
|
||||
--hash=sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434 \
|
||||
--hash=sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478 \
|
||||
--hash=sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6 \
|
||||
--hash=sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e \
|
||||
--hash=sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406 \
|
||||
--hash=sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76 \
|
||||
--hash=sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366 \
|
||||
--hash=sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721 \
|
||||
--hash=sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643 \
|
||||
--hash=sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e \
|
||||
--hash=sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3 \
|
||||
--hash=sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8 \
|
||||
--hash=sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a \
|
||||
--hash=sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c \
|
||||
--hash=sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f \
|
||||
--hash=sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970 \
|
||||
--hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \
|
||||
--hash=sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50 \
|
||||
--hash=sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2 \
|
||||
--hash=sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec \
|
||||
--hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 \
|
||||
--hash=sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc \
|
||||
--hash=sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959 \
|
||||
--hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \
|
||||
--hash=sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2 \
|
||||
--hash=sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896 \
|
||||
--hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \
|
||||
--hash=sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e \
|
||||
--hash=sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724 \
|
||||
--hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \
|
||||
--hash=sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25 \
|
||||
--hash=sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96 \
|
||||
--hash=sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0 \
|
||||
--hash=sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4 \
|
||||
--hash=sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424 \
|
||||
--hash=sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6 \
|
||||
--hash=sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10
|
||||
zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" or python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.5.0" and python_version < "3.8" and python_version >= "3.6" \
|
||||
--hash=sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098 \
|
||||
--hash=sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76
|
||||
|
|
|
@ -1875,6 +1875,7 @@ StartupCache = {
|
|||
"other",
|
||||
"permissions",
|
||||
"schemas",
|
||||
"menus",
|
||||
]),
|
||||
|
||||
_ensureDirectoryPromise: null,
|
||||
|
@ -1956,6 +1957,7 @@ StartupCache = {
|
|||
this.locales.delete(id),
|
||||
this.manifests.delete(id),
|
||||
this.permissions.delete(id),
|
||||
this.menus.delete(id),
|
||||
]).catch(e => {
|
||||
// Ignore the error. It happens when we try to flush the add-on
|
||||
// data after the AddonManager has flushed the entire startup cache.
|
||||
|
|
|
@ -74,6 +74,14 @@ urlbar:
|
|||
type: boolean
|
||||
fallbackPref: browser.urlbar.quicksuggest.enabled
|
||||
description: Gate for the Firefox Suggest feature as a whole. If false, the Firefox Suggest preferences UI and Suggest suggestions will not be shown. If true, the preferences UI will be shown, and the user can turn suggestions on or off.
|
||||
quickSuggestImpressionCapsSponsoredEnabled:
|
||||
type: boolean
|
||||
fallbackPref: browser.urlbar.quicksuggest.impressionCaps.sponsoredEnabled
|
||||
description: Whether sponsored suggestions are subject to impression frequency caps. If false, sponsored suggestions can be shown an unlimited number of times over any given period. If true, sponsored suggestion impressions will be subject to the caps in the remote settings configuration.
|
||||
quickSuggestImpressionCapsNonSponsoredEnabled:
|
||||
type: boolean
|
||||
fallbackPref: browser.urlbar.quicksuggest.impressionCaps.nonSponsoredEnabled
|
||||
description: Whether non-sponsored suggestions are subject to impression frequency caps. If false, non-sponsored suggestions can be shown an unlimited number of times over any given period. If true, non-sponsored suggestion impressions will be subject to the caps in the remote settings configuration.
|
||||
quickSuggestNonSponsoredEnabled:
|
||||
type: boolean
|
||||
description: Whether non-sponsored suggestions should be enabled by default. If this variable is specified, it will override the value implied by the scenario. It will never override the user's local preference to disable (or enable) non-sponsored suggestions, if the user has already toggled that preference.
|
||||
|
|
|
@ -94,7 +94,8 @@ skip-if = os == "win" && bits == 64 && debug # Bug 1683002
|
|||
[browser_tabIconOverlayPiP.js]
|
||||
[browser_telemetry_togglePiP.js]
|
||||
skip-if =
|
||||
os == "linux" && bits == 64 && !debug # Bug 1755274
|
||||
os == "linux" && bits == 64 && !debug # Bug 1755274
|
||||
os == "linux" && bits == 64 && !fission && debug # Bug 1755274
|
||||
[browser_text_tracks_webvtt_1.js]
|
||||
[browser_text_tracks_webvtt_2.js]
|
||||
[browser_text_tracks_webvtt_3.js]
|
||||
|
|
|
@ -3151,6 +3151,47 @@ contextservices.quicksuggest:
|
|||
- fx-search@mozilla.com
|
||||
- adw@mozilla.com
|
||||
expiry_version: never
|
||||
impression_cap:
|
||||
description: >
|
||||
This is recorded when an event related to an impression cap occurs. "hit"
|
||||
is recorded when an impression cap is hit. "reset" is recorded when a
|
||||
cap's counter is reset because its interval period has elapsed.
|
||||
objects: ["hit", "reset"]
|
||||
extra_keys:
|
||||
type: >
|
||||
The type of cap, one of: "sponsored", "nonsponsored"
|
||||
intervalSeconds: >
|
||||
The number of seconds in the cap's interval period. For lifetime caps,
|
||||
this value will be "Infinity".
|
||||
maxCount: >
|
||||
The maximum number of impressions allowed in the cap's interval period.
|
||||
startDate: >
|
||||
The timestamp at which the cap's interval period started, in number of
|
||||
seconds since Unix epoch.
|
||||
endDate: >
|
||||
The timestamp at which the cap's interval period will end (for "hit"
|
||||
events) or did end (for "reset" events), in number of milliseconds since
|
||||
Unix epoch. For lifetime caps, this value will be "Infinity".
|
||||
count: >
|
||||
The number of impressions in the cap's interval period.
|
||||
impressionDate: >
|
||||
The timestamp of the cap's most recent impression, in number of
|
||||
milliseconds since Unix epoch.
|
||||
eventDate: >
|
||||
The event's timestamp, in number of milliseconds since Unix epoch. For
|
||||
"reset" events, this may be earlier than the timestamp on the event
|
||||
itself because the implementation sometimes batches and records these
|
||||
events at a later date. This ``eventDate`` value should be preferred
|
||||
over the timestamp on the event itself.
|
||||
release_channel_collection: opt-out
|
||||
products:
|
||||
- "firefox"
|
||||
record_in_processes: ["main"]
|
||||
bug_numbers: [1761058]
|
||||
notification_emails:
|
||||
- fx-search@mozilla.com
|
||||
- adw@mozilla.com
|
||||
expiry_version: never
|
||||
|
||||
close_tab_warning:
|
||||
shown:
|
||||
|
|
|
@ -5922,6 +5922,18 @@ int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) {
|
|||
// run!
|
||||
rv = XRE_mainRun();
|
||||
|
||||
#if defined(XP_WIN)
|
||||
bool wantAudio = true;
|
||||
# ifdef MOZ_BACKGROUNDTASKS
|
||||
if (BackgroundTasks::IsBackgroundTaskMode()) {
|
||||
wantAudio = false;
|
||||
}
|
||||
# endif
|
||||
if (MOZ_LIKELY(wantAudio)) {
|
||||
mozilla::widget::StopAudioSession();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_INSTRUMENT_EVENT_LOOP
|
||||
mozilla::ShutdownEventTracing();
|
||||
#endif
|
||||
|
@ -5939,18 +5951,6 @@ int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) {
|
|||
|
||||
mScopedXPCOM = nullptr;
|
||||
|
||||
#if defined(XP_WIN)
|
||||
bool wantAudio = true;
|
||||
# ifdef MOZ_BACKGROUNDTASKS
|
||||
if (BackgroundTasks::IsBackgroundTaskMode()) {
|
||||
wantAudio = false;
|
||||
}
|
||||
# endif
|
||||
if (MOZ_LIKELY(wantAudio)) {
|
||||
mozilla::widget::StopAudioSession();
|
||||
}
|
||||
#endif
|
||||
|
||||
// unlock the profile after ScopedXPCOMStartup object (xpcom)
|
||||
// has gone out of scope. see bug #386739 for more details
|
||||
mProfileLock->Unlock();
|
||||
|
|
|
@ -36,7 +36,6 @@ forbid-mscom-init:
|
|||
- toolkit/crashreporter/google-breakpad/src/common/windows/pdb_source_line_writer.cc
|
||||
- toolkit/mozapps/defaultagent/main.cpp
|
||||
- uriloader/exthandler/win/nsOSHelperAppService.cpp
|
||||
- widget/windows/AudioSession.cpp
|
||||
- widget/windows/InkCollector.cpp
|
||||
- widget/windows/TaskbarPreview.cpp
|
||||
- widget/windows/WinTaskbar.cpp
|
||||
|
|
|
@ -110,10 +110,6 @@ already_AddRefed<PrintTarget> nsDeviceContextSpecGTK::MakePrintTarget() {
|
|||
return stream;
|
||||
}();
|
||||
|
||||
if (!stream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return PrintTargetPDF::CreateOrNull(stream, IntSize::Ceil(width, height));
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
* 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/. */
|
||||
|
||||
#include <windows.h>
|
||||
#include <atomic>
|
||||
#include <audiopolicy.h>
|
||||
#include <windows.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsIStringBundle.h"
|
||||
|
||||
//#include "AudioSession.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsID.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
@ -19,11 +19,10 @@
|
|||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/mscom/Utils.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
|
||||
#include <objbase.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace widget {
|
||||
|
||||
|
@ -33,10 +32,6 @@ namespace widget {
|
|||
* and implements IAudioSessionEvents (for callbacks from Windows)
|
||||
*/
|
||||
class AudioSession final : public IAudioSessionEvents {
|
||||
private:
|
||||
AudioSession();
|
||||
~AudioSession();
|
||||
|
||||
public:
|
||||
static AudioSession* GetSingleton();
|
||||
|
||||
|
@ -53,23 +48,17 @@ class AudioSession final : public IAudioSessionEvents {
|
|||
STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
|
||||
STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
|
||||
STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
|
||||
|
||||
private:
|
||||
nsresult OnSessionDisconnectedInternal();
|
||||
nsresult CommitAudioSessionData();
|
||||
|
||||
public:
|
||||
STDMETHODIMP OnSimpleVolumeChanged(float aVolume, BOOL aMute,
|
||||
LPCGUID aContext);
|
||||
STDMETHODIMP OnStateChanged(AudioSessionState aState);
|
||||
|
||||
nsresult Start();
|
||||
nsresult Stop();
|
||||
void Start();
|
||||
void Stop();
|
||||
void StopInternal();
|
||||
void InitializeAudioSession();
|
||||
|
||||
nsresult GetSessionData(nsID& aID, nsString& aSessionName,
|
||||
nsString& aIconPath);
|
||||
|
||||
nsresult SetSessionData(const nsID& aID, const nsString& aSessionName,
|
||||
const nsString& aIconPath);
|
||||
|
||||
|
@ -82,38 +71,46 @@ class AudioSession final : public IAudioSessionEvents {
|
|||
AUDIO_SESSION_DISCONNECTED // Audio session disconnected
|
||||
};
|
||||
|
||||
SessionState mState;
|
||||
|
||||
private:
|
||||
AudioSession();
|
||||
~AudioSession();
|
||||
nsresult CommitAudioSessionData();
|
||||
|
||||
protected:
|
||||
RefPtr<IAudioSessionControl> mAudioSessionControl;
|
||||
nsString mDisplayName;
|
||||
nsString mIconPath;
|
||||
nsID mSessionGroupingParameter;
|
||||
SessionState mState;
|
||||
// Guards the IAudioSessionControl
|
||||
mozilla::Mutex mMutex MOZ_UNANNOTATED;
|
||||
|
||||
ThreadSafeAutoRefCnt mRefCnt;
|
||||
NS_DECL_OWNINGTHREAD
|
||||
|
||||
static AudioSession* sService;
|
||||
};
|
||||
|
||||
nsresult StartAudioSession() { return AudioSession::GetSingleton()->Start(); }
|
||||
static std::atomic<AudioSession*> sService = nullptr;
|
||||
|
||||
nsresult StopAudioSession() { return AudioSession::GetSingleton()->Stop(); }
|
||||
|
||||
nsresult GetAudioSessionData(nsID& aID, nsString& aSessionName,
|
||||
nsString& aIconPath) {
|
||||
return AudioSession::GetSingleton()->GetSessionData(aID, aSessionName,
|
||||
aIconPath);
|
||||
void StartAudioSession() {
|
||||
AudioSession::GetSingleton()->InitializeAudioSession();
|
||||
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
|
||||
"StartAudioSession",
|
||||
[]() -> void { AudioSession::GetSingleton()->Start(); }));
|
||||
}
|
||||
|
||||
nsresult RecvAudioSessionData(const nsID& aID, const nsString& aSessionName,
|
||||
const nsString& aIconPath) {
|
||||
return AudioSession::GetSingleton()->SetSessionData(aID, aSessionName,
|
||||
aIconPath);
|
||||
}
|
||||
void StopAudioSession() {
|
||||
RefPtr<AudioSession> audioSession;
|
||||
AudioSession* temp = sService;
|
||||
audioSession.swap(temp);
|
||||
sService = nullptr;
|
||||
|
||||
AudioSession* AudioSession::sService = nullptr;
|
||||
if (audioSession) {
|
||||
NS_DispatchBackgroundTask(NS_NewRunnableFunction(
|
||||
"StopAudioSession",
|
||||
[audioSession]() -> void { audioSession->Stop(); }));
|
||||
}
|
||||
}
|
||||
|
||||
AudioSession::AudioSession() : mMutex("AudioSessionControl") {
|
||||
mState = UNINITIALIZED;
|
||||
|
@ -122,14 +119,16 @@ AudioSession::AudioSession() : mMutex("AudioSessionControl") {
|
|||
AudioSession::~AudioSession() {}
|
||||
|
||||
AudioSession* AudioSession::GetSingleton() {
|
||||
if (!(AudioSession::sService)) {
|
||||
if (!sService) {
|
||||
RefPtr<AudioSession> service = new AudioSession();
|
||||
service.forget(&AudioSession::sService);
|
||||
AudioSession* temp = nullptr;
|
||||
service.swap(temp);
|
||||
sService = temp;
|
||||
}
|
||||
|
||||
// We don't refcount AudioSession on the Gecko side, we hold one single ref
|
||||
// as long as the appshell is running.
|
||||
return AudioSession::sService;
|
||||
return sService;
|
||||
}
|
||||
|
||||
// It appears Windows will use us on a background thread ...
|
||||
|
@ -148,10 +147,40 @@ AudioSession::QueryInterface(REFIID iid, void** ppv) {
|
|||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
void AudioSession::InitializeAudioSession() {
|
||||
// This func must be run on the main thread as
|
||||
// nsStringBundle is not thread safe otherwise
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
MOZ_ASSERT(XRE_IsParentProcess(),
|
||||
"Should only get here in a chrome process!");
|
||||
|
||||
if (mState != UNINITIALIZED) return;
|
||||
|
||||
nsCOMPtr<nsIStringBundleService> bundleService =
|
||||
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
|
||||
MOZ_ASSERT(bundleService);
|
||||
|
||||
nsCOMPtr<nsIStringBundle> bundle;
|
||||
bundleService->CreateBundle("chrome://branding/locale/brand.properties",
|
||||
getter_AddRefs(bundle));
|
||||
MOZ_ASSERT(bundle);
|
||||
bundle->GetStringFromName("brandFullName", mDisplayName);
|
||||
|
||||
wchar_t* buffer;
|
||||
mIconPath.GetMutableData(&buffer, MAX_PATH);
|
||||
::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
|
||||
|
||||
[[maybe_unused]] nsresult rv =
|
||||
nsID::GenerateUUIDInPlace(mSessionGroupingParameter);
|
||||
MOZ_ASSERT(rv == NS_OK);
|
||||
}
|
||||
|
||||
// Once we are started Windows will hold a reference to us through our
|
||||
// IAudioSessionEvents interface that will keep us alive until the appshell
|
||||
// calls Stop.
|
||||
nsresult AudioSession::Start() {
|
||||
void AudioSession::Start() {
|
||||
MOZ_ASSERT(mscom::IsCurrentThreadMTA());
|
||||
MOZ_ASSERT(mState == UNINITIALIZED || mState == CLONED ||
|
||||
mState == AUDIO_SESSION_DISCONNECTED,
|
||||
"State invariants violated");
|
||||
|
@ -162,41 +191,6 @@ nsresult AudioSession::Start() {
|
|||
|
||||
HRESULT hr;
|
||||
|
||||
// There's a matching CoUninit in Stop() for this tied to a state of
|
||||
// UNINITIALIZED.
|
||||
hr = CoInitialize(nullptr);
|
||||
MOZ_ASSERT(SUCCEEDED(hr),
|
||||
"CoInitialize failure in audio session control, unexpected");
|
||||
|
||||
if (mState == UNINITIALIZED) {
|
||||
mState = FAILED;
|
||||
|
||||
// Content processes should be CLONED
|
||||
if (XRE_IsContentProcess()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(XRE_IsParentProcess(),
|
||||
"Should only get here in a chrome process!");
|
||||
|
||||
nsCOMPtr<nsIStringBundleService> bundleService =
|
||||
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
|
||||
NS_ENSURE_TRUE(bundleService, NS_ERROR_FAILURE);
|
||||
nsCOMPtr<nsIStringBundle> bundle;
|
||||
bundleService->CreateBundle("chrome://branding/locale/brand.properties",
|
||||
getter_AddRefs(bundle));
|
||||
NS_ENSURE_TRUE(bundle, NS_ERROR_FAILURE);
|
||||
|
||||
bundle->GetStringFromName("brandFullName", mDisplayName);
|
||||
|
||||
wchar_t* buffer;
|
||||
mIconPath.GetMutableData(&buffer, MAX_PATH);
|
||||
::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
|
||||
|
||||
nsresult rv = nsID::GenerateUUIDInPlace(mSessionGroupingParameter);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
mState = FAILED;
|
||||
|
||||
MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
|
||||
|
@ -205,21 +199,20 @@ nsresult AudioSession::Start() {
|
|||
RefPtr<IMMDeviceEnumerator> enumerator;
|
||||
hr = ::CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
|
||||
IID_IMMDeviceEnumerator, getter_AddRefs(enumerator));
|
||||
if (FAILED(hr)) return NS_ERROR_NOT_AVAILABLE;
|
||||
if (FAILED(hr)) return;
|
||||
|
||||
RefPtr<IMMDevice> device;
|
||||
hr = enumerator->GetDefaultAudioEndpoint(
|
||||
EDataFlow::eRender, ERole::eMultimedia, getter_AddRefs(device));
|
||||
if (FAILED(hr)) {
|
||||
if (hr == E_NOTFOUND) return NS_ERROR_NOT_AVAILABLE;
|
||||
return NS_ERROR_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<IAudioSessionManager> manager;
|
||||
hr = device->Activate(IID_IAudioSessionManager, CLSCTX_ALL, nullptr,
|
||||
getter_AddRefs(manager));
|
||||
if (FAILED(hr)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
@ -227,44 +220,18 @@ nsresult AudioSession::Start() {
|
|||
getter_AddRefs(mAudioSessionControl));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Increments refcount of 'this'.
|
||||
hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
|
||||
if (FAILED(hr)) {
|
||||
StopInternal();
|
||||
return NS_ERROR_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NewRunnableMethod("AudioSession::CommitAudioSessionData", this,
|
||||
&AudioSession::CommitAudioSessionData);
|
||||
NS_DispatchToMainThread(runnable);
|
||||
|
||||
CommitAudioSessionData();
|
||||
mState = STARTED;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void SpawnASCReleaseThread(RefPtr<IAudioSessionControl>&& aASC) {
|
||||
// Fake moving to the other thread by circumventing the ref count.
|
||||
// (RefPtrs don't play well with C++11 lambdas and we don't want to use
|
||||
// XPCOM here.)
|
||||
IAudioSessionControl* rawPtr = nullptr;
|
||||
aASC.forget(&rawPtr);
|
||||
MOZ_ASSERT(rawPtr);
|
||||
PRThread* thread = PR_CreateThread(
|
||||
PR_USER_THREAD,
|
||||
[](void* aRawPtr) {
|
||||
NS_SetCurrentThreadName("AudioASCReleaser");
|
||||
static_cast<IAudioSessionControl*>(aRawPtr)->Release();
|
||||
},
|
||||
rawPtr, PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_UNJOINABLE_THREAD, 0);
|
||||
if (!thread) {
|
||||
// We can't make a thread so just destroy the IAudioSessionControl here.
|
||||
rawPtr->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSession::StopInternal() {
|
||||
|
@ -273,35 +240,25 @@ void AudioSession::StopInternal() {
|
|||
if (mAudioSessionControl && (mState == STARTED || mState == STOPPED)) {
|
||||
// Decrement refcount of 'this'
|
||||
mAudioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
}
|
||||
|
||||
if (mAudioSessionControl) {
|
||||
// Avoid hanging when destroying AudioSessionControl. We do that by
|
||||
// moving the AudioSessionControl to a worker thread (that we never
|
||||
// 'join') for destruction.
|
||||
SpawnASCReleaseThread(std::move(mAudioSessionControl));
|
||||
// Deleting this COM object seems to require the STA / main thread.
|
||||
// Audio code may concurrently be running on the main thread and it may
|
||||
// block waiting for this to complete, creating deadlock. So we destroy the
|
||||
// object on the main thread instead.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
"ShutdownAudioSession",
|
||||
[asc = std::move(mAudioSessionControl)] { /* */ }));
|
||||
}
|
||||
}
|
||||
|
||||
nsresult AudioSession::Stop() {
|
||||
void AudioSession::Stop() {
|
||||
MOZ_ASSERT(mState == STARTED || mState == UNINITIALIZED || // XXXremove this
|
||||
mState == FAILED,
|
||||
"State invariants violated");
|
||||
SessionState state = mState;
|
||||
MOZ_ASSERT(mscom::IsCurrentThreadMTA());
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
mState = STOPPED;
|
||||
|
||||
{
|
||||
RefPtr<AudioSession> kungFuDeathGrip;
|
||||
kungFuDeathGrip.swap(sService);
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
StopInternal();
|
||||
}
|
||||
|
||||
if (state != UNINITIALIZED) {
|
||||
::CoUninitialize();
|
||||
}
|
||||
return NS_OK;
|
||||
StopInternal();
|
||||
}
|
||||
|
||||
void CopynsID(nsID& lhs, const nsID& rhs) {
|
||||
|
@ -342,7 +299,7 @@ nsresult AudioSession::SetSessionData(const nsID& aID,
|
|||
}
|
||||
|
||||
nsresult AudioSession::CommitAudioSessionData() {
|
||||
MutexAutoLock lock(mMutex);
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
if (!mAudioSessionControl) {
|
||||
// Stop() was called before we had a chance to do this.
|
||||
|
@ -350,7 +307,7 @@ nsresult AudioSession::CommitAudioSessionData() {
|
|||
}
|
||||
|
||||
HRESULT hr = mAudioSessionControl->SetGroupingParam(
|
||||
(LPGUID)&mSessionGroupingParameter, nullptr);
|
||||
(LPGUID) & (mSessionGroupingParameter), nullptr);
|
||||
if (FAILED(hr)) {
|
||||
StopInternal();
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -395,35 +352,20 @@ AudioSession::OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext) {
|
|||
|
||||
STDMETHODIMP
|
||||
AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) {
|
||||
// Run our code asynchronously. Per MSDN we can't do anything interesting
|
||||
// in this callback.
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NewRunnableMethod("widget::AudioSession::OnSessionDisconnectedInternal",
|
||||
this, &AudioSession::OnSessionDisconnectedInternal);
|
||||
NS_DispatchToMainThread(runnable);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
nsresult AudioSession::OnSessionDisconnectedInternal() {
|
||||
// When successful, UnregisterAudioSessionNotification will decrement the
|
||||
// refcount of 'this'. Start will re-increment it. In the interim,
|
||||
// we'll need to reference ourselves.
|
||||
RefPtr<AudioSession> kungFuDeathGrip(this);
|
||||
|
||||
{
|
||||
// We need to release the mutex before we call Start().
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
if (!mAudioSessionControl) return NS_OK;
|
||||
|
||||
if (!mAudioSessionControl) return S_OK;
|
||||
mAudioSessionControl->UnregisterAudioSessionNotification(this);
|
||||
mAudioSessionControl = nullptr;
|
||||
// Deleting this COM object seems to require the STA / main thread.
|
||||
// Audio code may concurrently be running on the main thread and it may
|
||||
// block waiting for this to complete, creating deadlock. So we destroy the
|
||||
// object on the main thread instead.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
"FreeAudioSession", [asc = std::move(mAudioSessionControl)] { /* */ }));
|
||||
mState = AUDIO_SESSION_DISCONNECTED;
|
||||
}
|
||||
|
||||
mState = AUDIO_SESSION_DISCONNECTED;
|
||||
CoUninitialize();
|
||||
Start(); // If it fails there's not much we can do.
|
||||
return NS_OK;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
|
|
|
@ -10,19 +10,10 @@ namespace mozilla {
|
|||
namespace widget {
|
||||
|
||||
// Start the audio session in the current process
|
||||
nsresult StartAudioSession();
|
||||
|
||||
// Pass the information necessary to start an audio session in another process
|
||||
nsresult GetAudioSessionData(nsID& aID, nsString& aSessionName,
|
||||
nsString& aIconPath);
|
||||
|
||||
// Receive the information necessary to start an audio session in a non-chrome
|
||||
// process
|
||||
nsresult RecvAudioSessionData(const nsID& aID, const nsString& aSessionName,
|
||||
const nsString& aIconPath);
|
||||
void StartAudioSession();
|
||||
|
||||
// Stop the audio session in the current process
|
||||
nsresult StopAudioSession();
|
||||
void StopAudioSession();
|
||||
|
||||
} // namespace widget
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -1699,12 +1699,11 @@ const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
|
|||
V(8, 15, 10, 2869), "FEATURE_FAILURE_INTEL_W7_HW_DECODING");
|
||||
|
||||
/* Bug 1203199/1092166: DXVA startup crashes on some intel drivers. */
|
||||
APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Windows, DeviceFamily::IntelAll,
|
||||
nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
|
||||
nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
|
||||
DRIVER_BUILD_ID_LESS_THAN_OR_EQUAL, 2849,
|
||||
"FEATURE_FAILURE_BUG_1203199_1",
|
||||
"Intel driver > X.X.X.2849");
|
||||
APPEND_TO_DRIVER_BLOCKLIST_RANGE(OperatingSystem::Windows, DeviceFamily::IntelAll,
|
||||
nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
|
||||
nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
|
||||
DRIVER_BETWEEN_INCLUSIVE, V(9,17,10,0), V(9,17,10,2849),
|
||||
"FEATURE_FAILURE_BUG_1203199_1", "Intel driver > 9.17.10.2849");
|
||||
|
||||
APPEND_TO_DRIVER_BLOCKLIST2(
|
||||
OperatingSystem::Windows, DeviceFamily::Nvidia8800GTS,
|
||||
|
|
|
@ -586,11 +586,6 @@ nsresult nsAppShell::Init() {
|
|||
|
||||
NS_IMETHODIMP
|
||||
nsAppShell::Run(void) {
|
||||
// Content processes initialize audio later through PContent using audio
|
||||
// tray id information pulled from the browser process AudioSession. This
|
||||
// way the two share a single volume control.
|
||||
// Note StopAudioSession() is called from nsAppRunner.cpp after xpcom is torn
|
||||
// down to insure the browser shuts down after child processes.
|
||||
if (XRE_IsParentProcess()) {
|
||||
bool wantAudio = true;
|
||||
#ifdef MOZ_BACKGROUNDTASKS
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
const NS_OS_TEMP_DIR = "TmpD";
|
||||
|
||||
const CWD = do_get_cwd();
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var hiddenUnixFile;
|
||||
|
|
|
@ -4,21 +4,15 @@
|
|||
* 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/. */
|
||||
|
||||
const { AppConstants } = ChromeUtils.import(
|
||||
"resource://gre/modules/AppConstants.jsm"
|
||||
);
|
||||
const MAX_TIME_DIFFERENCE = 2500;
|
||||
const MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
var LocalFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
|
||||
|
||||
function run_test() {
|
||||
test_toplevel_parent_is_null();
|
||||
test_normalize_crash_if_media_missing();
|
||||
test_file_modification_time();
|
||||
test_directory_modification_time();
|
||||
test_diskSpaceAvailable();
|
||||
test_diskCapacity();
|
||||
}
|
||||
|
||||
function test_toplevel_parent_is_null() {
|
||||
add_task(function test_toplevel_parent_is_null() {
|
||||
try {
|
||||
var lf = new LocalFile("C:\\");
|
||||
|
||||
|
@ -31,9 +25,9 @@ function test_toplevel_parent_is_null() {
|
|||
// not Windows
|
||||
Assert.equal(e.result, Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function test_normalize_crash_if_media_missing() {
|
||||
add_task(function test_normalize_crash_if_media_missing() {
|
||||
const a = "a".charCodeAt(0);
|
||||
const z = "z".charCodeAt(0);
|
||||
for (var i = a; i <= z; ++i) {
|
||||
|
@ -41,10 +35,10 @@ function test_normalize_crash_if_media_missing() {
|
|||
LocalFile(String.fromCharCode(i) + ":.\\test").normalize();
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Tests that changing a file's modification time is possible
|
||||
function test_file_modification_time() {
|
||||
add_task(function test_file_modification_time() {
|
||||
var file = do_get_profile();
|
||||
file.append("testfile");
|
||||
|
||||
|
@ -81,10 +75,10 @@ function test_file_modification_time() {
|
|||
Assert.ok(diff < MAX_TIME_DIFFERENCE);
|
||||
|
||||
file.remove(true);
|
||||
}
|
||||
});
|
||||
|
||||
// Tests that changing a directory's modification time is possible
|
||||
function test_directory_modification_time() {
|
||||
add_task(function test_directory_modification_time() {
|
||||
var dir = do_get_profile();
|
||||
dir.append("testdir");
|
||||
|
||||
|
@ -115,9 +109,9 @@ function test_directory_modification_time() {
|
|||
Assert.ok(diff < MAX_TIME_DIFFERENCE);
|
||||
|
||||
dir.remove(true);
|
||||
}
|
||||
});
|
||||
|
||||
function test_diskSpaceAvailable() {
|
||||
add_task(function test_diskSpaceAvailable() {
|
||||
let file = do_get_profile();
|
||||
file.QueryInterface(Ci.nsIFile);
|
||||
|
||||
|
@ -134,9 +128,9 @@ function test_diskSpaceAvailable() {
|
|||
Assert.ok(bytes > 0);
|
||||
|
||||
file.remove(true);
|
||||
}
|
||||
});
|
||||
|
||||
function test_diskCapacity() {
|
||||
add_task(function test_diskCapacity() {
|
||||
let file = do_get_profile();
|
||||
file.QueryInterface(Ci.nsIFile);
|
||||
|
||||
|
@ -154,41 +148,47 @@ function test_diskCapacity() {
|
|||
Assert.ok(startBytes === endBytes);
|
||||
|
||||
file.remove(true);
|
||||
}
|
||||
});
|
||||
|
||||
function test_file_creation_time() {
|
||||
const file = do_get_profile();
|
||||
file.append("testfile");
|
||||
add_task(
|
||||
{
|
||||
// TODO: Bug 1742928. Fix this on non-Mac platforms.
|
||||
skip_if: () => AppConstants.platform != "macosx",
|
||||
},
|
||||
function test_file_creation_time() {
|
||||
const file = do_get_profile();
|
||||
file.append("testfile");
|
||||
|
||||
if (file.exists()) {
|
||||
file.remove(true);
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
|
||||
Assert.ok(file.exists());
|
||||
|
||||
let creationTime;
|
||||
try {
|
||||
creationTime = file.creationTime;
|
||||
} catch (e) {
|
||||
if (e.name === "NS_ERROR_NOT_AVAILABLE") {
|
||||
// Creation time is not supported on this platform.
|
||||
file.remove(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const diff = Math.abs(creationTime - now);
|
||||
Assert.ok(diff < MAX_TIME_DIFFERENCE);
|
||||
|
||||
Assert.ok(creationTime === file.lastModifiedTime);
|
||||
|
||||
file.lastModifiedTime = now + MILLIS_PER_DAY;
|
||||
|
||||
Assert.ok(creationTime !== file.lastModifiedTime);
|
||||
Assert.ok(creationTime === file.creationTime);
|
||||
|
||||
if (file.exists()) {
|
||||
file.remove(true);
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
|
||||
Assert.ok(file.exists());
|
||||
|
||||
let creationTime;
|
||||
try {
|
||||
creationTime = file.creationTime;
|
||||
} catch (e) {
|
||||
if (e.name === "NS_ERROR_NOT_AVAILABLE") {
|
||||
// Creation time is not supported on this platform.
|
||||
file.remove(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const diff = Math.abs(creationTime - now);
|
||||
Assert.ok(diff < MAX_TIME_DIFFERENCE);
|
||||
|
||||
Assert.ok(creationTime === file.lastModifiedTime);
|
||||
|
||||
file.lastModifiedTime = now + MILLIS_PER_DAY;
|
||||
|
||||
Assert.ok(creationTime !== file.lastModifiedTime);
|
||||
Assert.ok(creationTime === file.creationTime);
|
||||
|
||||
file.remove(true);
|
||||
}
|
||||
);
|
||||
|
|
Загрузка…
Ссылка в новой задаче