Update On Tue Mar 4 19:52:53 CET 2025
This commit is contained in:
parent
6d40ed86c5
commit
eaefe60938
1745 changed files with 51573 additions and 30355 deletions
2
CLOBBER
2
CLOBBER
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -144,7 +144,6 @@
|
|||
|
||||
testAccessibleTree("c7",{ SECTION: [
|
||||
{ role: ROLE_PUSHBUTTON, name: "Hello" },
|
||||
{ TEXT_LEAF: [] }
|
||||
] });
|
||||
|
||||
let events = waitForOrderedEvents(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -159,6 +159,7 @@
|
|||
"SubDialogManager",
|
||||
"TabCrashHandler",
|
||||
"TabsSetupFlowManager",
|
||||
"TaskbarTabUI",
|
||||
"TelemetryEnvironment",
|
||||
"TranslationsParent",
|
||||
"UITour",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -65,6 +65,7 @@ DIRS += [
|
|||
"syncedtabs",
|
||||
"tabbrowser",
|
||||
"tabunloader",
|
||||
"taskbartabs",
|
||||
"textrecognition",
|
||||
"topsites",
|
||||
"translations",
|
||||
|
|
903
browser/components/newtab/AboutHomeStartupCache.sys.mjs
Normal file
903
browser/components/newtab/AboutHomeStartupCache.sys.mjs
Normal 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);
|
||||
},
|
||||
};
|
|
@ -14,6 +14,7 @@ XPIDL_SOURCES += [
|
|||
XPIDL_MODULE = "browser-newtab"
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"AboutHomeStartupCache.sys.mjs",
|
||||
"AboutNewTabService.sys.mjs",
|
||||
]
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
1750
browser/components/search/SERPCategorization.sys.mjs
Normal file
1750
browser/components/search/SERPCategorization.sys.mjs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -10,6 +10,7 @@ EXTRA_JS_MODULES += [
|
|||
"SearchOneOffs.sys.mjs",
|
||||
"SearchSERPTelemetry.sys.mjs",
|
||||
"SearchUIUtils.sys.mjs",
|
||||
"SERPCategorization.sys.mjs",
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
|
|
|
@ -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."
|
||||
);
|
||||
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
z-index: 3;
|
||||
|
||||
&.header-wrapper-overflow {
|
||||
align-items: baseline;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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: >
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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() {
|
||||
|
|
29
browser/components/taskbartabs/TaskbarTabUI.sys.mjs
Normal file
29
browser/components/taskbartabs/TaskbarTabUI.sys.mjs
Normal 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);
|
||||
},
|
||||
};
|
13
browser/components/taskbartabs/moz.build
Normal file
13
browser/components/taskbartabs/moz.build
Normal 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",
|
||||
]
|
4
browser/components/taskbartabs/test/browser/browser.toml
Normal file
4
browser/components/taskbartabs/test/browser/browser.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
[DEFAULT]
|
||||
|
||||
["browser_taskbarTabs_chromeTest.js"]
|
||||
run-if = ["os == 'win'"]
|
|
@ -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);
|
||||
});
|
|
@ -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 },
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
|
@ -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
|
||||
);
|
||||
}
|
||||
);
|
|
@ -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
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
150
browser/extensions/formautofill/test/fixtures/dynamic_form_changes.html
vendored
Normal file
150
browser/extensions/formautofill/test/fixtures/dynamic_form_changes.html
vendored
Normal 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>
|
|
@ -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>
|
60
browser/extensions/formautofill/test/fixtures/dynamic_formless_changes_node_mutations.html
vendored
Normal file
60
browser/extensions/formautofill/test/fixtures/dynamic_formless_changes_node_mutations.html
vendored
Normal 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>
|
46
browser/extensions/formautofill/test/fixtures/form_change_on_user_interaction.html
vendored
Normal file
46
browser/extensions/formautofill/test/fixtures/form_change_on_user_interaction.html
vendored
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ head = "head.js"
|
|||
support-files = ["../fixtures/**"]
|
||||
prefs = [
|
||||
"extensions.formautofill.test.ignoreVisibilityCheck=true",
|
||||
"extensions.formautofill.heuristics.detectDynamicFormChanges=false",
|
||||
]
|
||||
|
||||
["test_activeStatus.js"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
-------------
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
; Modules
|
||||
@RESPATH@/browser/modules/*
|
||||
@RESPATH@/modules/*
|
||||
@RESPATH@/moz-src/*
|
||||
@RESPATH@/browser/actors/*
|
||||
@RESPATH@/actors/*
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/";
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
116
build.gradle
116
build.gradle
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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],
|
||||
};
|
||||
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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 + "'");
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue