Update On Tue Mar 4 19:52:53 CET 2025

This commit is contained in:
github-action[bot] 2025-03-04 19:52:54 +01:00
parent 6d40ed86c5
commit eaefe60938
1745 changed files with 51573 additions and 30355 deletions

View file

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Merge day clobber 2025-03-03
Modified build files in third_party/libwebrtc - Bug 1948685 - Vendor libwebrtc from 39da6f3a75

View file

@ -816,9 +816,14 @@ void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
nsIFrame::TrailingWhitespace::DontTrim);
// Remove text accessible if rendered text is empty.
if (textAcc) {
if (text.mString.IsEmpty()) {
// Remove the TextLeafAccessible if:
// 1. The rendered text is empty; or
// 2. The text is just a space, but its layout frame has a width of 0,
// so it isn't visible. This can happen if there is whitespace before an
// invisible element at the end of a block.
if (text.mString.IsEmpty() ||
(text.mString.EqualsLiteral(" ") && textFrame->GetRect().IsEmpty())) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eTree | logging::eText)) {
logging::MsgBegin("TREE", "text node lost its content; doc: %p",

View file

@ -593,21 +593,34 @@ void nsAccessibilityService::FireAccessibleEvent(uint32_t aEvent,
void nsAccessibilityService::NotifyOfPossibleBoundsChange(
mozilla::PresShell* aPresShell, nsIContent* aContent) {
if (!aContent || (!IPCAccessibilityActive() && !aContent->IsText())) {
return;
}
DocAccessible* document = aPresShell->GetDocAccessible();
if (!document) {
return;
}
LocalAccessible* accessible = document->GetAccessible(aContent);
if (!accessible && aContent == document->GetContent()) {
// DocAccessible::GetAccessible() won't return the document if a root
// element like body is passed. In that case we need the doc accessible
// itself.
accessible = document;
}
if (!accessible) {
return;
}
if (IPCAccessibilityActive()) {
DocAccessible* document = aPresShell->GetDocAccessible();
if (document) {
LocalAccessible* accessible = document->GetAccessible(aContent);
if (!accessible && aContent == document->GetContent()) {
// DocAccessible::GetAccessible() won't return the document if a root
// element like body is passed. In that case we need the doc accessible
// itself.
accessible = document;
}
if (accessible) {
document->QueueCacheUpdate(accessible, CacheDomain::Bounds);
}
}
document->QueueCacheUpdate(accessible, CacheDomain::Bounds);
}
if (accessible->IsTextLeaf() &&
accessible->AsTextLeaf()->Text().EqualsLiteral(" ")) {
// This space might be becoming invisible, even though it still has a frame.
// In this case, the frame will have 0 width. Unfortunately, we can't check
// the frame width here because layout isn't ready yet, so we need to defer
// this until the refresh driver tick.
MOZ_ASSERT(aContent->IsText());
document->UpdateText(aContent);
}
}
@ -1293,6 +1306,7 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
// Ignore not rendered text nodes and whitespace text nodes between table
// cells.
if (text.mString.IsEmpty() ||
(text.mString.EqualsLiteral(" ") && frame->GetRect().IsEmpty()) ||
(aContext->IsTableRow() &&
nsCoreUtils::IsWhitespaceString(text.mString))) {
if (aIsSubtreeHidden) *aIsSubtreeHidden = true;

View file

@ -240,6 +240,18 @@ uint64_t DocAccessible::NativeState() const {
RefPtr<EditorBase> editorBase = GetEditor();
state |= editorBase ? states::EDITABLE : states::READONLY;
// GetFrame() returns the root frame, which is normally what we want. However,
// user-select: none might be set on the body, in which case this won't be
// exposed on the root frame. Therefore, we explicitly use the body frame
// here (if any).
nsIFrame* bodyFrame = mContent ? mContent->GetPrimaryFrame() : nullptr;
if ((state & states::EDITABLE) ||
(bodyFrame && bodyFrame->IsSelectable(nullptr))) {
// If the accessible is editable the layout selectable state only disables
// mouse selection, but keyboard (shift+arrow) selection is still possible.
state |= states::SELECTABLE_TEXT;
}
return state;
}

View file

@ -1209,20 +1209,36 @@ Relation RemoteAccessible::RelationByType(RelationType aType) const {
// the cached relations need to take precedence. For example, a <figure> with
// both aria-labelledby and a <figcaption> must return two LABELLED_BY
// targets: the aria-labelledby and then the <figcaption>.
if (aType == RelationType::LABELLED_BY && TagName() == nsGkAtoms::figure) {
auto AddChildWithTag = [this, &rel](nsAtom* aTarget) {
uint32_t count = ChildCount();
for (uint32_t c = 0; c < count; ++c) {
RemoteAccessible* child = RemoteChildAt(c);
MOZ_ASSERT(child);
if (child->TagName() == nsGkAtoms::figcaption) {
if (child->TagName() == aTarget) {
rel.AppendTarget(child);
}
}
} else if (aType == RelationType::LABEL_FOR &&
TagName() == nsGkAtoms::figcaption) {
if (RemoteAccessible* parent = RemoteParent()) {
if (parent->TagName() == nsGkAtoms::figure) {
rel.AppendTarget(parent);
};
if (aType == RelationType::LABELLED_BY) {
auto tag = TagName();
if (tag == nsGkAtoms::figure) {
AddChildWithTag(nsGkAtoms::figcaption);
} else if (tag == nsGkAtoms::fieldset) {
AddChildWithTag(nsGkAtoms::legend);
}
} else if (aType == RelationType::LABEL_FOR) {
auto tag = TagName();
if (tag == nsGkAtoms::figcaption) {
if (RemoteAccessible* parent = RemoteParent()) {
if (parent->TagName() == nsGkAtoms::figure) {
rel.AppendTarget(parent);
}
}
} else if (tag == nsGkAtoms::legend) {
if (RemoteAccessible* parent = RemoteParent()) {
if (parent->TagName() == nsGkAtoms::fieldset) {
rel.AppendTarget(parent);
}
}
}
}

View file

@ -289,3 +289,22 @@ addAccessibleTask(
},
{ topLevel: true, chrome: true }
);
/*
* Test relation caching for LABELLED_BY and LABEL_FOR with legend/fieldset.
*/
addAccessibleTask(
`
<fieldset id="fs">
<legend id="leg">legend</legend>
inner content
</fieldset>`,
async function testFieldsetLegendLabels(browser, accDoc) {
const fs = findAccessibleChildByID(accDoc, "fs");
const leg = findAccessibleChildByID(accDoc, "leg");
await testCachedRelation(fs, RELATION_LABELLED_BY, leg);
await testCachedRelation(leg, RELATION_LABEL_FOR, fs);
},
{ chrome: true, iframe: true, remoteIframe: true }
);

View file

@ -863,3 +863,43 @@ addAccessibleTask(
},
{ chrome: true, topLevel: true }
);
/**
* Test the selectable text state.
*/
addAccessibleTask(
`
<p id="selectableP">selectableP</p>
<p id="unselectableP" style="user-select: none;">unselectableP</p>
`,
async function testSelectableText(browser, docAcc) {
testStates(docAcc, 0, EXT_STATE_SELECTABLE_TEXT);
const selectableP = findAccessibleChildByID(docAcc, "selectableP");
testStates(selectableP, 0, EXT_STATE_SELECTABLE_TEXT);
const unselectableP = findAccessibleChildByID(docAcc, "unselectableP");
testStates(unselectableP, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT);
},
{ chrome: true, topLevel: true }
);
/**
* Test the selectable text state on an unselectable body.
*/
addAccessibleTask(
`
<style>
body {
user-select: none;
}
<p id="p">p</p>
`,
async function testSelectableTextUnselectableBody(browser, docAcc) {
testStates(docAcc, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT);
const p = findAccessibleChildByID(docAcc, "p");
testStates(p, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT);
},
{
chrome: true,
topLevel: true,
}
);

View file

@ -67,3 +67,44 @@ addAccessibleTask(
},
{ iframe: true, remoteIframe: true }
);
/**
* Test whitespace before a hidden element at the end of a block.
*/
addAccessibleTask(
`<div id="container"><span>a</span> <span id="b" hidden>b</span></div>`,
async function testBeforeHiddenElementAtEnd(browser, docAcc) {
const container = findAccessibleChildByID(docAcc, "container");
testAccessibleTree(container, {
role: ROLE_SECTION,
children: [{ role: ROLE_TEXT_LEAF, name: "a" }],
});
info("Showing b");
let reordered = waitForEvent(EVENT_REORDER, container);
await invokeContentTask(browser, [], () => {
content.document.getElementById("b").hidden = false;
});
await reordered;
testAccessibleTree(container, {
role: ROLE_SECTION,
children: [
{ role: ROLE_TEXT_LEAF, name: "a" },
{ role: ROLE_TEXT_LEAF, name: " " },
{ role: ROLE_TEXT_LEAF, name: "b" },
],
});
info("Hiding b");
reordered = waitForEvent(EVENT_REORDER, container);
await invokeContentTask(browser, [], () => {
content.document.getElementById("b").hidden = true;
});
await reordered;
testAccessibleTree(container, {
role: ROLE_SECTION,
children: [{ role: ROLE_TEXT_LEAF, name: "a" }],
});
},
{ chrome: true, topLevel: true }
);

View file

@ -1397,6 +1397,7 @@ body {
}
</style>
<input id="input">
<p id="p">p</p>
`,
async function testTextSupportedTextSelection() {
let result = await runPython(`
@ -1411,9 +1412,20 @@ body {
SupportedTextSelection_Multiple,
"input SupportedTextSelection correct"
);
// The IA2 -> UIA bridge doesn't understand that text isn't selectable in
// this document.
if (gIsUiaEnabled) {
// The IA2 -> UIA proxy doesn't expose the Text pattern on this text leaf.
is(
await runPython(`
p = findUiaByDomId(doc, "p")
pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
text = getUiaPattern(pLeaf, "Text")
return text.SupportedTextSelection
`),
SupportedTextSelection_None,
"pLeaf SupportedTextSelection correct"
);
// The IA2 -> UIA proxy doesn't understand that text isn't selectable in
// this document.
is(
await runPython(`getUiaPattern(doc, "Text").SupportedTextSelection`),
SupportedTextSelection_None,
@ -1423,6 +1435,39 @@ body {
}
);
/**
* Test the Text pattern's SupportedTextSelection property on a document with a
* selectable body.
*/
addUiaTask(
`<p id="p">p</p>`,
async function testTextSupportedTextSelectionSelectableBody() {
is(
await runPython(`
global doc
doc = getDocUia()
text = getUiaPattern(doc, "Text")
return text.SupportedTextSelection
`),
SupportedTextSelection_Multiple,
"doc SupportedTextSelection correct"
);
// The IA2 -> UIA proxy doesn't expose the Text pattern on this text leaf.
if (gIsUiaEnabled) {
is(
await runPython(`
p = findUiaByDomId(doc, "p")
pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
text = getUiaPattern(pLeaf, "Text")
return text.SupportedTextSelection
`),
SupportedTextSelection_Multiple,
"pLeaf SupportedTextSelection correct"
);
}
}
);
/**
* Test the Text pattern's GetSelection method with the caret.
*/

View file

@ -144,7 +144,6 @@
testAccessibleTree("c7",{ SECTION: [
{ role: ROLE_PUSHBUTTON, name: "Hello" },
{ TEXT_LEAF: [] }
] });
let events = waitForOrderedEvents(

View file

@ -165,7 +165,12 @@ UiaText::get_SupportedTextSelection(
if (!acc) {
return CO_E_OBJNOTCONNECTED;
}
if (acc->State() & states::SELECTABLE_TEXT) {
if (!acc->IsHyperText()) {
// Currently, the SELECTABLE_TEXT state is only exposed on HyperText
// Accessibles.
acc = acc->Parent();
}
if (acc && acc->State() & states::SELECTABLE_TEXT) {
*aRetVal = SupportedTextSelection_Multiple;
} else {
*aRetVal = SupportedTextSelection_None;

View file

@ -101,6 +101,17 @@ var gBrowserInit = {
);
toolbarMenubar.setAttribute("data-l10n-attrs", "toolbarname");
}
// If opening a Taskbar Tab window, add an attribute to the top-level element
// to inform window styling.
if (window.arguments && window.arguments[1]) {
let extraOptions = window.arguments[1];
if (
extraOptions instanceof Ci.nsIWritablePropertyBag2 &&
extraOptions.hasKey("taskbartab")
) {
window.document.documentElement.setAttribute("taskbartab", "");
}
}
// Run menubar initialization first, to avoid CustomTitlebar code picking
// up mutations from it and causing a reflow.
@ -221,7 +232,10 @@ var gBrowserInit = {
// have been initialized.
Services.obs.notifyObservers(window, "browser-window-before-show");
if (!window.toolbar.visible) {
if (
!window.toolbar.visible ||
window.document.documentElement.hasAttribute("taskbartab")
) {
// adjust browser UI for popups
gURLBar.readOnly = true;
}
@ -231,6 +245,7 @@ var gBrowserInit = {
Win10TabletModeUpdater.init();
CombinedStopReload.ensureInitialized();
gPrivateBrowsingUI.init();
TaskbarTabUI.init(window);
BrowserPageActions.init();
if (gToolbarKeyNavEnabled) {
ToolbarKeyboardNavigator.init();

View file

@ -46,11 +46,13 @@ ChromeUtils.defineESModuleGetters(this, {
LoginManagerParent: "resource://gre/modules/LoginManagerParent.sys.mjs",
MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
NewTabPagePreloading: "resource:///modules/NewTabPagePreloading.sys.mjs",
NewTabPagePreloading:
"moz-src:///browser/components/tabbrowser/NewTabPagePreloading.sys.mjs",
NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
nsContextMenu: "chrome://browser/content/nsContextMenu.sys.mjs",
OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.sys.mjs",
OpenInTabsUtils:
"moz-src:///browser/components/tabbrowser/OpenInTabsUtils.sys.mjs",
OpenSearchManager: "resource:///modules/OpenSearchManager.sys.mjs",
PageActions: "resource:///modules/PageActions.sys.mjs",
PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
@ -84,6 +86,7 @@ ChromeUtils.defineESModuleGetters(this, {
TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
TabsSetupFlowManager:
"resource:///modules/firefox-view-tabs-setup-manager.sys.mjs",
TaskbarTabUI: "resource:///modules/TaskbarTabUI.sys.mjs",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
ToolbarContextMenu: "resource:///modules/ToolbarContextMenu.sys.mjs",
TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
@ -799,6 +802,7 @@ function updateFxaToolbarMenu(enable, isInitialUpdate = false) {
const mainWindowEl = document.documentElement;
const fxaPanelEl = PanelMultiView.getViewNode(document, "PanelUI-fxa");
const taskbarTab = mainWindowEl.hasAttribute("taskbartab");
// To minimize the toolbar button flickering or appearing/disappearing during startup,
// we use this pref to anticipate the likely FxA status.
@ -813,7 +817,7 @@ function updateFxaToolbarMenu(enable, isInitialUpdate = false) {
fxaPanelEl.addEventListener("ViewShowing", gSync.updateSendToDeviceTitle);
if (enable && syncEnabled) {
if (enable && syncEnabled && !taskbarTab) {
mainWindowEl.setAttribute("fxatoolbarmenu", "visible");
// We have to manually update the sync state UI when toggling the FxA toolbar

View file

@ -159,6 +159,7 @@
"SubDialogManager",
"TabCrashHandler",
"TabsSetupFlowManager",
"TaskbarTabUI",
"TelemetryEnvironment",
"TranslationsParent",
"UITour",

View file

@ -430,7 +430,6 @@ var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
);
var gChromeMap = new Map();
var gOverrideMap = new Map();
var gComponentsSet = new Set();
// In this map when the value is a Set of URLs, the file is referenced if any
// of the files in the Set is referenced.
@ -505,8 +504,6 @@ function parseManifest(manifestUri) {
}
} else if (type == "resource") {
trackResourcePrefix(argv[0]);
} else if (type == "component") {
gComponentsSet.add(argv[1]);
}
}
});
@ -647,6 +644,10 @@ function parseCodeFile(fileUri) {
/["'`]chrome:\/\/[a-zA-Z0-9-]+\/(content|skin|locale)\/[^"'` ]*["'`]/g
);
if (!urls) {
urls = line.match(/["']moz-src:\/\/\/[^"']+["']/g);
}
if (!urls) {
urls = line.match(/["']resource:\/\/[^"']+["']/g);
if (
@ -734,7 +735,10 @@ function parseCodeFile(fileUri) {
if (!/\.(properties|js|jsm|mjs|json|css)$/.test(url)) {
url += ".js";
}
if (url.startsWith("resource://")) {
if (
url.startsWith("resource://") ||
url.startsWith("moz-src:///")
) {
addCodeReference(url, fileUri);
} else {
// if we end up with a chrome:// url here, it's likely because
@ -784,13 +788,22 @@ function parseCodeFile(fileUri) {
function convertToCodeURI(fileUri) {
let baseUri = fileUri;
let path = "";
while (true) {
while (baseUri) {
let slashPos = baseUri.lastIndexOf("/", baseUri.length - 2);
if (slashPos <= 0) {
// File not accessible from chrome protocol, try resource://
for (let res of gResourceMap) {
if (fileUri.startsWith(res[1])) {
return fileUri.replace(res[1], "resource://" + res[0] + "/");
let resourceUriString = fileUri.replace(
res[1],
`resource://${res[0]}/`
);
// If inside moz-src, treat as moz-src url.
resourceUriString = resourceUriString.replace(
/^resource:\/\/gre\/moz-src\//,
"moz-src:///"
);
return resourceUriString;
}
}
// Give up and return the original URL.
@ -802,6 +815,7 @@ function convertToCodeURI(fileUri) {
return gChromeMap.get(baseUri) + path;
}
}
throw new Error(`Unparsable URI: ${fileUri}`);
}
async function chromeFileExists(aURI) {
@ -849,6 +863,7 @@ function findChromeUrlsFromArray(array, prefix) {
// Only keep strings that look like real chrome or resource urls.
if (
/chrome:\/\/[a-zA-Z09-]+\/(content|skin|locale)\//.test(string) ||
/moz-src:\/\/\/\w+/.test(string) ||
/resource:\/\/[a-zA-Z09-]*\/.*\.[a-z]+/.test(string)
) {
gReferencesFromCode.set(string, null);
@ -862,10 +877,12 @@ add_task(async function checkAllTheFiles() {
const libxul = await IOUtils.read(PathUtils.xulLibraryPath);
findChromeUrlsFromArray(libxul, "chrome://");
findChromeUrlsFromArray(libxul, "resource://");
findChromeUrlsFromArray(libxul, "moz-src:///");
// Handle NS_LITERAL_STRING.
let uint16 = new Uint16Array(libxul.buffer);
findChromeUrlsFromArray(uint16, "chrome://");
findChromeUrlsFromArray(uint16, "resource://");
findChromeUrlsFromArray(uint16, "moz-src:///");
const kCodeExtensions = [
".xml",
@ -954,6 +971,7 @@ add_task(async function checkAllTheFiles() {
// the non-devtools paths:
let devtoolsPrefixes = [
"chrome://devtools",
"moz-src:///devtools/",
"resource://devtools/",
"resource://devtools-shared-images/",
"resource://devtools-highlighter-styles/",
@ -968,7 +986,9 @@ add_task(async function checkAllTheFiles() {
for (let uri of uris) {
uri = convertToCodeURI(uri.spec);
if (
(uri.startsWith("chrome://") || uri.startsWith("resource://")) &&
(uri.startsWith("chrome://") ||
uri.startsWith("resource://") ||
uri.startsWith("moz-src:///")) &&
isDevtools == hasDevtoolsPrefix(uri)
) {
chromeFiles.push(uri);
@ -1025,9 +1045,6 @@ add_task(async function checkAllTheFiles() {
if (rv && f.startsWith("resource://app/")) {
rv = isUnreferenced(f.replace("resource://app/", "resource:///"));
}
if (rv && /^resource:\/\/(?:app|gre)\/components\/[^/]+\.js$/.test(f)) {
rv = !gComponentsSet.has(f.replace(/.*\//, ""));
}
if (!rv) {
foundReference = true;
if (useAllowlist) {
@ -1108,7 +1125,9 @@ add_task(async function checkAllTheFiles() {
}
if (
(file.startsWith("chrome://") || file.startsWith("resource://")) &&
(file.startsWith("chrome://") ||
file.startsWith("resource://") ||
file.startsWith("moz-src:///")) &&
!(await chromeFileExists(file))
) {
// Ignore chrome prefixes that have been automatically expanded.

View file

@ -9,6 +9,17 @@
category app-startup nsBrowserGlue @mozilla.org/browser/browserglue;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110}
# Browser global components initializing before UI startup
category browser-before-ui-startup resource:///modules/sessionstore/SessionStore.sys.mjs SessionStore.init
category browser-before-ui-startup resource:///modules/BuiltInThemes.sys.mjs BuiltInThemes.maybeInstallActiveBuiltInTheme
#ifdef MOZ_NORMANDY
category browser-before-ui-startup resource://normandy/Normandy.sys.mjs Normandy.init
#endif
category browser-before-ui-startup chrome://pocket/content/SaveToPocket.sys.mjs SaveToPocket.init
category browser-before-ui-startup resource:///modules/ResetPBMPanel.sys.mjs ResetPBMPanel.init
category browser-before-ui-startup resource:///modules/AboutHomeStartupCache.sys.mjs AboutHomeStartupCache.init
category browser-before-ui-startup resource:///modules/AccountsGlue.sys.mjs AccountsGlue.init
# Browser window lifecycle consumers
category browser-window-domcontentloaded-before-tabbrowser resource:///modules/BrowserDOMWindow.sys.mjs BrowserDOMWindow.setupInWindow
category browser-window-domcontentloaded resource:///modules/BrowserWindowTracker.sys.mjs BrowserWindowTracker.track
@ -20,19 +31,19 @@ category browser-window-delayed-startup resource:///modules/ReportBrokenSite.sys
category browser-window-delayed-startup resource:///modules/SearchUIUtils.sys.mjs SearchUIUtils.init
category browser-window-unload resource:///modules/BrowserDOMWindow.sys.mjs BrowserDOMWindow.teardownInWindow
category browser-window-unload resource:///modules/NewTabPagePreloading.sys.mjs NewTabPagePreloading.removePreloadedBrowser
category browser-window-unload moz-src:///browser/components/tabbrowser/NewTabPagePreloading.sys.mjs NewTabPagePreloading.removePreloadedBrowser
# App startup consumers
category browser-idle-startup resource:///modules/PlacesUIUtils.sys.mjs PlacesUIUtils.unblockToolbars
category browser-idle-startup resource:///modules/BuiltInThemes.sys.mjs BuiltInThemes.ensureBuiltInThemes
category browser-idle-startup resource://gre/modules/RFPHelper.sys.mjs RFPHelper.init
category browser-idle-startup resource://gre/modules/Blocklist.sys.mjs Blocklist.loadBlocklistAsync
category browser-idle-startup resource:///modules/TabUnloader.sys.mjs TabUnloader.init
category browser-idle-startup moz-src:///browser/components/tabbrowser/TabUnloader.sys.mjs TabUnloader.init
category browser-idle-startup resource:///modules/GenAI.sys.mjs GenAI.init
category browser-idle-startup resource:///modules/QuickSuggest.sys.mjs QuickSuggest.init
category browser-idle-startup resource:///modules/UrlbarSearchTermsPersistence.sys.mjs UrlbarSearchTermsPersistence.init
category browser-idle-startup resource:///modules/ShoppingUtils.sys.mjs ShoppingUtils.init
category browser-idle-startup resource:///modules/SearchSERPTelemetry.sys.mjs SearchSERPCategorization.init
category browser-idle-startup resource:///modules/SERPCategorization.sys.mjs SERPCategorization.init
category browser-idle-startup resource://gre/modules/ContentRelevancyManager.sys.mjs ContentRelevancyManager.init
#ifdef MOZ_UPDATER
category browser-idle-startup resource://gre/modules/UpdateListener.sys.mjs UpdateListener.maybeShowUnsupportedNotification
@ -51,6 +62,8 @@ category browser-quit-application-granted resource://normandy/Normandy.sys.mjs N
category browser-quit-application-granted resource://gre/modules/RFPHelper.sys.mjs RFPHelper.uninit
category browser-quit-application-granted resource:///modules/ShoppingUtils.sys.mjs ShoppingUtils.uninit
category browser-quit-application-granted resource:///modules/asrouter/ASRouterNewTabHook.sys.mjs ASRouterNewTabHook.destroy
category browser-quit-application-granted resource:///modules/SERPCategorization.sys.mjs SERPCategorization.uninit
category browser-quit-application-granted resource:///modules/SearchSERPTelemetry.sys.mjs SearchSERPTelemetry.uninit
#ifdef MOZ_UPDATER
category browser-quit-application-granted resource://gre/modules/UpdateListener.sys.mjs UpdateListener.reset
#endif

File diff suppressed because it is too large Load diff

View file

@ -104,12 +104,15 @@ class RequestInfos {
}
/**
* Gets all requests across all browsing contexts
* Gets all requests.
*
* @returns {Array<object>} all the requests
*/
getAllRequests() {
return this.#map.values();
return this.#map
.values()
.map(entry => entry.request)
.toArray();
}
}

View file

@ -1042,6 +1042,7 @@ export var Policies = {
setAndLockPref("datareporting.healthreport.uploadEnabled", false);
setAndLockPref("datareporting.policy.dataSubmissionEnabled", false);
setAndLockPref("toolkit.telemetry.archive.enabled", false);
setAndLockPref("datareporting.usage.uploadEnabled", false);
blockAboutPage(manager, "about:telemetry");
}
},

View file

@ -41,6 +41,15 @@
"manifest": ["commands"],
"paths": [["commands"]]
},
"contextualIdentities": {
"url": "chrome://extensions/content/parent/ext-contextualIdentities.js",
"schema": "chrome://extensions/content/schemas/contextual_identities.json",
"scopes": ["addon_parent"],
"settings": true,
"events": ["startup"],
"permissions": ["contextualIdentities"],
"paths": [["contextualIdentities"]]
},
"devtools": {
"url": "chrome://browser/content/parent/ext-devtools.js",
"schema": "chrome://browser/content/schemas/devtools.json",

View file

@ -19,7 +19,7 @@ add_task(async function test_autoDiscardable() {
]),
"parent.js": () => {
const { TabUnloader } = ChromeUtils.importESModule(
"resource:///modules/TabUnloader.sys.mjs"
"moz-src:///browser/components/tabbrowser/TabUnloader.sys.mjs"
);
/* globals ExtensionAPI, ExtensionUtils */
const { ExtensionError } = ExtensionUtils;

View file

@ -65,6 +65,7 @@ DIRS += [
"syncedtabs",
"tabbrowser",
"tabunloader",
"taskbartabs",
"textrecognition",
"topsites",
"translations",

View file

@ -0,0 +1,903 @@
/* 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/. */
let lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
HomePage: "resource:///modules/HomePage.sys.mjs",
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
});
/**
* AboutHomeStartupCache is responsible for reading and writing the
* initial about:home document from the HTTP cache as a startup
* performance optimization. It only works when the "privileged about
* content process" is enabled and when ENABLED_PREF is set to true.
*
* See https://firefox-source-docs.mozilla.org/browser/extensions/newtab/docs/v2-system-addon/about_home_startup_cache.html
* for further details.
*/
export var AboutHomeStartupCache = {
ABOUT_HOME_URI_STRING: "about:home",
SCRIPT_EXTENSION: "script",
ENABLED_PREF: "browser.startup.homepage.abouthome_cache.enabled",
PRELOADED_NEWTAB_PREF: "browser.newtab.preload",
LOG_LEVEL_PREF: "browser.startup.homepage.abouthome_cache.loglevel",
// It's possible that the layout of about:home will change such that
// we want to invalidate any pre-existing caches. We do this by setting
// this meta key in the nsICacheEntry for the page.
//
// The version is currently set to the build ID, meaning that the cache
// is invalidated after every upgrade (like the main startup cache).
CACHE_VERSION_META_KEY: "version",
LOG_NAME: "AboutHomeStartupCache",
// These messages are used to request the "privileged about content process"
// to create the cached document, and then to receive that document.
CACHE_REQUEST_MESSAGE: "AboutHomeStartupCache:CacheRequest",
CACHE_RESPONSE_MESSAGE: "AboutHomeStartupCache:CacheResponse",
CACHE_USAGE_RESULT_MESSAGE: "AboutHomeStartupCache:UsageResult",
// When a "privileged about content process" is launched, this message is
// sent to give it some nsIInputStream's for the about:home document they
// should load.
SEND_STREAMS_MESSAGE: "AboutHomeStartupCache:InputStreams",
// This time in ms is used to debounce messages that are broadcast to
// all about:newtab's, or the preloaded about:newtab. We use those
// messages as a signal that it's likely time to refresh the cache.
CACHE_DEBOUNCE_RATE_MS: 5000,
// This is how long we'll block the AsyncShutdown while waiting for
// the cache to write. If we fail to write within that time, we will
// allow the shutdown to proceed.
SHUTDOWN_CACHE_WRITE_TIMEOUT_MS: 1000,
// The following values are as possible values for the
// browser.startup.abouthome_cache_result scalar. Keep these in sync with the
// scalar definition in Scalars.yaml and the matching Glean metric in
// browser/components/metrics.yaml. See setDeferredResult for more
// information.
CACHE_RESULT_SCALARS: {
UNSET: 0,
DOES_NOT_EXIST: 1,
CORRUPT_PAGE: 2,
CORRUPT_SCRIPT: 3,
INVALIDATED: 4,
LATE: 5,
VALID_AND_USED: 6,
DISABLED: 7,
NOT_LOADING_ABOUTHOME: 8,
PRELOADING_DISABLED: 9,
},
// This will be set to one of the values of CACHE_RESULT_SCALARS
// once it is determined which result best suits what occurred.
_cacheDeferredResultScalar: -1,
// A reference to the nsICacheEntry to read from and write to.
_cacheEntry: null,
// These nsIPipe's are sent down to the "privileged about content process"
// immediately after the process launches. This allows us to race the loading
// of the cache entry in the parent process with the load of the about:home
// page in the content process, since we'll connect the InputStream's to
// the pipes as soon as the nsICacheEntry is available.
//
// The page pipe is for the HTML markup for the page.
_pagePipe: null,
// The script pipe is for the JavaScript that the HTML markup loads
// to set its internal state.
_scriptPipe: null,
_cacheDeferred: null,
_enabled: false,
_initted: false,
_hasWrittenThisSession: false,
_finalized: false,
_firstPrivilegedProcessCreated: false,
init() {
if (this._initted) {
throw new Error("AboutHomeStartupCache already initted.");
}
this.setDeferredResult(this.CACHE_RESULT_SCALARS.UNSET);
this._enabled = Services.prefs.getBoolPref(
"browser.startup.homepage.abouthome_cache.enabled"
);
if (!this._enabled) {
this.recordResult(this.CACHE_RESULT_SCALARS.DISABLED);
return;
}
this.log = console.createInstance({
prefix: this.LOG_NAME,
maxLogLevelPref: this.LOG_LEVEL_PREF,
});
this.log.trace("Initting.");
// If the user is not configured to load about:home at startup, then
// let's not bother with the cache - loading it needlessly is more likely
// to hinder what we're actually trying to load.
let willLoadAboutHome =
!lazy.HomePage.overridden &&
Services.prefs.getIntPref("browser.startup.page") === 1;
if (!willLoadAboutHome) {
this.log.trace("Not configured to load about:home by default.");
this.recordResult(this.CACHE_RESULT_SCALARS.NOT_LOADING_ABOUTHOME);
return;
}
if (!Services.prefs.getBoolPref(this.PRELOADED_NEWTAB_PREF, false)) {
this.log.trace("Preloaded about:newtab disabled.");
this.recordResult(this.CACHE_RESULT_SCALARS.PRELOADING_DISABLED);
return;
}
Services.obs.addObserver(this, "ipc:content-created");
Services.obs.addObserver(this, "process-type-set");
Services.obs.addObserver(this, "ipc:content-shutdown");
Services.obs.addObserver(this, "intl:app-locales-changed");
this.log.trace("Constructing pipes.");
this._pagePipe = this.makePipe();
this._scriptPipe = this.makePipe();
this._cacheEntryPromise = new Promise(resolve => {
this._cacheEntryResolver = resolve;
});
let lci = Services.loadContextInfo.default;
let storage = Services.cache2.diskCacheStorage(lci);
try {
storage.asyncOpenURI(
this.aboutHomeURI,
"",
Ci.nsICacheStorage.OPEN_PRIORITY,
this
);
} catch (e) {
this.log.error("Failed to open about:home cache entry", e);
}
this._cacheTask = new lazy.DeferredTask(async () => {
await this.cacheNow();
}, this.CACHE_DEBOUNCE_RATE_MS);
lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
"AboutHomeStartupCache: Writing cache",
async () => {
await this.onShutdown();
},
() => this._cacheProgress
);
this._cacheDeferred = null;
this._initted = true;
this.log.trace("Initialized.");
},
get initted() {
return this._initted;
},
uninit() {
if (!this._enabled) {
return;
}
try {
Services.obs.removeObserver(this, "ipc:content-created");
Services.obs.removeObserver(this, "process-type-set");
Services.obs.removeObserver(this, "ipc:content-shutdown");
Services.obs.removeObserver(this, "intl:app-locales-changed");
} catch (e) {
// If we failed to initialize and register for these observer
// notifications, then attempting to remove them will throw.
// It's fine to ignore that case on shutdown.
}
if (this._cacheTask) {
this._cacheTask.disarm();
this._cacheTask = null;
}
this._pagePipe = null;
this._scriptPipe = null;
this._initted = false;
this._cacheEntry = null;
this._hasWrittenThisSession = false;
this._cacheEntryPromise = null;
this._cacheEntryResolver = null;
this._cacheDeferredResultScalar = -1;
if (this.log) {
this.log.trace("Uninitialized.");
this.log = null;
}
this._procManager = null;
this._procManagerID = null;
this._appender = null;
this._cacheDeferred = null;
this._finalized = false;
this._firstPrivilegedProcessCreated = false;
},
_aboutHomeURI: null,
get aboutHomeURI() {
if (this._aboutHomeURI) {
return this._aboutHomeURI;
}
this._aboutHomeURI = Services.io.newURI(this.ABOUT_HOME_URI_STRING);
return this._aboutHomeURI;
},
// For the AsyncShutdown blocker, this is used to populate the progress
// value.
_cacheProgress: "Not yet begun",
/**
* Called by the AsyncShutdown blocker on quit-application-granted
* to potentially flush the most recent cache to disk. If one was
* never written during the session, one is generated and written
* before the async function resolves.
*
* @param {boolean} withTimeout
* Whether or not the timeout mechanism should be used. Defaults
* to true.
* @returns {Promise<boolean>}
* If a cache has never been written, or a cache write is in
* progress, resolves true when the cache has been written. Also
* resolves to true if a cache didn't need to be written.
*
* Resolves to false if a cache write unexpectedly timed out.
*/
async onShutdown(withTimeout = true) {
// If we never wrote this session, arm the task so that the next
// step can finalize.
if (!this._hasWrittenThisSession) {
this.log.trace("Never wrote a cache this session. Arming cache task.");
this._cacheTask.arm();
}
Glean.browserStartup.abouthomeCacheShutdownwrite.set(
this._cacheTask.isArmed
);
if (this._cacheTask.isArmed) {
this.log.trace("Finalizing cache task on shutdown");
this._finalized = true;
// To avoid hanging shutdowns, we'll ensure that we wait a maximum of
// SHUTDOWN_CACHE_WRITE_TIMEOUT_MS millseconds before giving up.
const TIMED_OUT = Symbol();
let timeoutID = 0;
let timeoutPromise = new Promise(resolve => {
timeoutID = lazy.setTimeout(
() => resolve(TIMED_OUT),
this.SHUTDOWN_CACHE_WRITE_TIMEOUT_MS
);
});
let promises = [this._cacheTask.finalize()];
if (withTimeout) {
this.log.trace("Using timeout mechanism.");
promises.push(timeoutPromise);
} else {
this.log.trace("Skipping timeout mechanism.");
}
let result = await Promise.race(promises);
this.log.trace("Done blocking shutdown.");
lazy.clearTimeout(timeoutID);
if (result === TIMED_OUT) {
this.log.error("Timed out getting cache streams. Skipping cache task.");
return false;
}
}
this.log.trace("onShutdown is exiting");
return true;
},
/**
* Called by the _cacheTask DeferredTask to actually do the work of
* caching the about:home document.
*
* @returns {Promise<undefined>}
* Resolves when a fresh version of the cache has been written.
*/
async cacheNow() {
this.log.trace("Caching now.");
this._cacheProgress = "Getting cache streams";
let { pageInputStream, scriptInputStream } = await this.requestCache();
if (!pageInputStream || !scriptInputStream) {
this.log.trace("Failed to get cache streams.");
this._cacheProgress = "Failed to get streams";
return;
}
this.log.trace("Got cache streams.");
this._cacheProgress = "Writing to cache";
try {
this.log.trace("Populating cache.");
await this.populateCache(pageInputStream, scriptInputStream);
} catch (e) {
this._cacheProgress = "Failed to populate cache";
this.log.error("Populating the cache failed: ", e);
return;
}
this._cacheProgress = "Done";
this.log.trace("Done writing to cache.");
this._hasWrittenThisSession = true;
},
/**
* Requests the cached document streams from the "privileged about content
* process".
*
* @returns {Promise<object>}
* Resolves with an Object with the following properties:
*
* pageInputStream (nsIInputStream)
* The page content to write to the cache, or null if request the streams
* failed.
*
* scriptInputStream (nsIInputStream)
* The script content to write to the cache, or null if request the streams
* failed.
*/
requestCache() {
this.log.trace("Parent is requesting Activity Stream state object.");
if (!this._procManager) {
this.log.error("requestCache called with no _procManager!");
return { pageInputStream: null, scriptInputStream: null };
}
if (
this._procManager.remoteType != lazy.E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE
) {
this.log.error("Somehow got the wrong process type.");
return { pageInputStream: null, scriptInputStream: null };
}
let state = lazy.AboutNewTab.activityStream.store.getState();
return new Promise(resolve => {
this._cacheDeferred = resolve;
this.log.trace("Parent is requesting cache streams.");
this._procManager.sendAsyncMessage(this.CACHE_REQUEST_MESSAGE, { state });
});
},
/**
* Helper function that returns a newly constructed nsIPipe instance.
*
* @returns {nsIPipe}
*/
makePipe() {
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
pipe.init(
true /* non-blocking input */,
true /* non-blocking output */,
0 /* segment size */,
0 /* max segments */
);
return pipe;
},
get pagePipe() {
return this._pagePipe;
},
get scriptPipe() {
return this._scriptPipe;
},
/**
* Called when the nsICacheEntry has been accessed. If the nsICacheEntry
* has content that we want to send down to the "privileged about content
* process", then we connect that content to the nsIPipe's that may or
* may not have already been sent down to the process.
*
* In the event that the nsICacheEntry doesn't contain anything usable,
* the nsInputStreams on the nsIPipe's are closed.
*/
connectToPipes() {
this.log.trace(`Connecting nsICacheEntry to pipes.`);
// If the cache doesn't yet exist, we'll know because the version metadata
// won't exist yet.
let version;
try {
this.log.trace("");
version = this._cacheEntry.getMetaDataElement(
this.CACHE_VERSION_META_KEY
);
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
this.log.debug("Cache meta data does not exist. Closing streams.");
this.pagePipe.outputStream.close();
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.DOES_NOT_EXIST);
return;
}
throw e;
}
this.log.info("Version retrieved is", version);
if (version != Services.appinfo.appBuildID) {
this.log.info("Version does not match! Dooming and closing streams.\n");
// This cache is no good - doom it, and prepare for a new one.
this.clearCache();
this.pagePipe.outputStream.close();
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.INVALIDATED);
return;
}
let cachePageInputStream;
try {
cachePageInputStream = this._cacheEntry.openInputStream(0);
} catch (e) {
this.log.error("Failed to open main input stream for cache entry", e);
this.pagePipe.outputStream.close();
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.CORRUPT_PAGE);
return;
}
this.log.trace("Connecting page stream to pipe.");
lazy.NetUtil.asyncCopy(
cachePageInputStream,
this.pagePipe.outputStream,
() => {
this.log.info("Page stream connected to pipe.");
}
);
let cacheScriptInputStream;
try {
this.log.trace("Connecting script stream to pipe.");
cacheScriptInputStream =
this._cacheEntry.openAlternativeInputStream("script");
lazy.NetUtil.asyncCopy(
cacheScriptInputStream,
this.scriptPipe.outputStream,
() => {
this.log.info("Script stream connected to pipe.");
}
);
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
// For some reason, the script was not available. We'll close the pipe
// without sending anything into it. The privileged about content process
// will notice that there's nothing available in the pipe, and fall back
// to dynamically generating the page.
this.log.error("Script stream not available! Closing pipe.");
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.CORRUPT_SCRIPT);
} else {
throw e;
}
}
this.setDeferredResult(this.CACHE_RESULT_SCALARS.VALID_AND_USED);
this.log.trace("Streams connected to pipes.");
},
/**
* Called when we have received a the cache values from the "privileged
* about content process". The page and script streams are written to
* the nsICacheEntry.
*
* This writing is asynchronous, and if a write happens to already be
* underway when this function is called, that latter call will be
* ignored.
*
* @param {nsIInputStream} pageInputStream
* A stream containing the HTML markup to be saved to the cache.
* @param {nsIInputStream} scriptInputStream
* A stream containing the JS hydration script to be saved to the cache.
* @returns {Promise<undefined, Error>}
* When the cache has been successfully written to.
* Rejects with a JS Error if writing any part of the cache happens to
* fail.
*/
async populateCache(pageInputStream, scriptInputStream) {
await this.ensureCacheEntry();
await new Promise((resolve, reject) => {
// Doom the old cache entry, so we can start writing to a new one.
this.log.trace("Populating the cache. Dooming old entry.");
this.clearCache();
this.log.trace("Opening the page output stream.");
let pageOutputStream;
try {
pageOutputStream = this._cacheEntry.openOutputStream(0, -1);
} catch (e) {
reject(e);
return;
}
this.log.info("Writing the page cache.");
lazy.NetUtil.asyncCopy(pageInputStream, pageOutputStream, pageResult => {
if (!Components.isSuccessCode(pageResult)) {
this.log.error("Failed to write page. Result: " + pageResult);
reject(new Error(pageResult));
return;
}
this.log.trace(
"Writing the page data is complete. Now opening the " +
"script output stream."
);
let scriptOutputStream;
try {
scriptOutputStream = this._cacheEntry.openAlternativeOutputStream(
"script",
-1
);
} catch (e) {
reject(e);
return;
}
this.log.info("Writing the script cache.");
lazy.NetUtil.asyncCopy(
scriptInputStream,
scriptOutputStream,
scriptResult => {
if (!Components.isSuccessCode(scriptResult)) {
this.log.error("Failed to write script. Result: " + scriptResult);
reject(new Error(scriptResult));
return;
}
this.log.trace(
"Writing the script cache is done. Setting version."
);
try {
this._cacheEntry.setMetaDataElement(
"version",
Services.appinfo.appBuildID
);
} catch (e) {
this.log.error("Failed to write version.");
reject(e);
return;
}
this.log.trace(`Version is set to ${Services.appinfo.appBuildID}.`);
this.log.info("Caching of page and script is done.");
resolve();
}
);
});
});
this.log.trace("populateCache has finished.");
},
/**
* Returns a Promise that resolves once the nsICacheEntry for the cache
* is available to write to and read from.
*
* @returns {Promise<nsICacheEntry, string>}
* Resolves once the cache entry has become available.
*
* Rejects with an error message if getting the cache entry is attempted
* before the AboutHomeStartupCache component has been initialized.
*/
ensureCacheEntry() {
if (!this._initted) {
return Promise.reject(
"Cannot ensureCacheEntry - AboutHomeStartupCache is not initted"
);
}
return this._cacheEntryPromise;
},
/**
* Clears the contents of the cache.
*/
clearCache() {
this.log.trace("Clearing the cache.");
this._cacheEntry = this._cacheEntry.recreate();
this._cacheEntryPromise = new Promise(resolve => {
resolve(this._cacheEntry);
});
this._hasWrittenThisSession = false;
},
/**
* Called when a content process is created. If this is the "privileged
* about content process", then the cache streams will be sent to it.
*
* @param {number} childID
* The unique ID for the content process that was created, as passed by
* ipc:content-created.
* @param {ProcessMessageManager} procManager
* The ProcessMessageManager for the created content process.
* @param {nsIDOMProcessParent} processParent
* The nsIDOMProcessParent for the tab.
*/
onContentProcessCreated(childID, procManager, processParent) {
if (procManager.remoteType == lazy.E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE) {
if (this._finalized) {
this.log.trace(
"Ignoring privileged about content process launch after finalization."
);
return;
}
if (this._firstPrivilegedProcessCreated) {
this.log.trace(
"Ignoring non-first privileged about content processes."
);
return;
}
this.log.trace(
`A privileged about content process is launching with ID ${childID}.`
);
this.log.info("Sending input streams down to content process.");
let actor = processParent.getActor("BrowserProcess");
actor.sendAsyncMessage(this.SEND_STREAMS_MESSAGE, {
pageInputStream: this.pagePipe.inputStream,
scriptInputStream: this.scriptPipe.inputStream,
});
procManager.addMessageListener(this.CACHE_RESPONSE_MESSAGE, this);
procManager.addMessageListener(this.CACHE_USAGE_RESULT_MESSAGE, this);
this._procManager = procManager;
this._procManagerID = childID;
this._firstPrivilegedProcessCreated = true;
}
},
/**
* Called when a content process is destroyed. Either it shut down normally,
* or it crashed. If this is the "privileged about content process", then some
* internal state is cleared.
*
* @param {number} childID
* The unique ID for the content process that was created, as passed by
* ipc:content-shutdown.
*/
onContentProcessShutdown(childID) {
this.log.info(`Content process shutdown: ${childID}`);
if (this._procManagerID == childID) {
this.log.info("It was the current privileged about process.");
if (this._cacheDeferred) {
this.log.error(
"A privileged about content process shut down while cache streams " +
"were still en route."
);
// The crash occurred while we were waiting on cache input streams to
// be returned to us. Resolve with null streams instead.
this._cacheDeferred({ pageInputStream: null, scriptInputStream: null });
this._cacheDeferred = null;
}
this._procManager.removeMessageListener(
this.CACHE_RESPONSE_MESSAGE,
this
);
this._procManager.removeMessageListener(
this.CACHE_USAGE_RESULT_MESSAGE,
this
);
this._procManager = null;
this._procManagerID = null;
}
},
/**
* Called externally by ActivityStreamMessageChannel anytime
* a message is broadcast to all about:newtabs, or sent to the
* preloaded about:newtab. This is used to determine if we need
* to refresh the cache.
*/
onPreloadedNewTabMessage() {
if (!this._initted || !this._enabled) {
return;
}
if (this._finalized) {
this.log.trace("Ignoring preloaded newtab update after finalization.");
return;
}
this.log.trace("Preloaded about:newtab was updated.");
this._cacheTask.disarm();
this._cacheTask.arm();
},
/**
* Stores the CACHE_RESULT_SCALARS value that most accurately represents
* the current notion of how the cache has operated so far. It is stored
* temporarily like this because we need to hear from the privileged
* about content process to hear whether or not retrieving the cache
* actually worked on that end. The success state reported back from
* the privileged about content process will be compared against the
* deferred result scalar to compute what will be recorded to
* Telemetry.
*
* Note that this value will only be recorded if its value is GREATER
* than the currently recorded value. This is because it's possible for
* certain functions that record results to re-enter - but we want to record
* the _first_ condition that caused the cache to not be read from.
*
* @param {number} result
* One of the CACHE_RESULT_SCALARS values. If this value is less than
* the currently recorded value, it is ignored.
*/
setDeferredResult(result) {
if (this._cacheDeferredResultScalar < result) {
this._cacheDeferredResultScalar = result;
}
},
/**
* Records the final result of how the cache operated for the user
* during this session to Telemetry.
*
* @param {number} result
* One of the result constants from CACHE_RESULT_SCALARS.
*/
recordResult(result) {
// Note: this can be called very early on in the lifetime of
// AboutHomeStartupCache, so things like this.log might not exist yet.
Glean.browserStartup.abouthomeCacheResult.set(result);
},
/**
* Called when the parent process receives a message from the privileged
* about content process saying whether or not reading from the cache
* was successful.
*
* @param {boolean} success
* True if reading from the cache succeeded.
*/
onUsageResult(success) {
this.log.trace(`Received usage result. Success = ${success}`);
if (success) {
if (
this._cacheDeferredResultScalar !=
this.CACHE_RESULT_SCALARS.VALID_AND_USED
) {
this.log.error(
"Somehow got a success result despite having never " +
"successfully sent down the cache streams"
);
this.recordResult(this._cacheDeferredResultScalar);
} else {
this.recordResult(this.CACHE_RESULT_SCALARS.VALID_AND_USED);
}
return;
}
if (
this._cacheDeferredResultScalar ==
this.CACHE_RESULT_SCALARS.VALID_AND_USED
) {
// We failed to read from the cache despite having successfully
// sent it down to the content process. We presume then that the
// streams just didn't provide any bytes in time.
this.recordResult(this.CACHE_RESULT_SCALARS.LATE);
} else {
// We failed to read the cache, but already knew why. We can
// now record that value.
this.recordResult(this._cacheDeferredResultScalar);
}
},
QueryInterface: ChromeUtils.generateQI([
"nsICacheEntryOpenallback",
"nsIObserver",
]),
/* MessageListener */
receiveMessage(message) {
// Only the privileged about content process can write to the cache.
if (
message.target.remoteType != lazy.E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE
) {
this.log.error(
"Received a message from a non-privileged content process!"
);
return;
}
switch (message.name) {
case this.CACHE_RESPONSE_MESSAGE: {
this.log.trace("Parent received cache streams.");
if (!this._cacheDeferred) {
this.log.error("Parent doesn't have _cacheDeferred set up!");
return;
}
this._cacheDeferred(message.data);
this._cacheDeferred = null;
break;
}
case this.CACHE_USAGE_RESULT_MESSAGE: {
this.onUsageResult(message.data.success);
break;
}
}
},
/* nsIObserver */
observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "intl:app-locales-changed": {
this.clearCache();
break;
}
case "process-type-set":
// Intentional fall-through
case "ipc:content-created": {
let childID = aData;
let procManager = aSubject
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIMessageSender);
let pp = aSubject.QueryInterface(Ci.nsIDOMProcessParent);
this.onContentProcessCreated(childID, procManager, pp);
break;
}
case "ipc:content-shutdown": {
let childID = aData;
this.onContentProcessShutdown(childID);
break;
}
}
},
/* nsICacheEntryOpenCallback */
onCacheEntryCheck() {
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
},
onCacheEntryAvailable(aEntry) {
this.log.trace("Cache entry is available.");
this._cacheEntry = aEntry;
this.connectToPipes();
this._cacheEntryResolver(this._cacheEntry);
},
};

View file

@ -14,6 +14,7 @@ XPIDL_SOURCES += [
XPIDL_MODULE = "browser-newtab"
EXTRA_JS_MODULES += [
"AboutHomeStartupCache.sys.mjs",
"AboutNewTabService.sys.mjs",
]

View file

@ -15,7 +15,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.sys.mjs",
OpenInTabsUtils:
"moz-src:///browser/components/tabbrowser/OpenInTabsUtils.sys.mjs",
PlacesTransactions: "resource://gre/modules/PlacesTransactions.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,7 @@ EXTRA_JS_MODULES += [
"SearchOneOffs.sys.mjs",
"SearchSERPTelemetry.sys.mjs",
"SearchUIUtils.sys.mjs",
"SERPCategorization.sys.mjs",
]
BROWSER_CHROME_MANIFESTS += [

View file

@ -9,8 +9,7 @@ const TELEMETRY_PREF =
"browser.search.serpEventTelemetryCategorization.enabled";
ChromeUtils.defineESModuleGetters(lazy, {
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
});
XPCOMUtils.defineLazyPreferenceGetter(
@ -166,7 +165,7 @@ add_task(async function test_enable_experiment_when_pref_is_not_enabled() {
);
Assert.ok(
lazy.SearchSERPDomainToCategoriesMap.empty,
lazy.SERPDomainToCategoriesMap.empty,
"Domain to categories map should be empty."
);

View file

@ -10,11 +10,11 @@
*/
ChromeUtils.defineESModuleGetters(this, {
CATEGORIZATION_SETTINGS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SERPCategorization.sys.mjs",
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
ExperimentFakes: "resource://testing-common/NimbusTestUtils.sys.mjs",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorizationRecorder: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorizationRecorder: "resource:///modules/SERPCategorization.sys.mjs",
});
const TEST_PROVIDER_INFO = [

View file

@ -108,7 +108,7 @@ add_task(async function test_count_incremented_if_map_is_not_downloaded() {
resetTelemetry();
// Clear the existing domain-to-categories map.
await SearchSERPDomainToCategoriesMap.uninit({ deleteMap: true });
await SERPDomainToCategoriesMap.uninit({ deleteMap: true });
let sandbox = sinon.createSandbox();
sandbox
@ -120,7 +120,7 @@ add_task(async function test_count_incremented_if_map_is_not_downloaded() {
msg.wrappedJSObject.arguments[0].includes("Could not download file:")
);
});
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
info("Wait for download error.");
await downloadError;
info("Domain-to-categories map unsuccessfully downloaded.");
@ -158,7 +158,7 @@ add_task(async function test_threshold_reached() {
// Simulate a broken domain-to-categories map.
let sandbox = sinon.createSandbox();
sandbox.stub(SearchSERPDomainToCategoriesMap, "get").returns([]);
sandbox.stub(SERPDomainToCategoriesMap, "get").returns([]);
let submitted = false;
GleanPings.serpCategorization.testBeforeNextSubmit(reason => {

View file

@ -11,9 +11,8 @@
ChromeUtils.defineESModuleGetters(this, {
TELEMETRY_CATEGORIZATION_DOWNLOAD_SETTINGS:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
"resource:///modules/SERPCategorization.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});
@ -233,7 +232,7 @@ add_task(async function test_download_after_multiple_failures() {
Assert.equal(consoleObserved, false, "Encountered download failure");
Assert.equal(timeout, true, "Timeout occured");
Assert.ok(SearchSERPDomainToCategoriesMap.empty, "Map is empty");
Assert.ok(SERPDomainToCategoriesMap.empty, "Map is empty");
// Clean up.
await SpecialPowers.popPrefEnv();
@ -285,7 +284,7 @@ add_task(async function test_cancel_download_timer() {
await Promise.race([firstPromise, secondPromise]);
Assert.equal(consoleObserved, false, "Encountered download failure");
Assert.equal(timeout, true, "Timeout occured");
Assert.ok(SearchSERPDomainToCategoriesMap.empty, "Map is empty");
Assert.ok(SERPDomainToCategoriesMap.empty, "Map is empty");
// Clean up.
await resetCategorizationCollection(record);

View file

@ -9,14 +9,17 @@
* a test of the ping's submission upon startup.)
*/
// Can fail in TV mode.
requestLongerTimeout(2);
ChromeUtils.defineESModuleGetters(this, {
CATEGORIZATION_SETTINGS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
DomainToCategoriesStore: "resource:///modules/SearchSERPTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SERPCategorization.sys.mjs",
DomainToCategoriesStore: "resource:///modules/SERPCategorization.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorizationRecorder: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorizationRecorder: "resource:///modules/SERPCategorization.sys.mjs",
TELEMETRY_CATEGORIZATION_KEY:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
"resource:///modules/SERPCategorization.sys.mjs",
});
const TEST_PROVIDER_INFO = [
@ -306,7 +309,7 @@ add_task(async function test_count_incremented_if_store_is_not_created() {
resetTelemetry();
// Clear the existing domain-to-categories map.
await SearchSERPDomainToCategoriesMap.uninit({ deleteMap: true });
await SERPDomainToCategoriesMap.uninit({ deleteMap: true });
let sandbox = sinon.createSandbox();
sandbox
@ -314,7 +317,7 @@ add_task(async function test_count_incremented_if_store_is_not_created() {
.throws(new Error());
// Initializing should fail and cause the component to un-initialize.
let promise = waitForDomainToCategoriesUninit();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
info("Store for the domain-to-categories map not created successfully.");
@ -331,6 +334,6 @@ add_task(async function test_count_incremented_if_store_is_not_created() {
);
sandbox.restore();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await BrowserTestUtils.removeTab(tab);
});

View file

@ -8,9 +8,8 @@
*/
ChromeUtils.defineESModuleGetters(this, {
CATEGORIZATION_SETTINGS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SERPCategorization.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
});
const TEST_PROVIDER_INFO = [
@ -180,8 +179,8 @@ add_task(async function test_no_reporting_if_download_failure() {
// The map is going to attempt to redo a download. There are other tests that
// do it, so instead reset the map so later tests don't get interrupted by
// a sync event caused by this test.
await SearchSERPDomainToCategoriesMap.uninit();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.uninit();
await SERPDomainToCategoriesMap.init();
});
add_task(async function test_no_reporting_if_no_records() {

View file

@ -11,8 +11,8 @@
*/
ChromeUtils.defineESModuleGetters(this, {
SearchSERPCategorizationEventScheduler:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorizationEventScheduler:
"resource:///modules/SERPCategorization.sys.mjs",
});
const TEST_PROVIDER_INFO = [
@ -74,8 +74,8 @@ add_setup(async function () {
"browser.search.serpEventTelemetryCategorization.enabled"
)
) {
SearchSERPCategorizationEventScheduler.uninit();
SearchSERPCategorizationEventScheduler.init();
SERPCategorizationEventScheduler.uninit();
SERPCategorizationEventScheduler.init();
}
await waitForIdle();
@ -98,8 +98,8 @@ add_setup(async function () {
await waitForDomainToCategoriesUninit();
} else {
// The scheduler uses the mock idle service.
SearchSERPCategorizationEventScheduler.uninit();
SearchSERPCategorizationEventScheduler.init();
SERPCategorizationEventScheduler.uninit();
SERPCategorizationEventScheduler.init();
}
SearchSERPTelemetry.overrideSearchTelemetryForTests();
resetTelemetry();

View file

@ -9,11 +9,11 @@
*/
ChromeUtils.defineESModuleGetters(this, {
CATEGORIZATION_SETTINGS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPCategorizationEventScheduler:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SERPCategorization.sys.mjs",
SERPCategorizationEventScheduler:
"resource:///modules/SERPCategorization.sys.mjs",
TELEMETRY_CATEGORIZATION_KEY:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
"resource:///modules/SERPCategorization.sys.mjs",
});
const TEST_PROVIDER_INFO = [
@ -79,8 +79,8 @@ add_setup(async function () {
"browser.search.serpEventTelemetryCategorization.enabled"
)
) {
SearchSERPCategorizationEventScheduler.uninit();
SearchSERPCategorizationEventScheduler.init();
SERPCategorizationEventScheduler.uninit();
SERPCategorizationEventScheduler.init();
}
await waitForIdle();
@ -103,8 +103,8 @@ add_setup(async function () {
await waitForDomainToCategoriesUninit();
} else {
// The scheduler uses the mock idle service.
SearchSERPCategorizationEventScheduler.uninit();
SearchSERPCategorizationEventScheduler.init();
SERPCategorizationEventScheduler.uninit();
SERPCategorizationEventScheduler.init();
}
CATEGORIZATION_SETTINGS.WAKE_TIMEOUT_MS = oldWakeTimeout;
SearchSERPTelemetry.overrideSearchTelemetryForTests();

View file

@ -15,7 +15,8 @@ ChromeUtils.defineESModuleGetters(this, {
ADLINK_CHECK_TIMEOUT_MS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
SEARCH_TELEMETRY_SHARED: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPCategorization: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPCategorization:
"resource:///modules/SearchSERPCategorization.sys.mjs",
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",

View file

@ -5,24 +5,23 @@ ChromeUtils.defineESModuleGetters(this, {
ADLINK_CHECK_TIMEOUT_MS:
"resource:///actors/SearchSERPTelemetryChild.sys.mjs",
BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SERPCategorization.sys.mjs",
CustomizableUITestUtils:
"resource://testing-common/CustomizableUITestUtils.sys.mjs",
Region: "resource://gre/modules/Region.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
SEARCH_TELEMETRY_SHARED: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPTelemetryUtils: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
SERPCategorizationRecorder: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorizationRecorder: "resource:///modules/SERPCategorization.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
sinon: "resource://testing-common/Sinon.sys.mjs",
SPA_ADLINK_CHECK_TIMEOUT_MS:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
TELEMETRY_CATEGORIZATION_KEY:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
"resource:///modules/SERPCategorization.sys.mjs",
TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
VISIBILITY_THRESHOLD: "resource:///actors/SearchSERPTelemetryChild.sys.mjs",
});

View file

@ -46,7 +46,7 @@ class TestPingSubmitted(MarionetteTestCase):
# Record an event for the ping to eventually submit.
self.marionette.execute_script(
"""
const { SERPCategorizationRecorder } = ChromeUtils.importESModule("resource:///modules/SearchSERPTelemetry.sys.mjs");
const { SERPCategorizationRecorder } = ChromeUtils.importESModule("resource:///modules/SERPCategorization.sys.mjs");
SERPCategorizationRecorder.recordCategorizationTelemetry({
organic_category: "3",
organic_num_domains: "1",

View file

@ -7,8 +7,8 @@
*/
ChromeUtils.defineESModuleGetters(this, {
CATEGORIZATION_SETTINGS: "resource:///modules/SearchSERPTelemetry.sys.mjs",
DomainToCategoriesStore: "resource:///modules/SearchSERPTelemetry.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SERPCategorization.sys.mjs",
DomainToCategoriesStore: "resource:///modules/SERPCategorization.sys.mjs",
sinon: "resource://testing-common/Sinon.sys.mjs",
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
});

View file

@ -9,10 +9,9 @@
"use strict";
ChromeUtils.defineESModuleGetters(this, {
SearchSERPCategorization: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPTelemetryUtils: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorization: "resource:///modules/SERPCategorization.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
CATEGORIZATION_SETTINGS: "resource:///modules/SERPCategorization.sys.mjs",
});
ChromeUtils.defineLazyGetter(this, "gCryptoHash", () => {
@ -115,11 +114,11 @@ add_setup(async () => {
"browser.search.serpEventTelemetryCategorization.enabled",
true
);
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
});
add_task(async function test_categorization_simple() {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE
);
@ -137,7 +136,7 @@ add_task(async function test_categorization_simple() {
]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.deepEqual(
resultsToReport,
@ -147,7 +146,7 @@ add_task(async function test_categorization_simple() {
});
add_task(async function test_categorization_inconclusive() {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_INCONCLUSIVE
);
@ -165,12 +164,12 @@ add_task(async function test_categorization_inconclusive() {
]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.deepEqual(
resultsToReport,
{
category: SearchSERPTelemetryUtils.CATEGORIZATION.INCONCLUSIVE,
category: CATEGORIZATION_SETTINGS.INCONCLUSIVE,
num_domains: 10,
num_inconclusive: 10,
num_unknown: 0,
@ -182,7 +181,7 @@ add_task(async function test_categorization_inconclusive() {
add_task(async function test_categorization_unknown() {
// Reusing TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE since none of this task's
// domains will be keys within it.
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_SIMPLE
);
@ -200,12 +199,12 @@ add_task(async function test_categorization_unknown() {
]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.deepEqual(
resultsToReport,
{
category: SearchSERPTelemetryUtils.CATEGORIZATION.INCONCLUSIVE,
category: CATEGORIZATION_SETTINGS.INCONCLUSIVE,
num_domains: 10,
num_inconclusive: 0,
num_unknown: 10,
@ -215,7 +214,7 @@ add_task(async function test_categorization_unknown() {
});
add_task(async function test_categorization_unknown_and_inconclusive() {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_UNKNOWN_AND_INCONCLUSIVE
);
@ -233,12 +232,12 @@ add_task(async function test_categorization_unknown_and_inconclusive() {
]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.deepEqual(
resultsToReport,
{
category: SearchSERPTelemetryUtils.CATEGORIZATION.INCONCLUSIVE,
category: CATEGORIZATION_SETTINGS.INCONCLUSIVE,
num_domains: 10,
num_inconclusive: 5,
num_unknown: 5,
@ -249,7 +248,7 @@ add_task(async function test_categorization_unknown_and_inconclusive() {
// Tests a mixture of categorized, inconclusive and unknown domains.
add_task(async function test_categorization_all_types() {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_ALL_TYPES
);
@ -269,7 +268,7 @@ add_task(async function test_categorization_all_types() {
]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.deepEqual(
resultsToReport,
@ -284,7 +283,7 @@ add_task(async function test_categorization_all_types() {
});
add_task(async function test_categorization_tie() {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_TIE
);
@ -302,7 +301,7 @@ add_task(async function test_categorization_tie() {
]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.equal(
[1, 2].includes(resultsToReport.category),
@ -322,7 +321,7 @@ add_task(async function test_categorization_tie() {
});
add_task(async function test_rank_penalization_equal_scores() {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_1
);
@ -340,7 +339,7 @@ add_task(async function test_rank_penalization_equal_scores() {
]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.deepEqual(
resultsToReport,
@ -350,14 +349,14 @@ add_task(async function test_rank_penalization_equal_scores() {
});
add_task(async function test_rank_penalization_highest_score_lower_on_page() {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
TEST_DOMAIN_TO_CATEGORIES_MAP_RANK_PENALIZATION_2
);
let domains = new Set(["test61.com", "test62.com"]);
let resultsToReport =
await SearchSERPCategorization.applyCategorizationLogic(domains);
await SERPCategorization.applyCategorizationLogic(domains);
Assert.deepEqual(
resultsToReport,

View file

@ -8,8 +8,7 @@
"use strict";
ChromeUtils.defineESModuleGetters(this, {
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
});
add_task(async function record_matches_region() {
@ -108,10 +107,7 @@ add_task(async function record_matches_region() {
for (let { title, record, expectedResult, region } of TESTS) {
info(title);
let result = SearchSERPDomainToCategoriesMap.recordMatchesRegion(
record,
region
);
let result = SERPDomainToCategoriesMap.recordMatchesRegion(record, region);
Assert.equal(result, expectedResult, "Result should match.");
}
});
@ -214,10 +210,7 @@ add_task(async function find_records_for_region() {
for (let { title, record, expectedResult, region } of TESTS) {
info(title);
let result = SearchSERPDomainToCategoriesMap.findRecordsForRegion(
record,
region
);
let result = SERPDomainToCategoriesMap.findRecordsForRegion(record, region);
Assert.deepEqual(result, expectedResult, "Result should match.");
}
});

View file

@ -10,11 +10,10 @@
ChromeUtils.defineESModuleGetters(this, {
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
Region: "resource://gre/modules/Region.sys.mjs",
SearchSERPCategorization: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPCategorization: "resource:///modules/SERPCategorization.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
TELEMETRY_CATEGORIZATION_KEY:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
"resource:///modules/SERPCategorization.sys.mjs",
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
});
@ -182,24 +181,24 @@ add_task(async function test_initial_import() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 100 }],
"Return value from lookup of example.com should be the same."
);
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.org"),
await SERPDomainToCategoriesMap.get("example.org"),
[{ category: 2, score: 90 }],
"Return value from lookup of example.org should be the same."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function test_update_records() {
@ -216,7 +215,7 @@ add_task(async function test_update_records() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
info("Send update from Remote Settings with updates to attachments.");
@ -238,13 +237,13 @@ add_task(async function test_update_records() {
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 80 }],
"Return value from lookup of example.com should have changed."
);
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.org"),
await SERPDomainToCategoriesMap.get("example.org"),
[
{ category: 2, score: 50 },
{ category: 4, score: 80 },
@ -253,14 +252,14 @@ add_task(async function test_update_records() {
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
2,
"Version should be correct."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function test_delayed_initial_import() {
@ -274,10 +273,10 @@ add_task(async function test_delayed_initial_import() {
);
});
info("Initialize without records.");
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await observeNoRecordsFound;
Assert.ok(SearchSERPDomainToCategoriesMap.empty, "Map is empty.");
Assert.ok(SERPDomainToCategoriesMap.empty, "Map is empty.");
info("Send update from Remote Settings with updates to attachments.");
let record1a = await mockRecordWithCachedAttachment(RECORDS.record1a);
@ -295,26 +294,26 @@ add_task(async function test_delayed_initial_import() {
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 100 }],
"Return value from lookup of example.com should be the same."
);
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.org"),
await SERPDomainToCategoriesMap.get("example.org"),
[{ category: 2, score: 90 }],
"Return value from lookup of example.org should be the same."
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
1,
"Version should be correct."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function test_remove_record() {
@ -331,11 +330,11 @@ add_task(async function test_remove_record() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 80 }],
"Initialized properly."
);
@ -354,26 +353,26 @@ add_task(async function test_remove_record() {
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 80 }],
"Return value from lookup of example.com should remain unchanged."
);
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.org"),
await SERPDomainToCategoriesMap.get("example.org"),
[],
"Return value from lookup of example.org should be empty."
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
2,
"Version should be correct."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function test_different_versions_coexisting() {
@ -390,11 +389,11 @@ add_task(async function test_different_versions_coexisting() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[
{
category: 1,
@ -405,7 +404,7 @@ add_task(async function test_different_versions_coexisting() {
);
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.org"),
await SERPDomainToCategoriesMap.get("example.org"),
[
{ category: 2, score: 50 },
{ category: 4, score: 80 },
@ -414,14 +413,14 @@ add_task(async function test_different_versions_coexisting() {
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
2,
"Version should be the latest."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function test_download_error() {
@ -434,11 +433,11 @@ add_task(async function test_download_error() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[
{
category: 1,
@ -449,7 +448,7 @@ add_task(async function test_download_error() {
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
1,
"Version should be present."
);
@ -477,20 +476,20 @@ add_task(async function test_download_error() {
await observeDownloadError;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[],
"Domain should not exist in store."
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
null,
"Version should remain null."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function test_mock_restart() {
@ -507,11 +506,11 @@ add_task(async function test_mock_restart() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPCategorization.init();
await SERPCategorization.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[
{
category: 1,
@ -522,19 +521,19 @@ add_task(async function test_mock_restart() {
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
2,
"Version should be the latest."
);
info("Mock a restart by un-initializing the map.");
await SearchSERPCategorization.uninit();
await SERPCategorization.uninit();
promise = waitForDomainToCategoriesUpdate();
await SearchSERPCategorization.init();
await SERPCategorization.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[
{
category: 1,
@ -545,14 +544,14 @@ add_task(async function test_mock_restart() {
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
2,
"Version should be the latest."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function update_record_from_non_matching_region() {
@ -565,11 +564,11 @@ add_task(async function update_record_from_non_matching_region() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 100 }],
"Return value from lookup of example.com should exist."
);
@ -597,26 +596,26 @@ add_task(async function update_record_from_non_matching_region() {
await observeNoChange;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 100 }],
"Return value from lookup of example.com should still exist."
);
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.ca"),
await SERPDomainToCategoriesMap.get("example.ca"),
[],
"Domain from non-home region should not exist."
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
1,
"Version should be remain the same."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function update_record_from_non_matching_region() {
@ -629,11 +628,11 @@ add_task(async function update_record_from_non_matching_region() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 100 }],
"Return value from lookup of example.com should exist."
);
@ -661,26 +660,26 @@ add_task(async function update_record_from_non_matching_region() {
await observeNoChange;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 100 }],
"Return value from lookup of example.com should still exist."
);
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.ca"),
await SERPDomainToCategoriesMap.get("example.ca"),
[],
"Domain from non-home region should not exist."
);
Assert.equal(
SearchSERPDomainToCategoriesMap.version,
SERPDomainToCategoriesMap.version,
1,
"Version should be remain the same."
);
// Clean up.
await db.clear();
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});
add_task(async function update_() {
@ -693,17 +692,17 @@ add_task(async function update_() {
info("Initialize search categorization mappings.");
let promise = waitForDomainToCategoriesUpdate();
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await promise;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[{ category: 1, score: 100 }],
"Return value from lookup of example.com should exist."
);
// Re-init the Map to mimic a restart.
await SearchSERPDomainToCategoriesMap.uninit();
await SERPDomainToCategoriesMap.uninit();
info("Change home region to one that doesn't match region of map.");
let originalHomeRegion = Region.home;
@ -718,10 +717,10 @@ add_task(async function update_() {
);
});
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await observeDropStore;
Assert.deepEqual(
await SearchSERPDomainToCategoriesMap.get("example.com"),
await SERPDomainToCategoriesMap.get("example.com"),
[],
"Return value from lookup of example.com should be empty."
);
@ -729,5 +728,5 @@ add_task(async function update_() {
// Clean up.
await db.clear();
Region._setHomeRegion(originalHomeRegion);
await SearchSERPDomainToCategoriesMap.uninit(true);
await SERPDomainToCategoriesMap.uninit(true);
});

View file

@ -9,8 +9,7 @@
ChromeUtils.defineESModuleGetters(this, {
Region: "resource://gre/modules/Region.sys.mjs",
SearchSERPDomainToCategoriesMap:
"resource:///modules/SearchSERPTelemetry.sys.mjs",
SERPDomainToCategoriesMap: "resource:///modules/SERPCategorization.sys.mjs",
});
// For the tests, domains aren't checked, but add at least one value so the
@ -30,7 +29,7 @@ add_setup(async () => {
"browser.search.serpEventTelemetryCategorization.enabled",
true
);
await SearchSERPDomainToCategoriesMap.init();
await SERPDomainToCategoriesMap.init();
await Region.init();
let originalRegion = Region.home;
Region._setHomeRegion(USER_REGION);
@ -203,9 +202,9 @@ const TESTS = [
add_task(async function test_sync_may_modify_store() {
for (let test of TESTS) {
if (test.emptyMap) {
await SearchSERPDomainToCategoriesMap.overrideMapForTests({}, 0, false);
await SERPDomainToCategoriesMap.overrideMapForTests({}, 0, false);
} else {
await SearchSERPDomainToCategoriesMap.overrideMapForTests(
await SERPDomainToCategoriesMap.overrideMapForTests(
DATA,
VERSION,
test.isDefault
@ -213,11 +212,11 @@ add_task(async function test_sync_may_modify_store() {
}
info(
`Domain to Categories Map: ${
SearchSERPDomainToCategoriesMap.empty ? "Empty" : "Has Existing Data"
SERPDomainToCategoriesMap.empty ? "Empty" : "Has Existing Data"
}.`
);
info(`${test.title}.`);
let result = await SearchSERPDomainToCategoriesMap.syncMayModifyStore(
let result = await SERPDomainToCategoriesMap.syncMayModifyStore(
test.data,
USER_REGION
);

View file

@ -6240,9 +6240,13 @@ var SessionStoreInternal = {
*/
restoreWindowFeatures: function ssi_restoreWindowFeatures(aWindow, aWinData) {
var hidden = aWinData.hidden ? aWinData.hidden.split(",") : [];
WINDOW_HIDEABLE_FEATURES.forEach(function (aItem) {
aWindow[aItem].visible = !hidden.includes(aItem);
});
var isTaskbarTab =
aWindow.document.documentElement.getAttribute("taskbartab");
if (!isTaskbarTab) {
WINDOW_HIDEABLE_FEATURES.forEach(function (aItem) {
aWindow[aItem].visible = !hidden.includes(aItem);
});
}
if (aWinData.isPopup) {
this._windows[aWindow.__SSi].isPopup = true;
@ -6251,7 +6255,7 @@ var SessionStoreInternal = {
}
} else {
delete this._windows[aWindow.__SSi].isPopup;
if (aWindow.gURLBar) {
if (aWindow.gURLBar && !isTaskbarTab) {
aWindow.gURLBar.readOnly = false;
}
}

View file

@ -1629,9 +1629,10 @@ static nsresult PinShortcutToTaskbarImpl(bool aCheckOnly,
// during install or runtime - causes a race between it propagating to the
// virtual `shell:appsfolder` and attempts to pin via `ITaskbarManager`,
// resulting in pin failures when the latter occurs before the former. We can
// skip this when we're only checking whether we're pinned.
if (!aCheckOnly && !PollAppsFolderForShortcut(
aAppUserModelId, TimeDuration::FromSeconds(15))) {
// skip this when we're in a MSIX build or only checking whether we're pinned.
if (!widget::WinUtils::HasPackageIdentity() && !aCheckOnly &&
!PollAppsFolderForShortcut(aAppUserModelId,
TimeDuration::FromSeconds(15))) {
return NS_ERROR_FILE_NOT_FOUND;
}

View file

@ -25,7 +25,7 @@
position: sticky;
top: 0;
width: 100%;
z-index: 2;
z-index: 3;
&.header-wrapper-overflow {
align-items: baseline;

View file

@ -173,6 +173,7 @@ var SidebarController = {
revampL10nId: "sidebar-menu-review-checker-label",
iconUrl: "chrome://browser/content/shopping/assets/shopping.svg",
gleanEvent: Glean.shopping.sidebarToggle,
gleanClickEvent: Glean.sidebar.shoppingReviewCheckerIconClick,
recordSidebarVersion: true,
}
);

View file

@ -243,6 +243,26 @@ sidebar:
addon_id:
type: string
description: The extension's ID.
shopping_review_checker_icon_click:
type: event
description: >
The Review Checker icon was clicked.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1951175
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1951175
data_sensitivity:
- interaction
expires: 147
notification_emails:
- betling@mozilla.com
- fx-desktop-shopping-eng@mozilla.com
send_in_pings:
- events
extra_keys:
sidebar_open:
type: boolean
description: Whether the sidebar is expanded or collapsed.
keyboard_shortcut:
type: event
description: >

View file

@ -610,6 +610,37 @@ async function testIconClick(expanded) {
Services.fog.testResetFOG();
}
async function testIconClickReviewChecker(expanded) {
await SpecialPowers.pushPrefEnv({
set: [
[
"sidebar.main.tools",
"aichat,syncedtabs,history,bookmarks,reviewchecker",
],
],
});
const { sidebarMain } = SidebarController;
await SidebarController.initializeUIState({ launcherExpanded: expanded });
let reviewCheckerButton = sidebarMain.shadowRoot.querySelector(
"moz-button[view='viewReviewCheckerSidebar']"
);
EventUtils.synthesizeMouseAtCenter(reviewCheckerButton, {});
let event = Glean.sidebar.shoppingReviewCheckerIconClick.testGetValue();
Assert.equal(event?.length, 1, "One event was reported.");
Assert.deepEqual(
event?.[0].extra,
{ sidebar_open: `${expanded}` },
`Event indicates the sidebar was ${expanded ? "expanded" : "collapsed"}.`
);
await SpecialPowers.popPrefEnv();
Services.fog.testResetFOG();
}
add_task(async function test_icon_click_collapsed_sidebar() {
await testIconClick(false);
});
@ -617,3 +648,11 @@ add_task(async function test_icon_click_collapsed_sidebar() {
add_task(async function test_icon_click_expanded_sidebar() {
await testIconClick(true);
});
add_task(async function test_review_checker_icon_click_collapsed_sidebar() {
await testIconClickReviewChecker(false);
});
add_task(async function test_review_checker_icon_click_expanded_sidebar() {
await testIconClickReviewChecker(true);
});

View file

@ -9,7 +9,8 @@ let log = ChromeUtils.importESModule(
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.sys.mjs",
OpenInTabsUtils:
"moz-src:///browser/components/tabbrowser/OpenInTabsUtils.sys.mjs",
});
/**

View file

@ -206,52 +206,63 @@ export class SmartTabGroupingManager {
allTabs,
groupedIndices,
alreadyGroupedIndices,
threshold = NEAREST_NEIGHBOR_DEFAULT_THRESHOLD
threshold = NEAREST_NEIGHBOR_DEFAULT_THRESHOLD,
precomputedEmbeddings = [],
depth = 1
) {
// get tabs in group first
const tabsInGroup = groupedIndices.map(i => allTabs[i]);
const tabsInGroupData = await this._prepareTabData(tabsInGroup);
const tabsInGroupEmbeddings = await this._generateEmbeddings(
tabsInGroupData.map(a => a[EMBED_TEXT_KEY])
);
// get embeddings for all the tabs
const tabData = await this._prepareTabData(allTabs);
let embeddings = precomputedEmbeddings;
if (precomputedEmbeddings.length === 0) {
embeddings = await this._generateEmbeddings(
tabData.map(a => a[EMBED_TEXT_KEY])
);
}
// get tabs that we need to assign
// get tabs that need to be assigned
const groupedTabIndices = groupedIndices.concat(alreadyGroupedIndices);
const tabsToAssign = allTabs.filter(
(_, index) => !groupedTabIndices.includes(index)
);
const tabsToAssignData = await this._prepareTabData(tabsToAssign);
const tabsToAssignEmbeddings = await this._generateEmbeddings(
tabsToAssignData.map(a => a[EMBED_TEXT_KEY])
);
const tabsToAssignIndices = allTabs
.map((_, index) => index)
.filter(i => !groupedTabIndices.includes(i));
// find closest tabs
// if any tab is close to a tab in the existing group, add to list
const closestTabs = [];
// select MAX_NN_GROUPED_TABS so too many tabs in same group won't cause performance issues
for (let i = 0; i < tabsToAssign.length; i++) {
let closestTabs = [];
const similarTabsIndices = [];
for (let i = 0; i < tabsToAssignIndices.length; i++) {
let closestScore = null;
for (
let j = 0;
j < Math.min(tabsInGroup.length, MAX_NN_GROUPED_TABS);
j < Math.min(groupedIndices.length, MAX_NN_GROUPED_TABS);
j++
) {
const cosineSim = cosSim(
tabsToAssignEmbeddings[i],
tabsInGroupEmbeddings[j]
embeddings[tabsToAssignIndices[i]],
embeddings[groupedIndices[j]]
);
if (!closestScore || cosineSim > closestScore) {
closestScore = cosineSim;
}
}
if (closestScore > threshold) {
closestTabs.push([tabsToAssign[i], closestScore]);
closestTabs.push([allTabs[tabsToAssignIndices[i]], closestScore]);
similarTabsIndices.push(tabsToAssignIndices[i]);
}
}
// sort and return by tabs that are most similar
closestTabs.sort((a, b) => b[1] - a[1]);
return closestTabs.map(t => t[0]);
closestTabs = closestTabs.map(t => t[0]);
// recurse once if the initial call only had a single tab
// and we found at least 1 similar tab - this improves recall
if (groupedIndices.length === 1 && !!closestTabs.length && depth === 1) {
const recurseSimilarTabs = await this.findNearestNeighbors(
allTabs,
similarTabsIndices,
alreadyGroupedIndices.concat(groupedIndices),
threshold,
embeddings,
depth - 1
);
closestTabs = closestTabs.concat(recurseSimilarTabs);
}
return closestTabs;
}
/**

View file

@ -7,9 +7,9 @@
ChromeUtils.defineESModuleGetters(this, {
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
GroupsPanel: "resource:///modules/GroupsList.sys.mjs",
GroupsPanel: "moz-src:///browser/components/tabbrowser/GroupsList.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
TabsPanel: "resource:///modules/TabsList.sys.mjs",
TabsPanel: "moz-src:///browser/components/tabbrowser/TabsList.sys.mjs",
});
var gTabsPanel = {

View file

@ -106,9 +106,11 @@
);
ChromeUtils.defineESModuleGetters(this, {
AsyncTabSwitcher: "resource:///modules/AsyncTabSwitcher.sys.mjs",
AsyncTabSwitcher:
"moz-src:///browser/components/tabbrowser/AsyncTabSwitcher.sys.mjs",
PictureInPicture: "resource://gre/modules/PictureInPicture.sys.mjs",
SmartTabGroupingManager: "resource:///modules/SmartTabGrouping.sys.mjs",
SmartTabGroupingManager:
"moz-src:///browser/components/tabbrowser/SmartTabGrouping.sys.mjs",
UrlbarProviderOpenTabs:
"resource:///modules/UrlbarProviderOpenTabs.sys.mjs",
});
@ -8295,62 +8297,67 @@ var TabBarVisibility = {
_initialUpdateDone: false,
update(force = false) {
let toolbar = document.getElementById("TabsToolbar");
let navbar = document.getElementById("nav-bar");
let hideTabstrip = false;
let isPopup = !window.toolbar.visible;
let isVerticalTabs = Services.prefs.getBoolPref(
"sidebar.verticalTabs",
false
);
let nonPopupWithVerticalTabs = !isPopup && isVerticalTabs;
if (
!gBrowser /* gBrowser isn't initialized yet */ ||
gBrowser.visibleTabs.length == 1
) {
hideTabstrip = isPopup;
}
let isTaskbarTab = document.documentElement.hasAttribute("taskbartab");
let isSingleTabWindow = isPopup || isTaskbarTab;
if (nonPopupWithVerticalTabs) {
// CustomTitlebar decides if we can draw within the titlebar area.
// In vertical tabs mode, the toolbar with the horizontal tabstrip gets hidden
// and the navbar becomes a titlebar.
hideTabstrip = true;
CustomTitlebar.allowedBy("tabs-visible", true);
} else {
CustomTitlebar.allowedBy("tabs-visible", !hideTabstrip);
}
let hasVerticalTabs =
!isSingleTabWindow &&
Services.prefs.getBoolPref("sidebar.verticalTabs", false);
gNavToolbox.toggleAttribute("tabs-hidden", hideTabstrip);
// When `gBrowser` has not been initialized, we're opening a new window and
// assume only a single tab is loading.
let hasSingleTab = !gBrowser || gBrowser.visibleTabs.length == 1;
// To prevent tabs being lost, hiding the tabs toolbar should only work
// when only a single tab is visible or tabs are displayed elsewhere.
let hideTabsToolbar =
(isSingleTabWindow && hasSingleTab) || hasVerticalTabs;
// We only want a non-customized titlebar for popups. It should not be the
// case, but if a popup window contains more than one tab we re-enable
// titlebar customization and display tabs.
CustomTitlebar.allowedBy("non-popup", !(isPopup && hasSingleTab));
// Update the browser chrome.
let tabsToolbar = document.getElementById("TabsToolbar");
let navbar = document.getElementById("nav-bar");
gNavToolbox.toggleAttribute("tabs-hidden", hideTabsToolbar);
// Should the nav-bar look and function like a titlebar?
navbar.classList.toggle(
"browser-titlebar",
CustomTitlebar.enabled && hideTabstrip
CustomTitlebar.enabled && hideTabsToolbar
);
document
.getElementById("browser")
.classList.toggle(
"browser-toolbox-background",
CustomTitlebar.enabled && nonPopupWithVerticalTabs
CustomTitlebar.enabled && hasVerticalTabs
);
if (
hideTabstrip == toolbar.collapsed &&
hideTabsToolbar == tabsToolbar.collapsed &&
!force &&
this._initialUpdateDone
) {
// no further updates needed, toolbar.collapsed already matches hideTabstrip
// No further updates needed, `TabsToolbar` already matches the expected
// visibilty.
return;
}
this._initialUpdateDone = true;
toolbar.collapsed = hideTabstrip;
tabsToolbar.collapsed = hideTabsToolbar;
document.getElementById("menu_closeWindow").hidden = hideTabstrip;
// Stylize close menu items based on tab visibility. When a window will only
// ever have a single tab, only show the option to close the tab, and
// simplify the text since we don't need to disambiguate from closing the window.
document.getElementById("menu_closeWindow").hidden = hideTabsToolbar;
document.l10n.setAttributes(
document.getElementById("menu_close"),
hideTabstrip
hideTabsToolbar
? "tabbrowser-menuitem-close"
: "tabbrowser-menuitem-close-tab"
);

View file

@ -408,7 +408,7 @@
? "chrome://global/skin/icons/highlights.svg"
: "";
const { SmartTabGroupingManager } = ChromeUtils.importESModule(
"resource:///modules/SmartTabGrouping.sys.mjs"
"moz-src:///browser/components/tabbrowser/SmartTabGrouping.sys.mjs"
);
this.#smartTabGroupingManager = new SmartTabGroupingManager();

View file

@ -7,7 +7,7 @@ with Files("**"):
JAR_MANIFESTS += ["jar.mn"]
EXTRA_JS_MODULES += [
MOZ_SRC_FILES += [
"AsyncTabSwitcher.sys.mjs",
"GroupsList.sys.mjs",
"NewTabPagePreloading.sys.mjs",

View file

@ -4,7 +4,7 @@
"use strict";
const { TabUnloader } = ChromeUtils.importESModule(
"resource:///modules/TabUnloader.sys.mjs"
"moz-src:///browser/components/tabbrowser/TabUnloader.sys.mjs"
);
async function refreshData() {

View file

@ -0,0 +1,29 @@
/* 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 https://mozilla.org/MPL/2.0/. */
// Set up Taskbar Tabs Window UI
export var TaskbarTabUI = {
init(window) {
let document = window.document;
if (!document.documentElement.hasAttribute("taskbartab")) {
return;
}
// Ensure tab strip is hidden
window.TabBarVisibility.update();
// Hide pocket button
const saveToPocketButton = document.getElementById("save-to-pocket-button");
if (saveToPocketButton) {
saveToPocketButton.remove();
document.documentElement.setAttribute("pocketdisabled", "true");
}
// Hide bookmark star
document.getElementById("star-button-box").style.display = "none";
// Hide fxa in the hamburger menu
document.documentElement.setAttribute("fxadisabled", true);
},
};

View file

@ -0,0 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_JS_MODULES += [
"TaskbarTabUI.sys.mjs",
]
BROWSER_CHROME_MANIFESTS += [
"test/browser/browser.toml",
]

View file

@ -0,0 +1,4 @@
[DEFAULT]
["browser_taskbarTabs_chromeTest.js"]
run-if = ["os == 'win'"]

View file

@ -0,0 +1,95 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
});
// Given a window, check if it meets all requirements
// of the taskbar tab chrome UI
function checkWindowChrome(win) {
let document = win.document.documentElement;
ok(
document.hasAttribute("taskbartab"),
"The window HTML should have a taskbartab attribute"
);
ok(win.gURLBar.readOnly, "The URL bar should be read-only");
ok(
win.document.getElementById("TabsToolbar").collapsed,
"The tab bar should be collapsed"
);
is(
document.getAttribute("chromehidden"),
"menubar directories extrachrome ",
"The correct chrome hidden attributes should be populated"
);
ok(!win.menubar.visible, "menubar barprop should not be visible");
ok(!win.personalbar.visible, "personalbar barprop should not be visible");
is(
document.getAttribute("pocketdisabled"),
"true",
"Pocket button should be disabled"
);
let starButton = win.document.querySelector("#star-button-box");
is(
win.getComputedStyle(starButton).display,
"none",
"Bookmark button should not be visible"
);
ok(
!document.hasAttribute("fxatoolbarmenu"),
"Firefox accounts menu should not be displayed"
);
}
// Given a window, check if hamburger menu
// buttons that aren't relevant to taskbar tabs
// are hidden
function checkHamburgerMenu(win) {
let document = win.document.documentElement;
is(
document.getAttribute("fxadisabled"),
"true",
"fxadisabled attribute should be true"
);
}
add_task(async function testWindowChrome() {
let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
Ci.nsIWritablePropertyBag2
);
extraOptions.setPropertyAsBool("taskbartab", true);
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
args.appendElement(null);
args.appendElement(extraOptions);
args.appendElement(null);
// Simulate opening a taskbar tab window
let win = Services.ww.openWindow(
null,
AppConstants.BROWSER_CHROME_URL,
"_blank",
"chrome,dialog=no,titlebar,close,toolbar,location,personalbar=no,status,menubar=no,resizable,minimizable,scrollbars",
args
);
await new Promise(resolve => {
win.addEventListener("load", resolve, { once: true });
});
await win.delayedStartupPromise;
checkWindowChrome(win);
checkHamburgerMenu(win);
await BrowserTestUtils.closeWindow(win);
});

View file

@ -166,6 +166,7 @@ this.formautofill = class extends ExtensionAPI {
esModuleURI: "resource://autofill/FormAutofillChild.sys.mjs",
events: {
focusin: {},
"form-changed": { createActor: false },
"form-submission-detected": { createActor: false },
},
},

View file

@ -6,6 +6,10 @@ support-files = [
"../fixtures/autocomplete_iframe.html",
"../fixtures/autocomplete_iframe_sandboxed.html",
"../fixtures/autocomplete_simple_basic.html",
"../fixtures/dynamic_form_changes.html",
"../fixtures/form_change_on_user_interaction.html",
"../fixtures/dynamic_formless_changes_node_mutations.html",
"../fixtures/dynamic_formless_changes_element_visiblity_state.html",
"../fixtures/page_navigation.html",
"./empty.html",
"../fixtures/**",
@ -30,6 +34,8 @@ skip-if = [
["browser_autofill_address_select_inexact.js"]
["browser_autofill_address_select_match_isoid.js"]
["browser_autofill_creditCard_name.js"]
["browser_autofill_creditCard_type.js"]
@ -55,6 +61,8 @@ skip-if = [
["browser_dropdown_layout.js"]
["browser_dynamic_form_change_detection.js"]
["browser_editAddressDialog.js"]
skip-if = [
"verify",
@ -66,6 +74,8 @@ skip-if = [
["browser_fillclear_events.js"]
["browser_fill_on_dynamic_form_change_detection.js"]
["browser_form_changes.js"]
["browser_iframe_autofill_cc_number.js"]

View file

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// This test ensures that an inexact substring match on an option label is performed
// when autofilling dropdown values.
const TEST_PROFILE = {
"given-name": "Joe",
"family-name": "Smith",
"street-address": "7 First St",
"address-level1": "BR",
country: "IN",
};
const MARKUP_SELECT_STATE = `
<html><body>
<form>
<input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<input id="street" autocomplete="street-address">
<input id="address-level2" autocomplete="address-level2">
<select id="address-level1" autocomplete="address-level1">
<option value=""> --- Please Select --- </option>
<option value="1475">Andaman and Nicobar Islands</option>
<option value="1476">Andhra Pradesh</option>
<option value="1477">Arunachal Pradesh</option>
<option value="1478">Assam</option>
<option value="1479">Bihar</option>
<option value="1480">Chandigarh</option>
<option value="1481">Dadra and Nagar Haveli</option>
</select>
</form>
</body></html>
`;
add_autofill_heuristic_tests([
{
fixtureData: MARKUP_SELECT_STATE,
profile: TEST_PROFILE,
expectedResult: [
{
default: {
reason: "autocomplete",
},
fields: [
{ fieldName: "given-name", autofill: TEST_PROFILE["given-name"] },
{ fieldName: "family-name", autofill: TEST_PROFILE["family-name"] },
{
fieldName: "street-address",
autofill: TEST_PROFILE["street-address"],
},
{
fieldName: "address-level2",
autofill: TEST_PROFILE["address-level2"],
},
{ fieldName: "address-level1", autofill: 1479 },
],
},
],
},
]);

View file

@ -0,0 +1,242 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_setup(async () => {
await SpecialPowers.pushPrefEnv({
set: [
["extensions.formautofill.addresses.supported", "on"],
["extensions.formautofill.creditCards.supported", "on"],
["extensions.formautofill.heuristics.detectDynamicFormChanges", true],
],
});
});
const verifyCurrentIdentifiedFields = async (browser, expectedFields) => {
const actor =
browser.browsingContext.currentWindowGlobal.getActor("FormAutofill");
let actualFields = Array.from(actor.sectionsByRootId.values()).flat();
verifySectionFieldDetails(actualFields, expectedFields);
};
const expectedAddressFieldsExcludingAdditionalFields = [
{
fields: [
{ fieldName: "name" },
{ fieldName: "email" },
{ fieldName: "tel" },
{ fieldName: "country" },
],
},
];
const expectedAddressFieldsIncludingAdditionalFields = [
{
fields: [
{ fieldName: "name" },
{ fieldName: "email" },
{ fieldName: "tel" },
{ fieldName: "country" },
{ fieldName: "street-address" },
{ fieldName: "address-level1" },
{ fieldName: "address-level2" },
{ fieldName: "postal-code" },
],
},
];
const expectedCreditCardFieldsExcludingAdditionalFields = [
{
fields: [{ fieldName: "cc-number" }],
},
];
const expectedCreditCardFieldsIncludingAdditionalFields = [
{
fields: [
{ fieldName: "cc-number" },
{ fieldName: "cc-name" },
{ fieldName: "cc-exp-month" },
{ fieldName: "cc-exp-year" },
],
},
];
/**
* Tests that the identified fields are updated correctly during dynamic form changes
*
* The form changes can be of two types (see FormAutofillHeuristics.FORM_CHANGE_REASON):
* 1. An element changes its visibility state,
* e.g. visible element becomes invisible or vice versa
* 2. The form/document adds or removes nodes
*
* Both form changes should make FormAutofillChild consider triggering another identification process.
* This method tests that we identifiy the correct fields in three different scenarios:
* 1. Focusing on a field which triggers a first identification process
* 2. Filling a field that dispatches a change event which makes the site
* change the form, which triggers a second field identification process
* 3. Clearing the value from the field, which reverts the previous form change
* and triggers a third field identification process
*
* @param {string} documentPath
* @param {string} elementIdToFill
* @param {object[]} identifiedFieldsExcludingExtraFields
* @param {object[]} identifiedFieldsIncludingExtraFields
*/
const verifyIdentifiedFieldsDuringFormChange = async (
documentPath,
elementIdToFill,
identifiedFieldsExcludingExtraFields,
identifiedFieldsIncludingExtraFields
) => {
await BrowserTestUtils.withNewTab(documentPath, async browser => {
const fieldDetectionCompletedBeforeFormChangePromise =
getFieldDetectionCompletedPromiseResolver();
info("Focusing on a form field to trigger the identification process");
await SpecialPowers.spawn(browser, [elementIdToFill], elementId => {
const field = content.document.getElementById(elementId);
// Focus event invokes the first field identification process
field.focus();
});
info("Waiting for initial fieldDetectionCompleted notification");
await fieldDetectionCompletedBeforeFormChangePromise;
info("Checking that additional fields are not identified yet");
await verifyCurrentIdentifiedFields(
browser,
identifiedFieldsExcludingExtraFields
);
const fieldDetectionCompletedIncludingAdditionalFieldsPromise =
getFieldDetectionCompletedPromiseResolver();
info("Simulating user input so that additional field nodes are added.");
await SpecialPowers.spawn(browser, [elementIdToFill], elementId => {
let field = content.document.getElementById(elementId);
field.setUserInput("dummyValue");
});
// A "form-changed" event was dispatched, triggering another field identification process
info("Waiting for another fieldDetectionCompleted notification");
await fieldDetectionCompletedIncludingAdditionalFieldsPromise;
info("Checking that additional address fields are also identied.");
await verifyCurrentIdentifiedFields(
browser,
identifiedFieldsIncludingExtraFields
);
const fieldDetectionCompletedExcludingAdditionalFieldsPromise =
getFieldDetectionCompletedPromiseResolver();
info("Clearing user input, so that additional fields are removed again.");
await SpecialPowers.spawn(browser, [elementIdToFill], elementId => {
let field = content.document.getElementById(elementId);
field.focus();
field.setUserInput("");
});
// A "form-changed" event was dispatched, triggering another field identification process
info("Waiting for another fieldIdentified notification");
await fieldDetectionCompletedExcludingAdditionalFieldsPromise;
info("Checking that additional fields are removed from identified fields");
await verifyCurrentIdentifiedFields(
browser,
identifiedFieldsExcludingExtraFields
);
});
};
/**
* Tests that the correct address fields are identified in a form after "form-changed"
* events are dispatched with reasons "nodes-added" and "nodes-removed"
*/
add_task(
async function correct_address_fields_identified_in_form_during_form_changes_due_to_node_mutations() {
await verifyIdentifiedFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"country-node-addition",
expectedAddressFieldsExcludingAdditionalFields,
expectedAddressFieldsIncludingAdditionalFields
);
}
);
/**
* Tests that the correct credit card fields are identified in a form after "form-changed"
* events are dispatched with reasons "nodes-added" and "nodes-removed"
*/
add_task(
async function correct_credit_card_fields_identified_in_form_during_form_changes_due_to_node_mutations() {
await verifyIdentifiedFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"cc-number-node-addition",
expectedCreditCardFieldsExcludingAdditionalFields,
expectedCreditCardFieldsIncludingAdditionalFields
);
}
);
/**
* Tests that the correct address fields are identified in a form after "form-changed"
* events are dispatched with reasons "visible-element-became-invisible" and "invisible-element-became-visible"
*/
add_task(
async function correct_address_fields_identified_in_form_during_form_changes_due_to_element_visibility_change() {
await verifyIdentifiedFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"country-visibility-change",
expectedAddressFieldsExcludingAdditionalFields,
expectedAddressFieldsIncludingAdditionalFields
);
}
);
/**
* Tests that the correct credit card fields are identified in a form after "form-changed"
* events are dispatched with reasons "visible-element-became-invisible" and "invisible-element-became-visible"
*/
add_task(
async function correct_credit_card_fields_identified_in_form_during_form_changes_due_to_element_visibility_change() {
await verifyIdentifiedFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"cc-number-visibility-change",
expectedCreditCardFieldsExcludingAdditionalFields,
expectedCreditCardFieldsIncludingAdditionalFields
);
}
);
/**
* Tests that the correct fields are identified in a document (form-less) after "form-changed"
* events are dispatched with reasons "nodes-added" and "nodes-removed"
*/
add_task(
async function correct_fields_identified_in_formless_document_during_form_changes_due_to_node_mutations() {
await verifyIdentifiedFieldsDuringFormChange(
FORMLESS_FIELDS_WITH_DYNAMIC_FORM_CHANGE_AFTER_NODE_MUTATIONS,
"country-node-addition",
expectedAddressFieldsExcludingAdditionalFields,
expectedAddressFieldsIncludingAdditionalFields
);
}
);
/**
* Tests that the correct fields are identified in a document (form-less) after "form-changed"
* events are dispatched with reasons "visible-element-became-invisible" and "invisible-element-became-visible"
*/
add_task(
async function correct_fields_identified_in_formless_document_during_form_changes_due_to_element_visibility_change() {
await verifyIdentifiedFieldsDuringFormChange(
FORMLESS_FIELDS_WITH_DYNAMIC_FORM_CHANGE_AFTER_VISIBILITY_STATE_CHANGE,
"country-visibility-change",
expectedAddressFieldsExcludingAdditionalFields,
expectedAddressFieldsIncludingAdditionalFields
);
}
);

View file

@ -0,0 +1,271 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { FormAutofill } = ChromeUtils.importESModule(
"resource://autofill/FormAutofill.sys.mjs"
);
add_setup(async () => {
await SpecialPowers.pushPrefEnv({
set: [
["extensions.formautofill.addresses.supported", "on"],
["extensions.formautofill.creditCards.supported", "on"],
["extensions.formautofill.heuristics.detectDynamicFormChanges", true],
["extensions.formautofill.heuristics.fillOnDynamicFormChanges", true],
[
"extensions.formautofill.heuristics.fillOnDynamicFormChanges.timeout",
1000,
],
],
});
const oldValue = FormAutofillUtils.getOSAuthEnabled(
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF
);
FormAutofillUtils.setOSAuthEnabled(
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
false
);
await setStorage(TEST_ADDRESS_1);
await setStorage(TEST_CREDIT_CARD_1);
registerCleanupFunction(async () => {
await removeAllRecords();
FormAutofillUtils.setOSAuthEnabled(
FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
oldValue
);
});
});
const expectedFilledCreditCardFields = {
fields: [
{ fieldName: "cc-number", autofill: TEST_CREDIT_CARD_1["cc-number"] },
{ fieldName: "cc-name", autofill: TEST_CREDIT_CARD_1["cc-name"] },
{ fieldName: "cc-exp-month", autofill: TEST_CREDIT_CARD_1["cc-exp-month"] },
{ fieldName: "cc-exp-year", autofill: TEST_CREDIT_CARD_1["cc-exp-year"] },
],
};
const expectedFilledAddressFields = {
fields: [
{ fieldName: "name", autofill: "John R. Smith" },
{ fieldName: "email", autofill: TEST_ADDRESS_1.email },
{ fieldName: "tel", autofill: TEST_ADDRESS_1.tel },
{ fieldName: "country", autofill: TEST_ADDRESS_1.country },
{
fieldName: "street-address",
autofill: TEST_ADDRESS_1["street-address"].replace("\n", " "),
},
{ fieldName: "address-level1", autofill: TEST_ADDRESS_1["address-level1"] },
{ fieldName: "address-level2", autofill: TEST_ADDRESS_1["address-level2"] },
{ fieldName: "postal-code", autofill: TEST_ADDRESS_1["postal-code"] },
],
};
/**
* Verify that fields that are added/become visible immediately after
* an initial autocompletion get filled as well
*
* @param {string} documentPath
* @param {string} selectorToTriggerAutocompletion
*/
const verifyAutofilledFieldsDuringFormChange = async (
documentPath,
selectorToTriggerAutocompletion,
elementValueToVerifyAutofill,
expectedSection
) => {
await BrowserTestUtils.withNewTab(documentPath, async browser => {
info("Triggering autocompletion.");
await openPopupOn(browser, selectorToTriggerAutocompletion);
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
const filledOnFormChangePromise = TestUtils.topicObserved(
"formautofill-fill-after-form-change-complete"
);
await waitForAutofill(
browser,
selectorToTriggerAutocompletion,
elementValueToVerifyAutofill
);
info(
`Waiting for "formautofill-fill-after-form-change-complete" notification`
);
await filledOnFormChangePromise;
info("Verifying that all fields are filled correctly.");
const actor =
browser.browsingContext.currentWindowGlobal.getActor("FormAutofill");
const section = Array.from(actor.sectionsByRootId.values()).flat()[0];
await verifyAutofillResult(browser, section, expectedSection);
});
};
/**
* Tests that all address fields are filled.
* Form adds autofillable input fields after country field is modified
*/
add_task(
async function address_fields_filled_in_form_during_form_changes_due_to_node_mutations() {
await verifyAutofilledFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"#country-node-addition",
TEST_ADDRESS_1.country,
expectedFilledAddressFields
);
}
);
/**
* Tests that all credit card fields are filled.
* Form adds autofillable input fields after cc number field is modified.
*/
add_task(
async function credit_card_fields_filled_in_form_during_form_changes_due_to_node_mutations() {
await verifyAutofilledFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"#cc-number-node-addition",
TEST_CREDIT_CARD_1["cc-number"],
expectedFilledCreditCardFields
);
}
);
/**
* Tests that all address fields are filled.
* Form makes invisible autofillable input fields become visible after country field is modified
*/
add_task(
async function address_fields_filled_in_form_during_form_changes_due_to_element_visibility_change() {
await verifyAutofilledFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"#country-visibility-change",
TEST_ADDRESS_1.country,
expectedFilledAddressFields
);
}
);
/**
* Tests that all credit card fields are filled.
* Form makes invisible autofillable input fields become visible after cc number field is modified.
*/
add_task(
async function credit_card_fields_filled_in_form_during_form_changes_due_to_element_visibility_change() {
await verifyAutofilledFieldsDuringFormChange(
FORMS_WITH_DYNAMIC_FORM_CHANGE,
"#cc-number-visibility-change",
TEST_CREDIT_CARD_1["cc-number"],
expectedFilledCreditCardFields
);
}
);
/**
* Tests that all fields are filled.
* Formless document adds autofillable input fields after country field is modified.
*/
add_task(
async function address_fields_filled_in_formless_document_during_form_changes_due_to_node_mutations() {
await verifyAutofilledFieldsDuringFormChange(
FORMLESS_FIELDS_WITH_DYNAMIC_FORM_CHANGE_AFTER_NODE_MUTATIONS,
"#country-node-addition",
TEST_ADDRESS_1.country,
expectedFilledAddressFields
);
}
);
/**
* Tests that all fields are filled.
* Formless document makes invisible autofillable input fields become visible after country field is modified.
*/
add_task(
async function address_fields_filled_in_formless_document_during_form_changes_due_to_element_visibility_change() {
await verifyAutofilledFieldsDuringFormChange(
FORMLESS_FIELDS_WITH_DYNAMIC_FORM_CHANGE_AFTER_VISIBILITY_STATE_CHANGE,
"#country-visibility-change",
TEST_ADDRESS_1.country,
expectedFilledAddressFields
);
}
);
/**
* Tests that additional fields are not filled when the form change was initiated
* by a user interaction that triggered a "click" event on the form.
*/
add_task(
async function additional_fields_not_filled_on_user_initiated_form_change() {
await BrowserTestUtils.withNewTab(
FORM_WITH_USER_INITIATED_FORM_CHANGE,
async browser => {
const selectorToTriggerAutocompletion = "#country-visibility-change";
const elementValueToVerifyAutofill = TEST_ADDRESS_1.country;
info("Triggering autocompletion.");
await openPopupOn(browser, selectorToTriggerAutocompletion);
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
await waitForAutofill(
browser,
selectorToTriggerAutocompletion,
elementValueToVerifyAutofill
);
info(
"Simulating user interaction to cancel any filling on dynamic form change actions"
);
const showFieldButtonSelector = "#show-fields-btn";
await SpecialPowers.spawn(
browser,
[showFieldButtonSelector],
async btnSelector => {
const showFieldsButton =
content.document.querySelector(btnSelector);
showFieldsButton.click();
}
);
info(
"Waiting for any possible filling on dynamic form change to complete"
);
/* eslint-disable mozilla/no-arbitrary-setTimeout */
await new Promise(resolve => {
setTimeout(resolve, FormAutofill.fillOnDynamicFormChangeTimeout);
});
info(
"Verifying that all fields are detected, but additional fields are not filled"
);
const expectedAdditionalFieldsNotFilled = {
fields: [
{ fieldName: "name", autofill: "John R. Smith" },
{ fieldName: "email", autofill: TEST_ADDRESS_1.email },
{ fieldName: "tel", autofill: TEST_ADDRESS_1.tel },
{ fieldName: "country", autofill: TEST_ADDRESS_1.country },
{
fieldName: "street-address",
},
{ fieldName: "address-level1" },
{ fieldName: "address-level2" },
{ fieldName: "postal-code" },
],
};
const actor =
browser.browsingContext.currentWindowGlobal.getActor("FormAutofill");
const section = Array.from(actor.sectionsByRootId.values()).flat()[0];
await verifyAutofillResult(
browser,
section,
expectedAdditionalFieldsNotFilled
);
}
);
}
);

View file

@ -135,8 +135,13 @@ async function checkFormChangeHappened(formId) {
// Click the first entry of the autocomplete popup and make sure all fields are autofilled
await checkFieldsAutofilled(browser, formId, MOCK_STORAGE[0]);
// This is for checking the changes of element count.
const fieldsDetectedAfterFieldAdded =
getFieldDetectionCompletedPromiseResolver();
addInputField(browser, formId, "address-level2");
await fieldsDetectedAfterFieldAdded;
await openPopupOn(browser, `#${formId} input[name=name]`);
// Click on an autofilled field would show an autocomplete popup with "clear form" entry
@ -144,14 +149,20 @@ async function checkFormChangeHappened(formId) {
browser,
[
"Clear Autofill Form", // Clear Autofill Form
"Manage addresses", // FormAutofill Preferemce
"Manage addresses", // FormAutofill Preference
],
0
);
const fieldDetectedAfterFieldMutations =
getFieldDetectionCompletedPromiseResolver();
// This is for checking the changes of element removed and added then.
removeInputField(browser, `#${formId} input[name=address-level2]`);
addInputField(browser, formId, "address-level2");
await fieldDetectedAfterFieldMutations;
await openPopupOn(browser, `#${formId} input[name=address-level2]`);
await checkMenuEntries(
@ -178,6 +189,11 @@ async function checkFormChangeHappened(formId) {
add_setup(async function () {
await setStorage(MOCK_STORAGE[0]);
await setStorage(MOCK_STORAGE[1]);
await SpecialPowers.pushPrefEnv({
set: [
["extensions.formautofill.heuristics.detectDynamicFormChanges", true],
],
});
});
add_task(async function check_change_happened_in_form() {

View file

@ -84,6 +84,20 @@ const ADDRESS_FORM_WITH_PAGE_NAVIGATION_BUTTONS =
"address/capture_address_on_page_navigation.html";
const FORM_IFRAME_SANDBOXED_URL =
"https://example.org" + HTTP_TEST_PATH + "autocomplete_iframe_sandboxed.html";
const FORMS_WITH_DYNAMIC_FORM_CHANGE =
"https://example.org" + HTTP_TEST_PATH + "dynamic_form_changes.html";
const FORM_WITH_USER_INITIATED_FORM_CHANGE =
"https://example.org" +
HTTP_TEST_PATH +
"form_change_on_user_interaction.html";
const FORMLESS_FIELDS_WITH_DYNAMIC_FORM_CHANGE_AFTER_NODE_MUTATIONS =
"https://example.org" +
HTTP_TEST_PATH +
"dynamic_formless_changes_node_mutations.html";
const FORMLESS_FIELDS_WITH_DYNAMIC_FORM_CHANGE_AFTER_VISIBILITY_STATE_CHANGE =
"https://example.org" +
HTTP_TEST_PATH +
"dynamic_formless_changes_element_visiblity_state.html";
const CREDITCARD_FORM_URL =
"https://example.org" +
HTTP_TEST_PATH +
@ -377,6 +391,28 @@ async function waitForStorageChangedEvents(...eventTypes) {
);
}
/**
* Sets up a promise that resolves when the FormAutofillParent sends out a notification
* that the field detection processes have completed in all FormAutofill children.
*
* @returns {Promise}
*/
async function getFieldDetectionCompletedPromiseResolver() {
let fieldDetectionCompletedPromiseResolver;
const fieldDetectionCompletedObserver = {
fieldDetectionCompleted() {
info(`All fields detected.`);
fieldDetectionCompletedPromiseResolver();
FormAutofillParent.removeMessageObserver(fieldDetectionCompletedObserver);
},
};
return new Promise(resolve => {
fieldDetectionCompletedPromiseResolver = resolve;
FormAutofillParent.addMessageObserver(fieldDetectionCompletedObserver);
});
}
/**
* Wait until the element found matches the expected autofill value
*

View file

@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Address form adding and removing nodes dynamically on user input -->
<form id="address-form-node-addition">
<label for="name-node-addition">Name</label>
<input type="text" id="name-node-addition" autocomplete="name">
<label for="email-node-addition">Email</label>
<input type="email" id="email-node-addition" autocomplete="email">
<label for="phone-node-addition">Phone</label>
<input type="tel" id="phone-node-addition" autocomplete="tel">
<label for="country-node-addition">Country</label>
<input type="text" id="country-node-addition" autocomplete="country">
<div id="address-extra-fields-node-addition"></div>
</form>
<!-- Credit card form adding and removing nodes dynamically on user input -->
<form id="cc-form-node-addition">
<label for="cc-number-node-addition">Credit Card Number</label>
<input id="cc-number-node-addition" autocomplete="cc-number">
<div id="cc-extra-fields-node-addition"></div>
</form>
<!-- Address form toggling the visibility state dynamically for some elements on user input -->
<form id="address-form-visibility-change">
<label for="name-visibility-change">Name</label>
<input type="text" id="name-visibility-change" autocomplete="name">
<label for="email-visibility-change">Email</label>
<input type="email" id="email-visibility-change" autocomplete="email">
<label for="organization-visibility-change">Phone</label>
<input type="tel" id="organization-visibility-change" autocomplete="tel" />
<label for="country-visibility-change">Country</label>
<input type="text" id="country-visibility-change" autocomplete="country">
<label for="street-address-visibility-change">Street Address</label>
<input type="text" id="street-address-visibility-change" autocomplete="street-address" hidden/>
<label for="address-level1-visibility-change">Address Level 1</label>
<input type="text" id="address-level1-visibility-change" autocomplete="address-level1" hidden/>
<label for="address-level2-visibility-change">Address Level 2</label>
<input type="text" id="address-level2-visibility-change" autocomplete="address-level2" hidden/>
<label for="postal-code-visibility-change">Postal Code</label>
<input type="text" id="postal-code-visibility-change" autocomplete="postal-code" hidden/>
</form>
<!-- Credit card form toggling the visibility state dynamically for some elements on user input -->
<form id="cc-form-visibility-change">
<label for="cc-number-visibility-change">Credit Card Number</label>
<input id="cc-number-visibility-change" autocomplete="cc-number">
<label for="cc-name-visibility-change">Cardholder Name</label>
<input id="cc-name-visibility-change" autocomplete="cc-name" hidden>
<label for="cc-exp-month-visibility-change">Expiration Month</label>
<input id="cc-exp-month-visibility-change" autocomplete="cc-exp-month" hidden>
<label for="cc-exp-year-visibility-change">Expiration Year</label>
<input id="cc-exp-year-visibility-change" autocomplete="cc-exp-year" hidden>
</form>
</body>
<script>
const addField = (parent, id, labelText, autocomplete) => {
const label = document.createElement('label');
label.htmlFor = id;
label.textContent = labelText;
const input = document.createElement('input');
input.id = id;
input.type = "text";
input.autocomplete = autocomplete;
parent.appendChild(label);
parent.appendChild(input);
return input;
}
const ccNumberInputNodeAddition = document.getElementById("cc-number-node-addition");
ccNumberInputNodeAddition.addEventListener('input', (event) => {
let ccFields = document.getElementById("cc-extra-fields-node-addition");
if (event.target.value != "") {
addField(ccFields, "cc-name-node-addition", "Cardholder Name", "cc-name");
addField(ccFields, "cc-exp-month-node-addition", "Expiration Month", "cc-exp-month");
addField(ccFields, "cc-exp-year-node-addition", "Expiration Year", "cc-exp-year");
} else {
while (ccFields.firstChild) {
ccFields.firstChild.remove()
}
}
});
const countryInputNodeAddition = document.getElementById("country-node-addition");
countryInputNodeAddition.addEventListener('input', (event) => {
let addressFields = document.getElementById("address-extra-fields-node-addition");
if (event.target.value != "") {
addField(addressFields, "street-address-node-addition", "Street Address", "street-address");
addField(addressFields, "address-level1-node-addition", "Address Level 1", "address-level1");
addField(addressFields, "address-level2-node-addition", "Address Level 2", "address-level2");
addField(addressFields, "postal-code-node-addition", "Postal Code", "postal-code");
} else {
while (addressFields.firstChild) {
addressFields.firstChild.remove()
}
}
});
const countryInputVisibilityChange = document.getElementById("country-visibility-change");
countryInputVisibilityChange.addEventListener('input', (event) => {
if (event.target.value != "") {
document.getElementById("street-address-visibility-change").hidden = false;
document.getElementById("address-level1-visibility-change").hidden = false;
document.getElementById("address-level2-visibility-change").hidden = false;
document.getElementById("postal-code-visibility-change").hidden = false;
} else {
document.getElementById("street-address-visibility-change").hidden = true;
document.getElementById("address-level1-visibility-change").hidden = true;
document.getElementById("address-level2-visibility-change").hidden = true;
document.getElementById("postal-code-visibility-change").hidden = true;
}
});
const ccNumberInputVisibilityChange = document.getElementById("cc-number-visibility-change");
ccNumberInputVisibilityChange.addEventListener('input', (event) => {
if (event.target.value != "") {
document.getElementById("cc-name-visibility-change").hidden = false;
document.getElementById("cc-exp-month-visibility-change").hidden = false;
document.getElementById("cc-exp-year-visibility-change").hidden = false;
} else {
document.getElementById("cc-name-visibility-change").hidden = true;
document.getElementById("cc-exp-month-visibility-change").hidden = true;
document.getElementById("cc-exp-year-visibility-change").hidden = true;
}
});
</script>
</html>

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Address fields (form-less) toggling the visibility state dynamically for some elements on user input -->
<div>
<label for="name-visibility-change">Name</label>
<input type="text" id="name-visibility-change" autocomplete="name">
<label for="email-visibility-change">Email</label>
<input type="email" id="email-visibility-change" autocomplete="email">
<label for="organization-visibility-change">Phone</label>
<input type="tel" id="organization-visibility-change" autocomplete="tel" />
<label for="country-visibility-change">Country</label>
<input type="text" id="country-visibility-change" autocomplete="country">
<label for="street-address-visibility-change">Street Address</label>
<input type="text" id="street-address-visibility-change" autocomplete="street-address" hidden/>
<label for="address-level1-visibility-change">Address Level 1</label>
<input type="text" id="address-level1-visibility-change" autocomplete="address-level1" hidden/>
<label for="address-level2-visibility-change">Address Level 2</label>
<input type="text" id="address-level2-visibility-change" autocomplete="address-level2" hidden/>
<label for="postal-code-visibility-change">Postal Code</label>
<input type="text" id="postal-code-visibility-change" autocomplete="postal-code" hidden/>
</div>
</body>
<script>
const countryInputVisibilityChange = document.getElementById("country-visibility-change");
countryInputVisibilityChange.addEventListener('input', (event) => {
if (event.target.value != "") {
document.getElementById("street-address-visibility-change").hidden = false;
document.getElementById("address-level1-visibility-change").hidden = false;
document.getElementById("address-level2-visibility-change").hidden = false;
document.getElementById("postal-code-visibility-change").hidden = false;
} else {
document.getElementById("street-address-visibility-change").hidden = true;
document.getElementById("address-level1-visibility-change").hidden = true;
document.getElementById("address-level2-visibility-change").hidden = true;
document.getElementById("postal-code-visibility-change").hidden = true;
}
});
</script>
</html>

View file

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Address fields (form-less) adding and removing nodes dynamically on user input -->
<div>
<label for="name-node-addition">Name</label>
<input type="text" id="name-node-addition" autocomplete="name">
<label for="email-node-addition">Email</label>
<input type="email" id="email-node-addition" autocomplete="email">
<label for="phone-node-addition">Phone</label>
<input type="tel" id="phone-node-addition" autocomplete="tel">
<label for="country-node-addition">Country</label>
<input type="text" id="country-node-addition" autocomplete="country">
<div id="address-extra-fields-node-addition"></div>
</div>
</body>
<script>
const addField = (parent, id, labelText, autocomplete) => {
const label = document.createElement('label');
label.htmlFor = id;
label.textContent = labelText;
const input = document.createElement('input');
input.id = id;
input.type = "text";
input.autocomplete = autocomplete;
parent.appendChild(label);
parent.appendChild(input);
return input;
}
const countryInputNodeAddition = document.getElementById("country-node-addition");
countryInputNodeAddition.addEventListener('input', (event) => {
let addressFields = document.getElementById("address-extra-fields-node-addition");
if (event.target.value != "") {
addField(addressFields, "street-address-node-addition", "Street Address", "street-address");
addField(addressFields, "address-level1-node-addition", "Address Level 1", "address-level1");
addField(addressFields, "address-level2-node-addition", "Address Level 2", "address-level2");
addField(addressFields, "postal-code-node-addition", "Postal Code", "postal-code");
} else {
while (addressFields.firstChild) {
addressFields.firstChild.remove()
}
}
});
</script>
</html>

View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Form Autofill Address Demo Page</title>
</head>
<body>
<h1>Form Autofill Address Demo Page</h1>
<form id="address-form-visibility-change">
<label for="name-visibility-change">Name</label>
<input type="text" id="name-visibility-change" autocomplete="name">
<label for="email-visibility-change">Email</label>
<input type="email" id="email-visibility-change" autocomplete="email">
<label for="organization-visibility-change">Phone</label>
<input type="tel" id="organization-visibility-change" autocomplete="tel" />
<label for="country-visibility-change">Country</label>
<input type="text" id="country-visibility-change" autocomplete="country">
<label for="street-address-visibility-change">Street Address</label>
<input type="text" id="street-address-visibility-change" autocomplete="street-address" style="display:none"/>
<label for="address-level1-visibility-change">Address Level 1</label>
<input type="text" id="address-level1-visibility-change" autocomplete="address-level1" style="display:none"/>
<label for="address-level2-visibility-change">Address Level 2</label>
<input type="text" id="address-level2-visibility-change" autocomplete="address-level2" style="display:none"/>
<label for="postal-code-visibility-change">Postal Code</label>
<input type="text" id="postal-code-visibility-change" autocomplete="postal-code" style="display:none"/>
<button id="show-fields-btn" type="button">Show fields</button>
</form>
<script>
const showFieldButton = document.getElementById("show-fields-btn")
showFieldButton.addEventListener("click", () => {
document.getElementById("street-address-visibility-change").style.display = "";
document.getElementById("address-level1-visibility-change").style.display = "";
document.getElementById("address-level2-visibility-change").style.display = "";
document.getElementById("postal-code-visibility-change").style.display = "";
})
</script>
</body>
</html>

View file

@ -21,7 +21,7 @@
<div>
<div>
<div>
<label>Card Number</label>
<!--label>Card Number</label-->
<input type="text" name="GiftCode1"
title="overall type: EMAIL_ADDRESS
server type: EMAIL_ADDRESS

View file

@ -12,8 +12,7 @@ const TESTCASES = [
<input id="typeA" type="text">
</label>
</form>`,
inputId: "typeA",
expectedLabelIds: ["labelA"],
expectedLabelIds: [["labelA"]],
},
{
description: "Input contains in a label element.",
@ -23,14 +22,14 @@ const TESTCASES = [
</div>
</label>`,
inputId: "typeB",
expectedLabelIds: ["labelB"],
expectedLabelIds: [["labelB"]],
},
{
description: '"for" attribute used to indicate input by one label.',
document: `<label id="labelC" for="typeC">label type C</label>
<input id="typeC" type="text">`,
inputId: "typeC",
expectedLabelIds: ["labelC"],
expectedLabelIds: [["labelC"]],
},
{
description: '"for" attribute used to indicate input by multiple labels.',
@ -41,7 +40,7 @@ const TESTCASES = [
<input id="typeD" type="text">
</form>`,
inputId: "typeD",
expectedLabelIds: ["labelD1", "labelD2", "labelD3"],
expectedLabelIds: [["labelD1", "labelD2", "labelD3"]],
},
{
description:
@ -52,7 +51,7 @@ const TESTCASES = [
<label id="labelE4" for=" typeE ">label type E4</label>
<input id=" typeE " type="text">`,
inputId: " typeE ",
expectedLabelIds: [],
expectedLabelIds: [[]],
},
{
description: "Input contains in a label element.",
@ -63,7 +62,7 @@ const TESTCASES = [
</div>
</label>`,
inputId: "typeF",
expectedLabelIds: ["labelF"],
expectedLabelIds: [["labelF"], [""]],
},
{
description:
@ -75,7 +74,77 @@ const TESTCASES = [
</form>
<label id="labelG3" for="typeG">label type G3</label>`,
inputId: "typeG",
expectedLabelIds: ["labelG1", "labelG2", "labelG3"],
expectedLabelIds: [["labelG1", "labelG2", "labelG3"]],
},
{
description:
"labels with no for attribute or child with one input at a different level",
document: `<form>
<label id="labelH1">label H1</label>
<input>
<label id="labelH2">label H2</label>
<div><span><input></span></div>
</form>`,
inputId: "labelH1",
expectedLabelIds: [["labelH1"], ["labelH2"]],
},
{
description:
"labels with no for attribute or child with an input and button",
document: `<form>
<label id="labelI1">label I1</label>
<input>
<label id="labelI2">label I2</label>
<button>
<input>
</form>`,
inputId: "labelI1",
expectedLabelIds: [["labelI1"], []],
},
{
description: "three labels with no for attribute or child.",
document: `<form>
<button>
<label id="labelJ1">label J1</label>
<label id="labelJ2">label J2</label>
<input>
<label id="labelJ3">label J3</label>
<meter>
<input>
</form>`,
inputId: "labelJ1",
expectedLabelIds: [["labelJ2"], []],
},
{
description: "four labels with no for attribute or child.",
document: `<form>
<input>
<fieldset>
<label id="labelK1">label K1</label>
<label id="labelK2">label K2</label>
<input>
<label id="labelK3">label K3</label>
<div><b><input></b></div>
<label id="labelK4">label K4</label>
</fieldset>
<input>
</form>`,
inputId: "labelK1",
expectedLabelIds: [[], ["labelK2"], ["labelK3"], []],
},
{
description:
"labels with no for attribute or child and inputs at different level.",
document: `<form>
<input>
<div><span><input></span></div>
<label id="labelL1">label L1</label>
<label id="labelL2">label L2</label>
<div><span><input></span></div>
</input>
</form>`,
inputId: "labelK1",
expectedLabelIds: [[], [], ["labelL2"], []],
},
];
@ -88,13 +157,16 @@ TESTCASES.forEach(testcase => {
testcase.document
);
let input = doc.getElementById(testcase.inputId);
let labels = LabelUtils.findLabelElements(input);
let formElements = doc.querySelectorAll("input", "select");
let labelsIndex = 0;
for (let formElement of formElements) {
let labels = LabelUtils.findLabelElements(formElement);
Assert.deepEqual(
labels.map(l => l.id),
testcase.expectedLabelIds[labelsIndex++]
);
}
Assert.deepEqual(
labels.map(l => l.id),
testcase.expectedLabelIds
);
LabelUtils.clearLabelMap();
});
});

View file

@ -8,6 +8,7 @@ head = "head.js"
support-files = ["../fixtures/**"]
prefs = [
"extensions.formautofill.test.ignoreVisibilityCheck=true",
"extensions.formautofill.heuristics.detectDynamicFormChanges=false",
]
["test_activeStatus.js"]

View file

@ -393,9 +393,6 @@ export class BaseContent extends React.PureComponent {
// if selectedWallpaper exists - we override what light and dark prefs are to match that
lightWallpaper = wallpaper;
darkWallpaper = wallpaper;
} else {
lightWallpaper = wallpaperList.find(wp => wp.theme === "light") || "";
darkWallpaper = wallpaperList.find(wp => wp.theme === "dark") || "";
}
// solid-color-picker-#00d100

View file

@ -13423,9 +13423,6 @@ class BaseContent extends (external_React_default()).PureComponent {
// if selectedWallpaper exists - we override what light and dark prefs are to match that
lightWallpaper = wallpaper;
darkWallpaper = wallpaper;
} else {
lightWallpaper = wallpaperList.find(wp => wp.theme === "light") || "";
darkWallpaper = wallpaperList.find(wp => wp.theme === "dark") || "";
}
// solid-color-picker-#00d100

View file

@ -51,11 +51,18 @@ To build assets and run Firefox, run the following from the root of the mozilla-
Continuous development / debugging
----------------------------------
Running ``./mach npm run watchmc --prefix=browser/extensions/newtab`` will start a process that watches files in
``activity-stream`` and rebuilds the bundled files when JS or CSS files change.
**IMPORTANT NOTE**: This task will add inline source maps to help with debugging, which changes the memory footprint.
Do not use the ``watchmc`` task for profiling or performance testing!
For near real-time reloading, run the following commands in separate terminals to automatically rebuild bundled files whenever JSX or SCSS files change. After making a change, `restart your local instance </devtools-user/browser_console/index.html#controlling-the-browser>`_ to apply the updates.
.. code-block:: shell
./mach npm run watchmc --prefix=browser/extensions/newtab
./mach run
./mach watch
**IMPORTANT NOTE**: This task will add inline source maps to help with debugging, which changes the memory footprint. Do not use the ``watchmc`` task for profiling or performance testing! After finalizing your changes, be sure to run `the bundle command <./index.html#building-assets-and-running-firefox>`_ again before committing to remove the inline source maps.
Running tests
-------------

View file

@ -22,8 +22,6 @@ The official documentation for the HTTP cache [can be found here](https://firefo
### `AboutHomeStartupCache`
This singleton component lives inside of `BrowserGlue` to avoid having to load yet another JSM out of the `omni.ja` file in the parent process during startup.
`AboutHomeStartupCache` is responsible for feeding the "privileged about content process" with the `nsIInputStream`'s that it needs to present the initial `about:home` document. It is also responsible for populating the cache with updated versions of `about:home` that are sent by the "privileged about content process".
Since accessing the HTTP cache is asynchronous, there is an opportunity for a race, where the cache can either be accessed and available before the initial `about:home` is requested, or after. To accommodate for both cases, the `AboutHomeStartupCache` constructs `nsIPipe` instances, which it sends down to the "privileged about content process" as soon as one launches.
@ -48,7 +46,7 @@ When the `AboutRedirector` in the "privileged about content process" notices tha
If, at this point, nothing has been streamed from the parent, we fall back to loading the dynamic `about:home` document. This might occur if the cache doesn't exist yet, or if we were too slow to pull it off of the disk. Subsequent attempts to load `about:home` will bypass the cache and load the dynamic document instead. This is true even if the privileged about content process crashes and a new one is created.
The `AboutHomeStartupCacheChild` will also be responsible for generating the cache periodically. Periodically, the `AboutNewTabService` will send down the most up-to-date state for `about:home` from the parent process, and then the `AboutHomeStartupCacheChild` will generate document markup using ReactDOMServer within a `ChromeWorker`. After that's generated, the "privileged about content process" will send up `nsIInputStream` instances for both the markup and the script for the initial page state. The `AboutHomeStartupCache` singleton inside of `BrowserGlue` is responsible for receiving those `nsIInputStream`'s and persisting them in the HTTP cache for the next start.
The `AboutHomeStartupCacheChild` will also be responsible for generating the cache periodically. Periodically, the `AboutNewTabService` will send down the most up-to-date state for `about:home` from the parent process, and then the `AboutHomeStartupCacheChild` will generate document markup using ReactDOMServer within a `ChromeWorker`. After that's generated, the "privileged about content process" will send up `nsIInputStream` instances for both the markup and the script for the initial page state. The `AboutHomeStartupCache` singleton is responsible for receiving those `nsIInputStream`'s and persisting them in the HTTP cache for the next start.
## What is cached?

View file

@ -5,7 +5,7 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AboutHomeStartupCache: "resource:///modules/BrowserGlue.sys.mjs",
AboutHomeStartupCache: "resource:///modules/AboutHomeStartupCache.sys.mjs",
AboutNewTabParent: "resource:///actors/AboutNewTabParent.sys.mjs",
});

View file

@ -4,7 +4,7 @@
"use strict";
let { AboutHomeStartupCache } = ChromeUtils.importESModule(
"resource:///modules/BrowserGlue.sys.mjs"
"resource:///modules/AboutHomeStartupCache.sys.mjs"
);
const { sinon } = ChromeUtils.importESModule(
"resource://testing-common/Sinon.sys.mjs"

View file

@ -203,6 +203,7 @@
; Modules
@RESPATH@/browser/modules/*
@RESPATH@/modules/*
@RESPATH@/moz-src/*
@RESPATH@/browser/actors/*
@RESPATH@/actors/*

View file

@ -17,7 +17,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"af": {
"pin": false,
@ -37,7 +37,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"an": {
"pin": false,
@ -57,7 +57,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ar": {
"pin": false,
@ -77,7 +77,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ast": {
"pin": false,
@ -97,7 +97,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"az": {
"pin": false,
@ -117,7 +117,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"be": {
"pin": false,
@ -137,7 +137,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"bg": {
"pin": false,
@ -157,7 +157,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"bn": {
"pin": false,
@ -177,7 +177,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"bo": {
"pin": false,
@ -197,7 +197,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"br": {
"pin": false,
@ -217,7 +217,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"brx": {
"pin": false,
@ -237,7 +237,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"bs": {
"pin": false,
@ -257,7 +257,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ca": {
"pin": false,
@ -277,7 +277,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ca-valencia": {
"pin": false,
@ -297,7 +297,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"cak": {
"pin": false,
@ -317,7 +317,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ckb": {
"pin": false,
@ -337,7 +337,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"cs": {
"pin": false,
@ -357,7 +357,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"cy": {
"pin": false,
@ -377,7 +377,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"da": {
"pin": false,
@ -397,7 +397,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"de": {
"pin": false,
@ -417,7 +417,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"dsb": {
"pin": false,
@ -437,7 +437,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"el": {
"pin": false,
@ -457,7 +457,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"en-CA": {
"pin": false,
@ -477,7 +477,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"en-GB": {
"pin": false,
@ -497,7 +497,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"eo": {
"pin": false,
@ -517,7 +517,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"es-AR": {
"pin": false,
@ -537,7 +537,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"es-CL": {
"pin": false,
@ -557,7 +557,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"es-ES": {
"pin": false,
@ -577,7 +577,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"es-MX": {
"pin": false,
@ -597,7 +597,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"et": {
"pin": false,
@ -617,7 +617,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"eu": {
"pin": false,
@ -637,7 +637,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"fa": {
"pin": false,
@ -657,7 +657,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ff": {
"pin": false,
@ -677,7 +677,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"fi": {
"pin": false,
@ -697,7 +697,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"fr": {
"pin": false,
@ -717,7 +717,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"fur": {
"pin": false,
@ -737,7 +737,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"fy-NL": {
"pin": false,
@ -757,7 +757,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ga-IE": {
"pin": false,
@ -777,7 +777,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"gd": {
"pin": false,
@ -797,7 +797,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"gl": {
"pin": false,
@ -817,7 +817,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"gn": {
"pin": false,
@ -837,7 +837,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"gu-IN": {
"pin": false,
@ -857,7 +857,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"he": {
"pin": false,
@ -877,7 +877,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"hi-IN": {
"pin": false,
@ -897,7 +897,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"hr": {
"pin": false,
@ -917,7 +917,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"hsb": {
"pin": false,
@ -937,7 +937,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"hu": {
"pin": false,
@ -957,7 +957,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"hy-AM": {
"pin": false,
@ -977,7 +977,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"hye": {
"pin": false,
@ -997,7 +997,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ia": {
"pin": false,
@ -1017,7 +1017,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"id": {
"pin": false,
@ -1037,7 +1037,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"is": {
"pin": false,
@ -1057,7 +1057,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"it": {
"pin": false,
@ -1077,7 +1077,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ja": {
"pin": false,
@ -1095,7 +1095,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ja-JP-mac": {
"pin": false,
@ -1103,7 +1103,7 @@
"macosx64",
"macosx64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ka": {
"pin": false,
@ -1123,7 +1123,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"kab": {
"pin": false,
@ -1143,7 +1143,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"kk": {
"pin": false,
@ -1163,7 +1163,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"km": {
"pin": false,
@ -1183,7 +1183,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"kn": {
"pin": false,
@ -1203,7 +1203,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ko": {
"pin": false,
@ -1223,7 +1223,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"lij": {
"pin": false,
@ -1243,7 +1243,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"lo": {
"pin": false,
@ -1263,7 +1263,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"lt": {
"pin": false,
@ -1283,7 +1283,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ltg": {
"pin": false,
@ -1303,7 +1303,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"lv": {
"pin": false,
@ -1323,7 +1323,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"meh": {
"pin": false,
@ -1343,7 +1343,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"mk": {
"pin": false,
@ -1363,7 +1363,27 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ml": {
"pin": false,
"platforms": [
"linux",
"linux-devedition",
"linux64",
"linux64-aarch64",
"linux64-aarch64-devedition",
"linux64-devedition",
"macosx64",
"macosx64-devedition",
"win32",
"win32-devedition",
"win64",
"win64-aarch64",
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"mr": {
"pin": false,
@ -1383,7 +1403,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ms": {
"pin": false,
@ -1403,7 +1423,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"my": {
"pin": false,
@ -1423,7 +1443,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"nb-NO": {
"pin": false,
@ -1443,7 +1463,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ne-NP": {
"pin": false,
@ -1463,7 +1483,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"nl": {
"pin": false,
@ -1483,7 +1503,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"nn-NO": {
"pin": false,
@ -1503,7 +1523,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"oc": {
"pin": false,
@ -1523,7 +1543,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"pa-IN": {
"pin": false,
@ -1543,7 +1563,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"pl": {
"pin": false,
@ -1563,7 +1583,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"pt-BR": {
"pin": false,
@ -1583,7 +1603,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"pt-PT": {
"pin": false,
@ -1603,7 +1623,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"rm": {
"pin": false,
@ -1623,7 +1643,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ro": {
"pin": false,
@ -1643,7 +1663,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ru": {
"pin": false,
@ -1663,7 +1683,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sat": {
"pin": false,
@ -1683,7 +1703,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sc": {
"pin": false,
@ -1703,7 +1723,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"scn": {
"pin": false,
@ -1723,7 +1743,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sco": {
"pin": false,
@ -1743,7 +1763,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"si": {
"pin": false,
@ -1763,7 +1783,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sk": {
"pin": false,
@ -1783,7 +1803,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"skr": {
"pin": false,
@ -1803,7 +1823,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sl": {
"pin": false,
@ -1823,7 +1843,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"son": {
"pin": false,
@ -1843,7 +1863,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sq": {
"pin": false,
@ -1863,7 +1883,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sr": {
"pin": false,
@ -1883,7 +1903,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"sv-SE": {
"pin": false,
@ -1903,7 +1923,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"szl": {
"pin": false,
@ -1923,7 +1943,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ta": {
"pin": false,
@ -1943,7 +1963,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"te": {
"pin": false,
@ -1963,7 +1983,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"tg": {
"pin": false,
@ -1983,7 +2003,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"th": {
"pin": false,
@ -2003,7 +2023,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"tl": {
"pin": false,
@ -2023,7 +2043,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"tr": {
"pin": false,
@ -2043,7 +2063,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"trs": {
"pin": false,
@ -2063,7 +2083,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"uk": {
"pin": false,
@ -2083,7 +2103,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"ur": {
"pin": false,
@ -2103,7 +2123,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"uz": {
"pin": false,
@ -2123,7 +2143,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"vi": {
"pin": false,
@ -2143,7 +2163,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"wo": {
"pin": false,
@ -2163,7 +2183,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"xh": {
"pin": false,
@ -2183,7 +2203,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"zh-CN": {
"pin": false,
@ -2203,7 +2223,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
},
"zh-TW": {
"pin": false,
@ -2223,6 +2243,6 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f7b405ff6f8ece48fdaa286852ecf0aa2ba9bec0"
"revision": "574853459270ae7dfeae8e70b2648c7cbf9d48eb"
}
}

View file

@ -2,7 +2,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
const { TabUnloader } = ChromeUtils.importESModule(
"resource:///modules/TabUnloader.sys.mjs"
"moz-src:///browser/components/tabbrowser/TabUnloader.sys.mjs"
);
const BASE_URL = "https://example.com/browser/browser/modules/test/browser/";

View file

@ -4,7 +4,7 @@
"use strict";
const { TabUnloader } = ChromeUtils.importESModule(
"resource:///modules/TabUnloader.sys.mjs"
"moz-src:///browser/components/tabbrowser/TabUnloader.sys.mjs"
);
let TestTabUnloaderMethods = {

View file

@ -824,19 +824,22 @@
}
}
/* Persisted Search Terms rules specific when the Unified Search Button is present */
/* stylelint-disable-next-line media-query-no-invalid */
/* Persisted Search revert button */
.urlbar-revert-button {
list-style-image: url(chrome://global/skin/icons/defaultFavicon.svg);
fill: var(--toolbarbutton-icon-fill-attention);
&:focus-visible {
outline: var(--focus-outline);
}
}
#urlbar[persistsearchterms] {
#urlbar-revert-button-container {
display: inherit;
}
.urlbar-revert-button {
list-style-image: url(chrome://global/skin/icons/defaultFavicon.svg);
fill: var(--color-accent-primary);
}
.urlbar-revert-button:focus-visible {
outline: var(--focus-outline);
}
.urlbar-go-button {
display: none;
}
@ -1212,22 +1215,22 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
}
}
.searchmode-switcher-addEngine::before {
content: "";
position: relative;
display: flex;
background: url(chrome://browser/skin/search-indicator-badge-add.svg) no-repeat center;
height: 11px;
width: 11px;
margin-inline: 10px -21px;
margin-top: -13px;
}
#searchmode-switcher-popup {
--panel-padding: 4px 0;
> menuitem {
margin-inline: 4px;
&.searchmode-switcher-addEngine::before {
content: "";
position: relative;
display: flex;
background: url(chrome://browser/skin/search-indicator-badge-add.svg) no-repeat center;
height: 11px;
width: 11px;
margin-inline: 10px -21px;
margin-top: -13px;
}
}
}

View file

@ -15,34 +15,36 @@ buildscript {
}
ext {
detekt_plugin = Versions.detekt
python_envs_plugin = Versions.python_envs_plugin
ksp_plugin = Versions.ksp_plugin
detekt_plugin = libs.versions.detekt.get()
python_envs_plugin = libs.versions.python.envs.plugin.get()
ksp_plugin = libs.versions.ksp.plugin.get()
// Used in mobile/android/fenix/app/build.gradle
protobuf_plugin = Versions.protobuf_plugin
protobuf_plugin = libs.versions.protobuf.plugin.get()
}
dependencies {
classpath 'org.mozilla.apilint:apilint:0.5.3'
classpath ComponentsDependencies.tools_androidgradle
classpath libs.tools.androidgradle
classpath 'org.apache.commons:commons-exec:1.3'
classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.25.0'
classpath 'org.tomlj:tomlj:1.1.0'
classpath ComponentsDependencies.tools_kotlingradle
classpath libs.tools.kotlingradle
// Used in mobile/android/fenix/app/build.gradle
classpath ComponentsDependencies.androidx_navigation_safeargs
classpath ComponentsDependencies.osslicenses_plugin
classpath ComponentsDependencies.tools_benchmarkgradle
classpath "org.mozilla.telemetry:glean-gradle-plugin:${Versions.mozilla_glean}"
classpath libs.androidx.navigation.safeargs
classpath libs.osslicenses.plugin
classpath libs.tools.benchmarkgradle
classpath libs.glean.gradle.plugin
classpath "${ApplicationServicesConfig.groupId}:tooling-nimbus-gradle:${ApplicationServicesConfig.version}"
}
}
plugins {
id("io.gitlab.arturbosch.detekt").version("$detekt_plugin")
id("com.google.devtools.ksp").version("$ksp_plugin")
id 'ApkSizePlugin'
id "mozac.ConfigPlugin"
alias(libs.plugins.detekt)
alias(libs.plugins.ksp)
}
def tryInt = { string ->
@ -60,7 +62,7 @@ abstract class VerifyGleanVersionTask extends DefaultTask {
final RegularFileProperty source = project.objects.fileProperty().convention(project.layout.projectDirectory.file("Cargo.lock"))
@Input
String expectedVersion = Versions.mozilla_glean
String expectedVersion = project.ext.gleanVersion
@OutputFiles
FileCollection outputFiles = project.objects.fileCollection()
@ -103,7 +105,7 @@ allprojects {
topsrcdir = gradle.mozconfig.topsrcdir
topobjdir = gradle.mozconfig.topobjdir
gleanVersion = Versions.mozilla_glean // Verification done in verifyGleanVersion task
gleanVersion = libs.versions.mozilla.glean.get() // Verification done in verifyGleanVersion task
artifactSuffix = getArtifactSuffix()
versionName = getVersionName()
@ -226,37 +228,19 @@ class TaggedLogOutputStream extends org.apache.commons.exec.LogOutputStream {
}
}
ext.geckoBinariesOnlyIf = { task ->
// Never when Gradle was invoked within `mach build`.
if ('1' == System.env.GRADLE_INVOKED_WITHIN_MACH_BUILD) {
rootProject.logger.lifecycle("Skipping task ${task.path} because: within `mach build`")
return false
import org.gradle.api.services.BuildServiceParameters
abstract class MozconfigService implements BuildService<MozconfigService.Params>, AutoCloseable {
interface Params extends BuildServiceParameters {
MapProperty<String, Object> getMozconfigParam()
}
// Never for official builds.
if (mozconfig.substs.MOZILLA_OFFICIAL) {
rootProject.logger.lifecycle("Skipping task ${task.path} because: MOZILLA_OFFICIAL")
return false
void close() {
}
// Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale, and
// `MOZ_CHROME_MULTILOCALE`. To avoid failures, if Gradle is invoked with
// either, we don't invoke Make at all; this allows a multi-locale omnijar
// to be consumed without modification.
if ('multi' == System.env.AB_CD || System.env.MOZ_CHROME_MULTILOCALE) {
rootProject.logger.lifecycle("Skipping task ${task.path} because: AB_CD=multi")
return false
Object getMozconfig() {
return parameters.mozconfigParam.get()
}
// Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and handle resource
// and code generation themselves.
if ('1' == System.env.IS_LANGUAGE_REPACK) {
rootProject.logger.lifecycle("Skipping task ${task.path} because: IS_LANGUAGE_REPACK")
return false
}
rootProject.logger.lifecycle("Executing task ${task.path}")
return true
}
// Non-official versions are like "61.0a1" or "61.0b1", where "a1" and "b1"
@ -340,10 +324,47 @@ class MachExec extends Exec {
environment project.ext.mozconfig.orig_mozconfig.env.unmodified
environment 'MOZCONFIG', project.ext.mozconfig.substs.MOZCONFIG
}
static def geckoBinariesOnlyIf(task, mozconfig) {
// Never when Gradle was invoked within `mach build`.
if ('1' == System.env.GRADLE_INVOKED_WITHIN_MACH_BUILD) {
task.logger.lifecycle("Skipping task ${task.path} because: within `mach build`")
return false
}
// Never for official builds.
if (mozconfig.substs.MOZILLA_OFFICIAL) {
task.logger.lifecycle("Skipping task ${task.path} because: MOZILLA_OFFICIAL")
return false
}
// Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale, and
// `MOZ_CHROME_MULTILOCALE`. To avoid failures, if Gradle is invoked with
// either, we don't invoke Make at all; this allows a multi-locale omnijar
// to be consumed without modification.
if ('multi' == System.env.AB_CD || System.env.MOZ_CHROME_MULTILOCALE) {
task.logger.lifecycle("Skipping task ${task.path} because: AB_CD=multi")
return false
}
// Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and handle resource
// and code generation themselves.
if ('1' == System.env.IS_LANGUAGE_REPACK) {
task.logger.lifecycle("Skipping task ${task.path} because: IS_LANGUAGE_REPACK")
return false
}
task.logger.lifecycle("Executing task ${task.path}")
return true
}
}
task machBuildFaster(type: MachExec) {
onlyIf rootProject.ext.geckoBinariesOnlyIf
def mozconfigProvider = gradle.sharedServices.registerIfAbsent("mozconfig", MozconfigService) {
parameters.mozconfigParam.set(project.ext.mozconfig)
}
usesService(mozconfigProvider)
onlyIf { task -> MachExec.geckoBinariesOnlyIf(task, mozconfigProvider.get().getMozconfig()) }
workingDir "${topsrcdir}"
@ -363,8 +384,11 @@ task machBuildFaster(type: MachExec) {
}
task machStagePackage(type: MachExec) {
onlyIf rootProject.ext.geckoBinariesOnlyIf
def mozconfigProvider = gradle.sharedServices.registerIfAbsent("mozconfig", MozconfigService) {
parameters.mozconfigParam.set(project.ext.mozconfig)
}
usesService(mozconfigProvider)
onlyIf { task -> MachExec.geckoBinariesOnlyIf(task, mozconfigProvider.get().getMozconfig()) }
dependsOn rootProject.machBuildFaster
workingDir "${topobjdir}"
@ -426,13 +450,13 @@ afterEvaluate {
if (details.requested.group == 'org.mozilla.telemetry'
&& details.requested.name.contains('glean') ) {
def requested = details.requested.version.tokenize(".")
def defined = Versions.mozilla_glean.tokenize(".")
def defined = libs.versions.mozilla.glean.get().tokenize(".")
// Check the major version
if (requested[0] != defined[0]) {
throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${Versions.mozilla_glean}")
throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${libs.mozilla.glean}")
} else {
// Enforce that all (transitive) dependencies are using the defined Glean version
details.useVersion Versions.mozilla_glean
details.useVersion libs.versions.mozilla.glean.get()
}
}
}

View file

@ -12,7 +12,7 @@ option(env="CBINDGEN", nargs=1, help="Path to cbindgen")
def check_cbindgen_version(cbindgen, fatal=False):
log.debug("trying cbindgen: %s" % cbindgen)
cbindgen_min_version = Version("0.26.0")
cbindgen_min_version = Version("0.28.0")
# cbindgen x.y.z
version = Version(check_cmd_output(cbindgen, "--version").strip().split(" ")[1])

View file

@ -62,6 +62,7 @@ path:testing/web-platform/mozilla/tests/
glob:testing/web-platform/*.py
# For scheduling android-gradle-dependencies.
path:gradle/wrapper/gradle-wrapper.properties
path:mobile/android/config/
glob:**/*.gradle

View file

@ -79,7 +79,14 @@ const OOP_FRAME_DOCUMENT_SNAPSHOT = {
"text-align": "start",
"text-indent": "0px",
},
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
children: [
{
childCount: 1,
@ -176,7 +183,14 @@ const EXPECTED_SNAPSHOT = {
"text-align": "start",
"text-indent": "0px",
},
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
children: [OOP_FRAME_SNAPSHOT],
};

View file

@ -52,6 +52,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@ -153,7 +154,14 @@ const tests = [
keyboardShortcut: "",
childCount: 2,
indexInParent: 0,
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
},
},
},

View file

@ -39,6 +39,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@ -54,7 +55,13 @@ const tests = [
),
expected: {
sidebar: {
states: ["unavailable", "readonly", "focusable", "opaque"],
states: [
"unavailable",
"readonly",
"focusable",
"selectable text",
"opaque",
],
},
},
},

View file

@ -48,6 +48,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@ -146,7 +147,14 @@ const tests = [
keyboardShortcut: "",
childCount: 2,
indexInParent: 0,
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
},
},
},

View file

@ -40,6 +40,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@ -132,7 +133,14 @@ const tests = [
keyboardShortcut: "",
childCount: 2,
indexInParent: 0,
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
},
},
},

View file

@ -63,6 +63,10 @@ export function getClassSymbols(location) {
return async ({ parserWorker, dispatch }) => {
// See comment in getFunctionSymbols
await dispatch(loadSourceText(location.source, location.sourceActor));
return parserWorker.getClassSymbols(location.source.id);
const editor = getEditor();
return features.codemirrorNext && editor
? editor.getClassSymbols()
: parserWorker.getClassSymbols(location.source.id);
};
}

View file

@ -121,7 +121,7 @@ define(function (require, exports) {
// Render tree component.
return TreeView({
object: this.props.data,
mode: MODE.TINY,
mode: MODE.LONG,
onFilter: this.onFilter,
columns,
renderValue: this.renderValue,

View file

@ -3,23 +3,86 @@
"use strict";
const { ELLIPSIS } = require("resource://devtools/shared/l10n.js");
add_task(async function () {
info("Test Object type property started");
const TEST_JSON_URL = 'data:application/json,{"x":{"type":"string"}}';
const TEST_JSON_URL = `data:application/json,${JSON.stringify({
x: {
type: "string",
},
y: {
items: [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
],
length: 5,
a: 1,
b: 2,
c: 3,
d: 4,
e: 5,
},
})}`;
await addJsonViewTab(TEST_JSON_URL);
let count = await getElementCount(".jsonPanelBox .treeTable .treeRow");
is(count, 2, "There must be two rows");
is(await getRowsCount(), 24, "The tree has the expected number of rows");
// Collapse auto-expanded node.
is(await getRowText(0), `x: `, "The node starts expanded");
info(`Collapse auto-expanded "x" node.`);
await clickJsonNode(".jsonPanelBox .treeTable .treeLabel");
count = await getElementCount(".jsonPanelBox .treeTable .treeRow");
is(count, 1, "There must be one row");
is(
await getRowsCount(),
23,
`The tree has one less row after collapsing "x"`
);
const label = await getElementText(".jsonPanelBox .treeTable .objectCell");
is(label, `{${ELLIPSIS}}`, "The label must be indicating an object");
is(
await getRowText(0),
`x: { type: "string" }`,
"The label must be indicating an object"
);
is(await getRowText(1), `y: `, "The y row is expanded at first");
is(await getRowText(2), `items: `, "The items row is expanded");
info("Collapse y.items");
await clickJsonNode(".jsonPanelBox .treeTable tr:nth-of-type(3) .treeLabel");
is(
await getRowsCount(),
9,
`The tree has less rows after collapsing "y.items"`
);
is(
await getRowText(2),
`items: (14)[ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", … ]`,
"The items row is rendered as expected after being collapsed"
);
info("Collapse y");
await clickJsonNode(".jsonPanelBox .treeTable tr:nth-of-type(2) .treeLabel");
is(await getRowsCount(), 2, `The tree has only 2 rows after collapsing "y"`);
is(
await getRowText(1),
`y: { length: 5, a: 1, b: 2, … }`,
"The y row is rendered as expected after being collapsed"
);
});
function getRowsCount() {
return getElementCount(".jsonPanelBox .treeTable .treeRow");
}

View file

@ -215,6 +215,21 @@ function getElementAttr(selector, attr) {
);
}
/**
* Return the text of a row given its index, e.g. `key: "value"`
* @param {Number} rowIndex
* @returns {Promise<String>}
*/
async function getRowText(rowIndex) {
const key = await getElementText(
`.jsonPanelBox .treeTable .treeRow:nth-of-type(${rowIndex + 1}) .treeLabelCell`
);
const value = await getElementText(
`.jsonPanelBox .treeTable .treeRow:nth-of-type(${rowIndex + 1}) .treeValueCell`
);
return `${key}: ${value}`;
}
function focusElement(selector) {
info("Focus element: '" + selector + "'");

View file

@ -428,6 +428,8 @@ skip-if = [
["browser_net_req-resp-bodies.js"]
["browser_net_requests_with_empty_response.js"]
["browser_net_resend.js"]
["browser_net_resend_cors.js"]

View file

@ -107,7 +107,7 @@ add_task(async function () {
);
is(
values[0].textContent,
'Object { greeting: "Hello long string JSON!" }',
'{ greeting: "Hello long string JSON!" }',
"The first json property value was incorrect."
);

Some files were not shown because too many files have changed in this diff Show more