Update On Fri Sep 20 20:50:58 CEST 2024

This commit is contained in:
github-action[bot] 2024-09-20 20:50:59 +02:00
parent 887331eb56
commit 1940c09587
1973 changed files with 53834 additions and 34177 deletions

41
Cargo.lock generated
View file

@ -1374,17 +1374,6 @@ dependencies = [
"syn",
]
[[package]]
name = "derive_common"
version = "0.0.1"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "derive_more"
version = "0.99.999"
@ -2271,6 +2260,7 @@ dependencies = [
"fog-gtest",
"gecko-fuzz-targets",
"gkrust-shared",
"kvstore-gtest",
"l10nregistry-ffi-gtest",
"lmdb-rkv-sys",
"moz_task-gtest",
@ -3290,6 +3280,7 @@ name = "kvstore"
version = "0.1.0"
dependencies = [
"atomic_refcell",
"chrono",
"crossbeam-utils",
"cstr",
"futures",
@ -3311,6 +3302,16 @@ dependencies = [
"xpcom",
]
[[package]]
name = "kvstore-gtest"
version = "0.1.0"
dependencies = [
"kvstore",
"moz_task",
"rusqlite",
"tempfile",
]
[[package]]
name = "l10nregistry"
version = "0.3.0"
@ -4050,7 +4051,7 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
[[package]]
name = "naga"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"arrayvec",
"bit-set",
@ -5251,7 +5252,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.22.0"
version = "0.25.0"
dependencies = [
"bitflags 2.6.0",
"cssparser",
@ -5402,7 +5403,7 @@ dependencies = [
[[package]]
name = "servo_arc"
version = "0.1.1"
version = "0.4.0"
dependencies = [
"stable_deref_trait",
]
@ -5679,7 +5680,6 @@ name = "style_derive"
version = "0.0.1"
dependencies = [
"darling",
"derive_common",
"proc-macro2",
"quote",
"syn",
@ -5971,7 +5971,7 @@ dependencies = [
[[package]]
name = "to_shmem"
version = "0.0.1"
version = "0.1.0"
dependencies = [
"cssparser",
"servo_arc",
@ -5982,10 +5982,9 @@ dependencies = [
[[package]]
name = "to_shmem_derive"
version = "0.0.1"
version = "0.1.0"
dependencies = [
"darling",
"derive_common",
"proc-macro2",
"quote",
"syn",
@ -6822,7 +6821,7 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"arrayvec",
"bit-vec",
@ -6847,7 +6846,7 @@ dependencies = [
[[package]]
name = "wgpu-hal"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"android_system_properties",
"arrayvec",
@ -6886,7 +6885,7 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"bitflags 2.6.0",
"js-sys",

View file

@ -90,13 +90,15 @@
// Do tests
function doTests() {
// Front end stuff sometimes likes to stuff things in the hidden window(s)
// in which case we should repress all accessibles for those.
if (Services.appShell.hasHiddenWindow) {
// Front end stuff sometimes likes to stuff things in the hidden window(s)
// in which case we should repress all accessibles for those.
// Try to create an accessible for the hidden window's document.
let doc = Services.appShell.hiddenDOMWindow.document;
let hiddenDocAcc = gAccService.getAccessibleFor(doc);
ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible.");
// Try to create an accessible for the hidden window's document.
let doc = Services.appShell.hiddenDOMWindow.document;
let hiddenDocAcc = gAccService.getAccessibleFor(doc);
ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible.");
}
const gQueue = new eventQueue();
gQueue.push(new openDialogWnd("about:about"));

View file

@ -107,13 +107,15 @@
// Do tests
function doTests() {
// Front end stuff sometimes likes to stuff things in the hidden window(s)
// in which case we should repress all accessibles for those.
if (Services.appShell.hasHiddenWindow) {
// Front end stuff sometimes likes to stuff things in the hidden window(s)
// in which case we should repress all accessibles for those.
// Try to create an accessible for the hidden window's document.
let doc = Services.appShell.hiddenDOMWindow.document;
let hiddenDocAcc = gAccService.getAccessibleFor(doc);
ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible.");
// Try to create an accessible for the hidden window's document.
let doc = Services.appShell.hiddenDOMWindow.document;
let hiddenDocAcc = gAccService.getAccessibleFor(doc);
ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible.");
}
const gQueue = new eventQueue();
gQueue.push(new openWndShutdownDoc("../../events/docload/docload_wnd.html"));

View file

@ -770,11 +770,7 @@ pref("browser.search.separatePrivateDefault.ui.enabled", false);
pref("browser.search.separatePrivateDefault.ui.banner.max", 0);
// Enables search SERP telemetry page categorization.
#ifdef NIGHTLY_BUILD
pref("browser.search.serpEventTelemetryCategorization.enabled", true);
#else
pref("browser.search.serpEventTelemetryCategorization.enabled", false);
#endif
// A count of Glean SERP categorization event metrics that have been recorded
// but not yet submitted in a ping. Needed to prevent sending a ping with only

View file

@ -335,10 +335,6 @@ var gBrowserInit = {
this._cancelDelayedStartup();
// Bug 1531854 - The hidden window is force-created here
// until all of its dependencies are handled.
Services.appShell.hiddenDOMWindow;
gBrowser.addEventListener(
"PermissionStateChange",
function () {

View file

@ -6204,15 +6204,34 @@ var gPrivateBrowsingUI = {
}
if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
// Adjust the New Window menu entries
let newWindow = document.getElementById("menu_newNavigator");
let newPrivateWindow = document.getElementById("menu_newPrivateWindow");
if (newWindow && newPrivateWindow) {
newPrivateWindow.hidden = true;
newWindow.label = newPrivateWindow.label;
newWindow.accessKey = newPrivateWindow.accessKey;
newWindow.command = newPrivateWindow.command;
}
let hideNewWindowItem = (windowItem, privateWindowItem) => {
// In permanent browsing mode command "cmd_newNavigator" should act the
// same as "Tools:PrivateBrowsing".
// So we hide the redundant private window item. But we also rename the
// "new window" item to be "new private window".
// NOTE: We choose to hide privateWindowItem rather than windowItem so
// that we still show the "key" for "cmd_newNavigator" (Ctrl+N) rather
// than (Ctrl+Shift+P).
privateWindowItem.hidden = true;
windowItem.setAttribute(
"data-l10n-id",
privateWindowItem.getAttribute("data-l10n-id")
);
};
// Adjust the File menu items.
hideNewWindowItem(
document.getElementById("menu_newNavigator"),
document.getElementById("menu_newPrivateWindow")
);
// Adjust the App menu items.
hideNewWindowItem(
PanelMultiView.getViewNode(document, "appMenu-new-window-button2"),
PanelMultiView.getViewNode(
document,
"appMenu-new-private-window-button2"
)
);
}
},
};

View file

@ -96,6 +96,7 @@
<link rel="localization" href="preview/profiles.ftl"/>
<link rel="localization" href="preview/onboarding.ftl"/>
<link rel="localization" href="preview/firefoxRelayToAllBrowsers.ftl"/>
<link rel="localization" href="preview/tabGroups.ftl"/>
<title data-l10n-id="browser-main-window-title"></title>

View file

@ -7,6 +7,10 @@
<menupopup id="tabContextMenu">
<menuitem id="context_openANewTab" data-lazy-l10n-id="tab-context-new-tab"/>
<menuitem id="context_addTabToNewGroup"
data-lazy-l10n-id="tab-context-add-tab-to-new-group"
data-l10n-args='{"tabCount": 1}'
hidden="true"/>
<menuseparator/>
<menuitem id="context_reloadTab" data-lazy-l10n-id="reload-tab"/>
<menuitem id="context_reloadSelectedTabs" data-lazy-l10n-id="reload-tabs" hidden="true"/>

View file

@ -16,6 +16,13 @@ document.addEventListener(
case "context_openANewTab":
gBrowser.addAdjacentNewTab(TabContextMenu.contextTab);
break;
case "context_addTabToNewGroup":
if (gBrowser.selectedTabs.includes(TabContextMenu.contextTab)) {
gBrowser.addTabGroup("red", "", gBrowser.selectedTabs);
} else {
gBrowser.addTabGroup("red", "", [TabContextMenu.contextTab]);
}
break;
case "context_reloadTab":
gBrowser.reloadTab(TabContextMenu.contextTab);
break;

View file

@ -521,14 +521,6 @@
<toolbaritem id="PanelUI-button"
removable="false">
<toolbarbutton id="ion-button"
class="toolbarbutton-1"
delegatesanchor="true"
hidden="true"
badged="true"
tooltiptext="Ion"
onmousedown="switchToTabHavingURI('about:ion', true);"
onkeypress="switchToTabHavingURI('about:ion', true);"/>
<toolbarbutton id="PanelUI-menu-button"
class="toolbarbutton-1"
delegatesanchor="true"

View file

@ -1,133 +1,218 @@
// Make sure that we can open private browsing windows
/* 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/. */
function test() {
waitForExplicitFinish();
var nonPrivateWin = OpenBrowserWindow();
ok(
!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin),
add_task(async function testOpenBrowserWindow() {
let win = OpenBrowserWindow();
Assert.ok(
!PrivateBrowsingUtils.isWindowPrivate(win),
"OpenBrowserWindow() should open a normal window"
);
nonPrivateWin.close();
await BrowserTestUtils.closeWindow(win);
var privateWin = OpenBrowserWindow({ private: true });
ok(
PrivateBrowsingUtils.isWindowPrivate(privateWin),
win = OpenBrowserWindow({ private: true });
Assert.ok(
PrivateBrowsingUtils.isWindowPrivate(win),
"OpenBrowserWindow({private: true}) should open a private window"
);
await BrowserTestUtils.closeWindow(win);
nonPrivateWin = OpenBrowserWindow({ private: false });
ok(
!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin),
win = OpenBrowserWindow({ private: false });
Assert.ok(
!PrivateBrowsingUtils.isWindowPrivate(win),
"OpenBrowserWindow({private: false}) should open a normal window"
);
nonPrivateWin.close();
await BrowserTestUtils.closeWindow(win);
whenDelayedStartupFinished(privateWin, function () {
nonPrivateWin = privateWin.OpenBrowserWindow({ private: false });
ok(
!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin),
"privateWin.OpenBrowserWindow({private: false}) should open a normal window"
);
nonPrivateWin.close();
[
{
normal: "menu_newNavigator",
private: "menu_newPrivateWindow",
accesskey: true,
},
{
normal: "appmenu_newNavigator",
private: "appmenu_newPrivateWindow",
accesskey: false,
},
].forEach(function (menu) {
let newWindow = privateWin.document.getElementById(menu.normal);
let newPrivateWindow = privateWin.document.getElementById(menu.private);
if (newWindow && newPrivateWindow) {
ok(
!newPrivateWindow.hidden,
"New Private Window menu item should be hidden"
);
isnot(
newWindow.label,
newPrivateWindow.label,
"New Window's label shouldn't be overwritten"
);
if (menu.accesskey) {
isnot(
newWindow.accessKey,
newPrivateWindow.accessKey,
"New Window's accessKey shouldn't be overwritten"
);
}
isnot(
newWindow.command,
newPrivateWindow.command,
"New Window's command shouldn't be overwritten"
);
}
});
is(
privateWin.gBrowser.tabs[0].label,
"New Private Tab",
"New tabs in the private browsing windows should have 'New Private Tab' as the title."
);
privateWin.close();
Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true);
privateWin = OpenBrowserWindow({ private: true });
whenDelayedStartupFinished(privateWin, function () {
[
{
normal: "menu_newNavigator",
private: "menu_newPrivateWindow",
accessKey: true,
},
{
normal: "appmenu_newNavigator",
private: "appmenu_newPrivateWindow",
accessKey: false,
},
].forEach(function (menu) {
let newWindow = privateWin.document.getElementById(menu.normal);
let newPrivateWindow = privateWin.document.getElementById(menu.private);
if (newWindow && newPrivateWindow) {
ok(
newPrivateWindow.hidden,
"New Private Window menu item should be hidden"
);
is(
newWindow.label,
newPrivateWindow.label,
"New Window's label should be overwritten"
);
if (menu.accesskey) {
is(
newWindow.accessKey,
newPrivateWindow.accessKey,
"New Window's accessKey should be overwritten"
);
}
is(
newWindow.command,
newPrivateWindow.command,
"New Window's command should be overwritten"
);
}
});
is(
privateWin.gBrowser.tabs[0].label,
"New Tab",
"Normal tab title is used also in the permanent private browsing mode."
);
privateWin.close();
Services.prefs.clearUserPref("browser.privatebrowsing.autostart");
finish();
});
// In permanent browsing mode.
await SpecialPowers.pushPrefEnv({
set: [["browser.privatebrowsing.autostart", true]],
});
win = OpenBrowserWindow();
Assert.ok(
PrivateBrowsingUtils.isWindowPrivate(win),
"OpenBrowserWindow() in PBM should open a private window"
);
await BrowserTestUtils.closeWindow(win);
win = OpenBrowserWindow({ private: true });
Assert.ok(
PrivateBrowsingUtils.isWindowPrivate(win),
"OpenBrowserWindow({private: true}) in PBM should open a private window"
);
await BrowserTestUtils.closeWindow(win);
win = OpenBrowserWindow({ private: false });
Assert.ok(
PrivateBrowsingUtils.isWindowPrivate(win),
"OpenBrowserWindow({private: false}) in PBM should open a private window"
);
await BrowserTestUtils.closeWindow(win);
await SpecialPowers.popPrefEnv();
});
/**
* Check that the "new window" menu items have the expected properties.
*
* @param {Element} newWindowItem - The "new window" item to check.
* @param {Element} privateWindowItem - The "new private window" item to check.
* @param {Object} expect - The expected properties.
* @param {boolean} expect.privateVisible - Whether we expect the private item
* to be visible or not.
* @param {string} expect.newWindowL10nId - The expected string ID used by the
* "new window" item.
* @param {string} expect.privateWindowL10nId - The expected string ID used by
* the "new private window" item.
* @param {boolean} [useIsVisible=true] - Whether to test the "true" visibility
* of the item. Otherwise only the "hidden" attribute is checked.
*/
function assertMenuItems(
newWindowItem,
privateWindowItem,
expect,
useIsVisible = true
) {
Assert.ok(newWindowItem);
Assert.ok(privateWindowItem);
if (useIsVisible) {
Assert.ok(
BrowserTestUtils.isVisible(newWindowItem),
"New window item should be visible"
);
} else {
// The application menu is not accessible on macOS, just check the hidden
// attribute.
Assert.ok(!newWindowItem.hidden, "New window item should be visible");
}
Assert.equal(
newWindowItem.getAttribute("key"),
"key_newNavigator",
"New window item should use the same key"
);
Assert.equal(
newWindowItem.getAttribute("data-l10n-id"),
expect.newWindowL10nId
);
if (!expect.privateVisible) {
if (useIsVisible) {
Assert.ok(
BrowserTestUtils.isHidden(privateWindowItem),
"Private window item should be hidden"
);
} else {
Assert.ok(
privateWindowItem.hidden,
"Private window item should be hidden"
);
}
// Don't check attributes since hidden.
} else {
if (useIsVisible) {
Assert.ok(
BrowserTestUtils.isVisible(privateWindowItem),
"Private window item should be visible"
);
} else {
Assert.ok(
!privateWindowItem.hidden,
"Private window item should be visible"
);
}
Assert.equal(
privateWindowItem.getAttribute("key"),
"key_privatebrowsing",
"Private window item should use the same key"
);
Assert.equal(
privateWindowItem.getAttribute("data-l10n-id"),
expect.privateWindowL10nId
);
}
}
/**
* Check that a window has the expected "new window" items in the "File" and app
* menus.
*
* @param {Window} win - The window to check.
* @param {boolean} expectBoth - Whether we expect the window to contain both
* "new window" and "new private window" as separate controls.
*/
async function checkWindowMenus(win, expectBoth) {
// Check the File menu.
assertMenuItems(
win.document.getElementById("menu_newNavigator"),
win.document.getElementById("menu_newPrivateWindow"),
{
privateVisible: expectBoth,
// If in permanent private browsing, expect the new window item to use the
// "New private window" string.
newWindowL10nId: expectBoth
? "menu-file-new-window"
: "menu-file-new-private-window",
privateWindowL10nId: "menu-file-new-private-window",
},
// The file menu is difficult to open cross-platform, so we do not open it
// for this test.
false
);
// Open the app menu.
let appMenuButton = win.document.getElementById("PanelUI-menu-button");
let appMenu = win.document.getElementById("appMenu-popup");
let menuShown = BrowserTestUtils.waitForEvent(appMenu, "popupshown");
EventUtils.synthesizeMouseAtCenter(appMenuButton, {}, win);
await menuShown;
// Check the app menu.
assertMenuItems(
win.document.getElementById("appMenu-new-window-button2"),
win.document.getElementById("appMenu-new-private-window-button2"),
{
privateVisible: expectBoth,
// If in permanent private browsing, expect the new window item to use the
// "New private window" string.
newWindowL10nId: expectBoth
? "appmenuitem-new-window"
: "appmenuitem-new-private-window",
privateWindowL10nId: "appmenuitem-new-private-window",
}
);
appMenu.hidePopup();
}
add_task(async function testNewWindowMenuItems() {
// In non-private window, expect both menu items.
let win = await BrowserTestUtils.openNewBrowserWindow({
private: false,
});
await checkWindowMenus(win, true);
Assert.equal(win.gBrowser.currentURI.spec, "about:blank");
await BrowserTestUtils.closeWindow(win);
// In non-permanent private window, still expect both menu items.
win = await BrowserTestUtils.openNewBrowserWindow({
private: true,
});
await checkWindowMenus(win, true);
Assert.equal(win.gBrowser.currentURI.spec, "about:privatebrowsing");
await BrowserTestUtils.closeWindow(win);
// In permanent private browsing, expect only one menu item.
await SpecialPowers.pushPrefEnv({
set: [["browser.privatebrowsing.autostart", true]],
});
win = await BrowserTestUtils.openNewBrowserWindow();
await checkWindowMenus(win, false);
Assert.equal(win.gBrowser.currentURI.spec, "about:blank");
await BrowserTestUtils.closeWindow(win);
await SpecialPowers.popPrefEnv();
});

View file

@ -35,6 +35,8 @@ skip-if = [
["browser_hidden_browser_vsync.js"]
["browser_hiddenwindow_existence.js"]
["browser_panel_vsync.js"]
support-files = ["!/browser/components/downloads/test/browser/head.js"]
@ -64,12 +66,6 @@ run-if = [
"nightly_build", # Requires StartupRecorder.sys.mjs, which isn't shipped everywhere by default
]
["browser_startup_hiddenwindow.js"]
run-if = [
"os == 'linux'",
"os == 'win'",
]
["browser_tabclose.js"]
skip-if = [
"os == 'linux' && devedition", # Bug 1737131

View file

@ -0,0 +1,18 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_hiddenwindow_existence() {
switch (AppConstants.platform) {
case "macosx":
is(Services.appShell.hasHiddenWindow, true, "Hidden window exists");
break;
default:
is(
Services.appShell.hasHiddenWindow,
false,
"Hidden window does not exist"
);
}
});

View file

@ -1,47 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function () {
if (
!AppConstants.NIGHTLY_BUILD &&
!AppConstants.MOZ_DEV_EDITION &&
!AppConstants.DEBUG
) {
ok(
!("@mozilla.org/test/startuprecorder;1" in Cc),
"the startup recorder component shouldn't exist in this non-nightly/non-devedition/" +
"non-debug build."
);
return;
}
let startupRecorder =
Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
await startupRecorder.done;
let extras = Cu.cloneInto(startupRecorder.data.extras, {});
let phasesExpectations = {
"before profile selection": false,
"before opening first browser window": false,
"before first paint": AppConstants.platform === "macosx",
// Bug 1531854
"before handling user events": true,
"before becoming idle": true,
};
for (let phase in extras) {
if (!(phase in phasesExpectations)) {
ok(false, `Startup phase '${phase}' should be specified.`);
continue;
}
is(
extras[phase].hiddenWindowLoaded,
phasesExpectations[phase],
`Hidden window loaded at '${phase}': ${phasesExpectations[phase]}`
);
}
});

View file

@ -66,7 +66,6 @@ add_task(async function chromeUITest() {
"compat",
"config",
"downloads",
"ion",
"license",
"logins",
"loginsimportreport",

View file

@ -87,7 +87,6 @@ browser.jar:
content/browser/nsContextMenu.sys.mjs (content/nsContextMenu.sys.mjs)
content/browser/contentTheme.js (content/contentTheme.js)
#ifdef XP_MACOSX
# XXX: We should exclude this one as well (bug 71895)
* content/browser/hiddenWindowMac.xhtml (content/hiddenWindowMac.xhtml)
content/browser/nonbrowser-mac.js (content/nonbrowser-mac.js)
#endif

View file

@ -2416,84 +2416,6 @@ BrowserGlue.prototype = {
_checkHTTPSOnlyPBMPref();
},
_monitorIonPref() {
const PREF_ION_ID = "toolkit.telemetry.pioneerId";
const _checkIonPref = async () => {
for (let win of Services.wm.getEnumerator("navigator:browser")) {
win.document.getElementById("ion-button").hidden =
!Services.prefs.getStringPref(PREF_ION_ID, null);
}
};
const windowListener = {
onOpenWindow(xulWindow) {
const win = xulWindow.docShell.domWindow;
win.addEventListener("load", () => {
const ionButton = win.document.getElementById("ion-button");
if (ionButton) {
ionButton.hidden = !Services.prefs.getStringPref(PREF_ION_ID, null);
}
});
},
onCloseWindow() {},
};
Services.prefs.addObserver(PREF_ION_ID, _checkIonPref);
Services.wm.addListener(windowListener);
_checkIonPref();
},
_monitorIonStudies() {
const STUDY_ADDON_COLLECTION_KEY = "pioneer-study-addons-v1";
const PREF_ION_NEW_STUDIES_AVAILABLE =
"toolkit.telemetry.pioneer-new-studies-available";
const _badgeIcon = async () => {
for (let win of Services.wm.getEnumerator("navigator:browser")) {
win.document
.getElementById("ion-button")
.querySelector(".toolbarbutton-badge")
.classList.add("feature-callout");
}
};
const windowListener = {
onOpenWindow(xulWindow) {
const win = xulWindow.docShell.domWindow;
win.addEventListener("load", () => {
const ionButton = win.document.getElementById("ion-button");
if (ionButton) {
const badge = ionButton.querySelector(".toolbarbutton-badge");
if (
Services.prefs.getBoolPref(PREF_ION_NEW_STUDIES_AVAILABLE, false)
) {
badge.classList.add("feature-callout");
} else {
badge.classList.remove("feature-callout");
}
}
});
},
onCloseWindow() {},
};
// Update all open windows if the pref changes.
Services.prefs.addObserver(PREF_ION_NEW_STUDIES_AVAILABLE, _badgeIcon);
// Badge any currently-open windows.
if (Services.prefs.getBoolPref(PREF_ION_NEW_STUDIES_AVAILABLE, false)) {
_badgeIcon();
}
lazy.RemoteSettings(STUDY_ADDON_COLLECTION_KEY).on("sync", async () => {
Services.prefs.setBoolPref(PREF_ION_NEW_STUDIES_AVAILABLE, true);
});
// When a new window opens, check if we need to badge the icon.
Services.wm.addListener(windowListener);
},
_monitorGPCPref() {
const FEATURE_PREF_ENABLED = "privacy.globalprivacycontrol.enabled";
const FUNCTIONALITY_PREF_ENABLED =
@ -2598,8 +2520,6 @@ BrowserGlue.prototype = {
this._monitorWebcompatReporterPref();
this._monitorHTTPSOnlyPref();
this._monitorIonPref();
this._monitorIonStudies();
this._setupSearchDetection();
this._monitorGPCPref();
@ -3945,7 +3865,7 @@ BrowserGlue.prototype = {
_migrateUI() {
// Use an increasing number to keep track of the current migration state.
// Completely unrelated to the current Firefox release number.
const UI_VERSION = 149;
const UI_VERSION = 150;
const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
if (!Services.prefs.prefHasUserValue("browser.migration.version")) {
@ -4727,6 +4647,10 @@ BrowserGlue.prototype = {
});
}
if (currentUIVersion < 150) {
Services.prefs.clearUserPref("toolkit.telemetry.pioneerId");
}
// Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
},
@ -4949,7 +4873,7 @@ BrowserGlue.prototype = {
return;
}
if (Services.appShell.hiddenDOMWindow.openPreferences) {
if (AppConstants.platform == "macosx") {
Services.appShell.hiddenDOMWindow.openPreferences(...args);
}
},

View file

@ -80,7 +80,6 @@ export function StartupRecorder() {
"image-loading": new Set(),
},
code: {},
extras: {},
prefStats: {},
};
this.done = new Promise(resolve => {
@ -103,9 +102,6 @@ StartupRecorder.prototype = {
}
}),
};
this.data.extras[name] = {
hiddenWindowLoaded: Services.appShell.hasHiddenWindow,
};
},
observe(subject, topic, data) {

View file

@ -160,9 +160,6 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS |
nsIAboutModule::IS_SECURE_CHROME_UI},
{"ion", "chrome://browser/content/ion.html",
nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::IS_SECURE_CHROME_UI},
#ifdef MOZ_SELECTABLE_PROFILES
{"profilemanager", "chrome://browser/content/profiles/profiles.html",
nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI |

View file

@ -16,7 +16,6 @@ pages = [
'firefoxview',
'messagepreview',
'newtab',
'ion',
'pocket-home',
'pocket-saved',
'pocket-signup',

View file

@ -853,11 +853,11 @@ html {
}
}
.tiles-theme-container {
.tiles-single-select-container {
margin-block: -20px auto;
align-items: initial;
.theme {
.select-item {
min-width: 38px;
}
}
@ -875,7 +875,7 @@ html {
}
@media only screen and (width >= 800px) {
.tiles-theme-section {
.tiles-single-select-section {
margin-inline-start: -10px;
}
@ -915,7 +915,7 @@ html {
// duplicate the above styles for no-rdm. unfortunately SASS won't allow
// us to combine these selectors.
&:where([no-rdm='true']) {
.tiles-theme-section {
.tiles-single-select-section {
margin-inline-start: -10px;
}
@ -1485,7 +1485,7 @@ html {
}
}
.tiles-theme-container {
.tiles-single-select-container {
display: flex;
flex-direction: column;
align-items: center;
@ -1503,7 +1503,7 @@ html {
}
}
.tiles-theme-section {
.tiles-single-select-section {
border: 0;
display: flex;
flex-wrap: wrap;
@ -1520,24 +1520,57 @@ html {
outline: 2px solid var(--in-content-primary-button-background);
}
// newtab wallpaper specific styles
&.wallpaper {
justify-content: center;
gap: 10px;
// single select only specific styles
&.single-select:not(.wallpaper, .theme) {
flex-direction: row;
justify-content: flex-start;
.select-item {
flex: unset;
&:focus-visible {
outline: none;
// Only highlight the image, not the frame around the image and the label
.icon {
outline: initial;
}
}
}
}
// theme and wallpaper specific styles
&.wallpaper,
&.theme {
.select-item {
width: 180px;
}
}
// single select and wallpaper specific styles
&.single-select,
&.wallpaper {
&:hover, &:focus-within {
outline: none;
}
.theme {
flex: unset;
width: unset;
.select-item {
transition: var(--transition);
&:has(.input:focus) {
outline: 2px solid var(--in-content-primary-button-background);
outline-offset: 2px;
}
}
}
// newtab wallpaper specific styles
&.wallpaper {
justify-content: center;
gap: 10px;
.select-item {
flex: unset;
width: unset;
.icon {
width: 116px;
@ -1621,14 +1654,13 @@ html {
}
.theme {
.select-item {
align-items: center;
display: flex;
flex-direction: column;
flex: 1;
padding: 0;
min-width: 50px;
width: 180px;
color: #000;
box-shadow: none;
border-radius: 4px;
@ -2150,7 +2182,7 @@ html {
transition-delay: 0.8s;
}
.tiles-theme-section,
.tiles-single-select-section,
.multi-select-container,
migration-wizard {
transition-delay: 0.9s;
@ -2169,7 +2201,7 @@ html {
// Delays for transitioning-in of intermediate screens
.screen:not(.dialog-initial) {
.tiles-theme-section,
.tiles-single-select-section,
.multi-select-container
{
transition-delay: 0.2s;
@ -2247,7 +2279,7 @@ html {
.screen {
.welcome-text,
.multi-select-container,
.tiles-theme-section,
.tiles-single-select-section,
.primary,
.checkbox-container:not(.multi-select-item),
.secondary,
@ -2290,7 +2322,7 @@ html {
// content that is nested between inner main content and navigation CTAs
// requires an additional 0.1s transition to avoid overlap
.tiles-theme-section,
.tiles-single-select-section,
migration-wizard {
opacity: 0;
translate: 0 var(--translate);

View file

@ -182,6 +182,12 @@ export const MultiStageAboutWelcome = props => {
// multi select screen.
const [activeMultiSelects, setActiveMultiSelects] = useState({});
// Save the active single select state for each screen as string value keyed
// by screen id. Similar to above, this allows us to remember the state of
// each screen's single select picker when navigating back and forth between
// screens.
const [activeSingleSelects, setActiveSingleSelects] = useState({});
// Get the active theme so the rendering code can make it selected
// by default.
const [activeTheme, setActiveTheme] = useState(null);
@ -243,6 +249,15 @@ export const MultiStageAboutWelcome = props => {
: valueOrFn,
}));
const setActiveSingleSelect = valueOrFn =>
setActiveSingleSelects(prevState => ({
...prevState,
[currentScreen.id]:
typeof valueOrFn === "function"
? valueOrFn(prevState[currentScreen.id])
: valueOrFn,
}));
return index === order ? (
<WelcomeScreen
key={currentScreen.id + order}
@ -267,6 +282,8 @@ export const MultiStageAboutWelcome = props => {
activeMultiSelect={activeMultiSelects[currentScreen.id]}
setActiveMultiSelect={setActiveMultiSelect}
autoAdvance={currentScreen.auto_advance}
activeSingleSelect={activeSingleSelects[currentScreen.id]}
setActiveSingleSelect={setActiveSingleSelect}
negotiatedLanguage={negotiatedLanguage}
langPackInstallPhase={langPackInstallPhase}
forceHideStepsIndicator={currentScreen.force_hide_steps_indicator}
@ -493,6 +510,15 @@ export class WelcomeScreen extends React.PureComponent {
}
}
if (action.picker) {
let options = props.content.tiles.data;
options.forEach(opt => {
if (opt.id === value) {
AboutWelcomeUtils.handleUserAction(opt.action);
}
});
}
// If the action has persistActiveTheme: true, we set the initial theme to the currently active theme
// so that it can be reverted to in the event that the user navigates away from the screen
if (action.persistActiveTheme) {
@ -574,6 +600,8 @@ export class WelcomeScreen extends React.PureComponent {
setScreenMultiSelects={this.props.setScreenMultiSelects}
activeMultiSelect={this.props.activeMultiSelect}
setActiveMultiSelect={this.props.setActiveMultiSelect}
activeSingleSelect={this.props.activeSingleSelect}
setActiveSingleSelect={this.props.setActiveSingleSelect}
totalNumberOfScreens={this.props.totalNumberOfScreens}
appAndSystemLocaleInfo={this.props.appAndSystemLocaleInfo}
negotiatedLanguage={this.props.negotiatedLanguage}

View file

@ -7,7 +7,7 @@ import { Localized } from "./MSLocalized";
import { AboutWelcomeUtils } from "../lib/aboutwelcome-utils.mjs";
import { MobileDownloads } from "./MobileDownloads";
import { MultiSelect } from "./MultiSelect";
import { Themes } from "./Themes";
import { SingleSelect } from "./SingleSelect";
import {
SecondaryCTA,
StepsIndicator,
@ -50,6 +50,8 @@ export const MultiStageProtonScreen = props => {
setScreenMultiSelects={props.setScreenMultiSelects}
activeMultiSelect={props.activeMultiSelect}
setActiveMultiSelect={props.setActiveMultiSelect}
activeSingleSelect={props.activeSingleSelect}
setActiveSingleSelect={props.setActiveSingleSelect}
totalNumberOfScreens={props.totalNumberOfScreens}
handleAction={props.handleAction}
isFirstScreen={props.isFirstScreen}
@ -285,12 +287,15 @@ export class ProtonScreen extends React.PureComponent {
/>
) : null}
{content.tiles &&
content.tiles.type === "theme" &&
(content.tiles.type === "theme" ||
content.tiles.type === "single-select") &&
content.tiles.data ? (
<Themes
<SingleSelect
content={content}
activeTheme={this.props.activeTheme}
handleAction={this.props.handleAction}
activeSingleSelect={this.props.activeSingleSelect}
setActiveSingleSelect={this.props.setActiveSingleSelect}
/>
) : null}
{content.tiles &&

View file

@ -0,0 +1,126 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React, { useEffect } from "react";
import { Localized } from "./MSLocalized";
// This component was formerly "Themes" and continues to support theme and
// wallpaper pickers.
export const SingleSelect = ({
activeSingleSelect,
activeTheme,
content,
handleAction,
setActiveSingleSelect,
}) => {
const category = content.tiles?.category?.type || content.tiles?.type;
const isSingleSelect = category === "single-select";
// When screen renders for first time or user navigates back, update state to
// check default option.
useEffect(() => {
if (isSingleSelect && !activeSingleSelect) {
let newActiveSingleSelect =
content.tiles?.selected || content.tiles.data[0].id;
setActiveSingleSelect(newActiveSingleSelect);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const getIconStyles = (icon = {}) => {
const CONFIGURABLE_STYLES = [
"background",
"borderRadius",
"height",
"marginBlock",
"marginInline",
"paddingBlock",
"paddingInline",
"width",
];
let styles = {};
Object.keys(icon).forEach(styleProp => {
if (CONFIGURABLE_STYLES.includes(styleProp)) {
styles[styleProp] = icon[styleProp];
}
});
return styles;
};
return (
<div className="tiles-single-select-container">
<div>
<fieldset className={`tiles-single-select-section ${category}`}>
<Localized text={content.subtitle}>
<legend className="sr-only" />
</Localized>
{content.tiles.data.map(
({
description,
icon,
id,
label = "",
theme,
tooltip,
type = "",
}) => {
const value = id || theme;
const selected =
(theme && theme === activeTheme) ||
(isSingleSelect && activeSingleSelect === value);
const valOrObj = val => (typeof val === "object" ? val : {});
const handleClick = evt => {
if (isSingleSelect) {
setActiveSingleSelect(value);
}
handleAction(evt);
};
const handleKeyDown = evt => {
if (evt.key === "Enter" || evt.keyCode === 13) {
// Set target value to the input inside of the selected label
evt.currentTarget.value = value;
handleClick(evt);
}
};
return (
<Localized
key={value + (isSingleSelect ? "" : label)}
text={valOrObj(tooltip)}
>
{/* eslint-disable jsx-a11y/label-has-associated-control */}
{/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */}
<label
className={`select-item ${type}`}
title={value}
onKeyDown={e => handleKeyDown(e)}
>
<Localized text={valOrObj(description)}>
<input
type="radio"
value={value}
name={category === "theme" ? "theme" : id}
checked={selected}
className="sr-only input"
onClick={e => handleClick(e)}
/>
</Localized>
<div
className={`icon ${selected ? " selected" : ""} ${value}`}
style={getIconStyles(icon)}
/>
<Localized text={label}>
<div className="text" />
</Localized>
</label>
</Localized>
);
}
)}
</fieldset>
</div>
</div>
);
};

View file

@ -1,53 +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/. */
import React from "react";
import { Localized } from "./MSLocalized";
export const Themes = props => {
const category = props.content.tiles?.category?.type;
return (
<div className="tiles-theme-container">
<div>
<fieldset className={`tiles-theme-section ${category}`}>
<Localized text={props.content.subtitle}>
<legend className="sr-only" />
</Localized>
{props.content.tiles.data.map(
({ theme, label, tooltip, description, type }) => (
<Localized
key={theme + label}
text={typeof tooltip === "object" ? tooltip : {}}
>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
<label className={`theme ${type}`} title={theme + label}>
<Localized
text={typeof description === "object" ? description : {}}
>
<input
type="radio"
value={theme}
name={category === "wallpaper" ? theme : "theme"}
checked={theme === props.activeTheme}
className="sr-only input"
onClick={props.handleAction}
/>
</Localized>
<div
className={`icon ${
theme === props.activeTheme ? " selected" : ""
} ${theme}`}
/>
<Localized text={label}>
<div className="text" />
</Localized>
</label>
</Localized>
)
)}
</fieldset>
</div>
</div>
);
};

View file

@ -332,6 +332,12 @@ const MultiStageAboutWelcome = props => {
// multi select screen.
const [activeMultiSelects, setActiveMultiSelects] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
// Save the active single select state for each screen as string value keyed
// by screen id. Similar to above, this allows us to remember the state of
// each screen's single select picker when navigating back and forth between
// screens.
const [activeSingleSelects, setActiveSingleSelects] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({});
// Get the active theme so the rendering code can make it selected
// by default.
const [activeTheme, setActiveTheme] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
@ -376,6 +382,10 @@ const MultiStageAboutWelcome = props => {
...prevState,
[currentScreen.id]: typeof valueOrFn === "function" ? valueOrFn(prevState[currentScreen.id]) : valueOrFn
}));
const setActiveSingleSelect = valueOrFn => setActiveSingleSelects(prevState => ({
...prevState,
[currentScreen.id]: typeof valueOrFn === "function" ? valueOrFn(prevState[currentScreen.id]) : valueOrFn
}));
return index === order ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(WelcomeScreen, {
key: currentScreen.id + order,
id: currentScreen.id,
@ -399,6 +409,8 @@ const MultiStageAboutWelcome = props => {
activeMultiSelect: activeMultiSelects[currentScreen.id],
setActiveMultiSelect: setActiveMultiSelect,
autoAdvance: currentScreen.auto_advance,
activeSingleSelect: activeSingleSelects[currentScreen.id],
setActiveSingleSelect: setActiveSingleSelect,
negotiatedLanguage: negotiatedLanguage,
langPackInstallPhase: langPackInstallPhase,
forceHideStepsIndicator: currentScreen.force_hide_steps_indicator,
@ -587,6 +599,14 @@ class WelcomeScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
window.AWSelectTheme(themeToUse);
}
}
if (action.picker) {
let options = props.content.tiles.data;
options.forEach(opt => {
if (opt.id === value) {
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__.AboutWelcomeUtils.handleUserAction(opt.action);
}
});
}
// If the action has persistActiveTheme: true, we set the initial theme to the currently active theme
// so that it can be reverted to in the event that the user navigates away from the screen
@ -658,6 +678,8 @@ class WelcomeScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
setScreenMultiSelects: this.props.setScreenMultiSelects,
activeMultiSelect: this.props.activeMultiSelect,
setActiveMultiSelect: this.props.setActiveMultiSelect,
activeSingleSelect: this.props.activeSingleSelect,
setActiveSingleSelect: this.props.setActiveSingleSelect,
totalNumberOfScreens: this.props.totalNumberOfScreens,
appAndSystemLocaleInfo: this.props.appAndSystemLocaleInfo,
negotiatedLanguage: this.props.negotiatedLanguage,
@ -804,7 +826,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
/* harmony import */ var _MobileDownloads__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(7);
/* harmony import */ var _MultiSelect__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8);
/* harmony import */ var _Themes__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9);
/* harmony import */ var _SingleSelect__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9);
/* harmony import */ var _MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4);
/* harmony import */ var _LanguageSwitcher__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(10);
/* harmony import */ var _CTAParagraph__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(11);
@ -863,6 +885,8 @@ const MultiStageProtonScreen = props => {
setScreenMultiSelects: props.setScreenMultiSelects,
activeMultiSelect: props.activeMultiSelect,
setActiveMultiSelect: props.setActiveMultiSelect,
activeSingleSelect: props.activeSingleSelect,
setActiveSingleSelect: props.setActiveSingleSelect,
totalNumberOfScreens: props.totalNumberOfScreens,
handleAction: props.handleAction,
isFirstScreen: props.isFirstScreen,
@ -1046,10 +1070,12 @@ class ProtonScreen extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCom
installedAddons: this.props.installedAddons,
message_id: this.props.messageId,
handleAction: this.props.handleAction
}) : null, content.tiles && content.tiles.type === "theme" && content.tiles.data ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_Themes__WEBPACK_IMPORTED_MODULE_5__.Themes, {
}) : null, content.tiles && (content.tiles.type === "theme" || content.tiles.type === "single-select") && content.tiles.data ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_SingleSelect__WEBPACK_IMPORTED_MODULE_5__.SingleSelect, {
content: content,
activeTheme: this.props.activeTheme,
handleAction: this.props.handleAction
handleAction: this.props.handleAction,
activeSingleSelect: this.props.activeSingleSelect,
setActiveSingleSelect: this.props.setActiveSingleSelect
}) : null, content.tiles && content.tiles.type === "mobile_downloads" && content.tiles.data ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MobileDownloads__WEBPACK_IMPORTED_MODULE_3__.MobileDownloads, {
data: content.tiles.data,
handleAction: this.props.handleAction
@ -1478,7 +1504,7 @@ const MultiSelect = ({
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Themes: () => (/* binding */ Themes)
/* harmony export */ SingleSelect: () => (/* binding */ SingleSelect)
/* harmony export */ });
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
@ -1489,44 +1515,95 @@ __webpack_require__.r(__webpack_exports__);
const Themes = props => {
const category = props.content.tiles?.category?.type;
// This component was formerly "Themes" and continues to support theme and
// wallpaper pickers.
const SingleSelect = ({
activeSingleSelect,
activeTheme,
content,
handleAction,
setActiveSingleSelect
}) => {
const category = content.tiles?.category?.type || content.tiles?.type;
const isSingleSelect = category === "single-select";
// When screen renders for first time or user navigates back, update state to
// check default option.
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
if (isSingleSelect && !activeSingleSelect) {
let newActiveSingleSelect = content.tiles?.selected || content.tiles.data[0].id;
setActiveSingleSelect(newActiveSingleSelect);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const getIconStyles = (icon = {}) => {
const CONFIGURABLE_STYLES = ["background", "borderRadius", "height", "marginBlock", "marginInline", "paddingBlock", "paddingInline", "width"];
let styles = {};
Object.keys(icon).forEach(styleProp => {
if (CONFIGURABLE_STYLES.includes(styleProp)) {
styles[styleProp] = icon[styleProp];
}
});
return styles;
};
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "tiles-theme-container"
className: "tiles-single-select-container"
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("fieldset", {
className: `tiles-theme-section ${category}`
className: `tiles-single-select-section ${category}`
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
text: props.content.subtitle
text: content.subtitle
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("legend", {
className: "sr-only"
})), props.content.tiles.data.map(({
theme,
label,
tooltip,
})), content.tiles.data.map(({
description,
type
}) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
key: theme + label,
text: typeof tooltip === "object" ? tooltip : {}
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
className: `theme ${type}`,
title: theme + label
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
text: typeof description === "object" ? description : {}
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", {
type: "radio",
value: theme,
name: category === "wallpaper" ? theme : "theme",
checked: theme === props.activeTheme,
className: "sr-only input",
onClick: props.handleAction
})), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: `icon ${theme === props.activeTheme ? " selected" : ""} ${theme}`
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
text: label
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "text"
}))))))));
icon,
id,
label = "",
theme,
tooltip,
type = ""
}) => {
const value = id || theme;
const selected = theme && theme === activeTheme || isSingleSelect && activeSingleSelect === value;
const valOrObj = val => typeof val === "object" ? val : {};
const handleClick = evt => {
if (isSingleSelect) {
setActiveSingleSelect(value);
}
handleAction(evt);
};
const handleKeyDown = evt => {
if (evt.key === "Enter" || evt.keyCode === 13) {
// Set target value to the input inside of the selected label
evt.currentTarget.value = value;
handleClick(evt);
}
};
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
key: value + (isSingleSelect ? "" : label),
text: valOrObj(tooltip)
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
className: `select-item ${type}`,
title: value,
onKeyDown: e => handleKeyDown(e)
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
text: valOrObj(description)
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("input", {
type: "radio",
value: value,
name: category === "theme" ? "theme" : id,
checked: selected,
className: "sr-only input",
onClick: e => handleClick(e)
})), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: `icon ${selected ? " selected" : ""} ${value}`,
style: getIconStyles(icon)
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
text: label
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
className: "text"
}))));
}))));
};
/***/ }),

View file

@ -1692,11 +1692,11 @@ html {
margin-inline-start: 0;
}
}
.onboardingContainer .screen[pos=split] .tiles-theme-container {
.onboardingContainer .screen[pos=split] .tiles-single-select-container {
margin-block: -20px auto;
align-items: initial;
}
.onboardingContainer .screen[pos=split] .tiles-theme-container .theme {
.onboardingContainer .screen[pos=split] .tiles-single-select-container .select-item {
min-width: 38px;
}
@media (prefers-contrast: no-preference) and (prefers-color-scheme: dark) {
@ -1708,7 +1708,7 @@ html {
}
}
@media only screen and (width >= 800px) {
.onboardingContainer .screen[pos=split] .tiles-theme-section {
.onboardingContainer .screen[pos=split] .tiles-single-select-section {
margin-inline-start: -10px;
}
.onboardingContainer .screen[pos=split][reverse-split] {
@ -1738,7 +1738,7 @@ html {
margin-inline-start: 0;
}
}
.onboardingContainer .screen[pos=split]:where([no-rdm=true]) .tiles-theme-section {
.onboardingContainer .screen[pos=split]:where([no-rdm=true]) .tiles-single-select-section {
margin-inline-start: -10px;
}
.onboardingContainer .screen[pos=split]:where([no-rdm=true])[reverse-split] {
@ -2191,7 +2191,7 @@ html {
filter: invert(0);
}
}
.onboardingContainer .tiles-theme-container {
.onboardingContainer .tiles-single-select-container {
display: flex;
flex-direction: column;
align-items: center;
@ -2206,7 +2206,7 @@ html {
height: 1px;
width: 1px;
}
.onboardingContainer .tiles-theme-section {
.onboardingContainer .tiles-single-select-section {
border: 0;
display: flex;
flex-wrap: wrap;
@ -2216,104 +2216,121 @@ html {
padding: 10px;
transition: var(--transition);
}
.onboardingContainer .tiles-theme-section:hover, .onboardingContainer .tiles-theme-section:active, .onboardingContainer .tiles-theme-section:focus-within {
.onboardingContainer .tiles-single-select-section:hover, .onboardingContainer .tiles-single-select-section:active, .onboardingContainer .tiles-single-select-section:focus-within {
border-radius: 8px;
outline: 2px solid var(--in-content-primary-button-background);
}
.onboardingContainer .tiles-theme-section.wallpaper {
justify-content: center;
gap: 10px;
.onboardingContainer .tiles-single-select-section.single-select:not(.wallpaper, .theme) {
flex-direction: row;
justify-content: flex-start;
}
.onboardingContainer .tiles-theme-section.wallpaper:hover, .onboardingContainer .tiles-theme-section.wallpaper:focus-within {
.onboardingContainer .tiles-single-select-section.single-select:not(.wallpaper, .theme) .select-item {
flex: unset;
}
.onboardingContainer .tiles-single-select-section.single-select:not(.wallpaper, .theme) .select-item:focus-visible {
outline: none;
}
.onboardingContainer .tiles-theme-section.wallpaper .theme {
flex: unset;
width: unset;
.onboardingContainer .tiles-single-select-section.single-select:not(.wallpaper, .theme) .select-item:focus-visible .icon {
outline: initial;
}
.onboardingContainer .tiles-single-select-section.wallpaper .select-item, .onboardingContainer .tiles-single-select-section.theme .select-item {
width: 180px;
}
.onboardingContainer .tiles-single-select-section.single-select:hover, .onboardingContainer .tiles-single-select-section.single-select:focus-within, .onboardingContainer .tiles-single-select-section.wallpaper:hover, .onboardingContainer .tiles-single-select-section.wallpaper:focus-within {
outline: none;
}
.onboardingContainer .tiles-single-select-section.single-select .select-item, .onboardingContainer .tiles-single-select-section.wallpaper .select-item {
transition: var(--transition);
}
.onboardingContainer .tiles-theme-section.wallpaper .theme:has(.input:focus) {
.onboardingContainer .tiles-single-select-section.single-select .select-item:has(.input:focus), .onboardingContainer .tiles-single-select-section.wallpaper .select-item:has(.input:focus) {
outline: 2px solid var(--in-content-primary-button-background);
outline-offset: 2px;
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon {
.onboardingContainer .tiles-single-select-section.wallpaper {
justify-content: center;
gap: 10px;
}
.onboardingContainer .tiles-single-select-section.wallpaper .select-item {
flex: unset;
width: unset;
}
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon {
width: 116px;
height: 86px;
border-radius: 8px;
box-shadow: 0 1px 2px 0 rgba(58, 57, 68, 0.2);
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon:hover {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon:hover {
filter: brightness(45%);
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.dark-landscape {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.dark-landscape {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-landscape.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.dark-beach {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.dark-beach {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-beach.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.dark-color {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.dark-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.dark-mountain {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.dark-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.dark-panda {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.dark-panda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.dark-sky {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.dark-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.light-beach {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.light-beach {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.light-color {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.light-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.light-landscape {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.light-landscape {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.light-mountain {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.light-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.light-panda {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.light-panda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-panda.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .theme .icon.light-sky {
.onboardingContainer .tiles-single-select-section.wallpaper .select-item .icon.light-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif");
}
.onboardingContainer .tiles-theme-section.wallpaper .dark {
.onboardingContainer .tiles-single-select-section.wallpaper .dark {
display: none;
}
.onboardingContainer .tiles-theme-section.wallpaper .text {
.onboardingContainer .tiles-single-select-section.wallpaper .text {
display: none;
}
@media (prefers-color-scheme: dark) {
.onboardingContainer .tiles-theme-section.wallpaper .light {
.onboardingContainer .tiles-single-select-section.wallpaper .light {
display: none;
}
.onboardingContainer .tiles-theme-section.wallpaper .dark {
.onboardingContainer .tiles-single-select-section.wallpaper .dark {
display: block;
}
}
.onboardingContainer .tiles-theme-section .theme {
.onboardingContainer .tiles-single-select-section .select-item {
align-items: center;
display: flex;
flex-direction: column;
flex: 1;
padding: 0;
min-width: 50px;
width: 180px;
color: #000;
box-shadow: none;
border-radius: 4px;
cursor: pointer;
z-index: 0;
}
.onboardingContainer .tiles-theme-section .theme:focus, .onboardingContainer .tiles-theme-section .theme:active {
.onboardingContainer .tiles-single-select-section .select-item:focus, .onboardingContainer .tiles-single-select-section .select-item:active {
outline: initial;
outline-offset: initial;
}
.onboardingContainer .tiles-theme-section .theme .icon {
.onboardingContainer .tiles-single-select-section .select-item .icon {
background-size: cover;
width: 40px;
height: 40px;
@ -2322,29 +2339,29 @@ html {
outline-offset: -0.5px;
z-index: -1;
}
.onboardingContainer .tiles-theme-section .theme .icon:dir(rtl) {
.onboardingContainer .tiles-single-select-section .select-item .icon:dir(rtl) {
transform: scaleX(-1);
}
.onboardingContainer .tiles-theme-section .theme .icon:focus-visible, .onboardingContainer .tiles-theme-section .theme .icon:active, .onboardingContainer .tiles-theme-section .theme .icon.selected {
.onboardingContainer .tiles-single-select-section .select-item .icon:focus-visible, .onboardingContainer .tiles-single-select-section .select-item .icon:active, .onboardingContainer .tiles-single-select-section .select-item .icon.selected {
outline: 2px solid var(--in-content-primary-button-background);
outline-offset: 2px;
}
.onboardingContainer .tiles-theme-section .theme .icon.selected {
.onboardingContainer .tiles-single-select-section .select-item .icon.selected {
outline-color: var(--color-accent-primary-active);
}
.onboardingContainer .tiles-theme-section .theme .icon.light {
.onboardingContainer .tiles-single-select-section .select-item .icon.light {
background-image: url("resource://builtin-themes/light/icon.svg");
}
.onboardingContainer .tiles-theme-section .theme .icon.dark {
.onboardingContainer .tiles-single-select-section .select-item .icon.dark {
background-image: url("resource://builtin-themes/dark/icon.svg");
}
.onboardingContainer .tiles-theme-section .theme .icon.alpenglow {
.onboardingContainer .tiles-single-select-section .select-item .icon.alpenglow {
background-image: url("resource://builtin-themes/alpenglow/icon.svg");
}
.onboardingContainer .tiles-theme-section .theme .icon.default, .onboardingContainer .tiles-theme-section .theme .icon.automatic {
.onboardingContainer .tiles-single-select-section .select-item .icon.default, .onboardingContainer .tiles-single-select-section .select-item .icon.automatic {
background-image: url("resource://default-theme/icon.svg");
}
.onboardingContainer .tiles-theme-section .theme .text {
.onboardingContainer .tiles-single-select-section .select-item .text {
display: flex;
color: var(--in-content-page-color);
font-size: 14px;
@ -2353,7 +2370,7 @@ html {
margin-inline-start: 0;
margin-top: 9px;
}
.onboardingContainer .tiles-theme-section legend {
.onboardingContainer .tiles-single-select-section legend {
cursor: default;
}
.onboardingContainer .tiles-container {
@ -2752,7 +2769,7 @@ html {
.onboardingContainer .dialog-initial .welcome-text {
transition-delay: 0.8s;
}
.onboardingContainer .dialog-initial .tiles-theme-section,
.onboardingContainer .dialog-initial .tiles-single-select-section,
.onboardingContainer .dialog-initial .multi-select-container,
.onboardingContainer .dialog-initial migration-wizard {
transition-delay: 0.9s;
@ -2766,7 +2783,7 @@ html {
.onboardingContainer .dialog-initial .steps[above-button]:not(.progress-bar) {
transition-delay: 1s;
}
.onboardingContainer .screen:not(.dialog-initial) .tiles-theme-section,
.onboardingContainer .screen:not(.dialog-initial) .tiles-single-select-section,
.onboardingContainer .screen:not(.dialog-initial) .multi-select-container {
transition-delay: 0.2s;
}
@ -2825,7 +2842,7 @@ html {
}
.onboardingContainer.transition-in .screen .welcome-text,
.onboardingContainer.transition-in .screen .multi-select-container,
.onboardingContainer.transition-in .screen .tiles-theme-section,
.onboardingContainer.transition-in .screen .tiles-single-select-section,
.onboardingContainer.transition-in .screen .primary,
.onboardingContainer.transition-in .screen .checkbox-container:not(.multi-select-item),
.onboardingContainer.transition-in .screen .secondary,
@ -2855,7 +2872,7 @@ html {
translate: 0 var(--translate);
transition-delay: 0.1s;
}
.onboardingContainer.transition-out .screen:not(.dialog-last) .tiles-theme-section,
.onboardingContainer.transition-out .screen:not(.dialog-last) .tiles-single-select-section,
.onboardingContainer.transition-out .screen:not(.dialog-last) migration-wizard {
opacity: 0;
translate: 0 var(--translate);

View file

@ -814,3 +814,93 @@ add_task(async function test_aboutwelcome_fullscreen_property() {
doExperimentCleanup();
browser.closeBrowser();
});
/**
* Test configurability of single select picker icons styles
*/
add_task(async function test_aboutwelcome_single_select_icon_styles() {
let screens = [
makeTestContent(`TEST_SINGLE_SELECT_ICONS`, {
tiles: {
type: "single-select",
selected: "horizontal",
action: {
picker: "<event>",
},
data: [
{
icon: {
background: `center / contain no-repeat url("chrome://activity-stream/content/data/content/assets/fox-doodle-waving.gif")`,
width: "150px",
height: "100px",
marginInline: "10px",
borderRadius: "5px",
},
id: "test1",
label: {
raw: "test1 label",
},
action: {
type: "SET_PREF",
data: {
pref: {
name: "test1.pref",
value: true,
},
},
},
},
{
defaultValue: true,
icon: {
background: `center / contain no-repeat url("chrome://activity-stream/content/data/content/assets/heart.webp")`,
width: "150px",
height: "100px",
marginInline: "10px",
borderRadius: "5px",
},
id: "test2",
label: {
raw: "test2 label",
},
action: {
type: "SET_PREF",
data: {
pref: {
name: "test2.pref",
value: false,
},
},
},
},
],
},
}),
];
let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "aboutwelcome",
value: { enabled: true, screens },
});
let browser = await openAboutWelcome();
await test_element_styles(
browser,
".icon.test1",
// Expected styles:
{
"background-image":
'url("chrome://activity-stream/content/data/content/assets/fox-doodle-waving.gif")',
"background-repeat": "no-repeat",
"background-size": "contain",
width: "150px",
height: "100px",
"margin-inline": "10px",
"border-radius": "5px",
}
);
doExperimentCleanup();
browser.closeBrowser();
});

View file

@ -249,7 +249,7 @@ add_task(async function test_multistage_aboutwelcome_default() {
"main.AW_STEP3",
"div.onboardingContainer",
"div.section-main",
"div.tiles-theme-container",
"div.tiles-single-select-container",
"div.steps",
"div.indicator.current",
],
@ -498,10 +498,10 @@ add_task(async function test_AWMultistage_Themes() {
await ContentTask.spawn(browser, "Themes", async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.querySelector("label.theme"),
() => content.document.querySelector("label.select-item"),
"Theme Icons"
);
let themes = content.document.querySelectorAll("label.theme");
let themes = content.document.querySelectorAll("label.select-item");
Assert.equal(themes.length, 2, "Two themes displayed");
});

View file

@ -174,7 +174,7 @@ add_task(async function test_multistage_aboutwelcome_experimentAPI() {
"div.secondary-cta.top",
"button[value='secondary_button']",
"button[value='secondary_button_top']",
"label.theme",
"label.select-item",
"input[type='radio']",
],
// Unexpected selectors:

View file

@ -6,7 +6,7 @@ import {
ProgressBar,
WelcomeScreen,
} from "content-src/components/MultiStageAboutWelcome";
import { Themes } from "content-src/components/Themes";
import { SingleSelect } from "content-src/components/SingleSelect";
import React from "react";
import { shallow, mount } from "enzyme";
import { AboutWelcomeDefaults } from "modules/AboutWelcomeDefaults.sys.mjs";
@ -367,7 +367,7 @@ describe("MultiStageAboutWelcome module", () => {
});
it("should check this.props.activeTheme in the rendered input", () => {
const wrapper = shallow(<Themes {...THEME_SCREEN_PROPS} />);
const wrapper = shallow(<SingleSelect {...THEME_SCREEN_PROPS} />);
const selectedThemeInput = wrapper.find(".theme input[checked=true]");
assert.strictEqual(
@ -459,13 +459,122 @@ describe("MultiStageAboutWelcome module", () => {
it("should handle wallpaper click", () => {
const wrapper = mount(<WelcomeScreen {...WALLPAPER_SCREEN_PROPS} />);
const wallpaperOptions = wrapper.find(
".tiles-theme-section .theme input[name='mountain']"
".tiles-single-select-section .select-item input[value='mountain']"
);
wallpaperOptions.simulate("click");
assert.calledTwice(AboutWelcomeUtils.handleUserAction);
});
});
describe("Single select picker screen", () => {
let SINGLE_SELECT_SCREEN_PROPS;
beforeEach(() => {
SINGLE_SELECT_SCREEN_PROPS = {
content: {
title: {
raw: "Test title",
},
subtitle: {
raw: "Test subtitle",
},
tiles: {
type: "single-select",
selected: "test1",
action: {
picker: "<event>",
},
data: [
{
id: "test1",
label: {
raw: "test1 label",
},
action: {
type: "SET_PREF",
data: {
pref: {
name: "test1.pref",
value: true,
},
},
},
},
{
defaultValue: true,
id: "test2",
label: {
raw: "test2 label",
},
action: {
type: "SET_PREF",
data: {
pref: {
name: "test2.pref",
value: false,
},
},
},
},
],
},
secondary_button: {
label: {
raw: "Skip this step",
},
action: {
navigate: true,
},
has_arrow_icon: true,
},
},
navigate: sandbox.stub(),
setActiveSingleSelect: sandbox.stub(),
};
sandbox.stub(AboutWelcomeUtils, "handleUserAction").resolves();
});
it("should select the configured default value if present", async () => {
const wrapper = mount(
<WelcomeScreen {...SINGLE_SELECT_SCREEN_PROPS} />
);
assert.ok(
wrapper
.find(".tiles-single-select-section .select-item .test1")
.exists()
);
});
it("should preselect the active value if present", async () => {
SINGLE_SELECT_SCREEN_PROPS.activeSingleSelect = "test2";
const wrapper = mount(
<WelcomeScreen {...SINGLE_SELECT_SCREEN_PROPS} />
);
assert.ok(
wrapper
.find(".tiles-single-select-section .select-item .test2")
.exists()
);
});
it("should handle item click", () => {
const wrapper = mount(
<WelcomeScreen {...SINGLE_SELECT_SCREEN_PROPS} />
);
const selectOption = wrapper.find(
".tiles-single-select-section .select-item input[name='test1']"
);
selectOption.simulate("click");
assert.calledOnce(AboutWelcomeUtils.handleUserAction);
});
it("should handle item key down selection", () => {
const wrapper = mount(
<WelcomeScreen {...SINGLE_SELECT_SCREEN_PROPS} />
);
const selectOption = wrapper.find(
".tiles-single-select-section .select-item input[name='test1']"
);
selectOption.simulate("keydown", { key: "Enter" });
assert.calledOnce(AboutWelcomeUtils.handleUserAction);
});
});
describe("#handleAction", () => {
let SCREEN_PROPS;
let TEST_ACTION;

View file

@ -87,6 +87,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
"chatShortcutsCustom",
"browser.ml.chat.shortcuts.custom"
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"chatShortcutsIgnoreFields",
"browser.ml.chat.shortcuts.ignoreFields",
"input",
updateIgnoredInputs
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"chatSidebar",
@ -197,9 +204,11 @@ export const GenAI = {
lazy.chatProvider;
lazy.chatProviders;
lazy.chatShortcuts;
lazy.chatShortcutsIgnoreFields;
// Apply initial ordering of providers
reorderChatProviders();
updateIgnoredInputs();
// Handle nimbus feature pref setting
const featureId = "chatbot";
@ -325,6 +334,11 @@ export const GenAI = {
hide();
break;
case "GenAI:ShowShortcuts": {
// Ignore some input field selection to avoid showing shortcuts
if (this.ignoredInputs.has(data.inputType)) {
return;
}
// Add shortcuts to the current tab's brower stack if it doesn't exist
if (!shortcuts) {
shortcuts = stack.appendChild(document.createElement("div"));
@ -792,3 +806,13 @@ function reorderChatProviders() {
GenAI.chatProviders.forEach(val => (val.hidden = true));
toSet.forEach(args => GenAI.chatProviders.set(...args));
}
/**
* Update ignored input fields Set.
*/
function updateIgnoredInputs() {
GenAI.ignoredInputs = new Set(
// Skip empty string as no input type is ""
lazy.chatShortcutsIgnoreFields.split(",").filter(v => v)
);
}

View file

@ -125,6 +125,17 @@ add_task(async function test_plain_clicks() {
* Check that input selection can show shortcuts
*/
add_task(async function test_input_selection() {
Assert.equal(GenAI.ignoredInputs.size, 1, "Default ignore 1 type of field");
Assert.ok(GenAI.ignoredInputs.has("input"), "Default ignore inputs");
await SpecialPowers.pushPrefEnv({
set: [
["browser.ml.chat.shortcuts.ignoreFields", "contenteditable,textarea"],
],
});
Assert.equal(GenAI.ignoredInputs.size, 2, "Ignoring other fields not input");
Assert.ok(GenAI.ignoredInputs.has("textarea"), "Now ignore textarea");
Assert.ok(!GenAI.ignoredInputs.has("input"), "Not ignoring input for test");
const sandbox = sinon.createSandbox();
const stub = sandbox
.stub(GenAI, "handleShortcutsMessage")

View file

@ -1,180 +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/. */
body {
margin: 40px auto;
max-width: 664px;
font-family: system-ui;
}
@media (max-width: 830px) {
body {
margin-inline-start: 16px;
margin-inline-end: 16px;
}
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-block-end: 24px;
}
@media (max-width: 600px) {
header {
display: block;
}
}
#locale-notification {
display: none;
text-align: center;
}
#summary, #details, #data {
margin-block-end: 24px;
}
#summary, #summary, #details p, #data p {
font-size: 15px;
line-height: 22px;
}
#data ul {
padding-inline-start: 15px;
}
#data ul li {
margin-block-end: 8px;
}
#data a, #data strong {
font-weight: 600;
}
h2 {
font-size: 17px;
font-weight: 600;
}
details > summary {
user-select: none;
padding: 2px 6px;
width: 18em;
cursor: pointer;
outline: none;
font-size: 17px;
font-weight: 600;
}
#report-title, #title {
margin-block-end: 0;
}
@media (max-width: 600px) {
#title {
margin-block-end: 8px;
}
#enrollment-button {
margin-inline-start: 0;
}
}
#available-studies {
font-weight: 600;
}
.card {
display: flex;
flex-wrap: wrap;
}
.card-icon {
width: 32px;
height: 32px;
flex-shrink: 0;
}
.card-body {
flex-grow: 1;
margin-inline-start: 16px;
}
.card-name {
margin: 0;
font-size: 16px;
font-weight: 600;
line-height: 1;
}
.card-author {
margin: 0;
font-size: 14px;
font-weight: 400;
}
.card-actions {
align-self: center;
flex-shrink: 0;
min-width: 120px;
}
.join-button {
max-width: 200px;
margin: 0;
margin-inline-end: 16px;
}
.card-description {
font-size: 14px;
font-weight: normal;
width: 100%;
}
.card-data-collected {
font-size: 14px;
font-weight: normal;
}
*[hidden] {
display: none !important;
}
#ion-icon {
-moz-context-properties: fill;
fill: currentColor;
}
.modal {
max-height: 90%;
max-width: min(700px, 80%);
overflow: auto;
background: var(--in-content-page-background);
color: var(--in-content-page-color);
border: 1px solid transparent;
border-radius: 3.5px;
box-shadow: 0 2px 6px 0 rgba(0,0,0,0.3);
padding: 8px 16px 0;
}
.modal > footer {
display: flex;
justify-content: flex-end;
padding-bottom: 16px;
}
.modal::backdrop {
background-color: rgba(0,0,0,.5);
}
h1 > p { margin-bottom: 0}
.consent-list {
padding: 16px;
padding-inline-start: 32px;
margin-block-end: 16px;
border: 1px solid var(--in-content-box-border-color);
background: var(--in-content-box-background);
}

View file

@ -1,83 +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/.
### This file is not in a locales directory to prevent it from
### being translated as the feature is still in heavy development
### and strings are likely to change often.
-ion-brand-short-name = Ion
ion = { -ion-brand-short-name }
ion-document-title = Put your data to work for a better internet
ion-summary = { -ion-brand-short-name } puts your data to work to address some of todays most pressing technology concerns, like misinformation, data privacy, and ethical AI. The data you agree to share with Mozilla (the makers of Firefox) helps create tools for better internet transparency and design products that give control back to the people who use them. As an { -ion-brand-short-name } participant, youll also have the option to contribute your data to studies sponsored by research institutions and other organizations.
ion-study-prompt = Join to enroll
ion-join-study = Join Study
ion-leave-study = Leave Study
ion-enrollment-button = Join { -ion-brand-short-name }
ion-unenrollment-button = Leave { -ion-brand-short-name }
ion-current-studies = Current Studies
ion-no-current-studies = No current studies, please check back later.
ion-end-study = End Study
ion-ended-study = Study Ended
ion-accept-participate = Accept and Participate
ion-accept-leave = Accept and Leave
ion-cancel = Cancel
ion-consent-notice = { -ion-brand-short-name } Privacy Notice
ion-consent-study-notice = { -ion-brand-short-name } Study Privacy Consent Notice
leave-ion-consent-title = Youre leaving?
leave-ion-consent-bullet-thanks = Thank you for participating.
leave-ion-consent-bullet-manage = Were sorry to see you go. Once you leave { -ion-brand-short-name }, we will stop all data collection and unenroll you from any active studies. We will also delete the data youve contributed, where applicable. <a data-l10n-name="privacy-policy">Learn more about managing your { -ion-brand-short-name } data</a>.
leave-study-consent-title = Withdraw your contribution to this study?
leave-study-consent-bullet-manage = If you unenroll while this study is still active, { -ion-brand-short-name } will rescind your contribution to this research effort and will delete any data youve already contributed. <a data-l10n-name="privacy-policy">Learn more about managing your { -ion-brand-short-name } data</a>.
ion-consent-study-title = Leaving Study
ion-consent-study-join = Accept and Join Study
ion-consent-study-leave = Accept and Leave Study
ion-program-consent-intro = When you enroll in { -ion-brand-short-name }, you are sharing personal information. Keeping that information safe is important to us and to the integrity of { -ion-brand-short-name }. Here is how we safeguard your data and protect your identity.
ion-program-study-intro = When you enroll in this study, you are sharing personal information. Keeping that information safe is important to us and to the integrity of { -ion-brand-short-name }. Here is how we safeguard your data and protect your identity.
ion-works-title = How it works:
ion-works-bullet-get-started-title = Get started.
ion-works-bullet-get-started-content = Select the { ion-enrollment-button } button, review and agree to our Privacy Notice, and answer a few (optional) demographic questions. Note that { -ion-brand-short-name } is currently open to participants in the US who are 19 or older.
ion-works-bullet-enroll-title = Enroll in studies.
ion-works-bullet-enroll-content = Share your data with studies run by { -vendor-short-name } and our { -ion-brand-short-name } research partners. Youll have the opportunity to learn about a studys goals, the data it collects, and its research team before you enroll.
ion-works-bullet-control-title = Stay in control.
ion-works-bullet-control-content = The { -ion-brand-short-name } icon will appear on the { -brand-product-name } toolbar. Select the icon any time you want to return to this page to update your settings, enroll in a study, or leave a study or the { -ion-brand-short-name } program.
ion-your-data-title = Your data: why it matters and how we protect it
ion-your-data-summary = { -ion-brand-short-name } puts your data to work for a better internet. Our goal is to better understand topics like internet usage, online privacy, algorithmic bias, discrimination, and misinformation. This in turn can lead to new products that fundamentally change the tech landscape and hand more power and control back to users.
ion-your-data-bullet-know = Youll know the information we plan to collect before we collect it. We publish our data collection documentation, so you can confirm this for yourself. Read each privacy notice for detailed information.
ion-your-data-bullet-lengths = We prioritize securing your data and protecting your privacy.
ion-your-data-bullet-leave = You can leave the { -ion-brand-short-name } program at any time, and well stop collecting data when you do.
ion-your-data-learn-more = Learn more about <a data-l10n-name="privacy-policy">managing the data you share</a> with { -ion-brand-short-name }.
ion-us-only = Sorry, { -ion-brand-short-name } is currently only open to participants in the US.
ion-enroll-effective-date = Effective September 1, 2020
ion-enroll-summary = { -ion-brand-short-name } is an experimental initiative led by Mozilla to better understand how our users use and navigate the internet. { -ion-brand-short-name } is available to Firefox users in the United States who are 19 or older.
ion-enroll-demographic = When you join { -ion-brand-short-name }, well ask you to provide optional demographic data. Well also collect basic technical and interaction data as long as youre participating in { -ion-brand-short-name }. Once youve enrolled, youll have the opportunity to join available studies—each study will have a specific research purpose and unique privacy notice for you to review before you join it.
ion-enroll-privacy-notice = In this Privacy Notice, we detail what data the { -ion-brand-short-name } program collects and discloses, and why. Read each studys privacy notice for information about how data is collected and handled in that particular study. We also adhere to the <a data-l10n-name="privacy-notice">Mozilla Privacy Policy</a> for how we receive, handle, and share information.
ion-enroll-data-disclosure = To see a full list of the data we collect, click <a data-l10n-name="privacy-policy">here</a>.
ion-enroll-what-we-collect = What Information We Collect:
ion-enroll-collect-demographic = <strong>Demographic data:</strong> We collect optional, self-reported demographic data from { -ion-brand-short-name } participants, including their age, gender, race/ethnicity, education level, household income, and zip code.
ion-enroll-technical-data = <strong>Technical data:</strong> We collect basic information about your devices operating system. When Firefox sends data to us, your IP address is temporarily collected as part of our server logs.
ion-enroll-interaction-data = <strong>Interaction data:</strong> We collect data about your interactions with Firefox, like number and type of installed Firefox Add-ons and your active browsing session duration.
ion-enroll-location-data = <strong>Location data:</strong> We will use your IP address to approximate your country location, in addition to collecting your self-reported zip code (if you provide it).
ion-enroll-how-we-use = How We Use Your Information:
ion-enroll-r-and-d = We use the information we collect for for <strong>research and development</strong>, including:
ion-enroll-bullet-criteria = To determine which participants meet the criteria to be available to participate in particular research studies
ion-enroll-bullet-representative = To ensure our data sets are representative of the many users of Firefox
ion-enroll-bullet-improve-existing = To improve our existing products and services
ion-enroll-bullet-create = To create and develop new products
ion-enroll-who-we-disclose-to = Who We May Disclose Information To:
ion-enroll-who-we-disclose-bullet-gcp = <strong>Google Cloud Platform (GCP):</strong> We use GCP as our cloud-storage service. Mozilla has contracted with GCP requiring them to handle the data in ways that are approved by us.
ion-enroll-who-we-disclose-bullet-third-party = <strong>Third-party researchers:</strong> As part of being part of the { -ion-brand-short-name } program, we will offer you the ability to join studies. If necessary for the study, we may ask you to share all or some of the data collected under this Privacy Notice with the third party researcher(s) administering a study. Mozilla will contractually obligate the third party researchers to ensure that your data is handled in ways that are approved by us.
ion-enroll-who-we-disclose-bullet-public = <strong>General public:</strong> To advance our <a data-l10n-name="mozilla-manifesto">mission of being open</a>, we may release data sets to the general public. When we do so, we will aggregate the data and remove identifying information, so the data wont reveal the behaviors or characteristics of individual users.
ion-enroll-data-management = Data Management:
ion-enroll-data-management-learn-more = You can learn more about managing your { -ion-brand-short-name } and individual study data <a data-l10n-name="privacy-policy">here</a>. If you have any other questions regarding our privacy practices, please contact us at <a data-l10n-name="compliance-email">compliance@mozilla.com</a>.

View file

@ -1,206 +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/. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src chrome: blob:; img-src https:; object-src 'none'"
/>
<meta name="color-scheme" content="light dark" />
<link rel="localization" href="toolkit/branding/brandings.ftl" />
<link rel="localization" href="branding/brand.ftl" />
<!-- Temporary "en-US"-only l10n strings -->
<link rel="localization" href="preview/ion.ftl" />
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
<link rel="stylesheet" href="chrome://browser/content/ion.css" />
<script src="chrome://browser/content/ion.js"></script>
<link rel="icon" href="chrome://browser/skin/ion.svg" />
<title data-l10n-id="ion"></title>
</head>
<body>
<div id="locale-notification" data-l10n-id="ion-us-only"></div>
<div id="report-content">
<header>
<h1 id="title" data-l10n-id="ion-document-title"></h1>
<button id="enrollment-button" class="primary"></button>
</header>
<div id="summary" data-l10n-id="ion-summary"></div>
<details id="details" open>
<summary data-l10n-id="ion-works-title"></summary>
<p>
<strong data-l10n-id="ion-works-bullet-get-started-title"></strong>
<span data-l10n-id="ion-works-bullet-get-started-content"></span>
</p>
<p>
<strong data-l10n-id="ion-works-bullet-enroll-title"></strong>
<span data-l10n-id="ion-works-bullet-enroll-content"></span>
</p>
<p>
<strong data-l10n-id="ion-works-bullet-control-title"></strong>
<span data-l10n-id="ion-works-bullet-control-content"></span>
</p>
</details>
<details id="data" open>
<summary data-l10n-id="ion-your-data-title"></summary>
<p data-l10n-id="ion-your-data-summary"></p>
<ul>
<li data-l10n-id="ion-your-data-bullet-know"></li>
<li data-l10n-id="ion-your-data-bullet-lengths"></li>
<li data-l10n-id="ion-your-data-bullet-leave"></li>
</ul>
</details>
<p data-l10n-id="ion-your-data-learn-more">
<a
data-l10n-name="privacy-policy"
class="privacy-policy"
href="https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pioneer-managing-account-data"
target="_blank"
></a>
</p>
<h2 id="header-available-studies"></h2>
<div id="available-studies"></div>
<dialog id="join-ion-consent-dialog" class="modal" is="trapped-dialog">
<h3 data-l10n-id="ion-consent-notice"></h3>
<p data-l10n-id="ion-program-consent-intro"></p>
<ul id="join-ion-consent" class="consent-list">
<p data-l10n-id="ion-enroll-effective-date"></p>
<p data-l10n-id="ion-enroll-summary"></p>
<p data-l10n-id="ion-enroll-demographic"></p>
<p data-l10n-id="ion-enroll-privacy-notice">
<a
data-l10n-name="privacy-notice"
class="privacy-notice"
href="https://www.mozilla.org/%LOCALE%/privacy/"
target="_blank"
></a>
</p>
<h2 data-l10n-id="ion-enroll-what-we-collect"></h2>
<p data-l10n-id="ion-enroll-collect-demographic"></p>
<p data-l10n-id="ion-enroll-technical-data"></p>
<p data-l10n-id="ion-enroll-interaction-data"></p>
<p data-l10n-id="ion-enroll-location-data"></p>
<p data-l10n-id="ion-enroll-data-disclosure">
<a
data-l10n-name="privacy-policy"
class="privacy-policy"
href="https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pioneer-managing-account-data"
target="_blank"
></a>
</p>
<h2 data-l10n-id="ion-enroll-how-we-use"></h2>
<p data-l10n-id="ion-enroll-r-and-d"></p>
<ul>
<li data-l10n-id="ion-enroll-bullet-criteria"></li>
<li data-l10n-id="ion-enroll-bullet-representative"></li>
<li data-l10n-id="ion-enroll-bullet-improve-existing"></li>
<li data-l10n-id="ion-enroll-bullet-create"></li>
</ul>
<h2 data-l10n-id="ion-enroll-who-we-disclose-to"></h2>
<p data-l10n-id="ion-enroll-who-we-disclose-bullet-gcp"></p>
<p data-l10n-id="ion-enroll-who-we-disclose-bullet-third-party"></p>
<p data-l10n-id="ion-enroll-who-we-disclose-bullet-public">
<a
data-l10n-name="mozilla-manifesto"
href="https://www.mozilla.org/about/manifesto/"
target="_blank"
></a>
</p>
<h2 data-l10n-id="ion-enroll-data-management"></h2>
<p data-l10n-id="ion-enroll-data-management-learn-more">
<a
data-l10n-name="privacy-policy"
class="privacy-policy"
href="https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pioneer-managing-account-data"
target="_blank"
></a>
<a
data-l10n-name="compliance-email"
href="mailto:compliance@mozilla.com"
></a>
</p>
</ul>
<footer>
<button
id="join-ion-accept-dialog-button"
class="primary"
data-l10n-id="ion-accept-participate"
></button>
<button
id="join-ion-cancel-dialog-button"
data-l10n-id="ion-cancel"
></button>
</footer>
</dialog>
<dialog id="leave-ion-consent-dialog" class="modal" is="trapped-dialog">
<h3 data-l10n-id="leave-ion-consent-title"></h3>
<ul id="leave-ion-consent" class="consent-list">
<p data-l10n-id="leave-ion-consent-bullet-thanks"></p>
<p data-l10n-id="leave-ion-consent-bullet-manage">
<a
data-l10n-name="privacy-policy"
class="privacy-policy"
href="https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pioneer-managing-account-data"
target="_blank"
></a>
</p>
</ul>
<footer>
<button
id="leave-ion-cancel-dialog-button"
class="primary"
data-l10n-id="ion-cancel"
></button>
<button
id="leave-ion-accept-dialog-button"
data-l10n-id="ion-accept-leave"
></button>
</footer>
</dialog>
<dialog id="join-study-consent-dialog" class="modal" is="trapped-dialog">
<h3 data-l10n-id="ion-consent-study-notice"></h3>
<p data-l10n-id="ion-program-study-intro"></p>
<ul id="join-study-consent" class="consent-list"></ul>
<footer>
<button
id="join-study-accept-dialog-button"
class="primary"
data-l10n-id="ion-consent-study-join"
></button>
<button
id="join-study-cancel-dialog-button"
data-l10n-id="ion-cancel"
></button>
</footer>
</dialog>
<dialog id="leave-study-consent-dialog" class="modal" is="trapped-dialog">
<h3 data-l10n-id="leave-study-consent-title"></h3>
<ul id="leave-study-consent" class="consent-list">
<p data-l10n-id="leave-study-consent-bullet-manage">
<a
data-l10n-name="privacy-policy"
class="privacy-policy"
href="https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pioneer-managing-account-data"
target="_blank"
></a>
</p>
</ul>
<footer>
<button
id="leave-study-cancel-dialog-button"
class="primary"
data-l10n-id="ion-cancel"
></button>
<button
id="leave-study-accept-dialog-button"
data-l10n-id="ion-consent-study-leave"
></button>
</footer>
</dialog>
</div>
</body>
</html>

View file

@ -1,791 +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/. */
/**
* Control panel for the Ion project, formerly known as Pioneer.
* This lives in `about:ion` and provides a UI for users to un/enroll in the
* overall program, and to un/enroll from individual studies.
*
* NOTE - prefs and Telemetry both still mention Pioneer for backwards-compatibility,
* this may change in the future.
*/
const { AddonManager } = ChromeUtils.importESModule(
"resource://gre/modules/AddonManager.sys.mjs"
);
const { RemoteSettings } = ChromeUtils.importESModule(
"resource://services-settings/remote-settings.sys.mjs"
);
const { TelemetryController } = ChromeUtils.importESModule(
"resource://gre/modules/TelemetryController.sys.mjs"
);
let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(
Ci.nsIParserUtils
);
const PREF_ION_ID = "toolkit.telemetry.pioneerId";
const PREF_ION_NEW_STUDIES_AVAILABLE =
"toolkit.telemetry.pioneer-new-studies-available";
const PREF_ION_COMPLETED_STUDIES =
"toolkit.telemetry.pioneer-completed-studies";
/**
* Remote Settings keys for general content, and available studies.
*/
const CONTENT_COLLECTION_KEY = "pioneer-content-v2";
const STUDY_ADDON_COLLECTION_KEY = "pioneer-study-addons-v2";
const STUDY_LEAVE_REASONS = {
USER_ABANDONED: "user-abandoned",
STUDY_ENDED: "study-ended",
};
const PREF_TEST_CACHED_CONTENT = "toolkit.pioneer.testCachedContent";
const PREF_TEST_CACHED_ADDONS = "toolkit.pioneer.testCachedAddons";
const PREF_TEST_ADDONS = "toolkit.pioneer.testAddons";
/**
* Use the in-tree HTML Sanitizer to ensure that HTML from remote-settings is safe to use.
* Note that RS does use content-signing, we're doing this extra step as an in-depth security measure.
*
* @param {string} htmlString - unsanitized HTML (content-signed by remote-settings)
* @returns {DocumentFragment} - sanitized DocumentFragment
*/
function sanitizeHtml(htmlString) {
const content = document.createElement("div");
const contentFragment = parserUtils.parseFragment(
htmlString,
Ci.nsIParserUtils.SanitizerDropForms |
Ci.nsIParserUtils.SanitizerAllowStyle |
Ci.nsIParserUtils.SanitizerLogRemovals,
false,
Services.io.newURI("about:ion"),
content
);
return contentFragment;
}
function showEnrollmentStatus() {
const ionId = Services.prefs.getStringPref(PREF_ION_ID, null);
const enrollmentButton = document.getElementById("enrollment-button");
document.l10n.setAttributes(
enrollmentButton,
`ion-${ionId ? "un" : ""}enrollment-button`
);
enrollmentButton.classList.toggle("primary", !ionId);
// collapse content above the fold if enrolled, otherwise open it.
for (const section of ["details", "data"]) {
const details = document.getElementById(section);
if (ionId) {
details.removeAttribute("open");
} else {
details.setAttribute("open", true);
}
}
}
function toggleContentBasedOnLocale() {
const requestedLocale = Services.locale.requestedLocale;
if (requestedLocale !== "en-US") {
const localeNotificationBar = document.getElementById(
"locale-notification"
);
localeNotificationBar.style.display = "block";
const reportContent = document.getElementById("report-content");
reportContent.style.display = "none";
}
}
async function toggleEnrolled(studyAddonId, cachedAddons) {
let addon;
let install;
const cachedAddon = cachedAddons.find(a => a.addon_id == studyAddonId);
if (Cu.isInAutomation) {
install = {
install: async () => {
let testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, "[]");
testAddons = JSON.parse(testAddons);
testAddons.push(studyAddonId);
Services.prefs.setStringPref(
PREF_TEST_ADDONS,
JSON.stringify(testAddons)
);
},
};
let testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, "[]");
testAddons = JSON.parse(testAddons);
for (const testAddon of testAddons) {
if (testAddon == studyAddonId) {
addon = {};
addon.uninstall = () => {
Services.prefs.setStringPref(
PREF_TEST_ADDONS,
JSON.stringify(testAddons.filter(a => a.id != testAddon.id))
);
};
}
}
} else {
addon = await AddonManager.getAddonByID(studyAddonId);
install = await AddonManager.getInstallForURL(cachedAddon.sourceURI.spec);
}
const completedStudies = Services.prefs.getStringPref(
PREF_ION_COMPLETED_STUDIES,
"{}"
);
const study = document.querySelector(`.card[id="${cachedAddon.addon_id}"`);
const joinBtn = study.querySelector(".join-button");
if (addon) {
joinBtn.disabled = true;
await addon.uninstall();
await sendDeletionPing(studyAddonId);
document.l10n.setAttributes(joinBtn, "ion-join-study");
joinBtn.disabled = false;
// Record that the user abandoned this study, since it may not be re-join-able.
if (completedStudies) {
const studies = JSON.parse(completedStudies);
studies[studyAddonId] = STUDY_LEAVE_REASONS.USER_ABANDONED;
Services.prefs.setStringPref(
PREF_ION_COMPLETED_STUDIES,
JSON.stringify(studies)
);
}
} else {
// Check if this study is re-join-able before enrollment.
const studies = JSON.parse(completedStudies);
if (studyAddonId in studies) {
if (
"canRejoin" in cachedAddons[studyAddonId] &&
cachedAddons[studyAddonId].canRejoin === false
) {
console.error(
`Cannot rejoin ended study ${studyAddonId}, reason: ${studies[studyAddonId]}`
);
return;
}
}
joinBtn.disabled = true;
await install.install();
document.l10n.setAttributes(joinBtn, "ion-leave-study");
joinBtn.disabled = false;
// Send an enrollment ping for this study. Note that this could be sent again
// if we are re-joining.
await sendEnrollmentPing(studyAddonId);
}
await updateStudy(cachedAddon.addon_id);
}
async function showAvailableStudies(cachedAddons) {
const ionId = Services.prefs.getStringPref(PREF_ION_ID, null);
const defaultAddons = cachedAddons.filter(a => a.isDefault);
if (ionId) {
for (const defaultAddon of defaultAddons) {
let addon;
let install;
if (Cu.isInAutomation) {
install = {
install: async () => {
if (
defaultAddon.addon_id == "ion-v2-bad-default-example@mozilla.org"
) {
throw new Error("Bad test default add-on");
}
},
};
} else {
addon = await AddonManager.getAddonByID(defaultAddon.addon_id);
install = await AddonManager.getInstallForURL(
defaultAddon.sourceURI.spec
);
}
if (!addon) {
// Any default add-ons are required, try to reinstall.
await install.install();
}
}
}
const studyAddons = cachedAddons.filter(a => !a.isDefault);
for (const cachedAddon of studyAddons) {
if (!cachedAddon) {
console.error(
`about:ion - Study addon ID not found in cache: ${studyAddonId}`
);
return;
}
const studyAddonId = cachedAddon.addon_id;
const study = document.createElement("div");
study.setAttribute("id", studyAddonId);
study.setAttribute("class", "card card-no-hover");
if (cachedAddon.icons && 32 in cachedAddon.icons) {
const iconName = document.createElement("img");
iconName.setAttribute("class", "card-icon");
iconName.setAttribute("src", cachedAddon.icons[32]);
study.appendChild(iconName);
}
const studyBody = document.createElement("div");
studyBody.classList.add("card-body");
study.appendChild(studyBody);
const studyName = document.createElement("h3");
studyName.setAttribute("class", "card-name");
studyName.textContent = cachedAddon.name;
studyBody.appendChild(studyName);
const studyAuthor = document.createElement("span");
studyAuthor.setAttribute("class", "card-author");
studyAuthor.textContent = cachedAddon.authors.name;
studyBody.appendChild(studyAuthor);
const actions = document.createElement("div");
actions.classList.add("card-actions");
study.appendChild(actions);
const joinBtn = document.createElement("button");
joinBtn.setAttribute("id", `${studyAddonId}-join-button`);
joinBtn.classList.add("primary");
joinBtn.classList.add("join-button");
document.l10n.setAttributes(joinBtn, "ion-join-study");
joinBtn.addEventListener("click", async () => {
let addon;
if (Cu.isInAutomation) {
const testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, "[]");
for (const testAddon of JSON.parse(testAddons)) {
if (testAddon == studyAddonId) {
addon = {};
addon.uninstall = () => {
Services.prefs.setStringPref(PREF_TEST_ADDONS, "[]");
};
}
}
} else {
addon = await AddonManager.getAddonByID(studyAddonId);
}
let joinOrLeave = addon ? "leave" : "join";
let dialog = document.getElementById(
`${joinOrLeave}-study-consent-dialog`
);
dialog.setAttribute("addon-id", cachedAddon.addon_id);
const consentText = dialog.querySelector(
`[id=${joinOrLeave}-study-consent]`
);
// Clears out any existing children with a single #text node
consentText.textContent = "";
const contentFragment = sanitizeHtml(
cachedAddon[`${joinOrLeave}StudyConsent`]
);
consentText.appendChild(contentFragment);
dialog.showModal();
dialog.scrollTop = 0;
const openEvent = new Event("open");
dialog.dispatchEvent(openEvent);
});
actions.appendChild(joinBtn);
const studyDesc = document.createElement("div");
studyDesc.setAttribute("class", "card-description");
const contentFragment = sanitizeHtml(cachedAddon.description);
studyDesc.appendChild(contentFragment);
study.appendChild(studyDesc);
const studyDataCollected = document.createElement("div");
studyDataCollected.setAttribute("class", "card-data-collected");
study.appendChild(studyDataCollected);
const dataCollectionDetailsHeader = document.createElement("p");
dataCollectionDetailsHeader.textContent = "This study will collect:";
studyDataCollected.appendChild(dataCollectionDetailsHeader);
const dataCollectionDetails = document.createElement("ul");
for (const dataCollectionDetail of cachedAddon.dataCollectionDetails) {
const detailsBullet = document.createElement("li");
detailsBullet.textContent = dataCollectionDetail;
dataCollectionDetails.append(detailsBullet);
}
studyDataCollected.appendChild(dataCollectionDetails);
const availableStudies = document.getElementById("available-studies");
availableStudies.appendChild(study);
await updateStudy(studyAddonId);
}
const availableStudies = document.getElementById("header-available-studies");
document.l10n.setAttributes(availableStudies, "ion-current-studies");
}
async function updateStudy(studyAddonId) {
let addon;
if (Cu.isInAutomation) {
const testAddons = Services.prefs.getStringPref(PREF_TEST_ADDONS, "[]");
for (const testAddon of JSON.parse(testAddons)) {
if (testAddon == studyAddonId) {
addon = {
uninstall() {},
};
}
}
} else {
addon = await AddonManager.getAddonByID(studyAddonId);
}
const study = document.querySelector(`.card[id="${studyAddonId}"`);
const joinBtn = study.querySelector(".join-button");
const ionId = Services.prefs.getStringPref(PREF_ION_ID, null);
const completedStudies = Services.prefs.getStringPref(
PREF_ION_COMPLETED_STUDIES,
"{}"
);
const studies = JSON.parse(completedStudies);
if (studyAddonId in studies) {
study.style.opacity = 0.5;
joinBtn.disabled = true;
document.l10n.setAttributes(joinBtn, "ion-ended-study");
return;
}
if (ionId) {
study.style.opacity = 1;
joinBtn.disabled = false;
if (addon) {
document.l10n.setAttributes(joinBtn, "ion-leave-study");
} else {
document.l10n.setAttributes(joinBtn, "ion-join-study");
}
} else {
document.l10n.setAttributes(joinBtn, "ion-study-prompt");
study.style.opacity = 0.5;
joinBtn.disabled = true;
}
}
// equivalent to what we use for Telemetry IDs
// https://searchfox.org/mozilla-central/rev/9193635dca8cfdcb68f114306194ffc860456044/toolkit/components/telemetry/app/TelemetryUtils.jsm#222
function generateUUID() {
let str = Services.uuid.generateUUID().toString();
return str.substring(1, str.length - 1);
}
async function setup(cachedAddons) {
document
.getElementById("enrollment-button")
.addEventListener("click", async () => {
const ionId = Services.prefs.getStringPref(PREF_ION_ID, null);
if (ionId) {
let dialog = document.getElementById("leave-ion-consent-dialog");
dialog.showModal();
dialog.scrollTop = 0;
} else {
let dialog = document.getElementById("join-ion-consent-dialog");
dialog.showModal();
dialog.scrollTop = 0;
}
});
document
.getElementById("join-ion-cancel-dialog-button")
.addEventListener("click", () =>
document.getElementById("join-ion-consent-dialog").close()
);
document
.getElementById("leave-ion-cancel-dialog-button")
.addEventListener("click", () =>
document.getElementById("leave-ion-consent-dialog").close()
);
document
.getElementById("join-study-cancel-dialog-button")
.addEventListener("click", () =>
document.getElementById("join-study-consent-dialog").close()
);
document
.getElementById("leave-study-cancel-dialog-button")
.addEventListener("click", () =>
document.getElementById("leave-study-consent-dialog").close()
);
document
.getElementById("join-ion-accept-dialog-button")
.addEventListener("click", async () => {
const ionId = Services.prefs.getStringPref(PREF_ION_ID, null);
if (!ionId) {
let uuid = generateUUID();
Services.prefs.setStringPref(PREF_ION_ID, uuid);
for (const cachedAddon of cachedAddons) {
if (cachedAddon.isDefault) {
let install;
if (Cu.isInAutomation) {
install = {
install: async () => {
if (
cachedAddon.addon_id ==
"ion-v2-bad-default-example@mozilla.org"
) {
throw new Error("Bad test default add-on");
}
},
};
} else {
install = await AddonManager.getInstallForURL(
cachedAddon.sourceURI.spec
);
}
try {
await install.install();
} catch (ex) {
// No need to throw here, we'll try again before letting users enroll in any studies.
console.error(
`Could not install default add-on ${cachedAddon.addon_id}`
);
const availableStudies =
document.getElementById("available-studies");
document.l10n.setAttributes(
availableStudies,
"ion-no-current-studies"
);
}
}
const study = document.getElementById(cachedAddon.addon_id);
if (study) {
await updateStudy(cachedAddon.addon_id);
}
}
document.querySelector("dialog").close();
}
// A this point we should have a valid ion id, so we should be able to send
// the enrollment ping.
await sendEnrollmentPing();
showEnrollmentStatus();
});
document
.getElementById("leave-ion-accept-dialog-button")
.addEventListener("click", async () => {
const completedStudies = Services.prefs.getStringPref(
PREF_ION_COMPLETED_STUDIES,
"{}"
);
const studies = JSON.parse(completedStudies);
// Send a deletion ping for all completed studies the user has been a part of.
for (const studyAddonId in studies) {
await sendDeletionPing(studyAddonId);
}
Services.prefs.clearUserPref(PREF_ION_COMPLETED_STUDIES);
for (const cachedAddon of cachedAddons) {
// Record any studies that have been marked as concluded on the server, in case they re-enroll.
if ("studyEnded" in cachedAddon && cachedAddon.studyEnded === true) {
studies[cachedAddon.addon_id] = STUDY_LEAVE_REASONS.STUDY_ENDED;
Services.prefs.setStringPref(
PREF_ION_COMPLETED_STUDIES,
JSON.stringify(studies)
);
}
let addon;
if (Cu.isInAutomation) {
addon = {};
addon.id = cachedAddon.addon_id;
addon.uninstall = () => {
let testAddons = Services.prefs.getStringPref(
PREF_TEST_ADDONS,
"[]"
);
testAddons = JSON.parse(testAddons);
Services.prefs.setStringPref(
PREF_TEST_ADDONS,
JSON.stringify(
testAddons.filter(a => a.id != cachedAddon.addon_id)
)
);
};
} else {
addon = await AddonManager.getAddonByID(cachedAddon.addon_id);
}
if (addon) {
await sendDeletionPing(addon.id);
await addon.uninstall();
}
}
Services.prefs.clearUserPref(PREF_ION_ID);
for (const cachedAddon of cachedAddons) {
const study = document.getElementById(cachedAddon.addon_id);
if (study) {
await updateStudy(cachedAddon.addon_id);
}
}
document.getElementById("leave-ion-consent-dialog").close();
showEnrollmentStatus();
});
document
.getElementById("join-study-accept-dialog-button")
.addEventListener("click", async () => {
const dialog = document.getElementById("join-study-consent-dialog");
const studyAddonId = dialog.getAttribute("addon-id");
toggleEnrolled(studyAddonId, cachedAddons).then(dialog.close());
});
document
.getElementById("leave-study-accept-dialog-button")
.addEventListener("click", async () => {
const dialog = document.getElementById("leave-study-consent-dialog");
const studyAddonId = dialog.getAttribute("addon-id");
await toggleEnrolled(studyAddonId, cachedAddons).then(dialog.close());
});
const onAddonEvent = async addon => {
for (const cachedAddon of cachedAddons) {
if (cachedAddon.addon_id == addon.id) {
await updateStudy(addon.id);
}
}
};
const addonsListener = {
onEnabled: onAddonEvent,
onDisabled: onAddonEvent,
onInstalled: onAddonEvent,
onUninstalled: onAddonEvent,
};
AddonManager.addAddonListener(addonsListener);
window.addEventListener("unload", () => {
AddonManager.removeAddonListener(addonsListener);
});
}
function removeBadge() {
Services.prefs.setBoolPref(PREF_ION_NEW_STUDIES_AVAILABLE, false);
for (let win of Services.wm.getEnumerator("navigator:browser")) {
const badge = win.document
.getElementById("ion-button")
.querySelector(".toolbarbutton-badge");
badge.classList.remove("feature-callout");
}
}
// Updates Ion HTML page contents from RemoteSettings.
function updateContents(contents) {
for (const section of [
"title",
"summary",
"details",
"data",
"joinIonConsent",
"leaveIonConsent",
]) {
if (contents && section in contents) {
// Generate a corresponding dom-id style ID for a camel-case domId style JS attribute.
// Dynamically set the tag type based on which section is getting updated.
const domId = section
.split(/(?=[A-Z])/)
.join("-")
.toLowerCase();
// Clears out any existing children with a single #text node.
document.getElementById(domId).textContent = "";
const contentFragment = sanitizeHtml(contents[section]);
document.getElementById(domId).appendChild(contentFragment);
}
}
}
document.addEventListener("DOMContentLoaded", async () => {
toggleContentBasedOnLocale();
showEnrollmentStatus();
document.addEventListener("focus", removeBadge);
removeBadge();
const privacyPolicyLinks = document.querySelectorAll(
".privacy-policy,.privacy-notice"
);
for (const privacyPolicyLink of privacyPolicyLinks) {
const privacyPolicyFormattedLink = Services.urlFormatter.formatURL(
privacyPolicyLink.href
);
privacyPolicyLink.href = privacyPolicyFormattedLink;
}
let cachedContent;
let cachedAddons;
if (Cu.isInAutomation) {
let testCachedAddons = Services.prefs.getStringPref(
PREF_TEST_CACHED_ADDONS,
null
);
if (testCachedAddons) {
cachedAddons = JSON.parse(testCachedAddons);
}
let testCachedContent = Services.prefs.getStringPref(
PREF_TEST_CACHED_CONTENT,
null
);
if (testCachedContent) {
cachedContent = JSON.parse(testCachedContent);
}
} else {
cachedContent = await RemoteSettings(CONTENT_COLLECTION_KEY).get();
cachedAddons = await RemoteSettings(STUDY_ADDON_COLLECTION_KEY).get();
}
// Replace existing contents immediately on page load.
for (const contents of cachedContent) {
updateContents(contents);
}
for (const cachedAddon of cachedAddons) {
// Record any studies that have been marked as concluded on the server.
if ("studyEnded" in cachedAddon && cachedAddon.studyEnded === true) {
const completedStudies = Services.prefs.getStringPref(
PREF_ION_COMPLETED_STUDIES,
"{}"
);
const studies = JSON.parse(completedStudies);
studies[cachedAddon.addon_id] = STUDY_LEAVE_REASONS.STUDY_ENDED;
Services.prefs.setStringPref(
PREF_ION_COMPLETED_STUDIES,
JSON.stringify(studies)
);
}
}
await setup(cachedAddons);
try {
await showAvailableStudies(cachedAddons);
} catch (ex) {
// No need to throw here, we'll try again before letting users enroll in any studies.
console.error(`Could not show available studies`, ex);
}
});
async function sendDeletionPing(studyAddonId) {
const type = "pioneer-study";
const options = {
studyName: studyAddonId,
addPioneerId: true,
useEncryption: true,
// NOTE - while we're not actually sending useful data in this payload, the current Pioneer v2 Telemetry
// pipeline requires that pings are shaped this way so they are routed to the correct environment.
//
// At the moment, the public key used here isn't important but we do need to use *something*.
encryptionKeyId: "discarded",
publicKey: {
crv: "P-256",
kty: "EC",
x: "XLkI3NaY3-AF2nRMspC63BT1u0Y3moXYSfss7VuQ0mk",
y: "SB0KnIW-pqk85OIEYZenoNkEyOOp5GeWQhS1KeRtEUE",
},
schemaName: "deletion-request",
schemaVersion: 1,
// The schema namespace needs to be the study addon id, as we
// want to route the ping to the specific study table.
schemaNamespace: studyAddonId,
};
const payload = {
encryptedData: "",
};
await TelemetryController.submitExternalPing(type, payload, options);
}
/**
* Sends a Pioneer enrollment ping.
*
* The `creationDate` provided by the telemetry APIs will be used as the timestamp for
* considering the user enrolled in pioneer and/or the study.
*
* @param {string} [studyAddonId] - optional study id. It's sent in the ping, if present,
* to signal that user enroled in the study.
*/
async function sendEnrollmentPing(studyAddonId) {
let options = {
studyName: "pioneer-meta",
addPioneerId: true,
useEncryption: true,
// NOTE - while we're not actually sending useful data in this payload, the current Pioneer v2 Telemetry
// pipeline requires that pings are shaped this way so they are routed to the correct environment.
//
// At the moment, the public key used here isn't important but we do need to use *something*.
encryptionKeyId: "discarded",
publicKey: {
crv: "P-256",
kty: "EC",
x: "XLkI3NaY3-AF2nRMspC63BT1u0Y3moXYSfss7VuQ0mk",
y: "SB0KnIW-pqk85OIEYZenoNkEyOOp5GeWQhS1KeRtEUE",
},
schemaName: "pioneer-enrollment",
schemaVersion: 1,
// Note that the schema namespace directly informs how data is segregated after ingestion.
// If this is an enrollment ping for the pioneer program (in contrast to the enrollment to
// a specific study), use a meta namespace.
schemaNamespace: "pioneer-meta",
};
// If we were provided with a study id, then this is an enrollment to a study.
// Send the id alongside with the data and change the schema namespace to simplify
// the work on the ingestion pipeline.
if (typeof studyAddonId != "undefined") {
options.studyName = studyAddonId;
// The schema namespace needs to be the study addon id, as we
// want to route the ping to the specific study table.
options.schemaNamespace = studyAddonId;
}
await TelemetryController.submitExternalPing("pioneer-study", {}, options);
}

View file

@ -1,8 +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/.
browser.jar:
content/browser/ion.html (content/ion.html)
content/browser/ion.css (content/ion.css)
content/browser/ion.js (content/ion.js)

View file

@ -1,17 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ["test/browser/browser.toml"]
JAR_MANIFESTS += ["jar.mn"]
with Files("**"):
BUG_COMPONENT = ("Firefox", "General")
TESTING_JS_MODULES += [
"schemas/IonContentSchema.json",
"schemas/IonStudyAddonsSchema.json",
]

View file

@ -1,39 +0,0 @@
{
"definitions": {},
"title": "Root",
"type": "object",
"required": [
"title",
"summary",
"details",
"joinIonConsent",
"leaveIonConsent"
],
"properties": {
"title": {
"$id": "#root/title",
"title": "Title",
"type": "string"
},
"summary": {
"$id": "#root/summary",
"title": "Summary",
"type": "string"
},
"details": {
"$id": "#root/details",
"title": "Details",
"type": "string"
},
"joinIonConsent": {
"$id": "#root/joinIonConsent",
"title": "JoinIonconsent",
"type": "string"
},
"leaveIonConsent": {
"$id": "#root/leaveIonConsent",
"title": "LeaveIonconsent",
"type": "string"
}
}
}

View file

@ -1,159 +0,0 @@
{
"definitions": {},
"title": "Root",
"type": "object",
"required": [
"addon_id",
"icons",
"name",
"version",
"sourceURI",
"description",
"privacyPolicy",
"studyType",
"authors",
"dataCollectionDetails",
"moreInfo",
"isDefault",
"studyEnded",
"joinStudyConsent",
"leaveStudyConsent"
],
"properties": {
"addon_id": {
"$id": "#root/addon_id",
"title": "Addon_id",
"type": "string"
},
"icons": {
"$id": "#root/icons",
"title": "Icons",
"type": "object",
"required": ["32", "64", "128"],
"properties": {
"32": {
"$id": "#root/icons/32",
"title": "32",
"type": "string"
},
"64": {
"$id": "#root/icons/64",
"title": "64",
"type": "string"
},
"128": {
"$id": "#root/icons/128",
"title": "128",
"type": "string"
}
}
},
"name": {
"$id": "#root/name",
"title": "Name",
"type": "string"
},
"version": {
"$id": "#root/version",
"title": "Version",
"type": "string"
},
"sourceURI": {
"$id": "#root/sourceURI",
"title": "Sourceuri",
"type": "object",
"required": ["spec"],
"properties": {
"spec": {
"$id": "#root/sourceURI/spec",
"title": "Spec",
"type": "string"
}
}
},
"description": {
"$id": "#root/description",
"title": "Description",
"type": "string"
},
"privacyPolicy": {
"$id": "#root/privacyPolicy",
"title": "Privacypolicy",
"type": "object",
"required": ["spec"],
"properties": {
"spec": {
"$id": "#root/privacyPolicy/spec",
"title": "Spec",
"type": "string"
}
}
},
"studyType": {
"$id": "#root/studyType",
"title": "Studytype",
"type": "string"
},
"authors": {
"$id": "#root/authors",
"title": "Authors",
"type": "object",
"required": ["name", "url"],
"properties": {
"name": {
"$id": "#root/authors/name",
"title": "Name",
"type": "string"
},
"url": {
"$id": "#root/authors/url",
"title": "Url",
"type": "string"
}
}
},
"dataCollectionDetails": {
"$id": "#root/dataCollectionDetails",
"title": "Datacollectiondetails",
"type": "array",
"items": {
"$id": "#root/dataCollectionDetails/items",
"title": "Items",
"type": "string"
}
},
"moreInfo": {
"$id": "#root/moreInfo",
"title": "Moreinfo",
"type": "object",
"required": ["spec"],
"properties": {
"spec": {
"$id": "#root/moreInfo/spec",
"title": "Spec",
"type": "string"
}
}
},
"isDefault": {
"$id": "#root/isDefault",
"title": "Isdefault",
"type": "boolean"
},
"studyEnded": {
"$id": "#root/studyEnded",
"title": "Studyended",
"type": "boolean"
},
"joinStudyConsent": {
"$id": "#root/joinStudyConsent",
"title": "Joinstudyconsent",
"type": "string"
},
"leaveStudyConsent": {
"$id": "#root/leaveStudyConsent",
"title": "Leavestudyconsent",
"type": "string"
}
}
}

View file

@ -1,4 +0,0 @@
[DEFAULT]
["browser_ion_ui.js"]
fail-if = ["a11y_checks"] # Bug 1849063 clicked primary button is visually hidden and is not focusable

File diff suppressed because it is too large Load diff

View file

@ -42,7 +42,6 @@ DIRS += [
"extensions",
"firefoxview",
"genai",
"ion",
"messagepreview",
"migration",
"newtab",

View file

@ -19,24 +19,6 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
border-radius: var(--border-radius-medium);
position: relative;
overflow: hidden;
}
&.placeholder-seen {
&::before {
content: '';
display: block;
position: absolute;
top: 0;
inset-inline-start: -100%;
height: 100%;
width: 100%;
background: linear-gradient(90deg, rgba(255, 255, 255, 0%) 0%, var(--newtab-background-color-secondary) 50%, rgba(255, 255, 255, 0%) 100%);
z-index: 2;
@media (prefers-reduced-motion: no-preference) {
animation: loading 1.5s infinite;
}
}
.placeholder-fill {
background: var(--newtab-button-active-background);
@ -67,6 +49,24 @@ $ds-card-image-gradient-solid: rgba(0, 0, 0, 100%);
margin-inline: var(--space-large);
margin-block-end: var(--space-large);
}
}
&.placeholder-seen {
&::before {
content: '';
display: block;
position: absolute;
top: 0;
inset-inline-start: -100%;
height: 100%;
width: 100%;
background: linear-gradient(90deg, rgba(255, 255, 255, 0%) 0%, var(--newtab-background-color-secondary) 50%, rgba(255, 255, 255, 0%) 100%);
z-index: 2;
@media (prefers-reduced-motion: no-preference) {
animation: loading 1.5s infinite;
}
}
@keyframes loading {
0% {

View file

@ -4805,6 +4805,31 @@ main section {
position: relative;
overflow: hidden;
}
.ds-card.placeholder .placeholder-fill {
background: var(--newtab-button-active-background);
border-radius: var(--border-radius-small);
}
.ds-card.placeholder .placeholder-image {
width: 100%;
height: 140px;
border-radius: var(--border-radius-medium) var(--border-radius-medium) 0 0;
}
.ds-card.placeholder .placeholder-label {
width: 40%;
height: var(--size-item-small);
margin-bottom: var(--space-small);
margin-inline: var(--space-large);
}
.ds-card.placeholder .placeholder-header {
width: 80%;
height: 20px;
margin-inline: var(--space-large);
}
.ds-card.placeholder .placeholder-description {
height: 60px;
margin-inline: var(--space-large);
margin-block-end: var(--space-large);
}
.ds-card.placeholder-seen::before {
content: "";
display: block;
@ -4821,31 +4846,6 @@ main section {
animation: loading 1.5s infinite;
}
}
.ds-card.placeholder-seen .placeholder-fill {
background: var(--newtab-button-active-background);
border-radius: var(--border-radius-small);
}
.ds-card.placeholder-seen .placeholder-image {
width: 100%;
height: 140px;
border-radius: var(--border-radius-medium) var(--border-radius-medium) 0 0;
}
.ds-card.placeholder-seen .placeholder-label {
width: 40%;
height: var(--size-item-small);
margin-bottom: var(--space-small);
margin-inline: var(--space-large);
}
.ds-card.placeholder-seen .placeholder-header {
width: 80%;
height: 20px;
margin-inline: var(--space-large);
}
.ds-card.placeholder-seen .placeholder-description {
height: 60px;
margin-inline: var(--space-large);
margin-block-end: var(--space-large);
}
@keyframes loading {
0% {
inset-inline-start: -100%;

View file

@ -1254,8 +1254,23 @@ export class DiscoveryStreamFeed {
}
async clearSpocs() {
const endpoint =
this.store.getState().Prefs.values[PREF_SPOCS_CLEAR_ENDPOINT];
const state = this.store.getState();
let endpoint = state.Prefs.values[PREF_SPOCS_CLEAR_ENDPOINT];
const unifiedAdsEnabled = state.Prefs.values[PREF_UNIFIED_ADS_ENABLED];
let body = {
pocket_id: this._impressionId,
};
if (unifiedAdsEnabled) {
const endpointBaseUrl = state.Prefs.values[PREF_UNIFIED_ADS_ENDPOINT];
endpoint = `${endpointBaseUrl}v1/ads`;
body = {
context_id: lazy.contextId,
};
}
if (!endpoint) {
return;
}
@ -1265,9 +1280,7 @@ export class DiscoveryStreamFeed {
await this.fetchFromEndpoint(endpoint, {
method: "DELETE",
headers,
body: JSON.stringify({
pocket_id: this._impressionId,
}),
body: JSON.stringify(body),
});
}
@ -2089,7 +2102,7 @@ export class DiscoveryStreamFeed {
if (
!(
(this.showSponsoredStories ||
(this.showTopSites && this.showSponsoredTopSites)) &&
(this.showTopsites && this.showSponsoredTopsites)) &&
(this.showSponsoredTopsites ||
(this.showStories && this.showSponsoredStories))
)

View file

@ -75,6 +75,8 @@ export const USER_PREFS_ENCODING = {
export const PREF_IMPRESSION_ID = "impressionId";
export const TELEMETRY_PREF = "telemetry";
export const EVENTS_TELEMETRY_PREF = "telemetry.ut.events";
export const PREF_UNIFIED_ADS_ENABLED = "unifiedAds.enabled";
const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
// Used as the missing value for timestamps in the session ping
const TIMESTAMP_MISSING_VALUE = -1;
@ -137,6 +139,15 @@ export class TelemetryFeed {
return this._prefs.get(EVENTS_TELEMETRY_PREF);
}
get canSendUnifiedAdsCallbacks() {
const unifiedAdsEnabled = this._prefs.get(PREF_UNIFIED_ADS_ENABLED);
// Check PREF_UPLOAD_ENABLED if data reporting is allowed
const uploadEnabled = Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED);
return unifiedAdsEnabled && uploadEnabled;
}
get telemetryClientId() {
Object.defineProperty(this, "telemetryClientId", {
value: lazy.ClientID.getClientID(),
@ -615,6 +626,8 @@ export class TelemetryFeed {
// Legacy telemetry expects 1-based tile positions.
const legacyTelemetryPosition = position + 1;
const unifiedAdsEnabled = this._prefs.get(PREF_UNIFIED_ADS_ENABLED);
let pingType;
const session = this.sessions.get(au.getPortIdOfSender(action));
@ -659,12 +672,20 @@ export class TelemetryFeed {
Glean.topSites.position.set(legacyTelemetryPosition);
Glean.topSites.source.set(source);
Glean.topSites.tileId.set(tile_id);
if (data.reporting_url) {
if (data.reporting_url && unifiedAdsEnabled) {
Glean.topSites.reportingUrl.set(data.reporting_url);
}
Glean.topSites.advertiser.set(advertiser_name);
Glean.topSites.contextId.set(lazy.contextId);
GleanPings.topSites.submit();
if (this.canSendUnifiedAdsCallbacks) {
// Send callback events to MARS unified ads api
this.sendUnifiedAdsCallbackEvent({
url: data.reporting_url,
position,
});
}
}
handleTopSitesOrganicImpressionStats(action) {
@ -764,16 +785,24 @@ export class TelemetryFeed {
}),
});
if (shim) {
Glean.pocket.shim.set(shim);
if (fetchTimestamp) {
Glean.pocket.fetchTimestamp.set(fetchTimestamp * 1000);
if (this.canSendUnifiedAdsCallbacks) {
// Send unified ads callback event
this.sendUnifiedAdsCallbackEvent({
url: shim,
position: action.data.action_position,
});
} else {
Glean.pocket.shim.set(shim);
if (fetchTimestamp) {
Glean.pocket.fetchTimestamp.set(fetchTimestamp * 1000);
}
if (firstVisibleTimestamp) {
Glean.pocket.newtabCreationTimestamp.set(
firstVisibleTimestamp * 1000
);
}
GleanPings.spoc.submit("click");
}
if (firstVisibleTimestamp) {
Glean.pocket.newtabCreationTimestamp.set(
firstVisibleTimestamp * 1000
);
}
GleanPings.spoc.submit("click");
}
}
break;
@ -871,6 +900,34 @@ export class TelemetryFeed {
}
}
/**
* This function submits callback events to the MARS unified ads service.
*/
async sendUnifiedAdsCallbackEvent(data = { url: null, position: null }) {
if (!data.url) {
throw new Error(
`[Unified ads callback] Missing argument (No url). Cannot send telemetry event.`
);
}
// data.position can be 0 (0)
if (!data.position && data.position !== 0) {
throw new Error(
`[Unified ads callback] Missing argument (No position). Cannot send telemetry event.`
);
}
const url = new URL(data.url);
url.searchParams.append("position", data.position);
try {
await fetch(url.toString());
} catch (error) {
console.error("Error:", error);
}
}
/**
* This function is used by ActivityStreamStorage to report errors
* trying to access IndexedDB.
@ -1233,16 +1290,24 @@ export class TelemetryFeed {
}),
});
if (tile.shim) {
Glean.pocket.shim.set(tile.shim);
if (tile.fetchTimestamp) {
Glean.pocket.fetchTimestamp.set(tile.fetchTimestamp * 1000);
if (this.canSendUnifiedAdsCallbacks) {
// Send unified ads callback event
this.sendUnifiedAdsCallbackEvent({
url: tile.shim,
position: tile.pos,
});
} else {
Glean.pocket.shim.set(tile.shim);
if (tile.fetchTimestamp) {
Glean.pocket.fetchTimestamp.set(tile.fetchTimestamp * 1000);
}
if (data.firstVisibleTimestamp) {
Glean.pocket.newtabCreationTimestamp.set(
data.firstVisibleTimestamp * 1000
);
}
GleanPings.spoc.submit("impression");
}
if (data.firstVisibleTimestamp) {
Glean.pocket.newtabCreationTimestamp.set(
data.firstVisibleTimestamp * 1000
);
}
GleanPings.spoc.submit("impression");
}
});
}

View file

@ -34,6 +34,8 @@ export class ShoppingSidebarParent extends JSWindowActorParent {
"browser.shopping.experience2023.sidebarClosedCount";
static SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF =
"browser.shopping.experience2023.showKeepSidebarClosedMessage";
static INTEGRATED_SIDEBAR_PANEL_PREF =
"browser.shopping.experience2023.integratedSidebar";
updateProductURL(uri, flags) {
this.sendAsyncMessage("ShoppingSidebar:UpdateProductURL", {
@ -201,7 +203,7 @@ class ShoppingSidebarManagerClass {
XPCOMUtils.defineLazyPreferenceGetter(
this,
"isIntegratedSidebarPanel",
"browser.shopping.experience2023.integratedSidebar",
ShoppingSidebarParent.INTEGRATED_SIDEBAR_PANEL_PREF,
false,
this.updateSidebarVisibility
);

View file

@ -35,6 +35,8 @@ prefs = [
["browser_inprogress_analysis.js"]
["browser_integrated_sidebar.js"]
["browser_keep_close_message_bar.js"]
["browser_network_offline.js"]

View file

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const CONTENT_PAGE = "https://example.com";
add_task(async function test_integrated_sidebar() {
await SpecialPowers.pushPrefEnv({
set: [
["sidebar.revamp", true],
["browser.shopping.experience2023.integratedSidebar", true],
["sidebar.main.tools", "aichat,reviewchecker,syncedtabs,history"],
],
});
await BrowserTestUtils.withNewTab(CONTENT_PAGE, async function (browser) {
const { document } = browser.ownerGlobal;
let sidebar = document.querySelector("sidebar-main");
let reviewCheckerButton = await TestUtils.waitForCondition(
() =>
sidebar.shadowRoot.querySelector(
"moz-button[view=viewReviewCheckerSidebar]"
),
"Review Checker Button is added."
);
ok(
BrowserTestUtils.isVisible(reviewCheckerButton),
"Review Checker Button should be visible"
);
reviewCheckerButton.click();
ok(SidebarController.isOpen, "Sidebar is open");
Assert.equal(
SidebarController.currentID,
"viewReviewCheckerSidebar",
"Sidebar should have opened to the review checker"
);
Services.prefs.setBoolPref(
"browser.shopping.experience2023.integratedSidebar",
false
);
await TestUtils.waitForCondition(
() =>
!sidebar.shadowRoot.querySelector(
"moz-button[view=viewReviewCheckerSidebar]"
),
"Review Checker Button is removed."
);
ok(!SidebarController.isOpen, "Sidebar is closed");
});
Services.prefs.clearUserPref(
"browser.shopping.experience2023.integratedSidebar"
);
await SpecialPowers.popPrefEnv();
});

View file

@ -9,6 +9,7 @@
*/
const defaultTools = {
viewGenaiChatSidebar: "aichat",
viewReviewCheckerSidebar: "reviewchecker",
viewTabsSidebar: "syncedtabs",
viewHistorySidebar: "history",
viewBookmarksSidebar: "bookmarks",
@ -34,6 +35,11 @@ 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) {
this.hide();
}
// Update visibility of View -> Sidebar menu item.
const viewItem = document.getElementById(sidebar.menuId);
if (viewItem) {
@ -142,6 +148,19 @@ var SidebarController = {
}
);
this.registerPrefSidebar(
"browser.shopping.experience2023.integratedSidebar",
"viewReviewCheckerSidebar",
{
elementId: "sidebar-switcher-review-checker",
url: "chrome://browser/content/shopping/shopping.html",
menuId: "menu_reviewCheckerSidebar",
menuL10nId: "menu-view-review-checker",
revampL10nId: "sidebar-menu-review-checker-label",
iconUrl: "chrome://browser/content/shopping/assets/shopping.svg",
}
);
if (!this.sidebarRevampEnabled) {
this.registerPrefSidebar(
"browser.contextual-password-manager.enabled",

View file

@ -11,6 +11,7 @@ import "chrome://global/content/elements/moz-radio-group.mjs";
const l10nMap = new Map([
["viewGenaiChatSidebar", "sidebar-menu-genai-chat-label"],
["viewReviewCheckerSidebar", "sidebar-menu-review-checker-label"],
["viewHistorySidebar", "sidebar-menu-history-label"],
["viewTabsSidebar", "sidebar-menu-synced-tabs-label"],
["viewBookmarksSidebar", "sidebar-menu-bookmarks-label"],
@ -129,11 +130,11 @@ export class SidebarCustomize extends SidebarPage {
handleKeydown(e) {
if (e.code == "ArrowUp") {
if (this.activeExtIndex >= 0) {
if (this.activeExtIndex > 0) {
this.focusIndex(this.activeExtIndex - 1);
}
} else if (e.code == "ArrowDown") {
if (this.activeExtIndex < this.extensionLinks.length) {
if (this.activeExtIndex < this.extensionLinks.length - 1) {
this.focusIndex(this.activeExtIndex + 1);
}
} else if (
@ -164,16 +165,15 @@ export class SidebarCustomize extends SidebarPage {
return html` <div class="extension-item">
<img src=${extension.iconUrl} class="icon" role="presentation" />
<div
class="extension-link"
extensionId=${extension.extensionId}
tabindex=${index === this.activeExtIndex ? 0 : -1}
role="list-item"
role="listitem"
@click=${() => this.manageAddon(extension.extensionId)}
@keydown=${this.handleKeydown}
>
<a
href="about:addons"
tabindex="-1"
class="extension-link"
tabindex=${index === this.activeExtIndex ? 0 : -1}
target="_blank"
@click=${e => e.preventDefault()}
>${extension.tooltiptext}

View file

@ -15,7 +15,6 @@ prefs = [
["browser_extensions_sidebar.js"]
["browser_glean_sidebar.js"]
skip-if = ["a11y_checks"] # Bug 1919185
["browser_hide_sidebar_on_popup.js"]

View file

@ -216,3 +216,38 @@ add_task(async function test_flip_revamp_pref() {
ok(true, "The old sidebar is hidden and the new sidebar is shown.");
await BrowserTestUtils.closeWindow(win);
});
/**
* Check that conditional sidebar tools hide if open on pref change
*/
add_task(async function test_conditional_tools() {
const COMMAND_ID = "viewGenaiChatSidebar";
const win = await BrowserTestUtils.openNewBrowserWindow();
const { SidebarController } = win;
const sidebar = win.document.querySelector("sidebar-main");
await sidebar.updateComplete;
await SpecialPowers.pushPrefEnv({ set: [["browser.ml.chat.enabled", true]] });
await SidebarController.show(COMMAND_ID);
await TestUtils.waitForCondition(() => {
return (
SidebarController.isOpen && SidebarController.currentID == COMMAND_ID
);
}, "The sidebar was opened.");
ok(true, "Conditional sidebar is shown.");
await SpecialPowers.pushPrefEnv({
set: [["browser.ml.chat.enabled", false]],
});
await TestUtils.waitForCondition(() => {
return !SidebarController.isOpen;
}, "The sidebar is hidden.");
ok(true, "Conditional sidebar is hidden after the pref change.");
await BrowserTestUtils.closeWindow(win);
});

View file

@ -122,6 +122,12 @@
"browser.tabs.allow_transparent_browser",
false
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_tabGroupsEnabled",
"browser.tabs.groups.enabled",
false
);
if (AppConstants.MOZ_CRASHREPORTER) {
ChromeUtils.defineESModuleGetters(this, {
@ -7817,6 +7823,16 @@ var TabContextMenu = {
// autohide item's checked state to mirror the autohide pref.
showFullScreenViewContextMenuItems(aPopupMenu);
let contextAddTabToNewGroup = document.getElementById(
"context_addTabToNewGroup"
);
if (gBrowser._tabGroupsEnabled) {
contextAddTabToNewGroup.hidden = false;
contextAddTabToNewGroup.setAttribute("data-l10n-args", tabCountInfo);
} else {
contextAddTabToNewGroup.hidden = true;
}
// Only one of Reload_Tab/Reload_Selected_Tabs should be visible.
document.getElementById("context_reloadTab").hidden = multiselectionContext;
document.getElementById("context_reloadSelectedTabs").hidden =

View file

@ -2,6 +2,12 @@
* 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/. */
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [["browser.tabs.groups.enabled", true]],
});
});
add_task(async function test_tabGroupCreateAndAddTab() {
let tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank");
let group = gBrowser.addTabGroup("blue", "test", [tab1]);
@ -373,3 +379,221 @@ add_task(async function test_moveTabBetweenGroups() {
gBrowser.removeTabGroup(group2);
});
// Context menu tests
// ---
const withTabMenu = async function (tab, callback) {
const tabContextMenu = document.getElementById("tabContextMenu");
Assert.equal(
tabContextMenu.state,
"closed",
"context menu is initially closed"
);
const contextMenuShown = BrowserTestUtils.waitForPopupEvent(
tabContextMenu,
"shown"
);
EventUtils.synthesizeMouseAtCenter(
tab,
{ type: "contextmenu", button: 2 },
window
);
await contextMenuShown;
const addTabMenuItem = document.getElementById("context_addTabToNewGroup");
await callback(addTabMenuItem);
tabContextMenu.hidePopup();
};
/*
* Tests that the context menu options do not appear if the tab group pref is
* disabled
*/
add_task(async function test_tabGroupTabContextMenuWithoutPref() {
await SpecialPowers.pushPrefEnv({
set: [["browser.tabs.groups.enabled", false]],
});
let tab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
await withTabMenu(tab, async addTabMenuItem => {
Assert.ok(addTabMenuItem.hidden, "Add tab menu item is hidden");
});
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
/*
* Tests that if a tab is selected, the "add tab to group" option appears in
* the context menu, and clicking it adds the tab to a new group
*/
add_task(async function test_tabGroupContextMenuAddTabToGroup() {
let otherTab = BrowserTestUtils.addTab(gBrowser, "about:blank");
let otherGroup = gBrowser.addTabGroup("blue", "test", [otherTab]);
let tab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
await withTabMenu(tab, async addTabMenuItem => {
Assert.equal(tab.group, null, "tab is not in group");
Assert.ok(!addTabMenuItem.hidden, "Add tab menu item is visible");
addTabMenuItem.click();
});
Assert.ok(tab.group, "tab is in group");
Assert.notEqual(
tab.group,
otherGroup,
"tab is not in the pre-existing group"
);
Assert.equal(tab.group.label, "", "tab group label is empty");
gBrowser.removeTabGroup(otherGroup);
gBrowser.removeTabGroup(tab.group);
});
/*
* Tests that if multiple tabs are selected and one of the selected tabs has
* its context menu open, the "adds tab to group" option appears in the
* context menu, and clicking it adds the tabs to a new group
*/
add_task(async function test_tabGroupContextMenuAddTabsToGroup() {
let otherTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
let otherGroup = gBrowser.addTabGroup("blue", "test", [otherTab]);
const tabs = Array.from({ length: 3 }, () => {
return BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
});
// Click the first tab in our test group to make sure the default tab at the
// start of the tab strip is deselected
EventUtils.synthesizeMouseAtCenter(tabs[0], {});
tabs.forEach(t => {
EventUtils.synthesizeMouseAtCenter(
t,
{ ctrlKey: true, metaKey: true },
window
);
});
let tabToClick = tabs[2];
await withTabMenu(tabToClick, async addTabMenuItem => {
Assert.ok(!addTabMenuItem.hidden, "Add tab menu item is visible");
addTabMenuItem.click();
});
Assert.ok(tabs[0].group, "tab is in group");
Assert.notEqual(
tabs[0].group,
otherGroup,
"tab is not in the pre-existing group"
);
Assert.equal(tabs[0].group.label, "", "tab group label is empty");
let group = tabs[0].group;
tabs.forEach((t, idx) => {
Assert.equal(t.group, group, `tabs[${idx}] is in group`);
});
gBrowser.removeTabGroup(group);
gBrowser.removeTabGroup(otherGroup);
});
/*
* Tests that if a tab is selected and a tab that is *not* selected
* has its context menu open, the "add tab to group" option appears in the
* context menu, and clicking it adds the *context menu* tab to the group, not
* the selected tab
*/
add_task(
async function test_tabGroupContextMenuAddTabToGroupWhileAnotherSelected() {
let tab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
let otherTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
EventUtils.synthesizeMouseAtCenter(otherTab, {});
await withTabMenu(tab, async addTabMenuItem => {
Assert.equal(
gBrowser.selectedTabs.includes(TabContextMenu.contextTab),
false,
"context menu tab is not selected"
);
Assert.ok(!addTabMenuItem.hidden, "Add tab menu item is visible");
addTabMenuItem.click();
});
Assert.ok(tab.group, "tab is in group");
Assert.equal(otherTab.group, null, "otherTab is not in group");
gBrowser.removeTabGroup(tab.group);
BrowserTestUtils.removeTab(otherTab);
}
);
/*
* Tests that if multiple tabs are selected and a tab that is *not* selected
* has its context menu open, the "add tabs to group" option appears in the
* context menu, and clicking it adds the *context menu* tab to the group, not
* the selected tabs
*/
add_task(
async function test_tabGroupContextMenuAddTabToGroupWhileOthersSelected() {
let tab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
const otherTabs = Array.from({ length: 3 }, () => {
return BrowserTestUtils.addTab(gBrowser, "about:blank", {
skipAnimation: true,
});
});
otherTabs.forEach(t => {
EventUtils.synthesizeMouseAtCenter(
t,
{ ctrlKey: true, metaKey: true },
window
);
});
await withTabMenu(tab, async addTabMenuItem => {
Assert.ok(
!gBrowser.selectedTabs.includes(TabContextMenu.contextTab),
"context menu tab is not selected"
);
Assert.ok(!addTabMenuItem.hidden, "Add tab menu item is visible");
addTabMenuItem.click();
});
Assert.ok(tab.group, "tab is in group");
otherTabs.forEach((t, idx) => {
Assert.equal(t.group, null, `otherTab[${idx}] is not in group`);
});
gBrowser.removeTabGroup(tab.group);
otherTabs.forEach(t => {
BrowserTestUtils.removeTab(t);
});
}
);

View file

@ -30,6 +30,8 @@ reason = "test depends on update channel"
["browser_default_webprotocol_handler_mailto.js"]
run-if = ["os == 'win'"]
["browser_forced_colors.js"]
["browser_initial_tab_remoteType.js"]
https_first_disabled = true

View file

@ -0,0 +1,20 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_forced_colors_windows() {
await SpecialPowers.pushPrefEnv({
set: [["ui.useAccessibilityTheme", 1]],
});
ok(
!matchMedia("(forced-colors: active)").matches,
"forced-colors: active shouldn't match in chrome"
);
is(
matchMedia("(forced-colors)").matches,
AppConstants.platform == "win",
"forced-colors should match with HCM enabled on windows, even in chrome"
);
});

View file

@ -31,7 +31,7 @@ export class SearchModeSwitcher {
"nsIObserver",
"nsISupportsWeakReference",
]);
Services.obs.addObserver(this, "browser-search-engine-modified", true);
lazy.UrlbarPrefs.addObserver(this);
this.#popup = input.document.getElementById("searchmode-switcher-popup");
@ -39,26 +39,10 @@ export class SearchModeSwitcher {
this.#toolbarbutton = input.document.querySelector(
"#urlbar-searchmode-switcher"
);
this.#toolbarbutton.addEventListener("mousedown", this);
this.#toolbarbutton.addEventListener("keypress", this);
let closebutton = input.document.querySelector(
"#searchmode-switcher-close"
);
closebutton.addEventListener("mousedown", this);
closebutton.addEventListener("keypress", this);
let prefsbutton = input.document.querySelector(
"#searchmode-switcher-popup-search-settings-button"
);
prefsbutton.addEventListener("mousedown", this);
prefsbutton.addEventListener("keypress", this);
input.window.addEventListener(
"MozAfterPaint",
() => this.#updateSearchIcon(),
{ once: true }
);
if (lazy.UrlbarPrefs.get("scotchBonnet.enableOverride")) {
this.#enableObservers();
}
}
/**
@ -156,7 +140,9 @@ export class SearchModeSwitcher {
* Called when the value of the searchMode attribute on UrlbarInput is changed.
*/
onSearchModeChanged() {
this.#updateSearchIcon();
if (lazy.UrlbarPrefs.get("scotchBonnet.enableOverride")) {
this.#updateSearchIcon();
}
}
handleEvent(event) {
@ -198,9 +184,20 @@ export class SearchModeSwitcher {
*/
onPrefChanged(pref) {
switch (pref) {
case "keyword.enabled":
this.#updateSearchIcon();
case "scotchBonnet.enableOverride": {
if (lazy.UrlbarPrefs.get("scotchBonnet.enableOverride")) {
this.#enableObservers();
} else {
this.#disableObservers();
}
break;
}
case "keyword.enabled": {
if (lazy.UrlbarPrefs.get("scotchBonnet.enableOverride")) {
this.#updateSearchIcon();
}
break;
}
}
}
@ -391,4 +388,48 @@ export class SearchModeSwitcher {
this.#popup.hidePopup();
}
#enableObservers() {
Services.obs.addObserver(this, "browser-search-engine-modified", true);
this.#toolbarbutton.addEventListener("mousedown", this);
this.#toolbarbutton.addEventListener("keypress", this);
let closebutton = this.#input.document.querySelector(
"#searchmode-switcher-close"
);
closebutton.addEventListener("mousedown", this);
closebutton.addEventListener("keypress", this);
let prefsbutton = this.#input.document.querySelector(
"#searchmode-switcher-popup-search-settings-button"
);
prefsbutton.addEventListener("mousedown", this);
prefsbutton.addEventListener("keypress", this);
this.#input.window.addEventListener(
"MozAfterPaint",
() => this.#updateSearchIcon(),
{ once: true }
);
}
#disableObservers() {
Services.obs.removeObserver(this, "browser-search-engine-modified");
this.#toolbarbutton.removeEventListener("mousedown", this);
this.#toolbarbutton.removeEventListener("keypress", this);
let closebutton = this.#input.document.querySelector(
"#searchmode-switcher-close"
);
closebutton.removeEventListener("mousedown", this);
closebutton.removeEventListener("keypress", this);
let prefsbutton = this.#input.document.querySelector(
"#searchmode-switcher-popup-search-settings-button"
);
prefsbutton.removeEventListener("mousedown", this);
prefsbutton.removeEventListener("keypress", this);
}
}

View file

@ -11,43 +11,6 @@ add_setup(async function setup() {
});
});
add_task(async function basic() {
info("Open the urlbar and searchmode switcher popup");
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: "",
});
let popup = await UrlbarTestUtils.openSearchModeSwitcher(window);
Assert.ok(
!BrowserTestUtils.isVisible(gURLBar.view.panel),
"The UrlbarView is not visible"
);
info("Press on the bing menu button and enter search mode");
let popupHidden = UrlbarTestUtils.searchModeSwitcherPopupClosed(window);
popup.querySelector("toolbarbutton[label=Bing]").click();
await popupHidden;
await UrlbarTestUtils.assertSearchMode(window, {
engineName: "Bing",
entry: "other",
source: 3,
});
info("Press the close button and escape search mode");
window.document.querySelector("#searchmode-switcher-close").click();
await UrlbarTestUtils.assertSearchMode(window, null);
});
function updateEngine(fun) {
let updated = SearchTestUtils.promiseSearchNotification(
SearchUtils.MODIFIED_TYPE.CHANGED,
SearchUtils.TOPIC_ENGINE_MODIFIED
);
fun();
return updated;
}
add_task(async function disabled_unified_button() {
await SpecialPowers.pushPrefEnv({
set: [["browser.urlbar.scotchBonnet.enableOverride", false]],
@ -96,6 +59,43 @@ add_task(async function disabled_unified_button() {
await SpecialPowers.popPrefEnv();
});
add_task(async function basic() {
info("Open the urlbar and searchmode switcher popup");
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: "",
});
let popup = await UrlbarTestUtils.openSearchModeSwitcher(window);
Assert.ok(
!BrowserTestUtils.isVisible(gURLBar.view.panel),
"The UrlbarView is not visible"
);
info("Press on the bing menu button and enter search mode");
let popupHidden = UrlbarTestUtils.searchModeSwitcherPopupClosed(window);
popup.querySelector("toolbarbutton[label=Bing]").click();
await popupHidden;
await UrlbarTestUtils.assertSearchMode(window, {
engineName: "Bing",
entry: "other",
source: 3,
});
info("Press the close button and escape search mode");
window.document.querySelector("#searchmode-switcher-close").click();
await UrlbarTestUtils.assertSearchMode(window, null);
});
function updateEngine(fun) {
let updated = SearchTestUtils.promiseSearchNotification(
SearchUtils.MODIFIED_TYPE.CHANGED,
SearchUtils.TOPIC_ENGINE_MODIFIED
);
fun();
return updated;
}
add_task(async function new_window() {
let oldEngine = Services.search.getEngineByName("Bing");
await updateEngine(() => {

View file

@ -22,6 +22,8 @@ skip-if = ["apple_silicon && !debug"]
["browser_consecutive_cc_number.js"]
["browser_country_rules.js"]
["browser_de_fields.js"]
["browser_formless.js"]

View file

@ -0,0 +1,39 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* global add_heuristic_tests */
"use strict";
add_heuristic_tests([
{
fixtureData: `
<html>
<body>
<form>
<input id="strasse"/>
<input id="postal"/>
<select id="land"/>
<option value="fr">Frankreich</option>
<option value="de">Deutschland</option>
<option value="ca">Kanada</option>
<option value="us">USA</option>
</select>
</form>
</body>
</html>`,
expectedResult: [
{
default: {
reason: "regex-heuristic",
},
description: `Land matches country"`,
fields: [
{ fieldName: "address-line1" },
{ fieldName: "postal-code" },
{ fieldName: "country" },
],
},
],
},
]);

View file

@ -0,0 +1 @@
usr/bin

View file

@ -0,0 +1,6 @@
#!/bin/sh
FIREFOX="$(command -v firefox)"
[ -x "$FIREFOX.real" ] && exec "$FIREFOX.real" "$@"
exec firefox-esr "$@"

View file

@ -0,0 +1,7 @@
#!/bin/sh -e
if [ ${DEB_PKG_NAME} = firefox-esr ]; then
if [ "$$1" = "remove" ]; then
dpkg-divert --package firefox-esr --rename --remove /usr/bin/firefox
fi
fi

View file

@ -0,0 +1,7 @@
#!/bin/sh -e
if [ ${DEB_PKG_NAME} = firefox-esr ]; then
if [ "$$1" = "upgrade" ] || [ "$$1" = "install" ] ; then
dpkg-divert --package firefox-esr --divert /usr/bin/firefox.real --rename /usr/bin/firefox
fi
fi

View file

@ -5,3 +5,9 @@
override_dh_strip_nondeterminism:
dh_strip_nondeterminism -Xxpi
override_dh_install:
dh_install
ifeq ($(shell dpkg-parsechangelog -S Source), firefox-esr)
install -m 0755 debian/firefox.sh debian/firefox-esr/usr/bin/firefox
endif

View file

@ -0,0 +1,14 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
## Variables:
## $tabCount (Number): the number of tabs that are affected by the action.
tab-context-add-tab-to-new-group =
.label =
{ $tabCount ->
[1] Add Tab to New Group
*[other] Add Tabs to New Group
}
.accesskey = G

View file

@ -5,6 +5,9 @@
menu-view-genai-chat =
.label = AI Chatbot
menu-view-review-checker =
.label = Review Checker
## Labels for sidebar history panel
# Variables:
@ -97,6 +100,8 @@ sidebar-menu-bookmarks-label =
.label = Bookmarks
sidebar-menu-customize-label =
.label = Customize sidebar
sidebar-menu-review-checker-label =
.label = Review Checker
## Headings for sidebar menu panels.

View file

@ -8,7 +8,6 @@
# having to create the same entry for each locale.
[localization] @AB_CD@.jar:
preview/ion.ftl (../components/ion/content/ion.ftl)
preview/protections.ftl (../components/protections/content/protections.ftl)
preview/interventions.ftl (../components/urlbar/content/interventions.ftl)
preview/enUS-searchFeatures.ftl (../components/urlbar/content/enUS-searchFeatures.ftl)
@ -20,6 +19,7 @@
preview/profiles.ftl (../components/profiles/content/profiles.ftl)
preview/backupSettings.ftl (../locales-preview/backupSettings.ftl)
preview/firefoxRelayToAllBrowsers.ftl (../locales-preview/firefoxRelayToAllBrowsers.ftl)
preview/tabGroups.ftl (../locales-preview/tabGroups.ftl)
@AB_CD@.jar:
% locale browser @AB_CD@ %locale/browser/

View file

@ -16,7 +16,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"af": {
"pin": false,
@ -35,7 +35,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"an": {
"pin": false,
@ -54,7 +54,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ar": {
"pin": false,
@ -73,7 +73,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ast": {
"pin": false,
@ -92,7 +92,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"az": {
"pin": false,
@ -111,7 +111,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"be": {
"pin": false,
@ -130,7 +130,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"bg": {
"pin": false,
@ -149,7 +149,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"bn": {
"pin": false,
@ -168,7 +168,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"bo": {
"pin": false,
@ -187,7 +187,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"br": {
"pin": false,
@ -206,7 +206,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"brx": {
"pin": false,
@ -225,7 +225,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"bs": {
"pin": false,
@ -244,7 +244,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ca": {
"pin": false,
@ -263,7 +263,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ca-valencia": {
"pin": false,
@ -282,7 +282,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"cak": {
"pin": false,
@ -301,7 +301,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ckb": {
"pin": false,
@ -320,7 +320,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"cs": {
"pin": false,
@ -339,7 +339,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"cy": {
"pin": false,
@ -358,7 +358,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"da": {
"pin": false,
@ -377,7 +377,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"de": {
"pin": false,
@ -396,7 +396,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"dsb": {
"pin": false,
@ -415,7 +415,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"el": {
"pin": false,
@ -434,7 +434,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"en-CA": {
"pin": false,
@ -453,7 +453,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"en-GB": {
"pin": false,
@ -472,7 +472,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"eo": {
"pin": false,
@ -491,7 +491,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"es-AR": {
"pin": false,
@ -510,7 +510,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"es-CL": {
"pin": false,
@ -529,7 +529,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"es-ES": {
"pin": false,
@ -548,7 +548,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"es-MX": {
"pin": false,
@ -567,7 +567,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"et": {
"pin": false,
@ -586,7 +586,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"eu": {
"pin": false,
@ -605,7 +605,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"fa": {
"pin": false,
@ -624,7 +624,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ff": {
"pin": false,
@ -643,7 +643,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"fi": {
"pin": false,
@ -662,7 +662,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"fr": {
"pin": false,
@ -681,7 +681,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"fur": {
"pin": false,
@ -700,7 +700,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"fy-NL": {
"pin": false,
@ -719,7 +719,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ga-IE": {
"pin": false,
@ -738,7 +738,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"gd": {
"pin": false,
@ -757,7 +757,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"gl": {
"pin": false,
@ -776,7 +776,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"gn": {
"pin": false,
@ -795,7 +795,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"gu-IN": {
"pin": false,
@ -814,7 +814,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"he": {
"pin": false,
@ -833,7 +833,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"hi-IN": {
"pin": false,
@ -852,7 +852,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"hr": {
"pin": false,
@ -871,7 +871,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"hsb": {
"pin": false,
@ -890,7 +890,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"hu": {
"pin": false,
@ -909,7 +909,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"hy-AM": {
"pin": false,
@ -928,7 +928,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"hye": {
"pin": false,
@ -947,7 +947,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ia": {
"pin": false,
@ -966,7 +966,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"id": {
"pin": false,
@ -985,7 +985,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"is": {
"pin": false,
@ -1004,7 +1004,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"it": {
"pin": false,
@ -1023,7 +1023,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ja": {
"pin": false,
@ -1040,7 +1040,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ja-JP-mac": {
"pin": false,
@ -1048,7 +1048,7 @@
"macosx64",
"macosx64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ka": {
"pin": false,
@ -1067,7 +1067,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"kab": {
"pin": false,
@ -1086,7 +1086,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"kk": {
"pin": false,
@ -1105,7 +1105,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"km": {
"pin": false,
@ -1124,7 +1124,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"kn": {
"pin": false,
@ -1143,7 +1143,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ko": {
"pin": false,
@ -1162,7 +1162,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"lij": {
"pin": false,
@ -1181,7 +1181,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"lo": {
"pin": false,
@ -1200,7 +1200,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"lt": {
"pin": false,
@ -1219,7 +1219,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ltg": {
"pin": false,
@ -1238,7 +1238,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"lv": {
"pin": false,
@ -1257,7 +1257,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"meh": {
"pin": false,
@ -1276,7 +1276,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"mk": {
"pin": false,
@ -1295,7 +1295,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"mr": {
"pin": false,
@ -1314,7 +1314,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ms": {
"pin": false,
@ -1333,7 +1333,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"my": {
"pin": false,
@ -1352,7 +1352,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"nb-NO": {
"pin": false,
@ -1371,7 +1371,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ne-NP": {
"pin": false,
@ -1390,7 +1390,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"nl": {
"pin": false,
@ -1409,7 +1409,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"nn-NO": {
"pin": false,
@ -1428,7 +1428,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"oc": {
"pin": false,
@ -1447,7 +1447,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"pa-IN": {
"pin": false,
@ -1466,7 +1466,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"pl": {
"pin": false,
@ -1485,7 +1485,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"pt-BR": {
"pin": false,
@ -1504,7 +1504,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"pt-PT": {
"pin": false,
@ -1523,7 +1523,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"rm": {
"pin": false,
@ -1542,7 +1542,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ro": {
"pin": false,
@ -1561,7 +1561,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ru": {
"pin": false,
@ -1580,7 +1580,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sat": {
"pin": false,
@ -1599,7 +1599,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sc": {
"pin": false,
@ -1618,7 +1618,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"scn": {
"pin": false,
@ -1637,7 +1637,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sco": {
"pin": false,
@ -1656,7 +1656,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"si": {
"pin": false,
@ -1675,7 +1675,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sk": {
"pin": false,
@ -1694,7 +1694,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"skr": {
"pin": false,
@ -1713,7 +1713,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sl": {
"pin": false,
@ -1732,7 +1732,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"son": {
"pin": false,
@ -1751,7 +1751,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sq": {
"pin": false,
@ -1770,7 +1770,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sr": {
"pin": false,
@ -1789,7 +1789,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"sv-SE": {
"pin": false,
@ -1808,7 +1808,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"szl": {
"pin": false,
@ -1827,7 +1827,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ta": {
"pin": false,
@ -1846,7 +1846,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"te": {
"pin": false,
@ -1865,7 +1865,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"tg": {
"pin": false,
@ -1884,7 +1884,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"th": {
"pin": false,
@ -1903,7 +1903,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"tl": {
"pin": false,
@ -1922,7 +1922,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"tr": {
"pin": false,
@ -1941,7 +1941,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"trs": {
"pin": false,
@ -1960,7 +1960,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"uk": {
"pin": false,
@ -1979,7 +1979,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"ur": {
"pin": false,
@ -1998,7 +1998,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"uz": {
"pin": false,
@ -2017,7 +2017,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"vi": {
"pin": false,
@ -2036,7 +2036,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"wo": {
"pin": false,
@ -2055,7 +2055,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"xh": {
"pin": false,
@ -2074,7 +2074,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"zh-CN": {
"pin": false,
@ -2093,7 +2093,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
},
"zh-TW": {
"pin": false,
@ -2112,6 +2112,6 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "254b6375a525bdb081f92eefb5ae88f052426f64"
"revision": "739e514a9b16663a344cd174fce2da5b2b0b7e15"
}
}

View file

@ -1,4 +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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill light-dark(black, white)" d="M13.9 9.81a1.23 1.23 0 0 0 0-.17v-.08a5.67 5.67 0 0 0-2.4-3.36 1.17 1.17 0 0 1-.56-.95V3a1 1 0 0 0-1-1H6.06a1 1 0 0 0-1 1v2.25a1.17 1.17 0 0 1-.56 1 5.66 5.66 0 0 0-2.35 3.33v.12a.53.53 0 0 0 0 .11 5.35 5.35 0 0 0-.11 1 5.65 5.65 0 0 0 3.24 5.09 1 1 0 0 0 .44.1h4.57a1 1 0 0 0 .44-.1A5.65 5.65 0 0 0 14 10.83a5.3 5.3 0 0 0-.1-1.02zm-8.27-2a3.18 3.18 0 0 0 1.43-2.6V4h1.88v1.25a3.18 3.18 0 0 0 1.43 2.6 3.68 3.68 0 0 1 1.54 2.24v.22a2.82 2.82 0 0 1-3.68-.59A3.48 3.48 0 0 0 4.56 9a3.76 3.76 0 0 1 1.07-1.15z"></path></svg>

Before

Width:  |  Height:  |  Size: 870 B

View file

@ -215,7 +215,6 @@
skin/classic/browser/topsites.svg (../shared/icons/topsites.svg)
skin/classic/browser/trending.svg (../shared/icons/trending.svg)
skin/classic/browser/window.svg (../shared/icons/window.svg)
skin/classic/browser/ion.svg (../shared/icons/ion.svg)
skin/classic/browser/search-engine-placeholder.png (../shared/search/search-engine-placeholder.png)

View file

@ -44,6 +44,10 @@
&[sidebarcommand="viewGenaiChatSidebar"] {
min-width: 400px;
}
&[sidebarcommand="viewReviewCheckerSidebar"] {
width: 340px;
}
}
#sidebar-header {

View file

@ -957,11 +957,11 @@ tab-group {
/* Firefox View button and menu item */
:root:not([privatebrowsingmode], [firefoxviewhidden]) :is(toolbarbutton, toolbarpaletteitem) + #tabbrowser-tabs,
:root:not([privatebrowsingmode], [firefoxviewhidden]) :is(toolbarbutton, toolbarpaletteitem) ~ #tabbrowser-tabs,
:root[privatebrowsingmode]:not([firefoxviewhidden]) :is(
toolbarbutton:not(#firefox-view-button),
toolbarpaletteitem:not(#wrapper-firefox-view-button)
) + #tabbrowser-tabs {
) ~ #tabbrowser-tabs {
border-inline-start: var(--tabstrip-inner-border);
padding-inline-start: calc(var(--tab-overflow-pinned-tabs-width) + 2px);
margin-inline-start: 2px;

View file

@ -275,14 +275,6 @@
}
}
#ion-button {
list-style-image: url("chrome://browser/skin/ion.svg");
toolbar[customizing] & {
display: none;
}
}
#import-button {
list-style-image: url("chrome://browser/skin/import.svg");
}

View file

@ -1,3 +1,3 @@
#!/bin/sh
# See comment in cargo-linker.
eval ${MOZ_CARGO_WRAP_HOST_LD} ${MOZ_CARGO_WRAP_HOST_LDFLAGS} '"$@"'
eval ${MOZ_CARGO_WRAP_HOST_LD} '"$@"' ${MOZ_CARGO_WRAP_HOST_LDFLAGS}

View file

@ -1,3 +1,3 @@
@echo off
REM See comment in cargo-linker (without extension)
%MOZ_CARGO_WRAP_HOST_LD% %MOZ_CARGO_WRAP_HOST_LDFLAGS% %*
%MOZ_CARGO_WRAP_HOST_LD% %* %MOZ_CARGO_WRAP_HOST_LDFLAGS%

View file

@ -44,7 +44,7 @@ SANITIZERS = {
use_clang_sanitizer = os.environ.get("MOZ_CLANG_NEWER_THAN_RUSTC_LLVM")
wrap_ld = os.environ["MOZ_CARGO_WRAP_LD"]
args = split(os.environ["MOZ_CARGO_WRAP_LDFLAGS"])
args = []
for arg in sys.argv[1:]:
if arg in ["-lc++", "-lstdc++"]:
wrap_ld = os.environ["MOZ_CARGO_WRAP_LD_CXX"]
@ -60,6 +60,7 @@ for arg in sys.argv[1:]:
args.append(f"-fsanitize={SANITIZERS[suffix]}")
continue
args.append(arg)
args.extend(split(os.environ["MOZ_CARGO_WRAP_LDFLAGS"]))
wrap_ld = split(wrap_ld)
os.execvp(wrap_ld[0], wrap_ld + args)

View file

@ -1,2 +1,2 @@
@echo off
%MOZ_CARGO_WRAP_LD% %MOZ_CARGO_WRAP_LDFLAGS% %*
%MOZ_CARGO_WRAP_LD% %* %MOZ_CARGO_WRAP_LDFLAGS%

View file

@ -99,10 +99,28 @@ class WorkerDetail extends PureComponent {
);
}
render() {
const { fetch, pushServiceEndpoint, scope } = this.props.target.details;
renderOrigin() {
const { origin } = this.props.target.details;
const isEmptyList = !pushServiceEndpoint && !fetch && !scope && !status;
return Localized(
{
id: "about-debugging-worker-origin",
attrs: { label: true },
},
FieldPair({
slug: "origin",
label: "Origin",
value: origin,
})
);
}
render() {
const { fetch, pushServiceEndpoint, scope, origin } =
this.props.target.details;
const isEmptyList =
!pushServiceEndpoint && !fetch && !scope && !origin && !status;
return dom.dl(
{
@ -112,7 +130,8 @@ class WorkerDetail extends PureComponent {
},
pushServiceEndpoint ? this.renderPushService() : null,
fetch ? this.renderFetch() : null,
scope ? this.renderScope() : null
scope ? this.renderScope() : null,
origin ? this.renderOrigin() : null
);
}
}

View file

@ -50,7 +50,7 @@ function toComponentData(workers, isServiceWorker) {
const type = DEBUG_TARGETS.WORKER;
const icon = "chrome://devtools/skin/images/debugging-workers.svg";
let { fetch } = worker;
const { id, name, registrationFront, scope, subscription } = worker;
const { id, name, registrationFront, scope, subscription, origin } = worker;
let pushServiceEndpoint = null;
let status = null;
@ -70,6 +70,7 @@ function toComponentData(workers, isServiceWorker) {
registrationFront,
scope,
status,
origin,
},
icon,
id,

View file

@ -275,3 +275,5 @@ skip-if = ["a11y_checks"] # Bug 1849028 and 1849179 for causing crashes
["browser_aboutdebugging_thisfirefox_worker_inspection.js"]
["browser_aboutdebugging_workers_remote_runtime.js"]
["browser_aboutdebugging_serviceworker_runtime-page-origin.js"]

View file

@ -0,0 +1,74 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from helper-serviceworker.js */
Services.scriptloader.loadSubScript(
CHROME_URL_ROOT + "helper-serviceworker.js",
this
);
/* import-globals-from helper-collapsibilities.js */
Services.scriptloader.loadSubScript(
CHROME_URL_ROOT + "helper-collapsibilities.js",
this
);
const SW_TAB_URL = URL_ROOT_SSL + "resources/service-workers/push-sw.html";
const SW_URL = URL_ROOT_SSL + "resources/service-workers/push-sw.worker.js";
/**
* Test that service workers' origin attributes are displayed in the runtime page.
*/
add_task(async function () {
await SpecialPowers.pushPrefEnv({
set: [["privacy.firstparty.isolate", true]],
});
prepareCollapsibilitiesTest();
await enableServiceWorkerDebugging();
const { document, tab, window } = await openAboutDebugging({
enableWorkerUpdates: true,
});
const store = window.AboutDebugging.store;
await selectThisFirefoxPage(document, store);
// open a tab and register service worker
info("Register a service worker");
const swTab = await addTab(SW_TAB_URL);
// check that service worker is rendered
info("Wait until the service worker appears and is running");
await waitForServiceWorkerRunning(SW_URL, document);
let swPane = getDebugTargetPane("Service Workers", document);
Assert.strictEqual(
swPane.querySelectorAll(".qa-debug-target-item").length,
1,
"Service worker list has one element"
);
const url = new URL(SW_URL);
const firstPartyAttribute = url.origin + "^firstPartyDomain=" + url.hostname;
ok(
swPane
.querySelector(".qa-debug-target-item")
.textContent.includes(firstPartyAttribute),
"Service worker list displays the origin information correctly"
);
// unregister the service worker
info("Unregister service worker");
await unregisterServiceWorker(swTab);
// check that service worker is not rendered anymore
info("Wait for service worker to disappear");
await waitUntil(() => {
swPane = getDebugTargetPane("Service Workers", document);
return swPane.querySelectorAll(".qa-debug-target-item").length === 0;
});
info("Remove tabs");
await removeTab(swTab);
await removeTab(tab);
await SpecialPowers.popPrefEnv();
});

View file

@ -11,6 +11,7 @@ const worker = {
state: PropTypes.number.isRequired,
stateText: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
origin: PropTypes.string.isRequired,
workerDescriptorFront: PropTypes.object,
registrationFront: PropTypes.object,
};

View file

@ -37,6 +37,7 @@ class WorkerDescriptorFront extends DescriptorMixin(
// Do not use `form` name to avoid colliding with protocol.js's `form` method
this.targetForm = json;
this._url = json.url;
this.origin = json.origin;
this.type = json.type;
this.scope = json.scope;
this.fetch = json.fetch;

View file

@ -84,6 +84,7 @@ class RootFront extends FrontClassWithSpec(rootSpec) {
state: workerFront.state,
stateText: workerFront.stateText,
url: workerFront.url,
origin: workerFront.origin,
workerDescriptorFront,
};
});
@ -140,6 +141,7 @@ class RootFront extends FrontClassWithSpec(rootSpec) {
const worker = {
id: front.id,
url: front.url,
origin: front.origin,
name: front.url,
workerDescriptorFront: front,
};

View file

@ -61,6 +61,9 @@ class WalkerFront extends FrontClassWithSpec(walkerSpec) {
// calling watchRootNode, so we keep this assignment as a fallback.
this.rootNode = types.getType("domnode").read(json.root, this);
// Bug 1861328: boolean set to true when color scheme can't be changed (happens when `privacy.resistFingerprinting` is set to true)
this.rfpCSSColorScheme = json.rfpCSSColorScheme;
this.traits = json.traits;
}

View file

@ -43,6 +43,10 @@ class ServiceWorkerRegistrationFront extends FrontClassWithSpec(
return this._form.url;
}
get origin() {
return this._form.origin;
}
get evaluatingWorker() {
return this._getServiceWorker("evaluatingWorker");
}

View file

@ -25,6 +25,10 @@ class ServiceWorkerFront extends FrontClassWithSpec(serviceWorkerSpec) {
return this._form.url;
}
get origin() {
return this._form.origin;
}
get state() {
return this._form.state;
}

View file

@ -592,6 +592,12 @@ CssRuleView.prototype = {
"click",
this._onToggleDarkColorSchemeSimulation
);
const { rfpCSSColorScheme } = this.inspector.walker;
if (rfpCSSColorScheme) {
this.colorSchemeLightSimulationButton.setAttribute("disabled", true);
this.colorSchemeDarkSimulationButton.setAttribute("disabled", true);
console.warn("Color scheme simulation is disabled in RFP mode.");
}
},
/**

View file

@ -114,6 +114,8 @@ skip-if = ["os == 'win' && !debug"] # Bug 1703465
["browser_rules_color_scheme_simulation_rdm.js"]
["browser_rules_color_scheme_simulation_rfp.js"]
["browser_rules_colorpicker-and-image-tooltip_01.js"]
["browser_rules_colorpicker-and-image-tooltip_02.js"]

View file

@ -24,7 +24,9 @@ const TEST_URI = `
transition: all 1s;
@starting-style {
color: gold;
& { /* TODO: Remove & wrapper rule after bug 1919853 */
color: gold;
}
}
}
@ -95,11 +97,13 @@ const TEST_URI = `
outline-offset: 10px;
@starting-style {
--empty-start: ;
background-color: goldenrod;
padding-top: 3px;
margin-top: 3px;
outline-color: goldenrod;
& { /* TODO: Remove & wrapper rule after bug 1919853 */
--empty-start: ;
background-color: goldenrod;
padding-top: 3px;
margin-top: 3px;
outline-color: goldenrod;
}
}
}
</style>

View file

@ -0,0 +1,47 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test color scheme simulation buttons' state with RFPTarget::CSSPrefersColorScheme enabled
const TEST_URI = URL_ROOT_SSL + "doc_media_queries.html";
async function runTest(enabled) {
const sign = enabled ? "+" : "-";
await SpecialPowers.pushPrefEnv({
set: [
["privacy.fingerprintingProtection", true],
[
"privacy.fingerprintingProtection.overrides",
`${sign}CSSPrefersColorScheme`,
],
],
});
await addTab(TEST_URI);
const { inspector } = await openRuleView();
info("Check that the color scheme simulation buttons exist");
const lightButton = inspector.panelDoc.querySelector(
"#color-scheme-simulation-light-toggle"
);
const darkButton = inspector.panelDoc.querySelector(
"#color-scheme-simulation-dark-toggle"
);
ok(lightButton, "The light color scheme simulation button exists");
ok(darkButton, "The dark color scheme simulation button exists");
const expectedState = enabled ? "disabled" : "enabled";
is(lightButton.disabled, enabled, `Light button is ${expectedState}`);
is(darkButton.disabled, enabled, `Dark button is ${expectedState}`);
await SpecialPowers.popPrefEnv();
}
add_task(async function () {
await runTest(true);
});
add_task(async function () {
await runTest(false);
});

View file

@ -23,7 +23,9 @@ const TEST_URI = `
color: tomato;
@container (0px < width) {
background: gold;
& { /* TODO: Remove & wrapper after bug 1919853 */
background: gold;
}
}
}
}

View file

@ -11,7 +11,9 @@ const TEST_URI = `
container-type: inline-size;
@media screen {
container-name: main;
& { /* TODO: Remove & wrapper after bug 1919853 */
container-name: main;
}
& h1 {
border-color: gold;

View file

@ -389,6 +389,10 @@ about-debugging-worker-scope =
about-debugging-worker-push-service =
.label = Push Service
# Displayed for service workers in runtime pages, to label the origin of a worker.
about-debugging-worker-origin =
.label = Origin
# Displayed as title of the inspect button when service worker debugging is disabled.
about-debugging-worker-inspect-action-disabled =
.title = Service Worker inspection is currently disabled for multiprocess { -brand-shorter-name }

View file

@ -451,6 +451,10 @@ class HeadersPanel extends Component {
// stop the header click event
event.stopPropagation();
},
onKeyDown: event => {
// stop the header keydown event
event.stopPropagation();
},
},
span({ className: "headers-summary-label" }, RAW_HEADERS),
span(

View file

@ -218,8 +218,12 @@ async function testRequestWithFormattedView(
rawDataToggle = document.querySelector(
"#request-panel .raw-data-toggle-input .devtools-checkbox-toggle"
);
clickElement(rawDataToggle, monitor);
// Use keyboard to uncheck the toggle so we test Bug 1917296
rawDataToggle.focus();
EventUtils.synthesizeKey("VK_SPACE", {}, rawDataToggle.ownerGlobal);
await waitForContent;
ok(!rawDataToggle.checked, "Raw toggle is unchecked");
}
async function testRequestWithOnlyRawDataView(

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