Update On Fri Sep 20 20:50:58 CEST 2024
This commit is contained in:
parent
887331eb56
commit
1940c09587
1973 changed files with 53834 additions and 34177 deletions
41
Cargo.lock
generated
41
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
});
|
|
@ -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]}`
|
||||
);
|
||||
}
|
||||
});
|
|
@ -66,7 +66,6 @@ add_task(async function chromeUITest() {
|
|||
"compat",
|
||||
"config",
|
||||
"downloads",
|
||||
"ion",
|
||||
"license",
|
||||
"logins",
|
||||
"loginsimportreport",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -16,7 +16,6 @@ pages = [
|
|||
'firefoxview',
|
||||
'messagepreview',
|
||||
'newtab',
|
||||
'ion',
|
||||
'pocket-home',
|
||||
'pocket-saved',
|
||||
'pocket-signup',
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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"
|
||||
}))));
|
||||
}))));
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 today’s 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, you’ll 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 = You’re leaving?
|
||||
leave-ion-consent-bullet-thanks = Thank you for participating.
|
||||
leave-ion-consent-bullet-manage = We’re 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 you’ve 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 you’ve 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. You’ll have the opportunity to learn about a study’s 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 = You’ll 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 we’ll 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 }, we’ll ask you to provide optional demographic data. We’ll also collect basic technical and interaction data as long as you’re participating in { -ion-brand-short-name }. Once you’ve enrolled, you’ll 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 study’s 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 device’s 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 won’t 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>.
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
|
@ -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",
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -42,7 +42,6 @@ DIRS += [
|
|||
"extensions",
|
||||
"firefoxview",
|
||||
"genai",
|
||||
"ion",
|
||||
"messagepreview",
|
||||
"migration",
|
||||
"newtab",
|
||||
|
|
|
@ -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% {
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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))
|
||||
)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -35,6 +35,8 @@ prefs = [
|
|||
|
||||
["browser_inprogress_analysis.js"]
|
||||
|
||||
["browser_integrated_sidebar.js"]
|
||||
|
||||
["browser_keep_close_message_bar.js"]
|
||||
|
||||
["browser_network_offline.js"]
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
20
browser/components/tests/browser/browser_forced_colors.js
Normal file
20
browser/components/tests/browser/browser_forced_colors.js
Normal 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"
|
||||
);
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
1
browser/installer/linux/app/debian/dirs
Normal file
1
browser/installer/linux/app/debian/dirs
Normal file
|
@ -0,0 +1 @@
|
|||
usr/bin
|
6
browser/installer/linux/app/debian/firefox.sh
Normal file
6
browser/installer/linux/app/debian/firefox.sh
Normal file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
FIREFOX="$(command -v firefox)"
|
||||
[ -x "$FIREFOX.real" ] && exec "$FIREFOX.real" "$@"
|
||||
|
||||
exec firefox-esr "$@"
|
7
browser/installer/linux/app/debian/postrm.in
Normal file
7
browser/installer/linux/app/debian/postrm.in
Normal 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
|
7
browser/installer/linux/app/debian/preinst.in
Normal file
7
browser/installer/linux/app/debian/preinst.in
Normal 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
|
|
@ -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
|
||||
|
|
14
browser/locales-preview/tabGroups.ftl
Normal file
14
browser/locales-preview/tabGroups.ftl
Normal 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
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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)
|
||||
|
|
|
@ -44,6 +44,10 @@
|
|||
&[sidebarcommand="viewGenaiChatSidebar"] {
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
&[sidebarcommand="viewReviewCheckerSidebar"] {
|
||||
width: 340px;
|
||||
}
|
||||
}
|
||||
|
||||
#sidebar-header {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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%
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
@echo off
|
||||
%MOZ_CARGO_WRAP_LD% %MOZ_CARGO_WRAP_LDFLAGS% %*
|
||||
%MOZ_CARGO_WRAP_LD% %* %MOZ_CARGO_WRAP_LDFLAGS%
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,10 @@ class ServiceWorkerRegistrationFront extends FrontClassWithSpec(
|
|||
return this._form.url;
|
||||
}
|
||||
|
||||
get origin() {
|
||||
return this._form.origin;
|
||||
}
|
||||
|
||||
get evaluatingWorker() {
|
||||
return this._getServiceWorker("evaluatingWorker");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -23,7 +23,9 @@ const TEST_URI = `
|
|||
color: tomato;
|
||||
|
||||
@container (0px < width) {
|
||||
background: gold;
|
||||
& { /* TODO: Remove & wrapper after bug 1919853 */
|
||||
background: gold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue