Merge autoland to mozilla-central. a=merge

This commit is contained in:
Norisz Fay 2022-04-08 12:22:39 +03:00
Родитель 6b8edab566 3e31b63b4f
Коммит 5444611211
97 изменённых файлов: 7481 добавлений и 1197 удалений

Просмотреть файл

@ -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) {

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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

Двоичные данные
browser/components/newtab/data/content/assets/heart.webp Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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>

Двоичные данные
dom/media/webvtt/test/reftest/white.webm Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -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

4
python/sites/vendor.txt Normal file
Просмотреть файл

@ -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

1190
third_party/python/poetry.lock сгенерированный поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

4
third_party/python/requirements.in поставляемый
Просмотреть файл

@ -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

700
third_party/python/requirements.txt поставляемый
Просмотреть файл

@ -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);
}
);