Update On Thu Mar 27 19:54:58 CET 2025
This commit is contained in:
parent
0c665687bb
commit
6ed3f92ece
980 changed files with 33080 additions and 10620 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -7217,7 +7217,7 @@ version = "0.3.100"
|
|||
|
||||
[[package]]
|
||||
name = "webdriver"
|
||||
version = "0.52.0"
|
||||
version = "0.52.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
subsuite = "a11y"
|
||||
support-files = [
|
||||
"head.js",
|
||||
"doc_language.html",
|
||||
"doc_treeupdate_ariadialog.html",
|
||||
"doc_treeupdate_ariaowns.html",
|
||||
"doc_treeupdate_imagemap.html",
|
||||
|
|
|
@ -122,14 +122,6 @@ addAccessibleTask(
|
|||
-->
|
||||
<area id="area" href="#" shape="rect" coords="0,0,2,2" alt="area">
|
||||
</map>
|
||||
|
||||
<script>
|
||||
// Attach dummy event handlers here, because inline event handler attributes
|
||||
// will be blocked in the chrome context.
|
||||
for (const el of document.querySelectorAll('[data-event]')) {
|
||||
el["on" + el.dataset.event] = () => {};
|
||||
}
|
||||
</script>
|
||||
`,
|
||||
async function (browser, docAcc) {
|
||||
is(docAcc.actionCount, 0, "Doc should not have any actions");
|
||||
|
@ -255,6 +247,13 @@ addAccessibleTask(
|
|||
topLevel: true,
|
||||
iframe: true,
|
||||
remoteIframe: true,
|
||||
contentSetup: async function contentSetup() {
|
||||
// Attach dummy event handlers here, because inline event handler attributes
|
||||
// will be blocked in the chrome context.
|
||||
for (const el of content.document.querySelectorAll("[data-event]")) {
|
||||
el["on" + el.dataset.event] = () => {};
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -600,19 +600,6 @@ addAccessibleTask(
|
|||
<div id="popover2" popover>popover2</div>
|
||||
<button id="toggle5">toggle5</button>
|
||||
</template></div>
|
||||
<script>
|
||||
const toggle1 = document.getElementById("toggle1");
|
||||
const popover1 = document.getElementById("popover1");
|
||||
toggle1.popoverTargetElement = popover1;
|
||||
const toggle3 = document.getElementById("toggle3");
|
||||
const shadow = document.getElementById("shadowHost").shadowRoot;
|
||||
const toggle4 = shadow.getElementById("toggle4");
|
||||
const popover2 = shadow.getElementById("popover2");
|
||||
toggle3.popoverTargetElement = popover2;
|
||||
toggle4.popoverTargetElement = popover2;
|
||||
const toggle5 = shadow.getElementById("toggle5");
|
||||
toggle5.popoverTargetElement = popover1;
|
||||
</script>
|
||||
`,
|
||||
async function (browser, docAcc) {
|
||||
const toggle1 = findAccessibleChildByID(docAcc, "toggle1");
|
||||
|
@ -710,7 +697,24 @@ addAccessibleTask(
|
|||
// toggle4 is in the same shadow DOM as popover2.
|
||||
testStates(toggle4, STATE_COLLAPSED);
|
||||
},
|
||||
{ chrome: true, topLevel: true }
|
||||
{
|
||||
chrome: true,
|
||||
topLevel: true,
|
||||
contentSetup: async function contentSetup() {
|
||||
const doc = content.document;
|
||||
const toggle1 = doc.getElementById("toggle1");
|
||||
const popover1 = doc.getElementById("popover1");
|
||||
toggle1.popoverTargetElement = popover1;
|
||||
const toggle3 = doc.getElementById("toggle3");
|
||||
const shadow = doc.getElementById("shadowHost").shadowRoot;
|
||||
const toggle4 = shadow.getElementById("toggle4");
|
||||
const popover2 = shadow.getElementById("popover2");
|
||||
toggle3.popoverTargetElement = popover2;
|
||||
toggle4.popoverTargetElement = popover2;
|
||||
const toggle5 = shadow.getElementById("toggle5");
|
||||
toggle5.popoverTargetElement = popover1;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,21 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
addAccessibleTask(
|
||||
`
|
||||
<script>
|
||||
// We can't include the html element in snippets, so set lang on it here.
|
||||
document.documentElement.lang = "en";
|
||||
</script>
|
||||
<div id="inheritEn">inheritEn</div>
|
||||
<img id="imgInheritEn" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="imgInheritEn">
|
||||
<div id="de" lang="de">
|
||||
<div id="inheritDe">inheritDe <span id="es" role="none" lang="es">esLeaf</span></div>
|
||||
<img id="imgInheritDe" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="imgInheritDe">
|
||||
<div id="fr" lang="fr"></div>
|
||||
<img id="imgFr" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" lang="fr" alt="imgFr">
|
||||
<input id="radioFr" type="radio" lang="fr" aria-label="radioFr">
|
||||
</div>
|
||||
`,
|
||||
"e10s/doc_language.html",
|
||||
async function (browser, docAcc) {
|
||||
is(docAcc.language, "en", "Document language correct");
|
||||
const inheritEn = findAccessibleChildByID(docAcc, "inheritEn");
|
||||
|
|
|
@ -42,11 +42,6 @@ const snippet = `
|
|||
<div id="hidden-subtree-2">D</div>
|
||||
<div id="shadow-host"></div>
|
||||
</div>
|
||||
<script>
|
||||
const host = document.querySelector("#shadow-host");
|
||||
const shadowRoot = host.attachShadow({ mode: "open" });
|
||||
shadowRoot.innerHTML = "<div id='shadowDiv'>E</div>";
|
||||
</script>
|
||||
`;
|
||||
|
||||
async function setContentVisibility(browser, value) {
|
||||
|
@ -79,5 +74,15 @@ addAccessibleTask(
|
|||
await setContentVisibility(browser, "hidden");
|
||||
testAccessibleTree(target, { SECTION: [] });
|
||||
},
|
||||
{ iframe: true, remoteIframe: true, chrome: true }
|
||||
{
|
||||
iframe: true,
|
||||
remoteIframe: true,
|
||||
chrome: true,
|
||||
|
||||
contentSetup: async function contentSetup() {
|
||||
const host = content.document.querySelector("#shadow-host");
|
||||
const shadowRoot = host.attachShadow({ mode: "open" });
|
||||
shadowRoot.innerHTML = "<div id='shadowDiv'>E</div>";
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
18
accessible/tests/browser/e10s/doc_language.html
Normal file
18
accessible/tests/browser/e10s/doc_language.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Language Test</title>
|
||||
</head>
|
||||
<body id="body">
|
||||
<div id="inheritEn">inheritEn</div>
|
||||
<img id="imgInheritEn" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="imgInheritEn">
|
||||
<div id="de" lang="de">
|
||||
<div id="inheritDe">inheritDe <span id="es" role="none" lang="es">esLeaf</span></div>
|
||||
<img id="imgInheritDe" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="imgInheritDe">
|
||||
<div id="fr" lang="fr"></div>
|
||||
<img id="imgFr" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" lang="fr" alt="imgFr">
|
||||
<input id="radioFr" type="radio" lang="fr" aria-label="radioFr">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -160,12 +160,7 @@ addAccessibleTask(
|
|||
// test dynamic translation
|
||||
addAccessibleTask(
|
||||
`<div id="container" style="position: absolute; left: -300px; top: 100px;">Hello</div>
|
||||
<button id="b">Move</button>
|
||||
<script>
|
||||
document.getElementById("b").onclick = () => {
|
||||
container.style.transform = 'translateX(400px)'
|
||||
};
|
||||
</script>`,
|
||||
<button id="b">Move</button>`,
|
||||
async function (browser, accDoc) {
|
||||
const container = findAccessibleChildByID(accDoc, "container");
|
||||
await untilCacheOk(
|
||||
|
@ -183,5 +178,15 @@ addAccessibleTask(
|
|||
"container should be on screen and visible"
|
||||
);
|
||||
},
|
||||
{ chrome: true, iframe: true, remoteIframe: true }
|
||||
{
|
||||
chrome: true,
|
||||
iframe: true,
|
||||
remoteIframe: true,
|
||||
contentSetup: async function contentSetup() {
|
||||
content.document.getElementById("b").onclick = () => {
|
||||
content.document.getElementById("container").style.transform =
|
||||
"translateX(400px)";
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -73,16 +73,7 @@ addAccessibleTask(snippet2, async function (browser, accDoc) {
|
|||
* messes with the body element and we don't want that to impact other tests.
|
||||
*/
|
||||
addAccessibleTask(
|
||||
`
|
||||
<div id="host"></div>
|
||||
<script>
|
||||
const host = document.getElementById("host");
|
||||
host.attachShadow({ mode: "open" });
|
||||
const emptyScript = document.createElement("script");
|
||||
emptyScript.id = "emptyScript";
|
||||
document.head.append(emptyScript);
|
||||
</script>
|
||||
`,
|
||||
`<div id="host"></div>`,
|
||||
async function (browser, docAcc) {
|
||||
info("Moving body and setting slot on body");
|
||||
let reordered = waitForEvent(EVENT_REORDER, docAcc);
|
||||
|
@ -97,7 +88,20 @@ addAccessibleTask(
|
|||
await reordered;
|
||||
is(docAcc.childCount, 0, "document has no children after body move");
|
||||
},
|
||||
{ chrome: true, topLevel: true, iframe: true, remoteIframe: true }
|
||||
{
|
||||
chrome: true,
|
||||
topLevel: true,
|
||||
iframe: true,
|
||||
remoteIframe: true,
|
||||
contentSetup: async function contentSetup() {
|
||||
const doc = content.document;
|
||||
const host = doc.getElementById("host");
|
||||
host.attachShadow({ mode: "open" });
|
||||
const emptyScript = doc.createElement("script");
|
||||
emptyScript.id = "emptyScript";
|
||||
doc.head.append(emptyScript);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
addAccessibleTask(
|
||||
|
|
|
@ -174,7 +174,8 @@ export class DOMFullscreenParent extends JSWindowActorParent {
|
|||
this.waitingForChildExitFullscreen = false;
|
||||
Services.obs.notifyObservers(window, "fullscreen-painted");
|
||||
this.sendAsyncMessage("DOMFullscreen:Painted", {});
|
||||
TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
|
||||
Glean.fullscreen.change.stopAndAccumulate(this.timerId);
|
||||
this.timerId = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +216,7 @@ export class DOMFullscreenParent extends JSWindowActorParent {
|
|||
window.gXPInstallObserver.removeAllNotifications(browser);
|
||||
}
|
||||
|
||||
TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
|
||||
this.timerId = Glean.fullscreen.change.start();
|
||||
window.FullScreen.enterDomFullscreen(browser, this);
|
||||
this.updateFullscreenWindowReference(window);
|
||||
|
||||
|
@ -227,7 +228,7 @@ export class DOMFullscreenParent extends JSWindowActorParent {
|
|||
break;
|
||||
}
|
||||
case "MozDOMFullscreen:Exited": {
|
||||
TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
|
||||
this.timerId = Glean.fullscreen.change.start();
|
||||
|
||||
// Make sure that the actor has not been destroyed before
|
||||
// accessing its browsing context. Otherwise, a error may
|
||||
|
|
30
browser/actors/metrics.yaml
Normal file
30
browser/actors/metrics.yaml
Normal file
|
@ -0,0 +1,30 @@
|
|||
# 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/.
|
||||
|
||||
# Adding a new metric? We have docs for that!
|
||||
# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
|
||||
|
||||
---
|
||||
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
|
||||
$tags:
|
||||
- 'Firefox :: General'
|
||||
|
||||
fullscreen:
|
||||
change:
|
||||
type: timing_distribution
|
||||
description: >
|
||||
The time content uses to enter/exit fullscreen regardless of fullscreen
|
||||
transition timeout
|
||||
|
||||
This metric was generated to correspond to the Legacy Telemetry
|
||||
exponential histogram FULLSCREEN_CHANGE_MS.
|
||||
time_unit: millisecond
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1271160
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1271160
|
||||
notification_emails:
|
||||
- mozilla-telemetry@upsuper.org
|
||||
expires: never
|
||||
telemetry_mirror: FULLSCREEN_CHANGE_MS
|
|
@ -835,9 +835,8 @@ pref("browser.search.widget.removeAfterDaysUnused", 120);
|
|||
// capped at 100.
|
||||
pref("browser.search.totalSearches", 0);
|
||||
|
||||
// Enable new experimental shopping features. This is solely intended as a
|
||||
// rollout/"emergency stop" button - it will go away once the feature has
|
||||
// rolled out. There will be separate controls for user opt-in/opt-out.
|
||||
// Enables the Review Checker feature in the Shopping sidebar.
|
||||
// There are separate controls for user opt-in/opt-out.
|
||||
pref("browser.shopping.experience2023.enabled", false);
|
||||
|
||||
// Ternary int-valued pref indicating if the user has opted into the new
|
||||
|
@ -886,16 +885,11 @@ pref("browser.shopping.experience2023.sidebarClosedCount", 0);
|
|||
// When conditions are met, shows a prompt on the shopping sidebar asking users if they want to disable auto-open behavior
|
||||
pref("browser.shopping.experience2023.showKeepSidebarClosedMessage", true);
|
||||
|
||||
// Integrates the Review Checker shopping feature into the global sidebar
|
||||
// shoppingSidebar pref should be opposite of this to disable
|
||||
// Integrates the Review Checker feature into the global sidebar.
|
||||
// `enabled` pref should be opposite of this to disable
|
||||
// the custom shopping sidebar.
|
||||
pref("browser.shopping.experience2023.integratedSidebar", false);
|
||||
|
||||
// Enables showing the Review Checker in the Shopping sidebar.
|
||||
// integratedSidebar pref should be opposite of this to disable
|
||||
// the Review Checker sidebar panel.
|
||||
pref("browser.shopping.experience2023.shoppingSidebar", true);
|
||||
|
||||
// If true, users have already seen a card in the Review Checker sidebar panel
|
||||
// notifying users of the feature's new location and asking if they want to
|
||||
// move the sidebar to the left or right side. Else if false, users are yet to
|
||||
|
|
|
@ -207,7 +207,8 @@
|
|||
<panelview id="PanelUI-profiles"
|
||||
flex="1"
|
||||
has-custom-header="true">
|
||||
<box id="PanelUI-profiles-header" class="panel-header">
|
||||
<box id="PanelUI-profiles-header"
|
||||
class="panel-header panel-header-with-additional-element">
|
||||
<toolbarbutton class="subviewbutton subviewbutton-iconic subviewbutton-back"
|
||||
id="profiles-appmenu-back-button"
|
||||
tabindex="0"
|
||||
|
@ -217,7 +218,7 @@
|
|||
<html:span id="profiles-header-content"></html:span>
|
||||
</html:h1>
|
||||
<toolbarbutton id="profiles-edit-this-profile-button"
|
||||
class="subviewbutton subviewbutton-iconic toolbarbutton-1"
|
||||
class="subviewbutton subviewbutton-iconic"
|
||||
data-l10n-id="appmenu-edit-profile"
|
||||
/>
|
||||
</box>
|
||||
|
|
|
@ -222,7 +222,7 @@ document.addEventListener(
|
|||
if (!gContextMenu) {
|
||||
throw new Error("Context menu doesn't seem to be open.");
|
||||
}
|
||||
gContextMenu.addSearchFieldAsEngine();
|
||||
gContextMenu.addSearchFieldAsEngine().catch(console.error);
|
||||
break;
|
||||
case "context-searchselect": {
|
||||
let { searchTerms, usePrivate, principal, csp } = event.target;
|
||||
|
|
|
@ -525,7 +525,7 @@ var FullScreen = {
|
|||
// If there is no appropriate actor to send the message we have
|
||||
// no way to complete the transition and should abort by exiting
|
||||
// fullscreen.
|
||||
this._abortEnterFullscreen();
|
||||
this._abortEnterFullscreen(aActor);
|
||||
return;
|
||||
}
|
||||
// Record that the actor is waiting for its child to enter
|
||||
|
@ -552,7 +552,7 @@ var FullScreen = {
|
|||
// full-screen was made. Cancel full-screen.
|
||||
Services.focus.activeWindow != window
|
||||
) {
|
||||
this._abortEnterFullscreen();
|
||||
this._abortEnterFullscreen(aActor);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -670,17 +670,18 @@ var FullScreen = {
|
|||
return needToWaitForChildExit;
|
||||
},
|
||||
|
||||
_abortEnterFullscreen() {
|
||||
_abortEnterFullscreen(aActor) {
|
||||
// This function is called synchronously in fullscreen change, so
|
||||
// we have to avoid calling exitFullscreen synchronously here.
|
||||
//
|
||||
// This could reject if we're not currently in fullscreen
|
||||
// so just ignore rejection.
|
||||
setTimeout(() => document.exitFullscreen().catch(() => {}), 0);
|
||||
if (TelemetryStopwatch.running("FULLSCREEN_CHANGE_MS")) {
|
||||
if (aActor.timerId) {
|
||||
// Cancel the stopwatch for any fullscreen change to avoid
|
||||
// errors if it is started again.
|
||||
TelemetryStopwatch.cancel("FULLSCREEN_CHANGE_MS");
|
||||
Glean.fullscreen.change.cancel(aActor.timerId);
|
||||
aActor.timerId = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1397,6 +1397,11 @@ var gProtectionsHandler = {
|
|||
shimId: "TiktokEmbed",
|
||||
displayName: "Tiktok",
|
||||
},
|
||||
{
|
||||
sites: ["https://platform.twitter.com"],
|
||||
shimId: "TwitterEmbed",
|
||||
displayName: "X",
|
||||
},
|
||||
],
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,12 +8,6 @@
|
|||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
() => {
|
||||
const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
TabGroupMetrics:
|
||||
"moz-src:///browser/components/tabbrowser/TabGroupMetrics.sys.mjs",
|
||||
});
|
||||
|
||||
let mainPopupSet = document.getElementById("mainPopupSet");
|
||||
// eslint-disable-next-line complexity
|
||||
mainPopupSet.addEventListener("command", event => {
|
||||
|
@ -136,11 +130,7 @@ document.addEventListener(
|
|||
let tabGroup = gBrowser.getTabGroupById(tabGroupId);
|
||||
// Tabs need to be removed by their owning `Tabbrowser` or else
|
||||
// there are errors.
|
||||
tabGroup.ownerGlobal.gBrowser.removeTabGroup(tabGroup, {
|
||||
isUserTriggered: true,
|
||||
telemetrySource:
|
||||
lazy.TabGroupMetrics.METRIC_SOURCE.TAB_OVERFLOW_MENU,
|
||||
});
|
||||
tabGroup.ownerGlobal.gBrowser.removeTabGroup(tabGroup);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -2346,18 +2346,34 @@ export class nsContextMenu {
|
|||
});
|
||||
}
|
||||
|
||||
addSearchFieldAsEngine() {
|
||||
this.actor
|
||||
.getSearchFieldEngineData(this.targetIdentifier)
|
||||
.then(async ({ url, formData, charset, method }) => {
|
||||
let icon = this.browser.mIconURL;
|
||||
let uri = Services.io.newURI(url);
|
||||
await this.window.gDialogBox.open(
|
||||
"chrome://browser/content/search/addEngine.xhtml",
|
||||
{ uri, formData, charset, method, icon }
|
||||
);
|
||||
})
|
||||
.catch(console.error);
|
||||
async addSearchFieldAsEngine() {
|
||||
let { url, formData, charset, method } =
|
||||
await this.actor.getSearchFieldEngineData(this.targetIdentifier);
|
||||
|
||||
let { engineInfo } = await this.window.gDialogBox.open(
|
||||
"chrome://browser/content/search/addEngine.xhtml",
|
||||
{
|
||||
mode: "FORM",
|
||||
title: true,
|
||||
nameTemplate: Services.io.newURI(url).host,
|
||||
}
|
||||
);
|
||||
|
||||
// If the user saved, engineInfo contains `name` and `alias`.
|
||||
// Otherwise, it's undefined.
|
||||
if (engineInfo) {
|
||||
let searchEngine = await Services.search.addUserEngine({
|
||||
name: engineInfo.name,
|
||||
alias: engineInfo.alias,
|
||||
url,
|
||||
formData,
|
||||
charset,
|
||||
method,
|
||||
icon: this.browser.mIconURL,
|
||||
});
|
||||
|
||||
this.window.gURLBar.search("", { searchEngine });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2615,10 +2631,7 @@ export class nsContextMenu {
|
|||
getImageText() {
|
||||
let dialogBox = this.window.gBrowser.getTabDialogBox(this.browser);
|
||||
const imageTextResult = this.actor.getImageText(this.targetIdentifier);
|
||||
TelemetryStopwatch.start(
|
||||
"TEXT_RECOGNITION_API_PERFORMANCE",
|
||||
imageTextResult
|
||||
);
|
||||
let timerId = Glean.textRecognition.apiPerformance.start();
|
||||
const { dialog } = dialogBox.open(
|
||||
"chrome://browser/content/textrecognition/textrecognition.html",
|
||||
{
|
||||
|
@ -2627,7 +2640,8 @@ export class nsContextMenu {
|
|||
},
|
||||
imageTextResult,
|
||||
() => dialog.resizeVertically(),
|
||||
this.window.openLinkIn
|
||||
this.window.openLinkIn,
|
||||
timerId
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1332,7 +1332,9 @@ add_task(async function test_dom_full_screen() {
|
|||
await exited;
|
||||
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
return !TelemetryStopwatch.running("FULLSCREEN_CHANGE_MS");
|
||||
return !gBrowser.selectedBrowser.browsingContext.currentWindowGlobal.getActor(
|
||||
"DOMFullscreen"
|
||||
).timerId;
|
||||
});
|
||||
|
||||
if (AppConstants.platform == "macosx") {
|
||||
|
|
|
@ -3,12 +3,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// This test tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
|
||||
add_task(async function test_identityPopupCausesFSExit() {
|
||||
let url = "https://example.com/";
|
||||
|
||||
|
|
|
@ -7,12 +7,6 @@
|
|||
* (bug 1850993), and that we don't show network statuses in DOM fullscreen
|
||||
* (bug 1853896). */
|
||||
|
||||
// DOM FS tests tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
|
||||
let statuspanel = document.getElementById("statuspanel");
|
||||
let statuspanelLabel = document.getElementById("statuspanel-label");
|
||||
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// This test tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
const { PromiseTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/PromiseTestUtils.sys.mjs"
|
||||
);
|
||||
|
|
|
@ -8,12 +8,6 @@ async function pause() {
|
|||
return new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
// This test tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
|
||||
const IFRAME_ID = "testIframe";
|
||||
|
||||
async function testWindowFocus(isPopup, iframeID) {
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// This test tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
SimpleTest.requestLongerTimeout(2);
|
||||
|
||||
const IFRAME_ID = "testIframe";
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// This test tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
|
||||
function listenOneEvent(aEvent, aListener) {
|
||||
function listener(evt) {
|
||||
removeEventListener(aEvent, listener);
|
||||
|
|
|
@ -60,9 +60,6 @@ var gTests = [
|
|||
Assert.equal(accountData.uid, "uid");
|
||||
Assert.equal(accountData.unwrapBKey, "unwrap_b_key");
|
||||
Assert.equal(accountData.verified, true);
|
||||
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
|
||||
let client = new FxAccountsWebChannel({
|
||||
|
@ -72,6 +69,12 @@ var gTests = [
|
|||
login,
|
||||
},
|
||||
});
|
||||
|
||||
client._channel.send = (message, _context) => {
|
||||
Assert.equal(message.data.ok, true);
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
|
@ -144,9 +147,6 @@ var gTests = [
|
|||
let promiseLogout = new Promise(resolve => {
|
||||
let logout = uid => {
|
||||
Assert.equal(uid, "uid");
|
||||
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
|
||||
let client = new FxAccountsWebChannel({
|
||||
|
@ -156,6 +156,12 @@ var gTests = [
|
|||
logout,
|
||||
},
|
||||
});
|
||||
|
||||
client._channel.send = (message, _context) => {
|
||||
Assert.equal(message.data.ok, true);
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
|
@ -175,9 +181,6 @@ var gTests = [
|
|||
let promiseDelete = new Promise(resolve => {
|
||||
let logout = uid => {
|
||||
Assert.equal(uid, "uid");
|
||||
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
|
||||
let client = new FxAccountsWebChannel({
|
||||
|
@ -187,6 +190,12 @@ var gTests = [
|
|||
logout,
|
||||
},
|
||||
});
|
||||
|
||||
client._channel.send = (message, _context) => {
|
||||
Assert.equal(message.data.ok, true);
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
|
@ -216,9 +225,6 @@ var gTests = [
|
|||
"function",
|
||||
"We can reach the openTab method"
|
||||
);
|
||||
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
|
||||
let client = new FxAccountsWebChannel({
|
||||
|
@ -228,6 +234,12 @@ var gTests = [
|
|||
openFirefoxView,
|
||||
},
|
||||
});
|
||||
|
||||
client._channel.send = (message, _context) => {
|
||||
Assert.equal(message.data.ok, true);
|
||||
client.tearDown();
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
|
|
|
@ -881,7 +881,7 @@ let JSWINDOWACTORS = {
|
|||
matches: ["about:shoppingsidebar"],
|
||||
remoteTypes: ["privilegedabout"],
|
||||
messageManagerGroups: ["shopping-sidebar", "browsers"],
|
||||
enablePreference: "browser.shopping.experience2023.shoppingSidebar",
|
||||
enablePreference: "browser.shopping.experience2023.enabled",
|
||||
},
|
||||
|
||||
SpeechDispatcher: {
|
||||
|
|
|
@ -241,6 +241,7 @@ export class AboutLoginsChild extends JSWindowActorChild {
|
|||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "AboutLogins:ImportReportData":
|
||||
|
@ -255,6 +256,21 @@ export class AboutLoginsChild extends JSWindowActorChild {
|
|||
case "AboutLogins:Setup":
|
||||
this.#setup(message.data);
|
||||
break;
|
||||
case "AboutLogins:WaitForFocus": {
|
||||
return new Promise(resolve => {
|
||||
if (!this.document.hasFocus()) {
|
||||
this.document.ownerGlobal.addEventListener(
|
||||
"focus",
|
||||
() => {
|
||||
resolve();
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
default:
|
||||
this.#passMessageDataToContent(message);
|
||||
}
|
||||
|
|
|
@ -418,6 +418,13 @@ export class AboutLoginsParent extends JSWindowActorParent {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!this.browsingContext.canOpenModalPicker) {
|
||||
// Prompting for os auth removed the focus from about:logins.
|
||||
// Waiting for about:logins window to re-gain the focus, because only
|
||||
// active browsing contexts are allowed to open the file picker.
|
||||
await this.sendQuery("AboutLogins:WaitForFocus");
|
||||
}
|
||||
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
function fpCallback(aResult) {
|
||||
if (aResult != Ci.nsIFilePicker.returnCancel) {
|
||||
|
|
|
@ -1101,12 +1101,16 @@ export class AboutWelcomeShoppingChild extends AboutWelcomeChild {
|
|||
productHostname = new URL(productUrl).hostname;
|
||||
}
|
||||
let content = lazy.isIntegratedSidebar
|
||||
? this._AWGetOptInSidebarVariantContent(isProductPage, isSupportedSite)
|
||||
? this._AWGetOptInSidebarVariantContent(
|
||||
productUrl,
|
||||
isProductPage,
|
||||
isSupportedSite
|
||||
)
|
||||
: this._AWGetOptInDefaultContent(productHostname);
|
||||
optInDynamicContent = content;
|
||||
}
|
||||
|
||||
_AWGetOptInSidebarVariantContent(isProductPage, isSupportedSite) {
|
||||
_AWGetOptInSidebarVariantContent(productUrl, isProductPage, isSupportedSite) {
|
||||
let content;
|
||||
|
||||
if (!isProductPage && !isSupportedSite) {
|
||||
|
@ -1117,6 +1121,33 @@ export class AboutWelcomeShoppingChild extends AboutWelcomeChild {
|
|||
content = JSON.parse(JSON.stringify(OPTIN_SIDEBAR_VARIANT));
|
||||
}
|
||||
|
||||
const [optInScreen] = content.screens;
|
||||
|
||||
switch (productUrl) {
|
||||
case "www.walmart.com":
|
||||
optInScreen.content.above_button_content[0].text.args = {
|
||||
firstSite: "Walmart",
|
||||
secondSite: "Amazon",
|
||||
thirdSite: "Best Buy",
|
||||
};
|
||||
break;
|
||||
case "www.bestbuy.com":
|
||||
optInScreen.content.above_button_content[0].text.args = {
|
||||
firstSite: "Best Buy",
|
||||
secondSite: "Amazon",
|
||||
thirdSite: "Walmart",
|
||||
};
|
||||
break;
|
||||
case "www.amazon.com":
|
||||
// Intentional fall-through
|
||||
default:
|
||||
optInScreen.content.above_button_content[0].text.args = {
|
||||
firstSite: "Amazon",
|
||||
secondSite: "Walmart",
|
||||
thirdSite: "Best Buy",
|
||||
};
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,8 +49,6 @@ skip-if = ["os == 'linux' && bits == 64"] # Bug 1757875
|
|||
|
||||
["browser_aboutwelcome_multistage_transitions.js"]
|
||||
|
||||
["browser_aboutwelcome_multistage_video.js"]
|
||||
|
||||
["browser_aboutwelcome_observer.js"]
|
||||
https_first_disabled = true
|
||||
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { PermissionTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/PermissionTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const videoUrl =
|
||||
"https://www.mozilla.org/tests/dom/media/webaudio/test/noaudio.webm";
|
||||
|
||||
function testAutoplayPermission(browser) {
|
||||
let principal = browser.contentPrincipal;
|
||||
is(
|
||||
PermissionTestUtils.testPermission(principal, "autoplay-media"),
|
||||
Services.perms.ALLOW_ACTION,
|
||||
`Autoplay is allowed on ${principal.origin}`
|
||||
);
|
||||
}
|
||||
|
||||
async function openAWWithVideo({
|
||||
autoPlay = false,
|
||||
video_url = videoUrl,
|
||||
...rest
|
||||
} = {}) {
|
||||
const content = [
|
||||
{
|
||||
id: "VIDEO_ONBOARDING",
|
||||
content: {
|
||||
position: "center",
|
||||
logo: {},
|
||||
title: "Video onboarding",
|
||||
secondary_button: { label: "Skip video", action: { navigate: true } },
|
||||
video_container: {
|
||||
video_url,
|
||||
action: { navigate: true },
|
||||
autoPlay,
|
||||
...rest,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
await setAboutWelcomeMultiStage(JSON.stringify(content));
|
||||
let { cleanup, browser } = await openMRAboutWelcome();
|
||||
return {
|
||||
browser,
|
||||
content,
|
||||
async cleanup() {
|
||||
await SpecialPowers.popPrefEnv();
|
||||
await cleanup();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
add_task(async function test_aboutwelcome_video_autoplay() {
|
||||
let { cleanup, browser } = await openAWWithVideo({ autoPlay: true });
|
||||
|
||||
testAutoplayPermission(browser);
|
||||
|
||||
await SpecialPowers.spawn(browser, [videoUrl], async url => {
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector("main.with-video"),
|
||||
"Waiting for video onboarding screen"
|
||||
);
|
||||
let video = content.document.querySelector(`video[src='${url}'][autoplay]`);
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() =>
|
||||
video.currentTime > 0 &&
|
||||
!video.paused &&
|
||||
!video.ended &&
|
||||
video.readyState > 2,
|
||||
"Waiting for video to play"
|
||||
);
|
||||
ok(!video.error, "Video should not have an error");
|
||||
});
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
add_task(async function test_aboutwelcome_video_no_autoplay() {
|
||||
let { cleanup, browser } = await openAWWithVideo();
|
||||
|
||||
testAutoplayPermission(browser);
|
||||
|
||||
await SpecialPowers.spawn(browser, [videoUrl], async url => {
|
||||
let video = await ContentTaskUtils.waitForCondition(
|
||||
() =>
|
||||
content.document.querySelector(`video[src='${url}']:not([autoplay])`),
|
||||
"Waiting for video element to render"
|
||||
);
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => video.paused && !video.ended && video.readyState > 2,
|
||||
"Waiting for video to be playable but not playing"
|
||||
);
|
||||
ok(!video.error, "Video should not have an error");
|
||||
});
|
||||
|
||||
await cleanup();
|
||||
});
|
|
@ -537,10 +537,6 @@ const TEST_GLOBAL = {
|
|||
settings: {},
|
||||
},
|
||||
},
|
||||
TelemetryStopwatch: {
|
||||
start: () => {},
|
||||
finish: () => {},
|
||||
},
|
||||
Sampling: {
|
||||
ratioSample(_seed, _ratios) {
|
||||
return Promise.resolve(0);
|
||||
|
|
|
@ -466,6 +466,24 @@ messaging_system:
|
|||
send_in_pings:
|
||||
- messaging-system
|
||||
|
||||
message_request_time:
|
||||
type: timing_distribution
|
||||
description: >
|
||||
Firefox: Time in ms spent selecting and matching messages to user profile.
|
||||
|
||||
This metric was generated to correspond to the Legacy Telemetry
|
||||
exponential histogram MS_MESSAGE_REQUEST_TIME_MS.
|
||||
time_unit: millisecond
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1600335
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1600335
|
||||
notification_emails:
|
||||
- omc@mozilla.com
|
||||
- dmosedale@mozilla.com
|
||||
expires: never
|
||||
telemetry_mirror: MS_MESSAGE_REQUEST_TIME_MS
|
||||
|
||||
|
||||
messaging_system.attribution:
|
||||
source:
|
||||
|
|
|
@ -1946,7 +1946,7 @@ export class _ASRouter {
|
|||
return this.loadMessagesFromAllProviders();
|
||||
}
|
||||
|
||||
async sendPBNewTabMessage({ tabId, hideDefault }) {
|
||||
async sendPBNewTabMessage({ hideDefault }) {
|
||||
let message = null;
|
||||
const PromoInfo = {
|
||||
FOCUS: { enabledPref: "browser.promo.focus.enabled" },
|
||||
|
@ -1980,12 +1980,11 @@ export class _ASRouter {
|
|||
),
|
||||
}));
|
||||
|
||||
const telemetryObject = { tabId };
|
||||
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
const timerId = Glean.messagingSystem.messageRequestTime.start();
|
||||
message = await this.handleMessageRequest({
|
||||
template: "pb_newtab",
|
||||
});
|
||||
TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
Glean.messagingSystem.messageRequestTime.stopAndAccumulate(timerId);
|
||||
|
||||
// Format urls if any are defined
|
||||
["infoLinkUrl"].forEach(key => {
|
||||
|
@ -2026,7 +2025,6 @@ export class _ASRouter {
|
|||
* @param {object} [trigger.context] an object with data about the source of
|
||||
* the trigger, matched against the message's targeting expression
|
||||
* @param {MozBrowser} trigger.browser the browser to route messages to
|
||||
* @param {number} [trigger.tabId] identifier used only for exposure testing
|
||||
* @param {boolean} [skipLoadingMessages=false] pass true to skip looking for
|
||||
* new messages. use when calling from loadMessagesFromAllProviders to avoid
|
||||
* recursion. we call this from loadMessagesFromAllProviders in order to
|
||||
|
@ -2035,7 +2033,7 @@ export class _ASRouter {
|
|||
* @resolves {message} an object with the routed message
|
||||
*/
|
||||
async sendTriggerMessage(
|
||||
{ tabId, browser, ...trigger },
|
||||
{ browser, ...trigger },
|
||||
skipLoadingMessages = false
|
||||
) {
|
||||
if (!skipLoadingMessages) {
|
||||
|
@ -2052,8 +2050,7 @@ export class _ASRouter {
|
|||
browser === browser.ownerGlobal.gBrowser?.selectedBrowser;
|
||||
}
|
||||
}
|
||||
const telemetryObject = { tabId };
|
||||
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
const timerId = Glean.messagingSystem.messageRequestTime.start();
|
||||
// Return all the messages so that it can record the Reach event
|
||||
const messages =
|
||||
(await this.handleMessageRequest({
|
||||
|
@ -2062,7 +2059,7 @@ export class _ASRouter {
|
|||
triggerContext: trigger.context,
|
||||
returnAll: true,
|
||||
})) || [];
|
||||
TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
Glean.messagingSystem.messageRequestTime.stopAndAccumulate(timerId);
|
||||
|
||||
// Record the Reach event for all the messages with `forReachEvent`,
|
||||
// only send the first message without forReachEvent to the target
|
||||
|
|
|
@ -41,7 +41,7 @@ export class ASRouterParentProcessMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
handleMessage(name, data, { id: tabId, browser } = { browser: null }) {
|
||||
handleMessage(name, data, { browser } = { browser: null }) {
|
||||
switch (name) {
|
||||
case msg.AS_ROUTER_TELEMETRY_USER_EVENT:
|
||||
return this.handleTelemetry({
|
||||
|
@ -70,14 +70,12 @@ export class ASRouterParentProcessMessageHandler {
|
|||
case msg.TRIGGER: {
|
||||
return this._router.sendTriggerMessage({
|
||||
...(data && data.trigger),
|
||||
tabId,
|
||||
browser,
|
||||
});
|
||||
}
|
||||
case msg.PBNEWTAB_MESSAGE_REQUEST: {
|
||||
return this._router.sendPBNewTabMessage({
|
||||
...data,
|
||||
tabId,
|
||||
browser,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1771,7 +1771,7 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "topleft",
|
||||
|
@ -1780,7 +1780,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "topright",
|
||||
|
@ -1792,7 +1792,7 @@ const MESSAGES = () => {
|
|||
position: "callout",
|
||||
width: "401px",
|
||||
title: {
|
||||
string_id: "shopping-opt-in-integrated-headline",
|
||||
string_id: "shopping-callout-opt-in-integrated-headline",
|
||||
fontSize: "20px",
|
||||
letterSpacing: "0",
|
||||
},
|
||||
|
@ -2021,7 +2021,25 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomright",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
|
@ -2030,7 +2048,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "bottomright",
|
||||
|
@ -2042,7 +2060,7 @@ const MESSAGES = () => {
|
|||
position: "callout",
|
||||
width: "401px",
|
||||
title: {
|
||||
string_id: "shopping-opt-in-integrated-headline",
|
||||
string_id: "shopping-callout-opt-in-integrated-headline",
|
||||
fontSize: "20px",
|
||||
letterSpacing: "0",
|
||||
},
|
||||
|
@ -2500,7 +2518,7 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "topleft",
|
||||
|
@ -2509,7 +2527,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "topright",
|
||||
|
@ -2521,12 +2539,11 @@ const MESSAGES = () => {
|
|||
position: "callout",
|
||||
width: "401px",
|
||||
title: {
|
||||
string_id:
|
||||
"shopping-integrated-callout-disabled-auto-open-title",
|
||||
string_id: "shopping-integrated-callout-sidebar-closed-title",
|
||||
},
|
||||
subtitle: {
|
||||
string_id:
|
||||
"shopping-integrated-callout-disabled-auto-open-subtitle",
|
||||
"shopping-integrated-callout-sidebar-closed-subtitle",
|
||||
letterSpacing: "0",
|
||||
},
|
||||
logo: {
|
||||
|
@ -2572,7 +2589,25 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomright",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
|
@ -2581,7 +2616,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "bottomright",
|
||||
|
@ -2593,12 +2628,11 @@ const MESSAGES = () => {
|
|||
position: "callout",
|
||||
width: "401px",
|
||||
title: {
|
||||
string_id:
|
||||
"shopping-integrated-callout-disabled-auto-open-title",
|
||||
string_id: "shopping-integrated-callout-sidebar-closed-title",
|
||||
},
|
||||
subtitle: {
|
||||
string_id:
|
||||
"shopping-integrated-callout-disabled-auto-open-subtitle",
|
||||
"shopping-integrated-callout-sidebar-closed-subtitle",
|
||||
letterSpacing: "0",
|
||||
},
|
||||
logo: {
|
||||
|
@ -2654,12 +2688,11 @@ const MESSAGES = () => {
|
|||
position: "callout",
|
||||
width: "401px",
|
||||
title: {
|
||||
string_id:
|
||||
"shopping-integrated-callout-disabled-auto-open-title",
|
||||
string_id: "shopping-integrated-callout-sidebar-closed-title",
|
||||
},
|
||||
subtitle: {
|
||||
string_id:
|
||||
"shopping-integrated-callout-no-logo-disabled-auto-open-subtitle",
|
||||
"shopping-integrated-callout-no-logo-sidebar-closed-subtitle",
|
||||
letterSpacing: "0",
|
||||
},
|
||||
dismiss_button: {
|
||||
|
@ -2698,7 +2731,7 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "topleft",
|
||||
|
@ -2707,7 +2740,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "topright",
|
||||
|
@ -2770,7 +2803,25 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomright",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
|
@ -2779,7 +2830,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "bottomright",
|
||||
|
@ -2842,7 +2893,7 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "topleft",
|
||||
|
@ -2851,7 +2902,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "topright",
|
||||
|
@ -2914,7 +2965,25 @@ const MESSAGES = () => {
|
|||
anchors: [
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar'].tools-overflow",
|
||||
panel_position: {
|
||||
anchor_attachment: "topcenter",
|
||||
callout_attachment: "bottomright",
|
||||
},
|
||||
no_open_on_anchor: true,
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main:not([sidebar-positionend]) > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "rightcenter",
|
||||
callout_attachment: "bottomleft",
|
||||
|
@ -2923,7 +2992,7 @@ const MESSAGES = () => {
|
|||
},
|
||||
{
|
||||
selector:
|
||||
"#sidebar-main[positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
"#sidebar-main[sidebar-positionend] > sidebar-main::%shadow% .tools-and-extensions::%shadow% moz-button[view='viewReviewCheckerSidebar']",
|
||||
panel_position: {
|
||||
anchor_attachment: "leftcenter",
|
||||
callout_attachment: "bottomright",
|
||||
|
|
|
@ -107,14 +107,13 @@ export class _MomentsPageHub {
|
|||
}
|
||||
|
||||
async messageRequest({ triggerId, template }) {
|
||||
const telemetryObject = { triggerId };
|
||||
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
const timerId = Glean.messagingSystem.messageRequestTime.start();
|
||||
const messages = await this._handleMessageRequest({
|
||||
triggerId,
|
||||
template,
|
||||
returnAll: true,
|
||||
});
|
||||
TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
Glean.messagingSystem.messageRequestTime.stopAndAccumulate(timerId);
|
||||
|
||||
// Record the "reach" event for all the messages with `forReachEvent`,
|
||||
// only execute action for the first message without forReachEvent.
|
||||
|
|
|
@ -224,13 +224,12 @@ export class _ToolbarBadgeHub {
|
|||
}
|
||||
|
||||
async messageRequest({ triggerId, template }) {
|
||||
const telemetryObject = { triggerId };
|
||||
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
const timerId = Glean.messagingSystem.messageRequestTime.start();
|
||||
const message = await this._handleMessageRequest({
|
||||
triggerId,
|
||||
template,
|
||||
});
|
||||
TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
|
||||
Glean.messagingSystem.messageRequestTime.stopAndAccumulate(timerId);
|
||||
if (message) {
|
||||
this.registerBadgeNotificationListener(message);
|
||||
}
|
||||
|
|
|
@ -262,7 +262,6 @@ add_task(async function test_exposure_ping() {
|
|||
const exposureSpy = sinon.spy(NimbusTelemetry, "recordExposure");
|
||||
|
||||
await ASRouter.sendTriggerMessage({
|
||||
tabId: 1,
|
||||
browser: gBrowser.selectedBrowser,
|
||||
id: "openURL",
|
||||
param: { host: "messenger.com" },
|
||||
|
|
|
@ -1652,7 +1652,6 @@ describe("ASRouter", () => {
|
|||
Router.loadMessagesFromAllProviders.onFirstCall().resolves();
|
||||
|
||||
await Router.sendTriggerMessage({
|
||||
tabId: 0,
|
||||
browser: gBrowser.selectedBrowser,
|
||||
id: "firstRun",
|
||||
});
|
||||
|
@ -1668,35 +1667,24 @@ describe("ASRouter", () => {
|
|||
);
|
||||
});
|
||||
it("should record telemetry information", async () => {
|
||||
const startTelemetryStopwatch = sandbox.stub(
|
||||
global.TelemetryStopwatch,
|
||||
"start"
|
||||
const fakeTimerId = 42;
|
||||
const start = sandbox
|
||||
.stub(global.Glean.messagingSystem.messageRequestTime, "start")
|
||||
.returns(fakeTimerId);
|
||||
const stopAndAccumulate = sandbox.stub(
|
||||
global.Glean.messagingSystem.messageRequestTime,
|
||||
"stopAndAccumulate"
|
||||
);
|
||||
const finishTelemetryStopwatch = sandbox.stub(
|
||||
global.TelemetryStopwatch,
|
||||
"finish"
|
||||
);
|
||||
|
||||
const tabId = 123;
|
||||
|
||||
await Router.sendTriggerMessage({
|
||||
tabId,
|
||||
browser: {},
|
||||
id: "firstRun",
|
||||
});
|
||||
|
||||
assert.calledTwice(startTelemetryStopwatch);
|
||||
assert.calledWithExactly(
|
||||
startTelemetryStopwatch,
|
||||
"MS_MESSAGE_REQUEST_TIME_MS",
|
||||
{ tabId }
|
||||
);
|
||||
assert.calledTwice(finishTelemetryStopwatch);
|
||||
assert.calledWithExactly(
|
||||
finishTelemetryStopwatch,
|
||||
"MS_MESSAGE_REQUEST_TIME_MS",
|
||||
{ tabId }
|
||||
);
|
||||
assert.calledTwice(start);
|
||||
assert.calledWithExactly(start);
|
||||
assert.calledTwice(stopAndAccumulate);
|
||||
assert.calledWithExactly(stopAndAccumulate, fakeTimerId);
|
||||
});
|
||||
it("should have previousSessionEnd in the message context", () => {
|
||||
assert.propertyVal(
|
||||
|
@ -1737,7 +1725,6 @@ describe("ASRouter", () => {
|
|||
sandbox.spy(Glean.messagingExperiments.reachCfr, "record");
|
||||
|
||||
await Router.sendTriggerMessage({
|
||||
tabId: 0,
|
||||
browser: {},
|
||||
id: "foo",
|
||||
});
|
||||
|
@ -1760,7 +1747,6 @@ describe("ASRouter", () => {
|
|||
sandbox.spy(Glean.messagingExperiments.reachCfr, "record");
|
||||
|
||||
await Router.sendTriggerMessage({
|
||||
tabId: 0,
|
||||
browser: {},
|
||||
id: "foo",
|
||||
});
|
||||
|
@ -1790,7 +1776,6 @@ describe("ASRouter", () => {
|
|||
sandbox.stub(Router, "handleMessageRequest").resolves(messages);
|
||||
|
||||
await Router.sendTriggerMessage({
|
||||
tabId: 0,
|
||||
browser: {},
|
||||
id: "foo",
|
||||
});
|
||||
|
|
|
@ -206,7 +206,6 @@ describe("ASRouterParentProcessMessageHandler", () => {
|
|||
assert.calledOnce(config.router.sendTriggerMessage);
|
||||
assert.calledWith(config.router.sendTriggerMessage, {
|
||||
stuff: {},
|
||||
tabId: 100,
|
||||
browser: { ownerGlobal: {} },
|
||||
});
|
||||
assert.deepEqual(result, { value: 1 });
|
||||
|
|
|
@ -46,6 +46,12 @@ describe("MomentsPageHub", () => {
|
|||
record: () => {},
|
||||
},
|
||||
},
|
||||
messagingSystem: {
|
||||
messageRequestTime: {
|
||||
start() {},
|
||||
stopAndAccumulate() {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -146,30 +152,22 @@ describe("MomentsPageHub", () => {
|
|||
|
||||
assert.notCalled(setStringPrefStub);
|
||||
});
|
||||
it("should record telemetry events", async () => {
|
||||
const startTelemetryStopwatch = sandbox.stub(
|
||||
global.TelemetryStopwatch,
|
||||
"start"
|
||||
);
|
||||
const finishTelemetryStopwatch = sandbox.stub(
|
||||
global.TelemetryStopwatch,
|
||||
"finish"
|
||||
it("should record a message request time", async () => {
|
||||
const fakeTimerId = 42;
|
||||
const start = sandbox
|
||||
.stub(global.Glean.messagingSystem.messageRequestTime, "start")
|
||||
.returns(fakeTimerId);
|
||||
const stopAndAccumulate = sandbox.stub(
|
||||
global.Glean.messagingSystem.messageRequestTime,
|
||||
"stopAndAccumulate"
|
||||
);
|
||||
|
||||
await instance.messageRequest({ triggerId: "trigger" });
|
||||
|
||||
assert.calledOnce(startTelemetryStopwatch);
|
||||
assert.calledWithExactly(
|
||||
startTelemetryStopwatch,
|
||||
"MS_MESSAGE_REQUEST_TIME_MS",
|
||||
{ triggerId: "trigger" }
|
||||
);
|
||||
assert.calledOnce(finishTelemetryStopwatch);
|
||||
assert.calledWithExactly(
|
||||
finishTelemetryStopwatch,
|
||||
"MS_MESSAGE_REQUEST_TIME_MS",
|
||||
{ triggerId: "trigger" }
|
||||
);
|
||||
assert.calledOnce(start);
|
||||
assert.calledWithExactly(start);
|
||||
assert.calledOnce(stopAndAccumulate);
|
||||
assert.calledWithExactly(stopAndAccumulate, fakeTimerId);
|
||||
});
|
||||
it("should record Reach event for the Moments page experiment", async () => {
|
||||
const momentsMessages = (await PanelTestProvider.getMessages()).filter(
|
||||
|
|
|
@ -163,31 +163,23 @@ describe("ToolbarBadgeHub", () => {
|
|||
|
||||
assert.notCalled(instance.registerBadgeNotificationListener);
|
||||
});
|
||||
it("should record telemetry events", async () => {
|
||||
const startTelemetryStopwatch = sandbox.stub(
|
||||
global.TelemetryStopwatch,
|
||||
"start"
|
||||
);
|
||||
const finishTelemetryStopwatch = sandbox.stub(
|
||||
global.TelemetryStopwatch,
|
||||
"finish"
|
||||
it("should record a message request time", async () => {
|
||||
const fakeTimerId = 42;
|
||||
const start = sandbox
|
||||
.stub(global.Glean.messagingSystem.messageRequestTime, "start")
|
||||
.returns(fakeTimerId);
|
||||
const stopAndAccumulate = sandbox.stub(
|
||||
global.Glean.messagingSystem.messageRequestTime,
|
||||
"stopAndAccumulate"
|
||||
);
|
||||
handleMessageRequestStub.returns(null);
|
||||
|
||||
await instance.messageRequest({ triggerId: "trigger" });
|
||||
|
||||
assert.calledOnce(startTelemetryStopwatch);
|
||||
assert.calledWithExactly(
|
||||
startTelemetryStopwatch,
|
||||
"MS_MESSAGE_REQUEST_TIME_MS",
|
||||
{ triggerId: "trigger" }
|
||||
);
|
||||
assert.calledOnce(finishTelemetryStopwatch);
|
||||
assert.calledWithExactly(
|
||||
finishTelemetryStopwatch,
|
||||
"MS_MESSAGE_REQUEST_TIME_MS",
|
||||
{ triggerId: "trigger" }
|
||||
);
|
||||
assert.calledOnce(start);
|
||||
assert.calledWithExactly(start);
|
||||
assert.calledOnce(stopAndAccumulate);
|
||||
assert.calledWithExactly(stopAndAccumulate, fakeTimerId);
|
||||
});
|
||||
});
|
||||
describe("addToolbarNotification", () => {
|
||||
|
|
|
@ -549,10 +549,6 @@ const TEST_GLOBAL = {
|
|||
settings: {},
|
||||
},
|
||||
},
|
||||
TelemetryStopwatch: {
|
||||
start: () => {},
|
||||
finish: () => {},
|
||||
},
|
||||
Sampling: {
|
||||
ratioSample(_seed, _ratios) {
|
||||
return Promise.resolve(0);
|
||||
|
@ -584,6 +580,12 @@ const TEST_GLOBAL = {
|
|||
record() {},
|
||||
},
|
||||
},
|
||||
messagingSystem: {
|
||||
messageRequestTime: {
|
||||
start() {},
|
||||
stopAndAccumulate() {},
|
||||
},
|
||||
},
|
||||
newtab: {
|
||||
opened: {
|
||||
record() {},
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
let lazy = {};
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "FluentStrings", function () {
|
||||
return new Localization(["browser/toolbarDropHandler.ftl"], true);
|
||||
});
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
HomePage: "resource:///modules/HomePage.sys.mjs",
|
||||
OpenInTabsUtils:
|
||||
|
@ -21,13 +25,19 @@ export var ToolbarDropHandler = {
|
|||
}
|
||||
},
|
||||
|
||||
_openHomeDialog(aURL, win) {
|
||||
var promptTitle = win.gNavigatorBundle.getString("droponhometitle");
|
||||
async _openHomeDialog(aURL, win) {
|
||||
const [promptTitle, promptMsgSingle, promptMsgMultiple] =
|
||||
await lazy.FluentStrings.formatValues([
|
||||
"toolbar-drop-on-home-title",
|
||||
"toolbar-drop-on-home-msg",
|
||||
"toolbar-drop-on-home-msg-multiple",
|
||||
]);
|
||||
|
||||
var promptMsg;
|
||||
if (aURL.includes("|")) {
|
||||
promptMsg = win.gNavigatorBundle.getString("droponhomemsgMultiple");
|
||||
promptMsg = promptMsgMultiple;
|
||||
} else {
|
||||
promptMsg = win.gNavigatorBundle.getString("droponhomemsg");
|
||||
promptMsg = promptMsgSingle;
|
||||
}
|
||||
|
||||
var pressedVal = Services.prompt.confirmEx(
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
// This test tends to trigger a race in the fullscreen time telemetry,
|
||||
// where the fullscreen enter and fullscreen exit events (which use the
|
||||
// same histogram ID) overlap. That causes TelemetryStopwatch to log an
|
||||
// error.
|
||||
SimpleTest.ignoreAllUncaughtExceptions(true);
|
||||
|
||||
const { AppMenuNotifications } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppMenuNotifications.sys.mjs"
|
||||
);
|
||||
|
|
|
@ -142,9 +142,9 @@ class HistoryInView extends ViewPage {
|
|||
Glean.firefoxviewNext.historyVisits.record();
|
||||
|
||||
if (this.controller.searchQuery) {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById("FIREFOX_VIEW_CUMULATIVE_SEARCHES")
|
||||
.add("history", this.cumulativeSearches);
|
||||
Glean.firefoxview.cumulativeSearches.history.accumulateSingleSample(
|
||||
this.cumulativeSearches
|
||||
);
|
||||
this.cumulativeSearches = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -407,3 +407,30 @@ firefoxview_next:
|
|||
Section within which show all is clicked.
|
||||
type: string
|
||||
telemetry_mirror: Firefoxview_next_SearchShowAll_Showallbutton
|
||||
|
||||
firefoxview:
|
||||
cumulative_searches:
|
||||
type: labeled_custom_distribution
|
||||
description: >
|
||||
Cumulative no. of searches performed before selecting a resulting tab.
|
||||
|
||||
This metric was generated to correspond to the Legacy Telemetry enumerated
|
||||
histogram FIREFOX_VIEW_CUMULATIVE_SEARCHES.
|
||||
labels:
|
||||
- recentbrowsing
|
||||
- opentabs
|
||||
- recentlyclosed
|
||||
- history
|
||||
- syncedtabs
|
||||
range_min: 0
|
||||
range_max: 100
|
||||
bucket_count: 101
|
||||
histogram_type: linear
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1869765
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1869765
|
||||
notification_emails:
|
||||
- firefoxview@mozilla.com
|
||||
expires: never
|
||||
telemetry_mirror: FIREFOX_VIEW_CUMULATIVE_SEARCHES
|
||||
|
|
|
@ -494,12 +494,9 @@ class OpenTabsInViewCard extends ViewPageContent {
|
|||
window: this.title || "Window 1 (Current)",
|
||||
});
|
||||
if (this.searchQuery) {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById("FIREFOX_VIEW_CUMULATIVE_SEARCHES")
|
||||
.add(
|
||||
this.recentBrowsing ? "recentbrowsing" : "opentabs",
|
||||
this.cumulativeSearches
|
||||
);
|
||||
Glean.firefoxview.cumulativeSearches[
|
||||
this.recentBrowsing ? "recentbrowsing" : "opentabs"
|
||||
].accumulateSingleSample(this.cumulativeSearches);
|
||||
this.cumulativeSearches = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,12 +225,9 @@ class RecentlyClosedTabsInView extends ViewPage {
|
|||
page: this.recentBrowsing ? "recentbrowsing" : "recentlyclosed",
|
||||
});
|
||||
if (this.searchQuery) {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById("FIREFOX_VIEW_CUMULATIVE_SEARCHES")
|
||||
.add(
|
||||
this.recentBrowsing ? "recentbrowsing" : "recentlyclosed",
|
||||
this.cumulativeSearches
|
||||
);
|
||||
Glean.firefoxview.cumulativeSearches[
|
||||
this.recentBrowsing ? "recentbrowsing" : "recentlyclosed"
|
||||
].accumulateSingleSample(this.cumulativeSearches);
|
||||
this.cumulativeSearches = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,12 +168,9 @@ class SyncedTabsInView extends ViewPage {
|
|||
});
|
||||
|
||||
if (this.controller.searchQuery) {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById("FIREFOX_VIEW_CUMULATIVE_SEARCHES")
|
||||
.add(
|
||||
this.recentBrowsing ? "recentbrowsing" : "syncedtabs",
|
||||
this.cumulativeSearches
|
||||
);
|
||||
Glean.firefoxview.cumulativeSearches[
|
||||
this.recentBrowsing ? "recentbrowsing" : "syncedtabs"
|
||||
].accumulateSingleSample(this.cumulativeSearches);
|
||||
this.cumulativeSearches = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
.optin-footer-message {
|
||||
margin-top: var(--space-large);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.optin-heading-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
@ -39,7 +44,7 @@
|
|||
}
|
||||
|
||||
.optin-progress-bar-wrapper {
|
||||
margin-top: var(--space-medium);
|
||||
margin-bottom: var(--space-medium);
|
||||
}
|
||||
|
||||
.optin-progress-bar {
|
||||
|
|
|
@ -18,6 +18,7 @@ class ModelOptin extends MozLitElement {
|
|||
messageL10nId: { type: String, fluent: true },
|
||||
optinButtonL10nId: { type: String, fluent: true },
|
||||
optoutButtonL10nId: { type: String, fluent: true },
|
||||
footerMessageL10nId: { type: String, fluent: true },
|
||||
cancelDownloadButtonL10nId: { type: String, fluent: true },
|
||||
isLoading: { type: Boolean, reflect: true },
|
||||
progressStatus: { type: Number }, // Expected to be a number between 0 and 100
|
||||
|
@ -28,6 +29,7 @@ class ModelOptin extends MozLitElement {
|
|||
confirm: "MlModelOptinConfirm",
|
||||
deny: "MlModelOptinDeny",
|
||||
cancelDownload: "MlModelOptinCancelDownload",
|
||||
footerLinkClick: "MlModelOptinFooterLinkClick",
|
||||
};
|
||||
|
||||
static eventBehaviors = {
|
||||
|
@ -65,6 +67,14 @@ class ModelOptin extends MozLitElement {
|
|||
this.progressStatus = undefined;
|
||||
}
|
||||
|
||||
handleFooterLinkClick(e) {
|
||||
// ftl overrides the html, need to manually watch for event in parent.
|
||||
if (e.target.id !== "optin-footer-link") {
|
||||
return;
|
||||
}
|
||||
this.dispatch(ModelOptin.events.footerLinkClick);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link
|
||||
|
@ -72,6 +82,18 @@ class ModelOptin extends MozLitElement {
|
|||
href="chrome://browser/content/genai/content/model-optin.css"
|
||||
/>
|
||||
<section ?hidden=${this.isHidden} class="optin-wrapper">
|
||||
${this.isLoading
|
||||
? html`
|
||||
<div class="optin-progress-bar-wrapper">
|
||||
<progress
|
||||
class="optin-progress-bar"
|
||||
value=${this.progressStatus}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div class="optin-header-wrapper">
|
||||
<div class="optin-header">
|
||||
${this.headingIcon
|
||||
|
@ -90,28 +112,20 @@ class ModelOptin extends MozLitElement {
|
|||
|
||||
${this.isLoading
|
||||
? html`
|
||||
<div>
|
||||
<div class="optin-actions">
|
||||
<moz-button
|
||||
data-l10n-id=${this.cancelDownloadButtonL10nId}
|
||||
@click=${this.handleCancelDownloadClick}
|
||||
>
|
||||
</moz-button>
|
||||
</div>
|
||||
<!-- Inlined progress bar -->
|
||||
<div class="optin-progress-bar-wrapper">
|
||||
<progress
|
||||
class="optin-progress-bar"
|
||||
value=${this.progressStatus}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
<div class="optin-actions">
|
||||
<moz-button
|
||||
size="small"
|
||||
data-l10n-id=${this.cancelDownloadButtonL10nId}
|
||||
@click=${this.handleCancelDownloadClick}
|
||||
>
|
||||
</moz-button>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="optin-actions">
|
||||
<moz-button-group>
|
||||
<moz-button
|
||||
size="small"
|
||||
id="optin-confirm-button"
|
||||
type="primary"
|
||||
data-l10n-id=${this.optinButtonL10nId}
|
||||
|
@ -119,6 +133,7 @@ class ModelOptin extends MozLitElement {
|
|||
>
|
||||
</moz-button>
|
||||
<moz-button
|
||||
size="small"
|
||||
id="optin-deny-button"
|
||||
data-l10n-id=${this.optoutButtonL10nId}
|
||||
@click=${this.handleDenyClick}
|
||||
|
@ -127,6 +142,21 @@ class ModelOptin extends MozLitElement {
|
|||
</moz-button-group>
|
||||
</div>
|
||||
`}
|
||||
${!this.isLoading && this.footerMessageL10nId !== ""
|
||||
? html`
|
||||
<p
|
||||
class="optin-footer-message"
|
||||
data-l10n-id=${this.footerMessageL10nId}
|
||||
@click=${this.handleFooterLinkClick}
|
||||
>
|
||||
<a
|
||||
id="optin-footer-link"
|
||||
data-l10n-name="settings"
|
||||
href="#"
|
||||
></a>
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -1190,11 +1190,15 @@ var gPrivacyPane = {
|
|||
this.initDataCollection();
|
||||
|
||||
if (AppConstants.MOZ_DATA_REPORTING) {
|
||||
this.initSubmitHealthReport();
|
||||
this.updateSubmitHealthReportFromPref();
|
||||
Preferences.get(PREF_UPLOAD_ENABLED).on(
|
||||
"change",
|
||||
gPrivacyPane.updateSubmitHealthReportFromPref
|
||||
);
|
||||
setEventListener(
|
||||
"submitHealthReportBox",
|
||||
"command",
|
||||
gPrivacyPane.updateSubmitHealthReport
|
||||
gPrivacyPane.updateSubmitHealthReportToPref
|
||||
);
|
||||
if (AppConstants.MOZ_NORMANDY) {
|
||||
this.initOptOutStudyCheckbox();
|
||||
|
@ -3414,10 +3418,11 @@ var gPrivacyPane = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Initialize the health report service reference and checkbox.
|
||||
* Update the health report service checkbox from preference.
|
||||
*/
|
||||
initSubmitHealthReport() {
|
||||
updateSubmitHealthReportFromPref() {
|
||||
let checkbox = document.getElementById("submitHealthReportBox");
|
||||
let telemetryContainer = document.getElementById("telemetry-container");
|
||||
|
||||
// Telemetry is only sending data if MOZ_TELEMETRY_REPORTING is defined.
|
||||
// We still want to display the preferences panel if that's not the case, but
|
||||
|
@ -3433,12 +3438,13 @@ var gPrivacyPane = {
|
|||
checkbox.checked =
|
||||
Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED) &&
|
||||
AppConstants.MOZ_TELEMETRY_REPORTING;
|
||||
telemetryContainer.hidden = checkbox.checked;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the health report preference with state from checkbox.
|
||||
*/
|
||||
updateSubmitHealthReport() {
|
||||
updateSubmitHealthReportToPref() {
|
||||
let checkbox = document.getElementById("submitHealthReportBox");
|
||||
let telemetryContainer = document.getElementById("telemetry-container");
|
||||
|
||||
|
|
|
@ -166,12 +166,6 @@
|
|||
data-l10n-id="search-restore-default"
|
||||
/>
|
||||
<spacer flex="1"/>
|
||||
<button id="removeEngineButton"
|
||||
is="highlightable-button"
|
||||
class="searchEngineAction"
|
||||
data-l10n-id="search-remove-engine"
|
||||
disabled="true"
|
||||
/>
|
||||
<button id="addEngineButton"
|
||||
is="highlightable-button"
|
||||
class="searchEngineAction"
|
||||
|
@ -185,6 +179,20 @@
|
|||
add-engine-dialog.buttonlabelaccept,
|
||||
"
|
||||
/>
|
||||
<button id="editEngineButton"
|
||||
is="highlightable-button"
|
||||
class="searchEngineAction"
|
||||
hidden="true"
|
||||
disabled="true"
|
||||
label="Edit"
|
||||
/>
|
||||
<button id="removeEngineButton"
|
||||
is="highlightable-button"
|
||||
class="searchEngineAction"
|
||||
data-l10n-id="search-remove-engine"
|
||||
disabled="true"
|
||||
/>
|
||||
|
||||
</hbox>
|
||||
<hbox id="addEnginesBox" pack="start">
|
||||
<label id="addEngines" data-l10n-id="search-find-more-link" is="text-link"></label>
|
||||
|
|
|
@ -31,6 +31,7 @@ Preferences.addAll([
|
|||
{ id: "browser.urlbar.recentsearches.featureGate", type: "bool" },
|
||||
{ id: "browser.urlbar.suggest.recentsearches", type: "bool" },
|
||||
{ id: "browser.urlbar.scotchBonnet.enableOverride", type: "bool" },
|
||||
{ id: "browser.urlbar.update2.engineAliasRefresh", type: "bool" },
|
||||
]);
|
||||
|
||||
const ENGINE_FLAVOR = "text/x-moz-search-engine";
|
||||
|
@ -84,6 +85,11 @@ var gSearchPane = {
|
|||
"browser.search.suggest.enabled.private"
|
||||
);
|
||||
|
||||
Preferences.get("browser.urlbar.update2.engineAliasRefresh").on(
|
||||
"change",
|
||||
() => gEngineView.updateUserEngineButtonVisibility()
|
||||
);
|
||||
|
||||
let updateSuggestionCheckboxes =
|
||||
this._updateSuggestionCheckboxes.bind(this);
|
||||
suggestsPref.on("change", updateSuggestionCheckboxes);
|
||||
|
@ -809,7 +815,7 @@ class EngineView {
|
|||
|
||||
this.loadL10nNames();
|
||||
this.#addListeners();
|
||||
this.#showAddEngineButton();
|
||||
this.updateUserEngineButtonVisibility();
|
||||
}
|
||||
|
||||
async loadL10nNames() {
|
||||
|
@ -864,17 +870,15 @@ class EngineView {
|
|||
}
|
||||
|
||||
/**
|
||||
* Shows the "Add Search Engine" button if the pref is enabled.
|
||||
* Shows the Add and Edit Search Engines buttons if the pref is enabled.
|
||||
*/
|
||||
#showAddEngineButton() {
|
||||
updateUserEngineButtonVisibility() {
|
||||
let aliasRefresh = Services.prefs.getBoolPref(
|
||||
"browser.urlbar.update2.engineAliasRefresh",
|
||||
false
|
||||
);
|
||||
if (aliasRefresh) {
|
||||
let addButton = document.getElementById("addEngineButton");
|
||||
addButton.hidden = false;
|
||||
}
|
||||
document.getElementById("addEngineButton").hidden = !aliasRefresh;
|
||||
document.getElementById("editEngineButton").hidden = !aliasRefresh;
|
||||
}
|
||||
|
||||
get lastEngineIndex() {
|
||||
|
@ -1024,10 +1028,19 @@ class EngineView {
|
|||
case "removeEngineButton":
|
||||
Services.search.removeEngine(this.selectedEngine.originalEngine);
|
||||
break;
|
||||
case "editEngineButton":
|
||||
gSubDialog.open(
|
||||
"chrome://browser/content/search/addEngine.xhtml",
|
||||
{ features: "resizable=no, modal=yes" },
|
||||
{ engine: this.selectedEngine.originalEngine, mode: "EDIT" }
|
||||
);
|
||||
break;
|
||||
case "addEngineButton":
|
||||
gSubDialog.open("chrome://browser/content/search/addEngine.xhtml", {
|
||||
features: "resizable=no, modal=yes",
|
||||
});
|
||||
gSubDialog.open(
|
||||
"chrome://browser/content/search/addEngine.xhtml",
|
||||
{ features: "resizable=no, modal=yes" },
|
||||
{ mode: "NEW" }
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -1078,6 +1091,8 @@ class EngineView {
|
|||
#onTreeSelect() {
|
||||
document.getElementById("removeEngineButton").disabled =
|
||||
!this.isEngineSelectedAndRemovable();
|
||||
document.getElementById("editEngineButton").disabled =
|
||||
!this.selectedEngine?.isUserEngine;
|
||||
}
|
||||
|
||||
#onTreeKeyPress(aEvent) {
|
||||
|
@ -1328,19 +1343,14 @@ class EngineView {
|
|||
}
|
||||
}
|
||||
async setCellText(index, column, value) {
|
||||
let engine = this._engineStore.engines[index];
|
||||
if (column.id == "engineKeyword") {
|
||||
let valid = await this.#changeKeyword(
|
||||
this._engineStore.engines[index],
|
||||
value
|
||||
);
|
||||
let valid = await this.#changeKeyword(engine, value);
|
||||
if (!valid) {
|
||||
this.#startEditingAlias(index);
|
||||
}
|
||||
} else if (column.id == "engineName") {
|
||||
let valid = await this.#changeName(
|
||||
this._engineStore.engines[index],
|
||||
value
|
||||
);
|
||||
} else if (column.id == "engineName" && engine.isUserEngine) {
|
||||
let valid = await this.#changeName(engine, value);
|
||||
if (!valid) {
|
||||
this.#startEditingName(index);
|
||||
}
|
||||
|
|
|
@ -90,9 +90,6 @@ run-if = ["os == 'win'"]
|
|||
|
||||
["browser_dns_over_https_exceptions_subdialog.js"]
|
||||
|
||||
["browser_engines.js"]
|
||||
fail-if = ["a11y_checks"] # Bug 1854636 clicked treechildren#engineChildren may not be focusable
|
||||
|
||||
["browser_ensure_prefs_bindings_initted.js"]
|
||||
|
||||
["browser_etp_exceptions_dialog.js"]
|
||||
|
@ -199,6 +196,8 @@ skip-if = ["true"]
|
|||
|
||||
["browser_privacy_syncDataClearing_v2.js"]
|
||||
|
||||
["browser_privacy_uploadEnabled.js"]
|
||||
|
||||
["browser_privacypane_2.js"]
|
||||
|
||||
["browser_privacypane_3.js"]
|
||||
|
@ -220,6 +219,9 @@ support-files = ["!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
|
|||
|
||||
["browser_searchShowSuggestionsFirst.js"]
|
||||
|
||||
["browser_search_engineList.js"]
|
||||
fail-if = ["a11y_checks"] # Bug 1854636 clicked treechildren#engineChildren may not be focusable
|
||||
|
||||
["browser_search_firefoxSuggest.js"]
|
||||
|
||||
["browser_search_no_results_change_category.js"]
|
||||
|
@ -247,8 +249,12 @@ skip-if = ["tsan"] # Bug 1678829
|
|||
|
||||
["browser_search_subdialogs_within_preferences_8.js"]
|
||||
|
||||
["browser_search_subdialogs_within_preferences_9.js"]
|
||||
|
||||
["browser_search_subdialogs_within_preferences_site_data.js"]
|
||||
|
||||
["browser_search_userEngineDialog.js"]
|
||||
|
||||
["browser_search_within_preferences_1.js"]
|
||||
|
||||
["browser_search_within_preferences_2.js"]
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
const { SearchTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/SearchTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
SearchTestUtils.init(this);
|
||||
|
||||
function getCellText(tree, i, cellName) {
|
||||
return tree.view.getCellText(i, tree.columns.getNamedColumn(cellName));
|
||||
}
|
||||
|
||||
add_setup(async function () {
|
||||
await SearchTestUtils.installSearchExtension({
|
||||
keyword: ["testing", "customkeyword"],
|
||||
search_url: "https://example.com/engine1",
|
||||
search_url_get_params: "search={searchTerms}",
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_engine_list() {
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("search", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
is(prefs.selectedPane, "paneSearch", "Search pane is selected by default");
|
||||
let doc = gBrowser.contentDocument;
|
||||
|
||||
let tree = doc.querySelector("#engineList");
|
||||
ok(
|
||||
!tree.hidden,
|
||||
"The search engine list should be visible when Search is requested"
|
||||
);
|
||||
|
||||
// Check for default search engines to be displayed in the engineList
|
||||
let defaultEngines = await Services.search.getAppProvidedEngines();
|
||||
for (let i = 0; i < defaultEngines.length; i++) {
|
||||
let engine = defaultEngines[i];
|
||||
is(
|
||||
getCellText(tree, i, "engineName"),
|
||||
engine.name,
|
||||
"Default search engine " + engine.name + " displayed correctly"
|
||||
);
|
||||
}
|
||||
|
||||
let customEngineIndex = defaultEngines.length;
|
||||
is(
|
||||
getCellText(tree, customEngineIndex, "engineKeyword"),
|
||||
"testing, customkeyword",
|
||||
"Show internal aliases"
|
||||
);
|
||||
|
||||
// Scroll the treeview into view since mouse operations
|
||||
// off screen can act confusingly.
|
||||
tree.scrollIntoView();
|
||||
let rect = tree.getCoordsForCellItem(
|
||||
customEngineIndex,
|
||||
tree.columns.getNamedColumn("engineKeyword"),
|
||||
"text"
|
||||
);
|
||||
let x = rect.x + rect.width / 2;
|
||||
let y = rect.y + rect.height / 2;
|
||||
let win = tree.ownerGlobal;
|
||||
|
||||
let promise = BrowserTestUtils.waitForEvent(tree, "dblclick");
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 1 }, win);
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 2 }, win);
|
||||
await promise;
|
||||
|
||||
EventUtils.sendString("newkeyword");
|
||||
EventUtils.sendKey("RETURN");
|
||||
|
||||
await TestUtils.waitForCondition(() => {
|
||||
return (
|
||||
getCellText(tree, customEngineIndex, "engineKeyword") ===
|
||||
"newkeyword, testing, customkeyword"
|
||||
);
|
||||
});
|
||||
|
||||
// Avoid duplicated keywords
|
||||
tree.view.setCellText(
|
||||
0,
|
||||
tree.columns.getNamedColumn("engineKeyword"),
|
||||
"keyword"
|
||||
);
|
||||
tree.view.setCellText(
|
||||
1,
|
||||
tree.columns.getNamedColumn("engineKeyword"),
|
||||
"keyword"
|
||||
);
|
||||
isnot(
|
||||
getCellText(tree, 1, "engineKeyword"),
|
||||
"keyword",
|
||||
"Do not allow duplicated keywords"
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
|
||||
add_task(async function test_remove_button_disabled_state() {
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("search", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
is(prefs.selectedPane, "paneSearch", "Search pane is selected by default");
|
||||
let doc = gBrowser.contentDocument;
|
||||
|
||||
let tree = doc.querySelector("#engineList");
|
||||
ok(
|
||||
!tree.hidden,
|
||||
"The search engine list should be visible when Search is requested"
|
||||
);
|
||||
|
||||
let defaultEngines = await Services.search.getAppProvidedEngines();
|
||||
for (let i = 0; i < defaultEngines.length; i++) {
|
||||
let engine = defaultEngines[i];
|
||||
|
||||
let isDefaultSearchEngine =
|
||||
engine.name == Services.search.defaultEngine.name ||
|
||||
engine.name == Services.search.defaultPrivateEngine.name;
|
||||
|
||||
tree.scrollIntoView();
|
||||
let rect = tree.getCoordsForCellItem(
|
||||
i,
|
||||
tree.columns.getNamedColumn("engineName"),
|
||||
"text"
|
||||
);
|
||||
let x = rect.x + rect.width / 2;
|
||||
let y = rect.y + rect.height / 2;
|
||||
let win = tree.ownerGlobal;
|
||||
|
||||
let promise = BrowserTestUtils.waitForEvent(tree, "click");
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 1 }, win);
|
||||
await promise;
|
||||
|
||||
let removeButton = doc.querySelector("#removeEngineButton");
|
||||
is(
|
||||
removeButton.disabled,
|
||||
isDefaultSearchEngine,
|
||||
"Remove button is in correct disable state"
|
||||
);
|
||||
}
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests the submitHealthReportBox checkbox is automatically updated when the
|
||||
// corresponding datareporting.healthreport.uploadEnabled pref is changed.
|
||||
|
||||
const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
|
||||
|
||||
add_task(async function test_updatePageFromPref() {
|
||||
if (!AppConstants.MOZ_TELEMETRY_REPORTING) {
|
||||
ok(true, "Skipping test because telemetry reporting is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[PREF_UPLOAD_ENABLED, false]],
|
||||
});
|
||||
|
||||
await openPreferencesViaOpenPreferencesAPI("panePrivacy", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
|
||||
const doc = gBrowser.selectedBrowser.contentDocument;
|
||||
const checkbox = doc.getElementById("submitHealthReportBox");
|
||||
Assert.ok(!checkbox.checked, "checkbox should match pref state on page load");
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[PREF_UPLOAD_ENABLED, true]],
|
||||
});
|
||||
|
||||
let checkboxUpdated = BrowserTestUtils.waitForMutationCondition(
|
||||
checkbox,
|
||||
{ attributeFilter: ["checked"] },
|
||||
() => checkbox.checked
|
||||
);
|
||||
await checkboxUpdated;
|
||||
|
||||
Assert.ok(checkbox.checked, "pref change should trigger checkbox update");
|
||||
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
|
@ -0,0 +1,228 @@
|
|||
const { SearchTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/SearchTestUtils.sys.mjs"
|
||||
);
|
||||
const { SearchUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/SearchUtils.sys.mjs"
|
||||
);
|
||||
const { sinon } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/Sinon.sys.mjs"
|
||||
);
|
||||
|
||||
SearchTestUtils.init(this);
|
||||
|
||||
let userEngine;
|
||||
let extensionEngine;
|
||||
let installedEngines;
|
||||
|
||||
function getCellText(tree, i, cellName) {
|
||||
return tree.view.getCellText(i, tree.columns.getNamedColumn(cellName));
|
||||
}
|
||||
|
||||
async function engine_list_test(fn) {
|
||||
let task = async () => {
|
||||
let prefs = await openPreferencesViaOpenPreferencesAPI("search", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
Assert.equal(
|
||||
prefs.selectedPane,
|
||||
"paneSearch",
|
||||
"Search pane is selected by default"
|
||||
);
|
||||
let doc = gBrowser.contentDocument;
|
||||
let tree = doc.querySelector("#engineList");
|
||||
Assert.ok(
|
||||
!tree.hidden,
|
||||
"The search engine list should be visible when Search is requested"
|
||||
);
|
||||
// Scroll the treeview into view since mouse operations
|
||||
// off screen can act confusingly.
|
||||
tree.scrollIntoView();
|
||||
await fn(tree, doc);
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
};
|
||||
|
||||
// Make sure the name of the passed function is used in logs.
|
||||
Object.defineProperty(task, "name", { value: fn.name });
|
||||
add_task(task);
|
||||
}
|
||||
|
||||
add_setup(async function () {
|
||||
installedEngines = await Services.search.getAppProvidedEngines();
|
||||
|
||||
await SearchTestUtils.installSearchExtension({
|
||||
keyword: ["testing", "customkeyword"],
|
||||
search_url: "https://example.com/engine1",
|
||||
search_url_get_params: "search={searchTerms}",
|
||||
name: "Extension Engine",
|
||||
});
|
||||
extensionEngine = Services.search.getEngineByName("Extension Engine");
|
||||
installedEngines.push(extensionEngine);
|
||||
|
||||
userEngine = await Services.search.addUserEngine({
|
||||
name: "User Engine",
|
||||
url: "https://example.com/user?q={searchTerms}&b=ff",
|
||||
alias: "u",
|
||||
});
|
||||
installedEngines.push(userEngine);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
await Services.search.removeEngine(userEngine);
|
||||
// Extension engine is cleaned up by SearchTestUtils.
|
||||
});
|
||||
});
|
||||
|
||||
engine_list_test(async function test_engine_list(tree) {
|
||||
let userEngineIndex = installedEngines.length - 1;
|
||||
for (let i = 0; i < installedEngines.length; i++) {
|
||||
let engine = installedEngines[i];
|
||||
Assert.equal(
|
||||
getCellText(tree, i, "engineName"),
|
||||
engine.name,
|
||||
"Search engine " + engine.name + " displayed correctly."
|
||||
);
|
||||
Assert.equal(
|
||||
tree.view.isEditable(i, tree.columns.getNamedColumn("engineName")),
|
||||
i == userEngineIndex,
|
||||
"Only user engine name is editable."
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
engine_list_test(async function test_change_keyword(tree) {
|
||||
let extensionEngineIndex = installedEngines.length - 2;
|
||||
Assert.equal(
|
||||
getCellText(tree, extensionEngineIndex, "engineKeyword"),
|
||||
"testing, customkeyword",
|
||||
"Internal keywords are displayed."
|
||||
);
|
||||
let rect = tree.getCoordsForCellItem(
|
||||
extensionEngineIndex,
|
||||
tree.columns.getNamedColumn("engineKeyword"),
|
||||
"text"
|
||||
);
|
||||
|
||||
// Test editing keyword of extension engine because it
|
||||
// has user-defined and extension-provided keywords.
|
||||
let x = rect.x + rect.width / 2;
|
||||
let y = rect.y + rect.height / 2;
|
||||
let win = tree.ownerGlobal;
|
||||
|
||||
let promise = BrowserTestUtils.waitForEvent(tree, "dblclick");
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 1 }, win);
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 2 }, win);
|
||||
await promise;
|
||||
|
||||
promise = SearchTestUtils.promiseSearchNotification(
|
||||
SearchUtils.MODIFIED_TYPE.CHANGED,
|
||||
SearchUtils.TOPIC_ENGINE_MODIFIED
|
||||
);
|
||||
EventUtils.sendString("newkeyword");
|
||||
EventUtils.sendKey("RETURN");
|
||||
await promise;
|
||||
|
||||
Assert.equal(
|
||||
getCellText(tree, extensionEngineIndex, "engineKeyword"),
|
||||
"newkeyword, testing, customkeyword",
|
||||
"User-defined keyword was added."
|
||||
);
|
||||
|
||||
// Test duplicated keywords (different capitalization).
|
||||
tree.view.setCellText(
|
||||
0,
|
||||
tree.columns.getNamedColumn("engineKeyword"),
|
||||
"keyword"
|
||||
);
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
let keywordBefore = getCellText(tree, 1, "engineKeyword");
|
||||
let alertSpy = sinon.spy(win, "alert");
|
||||
tree.view.setCellText(
|
||||
1,
|
||||
tree.columns.getNamedColumn("engineKeyword"),
|
||||
"Keyword"
|
||||
);
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
Assert.ok(alertSpy.calledOnce, "Warning was shown.");
|
||||
Assert.equal(
|
||||
getCellText(tree, 1, "engineKeyword"),
|
||||
keywordBefore,
|
||||
"Did not modify keywords."
|
||||
);
|
||||
});
|
||||
|
||||
engine_list_test(async function test_rename_engines(tree) {
|
||||
// Test editing name of user search engine because
|
||||
// only the names of user engines can be edited.
|
||||
let userEngineIndex = installedEngines.length - 1;
|
||||
let rect = tree.getCoordsForCellItem(
|
||||
userEngineIndex,
|
||||
tree.columns.getNamedColumn("engineName"),
|
||||
"text"
|
||||
);
|
||||
let x = rect.x + rect.width / 2;
|
||||
let y = rect.y + rect.height / 2;
|
||||
let win = tree.ownerGlobal;
|
||||
|
||||
let promise = BrowserTestUtils.waitForEvent(tree, "dblclick");
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 1 }, win);
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 2 }, win);
|
||||
await promise;
|
||||
|
||||
EventUtils.sendString("User Engine 2");
|
||||
promise = SearchTestUtils.promiseSearchNotification(
|
||||
SearchUtils.MODIFIED_TYPE.CHANGED,
|
||||
SearchUtils.TOPIC_ENGINE_MODIFIED
|
||||
);
|
||||
EventUtils.sendKey("RETURN");
|
||||
await promise;
|
||||
|
||||
Assert.equal(userEngine.name, "User Engine 2", "Engine was renamed.");
|
||||
|
||||
// Avoid duplicated engine names.
|
||||
let alertSpy = sinon.spy(win, "alert");
|
||||
tree.view.setCellText(
|
||||
userEngineIndex,
|
||||
tree.columns.getNamedColumn("engineName"),
|
||||
"Extension Engine"
|
||||
);
|
||||
await TestUtils.waitForTick();
|
||||
|
||||
Assert.ok(alertSpy.calledOnce, "Warning was shown.");
|
||||
Assert.equal(
|
||||
getCellText(tree, userEngineIndex, "engineName"),
|
||||
"User Engine 2",
|
||||
"Name was not modified."
|
||||
);
|
||||
|
||||
alertSpy.restore();
|
||||
});
|
||||
|
||||
engine_list_test(async function test_remove_button_disabled_state(tree, doc) {
|
||||
let appProvidedEngines = await Services.search.getAppProvidedEngines();
|
||||
let win = tree.ownerGlobal;
|
||||
for (let i = 0; i < appProvidedEngines.length; i++) {
|
||||
let engine = appProvidedEngines[i];
|
||||
let isDefaultSearchEngine =
|
||||
engine.id == Services.search.defaultEngine.id ||
|
||||
engine.id == Services.search.defaultPrivateEngine.id;
|
||||
|
||||
let rect = tree.getCoordsForCellItem(
|
||||
i,
|
||||
tree.columns.getNamedColumn("engineName"),
|
||||
"text"
|
||||
);
|
||||
let x = rect.x + rect.width / 2;
|
||||
let y = rect.y + rect.height / 2;
|
||||
|
||||
let promise = BrowserTestUtils.waitForEvent(tree, "click");
|
||||
EventUtils.synthesizeMouse(tree.body, x, y, { clickCount: 1 }, win);
|
||||
await promise;
|
||||
|
||||
Assert.equal(
|
||||
doc.querySelector("#removeEngineButton").disabled,
|
||||
isDefaultSearchEngine,
|
||||
"Remove button is in correct disabled state."
|
||||
);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* This file contains tests for the "Add Engine" subdialog.
|
||||
*/
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.update2.engineAliasRefresh", true]],
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test for searching for the "Add Engine" subdialog.
|
||||
*/
|
||||
add_task(async function searchAddEngine() {
|
||||
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
await evaluateSearchResults("Add Engine", "oneClickSearchProvidersGroup");
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
});
|
|
@ -0,0 +1,424 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { SearchTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/SearchTestUtils.sys.mjs"
|
||||
);
|
||||
const { SearchUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/SearchUtils.sys.mjs"
|
||||
);
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.update2.engineAliasRefresh", true]],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_addEngine() {
|
||||
await openPreferencesViaOpenPreferencesAPI("search", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
|
||||
// Add new engine via add engine dialog.
|
||||
let doc = gBrowser.contentDocument;
|
||||
let addButton = doc.querySelector("#addEngineButton");
|
||||
let dialogWin = await openDialogWith(doc, () => addButton.click());
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("titleContainer").style.display,
|
||||
"none",
|
||||
"Adjustable title is hidden in add engine dialog."
|
||||
);
|
||||
|
||||
setName("Bugzilla", dialogWin);
|
||||
setUrl(
|
||||
"https://bugzilla.mozilla.org/buglist.cgi?quicksearch=%s&list_id=17442621",
|
||||
dialogWin
|
||||
);
|
||||
await setAlias("bz", dialogWin);
|
||||
|
||||
let promiseAdded = SearchTestUtils.promiseSearchNotification(
|
||||
SearchUtils.MODIFIED_TYPE.ADDED,
|
||||
SearchUtils.TOPIC_ENGINE_MODIFIED
|
||||
);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
|
||||
await promiseAdded;
|
||||
Assert.ok(true, "Got added notification.");
|
||||
|
||||
// Check new engine.
|
||||
let engine = Services.search.getEngineByName("Bugzilla");
|
||||
Assert.equal(engine.name, "Bugzilla", "Name is correct.");
|
||||
Assert.equal(
|
||||
engine.getSubmission("föö").uri.spec,
|
||||
"https://bugzilla.mozilla.org/buglist.cgi?quicksearch=f%C3%B6%C3%B6&list_id=17442621",
|
||||
"URL is correct and encodes search terms using utf-8."
|
||||
);
|
||||
Assert.equal(engine.alias, "bz", "Alias is correct.");
|
||||
|
||||
// Clean up.
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await Services.search.removeEngine(engine);
|
||||
});
|
||||
|
||||
add_task(async function test_validation() {
|
||||
await openPreferencesViaOpenPreferencesAPI("search", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
let existingEngine = await Services.search.addUserEngine({
|
||||
name: "user",
|
||||
url: "https://example.com/user?q={searchTerms}&b=ff",
|
||||
alias: "u",
|
||||
});
|
||||
|
||||
let doc = gBrowser.contentDocument;
|
||||
let addButton = doc.querySelector("#addEngineButton");
|
||||
let dialogWin = await openDialogWith(doc, () => addButton.click());
|
||||
|
||||
let button = dialogWin.document
|
||||
.querySelector("dialog")
|
||||
.shadowRoot.querySelector('button[dlgtype="accept"]');
|
||||
let name = dialogWin.document.getElementById("engineName");
|
||||
let url = dialogWin.document.getElementById("engineUrl");
|
||||
let alias = dialogWin.document.getElementById("engineAlias");
|
||||
|
||||
Assert.ok(
|
||||
name.value == "" && url.value == "" && alias.value == "",
|
||||
"Everything is empty initially."
|
||||
);
|
||||
Assert.ok(button.disabled, "Button is disabled initially.");
|
||||
|
||||
setName("Example", dialogWin);
|
||||
setUrl("https://example.com/search?q=%s", dialogWin);
|
||||
await setAlias("abc", dialogWin);
|
||||
Assert.ok(!button.disabled, "Button is enabled when everything is there.");
|
||||
|
||||
// Check URL
|
||||
setUrl("", dialogWin);
|
||||
Assert.ok(button.disabled, "URL is required.");
|
||||
setUrl("javascript://%s", dialogWin);
|
||||
Assert.ok(button.disabled, "Javascript URLs are not allowed.");
|
||||
setUrl("https://example.com/search?q=%s", dialogWin);
|
||||
Assert.ok(!button.disabled, "Good URL enables the button.");
|
||||
|
||||
// Check name
|
||||
setName("", dialogWin);
|
||||
Assert.ok(button.disabled, "Name is required.");
|
||||
setName(existingEngine.name, dialogWin);
|
||||
Assert.ok(button.disabled, "Existing name is not allowed.");
|
||||
setName("Example", dialogWin);
|
||||
Assert.ok(!button.disabled, "Good name enables the button.");
|
||||
|
||||
// Check alias
|
||||
await setAlias("", dialogWin);
|
||||
Assert.ok(!button.disabled, "Alias is not required.");
|
||||
await setAlias(existingEngine.alias, dialogWin);
|
||||
Assert.ok(button.disabled, "Existing alias is not allowed.");
|
||||
await setAlias("abc", dialogWin);
|
||||
Assert.ok(!button.disabled, "Good alias enables the button.");
|
||||
|
||||
// Clean up.
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await Services.search.removeEngine(existingEngine);
|
||||
});
|
||||
|
||||
add_task(async function test_editEngine() {
|
||||
await openPreferencesViaOpenPreferencesAPI("search", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
|
||||
let doc = gBrowser.contentDocument;
|
||||
let tree = doc.querySelector("#engineList");
|
||||
let view = tree.view.wrappedJSObject;
|
||||
let engine = await Services.search.addUserEngine({
|
||||
name: "user",
|
||||
url: "https://example.com/user?q={searchTerms}&b=ff",
|
||||
alias: "u",
|
||||
});
|
||||
|
||||
// Check buttons of all search engines + local shortcuts.
|
||||
let removeButton = doc.querySelector("#removeEngineButton");
|
||||
let editButton = doc.querySelector("#editEngineButton");
|
||||
|
||||
let userEngineIndex = null;
|
||||
for (let i = 0; i < tree.view.rowCount; i++) {
|
||||
view.selection.select(i);
|
||||
let selectedEngine = view.selectedEngine;
|
||||
if (selectedEngine?.isUserEngine) {
|
||||
Assert.equal(selectedEngine.name, "user", "Is the new engine.");
|
||||
Assert.ok(!removeButton.disabled, "Remove button is enabled.");
|
||||
Assert.ok(!editButton.disabled, "Edit button is enabled.");
|
||||
userEngineIndex = i;
|
||||
} else {
|
||||
Assert.ok(editButton.disabled, "Edit button is disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if table contains new engine without reloading.
|
||||
Assert.ok(!!userEngineIndex, "User engine is in the table.");
|
||||
view.selection.select(userEngineIndex);
|
||||
|
||||
// Open the dialog.
|
||||
let dialogWin = await openDialogWith(doc, () => editButton.click());
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("titleContainer").style.display,
|
||||
"none",
|
||||
"Adjustable title is hidden in edit engine dialog."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineName").value,
|
||||
"user",
|
||||
"Name in dialog is correct."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineUrl").value,
|
||||
"https://example.com/user?q=%s&b=ff",
|
||||
"URL in dialog is correct."
|
||||
);
|
||||
Assert.ok(
|
||||
dialogWin.document.getElementById("enginePostDataRow").hidden,
|
||||
"Post data input is hidden."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("suggestUrl").value,
|
||||
"",
|
||||
"Suggest URL in dialog is empty."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineAlias").value,
|
||||
"u",
|
||||
"Alias in dialog is correct."
|
||||
);
|
||||
|
||||
// Set new values.
|
||||
setName("Searchfox", dialogWin);
|
||||
setUrl("https://searchfox.org/mozilla-central/search?q=%s", dialogWin);
|
||||
await setAlias("sf", dialogWin);
|
||||
|
||||
// Save changes to engine.
|
||||
let promiseChanged = SearchTestUtils.promiseSearchNotification(
|
||||
SearchUtils.MODIFIED_TYPE.CHANGED,
|
||||
SearchUtils.TOPIC_ENGINE_MODIFIED,
|
||||
3
|
||||
);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
|
||||
await promiseChanged;
|
||||
Assert.ok(true, "Got 3 change notifications.");
|
||||
|
||||
// Open dialog again and check values.
|
||||
dialogWin = await openDialogWith(doc, () => editButton.click());
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineName").value,
|
||||
"Searchfox",
|
||||
"Name in dialog reflects change."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineUrl").value,
|
||||
"https://searchfox.org/mozilla-central/search?q=%s",
|
||||
"URL in dialog reflects change."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineAlias").value,
|
||||
"sf",
|
||||
"Alias in dialog reflects change."
|
||||
);
|
||||
|
||||
// Clean up.
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await Services.search.removeEngine(engine);
|
||||
});
|
||||
|
||||
add_task(async function test_editPostEngine() {
|
||||
await openPreferencesViaOpenPreferencesAPI("search", {
|
||||
leaveOpen: true,
|
||||
});
|
||||
|
||||
let doc = gBrowser.contentDocument;
|
||||
let tree = doc.querySelector("#engineList");
|
||||
let view = tree.view.wrappedJSObject;
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("q", "{searchTerms}");
|
||||
let engine = await Services.search.addUserEngine({
|
||||
name: "user post",
|
||||
url: "https://example.com/user",
|
||||
formData,
|
||||
method: "POST",
|
||||
alias: "u",
|
||||
});
|
||||
|
||||
let editButton = doc.querySelector("#editEngineButton");
|
||||
|
||||
for (let i = 0; i < tree.view.rowCount; i++) {
|
||||
view.selection.select(i);
|
||||
let selectedEngine = view.selectedEngine;
|
||||
if (selectedEngine?.isUserEngine) {
|
||||
view.selection.select(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the dialog.
|
||||
let dialogWin = await openDialogWith(doc, () => editButton.click());
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineName").value,
|
||||
"user post",
|
||||
"Name in dialog is correct."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineUrl").value,
|
||||
"https://example.com/user",
|
||||
"URL in dialog is correct."
|
||||
);
|
||||
Assert.ok(
|
||||
!dialogWin.document.getElementById("enginePostDataRow").hidden,
|
||||
"Post data input is visible."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("enginePostData").value,
|
||||
"q=%s",
|
||||
"Post data in dialog is correct."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("suggestUrl").value,
|
||||
"",
|
||||
"Suggest URL in dialog is empty."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineAlias").value,
|
||||
"u",
|
||||
"Alias in dialog is correct."
|
||||
);
|
||||
|
||||
// Set new values.
|
||||
setName("Searchfox", dialogWin);
|
||||
setUrl("https://searchfox.org/mozilla-central/search", dialogWin);
|
||||
setPostData("q=%s&path=&case=false®exp=false", dialogWin);
|
||||
setSuggestUrl("https://searchfox.org/suggest/%s", dialogWin);
|
||||
await setAlias("sf", dialogWin);
|
||||
|
||||
// Save changes to engine.
|
||||
let promiseChanged = SearchTestUtils.promiseSearchNotification(
|
||||
SearchUtils.MODIFIED_TYPE.CHANGED,
|
||||
SearchUtils.TOPIC_ENGINE_MODIFIED,
|
||||
3
|
||||
);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
|
||||
await promiseChanged;
|
||||
Assert.ok(true, "Got 3 change notifications.");
|
||||
|
||||
// Open dialog again and check values.
|
||||
dialogWin = await openDialogWith(doc, () => editButton.click());
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineName").value,
|
||||
"Searchfox",
|
||||
"Name in dialog reflects change."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineUrl").value,
|
||||
"https://searchfox.org/mozilla-central/search",
|
||||
"URL in dialog reflects changes."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("enginePostData").value,
|
||||
"q=%s&path=&case=false®exp=false",
|
||||
"Post data in dialog reflects changes."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("suggestUrl").value,
|
||||
"https://searchfox.org/suggest/%s",
|
||||
"Suggest URL in dialog reflects changes."
|
||||
);
|
||||
Assert.equal(
|
||||
dialogWin.document.getElementById("engineAlias").value,
|
||||
"sf",
|
||||
"Alias in dialog reflects changes."
|
||||
);
|
||||
|
||||
// Check values of search engine object.
|
||||
Assert.equal(
|
||||
engine.name,
|
||||
"Searchfox",
|
||||
"Name of search engine object was updated."
|
||||
);
|
||||
let submission = engine.getSubmission("foo");
|
||||
Assert.equal(
|
||||
submission.uri.spec,
|
||||
"https://searchfox.org/mozilla-central/search",
|
||||
"Submission URL reflects changes."
|
||||
);
|
||||
Assert.equal(
|
||||
decodePostData(submission.postData),
|
||||
"q=foo&path=&case=false®exp=false",
|
||||
"Submission post data reflects changes"
|
||||
);
|
||||
submission = engine.getSubmission("foo", SearchUtils.URL_TYPE.SUGGEST_JSON);
|
||||
Assert.equal(
|
||||
submission.uri.spec,
|
||||
"https://searchfox.org/suggest/foo",
|
||||
"Submission URL reflects changes."
|
||||
);
|
||||
Assert.equal(submission.postData, null, "Submission URL is still GET.");
|
||||
Assert.equal(
|
||||
engine.alias,
|
||||
"sf",
|
||||
"Alias of search engine object was updated."
|
||||
);
|
||||
|
||||
// Clean up.
|
||||
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||
await Services.search.removeEngine(engine);
|
||||
});
|
||||
|
||||
async function openDialogWith(doc, fn) {
|
||||
let dialogLoaded = TestUtils.topicObserved("subdialog-loaded");
|
||||
await fn();
|
||||
let [dialogWin] = await dialogLoaded;
|
||||
await doc.ownerGlobal.gSubDialog.dialogs[0]._dialogReady;
|
||||
Assert.ok(true, "Engine dialog opened.");
|
||||
return dialogWin;
|
||||
}
|
||||
|
||||
function setName(value, win) {
|
||||
fillTextField("engineName", value, win);
|
||||
}
|
||||
|
||||
function setUrl(value, win) {
|
||||
fillTextField("engineUrl", value, win);
|
||||
}
|
||||
|
||||
function setPostData(value, win) {
|
||||
fillTextField("enginePostData", value, win);
|
||||
}
|
||||
|
||||
function setSuggestUrl(value, win) {
|
||||
fillTextField("suggestUrl", value, win);
|
||||
}
|
||||
|
||||
async function setAlias(value, win) {
|
||||
fillTextField("engineAlias", value, win);
|
||||
await TestUtils.waitForTick();
|
||||
}
|
||||
|
||||
function fillTextField(id, text, win) {
|
||||
let elt = win.document.getElementById(id);
|
||||
elt.focus();
|
||||
elt.select();
|
||||
EventUtils.synthesizeKey("a", { metaKey: true }, win);
|
||||
EventUtils.synthesizeKey("KEY_Backspace", {}, win);
|
||||
|
||||
for (let c of text.split("")) {
|
||||
EventUtils.synthesizeKey(c, {}, win);
|
||||
}
|
||||
}
|
||||
|
||||
function decodePostData(postData) {
|
||||
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
|
||||
Ci.nsIBinaryInputStream
|
||||
);
|
||||
binaryStream.setInputStream(postData.data);
|
||||
|
||||
return binaryStream
|
||||
.readBytes(binaryStream.available())
|
||||
.replace("searchTerms", "%s");
|
||||
}
|
|
@ -24,7 +24,7 @@ const PROFILE_THEMES_MAP = new Map([
|
|||
[
|
||||
"{b90acfd0-f0fc-4add-9195-f6306d25cdfa}",
|
||||
{
|
||||
dataL10nId: "profiles-marigold-theme",
|
||||
dataL10nId: "profiles-marigold-theme-2",
|
||||
downloadURL:
|
||||
"https://addons.mozilla.org/firefox/downloads/file/4381985/marigold-1.9.xpi",
|
||||
colors: {
|
||||
|
@ -38,7 +38,7 @@ const PROFILE_THEMES_MAP = new Map([
|
|||
[
|
||||
"{388d9fae-8a28-4f9f-9aad-fb9e84e4f3c3}",
|
||||
{
|
||||
dataL10nId: "profiles-lichen-theme",
|
||||
dataL10nId: "profiles-lichen-theme-2",
|
||||
downloadURL:
|
||||
"https://addons.mozilla.org/firefox/downloads/file/4381979/lichen_soft-1.3.xpi",
|
||||
colors: {
|
||||
|
@ -52,7 +52,7 @@ const PROFILE_THEMES_MAP = new Map([
|
|||
[
|
||||
"{3ac3b0d7-f017-40e1-b142-a26f794e7015}",
|
||||
{
|
||||
dataL10nId: "profiles-magnolia-theme",
|
||||
dataL10nId: "profiles-magnolia-theme-2",
|
||||
downloadURL:
|
||||
"https://addons.mozilla.org/firefox/downloads/file/4381978/magnolia-1.1.xpi",
|
||||
colors: {
|
||||
|
@ -66,7 +66,7 @@ const PROFILE_THEMES_MAP = new Map([
|
|||
[
|
||||
"{ba48d251-0732-45c2-9f2f-39c68e82d047}",
|
||||
{
|
||||
dataL10nId: "profiles-lavender-theme",
|
||||
dataL10nId: "profiles-lavender-theme-2",
|
||||
downloadURL:
|
||||
"https://addons.mozilla.org/firefox/downloads/file/4381983/lavender_soft-1.2.xpi",
|
||||
colors: {
|
||||
|
@ -93,7 +93,7 @@ const PROFILE_THEMES_MAP = new Map([
|
|||
[
|
||||
"{750fa518-b61f-4068-9974-330dcf45442f}",
|
||||
{
|
||||
dataL10nId: "profiles-ocean-theme",
|
||||
dataL10nId: "profiles-ocean-theme-2",
|
||||
downloadURL:
|
||||
"https://addons.mozilla.org/firefox/downloads/file/4381977/ocean_dark-1.1.xpi",
|
||||
colors: {
|
||||
|
@ -107,7 +107,7 @@ const PROFILE_THEMES_MAP = new Map([
|
|||
[
|
||||
"{25b5a343-4238-4bae-b1f9-93a33f258167}",
|
||||
{
|
||||
dataL10nId: "profiles-terracotta-theme",
|
||||
dataL10nId: "profiles-terracotta-theme-2",
|
||||
downloadURL:
|
||||
"https://addons.mozilla.org/firefox/downloads/file/4381976/terracotta_dark-1.1.xpi",
|
||||
colors: {
|
||||
|
@ -121,7 +121,7 @@ const PROFILE_THEMES_MAP = new Map([
|
|||
[
|
||||
"{f9261f02-c03c-4352-92ee-78dd8b41ca98}",
|
||||
{
|
||||
dataL10nId: "profiles-moss-theme",
|
||||
dataL10nId: "profiles-moss-theme-2",
|
||||
downloadURL:
|
||||
"https://addons.mozilla.org/firefox/downloads/file/4381975/moss_dark-1.1.xpi",
|
||||
colors: {
|
||||
|
|
|
@ -113,6 +113,7 @@ class SelectableProfileServiceClass extends EventEmitter {
|
|||
"browser.crashReports.unsubmittedCheck.autoSubmit2",
|
||||
"browser.discovery.enabled",
|
||||
"browser.urlbar.quicksuggest.dataCollection.enabled",
|
||||
"datareporting.healthreport.uploadEnabled",
|
||||
"datareporting.policy.currentPolicyVersion",
|
||||
"datareporting.policy.dataSubmissionEnabled",
|
||||
"datareporting.policy.dataSubmissionPolicyAcceptedVersion",
|
||||
|
|
|
@ -140,7 +140,6 @@ export class EditProfileCard extends MozLitElement {
|
|||
this.themes = themes;
|
||||
|
||||
this.setFavicon();
|
||||
this.focusInput();
|
||||
}
|
||||
|
||||
async getUpdateComplete() {
|
||||
|
@ -155,13 +154,6 @@ export class EditProfileCard extends MozLitElement {
|
|||
return result;
|
||||
}
|
||||
|
||||
async focusInput() {
|
||||
await this.getUpdateComplete();
|
||||
this.nameInput.focus();
|
||||
this.nameInput.value = "";
|
||||
this.nameInput.value = this.profile.name;
|
||||
}
|
||||
|
||||
setFavicon() {
|
||||
let favicon = document.getElementById("favicon");
|
||||
favicon.href = `chrome://browser/content/profiles/assets/16_${this.profile.avatar}.svg`;
|
||||
|
@ -295,7 +287,10 @@ export class EditProfileCard extends MozLitElement {
|
|||
}
|
||||
|
||||
headerTemplate() {
|
||||
return html`<h1 data-l10n-id="edit-profile-page-header"></h1>`;
|
||||
return html`<h1
|
||||
id="profile-header"
|
||||
data-l10n-id="edit-profile-page-header"
|
||||
></h1>`;
|
||||
}
|
||||
|
||||
nameInputTemplate() {
|
||||
|
@ -449,7 +444,7 @@ export class EditProfileCard extends MozLitElement {
|
|||
href="chrome://global/skin/in-content/common.css"
|
||||
/>
|
||||
<moz-card
|
||||
><div id="edit-profile-card">
|
||||
><div id="edit-profile-card" aria-labelledby="profile-header">
|
||||
<img
|
||||
id="header-avatar"
|
||||
data-l10n-id=${this.profile.avatarL10nId}
|
||||
|
|
|
@ -55,7 +55,6 @@ export class NewProfileCard extends EditProfileCard {
|
|||
}
|
||||
|
||||
async setInitialInput() {
|
||||
await super.focusInput();
|
||||
if (RPMGetBoolPref("browser.profiles.profile-name.updated", false)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ export class ProfilesThemeCard extends MozLitElement {
|
|||
<moz-card class="theme-card">
|
||||
<div class="theme-content">
|
||||
<div class="img-holder">
|
||||
<img />
|
||||
<img alt="" />
|
||||
</div>
|
||||
<div
|
||||
class="theme-name"
|
||||
|
|
|
@ -123,12 +123,6 @@ add_task(async function test_create_profile_name() {
|
|||
|
||||
await newProfileCard.getUpdateComplete();
|
||||
|
||||
Assert.equal(
|
||||
Services.focus.focusedElement.id,
|
||||
newProfileCard.nameInput.id,
|
||||
"Name input is focused"
|
||||
);
|
||||
|
||||
let nameInput = newProfileCard.nameInput;
|
||||
Assert.equal(nameInput.value, "", "Profile name is empty to start");
|
||||
|
||||
|
|
|
@ -106,12 +106,6 @@ add_task(async function test_edit_profile_name() {
|
|||
|
||||
await editProfileCard.getUpdateComplete();
|
||||
|
||||
Assert.equal(
|
||||
Services.focus.focusedElement.id,
|
||||
editProfileCard.nameInput.id,
|
||||
"Name input is focused"
|
||||
);
|
||||
|
||||
let nameInput = editProfileCard.nameInput;
|
||||
nameInput.value = newProfileName;
|
||||
nameInput.dispatchEvent(new content.Event("input"));
|
||||
|
|
|
@ -10,10 +10,7 @@ hbox {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
#engineNameLabel,
|
||||
#engineUrlLabel,
|
||||
#suggestUrlLabel,
|
||||
#engineAliasLabel {
|
||||
.dialogRow > label {
|
||||
/* Align the labels with the inputs */
|
||||
margin-inline-start: 4px;
|
||||
}
|
||||
|
@ -25,7 +22,7 @@ hbox {
|
|||
text-align: match-parent;
|
||||
}
|
||||
|
||||
.dialogRow {
|
||||
.dialogRow:not([hidden]) {
|
||||
--grid-padding: 16px;
|
||||
margin-block: 0 var(--grid-padding);
|
||||
display: flex;
|
||||
|
|
|
@ -4,112 +4,301 @@
|
|||
|
||||
/* globals AdjustableTitle */
|
||||
|
||||
let gAddEngineDialog = {
|
||||
_form: null,
|
||||
_name: null,
|
||||
_alias: null,
|
||||
loadedResolvers: Promise.withResolvers(),
|
||||
// This is the dialog that is displayed when adding or editing a search engine
|
||||
// in about:preferences, or when adding a search engine via the context menu of
|
||||
// an HTML form. Depending on the scenario where it is used, different arguments
|
||||
// must be supplied in an object in `window.arguments[0]`:
|
||||
// - `mode` [required] - The type of dialog: NEW, EDIT or FORM.
|
||||
// - `title` [optional] - To display a title in the window element.
|
||||
// - all arguments required by the constructor of the dialog class
|
||||
|
||||
onLoad() {
|
||||
try {
|
||||
this.init();
|
||||
} finally {
|
||||
this.loadedResolvers.resolve();
|
||||
}
|
||||
},
|
||||
const lazy = {};
|
||||
|
||||
init() {
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
|
||||
});
|
||||
|
||||
// Set the appropriate l10n id before the dialog's connectedCallback.
|
||||
let mode = window.arguments[0].mode == "EDIT" ? "edit" : "add";
|
||||
document.l10n.setAttributes(
|
||||
document.querySelector("dialog"),
|
||||
mode + "-engine-dialog"
|
||||
);
|
||||
document.l10n.setAttributes(
|
||||
document.querySelector("window"),
|
||||
mode + "-engine-window"
|
||||
);
|
||||
|
||||
/**
|
||||
* The abstract base class for all types of user search engine dialogs.
|
||||
* All subclasses must implement the abstract method `onAddEngine`.
|
||||
*/
|
||||
class EngineDialog {
|
||||
constructor() {
|
||||
this._dialog = document.querySelector("dialog");
|
||||
this._form = document.getElementById("addEngineForm");
|
||||
this._name = document.getElementById("engineName");
|
||||
this._alias = document.getElementById("engineAlias");
|
||||
|
||||
// These arguments only exist if this dialog was opened via
|
||||
// "Add Search Engine" in the context menu.
|
||||
if (window.arguments?.[0]) {
|
||||
let { uri, formData, charset, method, icon } = window.arguments[0];
|
||||
this._formData = formData;
|
||||
this._charset = charset;
|
||||
this._method = method;
|
||||
this._icon = icon;
|
||||
this._uri = uri.spec;
|
||||
|
||||
this._name.value = uri.host;
|
||||
this.onFormInput();
|
||||
|
||||
document.getElementById("engineUrlRow").remove();
|
||||
document.getElementById("suggestUrlRow").remove();
|
||||
let title = { raw: document.title };
|
||||
document.documentElement.setAttribute(
|
||||
"headertitle",
|
||||
JSON.stringify(title)
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
"--icon-url",
|
||||
'url("chrome://browser/skin/preferences/category-search.svg")'
|
||||
);
|
||||
} else {
|
||||
AdjustableTitle.hide();
|
||||
}
|
||||
this._url = document.getElementById("engineUrl");
|
||||
this._suggestUrl = document.getElementById("suggestUrl");
|
||||
|
||||
this._name.addEventListener("input", this.onNameInput.bind(this));
|
||||
this._alias.addEventListener("input", this.onAliasInput.bind(this));
|
||||
this._form.addEventListener("input", this.onFormInput.bind(this));
|
||||
|
||||
this._url.addEventListener("input", this.onFormInput.bind(this));
|
||||
document.addEventListener("dialogaccept", this.onAddEngine.bind(this));
|
||||
},
|
||||
}
|
||||
|
||||
onAddEngine() {
|
||||
let url =
|
||||
this._uri ||
|
||||
document.getElementById("engineUrl").value.replace(/%s/, "{searchTerms}");
|
||||
throw new Error("abstract");
|
||||
}
|
||||
|
||||
let suggestUrl = document
|
||||
.getElementById("suggestUrl")
|
||||
?.value.replace(/%s/, "{searchTerms}");
|
||||
|
||||
Services.search.addUserEngine({
|
||||
url,
|
||||
name: this._name.value,
|
||||
alias: this._alias.value,
|
||||
// The values below may be undefined.
|
||||
formData: this._formData,
|
||||
charset: this._charset,
|
||||
method: this._method,
|
||||
icon: this._icon,
|
||||
suggestUrl,
|
||||
});
|
||||
},
|
||||
isNameValid(name) {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return !Services.search.getEngineByName(name);
|
||||
}
|
||||
|
||||
onNameInput() {
|
||||
if (this._name.value) {
|
||||
let engine = Services.search.getEngineByName(this._name.value);
|
||||
let validity = engine
|
||||
? document.getElementById("engineNameExists").textContent
|
||||
: "";
|
||||
this._name.setCustomValidity(validity);
|
||||
let name = this._name.value.trim();
|
||||
let validity = this.isNameValid(name)
|
||||
? ""
|
||||
: document.getElementById("engineNameExists").textContent;
|
||||
this._name.setCustomValidity(validity);
|
||||
this.onFormInput();
|
||||
}
|
||||
|
||||
async isAliasValid(alias) {
|
||||
if (!alias) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
return !(await Services.search.getEngineByAlias(alias));
|
||||
}
|
||||
|
||||
async onAliasInput() {
|
||||
let validity = "";
|
||||
if (this._alias.value) {
|
||||
let engine = await Services.search.getEngineByAlias(this._alias.value);
|
||||
if (engine) {
|
||||
validity = document.getElementById("engineAliasExists").textContent;
|
||||
}
|
||||
}
|
||||
let alias = this._alias.value.trim();
|
||||
let validity = (await this.isAliasValid(alias))
|
||||
? ""
|
||||
: document.getElementById("engineAliasExists").textContent;
|
||||
this._alias.setCustomValidity(validity);
|
||||
},
|
||||
this.onFormInput();
|
||||
}
|
||||
|
||||
// This function is not set as a listener but called directly because it
|
||||
// depends on the output of `isAliasValid`, but `isAliasValid` contains an
|
||||
// await, so it would finish after this function.
|
||||
onFormInput() {
|
||||
this._dialog.setAttribute(
|
||||
"buttondisabledaccept",
|
||||
!this._form.checkValidity()
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
document.mozSubdialogReady = gAddEngineDialog.loadedResolvers.promise;
|
||||
/**
|
||||
* This dialog is opened when adding a new search engine in preferences.
|
||||
*/
|
||||
class NewEngineDialog extends EngineDialog {
|
||||
onAddEngine() {
|
||||
Services.search.addUserEngine({
|
||||
name: this._name.value.trim(),
|
||||
url: this._url.value.trim().replace(/%s/, "{searchTerms}"),
|
||||
suggestUrl: this._suggestUrl.value.trim().replace(/%s/, "{searchTerms}"),
|
||||
alias: this._alias.value.trim(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => gAddEngineDialog.onLoad());
|
||||
/**
|
||||
* This dialog is opened when editing a user search engine in preferences.
|
||||
*/
|
||||
class EditEngineDialog extends EngineDialog {
|
||||
#engine;
|
||||
/**
|
||||
* Initializes the dialog with information from a user search engine.
|
||||
*
|
||||
* @param {object} args
|
||||
* The arguments.
|
||||
* @param {nsISearchEngine} args.engine
|
||||
* The search engine to edit. Must be a UserSearchEngine.
|
||||
*/
|
||||
constructor({ engine }) {
|
||||
super();
|
||||
this._postData = document.getElementById("enginePostData");
|
||||
|
||||
this.#engine = engine;
|
||||
this._name.value = engine.name;
|
||||
this._alias.value = engine.alias ?? "";
|
||||
|
||||
let [url, postData] = this.getSubmissionTemplate(
|
||||
lazy.SearchUtils.URL_TYPE.SEARCH
|
||||
);
|
||||
this._url.value = url;
|
||||
if (postData) {
|
||||
document.getElementById("enginePostDataRow").hidden = false;
|
||||
this._postData.value = postData;
|
||||
}
|
||||
|
||||
let [suggestUrl] = this.getSubmissionTemplate(
|
||||
lazy.SearchUtils.URL_TYPE.SUGGEST_JSON
|
||||
);
|
||||
this._suggestUrl.value = suggestUrl ?? "";
|
||||
|
||||
this.onNameInput();
|
||||
this.onAliasInput();
|
||||
}
|
||||
|
||||
onAddEngine() {
|
||||
this.#engine.wrappedJSObject.rename(this._name.value.trim());
|
||||
this.#engine.alias = this._alias.value.trim();
|
||||
|
||||
let newURL = this._url.value.trim();
|
||||
let newPostData = this._postData.value.trim();
|
||||
|
||||
// UserSearchEngine.changeUrl() does not check whether the URL has actually changed.
|
||||
let [prevURL, prevPostData] = this.getSubmissionTemplate(
|
||||
lazy.SearchUtils.URL_TYPE.SEARCH
|
||||
);
|
||||
if (newURL != prevURL || (prevPostData && prevPostData != newPostData)) {
|
||||
this.#engine.wrappedJSObject.changeUrl(
|
||||
lazy.SearchUtils.URL_TYPE.SEARCH,
|
||||
newURL.replace(/%s/, "{searchTerms}"),
|
||||
prevPostData ? newPostData.replace(/%s/, "{searchTerms}") : null
|
||||
);
|
||||
}
|
||||
|
||||
let newSuggestURL = this._suggestUrl.value.trim() || null;
|
||||
let [prevSuggestUrl] = this.getSubmissionTemplate(
|
||||
lazy.SearchUtils.URL_TYPE.SUGGEST_JSON
|
||||
);
|
||||
if (newSuggestURL != prevSuggestUrl) {
|
||||
this.#engine.wrappedJSObject.changeUrl(
|
||||
lazy.SearchUtils.URL_TYPE.SUGGEST_JSON,
|
||||
newSuggestURL.replace(/%s/, "{searchTerms}"),
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
isNameValid(name) {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
let engine = Services.search.getEngineByName(this._name.value);
|
||||
return !engine || engine.id == this.#engine.id;
|
||||
}
|
||||
|
||||
async isAliasValid(alias) {
|
||||
if (!alias) {
|
||||
return true;
|
||||
}
|
||||
let engine = await Services.search.getEngineByAlias(this._alias.value);
|
||||
return !engine || engine.id == this.#engine.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns url and post data templates of the requested type.
|
||||
* Both contain %s in place of the search terms.
|
||||
*
|
||||
* If no url of the requested type exists, both are null.
|
||||
* If the url is a GET url, the post data is null.
|
||||
*
|
||||
* @param {string} urlType
|
||||
* The `SearchUtils.URL_TYPE`.
|
||||
* @returns {[?string, ?string]}
|
||||
* Array of the url and post data.
|
||||
*/
|
||||
getSubmissionTemplate(urlType) {
|
||||
let submission = this.#engine.getSubmission("searchTerms", urlType);
|
||||
if (!submission) {
|
||||
return [null, null];
|
||||
}
|
||||
let postData = null;
|
||||
if (submission.postData) {
|
||||
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
|
||||
Ci.nsIBinaryInputStream
|
||||
);
|
||||
binaryStream.setInputStream(submission.postData.data);
|
||||
|
||||
postData = binaryStream
|
||||
.readBytes(binaryStream.available())
|
||||
.replace("searchTerms", "%s");
|
||||
}
|
||||
let url = submission.uri.spec.replace("searchTerms", "%s");
|
||||
return [url, postData];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This dialog is opened via the context menu of an input and lets the
|
||||
* user choose a name and an alias for an engine. Unlike the other two
|
||||
* dialogs, it does not add or change an engine in the search service,
|
||||
* and instead returns the user input to the caller.
|
||||
*
|
||||
* The chosen name and alias are returned via `window.arguments[0].engineInfo`.
|
||||
* If the user chooses to not save the engine, it's undefined.
|
||||
*/
|
||||
class NewEngineFromFormDialog extends EngineDialog {
|
||||
/**
|
||||
* Initializes the dialog.
|
||||
*
|
||||
* @param {object} args
|
||||
* The arguments.
|
||||
* @param {string} args.nameTemplate
|
||||
* The initial value of the name input.
|
||||
*/
|
||||
constructor({ nameTemplate }) {
|
||||
super();
|
||||
this._name.value = nameTemplate;
|
||||
this.onNameInput();
|
||||
this.onAliasInput();
|
||||
|
||||
document.getElementById("engineUrlRow").remove();
|
||||
this._url = null;
|
||||
document.getElementById("suggestUrlRow").remove();
|
||||
this._suggestUrl = null;
|
||||
|
||||
let title = { raw: document.title };
|
||||
document.documentElement.setAttribute("headertitle", JSON.stringify(title));
|
||||
document.documentElement.style.setProperty(
|
||||
"--icon-url",
|
||||
'url("chrome://browser/skin/preferences/category-search.svg")'
|
||||
);
|
||||
}
|
||||
|
||||
onAddEngine() {
|
||||
window.arguments[0].engineInfo = {
|
||||
name: this._name.value.trim(),
|
||||
// Empty string means no alias.
|
||||
alias: this._alias.value.trim(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let loadedResolvers = Promise.withResolvers();
|
||||
document.mozSubdialogReady = loadedResolvers.promise;
|
||||
|
||||
let gAddEngineDialog = null;
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
if (!window.arguments[0].title) {
|
||||
AdjustableTitle.hide();
|
||||
}
|
||||
|
||||
try {
|
||||
switch (window.arguments[0].mode) {
|
||||
case "NEW":
|
||||
gAddEngineDialog = new NewEngineDialog();
|
||||
break;
|
||||
case "EDIT":
|
||||
gAddEngineDialog = new EditEngineDialog(window.arguments[0]);
|
||||
break;
|
||||
case "FORM":
|
||||
gAddEngineDialog = new NewEngineFromFormDialog(window.arguments[0]);
|
||||
break;
|
||||
default:
|
||||
throw new Error("Mode not supported for addEngine dialog.");
|
||||
}
|
||||
} finally {
|
||||
loadedResolvers.resolve();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
<window
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
data-l10n-id="add-engine-window"
|
||||
data-l10n-attrs="title, style"
|
||||
headerparent="add-engine-dialog"
|
||||
neediconheader="true"
|
||||
|
@ -18,7 +17,6 @@
|
|||
id="add-engine-dialog"
|
||||
buttons="accept,cancel"
|
||||
buttondisabledaccept="true"
|
||||
data-l10n-id="add-engine-dialog"
|
||||
data-l10n-attrs="buttonlabelaccept, buttonaccesskeyaccept"
|
||||
>
|
||||
<linkset>
|
||||
|
@ -32,8 +30,8 @@
|
|||
<html:link rel="localization" href="browser/search.ftl" />
|
||||
</linkset>
|
||||
|
||||
<script src="chrome://browser/content/search/addEngine.js" />
|
||||
<script src="chrome://global/content/adjustableTitle.js" />
|
||||
<script src="chrome://browser/content/search/addEngine.js" />
|
||||
|
||||
<separator class="thin" />
|
||||
|
||||
|
@ -65,7 +63,7 @@
|
|||
id="engineUrl"
|
||||
type="url"
|
||||
required="required"
|
||||
pattern="https?:.*%s.*"
|
||||
pattern="https?:.*"
|
||||
/>
|
||||
</hbox>
|
||||
</html:div>
|
||||
|
@ -77,7 +75,18 @@
|
|||
data-l10n-id="add-engine-suggest-url"
|
||||
/>
|
||||
<hbox>
|
||||
<html:input id="suggestUrl" type="url" pattern="https?:.*%s.*" />
|
||||
<html:input id="suggestUrl" type="url" pattern="https?:.*" />
|
||||
</hbox>
|
||||
</html:div>
|
||||
|
||||
<html:div class="dialogRow" id="enginePostDataRow" hidden="hidden">
|
||||
<html:label
|
||||
id="enginePostDataLabel"
|
||||
for="enginePostData"
|
||||
data-l10n-id="add-engine-post-data"
|
||||
/>
|
||||
<hbox>
|
||||
<html:input id="enginePostData" type="text" />
|
||||
</hbox>
|
||||
</html:div>
|
||||
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineLazyGetter(this, "UrlbarTestUtils", () => {
|
||||
const { UrlbarTestUtils: module } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/UrlbarTestUtils.sys.mjs"
|
||||
);
|
||||
module.init(this);
|
||||
return module;
|
||||
});
|
||||
|
||||
const TESTS = [
|
||||
{
|
||||
action: "/search",
|
||||
|
@ -95,13 +103,13 @@ async function addEngine(browser, selector, name, alias) {
|
|||
fillTextField("engineAlias", alias, dialogWin);
|
||||
|
||||
info("Saving engine.");
|
||||
let promiseAdded = SearchTestUtils.promiseSearchNotification(
|
||||
SearchUtils.MODIFIED_TYPE.ADDED,
|
||||
SearchUtils.TOPIC_ENGINE_MODIFIED
|
||||
);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
|
||||
await window.gDialogBox.dialog._closingPromise;
|
||||
await promiseAdded;
|
||||
await TestUtils.waitForCondition(
|
||||
() => gURLBar.searchMode?.engineName == name
|
||||
);
|
||||
Assert.ok(true, "Went into search mode.");
|
||||
|
||||
await UrlbarTestUtils.exitSearchMode(window);
|
||||
return Services.search.getEngineByName(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -325,12 +325,6 @@ export class ReviewCheckerChild extends RemotePageChild {
|
|||
async setLocation() {
|
||||
// check if can fetch and show data
|
||||
let url = await this.sendQuery("GetCurrentURL");
|
||||
|
||||
// Bail out if we opted out in the meantime, or don't have a URI.
|
||||
if (!this.canContinue(null, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.locationChanged({ url });
|
||||
}
|
||||
|
||||
|
@ -634,7 +628,7 @@ export class ReviewCheckerChild extends RemotePageChild {
|
|||
adsEnabledByUser: lazy.adsEnabledByUser,
|
||||
autoOpenEnabled: lazy.autoOpenEnabled,
|
||||
autoOpenEnabledByUser: lazy.autoOpenEnabledByUser,
|
||||
showOnboarding: !this.canFetchAndShowData,
|
||||
showOnboarding: false,
|
||||
data: null,
|
||||
recommendationData: null,
|
||||
focusCloseButton,
|
||||
|
|
|
@ -277,13 +277,16 @@ export class ReviewCheckerManager {
|
|||
// Check if we should auto-open to allow opting in.
|
||||
shouldAutoOpen = lazy.ShoppingUtils.handleAutoActivateOnProduct();
|
||||
|
||||
lazy.ShoppingUtils.sendTrigger({
|
||||
browser: selectedBrowser,
|
||||
id: "shoppingProductPageWithIntegratedRCSidebarClosed",
|
||||
context: {
|
||||
isReviewCheckerInSidebarClosed: !this.SidebarController?.isOpen,
|
||||
},
|
||||
});
|
||||
// Only trigger the callout if the panel is not auto-opening
|
||||
if (!shouldAutoOpen) {
|
||||
lazy.ShoppingUtils.sendTrigger({
|
||||
browser: selectedBrowser,
|
||||
id: "shoppingProductPageWithIntegratedRCSidebarClosed",
|
||||
context: {
|
||||
isReviewCheckerInSidebarClosed: !this.SidebarController?.isOpen,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Only show sidebar if no sidebar panel is currently showing,
|
||||
|
|
|
@ -41,23 +41,29 @@ export const ShoppingUtils = {
|
|||
handledAutoActivate: false,
|
||||
nimbusEnabled: false,
|
||||
nimbusControl: false,
|
||||
nimbusIntegratedSidebar: false,
|
||||
everyWindowCallbackId: `shoppingutils-${Services.uuid.generateUUID()}`,
|
||||
managers: new WeakMap(),
|
||||
|
||||
_updateNimbusVariables() {
|
||||
this.nimbusIntegratedSidebar =
|
||||
lazy.NimbusFeatures.shopping2023.getVariable("integratedSidebar");
|
||||
this.nimbusEnabled =
|
||||
this.nimbusIntegratedSidebar ||
|
||||
lazy.NimbusFeatures.shopping2023.getVariable("enabled");
|
||||
this.nimbusControl =
|
||||
lazy.NimbusFeatures.shopping2023.getVariable("control");
|
||||
},
|
||||
|
||||
onNimbusUpdate() {
|
||||
if (this.initialized) {
|
||||
ShoppingUtils.uninit();
|
||||
}
|
||||
this._updateNimbusVariables();
|
||||
if (this.nimbusEnabled) {
|
||||
ShoppingUtils.init();
|
||||
Glean.shoppingSettings.nimbusDisabledShopping.set(false);
|
||||
} else {
|
||||
ShoppingUtils.uninit();
|
||||
Glean.shoppingSettings.nimbusDisabledShopping.set(true);
|
||||
}
|
||||
},
|
||||
|
@ -71,7 +77,6 @@ export const ShoppingUtils = {
|
|||
}
|
||||
this.onNimbusUpdate = this.onNimbusUpdate.bind(this);
|
||||
this.onActiveUpdate = this.onActiveUpdate.bind(this);
|
||||
this.onIntegratedSidebarUpdate = this.onIntegratedSidebarUpdate.bind(this);
|
||||
this._addManagerForWindow = this._addManagerForWindow.bind(this);
|
||||
this._removeManagerForWindow = this._removeManagerForWindow.bind(this);
|
||||
|
||||
|
@ -95,22 +100,17 @@ export const ShoppingUtils = {
|
|||
this.recordUserAdsPreference();
|
||||
this.recordUserAutoOpenPreference();
|
||||
|
||||
if (this.isAutoOpenEligible()) {
|
||||
Services.prefs.setBoolPref(ACTIVE_PREF, true);
|
||||
if (this.nimbusIntegratedSidebar) {
|
||||
this._addReviewCheckerManagers();
|
||||
} else {
|
||||
if (this.isAutoOpenEligible()) {
|
||||
Services.prefs.setBoolPref(ACTIVE_PREF, true);
|
||||
}
|
||||
Services.prefs.addObserver(ACTIVE_PREF, this.onActiveUpdate);
|
||||
}
|
||||
Services.prefs.addObserver(ACTIVE_PREF, this.onActiveUpdate);
|
||||
|
||||
Services.prefs.addObserver(
|
||||
INTEGRATED_SIDEBAR_PREF,
|
||||
this.onIntegratedSidebarUpdate
|
||||
);
|
||||
|
||||
Services.prefs.setIntPref(SIDEBAR_CLOSED_COUNT_PREF, 0);
|
||||
|
||||
if (ShoppingUtils.integratedSidebar) {
|
||||
this._addReviewCheckerManagers();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
},
|
||||
|
||||
|
@ -127,11 +127,6 @@ export const ShoppingUtils = {
|
|||
|
||||
Services.prefs.removeObserver(ACTIVE_PREF, this.onActiveUpdate);
|
||||
|
||||
Services.prefs.removeObserver(
|
||||
INTEGRATED_SIDEBAR_PREF,
|
||||
this.onIntegratedSidebarUpdate
|
||||
);
|
||||
|
||||
if (this.managers.size) {
|
||||
this._removeReviewCheckerManagers();
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ class NewPositionNotificationCard extends MozLitElement {
|
|||
></h2>
|
||||
<p
|
||||
id="notification-card-body"
|
||||
data-l10n-id="shopping-integrated-new-position-notification-subtitle"
|
||||
data-l10n-id="shopping-integrated-new-position-notification-move-right-subtitle"
|
||||
@click=${this.handleClickSettingsLink}
|
||||
>
|
||||
<a
|
||||
|
|
|
@ -6,77 +6,4 @@
|
|||
### being translated as the feature is still in heavy development
|
||||
### and strings are likely to change often.
|
||||
|
||||
## Opt-in message strings for Review Checker when it is integrated into the global sidebar.
|
||||
|
||||
shopping-opt-in-integrated-headline = Can you trust these reviews?
|
||||
shopping-opt-in-integrated-subtitle = Turn on Review Checker in { -brand-product-name } to see how reliable product reviews are, before you buy. It uses AI technology to analyze reviews. <a data-l10n-name="learn_more">Learn more</a>
|
||||
|
||||
## Messages for callout for users not opted into the sidebar integrated version of Review Checker.
|
||||
|
||||
# Appears underneath shopping-opt-in-integrated-headline to answer the question 'Can you trust these reviews?'
|
||||
shopping-callout-not-opted-in-integrated-paragraph1 = Turn on Review Checker from { -brand-product-name } to find out. It’s powered by { -fakespot-brand-full-name } and uses AI technology to analyze reviews.
|
||||
|
||||
shopping-callout-not-opted-in-integrated-paragraph2 = By selecting “{ shopping-opt-in-integrated-button }” you agree to { -brand-product-name }’s <a data-l10n-name="privacy_policy">privacy notice</a> and { -fakespot-brand-full-name }’s <a data-l10n-name="terms_of_use">terms of use</a>.
|
||||
shopping-callout-not-opted-in-integrated-reminder-dismiss-button = Dismiss
|
||||
shopping-callout-not-opted-in-integrated-reminder-accept-button = Turn on Review Checker
|
||||
shopping-callout-not-opted-in-integrated-reminder-do-not-show = Don’t show this recommendation again
|
||||
shopping-callout-not-opted-in-integrated-reminder-show-fewer = Show fewer recommendations
|
||||
shopping-callout-not-opted-in-integrated-reminder-manage-settings = Manage settings
|
||||
shopping-opt-in-integrated-subtitle-unsupported-site = Review Checker from { -brand-product-name } helps you know how reliable a product’s reviews are, before you buy. It uses AI technology to analyze reviews. <a data-l10n-name="learn_more">Learn more</a>
|
||||
|
||||
shopping-opt-in-integrated-privacy-policy-and-terms-of-use = Review Checker is powered by { -fakespot-brand-full-name }. By selecting “{ shopping-opt-in-integrated-button }“ you agree to { -brand-product-name }’s <a data-l10n-name="privacy_policy">privacy notice</a> and { -fakespot-brand-name }’s <a data-l10n-name="terms_of_use">terms of use.</a>
|
||||
shopping-opt-in-integrated-button = Try Review Checker
|
||||
|
||||
## Message strings for Review Checker's empty states.
|
||||
|
||||
shopping-empty-state-header = Ready to check reviews
|
||||
shopping-empty-state-supported-site = View a product and { -brand-product-name } will check if the reviews are reliable.
|
||||
|
||||
# We show a list of sites supported by Review Checker whenever a user opens the feature in an unsupported site.
|
||||
# This string will be displayed above the list of sites. The list will be hardcoded and does not require localization.
|
||||
shopping-empty-state-non-supported-site = Review Checker works when you shop on:
|
||||
|
||||
## Confirm disabling Review Checker for newly opted out users
|
||||
|
||||
shopping-integrated-callout-opted-out-title = Review Checker is off
|
||||
shopping-integrated-callout-opted-out-subtitle = To turn it back on, select the price tag in the sidebar and turn on Review Checker.
|
||||
|
||||
## Callout for where to find Review Checker when the sidebar closes
|
||||
|
||||
shopping-integrated-callout-sidebar-closed-title = Get back to Review Checker
|
||||
shopping-integrated-callout-sidebar-closed-subtitle = Select the price tag in the sidebar to see if you can trust a product’s reviews.
|
||||
|
||||
## Pref confirmation callout for auto-open
|
||||
|
||||
shopping-integrated-callout-disabled-auto-open-title = Get back to Review Checker
|
||||
shopping-integrated-callout-disabled-auto-open-subtitle = Select the price tag in the sidebar to see if you can trust a product’s reviews.
|
||||
shopping-integrated-callout-no-logo-disabled-auto-open-subtitle = Select the sidebar button to see if you can trust a product’s reviews.
|
||||
|
||||
## Strings for a notification card about Review Checker's new position in the sidebar.
|
||||
## The card will only appear for users that have the default sidebar position, which is on the left side for non RTL locales.
|
||||
## Review Checker in the sidebar is only available to en-US users at this time, so we can assume that the default position is on the left side.
|
||||
|
||||
shopping-integrated-new-position-notification-title = Same Review Checker, new spot
|
||||
shopping-integrated-new-position-notification-subtitle = Keep Review Checker and the rest of the { -brand-product-name } sidebar here — or move them to the right. Switch now or anytime in <a data-l10n-name="sidebar_settings">sidebar settings</a>.
|
||||
shopping-integrated-new-position-notification-move-right-button = Move right
|
||||
shopping-integrated-new-position-notification-move-left-button = Move left
|
||||
shopping-integrated-new-position-notification-dismiss-button = Got it
|
||||
|
||||
## Combined setting for auto-open and auto-close.
|
||||
|
||||
shopping-settings-auto-open-and-close-toggle =
|
||||
.label = Automatically open and close Review Checker
|
||||
|
||||
# Description text for regions where we support three sites. Sites are limited to Amazon, Walmart and Best Buy.
|
||||
# Variables:
|
||||
# $firstSite (String) - The first shopping page name
|
||||
# $secondSite (String) - The second shopping page name
|
||||
# $thirdSite (String) - The third shopping page name
|
||||
shopping-settings-auto-open-and-close-description-three-sites = Opens when you view products on { $firstSite }, { $secondSite }, and { $thirdSite } and closes when you leave
|
||||
|
||||
# Description text for regions where we support only one site (e.g. currently used in FR/DE with Amazon).
|
||||
# Variables:
|
||||
# $currentSite (String) - The current shopping page name
|
||||
shopping-settings-auto-open-and-close-description-single-site = Opens when you view products on { $currentSite } and closes when you leave
|
||||
|
||||
##
|
||||
|
|
|
@ -21,7 +21,6 @@ prefs = [
|
|||
"toolkit.shopping.ohttpRelayURL=https://example.com/relay", # These URLs don't actually host a relay or gateway config, but are needed to stop us making outside network connections.
|
||||
"toolkit.shopping.ohttpConfigURL=https://example.com/ohttp-config",
|
||||
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features=false", # Disable the fakespot feature callouts to avoid interference. Individual tests that need them can re-enable them as needed.
|
||||
"browser.shopping.experience2023.shoppingSidebar=true",
|
||||
"browser.shopping.experience2023.integratedSidebar=false",
|
||||
]
|
||||
|
||||
|
|
|
@ -12,7 +12,9 @@ support-files = [
|
|||
]
|
||||
|
||||
prefs = [
|
||||
"browser.shopping.experience2023.enabled=true",
|
||||
"sidebar.revamp=true",
|
||||
"browser.shopping.experience2023.integratedSidebar=true",
|
||||
"browser.shopping.experience2023.enabled=false",
|
||||
"browser.shopping.experience2023.optedIn=1",
|
||||
"browser.shopping.experience2023.ads.enabled=true",
|
||||
"browser.shopping.experience2023.ads.userEnabled=true",
|
||||
|
@ -22,9 +24,6 @@ prefs = [
|
|||
"toolkit.shopping.ohttpRelayURL=https://example.com/relay", # These URLs don't actually host a relay or gateway config, but are needed to stop us making outside network connections.
|
||||
"toolkit.shopping.ohttpConfigURL=https://example.com/ohttp-config",
|
||||
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features=false", # Disable the fakespot feature callouts to avoid interference. Individual tests that need them can re-enable them as needed.
|
||||
"sidebar.revamp=true",
|
||||
"browser.shopping.experience2023.shoppingSidebar=false",
|
||||
"browser.shopping.experience2023.integratedSidebar=true",
|
||||
"browser.shopping.experience2023.newPositionCard.hasSeen=true",
|
||||
]
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ add_setup(async function setup() {
|
|||
set: [
|
||||
["sidebar.revamp", true],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
["browser.shopping.experience2023.shoppingSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", false],
|
||||
["sidebar.main.tools", "aichat,reviewchecker,syncedtabs,history"],
|
||||
["toolkit.shopping.ohttpRelayURL", ""],
|
||||
["toolkit.shopping.ohttpConfigURL", ""],
|
||||
|
|
|
@ -49,7 +49,7 @@ add_setup(async function setup() {
|
|||
set: [
|
||||
["sidebar.revamp", true],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
["browser.shopping.experience2023.shoppingSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", false],
|
||||
["sidebar.main.tools", "aichat,reviewchecker,syncedtabs,history"],
|
||||
["toolkit.shopping.ohttpRelayURL", ""],
|
||||
["toolkit.shopping.ohttpConfigURL", ""],
|
||||
|
|
|
@ -96,7 +96,7 @@ add_setup(async function setup() {
|
|||
set: [
|
||||
["sidebar.revamp", true],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
["browser.shopping.experience2023.shoppingSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", false],
|
||||
["browser.shopping.experience2023.autoOpen.enabled", true],
|
||||
["browser.shopping.experience2023.autoOpen.userEnabled", true],
|
||||
["sidebar.main.tools", "aichat,reviewchecker,syncedtabs,history"],
|
||||
|
|
|
@ -15,9 +15,15 @@ const PRODUCT_URI = Services.io.newURI(
|
|||
"https://example.com/product/B09TJGHL5F"
|
||||
);
|
||||
const CONTENT_PAGE_URI = Services.io.newURI("https://example.com");
|
||||
const UNSUPPORTED_NON_PDP_URI = Services.io.newURI("about:about");
|
||||
const UNSUPPORTED_NON_PDP_URL = "about:about";
|
||||
const UNSUPPORTED_NON_PDP_URI = Services.io.newURI(UNSUPPORTED_NON_PDP_URL);
|
||||
|
||||
const REVIEW_CHECKER_ACTOR = "ReviewChecker";
|
||||
|
||||
const EMPTY_STATE_CLASS_NAME = ".FS_OPT_IN_SIDEBAR_VARIANT";
|
||||
const EMPTY_STATE_UNSUPPORTED_PDP_CLASS_NAME =
|
||||
".FS_OPT_IN_SIDEBAR_VARIANT_UNSUPPORTED_NON_PDP";
|
||||
|
||||
/**
|
||||
* Toggle prefs involved in automatically activating the sidebar on PDPs if the
|
||||
* user has not opted in. Onboarding should only try to auto-activate the
|
||||
|
@ -74,6 +80,82 @@ function setOnboardingPrefs(states = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that we're rendering the expected onboarding UI in about:shoppingsidebar.
|
||||
* Unlike assertEmptyStateTypeWithRC, this function is meant for tests that load
|
||||
* about:shoppingsidebar in a tab and don't utilize the sidebar.
|
||||
*
|
||||
* @param {Browser} browser the current browser
|
||||
* @param {string} messageName class name of the expected onboarding UI
|
||||
*/
|
||||
async function assertEmptyStateType(browser, messageName) {
|
||||
await SpecialPowers.spawn(browser, [messageName], async className => {
|
||||
let shoppingContainer = await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector("shopping-container"),
|
||||
"shopping-container"
|
||||
);
|
||||
let containerElem =
|
||||
shoppingContainer.shadowRoot.getElementById("shopping-container");
|
||||
let messageSlot = containerElem.getElementsByTagName("slot");
|
||||
|
||||
// Check multi-stage-message-slot used to show opt-In message is
|
||||
// rendered inside shopping container when user optedIn pref value is 0
|
||||
ok(messageSlot.length, `message slot element exists`);
|
||||
is(
|
||||
messageSlot[0].name,
|
||||
"multi-stage-message-slot",
|
||||
"multi-stage-message-slot showing opt-in message rendered"
|
||||
);
|
||||
|
||||
ok(
|
||||
!content.document.getElementById("multi-stage-message-root").hidden,
|
||||
"message is shown"
|
||||
);
|
||||
ok(content.document.querySelector(className), "Rendered correct message");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that we're rendering the expected onboarding UI in the
|
||||
* Review Checker sidebar panel. Works differently from assertEmptyStateType by
|
||||
* utilizing the helper function withReviewCheckerSidebar.
|
||||
*
|
||||
* @param {string} emptyStateClassName class name of the expected onboarding UI
|
||||
*/
|
||||
async function assertEmptyStateTypeWithRC(emptyStateClassName) {
|
||||
await withReviewCheckerSidebar(
|
||||
async className => {
|
||||
let shoppingContainer = await ContentTaskUtils.waitForCondition(
|
||||
() =>
|
||||
content.document.querySelector("shopping-container")?.wrappedJSObject,
|
||||
"Review Checker is loaded."
|
||||
);
|
||||
await shoppingContainer.updateComplete;
|
||||
let containerElem =
|
||||
shoppingContainer.shadowRoot.getElementById("shopping-container");
|
||||
let messageSlot = containerElem.getElementsByTagName("slot");
|
||||
|
||||
// Check multi-stage-message-slot used to show opt-In message is
|
||||
// rendered inside shopping container when user optedIn pref value is 0
|
||||
ok(messageSlot.length, `message slot element exists`);
|
||||
is(
|
||||
messageSlot[0].name,
|
||||
"multi-stage-message-slot",
|
||||
"multi-stage-message-slot showing opt-in message rendered"
|
||||
);
|
||||
ok(
|
||||
!content.document.getElementById("multi-stage-message-root").hidden,
|
||||
"message is shown"
|
||||
);
|
||||
ok(
|
||||
content.document.querySelector(`${className}`),
|
||||
`Rendered correct message ${className}`
|
||||
);
|
||||
},
|
||||
[emptyStateClassName]
|
||||
);
|
||||
}
|
||||
|
||||
add_setup(async function setup() {
|
||||
// Block on testFlushAllChildren to ensure Glean is initialized before
|
||||
// running tests.
|
||||
|
@ -117,35 +199,7 @@ add_task(async function test_showOnboarding_notOptedIn() {
|
|||
);
|
||||
actor.updateCurrentURL(PRODUCT_URI);
|
||||
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let shoppingContainer = await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector("shopping-container"),
|
||||
"shopping-container"
|
||||
);
|
||||
|
||||
let containerElem =
|
||||
shoppingContainer.shadowRoot.getElementById("shopping-container");
|
||||
let messageSlot = containerElem.getElementsByTagName("slot");
|
||||
|
||||
// Check multi-stage-message-slot used to show opt-In message is
|
||||
// rendered inside shopping container when user optedIn pref value is 0
|
||||
ok(messageSlot.length, `message slot element exists`);
|
||||
is(
|
||||
messageSlot[0].name,
|
||||
"multi-stage-message-slot",
|
||||
"multi-stage-message-slot showing opt-in message rendered"
|
||||
);
|
||||
|
||||
ok(
|
||||
!content.document.getElementById("multi-stage-message-root").hidden,
|
||||
"message is shown"
|
||||
);
|
||||
|
||||
ok(
|
||||
content.document.querySelector(".FS_OPT_IN_SIDEBAR_VARIANT"),
|
||||
"Rendered correct message"
|
||||
);
|
||||
});
|
||||
await assertEmptyStateType(browser, EMPTY_STATE_CLASS_NAME);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -188,33 +242,7 @@ add_task(async function test_showOnboarding_notOptedIn_supported() {
|
|||
);
|
||||
actor.updateCurrentURL(CONTENT_PAGE_URI);
|
||||
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let shoppingContainer = await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector("shopping-container"),
|
||||
"shopping-container"
|
||||
);
|
||||
let containerElem =
|
||||
shoppingContainer.shadowRoot.getElementById("shopping-container");
|
||||
let messageSlot = containerElem.getElementsByTagName("slot");
|
||||
|
||||
// Check multi-stage-message-slot used to show opt-In message is
|
||||
// rendered inside shopping container when user optedIn pref value is 0
|
||||
ok(messageSlot.length, `message slot element exists`);
|
||||
is(
|
||||
messageSlot[0].name,
|
||||
"multi-stage-message-slot",
|
||||
"multi-stage-message-slot showing opt-in message rendered"
|
||||
);
|
||||
|
||||
ok(
|
||||
!content.document.getElementById("multi-stage-message-root").hidden,
|
||||
"message is shown"
|
||||
);
|
||||
ok(
|
||||
content.document.querySelector(".FS_OPT_IN_SIDEBAR_VARIANT"),
|
||||
"Rendered correct message"
|
||||
);
|
||||
});
|
||||
await assertEmptyStateType(browser, EMPTY_STATE_CLASS_NAME);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -260,35 +288,10 @@ add_task(
|
|||
);
|
||||
actor.updateCurrentURL(UNSUPPORTED_NON_PDP_URI);
|
||||
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let shoppingContainer = await ContentTaskUtils.waitForCondition(
|
||||
() => content.document.querySelector("shopping-container"),
|
||||
"shopping-container"
|
||||
);
|
||||
|
||||
let containerElem =
|
||||
shoppingContainer.shadowRoot.getElementById("shopping-container");
|
||||
let messageSlot = containerElem.getElementsByTagName("slot");
|
||||
|
||||
// Check multi-stage-message-slot used to show opt-In message is
|
||||
// rendered inside shopping container when user optedIn pref value is 0
|
||||
ok(messageSlot.length, `message slot element exists`);
|
||||
is(
|
||||
messageSlot[0].name,
|
||||
"multi-stage-message-slot",
|
||||
"multi-stage-message-slot showing opt-in message rendered"
|
||||
);
|
||||
ok(
|
||||
!content.document.getElementById("multi-stage-message-root").hidden,
|
||||
"message is shown"
|
||||
);
|
||||
ok(
|
||||
content.document.querySelector(
|
||||
".FS_OPT_IN_SIDEBAR_VARIANT_UNSUPPORTED_NON_PDP"
|
||||
),
|
||||
"Rendered correct message"
|
||||
);
|
||||
});
|
||||
await assertEmptyStateType(
|
||||
browser,
|
||||
EMPTY_STATE_UNSUPPORTED_PDP_CLASS_NAME
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -404,3 +407,80 @@ add_task(async function test_hideOnboarding_OptIn_AfterSurveySeen() {
|
|||
);
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the onboarding UI type is consistent between PDPs and unsupported sites after
|
||||
* reload, open/close, and tab switch.
|
||||
*/
|
||||
add_task(
|
||||
async function test_showOnboarding_integrated_sidebar_correct_ui_on_rerender() {
|
||||
// OptedIn pref Value is 0 when a user hasn't opted-in
|
||||
setOnboardingPrefs({ optedIn: 0, telemetryEnabled: true });
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.shopping.experience2023.autoClose.userEnabled", false],
|
||||
["browser.shopping.experience2023.autoOpen.userEnabled", true],
|
||||
["toolkit.shopping.ohttpRelayURL", ""],
|
||||
["toolkit.shopping.ohttpConfigURL", ""],
|
||||
],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(PRODUCT_TEST_URL, async browser => {
|
||||
await SidebarController.show("viewReviewCheckerSidebar");
|
||||
await assertEmptyStateTypeWithRC(EMPTY_STATE_CLASS_NAME);
|
||||
|
||||
// Verify UI after tab reload.
|
||||
let loadedPromise = BrowserTestUtils.browserLoaded(browser);
|
||||
await BrowserTestUtils.reloadTab(gBrowser.selectedTab);
|
||||
await loadedPromise;
|
||||
Assert.ok(true, "Promise resolved after reloading tab");
|
||||
|
||||
info("Verifying UI for PDP after reload");
|
||||
await assertEmptyStateTypeWithRC(EMPTY_STATE_CLASS_NAME);
|
||||
|
||||
// Verify UI after closing and reopening the RC panel.
|
||||
SidebarController.hide();
|
||||
await SidebarController.show("viewReviewCheckerSidebar");
|
||||
|
||||
info("Verifying UI for PDP after closing then reopening the RC panel");
|
||||
await assertEmptyStateTypeWithRC(EMPTY_STATE_CLASS_NAME);
|
||||
|
||||
// Verify UI after navigating to an unsupported site tab.
|
||||
let unsupportedSiteTab = BrowserTestUtils.addTab(
|
||||
gBrowser,
|
||||
UNSUPPORTED_NON_PDP_URL
|
||||
);
|
||||
let nonPDPBrowser = unsupportedSiteTab.linkedBrowser;
|
||||
await BrowserTestUtils.browserLoaded(
|
||||
nonPDPBrowser,
|
||||
false,
|
||||
UNSUPPORTED_NON_PDP_URL
|
||||
);
|
||||
|
||||
await BrowserTestUtils.switchTab(gBrowser, unsupportedSiteTab);
|
||||
Assert.ok(true, "Browser is loaded after switching tabs");
|
||||
Assert.ok(SidebarController.isOpen, "Sidebar is open now");
|
||||
|
||||
info(
|
||||
"Verifying UI for unsupported sites after switching to an unsupported site tab"
|
||||
);
|
||||
await assertEmptyStateTypeWithRC(EMPTY_STATE_UNSUPPORTED_PDP_CLASS_NAME);
|
||||
|
||||
// Verify UI after closing and reopening the RC panel.
|
||||
SidebarController.hide();
|
||||
await SidebarController.show("viewReviewCheckerSidebar");
|
||||
|
||||
info(
|
||||
"Verifying UI for unsupported sites after closing then reopening the RC panel"
|
||||
);
|
||||
await assertEmptyStateTypeWithRC(EMPTY_STATE_UNSUPPORTED_PDP_CLASS_NAME);
|
||||
|
||||
await BrowserTestUtils.removeTab(unsupportedSiteTab);
|
||||
});
|
||||
|
||||
SidebarController.hide();
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,11 +10,11 @@ const PRODUCT_PAGE = "https://example.com/product/Y4YM0Z1LL4";
|
|||
|
||||
let verifySidebarPanelNotAdded = async win => {
|
||||
const { document } = win;
|
||||
let sidebar = document.querySelector("sidebar-main");
|
||||
await TestUtils.waitForCondition(
|
||||
() => sidebar.toolButtons,
|
||||
"Sidebar tools have been added."
|
||||
);
|
||||
let sidebar;
|
||||
await TestUtils.waitForCondition(() => {
|
||||
sidebar = document.querySelector("sidebar-main");
|
||||
return sidebar.toolButtons;
|
||||
}, "Sidebar tools have been added.");
|
||||
let reviewCheckerButton = sidebar.shadowRoot.querySelector(
|
||||
"moz-button[view=viewReviewCheckerSidebar]"
|
||||
);
|
||||
|
@ -36,8 +36,14 @@ add_task(async function test_bug_1901979_pref_toggle_private_windows() {
|
|||
verifySidebarPanelNotAdded(privateWindow);
|
||||
|
||||
// Flip the prefs to trigger the bug.
|
||||
Services.prefs.setBoolPref("browser.shopping.experience2023.enabled", false);
|
||||
Services.prefs.setBoolPref("browser.shopping.experience2023.enabled", true);
|
||||
Services.prefs.setBoolPref(
|
||||
"browser.shopping.experience2023.integratedSidebar",
|
||||
false
|
||||
);
|
||||
Services.prefs.setBoolPref(
|
||||
"browser.shopping.experience2023.integratedSidebar",
|
||||
true
|
||||
);
|
||||
|
||||
// Verify we still haven't displayed the sidebar.
|
||||
verifySidebarPanelNotAdded(privateWindow);
|
||||
|
|
|
@ -131,7 +131,7 @@ add_setup(async function setup() {
|
|||
set: [
|
||||
["sidebar.revamp", true],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
["browser.shopping.experience2023.shoppingSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", false],
|
||||
["browser.shopping.experience2023.autoOpen.enabled", true],
|
||||
["browser.shopping.experience2023.autoOpen.userEnabled", true],
|
||||
["sidebar.main.tools", "aichat,reviewchecker,syncedtabs,history"],
|
||||
|
|
|
@ -17,7 +17,7 @@ add_setup(async function setup() {
|
|||
set: [
|
||||
["sidebar.revamp", true],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
["browser.shopping.experience2023.shoppingSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", false],
|
||||
["sidebar.main.tools", "aichat,reviewchecker,syncedtabs,history"],
|
||||
["toolkit.shopping.ohttpRelayURL", ""],
|
||||
["toolkit.shopping.ohttpConfigURL", ""],
|
||||
|
|
|
@ -16,7 +16,7 @@ add_setup(async function setup() {
|
|||
set: [
|
||||
["sidebar.revamp", true],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
["browser.shopping.experience2023.shoppingSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", false],
|
||||
["sidebar.main.tools", "aichat,reviewchecker,syncedtabs,history"],
|
||||
["toolkit.shopping.ohttpRelayURL", ""],
|
||||
["toolkit.shopping.ohttpConfigURL", ""],
|
||||
|
|
|
@ -103,19 +103,44 @@ add_task(async function test_onOptIn() {
|
|||
gBrowser,
|
||||
},
|
||||
async browser => {
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
() => !!content.document.querySelector("shopping-container .primary")
|
||||
);
|
||||
const REVIEW_CHECKER_ACTOR = "ReviewChecker";
|
||||
|
||||
// "Yes, try it" button
|
||||
let primary = content.document.querySelector(
|
||||
"shopping-container .primary"
|
||||
);
|
||||
primary.click();
|
||||
});
|
||||
await SpecialPowers.spawn(
|
||||
browser,
|
||||
[{ REVIEW_CHECKER_ACTOR, PRODUCT_TEST_URL }],
|
||||
async args => {
|
||||
if (
|
||||
Services.prefs.getBoolPref(
|
||||
"browser.shopping.experience2023.integratedSidebar"
|
||||
)
|
||||
) {
|
||||
let actor = content.windowGlobalChild.getExistingActor(
|
||||
args.REVIEW_CHECKER_ACTOR
|
||||
);
|
||||
Assert.ok(actor, "ReviewCheckerChild found");
|
||||
|
||||
/**
|
||||
* We normally exit early on location change if the URL is about:shoppingsidebar.
|
||||
* For this test, make sure the onboarding UI renders by directly calling
|
||||
* ReviewCheckerChild.locationChanged and passing in a mock PDP URL.
|
||||
*/
|
||||
actor.locationChanged({ url: args.PRODUCT_TEST_URL });
|
||||
}
|
||||
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
() =>
|
||||
!!content.document.querySelector("shopping-container .primary")
|
||||
);
|
||||
|
||||
// "Yes, try it" button
|
||||
let primary = content.document.querySelector(
|
||||
"shopping-container .primary"
|
||||
);
|
||||
primary.click();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -136,6 +161,7 @@ add_task(async function test_onOptIn() {
|
|||
* Helper function to click the links in the Link Paragraph.
|
||||
*/
|
||||
async function linkParagraphClickLinks() {
|
||||
const REVIEW_CHECKER_ACTOR = "ReviewChecker";
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
let handleActionStub = sandbox
|
||||
|
@ -152,22 +178,44 @@ async function linkParagraphClickLinks() {
|
|||
gBrowser,
|
||||
},
|
||||
async browser => {
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
// Can safely assume that if one of the link exists, they both do.
|
||||
() =>
|
||||
!!content.document.querySelector(
|
||||
".legal-paragraph a[value='terms_of_use']"
|
||||
await SpecialPowers.spawn(
|
||||
browser,
|
||||
[{ REVIEW_CHECKER_ACTOR, PRODUCT_TEST_URL }],
|
||||
async args => {
|
||||
if (
|
||||
Services.prefs.getBoolPref(
|
||||
"browser.shopping.experience2023.integratedSidebar"
|
||||
)
|
||||
);
|
||||
) {
|
||||
let actor = content.windowGlobalChild.getExistingActor(
|
||||
args.REVIEW_CHECKER_ACTOR
|
||||
);
|
||||
Assert.ok(actor, "ReviewCheckerChild found");
|
||||
|
||||
let termsOfUse = content.document.querySelector(
|
||||
"shopping-container .legal-paragraph a[value='terms_of_use']"
|
||||
);
|
||||
termsOfUse.click();
|
||||
});
|
||||
/**
|
||||
* We normally exit early on location change if the URL is about:shoppingsidebar.
|
||||
* For this test, make sure the onboarding UI renders by directly calling
|
||||
* ReviewCheckerChild.locationChanged and passing in a mock PDP URL.
|
||||
*/
|
||||
actor.locationChanged({ url: args.PRODUCT_TEST_URL });
|
||||
}
|
||||
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
// Can safely assume that if one of the link exists, they both do.
|
||||
() =>
|
||||
!!content.document.querySelector(
|
||||
".legal-paragraph a[value='terms_of_use']"
|
||||
)
|
||||
);
|
||||
|
||||
let termsOfUse = content.document.querySelector(
|
||||
"shopping-container .legal-paragraph a[value='terms_of_use']"
|
||||
);
|
||||
termsOfUse.click();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -185,21 +233,43 @@ async function linkParagraphClickLinks() {
|
|||
gBrowser,
|
||||
},
|
||||
async browser => {
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
// Can safely assume that if one of the link exists, they both do.
|
||||
() =>
|
||||
!!content.document.querySelector(
|
||||
".legal-paragraph a[value='terms_of_use']"
|
||||
await SpecialPowers.spawn(
|
||||
browser,
|
||||
[{ REVIEW_CHECKER_ACTOR, PRODUCT_TEST_URL }],
|
||||
async args => {
|
||||
if (
|
||||
Services.prefs.getBoolPref(
|
||||
"browser.shopping.experience2023.integratedSidebar"
|
||||
)
|
||||
);
|
||||
let privacyPolicy = content.document.querySelector(
|
||||
"shopping-container .legal-paragraph a[value='privacy_policy']"
|
||||
);
|
||||
privacyPolicy.click();
|
||||
});
|
||||
) {
|
||||
let actor = content.windowGlobalChild.getExistingActor(
|
||||
args.REVIEW_CHECKER_ACTOR
|
||||
);
|
||||
Assert.ok(actor, "ReviewCheckerChild found");
|
||||
|
||||
/**
|
||||
* We normally exit early on location change if the URL is about:shoppingsidebar.
|
||||
* For this test, make sure the onboarding UI renders by directly calling
|
||||
* ReviewCheckerChild.locationChanged and passing in a mock PDP URL.
|
||||
*/
|
||||
actor.locationChanged({ url: args.PRODUCT_TEST_URL });
|
||||
}
|
||||
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
// Can safely assume that if one of the link exists, they both do.
|
||||
() =>
|
||||
!!content.document.querySelector(
|
||||
".legal-paragraph a[value='terms_of_use']"
|
||||
)
|
||||
);
|
||||
let privacyPolicy = content.document.querySelector(
|
||||
"shopping-container .legal-paragraph a[value='privacy_policy']"
|
||||
);
|
||||
privacyPolicy.click();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
await handleActionStubCalled;
|
||||
|
@ -216,18 +286,39 @@ async function linkParagraphClickLinks() {
|
|||
gBrowser,
|
||||
},
|
||||
async browser => {
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
() => content.document.querySelector(".link-paragraph a")
|
||||
);
|
||||
let learnMore = content.document.querySelector(
|
||||
"shopping-container .link-paragraph a[value='learn_more']"
|
||||
);
|
||||
// Learn More link button.
|
||||
learnMore.click();
|
||||
});
|
||||
await SpecialPowers.spawn(
|
||||
browser,
|
||||
[{ REVIEW_CHECKER_ACTOR, PRODUCT_TEST_URL }],
|
||||
async args => {
|
||||
if (
|
||||
Services.prefs.getBoolPref(
|
||||
"browser.shopping.experience2023.integratedSidebar"
|
||||
)
|
||||
) {
|
||||
let actor = content.windowGlobalChild.getExistingActor(
|
||||
args.REVIEW_CHECKER_ACTOR
|
||||
);
|
||||
Assert.ok(actor, "ReviewCheckerChild found");
|
||||
|
||||
/**
|
||||
* We normally exit early on location change if the URL is about:shoppingsidebar.
|
||||
* For this test, make sure the onboarding UI renders by directly calling
|
||||
* ReviewCheckerChild.locationChanged and passing in a mock PDP URL.
|
||||
*/
|
||||
actor.locationChanged({ url: args.PRODUCT_TEST_URL });
|
||||
}
|
||||
await ContentTaskUtils.waitForMutationCondition(
|
||||
content.document,
|
||||
{ childList: true, subtree: true },
|
||||
() => content.document.querySelector(".link-paragraph a")
|
||||
);
|
||||
let learnMore = content.document.querySelector(
|
||||
"shopping-container .link-paragraph a[value='learn_more']"
|
||||
);
|
||||
// Learn More link button.
|
||||
learnMore.click();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
await handleActionStubCalled;
|
||||
|
|
|
@ -357,6 +357,7 @@ add_task(async function test_onboarding_resets_after_opt_out() {
|
|||
["browser.shopping.experience2023.survey.pdpVisits", 5],
|
||||
["browser.shopping.experience2023.survey.optedInTime", time25HrsAgo],
|
||||
["browser.shopping.experience2023.integratedSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", true],
|
||||
],
|
||||
});
|
||||
await BrowserTestUtils.withNewTab(
|
||||
|
@ -463,7 +464,7 @@ add_task(
|
|||
["browser.shopping.experience2023.survey.pdpVisits", 5],
|
||||
["browser.shopping.experience2023.survey.optedInTime", time25HrsAgo],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
["browser.shopping.experience2023.shoppingSidebar", false],
|
||||
["browser.shopping.experience2023.enabled", false],
|
||||
],
|
||||
});
|
||||
await BrowserTestUtils.withNewTab(
|
||||
|
|
|
@ -211,7 +211,7 @@ add_task(async function test_hideOnboarding_onClose() {
|
|||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.shopping.experience2023.integratedSidebar", false],
|
||||
["browser.shopping.experience2023.shoppingSidebar", true],
|
||||
["browser.shopping.experience2023.enabled", true],
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -23,19 +23,6 @@ let verifySidebarNotShown = win => {
|
|||
);
|
||||
};
|
||||
|
||||
let verifySidebarPanelNotAdded = async win => {
|
||||
const { document } = win;
|
||||
let sidebar = document.querySelector("sidebar-main");
|
||||
await TestUtils.waitForCondition(
|
||||
() => sidebar.toolButtons,
|
||||
"Sidebar tools have been added."
|
||||
);
|
||||
let reviewCheckerButton = sidebar.shadowRoot.querySelector(
|
||||
"moz-button[view=viewReviewCheckerSidebar]"
|
||||
);
|
||||
Assert.equal(reviewCheckerButton, null, "Review Checker should not exist.");
|
||||
};
|
||||
|
||||
add_task(async function test_private_window_disabled() {
|
||||
let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
|
||||
private: true,
|
||||
|
@ -76,27 +63,3 @@ add_task(async function test_bug_1901979_pref_toggle_private_windows() {
|
|||
|
||||
await BrowserTestUtils.closeWindow(privateWindow);
|
||||
});
|
||||
|
||||
add_task(async function test_private_window_does_not_have_integrated_sidebar() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["sidebar.revamp", true],
|
||||
["sidebar.verticalTabs", true],
|
||||
["browser.shopping.experience2023.integratedSidebar", true],
|
||||
],
|
||||
});
|
||||
|
||||
let privateWindow = await BrowserTestUtils.openNewBrowserWindow({
|
||||
private: true,
|
||||
});
|
||||
|
||||
let browser = privateWindow.gBrowser.selectedBrowser;
|
||||
BrowserTestUtils.startLoadingURIString(browser, PRODUCT_PAGE);
|
||||
await BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
verifySidebarPanelNotAdded(privateWindow);
|
||||
|
||||
await BrowserTestUtils.closeWindow(privateWindow);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
|
|
@ -369,7 +369,7 @@ export class SidebarState {
|
|||
mainEl.toggleAttribute("sidebar-launcher-expanded", expanded);
|
||||
}
|
||||
splitterEl?.toggleAttribute("sidebar-launcher-expanded", expanded);
|
||||
boxEl.toggleAttribute("sidebar-launcher-expanded", expanded);
|
||||
boxEl?.toggleAttribute("sidebar-launcher-expanded", expanded);
|
||||
contentAreaEl.toggleAttribute("sidebar-launcher-expanded", expanded);
|
||||
this.#controller.updateToolbarButton();
|
||||
if (!this.launcherDragActive) {
|
||||
|
|
|
@ -25,6 +25,7 @@ const toolsNameMap = {
|
|||
};
|
||||
const EXPAND_ON_HOVER_DEBOUNCE_RATE_MS = 200;
|
||||
const EXPAND_ON_HOVER_DEBOUNCE_TIMEOUT_MS = 1000;
|
||||
const LAUNCHER_SPLITTER_WIDTH = 4;
|
||||
|
||||
var SidebarController = {
|
||||
makeSidebar({ elementId, ...rest }) {
|
||||
|
@ -46,8 +47,12 @@ var SidebarController = {
|
|||
|
||||
let switcherMenuitem;
|
||||
const updateMenus = visible => {
|
||||
// Hide the sidebar if it is open and should not be visible.
|
||||
if (!visible && this.isOpen && this.currentID == commandID) {
|
||||
// Hide the sidebar if it is open and should not be visible,
|
||||
// and unset the current command and lastOpenedId so they do not
|
||||
// re-open the next time the sidebar does.
|
||||
if (!visible && this._state.command == commandID) {
|
||||
this._state.command = "";
|
||||
this.lastOpenedId = null;
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
@ -266,8 +271,6 @@ var SidebarController = {
|
|||
_mainResizeObserverAdded: false,
|
||||
_mainResizeObserver: null,
|
||||
_ongoingAnimations: [],
|
||||
_launcherMouseOverListenerAdded: false,
|
||||
_launcherMouseOutListenerAdded: false,
|
||||
|
||||
/**
|
||||
* @type {MutationObserver | null}
|
||||
|
@ -714,9 +717,6 @@ var SidebarController = {
|
|||
*/
|
||||
reversePosition() {
|
||||
Services.prefs.setBoolPref(this.POSITION_START_PREF, !this._positionStart);
|
||||
if (this.sidebarRevampVisibility === "expand-on-hover") {
|
||||
this.setLauncherCollapsedWidth();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1006,15 +1006,6 @@ var SidebarController = {
|
|||
]);
|
||||
},
|
||||
|
||||
async _waitForOngoingAnimations() {
|
||||
// Wait for any ongoing animations to finish
|
||||
return new Promise(resolve => {
|
||||
if (!this._ongoingAnimations.length) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Wait for Lit updates and ongoing animations to complete.
|
||||
*
|
||||
|
@ -1025,10 +1016,12 @@ var SidebarController = {
|
|||
// Legacy sidebar doesn't have animations, nothing to await.
|
||||
return null;
|
||||
}
|
||||
const tasks = [
|
||||
this.sidebarMain.updateComplete,
|
||||
...this._ongoingAnimations.map(animation => animation.finished),
|
||||
];
|
||||
const tasks = [this.sidebarMain.updateComplete];
|
||||
if (this._ongoingAnimations?.length) {
|
||||
tasks.push(
|
||||
...this._ongoingAnimations.map(animation => animation.finished)
|
||||
);
|
||||
}
|
||||
return Promise.allSettled(tasks);
|
||||
},
|
||||
|
||||
|
@ -1906,95 +1899,43 @@ var SidebarController = {
|
|||
this.sidebarMain.requestUpdate();
|
||||
},
|
||||
|
||||
onMouseOver() {
|
||||
debouncedMouseEnter() {
|
||||
const contentArea = document.getElementById("tabbrowser-tabbox");
|
||||
SidebarController._box.toggleAttribute("sidebar-launcher-hovered", true);
|
||||
this._box.toggleAttribute("sidebar-launcher-hovered", true);
|
||||
contentArea.toggleAttribute("sidebar-launcher-hovered", true);
|
||||
SidebarController._state.launcherHoverActive = true;
|
||||
if (SidebarController._animationEnabled && !window.gReduceMotion) {
|
||||
if (SidebarController._ongoingAnimations.length) {
|
||||
SidebarController._ongoingAnimations.forEach(a => a.cancel());
|
||||
SidebarController._ongoingAnimations = [];
|
||||
}
|
||||
SidebarController._animateSidebarMain();
|
||||
this._state.launcherHoverActive = true;
|
||||
if (this._animationEnabled && !window.gReduceMotion) {
|
||||
this._animateSidebarMain();
|
||||
}
|
||||
SidebarController._state.launcherExpanded = true;
|
||||
SidebarController.sidebarContainer.removeEventListener(
|
||||
"mouseover",
|
||||
SidebarController.handleMouseOver
|
||||
);
|
||||
SidebarController._launcherMouseOverListenerAdded = false;
|
||||
this._state.launcherExpanded = true;
|
||||
},
|
||||
|
||||
onMouseOut(e) {
|
||||
if (SidebarController.sidebarContainer.matches(":hover")) {
|
||||
// Don't fire mouseout if hovered over launcher child element
|
||||
e?.stopPropagation();
|
||||
} else {
|
||||
const contentArea = document.getElementById("tabbrowser-tabbox");
|
||||
SidebarController._box.toggleAttribute("sidebar-launcher-hovered", false);
|
||||
contentArea.toggleAttribute("sidebar-launcher-hovered", false);
|
||||
SidebarController._state.launcherHoverActive = false;
|
||||
if (!SidebarController._launcherMouseOverListenerAdded) {
|
||||
SidebarController.sidebarContainer.addEventListener(
|
||||
"mouseover",
|
||||
SidebarController.handleMouseOver
|
||||
);
|
||||
SidebarController._launcherMouseOverListenerAdded = true;
|
||||
}
|
||||
if (SidebarController._animationEnabled && !window.gReduceMotion) {
|
||||
if (SidebarController._ongoingAnimations.length) {
|
||||
SidebarController._ongoingAnimations.forEach(a => a.cancel());
|
||||
SidebarController._ongoingAnimations = [];
|
||||
}
|
||||
SidebarController._animateSidebarMain();
|
||||
}
|
||||
SidebarController._state.launcherExpanded = false;
|
||||
SidebarController.sidebarContainer.removeEventListener(
|
||||
"mouseout",
|
||||
SidebarController.onMouseOut
|
||||
);
|
||||
SidebarController._launcherMouseOutListenerAdded = false;
|
||||
onMouseLeave() {
|
||||
this.mouseEnterTask.disarm();
|
||||
const contentArea = document.getElementById("tabbrowser-tabbox");
|
||||
this._box.toggleAttribute("sidebar-launcher-hovered", false);
|
||||
contentArea.toggleAttribute("sidebar-launcher-hovered", false);
|
||||
this._state.launcherHoverActive = false;
|
||||
if (this._animationEnabled && !window.gReduceMotion) {
|
||||
this._animateSidebarMain();
|
||||
}
|
||||
this._state.launcherExpanded = false;
|
||||
},
|
||||
|
||||
handleMouseOver(e) {
|
||||
SidebarController.mouseOverTask = new DeferredTask(
|
||||
onMouseEnter() {
|
||||
this.mouseEnterTask = new DeferredTask(
|
||||
() => {
|
||||
if (!SidebarController.sidebarContainer.matches(":hover")) {
|
||||
// Don't fire mouseout if hovered over element outside of the launcher
|
||||
e?.stopPropagation();
|
||||
} else {
|
||||
if (!SidebarController._launcherMouseOutListenerAdded) {
|
||||
SidebarController.sidebarContainer.addEventListener(
|
||||
"mouseout",
|
||||
SidebarController.onMouseOut
|
||||
);
|
||||
SidebarController._launcherMouseOutListenerAdded = true;
|
||||
}
|
||||
SidebarController.onMouseOver();
|
||||
}
|
||||
this.debouncedMouseEnter();
|
||||
},
|
||||
EXPAND_ON_HOVER_DEBOUNCE_RATE_MS,
|
||||
EXPAND_ON_HOVER_DEBOUNCE_TIMEOUT_MS
|
||||
);
|
||||
SidebarController.mouseOverTask?.arm();
|
||||
this.mouseEnterTask?.arm();
|
||||
},
|
||||
|
||||
async setLauncherCollapsedWidth() {
|
||||
let browserEl = document.getElementById("browser");
|
||||
let collapsedWidth;
|
||||
if (this.getUIState().launcherExpanded) {
|
||||
this._state.launcherExpanded = false;
|
||||
await this.sidebarMain.updateComplete;
|
||||
collapsedWidth = await new Promise(resolve => {
|
||||
requestAnimationFrame(() => {
|
||||
resolve(this._getRects([this.sidebarMain])[0][1].width);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
collapsedWidth = this._getRects([this.sidebarMain])[0][1].width;
|
||||
}
|
||||
let collapsedWidth = this._getRects([this.sidebarMain])[0][1].width;
|
||||
|
||||
browserEl.style.setProperty(
|
||||
"--sidebar-launcher-collapsed-width",
|
||||
|
@ -2002,6 +1943,42 @@ var SidebarController = {
|
|||
);
|
||||
},
|
||||
|
||||
handleEvent(e) {
|
||||
switch (e.type) {
|
||||
case "mouseout":
|
||||
if (
|
||||
(this._positionStart && e.x < 0) ||
|
||||
(!this._positionStart && e.x > window.outerWidth)
|
||||
) {
|
||||
this.mouseEnterTask?.disarm();
|
||||
// Only collapse sidebar if not moused over the window
|
||||
if (this.getUIState().launcherExpanded) {
|
||||
if (this._animationEnabled && !window.gReduceMotion) {
|
||||
this._animateSidebarMain();
|
||||
}
|
||||
this._state.launcherExpanded = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
getMouseTargetRect() {
|
||||
let launcherRect = window.windowUtils.getBoundsWithoutFlushing(
|
||||
SidebarController.sidebarContainer
|
||||
);
|
||||
return {
|
||||
top: launcherRect.top,
|
||||
bottom: launcherRect.bottom,
|
||||
left: this._positionStart
|
||||
? launcherRect.left
|
||||
: launcherRect.left + LAUNCHER_SPLITTER_WIDTH,
|
||||
right: this._positionStart
|
||||
? launcherRect.right - LAUNCHER_SPLITTER_WIDTH
|
||||
: launcherRect.right,
|
||||
};
|
||||
},
|
||||
|
||||
async toggleExpandOnHover(isEnabled, isDragEnded) {
|
||||
document.documentElement.toggleAttribute(
|
||||
"sidebar-expand-on-hover",
|
||||
|
@ -2011,17 +1988,18 @@ var SidebarController = {
|
|||
if (!this._state) {
|
||||
this._state = new this.SidebarState(this);
|
||||
}
|
||||
this.sidebarContainer.addEventListener("mouseover", this.handleMouseOver);
|
||||
await this.sidebarMain.updateComplete;
|
||||
await this._waitForOngoingAnimations();
|
||||
if (this.getUIState().launcherExpanded && !isDragEnded) {
|
||||
this._state.launcherExpanded = false;
|
||||
await this.waitUntilStable();
|
||||
}
|
||||
MousePosTracker.addListener(this);
|
||||
window.addEventListener("mouseout", this);
|
||||
if (!isDragEnded) {
|
||||
await this.setLauncherCollapsedWidth();
|
||||
}
|
||||
} else {
|
||||
this.sidebarContainer.removeEventListener(
|
||||
"mouseover",
|
||||
this.handleMouseOver
|
||||
);
|
||||
MousePosTracker.removeListener(this);
|
||||
window.removeEventListener("mouseout", this);
|
||||
if (!this.mouseOverTask?.isFinalized) {
|
||||
this.mouseOverTask?.finalize();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ async function mouseOverSidebarToExpand() {
|
|||
SidebarController.sidebarContainer,
|
||||
{ attributes: true },
|
||||
async () => {
|
||||
await SidebarController._waitForOngoingAnimations();
|
||||
await SidebarController.waitUntilStable();
|
||||
return (
|
||||
SidebarController.sidebarContainer.hasAttribute(
|
||||
"sidebar-launcher-expanded"
|
||||
|
@ -61,7 +61,7 @@ async function mouseOutSidebarToCollapse() {
|
|||
SidebarController.sidebarContainer,
|
||||
{ attributes: true },
|
||||
async () => {
|
||||
await SidebarController._waitForOngoingAnimations();
|
||||
await SidebarController.waitUntilStable();
|
||||
return (
|
||||
!SidebarController.sidebarContainer.hasAttribute(
|
||||
"sidebar-launcher-expanded"
|
||||
|
@ -129,6 +129,7 @@ add_task(async function test_enable_expand_on_hover() {
|
|||
|
||||
await mouseOverSidebarToExpand();
|
||||
await mouseOutSidebarToCollapse();
|
||||
await SidebarController.waitUntilStable();
|
||||
|
||||
panel.positionInput.click();
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
|
@ -152,6 +153,15 @@ add_task(async function test_enable_expand_on_hover() {
|
|||
"The sidebar is positioned on the left"
|
||||
);
|
||||
|
||||
await mouseOutSidebarToCollapse();
|
||||
await SidebarController.toggleExpandOnHover(false);
|
||||
await SidebarController.waitUntilStable();
|
||||
});
|
||||
|
||||
add_task(async function test_expand_on_hover_pinned_tabs() {
|
||||
await SidebarController.toggleExpandOnHover(true);
|
||||
await SidebarController.waitUntilStable();
|
||||
|
||||
let newTabButton = document.getElementById("tabs-newtab-button");
|
||||
info("Open 2 new tabs using the new tab button.");
|
||||
newTabButton.click();
|
||||
|
@ -162,8 +172,6 @@ add_task(async function test_enable_expand_on_hover() {
|
|||
let unpinnedTabs = gBrowser.visibleTabs.filter(tab => !tab.pinned);
|
||||
gBrowser.pinTab(unpinnedTabs[0]);
|
||||
let pinnedTabs = gBrowser.visibleTabs.filter(tab => tab.pinned);
|
||||
|
||||
await mouseOverSidebarToExpand();
|
||||
let verticalPinnedTabsContainer = document.getElementById(
|
||||
"vertical-pinned-tabs-container"
|
||||
);
|
||||
|
@ -173,9 +181,17 @@ add_task(async function test_enable_expand_on_hover() {
|
|||
let inlineMuteButton =
|
||||
gBrowser.selectedTab.querySelector(".tab-audio-button");
|
||||
let muteButtonComputedStyle = window.getComputedStyle(inlineMuteButton);
|
||||
await mouseOutSidebarToCollapse();
|
||||
await mouseOverSidebarToExpand();
|
||||
let pinnedTabComputedStyle = window.getComputedStyle(pinnedTabs[0]);
|
||||
await mouseOverSidebarToExpand();
|
||||
await SidebarController.waitUntilStable();
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
SidebarController.sidebarContainer,
|
||||
{ attributes: true },
|
||||
() =>
|
||||
SidebarController._state.launcherExpanded &&
|
||||
SidebarController.sidebarMain.hasAttribute("expanded"),
|
||||
"The launcher is expanded"
|
||||
);
|
||||
is(
|
||||
Math.round(parseInt(verticalTabsComputedStyle.width)),
|
||||
Math.round(parseInt(pinnedTabComputedStyle.width)),
|
||||
|
@ -189,5 +205,38 @@ add_task(async function test_enable_expand_on_hover() {
|
|||
);
|
||||
|
||||
await mouseOutSidebarToCollapse();
|
||||
SidebarController.hide();
|
||||
await SidebarController.toggleExpandOnHover(false);
|
||||
await SidebarController.waitUntilStable();
|
||||
});
|
||||
|
||||
add_task(async function test_expand_on_hover_context_menu() {
|
||||
await SidebarController.toggleExpandOnHover(true);
|
||||
await SidebarController.waitUntilStable();
|
||||
await mouseOverSidebarToExpand();
|
||||
await SidebarController.waitUntilStable();
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
SidebarController.sidebarContainer,
|
||||
{ attributes: true },
|
||||
() => SidebarController._state.launcherExpanded,
|
||||
"The launcher is expanded"
|
||||
);
|
||||
|
||||
const toolbarContextMenu = document.getElementById("toolbar-context-menu");
|
||||
await openAndWaitForContextMenu(
|
||||
toolbarContextMenu,
|
||||
SidebarController.sidebarMain,
|
||||
() => {
|
||||
ok(
|
||||
!document.getElementById("toolbar-context-customize-sidebar").hidden,
|
||||
"The sidebar context menu is loaded"
|
||||
);
|
||||
ok(
|
||||
SidebarController._state.launcherExpanded,
|
||||
"The sidebar launcher is still expanded with the context menu open"
|
||||
);
|
||||
}
|
||||
);
|
||||
await mouseOutSidebarToCollapse();
|
||||
await SidebarController.toggleExpandOnHover(false);
|
||||
await SidebarController.waitUntilStable();
|
||||
});
|
||||
|
|
|
@ -836,13 +836,20 @@ export class SmartTabGroupingManager {
|
|||
* @param {number} numTabsInGroup Number of tabs used to generate the label
|
||||
* @param {string} mlLabel ML generated label for the tab group
|
||||
* @param {string} userLabel User saved label for the tab group
|
||||
* @param {string} id The id of the group
|
||||
*/
|
||||
async handleLabelTelemetry({ action, numTabsInGroup, mlLabel, userLabel }) {
|
||||
async handleLabelTelemetry({
|
||||
action,
|
||||
numTabsInGroup,
|
||||
mlLabel,
|
||||
userLabel,
|
||||
id = "",
|
||||
}) {
|
||||
const { [ML_TASK_TEXT2TEXT]: topicEngineConfig } =
|
||||
await this.getEngineConfigs();
|
||||
Glean.browserMlInteraction.smartTabTopic.record({
|
||||
Glean.tabgroup.smartTabTopic.record({
|
||||
action,
|
||||
num_tabs_in_group: numTabsInGroup,
|
||||
tabs_in_group: numTabsInGroup,
|
||||
ml_label_length: (mlLabel || "").length,
|
||||
user_label_length: (userLabel || "").length,
|
||||
levenshtein_distance: lazy.NLP.levenshtein(
|
||||
|
@ -850,6 +857,7 @@ export class SmartTabGroupingManager {
|
|||
mlLabel || ""
|
||||
),
|
||||
model_revision: topicEngineConfig.modelRevision || "",
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -863,6 +871,7 @@ export class SmartTabGroupingManager {
|
|||
* @param {number} numTabsSuggested Number of tabs suggested by the model
|
||||
* @param {number} numTabsApproved Number of tabs approved by the user
|
||||
* @param {number} numTabsRemoved Number of tabs removed by the user
|
||||
* @param {string} id The id of the group
|
||||
*/
|
||||
async handleSuggestTelemetry({
|
||||
action,
|
||||
|
@ -871,17 +880,19 @@ export class SmartTabGroupingManager {
|
|||
numTabsSuggested,
|
||||
numTabsApproved,
|
||||
numTabsRemoved,
|
||||
id = "",
|
||||
}) {
|
||||
const { [ML_TASK_FEATURE_EXTRACTION]: embeddingEngineConfig } =
|
||||
await this.getEngineConfigs();
|
||||
Glean.browserMlInteraction.smartTabSuggest.record({
|
||||
Glean.tabgroup.smartTabSuggest.record({
|
||||
action,
|
||||
num_tabs_in_window: numTabsInWindow,
|
||||
num_tabs_in_group: numTabsInGroup,
|
||||
num_tabs_suggested: numTabsSuggested,
|
||||
num_tabs_approved: numTabsApproved,
|
||||
num_tabs_removed: numTabsRemoved,
|
||||
tabs_in_window: numTabsInWindow,
|
||||
tabs_in_group: numTabsInGroup,
|
||||
tabs_suggested: numTabsSuggested,
|
||||
tabs_approved: numTabsApproved,
|
||||
tabs_removed: numTabsRemoved,
|
||||
model_revision: embeddingEngineConfig.modelRevision || "",
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -893,14 +904,14 @@ export class SmartTabGroupingManager {
|
|||
async getEngineConfigs() {
|
||||
if (!this.topicEngineConfig) {
|
||||
this.topicEngineConfig = await lazy.MLEngineParent.getInferenceOptions(
|
||||
this.config.topicGeneration.engineId,
|
||||
this.config.topicGeneration.featureId,
|
||||
this.config.topicGeneration.taskName
|
||||
);
|
||||
}
|
||||
if (!this.embeddingEngineConfig) {
|
||||
this.embeddingEngineConfig =
|
||||
await lazy.MLEngineParent.getInferenceOptions(
|
||||
this.config.embedding.engineId,
|
||||
this.config.embedding.featureId,
|
||||
this.config.embedding.taskName
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* A common list of systems, surfaces, controls, etc. from which user
|
||||
* interactions with tab groups could originate. These "source" values
|
||||
* should be sent as extra data with tab group-related metrics events.
|
||||
*/
|
||||
const METRIC_SOURCE = Object.freeze({
|
||||
TAB_OVERFLOW_MENU: "tab_overflow",
|
||||
TAB_GROUP_MENU: "tab_group",
|
||||
UNKNOWN: "unknown",
|
||||
});
|
||||
|
||||
export const TabGroupMetrics = {
|
||||
METRIC_SOURCE,
|
||||
};
|
|
@ -110,8 +110,6 @@
|
|||
PictureInPicture: "resource://gre/modules/PictureInPicture.sys.mjs",
|
||||
SmartTabGroupingManager:
|
||||
"moz-src:///browser/components/tabbrowser/SmartTabGrouping.sys.mjs",
|
||||
TabGroupMetrics:
|
||||
"moz-src:///browser/components/tabbrowser/TabGroupMetrics.sys.mjs",
|
||||
UrlbarProviderOpenTabs:
|
||||
"resource:///modules/UrlbarProviderOpenTabs.sys.mjs",
|
||||
});
|
||||
|
@ -2218,16 +2216,18 @@
|
|||
browserSidebarContainer.className = "browserSidebarContainer";
|
||||
browserSidebarContainer.appendChild(browserContainer);
|
||||
|
||||
let visibility = Services.prefs.getStringPref(
|
||||
"sidebar.visibility",
|
||||
"always-show"
|
||||
);
|
||||
let expandOnHover = Services.prefs.getBoolPref(
|
||||
"sidebar.expandOnHover",
|
||||
false
|
||||
);
|
||||
if (visibility === "expand-on-hover" && expandOnHover) {
|
||||
SidebarController.toggleExpandOnHover(true);
|
||||
if (!isPreloadBrowser) {
|
||||
let visibility = Services.prefs.getStringPref(
|
||||
"sidebar.visibility",
|
||||
"always-show"
|
||||
);
|
||||
let expandOnHover = Services.prefs.getBoolPref(
|
||||
"sidebar.expandOnHover",
|
||||
false
|
||||
);
|
||||
if (visibility === "expand-on-hover" && expandOnHover) {
|
||||
SidebarController.toggleExpandOnHover(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent the superfluous initial load of a blank document
|
||||
|
@ -2588,6 +2588,7 @@
|
|||
index: tab._tPos + 1,
|
||||
userContextId: tab.userContextId,
|
||||
tabGroup: tab.group,
|
||||
focusUrlBar: true,
|
||||
});
|
||||
resolve(this.selectedBrowser);
|
||||
}),
|
||||
|
@ -2966,9 +2967,7 @@
|
|||
* switches windows).
|
||||
* Causes the group create UI to be displayed and telemetry events to be fired.
|
||||
* @param {string} [options.telemetryUserCreateSource]
|
||||
* The means by which the tab group was created.
|
||||
* @see TabGroupMetrics.METRIC_SOURCE for possible values.
|
||||
* Defaults to "unknown".
|
||||
* The means by which the tab group was created. Defaults to "unknown".
|
||||
*/
|
||||
addTabGroup(
|
||||
tabs,
|
||||
|
@ -3027,23 +3026,8 @@
|
|||
* The tab group to remove.
|
||||
* @param {object} [options]
|
||||
* Options to use when removing tabs. @see removeTabs for more info.
|
||||
* @param {boolean} [options.isUserTriggered=false]
|
||||
* Should be true if this group is being removed by an explicit
|
||||
* request from the user (as opposed to a group being removed
|
||||
* for technical reasons, such as when an already existing group
|
||||
* switches windows). This causes telemetry events to fire.
|
||||
* @param {string} [options.telemetrySource="unknown"]
|
||||
* The means by which the tab group was removed.
|
||||
* @see TabGroupMetrics.METRIC_SOURCE for possible values.
|
||||
* Defaults to "unknown".
|
||||
*/
|
||||
async removeTabGroup(
|
||||
group,
|
||||
options = {
|
||||
isUserTriggered: false,
|
||||
telemetrySource: this.TabGroupMetrics.METRIC_SOURCE.UNKNOWN,
|
||||
}
|
||||
) {
|
||||
async removeTabGroup(group, options = {}) {
|
||||
if (this.tabGroupMenu.panel.state != "closed") {
|
||||
this.tabGroupMenu.panel.hidePopup(options.animate);
|
||||
}
|
||||
|
@ -3077,8 +3061,6 @@
|
|||
bubbles: true,
|
||||
detail: {
|
||||
skipSessionStore: options.skipSessionStore,
|
||||
isUserTriggered: options.isUserTriggered,
|
||||
telemetrySource: options.telemetrySource,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
}
|
||||
);
|
||||
|
||||
const { TabGroupMetrics } = ChromeUtils.importESModule(
|
||||
"moz-src:///browser/components/tabbrowser/TabGroupMetrics.sys.mjs"
|
||||
);
|
||||
|
||||
class MozTabbrowserTabGroupMenu extends MozXULElement {
|
||||
static COLORS = [
|
||||
"blue",
|
||||
|
@ -50,17 +46,19 @@
|
|||
static AI_ICON = "chrome://global/skin/icons/highlights.svg";
|
||||
|
||||
static headerSection = /*html*/ `
|
||||
<html:div class="panel-header">
|
||||
<html:h1
|
||||
id="tab-group-editor-title-create"
|
||||
class="tab-group-create-mode-only"
|
||||
data-l10n-id="tab-group-editor-title-create">
|
||||
</html:h1>
|
||||
<html:h1
|
||||
id="tab-group-editor-title-edit"
|
||||
class="tab-group-edit-mode-only"
|
||||
data-l10n-id="tab-group-editor-title-edit">
|
||||
</html:h1>
|
||||
<html:div id="tab-group-default-header">
|
||||
<html:div class="panel-header" >
|
||||
<html:h1
|
||||
id="tab-group-editor-title-create"
|
||||
class="tab-group-create-mode-only"
|
||||
data-l10n-id="tab-group-editor-title-create">
|
||||
</html:h1>
|
||||
<html:h1
|
||||
id="tab-group-editor-title-edit"
|
||||
class="tab-group-edit-mode-only"
|
||||
data-l10n-id="tab-group-editor-title-edit">
|
||||
</html:h1>
|
||||
</html:div>
|
||||
</html:div>
|
||||
`;
|
||||
|
||||
|
@ -110,7 +108,6 @@
|
|||
<html:div class="panel-header">
|
||||
<html:h1 data-l10n-id="tab-group-editor-title-suggest"></html:h1>
|
||||
</html:div>
|
||||
<toolbarseparator />
|
||||
</html:div>
|
||||
`;
|
||||
|
||||
|
@ -169,7 +166,7 @@
|
|||
|
||||
static defaultActions = /*html*/ `
|
||||
<html:moz-button-group
|
||||
class="panel-body tab-group-create-actions tab-group-create-mode-only"
|
||||
class="tab-group-create-actions tab-group-create-mode-only"
|
||||
id="tab-group-default-actions">
|
||||
<html:moz-button
|
||||
id="tab-group-editor-button-cancel"
|
||||
|
@ -207,8 +204,8 @@
|
|||
ignorekeys="true"
|
||||
norolluponanchor="true">
|
||||
|
||||
<html:div id="tab-group-main">
|
||||
${this.headerSection}
|
||||
${this.suggestionsHeader}
|
||||
|
||||
<toolbarseparator />
|
||||
|
||||
|
@ -228,6 +225,8 @@
|
|||
/>
|
||||
</html:div>
|
||||
|
||||
|
||||
<html:div id="tab-group-main">
|
||||
<html:div
|
||||
class="panel-body tab-group-editor-swatches"
|
||||
role="radiogroup"
|
||||
|
@ -242,26 +241,22 @@
|
|||
|
||||
${this.suggestionsButton}
|
||||
|
||||
<html:p
|
||||
hidden="true"
|
||||
id="tab-group-suggestions-disclaimer"
|
||||
data-l10n-id="tab-group-suggestions-disclaimer">
|
||||
<a data-l10n-name="support" href="#"></a>
|
||||
</html:p>
|
||||
|
||||
<html:moz-button
|
||||
hidden="true"
|
||||
disabled="true"
|
||||
type="icon ghost"
|
||||
id="tab-group-suggestions-message"
|
||||
data-l10n-id="tab-group-editor-no-tabs-found">
|
||||
</html:moz-button>
|
||||
|
||||
<html:div id="tab-group-suggestions-message-container" hidden="true">
|
||||
<html:moz-button
|
||||
disabled="true"
|
||||
type="icon ghost"
|
||||
id="tab-group-suggestions-message"
|
||||
data-l10n-id="tab-group-editor-no-tabs-found-title">
|
||||
</html:moz-button>
|
||||
<html:p
|
||||
data-l10n-id="tab-group-editor-no-tabs-found-message">
|
||||
</html:p>
|
||||
</html:div>
|
||||
|
||||
${this.defaultActions}
|
||||
|
||||
</html:div>
|
||||
|
||||
${this.suggestionsHeader}
|
||||
|
||||
${this.loadingSection}
|
||||
${this.loadingActions}
|
||||
${this.suggestionsSection}
|
||||
|
@ -311,6 +306,7 @@
|
|||
#suggestionState = MozTabbrowserTabGroupMenu.State.CREATE_STANDARD_INITIAL;
|
||||
#suggestionsHeading;
|
||||
#suggestionsHeader;
|
||||
#defaultHeader;
|
||||
#suggestionsContainer;
|
||||
#suggestions;
|
||||
#suggestionButton;
|
||||
|
@ -319,7 +315,7 @@
|
|||
#suggestionsLoading;
|
||||
#selectSuggestionsToggle;
|
||||
#suggestionsMessage;
|
||||
#suggestionsDisclaimer;
|
||||
#suggestionsMessageContainer;
|
||||
#selectedSuggestedTabs = [];
|
||||
#suggestedMlLabel;
|
||||
#hasSuggestedMlTabs = false;
|
||||
|
@ -379,7 +375,7 @@
|
|||
this.#swatchesContainer = this.querySelector(
|
||||
".tab-group-editor-swatches"
|
||||
);
|
||||
|
||||
this.#defaultHeader = this.querySelector("#tab-group-default-header");
|
||||
this.#defaultActions = this.querySelector("#tab-group-default-actions");
|
||||
this.#tabGroupMain = this.querySelector("#tab-group-main");
|
||||
this.#initSuggestions();
|
||||
|
@ -445,10 +441,7 @@
|
|||
document
|
||||
.getElementById("tabGroupEditor_deleteGroup")
|
||||
.addEventListener("command", () => {
|
||||
gBrowser.removeTabGroup(this.activeGroup, {
|
||||
isUserTriggered: true,
|
||||
telemetrySource: TabGroupMetrics.METRIC_SOURCE.TAB_GROUP_MENU,
|
||||
});
|
||||
gBrowser.removeTabGroup(this.activeGroup);
|
||||
});
|
||||
|
||||
this.panel.addEventListener("popupshown", this);
|
||||
|
@ -474,6 +467,7 @@
|
|||
}
|
||||
|
||||
#initSmartTabGroupsOptin() {
|
||||
this.#handleMLOptinTelemetry("step0-optin-shown");
|
||||
this.suggestionState = MozTabbrowserTabGroupMenu.State.OPTIN;
|
||||
|
||||
// Init optin component
|
||||
|
@ -482,23 +476,48 @@
|
|||
"tab-group-suggestions-optin-title";
|
||||
this.#suggestionsOptin.messageL10nId =
|
||||
"tab-group-suggestions-optin-message";
|
||||
this.#suggestionsOptin.footerMessageL10nId =
|
||||
"tab-group-suggestions-optin-message-footer";
|
||||
this.#suggestionsOptin.headingIcon = MozTabbrowserTabGroupMenu.AI_ICON;
|
||||
|
||||
// On Confirm
|
||||
this.#suggestionsOptin.addEventListener("MlModelOptinConfirm", () => {
|
||||
this.#handleMLOptinTelemetry("step1-optin-confirmed");
|
||||
Services.prefs.setBoolPref("browser.tabs.groups.smart.optin", true);
|
||||
this.#handleFirstDownloadAndSuggest();
|
||||
});
|
||||
|
||||
// On Deny
|
||||
this.#suggestionsOptin.addEventListener("MlModelOptinDeny", () => {
|
||||
this.SmartTabGroupingManager.terminateProcess();
|
||||
this.#handleMLOptinTelemetry("step1-optin-denied");
|
||||
this.#smartTabGroupingManager.terminateProcess();
|
||||
this.suggestionState = this.createMode
|
||||
? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL
|
||||
: MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL;
|
||||
this.#setFormToDisabled(false);
|
||||
});
|
||||
|
||||
// On Cancel Model Download
|
||||
this.#suggestionsOptin.addEventListener(
|
||||
"MlModelOptinCancelDownload",
|
||||
() => {
|
||||
this.#handleMLOptinTelemetry("step2-optin-cancel-download");
|
||||
this.#smartTabGroupingManager.terminateProcess();
|
||||
this.suggestionState = this.createMode
|
||||
? MozTabbrowserTabGroupMenu.State.CREATE_AI_INITIAL
|
||||
: MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL;
|
||||
this.#setFormToDisabled(false);
|
||||
}
|
||||
);
|
||||
|
||||
// On Footer link click
|
||||
this.#suggestionsOptin.addEventListener(
|
||||
"MlModelOptinFooterLinkClick",
|
||||
() => {
|
||||
openTrustedLinkIn("about:preferences", "tab");
|
||||
}
|
||||
);
|
||||
|
||||
this.#suggestionsOptinContainer.appendChild(this.#suggestionsOptin);
|
||||
}
|
||||
|
||||
|
@ -540,15 +559,15 @@
|
|||
this.#selectSuggestionsToggle.addEventListener("click", () => {
|
||||
this.#handleSelectToggle();
|
||||
});
|
||||
this.#suggestionsMessageContainer = this.querySelector(
|
||||
"#tab-group-suggestions-message-container"
|
||||
);
|
||||
this.#suggestionsMessage = this.querySelector(
|
||||
"#tab-group-suggestions-message"
|
||||
);
|
||||
this.#suggestionsMessage.iconSrc = this.smartTabGroupsEnabled
|
||||
? MozTabbrowserTabGroupMenu.AI_ICON
|
||||
: "";
|
||||
this.#suggestionsDisclaimer = this.querySelector(
|
||||
"#tab-group-suggestions-disclaimer"
|
||||
);
|
||||
this.#createSuggestionsButton = this.querySelector(
|
||||
"#tab-group-create-suggestions-button"
|
||||
);
|
||||
|
@ -726,6 +745,7 @@
|
|||
this.#panel.openPopup(group.firstChild, {
|
||||
position: this.#panelPosition,
|
||||
});
|
||||
// If user has opted in kick off label generation
|
||||
this.smartTabGroupsOptin && this.#initMlGroupLabel();
|
||||
}
|
||||
|
||||
|
@ -806,10 +826,19 @@
|
|||
this.dispatchEvent(
|
||||
new CustomEvent("TabGroupCreateDone", { bubbles: true })
|
||||
);
|
||||
if (
|
||||
this.smartTabGroupsEnabled &&
|
||||
(this.#suggestedMlLabel || this.#hasSuggestedMlTabs)
|
||||
) {
|
||||
this.#handleMlTelemetry("save-popup-hidden");
|
||||
}
|
||||
} else {
|
||||
this.activeGroup.ungroupTabs();
|
||||
}
|
||||
}
|
||||
if (this.#nameField.disabled) {
|
||||
this.#setFormToDisabled(false);
|
||||
}
|
||||
this.activeGroup = null;
|
||||
this.#smartTabGroupingManager.terminateProcess();
|
||||
}
|
||||
|
@ -922,12 +951,12 @@
|
|||
|
||||
async #handleFirstDownloadAndSuggest() {
|
||||
this.#setFormToDisabled(true);
|
||||
this.#suggestionsOptin.isLoading = true;
|
||||
this.#suggestionsOptin.headingL10nId =
|
||||
"tab-group-suggestions-optin-title-download";
|
||||
this.#suggestionsOptin.messageL10nId =
|
||||
"tab-group-suggestions-optin-message-download";
|
||||
this.#suggestionsOptin.headingIcon = "";
|
||||
this.#suggestionsOptin.isLoading = true;
|
||||
|
||||
// Init progress with value to show determiniate progress
|
||||
this.#suggestionsOptin.progressStatus = 0;
|
||||
|
@ -938,8 +967,10 @@
|
|||
// Clean up optin UI
|
||||
this.#setFormToDisabled(false);
|
||||
this.#suggestionsOptin.isHidden = true;
|
||||
this.#suggestionsOptin.isLoading = false;
|
||||
|
||||
// Continue on with the suggest flow
|
||||
this.#handleMLOptinTelemetry("step3-optin-completed");
|
||||
this.#initMlGroupLabel();
|
||||
this.#handleSmartSuggest();
|
||||
}
|
||||
|
@ -957,6 +988,13 @@
|
|||
this.suggestionState = this.#createMode
|
||||
? MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_NO_SUGGESTIONS
|
||||
: MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_NO_SUGGESTIONS;
|
||||
|
||||
// there's no "save" button from the edit ai interaction with
|
||||
// no tab suggestions, so we need to capture here
|
||||
if (!this.#createMode) {
|
||||
this.#hasSuggestedMlTabs = true;
|
||||
this.#handleMlTelemetry("save");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -975,7 +1013,7 @@
|
|||
|
||||
/**
|
||||
* Sends Glean metrics if smart tab grouping is enabled
|
||||
* @param {string} action "save" or "cancel"
|
||||
* @param {string} action "save", "save-popup-hidden" or "cancel"
|
||||
*/
|
||||
#handleMlTelemetry(action) {
|
||||
if (!this.smartTabGroupsEnabled) {
|
||||
|
@ -987,6 +1025,7 @@
|
|||
numTabsInGroup: this.#activeGroup.tabs.length,
|
||||
mlLabel: this.#suggestedMlLabel,
|
||||
userLabel: this.#nameField.value,
|
||||
id: this.#activeGroup.id,
|
||||
});
|
||||
this.#suggestedMlLabel = "";
|
||||
}
|
||||
|
@ -999,11 +1038,22 @@
|
|||
numTabsApproved: this.#selectedSuggestedTabs.length,
|
||||
numTabsRemoved:
|
||||
this.#suggestedTabs.length - this.#selectedSuggestedTabs.length,
|
||||
id: this.#activeGroup.id,
|
||||
});
|
||||
this.#hasSuggestedMlTabs = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends Glean metrics for opt-in UI flow
|
||||
* @param {string} step contains step number and description of flow
|
||||
*/
|
||||
#handleMLOptinTelemetry(step) {
|
||||
Glean.tabgroup.smartTabOptin.record({
|
||||
step,
|
||||
});
|
||||
}
|
||||
|
||||
#createRow(tab, index) {
|
||||
// Create Row
|
||||
let row = document.createXULElement("toolbaritem");
|
||||
|
@ -1084,12 +1134,8 @@
|
|||
this.#setElementVisibility(this.#suggestionButton, value);
|
||||
}
|
||||
|
||||
#showSuggestionMessage(value) {
|
||||
this.#setElementVisibility(this.#suggestionsMessage, value);
|
||||
}
|
||||
|
||||
#showSuggestionsDisclaimer(value) {
|
||||
this.#setElementVisibility(this.#suggestionsDisclaimer, value);
|
||||
#showSuggestionMessageContainer(value) {
|
||||
this.#setElementVisibility(this.#suggestionsMessageContainer, value);
|
||||
}
|
||||
|
||||
#showSuggestionsSeparator(value) {
|
||||
|
@ -1110,19 +1156,21 @@
|
|||
}
|
||||
|
||||
/**
|
||||
* Unique state setter for a "3rd" panel state while in Edit Mode
|
||||
* Unique state setter for a "3rd" panel state while in suggest Mode
|
||||
* that just shows suggestions and hides the majority of the panel
|
||||
* @param {boolean} value
|
||||
*/
|
||||
#setEditModeSuggestionState(value) {
|
||||
#setSuggestModeSuggestionState(value) {
|
||||
this.#setElementVisibility(this.#suggestionsHeader, !value);
|
||||
this.#setElementVisibility(this.#tabGroupMain, !value);
|
||||
this.#setElementVisibility(this.#suggestionsHeading, value);
|
||||
this.#setElementVisibility(this.#defaultHeader, !value);
|
||||
this.#panel.classList.toggle("tab-group-editor-panel-expanded", value);
|
||||
}
|
||||
|
||||
#resetCommonUI() {
|
||||
this.#setLoadingState(false);
|
||||
this.#setEditModeSuggestionState(false);
|
||||
this.#setSuggestModeSuggestionState(false);
|
||||
this.#suggestedTabs = [];
|
||||
this.#selectedSuggestedTabs = [];
|
||||
this.#suggestions.innerHTML = "";
|
||||
|
@ -1137,8 +1185,7 @@
|
|||
this.#resetCommonUI();
|
||||
this.#showDefaultTabGroupActions(true);
|
||||
this.#showSuggestionButton(false);
|
||||
this.#showSuggestionMessage(false);
|
||||
this.#showSuggestionsDisclaimer(false);
|
||||
this.#showSuggestionMessageContainer(false);
|
||||
this.#showSuggestionsSeparator(false);
|
||||
break;
|
||||
|
||||
|
@ -1147,9 +1194,8 @@
|
|||
this.#resetCommonUI();
|
||||
this.#showSuggestionButton(true);
|
||||
this.#showDefaultTabGroupActions(true);
|
||||
this.#showSuggestionMessage(false);
|
||||
this.#showSuggestionMessageContainer(false);
|
||||
this.#setSelectToggleState("deselect");
|
||||
this.#showSuggestionsDisclaimer(true);
|
||||
this.#setSuggestionsButtonCreateModeState(true);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
break;
|
||||
|
@ -1159,8 +1205,7 @@
|
|||
.CREATE_AI_INITIAL_SUGGESTIONS_DISABLED:
|
||||
this.#resetCommonUI();
|
||||
this.#showSuggestionButton(false);
|
||||
this.#showSuggestionsDisclaimer(false);
|
||||
this.#showSuggestionMessage(true);
|
||||
this.#showSuggestionMessageContainer(true);
|
||||
this.#showDefaultTabGroupActions(true);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
break;
|
||||
|
@ -1172,12 +1217,13 @@
|
|||
this.#showSuggestionButton(false);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
this.#showDefaultTabGroupActions(false);
|
||||
this.#setSuggestModeSuggestionState(true);
|
||||
break;
|
||||
|
||||
// CREATE AI WITH NO SUGGESTIONS
|
||||
case MozTabbrowserTabGroupMenu.State.CREATE_AI_WITH_NO_SUGGESTIONS:
|
||||
this.#setLoadingState(false);
|
||||
this.#showSuggestionMessage(true);
|
||||
this.#showSuggestionMessageContainer(true);
|
||||
this.#showDefaultTabGroupActions(true);
|
||||
this.#showSuggestionButton(false);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
|
@ -1187,20 +1233,18 @@
|
|||
case MozTabbrowserTabGroupMenu.State.EDIT_STANDARD_INITIAL:
|
||||
this.#resetCommonUI();
|
||||
this.#showSuggestionButton(false);
|
||||
this.#showSuggestionMessage(false);
|
||||
this.#showSuggestionMessageContainer(false);
|
||||
this.#showDefaultTabGroupActions(false);
|
||||
this.#showSuggestionsDisclaimer(false);
|
||||
this.#showSuggestionsSeparator(false);
|
||||
break;
|
||||
|
||||
// EDIT AI INITIAL
|
||||
case MozTabbrowserTabGroupMenu.State.EDIT_AI_INITIAL:
|
||||
this.#resetCommonUI();
|
||||
this.#showSuggestionMessage(false);
|
||||
this.#showSuggestionMessageContainer(false);
|
||||
this.#setSelectToggleState("deselect");
|
||||
this.#showSuggestionButton(true);
|
||||
this.#showDefaultTabGroupActions(false);
|
||||
this.#showSuggestionsDisclaimer(false);
|
||||
this.#setSuggestionsButtonCreateModeState(false);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
break;
|
||||
|
@ -1209,10 +1253,9 @@
|
|||
case MozTabbrowserTabGroupMenu.State
|
||||
.EDIT_AI_INITIAL_SUGGESTIONS_DISABLED:
|
||||
this.#resetCommonUI();
|
||||
this.#showSuggestionMessage(true);
|
||||
this.#showSuggestionMessageContainer(true);
|
||||
this.#showSuggestionButton(false);
|
||||
this.#showDefaultTabGroupActions(false);
|
||||
this.#showSuggestionsDisclaimer(false);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
break;
|
||||
|
||||
|
@ -1220,14 +1263,14 @@
|
|||
case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_SUGGESTIONS:
|
||||
this.#setLoadingState(false);
|
||||
this.#showSmartSuggestionsContainer(true);
|
||||
this.#setEditModeSuggestionState(true);
|
||||
this.#setSuggestModeSuggestionState(true);
|
||||
this.#showSuggestionsSeparator(false);
|
||||
break;
|
||||
|
||||
// EDIT AI WITH NO SUGGESTIONS
|
||||
case MozTabbrowserTabGroupMenu.State.EDIT_AI_WITH_NO_SUGGESTIONS:
|
||||
this.#setLoadingState(false);
|
||||
this.#showSuggestionMessage(true);
|
||||
this.#showSuggestionMessageContainer(true);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
break;
|
||||
|
||||
|
@ -1235,8 +1278,7 @@
|
|||
case MozTabbrowserTabGroupMenu.State.LOADING:
|
||||
this.#showDefaultTabGroupActions(false);
|
||||
this.#showSuggestionButton(false);
|
||||
this.#showSuggestionsDisclaimer(false);
|
||||
this.#showSuggestionMessage(false);
|
||||
this.#showSuggestionMessageContainer(false);
|
||||
this.#setLoadingState(true);
|
||||
this.#showSuggestionsSeparator(true);
|
||||
this.#showDefaultTabGroupActions(false);
|
||||
|
@ -1249,7 +1291,6 @@
|
|||
|
||||
case MozTabbrowserTabGroupMenu.State.OPTIN:
|
||||
this.#showSuggestionButton(false);
|
||||
this.#showSuggestionsDisclaimer(false);
|
||||
this.#showDefaultTabGroupActions(false);
|
||||
break;
|
||||
}
|
||||
|
|
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