Update On Tue Mar 25 19:23:03 CET 2025

This commit is contained in:
github-action[bot] 2025-03-25 19:23:04 +01:00
parent 240d9e05c1
commit 61427ba9a4
3058 changed files with 59039 additions and 22181 deletions

4
Cargo.lock generated
View file

@ -2781,9 +2781,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.22" version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",

View file

@ -219,6 +219,11 @@ bool SelectionManager::SelectionRangeChanged(SelectionType aType,
dom::Document* doc = start->OwnerDoc(); dom::Document* doc = start->OwnerDoc();
MOZ_ASSERT(doc); MOZ_ASSERT(doc);
nsINode* node = aRange.GetClosestCommonInclusiveAncestor(); nsINode* node = aRange.GetClosestCommonInclusiveAncestor();
if (!node) {
// Bug 1954751: This can happen when a Selection is being garbage collected,
// but it's unclear exactly what other circumstances are involved.
return false;
}
HyperTextAccessible* acc = nsAccUtils::GetTextContainer(node); HyperTextAccessible* acc = nsAccUtils::GetTextContainer(node);
if (!acc) { if (!acc) {
return true; return true;

View file

@ -116,10 +116,8 @@ void nsCoreUtils::DispatchClickEvent(XULTreeElement* aTree, int32_t aRowIndex,
int32_t cnvdY = presContext->CSSPixelsToDevPixels(tcY + int32_t(rect.y) + 1) + int32_t cnvdY = presContext->CSSPixelsToDevPixels(tcY + int32_t(rect.y) + 1) +
presContext->AppUnitsToDevPixels(offset.y); presContext->AppUnitsToDevPixels(offset.y);
if (StaticPrefs::dom_popup_experimental()) { // This isn't needed once bug 1924790 is fixed.
// This isn't needed once bug 1924790 is fixed. tcElm->OwnerDoc()->NotifyUserGestureActivation();
tcElm->OwnerDoc()->NotifyUserGestureActivation();
}
// XUL is just desktop, so there is no real reason for senfing touch events. // XUL is just desktop, so there is no real reason for senfing touch events.
DispatchMouseEvent(eMouseDown, cnvdX, cnvdY, tcElm, tcFrame, presShell, DispatchMouseEvent(eMouseDown, cnvdX, cnvdY, tcElm, tcFrame, presShell,

View file

@ -2573,10 +2573,9 @@ void LocalAccessible::DispatchClickEvent(uint32_t aActionIndex) const {
nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, mContent, frame, presShell, nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, mContent, frame, presShell,
widget); widget);
if (StaticPrefs::dom_popup_experimental()) { // This isn't needed once bug 1924790 is fixed.
// This isn't needed once bug 1924790 is fixed. mContent->OwnerDoc()->NotifyUserGestureActivation();
mContent->OwnerDoc()->NotifyUserGestureActivation();
}
nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, mContent, frame, presShell, nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, mContent, frame, presShell,
widget); widget);
nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, mContent, frame, presShell, nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, mContent, frame, presShell,

View file

@ -381,19 +381,6 @@ between
<div id="popover2" popover>popover2</div> <div id="popover2" popover>popover2</div>
<button id="toggle5">toggle5</button> <button id="toggle5">toggle5</button>
</template></div> </template></div>
<script>
const toggle1 = document.getElementById("toggle1");
const popover1 = document.getElementById("popover1");
toggle1.popoverTargetElement = popover1;
const toggle3 = document.getElementById("toggle3");
const shadow = document.getElementById("shadowHost").shadowRoot;
const toggle4 = shadow.getElementById("toggle4");
const popover2 = shadow.getElementById("popover2");
toggle3.popoverTargetElement = popover2;
toggle4.popoverTargetElement = popover2;
const toggle5 = shadow.getElementById("toggle5");
toggle5.popoverTargetElement = popover1;
</script>
`, `,
async function testPopoverIdl(browser, docAcc) { async function testPopoverIdl(browser, docAcc) {
// No popover is showing, so there shouldn't be any details relations. // No popover is showing, so there shouldn't be any details relations.
@ -465,7 +452,23 @@ between
await hidden; await hidden;
await testCachedRelation(toggle4, RELATION_DETAILS, []); await testCachedRelation(toggle4, RELATION_DETAILS, []);
}, },
{ chrome: true, topLevel: true } {
chrome: true,
topLevel: true,
contentSetup: async function contentSetup() {
const toggle1 = content.document.getElementById("toggle1");
const popover1 = content.document.getElementById("popover1");
toggle1.popoverTargetElement = popover1;
const toggle3 = content.document.getElementById("toggle3");
const shadow = content.document.getElementById("shadowHost").shadowRoot;
const toggle4 = shadow.getElementById("toggle4");
const popover2 = shadow.getElementById("popover2");
toggle3.popoverTargetElement = popover2;
toggle4.popoverTargetElement = popover2;
const toggle5 = shadow.getElementById("toggle5");
toggle5.popoverTargetElement = popover1;
},
}
); );
/** /**

View file

@ -348,6 +348,12 @@ function wrapWithIFrame(doc, options = {}) {
id: DEFAULT_IFRAME_DOC_BODY_ID, id: DEFAULT_IFRAME_DOC_BODY_ID,
...iframeDocBodyAttrs, ...iframeDocBodyAttrs,
}; };
if (options.contentSetup) {
// Hide the body initially so we can ensure that any changes made by
// contentSetup are included when the body's content is initially added to
// the accessibility tree.
iframeDocBodyAttrs["aria-hidden"] = "true";
}
if (options.remoteIframe) { if (options.remoteIframe) {
// eslint-disable-next-line @microsoft/sdl/no-insecure-url // eslint-disable-next-line @microsoft/sdl/no-insecure-url
const srcURL = new URL(`http://example.net/document-builder.sjs`); const srcURL = new URL(`http://example.net/document-builder.sjs`);
@ -418,6 +424,11 @@ function snippetToURL(doc, options = {}) {
if (gIsIframe) { if (gIsIframe) {
doc = wrapWithIFrame(doc, options); doc = wrapWithIFrame(doc, options);
} else if (options.contentSetup) {
// Hide the body initially so we can ensure that any changes made by
// contentSetup are included when the body's content is initially added to
// the accessibility tree.
attrs["aria-hidden"] = "true";
} }
const encodedDoc = encodeURIComponent( const encodedDoc = encodeURIComponent(
@ -595,6 +606,21 @@ function accessibleTask(doc, task, options = {}) {
} }
} }
if (options.contentSetup) {
info("Executing contentSetup");
const ready = waitForEvent(EVENT_REORDER, currentContentDoc());
await invokeContentTask(browser, [], options.contentSetup);
// snippetToURL set aria-hidden on the body. We now Remove aria-hidden
// and wait for a reorder on the body. This guarantees that any
// changes made by contentSetup are included when the body's content
// is initially added to the accessibility tree and that the
// accessibility tree is up to date.
await invokeContentTask(browser, [], () => {
content.document.body.removeAttribute("aria-hidden");
});
await ready;
info("contentSetup done");
}
await loadContentScripts(browser, { await loadContentScripts(browser, {
script: "Common.sys.mjs", script: "Common.sys.mjs",
symbol: "CommonUtils", symbol: "CommonUtils",
@ -670,6 +696,22 @@ function accessibleTask(doc, task, options = {}) {
* - {CacheDomain} cacheDomains * - {CacheDomain} cacheDomains
* The set of cache domains that should be present at the start of the * The set of cache domains that should be present at the start of the
* test. If not set, all cache domains will be present. * test. If not set, all cache domains will be present.
* - {Function|AsyncFunction} contentSetup
* An optional task to run to set up the content document before the
* test starts. If this test is to be run as a chrome document in the
* parent process (chrome: true), This should be used instead of an
* inline <script> element in the test snippet, since inline script is
* not allowed in such documents. This task is ultimately executed
* using SpecialPowers.spawn. Any updates to the content within the
* body will be included when the content is initially added to the
* accessibility tree. The accessibility tree is guaranteed to be up
* to date when the test starts. This will not work correctly for
* changes to the html or body elements themselves. Note that you will
* need to define this exactly as follows:
* contentSetup: async function contentSetup() { ... }
* async contentSetup() will fail when the task is serialized.
* contentSetup: async function() will be changed to
* async contentSetup() by the linter and likewise fail.
*/ */
function addAccessibleTask(doc, task, options = {}) { function addAccessibleTask(doc, task, options = {}) {
const { const {

View file

@ -170,18 +170,7 @@ add_task(async function testTextFragmentSamePage() {
* Test custom highlight mutations. * Test custom highlight mutations.
*/ */
addAccessibleTask( addAccessibleTask(
` snippet,
${snippet}
<script>
const firstText = document.getElementById("first").firstChild;
// Highlight the word "first".
const range1 = new Range();
range1.setStart(firstText, 4);
range1.setEnd(firstText, 9);
const highlight1 = new Highlight(range1);
CSS.highlights.set("highlight1", highlight1);
</script>
`,
async function testCustomHighlightMutations(browser, docAcc) { async function testCustomHighlightMutations(browser, docAcc) {
info("Checking initial highlight"); info("Checking initial highlight");
const first = findAccessibleChildByID(docAcc, "first"); const first = findAccessibleChildByID(docAcc, "first");
@ -272,41 +261,26 @@ ${snippet}
}); });
await rangeCheck; await rangeCheck;
}, },
{ chrome: true, topLevel: true } {
chrome: true,
topLevel: true,
contentSetup: async function contentSetup() {
const firstText = content.document.getElementById("first").firstChild;
// Highlight the word "first".
const range1 = new content.Range();
range1.setStart(firstText, 4);
range1.setEnd(firstText, 9);
const highlight1 = new content.Highlight(range1);
content.CSS.highlights.set("highlight1", highlight1);
},
}
); );
/** /**
* Test custom highlight types. * Test custom highlight types.
*/ */
addAccessibleTask( addAccessibleTask(
` snippet,
${snippet}
<script>
const firstText = document.getElementById("first").firstChild;
// Highlight the word "The".
const range1 = new Range();
range1.setStart(firstText, 0);
range1.setEnd(firstText, 3);
const highlight = new Highlight(range1);
CSS.highlights.set("highlight", highlight);
// Make the word "first" a spelling error.
const range2 = new Range();
range2.setStart(firstText, 4);
range2.setEnd(firstText, 9);
const spelling = new Highlight(range2);
spelling.type = "spelling-error";
CSS.highlights.set("spelling", spelling);
// Make the word "phrase" a grammar error.
const range3 = new Range();
range3.setStart(firstText, 10);
range3.setEnd(firstText, 16);
const grammar = new Highlight(range3);
grammar.type = "grammar-error";
CSS.highlights.set("grammar", grammar);
</script>
`,
async function testCustomHighlightTypes(browser, docAcc) { async function testCustomHighlightTypes(browser, docAcc) {
const first = findAccessibleChildByID(docAcc, "first"); const first = findAccessibleChildByID(docAcc, "first");
ok( ok(
@ -345,85 +319,42 @@ ${snippet}
"second highlight ranges correct" "second highlight ranges correct"
); );
}, },
{ chrome: true, topLevel: true } {
chrome: true,
topLevel: true,
contentSetup: async function contentSetup() {
const firstText = content.document.getElementById("first").firstChild;
// Highlight the word "The".
const range1 = new content.Range();
range1.setStart(firstText, 0);
range1.setEnd(firstText, 3);
const highlight = new content.Highlight(range1);
content.CSS.highlights.set("highlight", highlight);
// Make the word "first" a spelling error.
const range2 = new content.Range();
range2.setStart(firstText, 4);
range2.setEnd(firstText, 9);
const spelling = new content.Highlight(range2);
spelling.type = "spelling-error";
content.CSS.highlights.set("spelling", spelling);
// Make the word "phrase" a grammar error.
const range3 = new content.Range();
range3.setStart(firstText, 10);
range3.setEnd(firstText, 16);
const grammar = new content.Highlight(range3);
grammar.type = "grammar-error";
content.CSS.highlights.set("grammar", grammar);
},
}
); );
/** /**
* Test overlapping custom highlights. * Test overlapping custom highlights.
*/ */
addAccessibleTask( addAccessibleTask(
` snippet,
${snippet}
<script>
const firstText = document.getElementById("first").firstChild;
// Make the word "The" both a highlight and a spelling error.
const range1 = new Range();
range1.setStart(firstText, 0);
range1.setEnd(firstText, 3);
const highlight1 = new Highlight(range1);
CSS.highlights.set("highlight1", highlight1);
const spelling = new Highlight(range1);
spelling.type = "spelling-error";
CSS.highlights.set("spelling", spelling);
// Highlight the word "first".
const range2 = new Range();
range2.setStart(firstText, 4);
range2.setEnd(firstText, 9);
highlight1.add(range2);
// Make "fir" a spelling error.
const range3 = new Range();
range3.setStart(firstText, 4);
range3.setEnd(firstText, 7);
spelling.add(range3);
// Make "rst" a spelling error.
const range4 = new Range();
range4.setStart(firstText, 6);
range4.setEnd(firstText, 9);
spelling.add(range4);
// Highlight the word "phrase".
const range5 = new Range();
range5.setStart(firstText, 10);
range5.setEnd(firstText, 16);
highlight1.add(range5);
// Make "ras" a spelling error.
const range6 = new Range();
range6.setStart(firstText, 12);
range6.setEnd(firstText, 15);
spelling.add(range6);
const secondText = document.querySelector("#second i").firstChild;
// Highlight the word "second".
const range7 = new Range();
range7.setStart(secondText, 0);
range7.setEnd(secondText, 6);
highlight1.add(range7);
// Make "sec" a spelling error.
const range8 = new Range();
range8.setStart(secondText, 0);
range8.setEnd(secondText, 3);
spelling.add(range8);
// Make "nd" a spelling error.
const range9 = new Range();
range9.setStart(secondText, 4);
range9.setEnd(secondText, 6);
spelling.add(range9);
const phrase2Text = document.querySelector("#second b").firstChild;
// Highlight the word "phrase".
const range10 = new Range();
range10.setStart(phrase2Text, 0);
range10.setEnd(phrase2Text, 6);
highlight1.add(range10);
// Highlight "ras" using a different Highlight.
const range11 = new Range();
range11.setStart(phrase2Text, 2);
range11.setEnd(phrase2Text, 5);
const highlight2 = new Highlight(range11);
CSS.highlights.set("highlight2", highlight2);
</script>
`,
async function testCustomHighlightOverlapping(browser, docAcc) { async function testCustomHighlightOverlapping(browser, docAcc) {
const first = findAccessibleChildByID(docAcc, "first"); const first = findAccessibleChildByID(docAcc, "first");
ok( ok(
@ -484,5 +415,78 @@ ${snippet}
"second spelling ranges correct" "second spelling ranges correct"
); );
}, },
{ chrome: true, topLevel: true } {
chrome: true,
topLevel: true,
contentSetup: async function contentSetup() {
const firstText = content.document.getElementById("first").firstChild;
// Make the word "The" both a highlight and a spelling error.
const range1 = new content.Range();
range1.setStart(firstText, 0);
range1.setEnd(firstText, 3);
const highlight1 = new content.Highlight(range1);
content.CSS.highlights.set("highlight1", highlight1);
const spelling = new content.Highlight(range1);
spelling.type = "spelling-error";
content.CSS.highlights.set("spelling", spelling);
// Highlight the word "first".
const range2 = new content.Range();
range2.setStart(firstText, 4);
range2.setEnd(firstText, 9);
highlight1.add(range2);
// Make "fir" a spelling error.
const range3 = new content.Range();
range3.setStart(firstText, 4);
range3.setEnd(firstText, 7);
spelling.add(range3);
// Make "rst" a spelling error.
const range4 = new content.Range();
range4.setStart(firstText, 6);
range4.setEnd(firstText, 9);
spelling.add(range4);
// Highlight the word "phrase".
const range5 = new content.Range();
range5.setStart(firstText, 10);
range5.setEnd(firstText, 16);
highlight1.add(range5);
// Make "ras" a spelling error.
const range6 = new content.Range();
range6.setStart(firstText, 12);
range6.setEnd(firstText, 15);
spelling.add(range6);
const secondText = content.document.querySelector("#second i").firstChild;
// Highlight the word "second".
const range7 = new content.Range();
range7.setStart(secondText, 0);
range7.setEnd(secondText, 6);
highlight1.add(range7);
// Make "sec" a spelling error.
const range8 = new content.Range();
range8.setStart(secondText, 0);
range8.setEnd(secondText, 3);
spelling.add(range8);
// Make "nd" a spelling error.
const range9 = new content.Range();
range9.setStart(secondText, 4);
range9.setEnd(secondText, 6);
spelling.add(range9);
const phrase2Text =
content.document.querySelector("#second b").firstChild;
// Highlight the word "phrase".
const range10 = new content.Range();
range10.setStart(phrase2Text, 0);
range10.setEnd(phrase2Text, 6);
highlight1.add(range10);
// Highlight "ras" using a different Highlight.
const range11 = new content.Range();
range11.setStart(phrase2Text, 2);
range11.setEnd(phrase2Text, 5);
const highlight2 = new content.Highlight(range11);
content.CSS.highlights.set("highlight2", highlight2);
},
}
); );

View file

@ -1841,6 +1841,7 @@ pref("browser.newtabpage.activity-stream.newtabWallpapers.customWallpaper.enable
// Utility preferences for custom wallpaper upload // Utility preferences for custom wallpaper upload
pref("browser.newtabpage.activity-stream.newtabWallpapers.customWallpaper.uuid", ""); pref("browser.newtabpage.activity-stream.newtabWallpapers.customWallpaper.uuid", "");
pref("browser.newtabpage.activity-stream.newtabWallpapers.customWallpaper.fileSize", 0); pref("browser.newtabpage.activity-stream.newtabWallpapers.customWallpaper.fileSize", 0);
pref("browser.newtabpage.activity-stream.newtabWallpapers.customWallpaper.fileSize.enabled", false);
// Current new tab page background images. // Current new tab page background images.
pref("browser.newtabpage.activity-stream.newtabWallpapers.wallpaper", ""); pref("browser.newtabpage.activity-stream.newtabWallpapers.wallpaper", "");
@ -2099,10 +2100,10 @@ pref("sidebar.revamp.round-content-area", false);
pref("sidebar.animation.enabled", true); pref("sidebar.animation.enabled", true);
pref("sidebar.animation.duration-ms", 200); pref("sidebar.animation.duration-ms", 200);
pref("sidebar.animation.expand-on-hover.duration-ms", 400); pref("sidebar.animation.expand-on-hover.duration-ms", 400);
// The sidebar.main.tools pref cannot be changed. // This pref is used to store user customized tools in the sidebar launcher and shouldn't be changed.
// Use the sidebar.newTool.migration. pref branch to introduce a new "tool" to the sidebar launcher; // See https://firefox-source-docs.mozilla.org/browser/components/sidebar/docs/index.html for ways
// see https://firefox-source-docs.mozilla.org/browser/components/sidebar/docs/index.html for instructions. // you can introduce a new tool to the sidebar launcher.
pref("sidebar.main.tools", "aichat,syncedtabs,history"); pref("sidebar.main.tools", "");
pref("sidebar.verticalTabs", false); pref("sidebar.verticalTabs", false);
pref("sidebar.visibility", "always-show"); pref("sidebar.visibility", "always-show");
// Sidebar UI state is stored per-window via session restore. Use this pref // Sidebar UI state is stored per-window via session restore. Use this pref

View file

@ -24,7 +24,7 @@ add_task(async function findbar_test() {
await gFindBarPromise; await gFindBarPromise;
gFindBar.open(); gFindBar.open();
await new ContentTask.spawn(newTab.linkedBrowser, null, async function () { await ContentTask.spawn(newTab.linkedBrowser, null, async function () {
let iframe = content.document.getElementById("iframe"); let iframe = content.document.getElementById("iframe");
let awaitLoad = ContentTaskUtils.waitForEvent(iframe, "load", false); let awaitLoad = ContentTaskUtils.waitForEvent(iframe, "load", false);
iframe.src = "https://example.org/"; iframe.src = "https://example.org/";

View file

@ -19,7 +19,7 @@ const TEST_CASES = [
}, },
{ {
type: "chrome page", type: "chrome page",
testURL: "chrome://global/skin/in-content/info-pages.css", testURL: "chrome://global/content/mozilla.html",
hidden: true, hidden: true,
}, },
{ {

View file

@ -75,8 +75,8 @@ var tests = [
}, },
{ {
name: "chrome:", name: "chrome:",
location: "chrome://global/skin/in-content/info-pages.css", location: "chrome://global/content/mozilla.html",
hostForDisplay: "chrome://global/skin/in-content/info-pages.css", hostForDisplay: "chrome://global/content/mozilla.html",
hasSubview: false, hasSubview: false,
}, },
]; ];

View file

@ -3698,7 +3698,7 @@ BrowserGlue.prototype = {
_migrateUI() { _migrateUI() {
// Use an increasing number to keep track of the current migration state. // Use an increasing number to keep track of the current migration state.
// Completely unrelated to the current Firefox release number. // Completely unrelated to the current Firefox release number.
const UI_VERSION = 152; const UI_VERSION = 153;
const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL; const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
if (!Services.prefs.prefHasUserValue("browser.migration.version")) { if (!Services.prefs.prefHasUserValue("browser.migration.version")) {
@ -4501,6 +4501,19 @@ BrowserGlue.prototype = {
} }
} }
if (
currentUIVersion < 153 &&
Services.prefs.getBoolPref("sidebar.revamp") &&
!Services.prefs.prefHasUserValue("sidebar.main.tools")
) {
// This pref will now be a user set branch but we want to preserve the previous
// default value for existing sidebar.revamp users who hadn't changed it.
Services.prefs.setCharPref(
"sidebar.main.tools",
"aichat,syncedtabs,history"
);
}
// Update the migration version. // Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION); Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
}, },

View file

@ -1894,10 +1894,8 @@ const MESSAGES = () => {
{ {
type: "action", type: "action",
label: { label: {
raw: { string_id:
string_id: "shopping-callout-not-opted-in-integrated-reminder-do-not-show",
"shopping-callout-not-opted-in-integrated-reminder-do-not-show",
},
}, },
action: { action: {
type: "SET_PREF", type: "SET_PREF",
@ -1914,10 +1912,8 @@ const MESSAGES = () => {
{ {
type: "action", type: "action",
label: { label: {
raw: { string_id:
string_id: "shopping-callout-not-opted-in-integrated-reminder-show-fewer",
"shopping-callout-not-opted-in-integrated-reminder-show-fewer",
},
}, },
action: { action: {
type: "MULTI_ACTION", type: "MULTI_ACTION",
@ -1954,10 +1950,8 @@ const MESSAGES = () => {
{ {
type: "action", type: "action",
label: { label: {
raw: { string_id:
string_id: "shopping-callout-not-opted-in-integrated-reminder-manage-settings",
"shopping-callout-not-opted-in-integrated-reminder-manage-settings",
},
}, },
action: { action: {
type: "OPEN_ABOUT_PAGE", type: "OPEN_ABOUT_PAGE",
@ -2150,10 +2144,8 @@ const MESSAGES = () => {
{ {
type: "action", type: "action",
label: { label: {
raw: { string_id:
string_id: "shopping-callout-not-opted-in-integrated-reminder-do-not-show",
"shopping-callout-not-opted-in-integrated-reminder-do-not-show",
},
}, },
action: { action: {
type: "SET_PREF", type: "SET_PREF",
@ -2170,10 +2162,8 @@ const MESSAGES = () => {
{ {
type: "action", type: "action",
label: { label: {
raw: { string_id:
string_id: "shopping-callout-not-opted-in-integrated-reminder-show-fewer",
"shopping-callout-not-opted-in-integrated-reminder-show-fewer",
},
}, },
action: { action: {
type: "MULTI_ACTION", type: "MULTI_ACTION",
@ -2210,10 +2200,8 @@ const MESSAGES = () => {
{ {
type: "action", type: "action",
label: { label: {
raw: { string_id:
string_id: "shopping-callout-not-opted-in-integrated-reminder-manage-settings",
"shopping-callout-not-opted-in-integrated-reminder-manage-settings",
},
}, },
action: { action: {
type: "OPEN_ABOUT_PAGE", type: "OPEN_ABOUT_PAGE",

View file

@ -29,9 +29,13 @@ add_task(async function test_show_chat() {
Assert.ok(GenAI.canShowChatEntrypoint, "Can show with provider"); Assert.ok(GenAI.canShowChatEntrypoint, "Can show with provider");
Services.prefs.setStringPref("sidebar.main.tools", "aichat");
Services.prefs.setBoolPref("sidebar.revamp", true); Services.prefs.setBoolPref("sidebar.revamp", true);
Assert.ok(GenAI.canShowChatEntrypoint, "Can show with revamp"); Assert.ok(
GenAI.canShowChatEntrypoint,
"Can show with revamp and aichat tool"
);
Services.prefs.setStringPref("sidebar.main.tools", "history"); Services.prefs.setStringPref("sidebar.main.tools", "history");

View file

@ -341,6 +341,7 @@ newtab:
- interaction - interaction
notification_emails: notification_emails:
- nbarrett@mozilla.com - nbarrett@mozilla.com
- mcrawford@mozilla.com
expires: never expires: never
extra_keys: extra_keys:
selected_wallpaper: selected_wallpaper:
@ -353,28 +354,6 @@ newtab:
description: > description: >
Whether or not user had a previously set wallpaper Whether or not user had a previously set wallpaper
type: boolean type: boolean
newtab_visit_id: *newtab_visit_id
send_in_pings:
- newtab
wallpaper_upload:
type: event
description: >
Recorded when a user uploads a custom wallpaper
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1943663
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1943663
data_sensitivity:
- interaction
notification_emails:
- mcrawford@mozilla.com
expires: never
extra_keys:
had_previous_wallpaper:
description: >
Whether or not user had a previously set wallpaper
type: boolean
had_uploaded_previously: had_uploaded_previously:
description: > description: >
Whether or not user had a previously uploaded a custom wallpaper Whether or not user had a previously uploaded a custom wallpaper

View file

@ -0,0 +1,16 @@
<!-- 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 width="120" height="40" viewBox="0 0 120 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="16" width="120" height="24" fill="white"/>
<rect y="12" width="120" height="4" fill="#F9F9FB"/>
<rect width="120" height="12" fill="#F0F0F4"/>
<mask id="mask0" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="0" width="86" height="41">
<path d="M76 0.0661167C76 0.0661167 60.5 -1.9339 60.5 16.0661C60.5 37.0662 48.3333 40.0661 42 40.5661L128 40.5662V0.0661167H76Z" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0)">
<path d="M45 16H120V40H45V16Z" fill="#42414D"/>
<path d="M45 0H120V12H45V0Z" fill="#1C1B22"/>
<path d="M45 12H120V16H45V12Z" fill="#2B2A33"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 897 B

View file

@ -10,18 +10,28 @@
gap: var(--space-xlarge); gap: var(--space-xlarge);
} }
#profile-content h2[data-l10n-id="edit-profile-page-header"] {
margin-block: 0;
}
#header-avatar { #header-avatar {
-moz-context-properties: fill, stroke; -moz-context-properties: fill, stroke;
width: var(--header-avatar-size); width: var(--header-avatar-size);
height: var(--header-avatar-size); height: var(--header-avatar-size);
border-radius: var(--border-radius-circle); border-radius: var(--border-radius-circle);
margin-inline-end: var(--space-xxlarge);
} }
#profile-name-area { #profile-name-area {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-xsmall); gap: var(--space-xsmall);
margin-block: 0 var(--space-large);
}
#profile-name-area label {
margin-bottom: var(--space-xsmall);
} }
#profile-name { #profile-name {
@ -68,8 +78,18 @@
color: var(--icon-color-success); color: var(--icon-color-success);
} }
#themes::part(inputs) {
margin-top: var(--space-medium);
}
#avatars::part(inputs) {
margin-top: var(--space-medium);
}
#avatars::part(inputs), #avatars::part(inputs),
#themes::part(inputs) { #themes::part(inputs) {
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
row-gap: var(--space-small);
column-gap: var(--space-medium);
} }

View file

@ -77,7 +77,7 @@ export class NewProfileCard extends EditProfileCard {
<span data-l10n-id="new-profile-page-header-description"></span> <span data-l10n-id="new-profile-page-header-description"></span>
<a <a
is="moz-support-link" is="moz-support-link"
support-page="profiles" support-page="profile-management"
data-l10n-id="new-profile-page-learn-more" data-l10n-id="new-profile-page-learn-more"
></a> ></a>
</p> </p>

View file

@ -7,10 +7,10 @@
outline-offset: var(--focus-outline-offset); outline-offset: var(--focus-outline-offset);
border: 1px solid var(--border-color-interactive); border: 1px solid var(--border-color-interactive);
} }
.wrapper[checked] ::slotted(*:first-of-type) { .wrapper[checked] ::slotted(*:first-of-type) {
border: var(--focus-outline); border-width: var(--border-width);
border-width: 1px; border-style: solid;
border-color: var(--border-color-interactive);
} }
.wrapper:focus-within ::slotted(*:first-of-type) { .wrapper:focus-within ::slotted(*:first-of-type) {

View file

@ -52,7 +52,8 @@ new-profile-card {
#delete-profile-card { #delete-profile-card {
display: flex; display: flex;
gap: var(--space-xxlarge); gap: var(--space-xxlarge);
padding: var(--space-xxlarge); padding-block: 50px var(--space-xxlarge);
padding-inline: var(--space-xxlarge);
@media only screen and (width <= 830px) { @media only screen and (width <= 830px) {
flex-direction: column; flex-direction: column;

View file

@ -20,12 +20,37 @@ export class ProfilesThemeCard extends MozLitElement {
imgHolder: ".img-holder", imgHolder: ".img-holder",
}; };
firstUpdated() {
super.firstUpdated();
this.updateThemeImage();
}
updateThemeImage() {
if (!this.theme) {
return;
}
if (this.theme.id === "default-theme@mozilla.org") {
// For system theme, we use a special SVG that shows the light/dark wave design
this.backgroundImg.src =
"chrome://browser/content/profiles/assets/system-theme-background.svg";
// Reset any inline styles since the SVG has its own colors
this.backgroundImg.style.fill = "";
this.backgroundImg.style.stroke = "";
this.imgHolder.style.backgroundColor = "";
} else {
// For other themes, use the standard SVG with dynamic colors
this.backgroundImg.src =
"chrome://browser/content/profiles/assets/theme-selector-background.svg";
this.backgroundImg.style.fill = this.theme.chromeColor;
this.backgroundImg.style.stroke = this.theme.toolbarColor;
this.imgHolder.style.backgroundColor = this.theme.contentColor;
}
}
updated() { updated() {
super.updated(); super.updated();
this.updateThemeImage();
this.backgroundImg.style.fill = this.theme.chromeColor;
this.backgroundImg.style.stroke = this.theme.toolbarColor;
this.imgHolder.style.backgroundColor = this.theme.contentColor;
} }
render() { render() {
@ -42,9 +67,7 @@ export class ProfilesThemeCard extends MozLitElement {
<moz-card class="theme-card"> <moz-card class="theme-card">
<div class="theme-content"> <div class="theme-content">
<div class="img-holder"> <div class="img-holder">
<img <img />
src="chrome://browser/content/profiles/assets/theme-selector-background.svg"
/>
</div> </div>
<div <div
class="theme-name" class="theme-name"

View file

@ -36,6 +36,7 @@ head = "../unit/head.js head.js"
run-if = ["os != 'linux'"] # Linux clients cannot remote themselves. run-if = ["os != 'linux'"] # Linux clients cannot remote themselves.
["browser_preferences.js"] ["browser_preferences.js"]
fail-if = ["a11y_checks"] # Bug 1955503
["browser_test_db_lazily_created.js"] ["browser_test_db_lazily_created.js"]

View file

@ -640,8 +640,7 @@ export class ShoppingContainer extends MozLitElement {
!RPMGetBoolPref(HAS_SEEN_POSITION_NOTIFICATION_CARD_PREF, true) && !RPMGetBoolPref(HAS_SEEN_POSITION_NOTIFICATION_CARD_PREF, true) &&
this.isProductPage; this.isProductPage;
let canShowKeepClosedMessage = let canShowKeepClosedMessage =
this.showingKeepClosedMessage && this.showingKeepClosedMessage && this.isProductPage;
RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, true);
if (canShowNotificationCard) { if (canShowNotificationCard) {
return this.newPositionNotificationCardTemplate(); return this.newPositionNotificationCardTemplate();
@ -730,7 +729,9 @@ export class ShoppingContainer extends MozLitElement {
if ( if (
yetToSeeNotificationCard || yetToSeeNotificationCard ||
!RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false) !RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false) ||
this.showOnboarding ||
!this.isProductPage
) { ) {
return false; return false;
} }

View file

@ -5,6 +5,10 @@
/* import-globals-from head.js */ /* import-globals-from head.js */
// withReviewCheckerSidebar calls SpecialPowers.spawn, which injects
// ContentTaskUtils in the scope of the callback. Eslint doesn't know about
// that.
/* global ContentTaskUtils */
const CONTENT_PAGE = "https://example.com"; const CONTENT_PAGE = "https://example.com";
add_setup(async function setup() { add_setup(async function setup() {

View file

@ -4,6 +4,10 @@
"use strict"; "use strict";
/* import-globals-from head.js */ /* import-globals-from head.js */
// withReviewCheckerSidebar calls SpecialPowers.spawn, which injects
// ContentTaskUtils in the scope of the callback. Eslint doesn't know about
// that.
/* global ContentTaskUtils */
const CONTENT_PAGE = "https://example.com"; const CONTENT_PAGE = "https://example.com";
const NON_PDP_PAGE = "about:about"; const NON_PDP_PAGE = "about:about";

View file

@ -4,6 +4,12 @@
"use strict"; "use strict";
/* import-globals-from head.js */ /* import-globals-from head.js */
// withReviewCheckerSidebar calls SpecialPowers.spawn, which injects
// ContentTaskUtils in the scope of the callback. Eslint doesn't know about
// that.
/* global ContentTaskUtils */
const NON_PDP_PAGE = "about:about";
async function testNotificationCardThenCloseRC() { async function testNotificationCardThenCloseRC() {
await withReviewCheckerSidebar(async _args => { await withReviewCheckerSidebar(async _args => {
@ -228,3 +234,93 @@ add_task(
await SpecialPowers.popPrefEnv(); await SpecialPowers.popPrefEnv();
} }
); );
add_task(async function test_keep_closed_message_not_visible_non_pdp() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.shopping.experience2023.newPositionCard.hasSeen", true],
["browser.shopping.experience2023.showKeepSidebarClosedMessage", true],
// Set to minimum closed counts met, to speed up testing
["browser.shopping.experience2023.sidebarClosedCount", 4],
],
});
await BrowserTestUtils.withNewTab(PRODUCT_TEST_URL, async _browser => {
await SidebarController.show("viewReviewCheckerSidebar");
info("Waiting for sidebar to update.");
await reviewCheckerSidebarUpdated(PRODUCT_TEST_URL);
await TestUtils.waitForTick();
Assert.ok(SidebarController.isOpen, "Sidebar is open now");
await withReviewCheckerSidebar(async _args => {
let shoppingContainer = await ContentTaskUtils.waitForCondition(
() =>
content.document.querySelector("shopping-container")?.wrappedJSObject,
"Review Checker is loaded."
);
await shoppingContainer.updateComplete;
info("Shopping container update complete");
let keepClosedPromise = ContentTaskUtils.waitForCondition(
() => shoppingContainer.keepClosedMessageBarEl,
"Keep closed message is visible."
);
shoppingContainer.closeButtonEl.click();
await keepClosedPromise;
});
let nonPDPTab = BrowserTestUtils.addTab(gBrowser, NON_PDP_PAGE);
let nonPDPBrowser = nonPDPTab.linkedBrowser;
let browserLoadedPromise = BrowserTestUtils.browserLoaded(
nonPDPBrowser,
false,
NON_PDP_PAGE
);
await browserLoadedPromise;
info("Switching tabs now");
await BrowserTestUtils.switchTab(gBrowser, nonPDPTab);
Assert.ok(true, "Browser is loaded");
await SidebarController.show("viewReviewCheckerSidebar");
await withReviewCheckerSidebar(async _args => {
let shoppingContainer = await ContentTaskUtils.waitForCondition(
() =>
content.document.querySelector("shopping-container")?.wrappedJSObject,
"Review Checker is loaded."
);
await shoppingContainer.updateComplete;
Assert.ok(
!shoppingContainer.keepClosedMessageBarEl,
"'Keep closed' message is not visible before close button click"
);
shoppingContainer.closeButtonEl.click();
let showKeepSidebarClosedMessage = Services.prefs.getBoolPref(
"browser.shopping.experience2023.showKeepSidebarClosedMessage"
);
Assert.ok(
showKeepSidebarClosedMessage,
"browser.shopping.experience2023.showKeepSidebarClosedMessage is true"
);
});
Assert.ok(
!SidebarController.isOpen,
"'Keep closed' message did not prevent sidebar from closing"
);
await BrowserTestUtils.removeTab(nonPDPTab);
});
SidebarController.hide();
await SpecialPowers.popPrefEnv();
});

View file

@ -9,6 +9,10 @@ const BACKUP_STATE_PREF = "sidebar.backupState";
const VISIBILITY_SETTING_PREF = "sidebar.visibility"; const VISIBILITY_SETTING_PREF = "sidebar.visibility";
const SIDEBAR_TOOLS = "sidebar.main.tools"; const SIDEBAR_TOOLS = "sidebar.main.tools";
// New panels that are ready to be introduced to new sidebar users should be added to this list;
// ensure your feature flag is enabled at the same time you do this and that its the same value as
// what you added to .
const DEFAULT_LAUNCHER_TOOLS = "aichat,syncedtabs,history,bookmarks";
const lazy = {}; const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs", ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
@ -40,7 +44,15 @@ XPCOMUtils.defineLazyPreferenceGetter(
() => SidebarManager.updateDefaultTools() () => SidebarManager.updateDefaultTools()
); );
XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarTools", SIDEBAR_TOOLS); XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarTools", SIDEBAR_TOOLS, "");
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"newSidebarHasBeenUsed",
"sidebar.new-sidebar.has-used",
false,
() => SidebarManager.updateDefaultTools()
);
export const SidebarManager = { export const SidebarManager = {
/** /**
@ -84,7 +96,7 @@ export const SidebarManager = {
} }
}; };
setPref("nimbus", slug); setPref("nimbus", slug);
["main.tools", "revamp", "verticalTabs", "visibility"].forEach(pref => ["revamp", "verticalTabs", "visibility"].forEach(pref =>
setPref(pref, lazy.NimbusFeatures[featureId].getVariable(pref)) setPref(pref, lazy.NimbusFeatures[featureId].getVariable(pref))
); );
}); });
@ -119,16 +131,20 @@ export const SidebarManager = {
}, },
/** /**
* Appends any new tools defined on the sidebar.newTool.migration pref branch * Prepopulates default tools for new sidebar users and appends any new tools defined
* to the sidebar.main.tools pref one time as a way of introducing a new tool * on the sidebar.newTool.migration pref branch to the sidebar.main.tools pref.
* to the launcher without overwriting what a user had previously customized.
*/ */
updateDefaultTools() { updateDefaultTools() {
if (!lazy.sidebarRevampEnabled) { if (!lazy.sidebarRevampEnabled) {
return; return;
} }
let tools = lazy.sidebarTools; let tools = lazy.sidebarTools;
// For new sidebar.revamp users, we pre-populate a set of default tools to show in the launcher.
if (!tools && !lazy.newSidebarHasBeenUsed) {
tools = DEFAULT_LAUNCHER_TOOLS;
}
for (const pref of Services.prefs.getChildList( for (const pref of Services.prefs.getChildList(
"sidebar.newTool.migration." "sidebar.newTool.migration."
)) { )) {
@ -136,8 +152,7 @@ export const SidebarManager = {
let options = JSON.parse(Services.prefs.getStringPref(pref)); let options = JSON.parse(Services.prefs.getStringPref(pref));
let newTool = pref.split(".")[3]; let newTool = pref.split(".")[3];
// ensure we only add this tool once if (options?.alreadyShown) {
if (options?.alreadyShown || lazy.sidebarTools.includes(newTool)) {
continue; continue;
} }
@ -155,7 +170,10 @@ export const SidebarManager = {
continue; continue;
} }
} }
tools = tools + "," + newTool; // avoid adding a tool from the pref branch where it's already been added to the DEFAULT_LAUNCHER_TOOLS (for new users)
if (!tools.includes(newTool)) {
tools += "," + newTool;
}
options.alreadyShown = true; options.alreadyShown = true;
Services.prefs.setStringPref(pref, JSON.stringify(options)); Services.prefs.setStringPref(pref, JSON.stringify(options));
} catch (ex) { } catch (ex) {

View file

@ -15,12 +15,12 @@ const { DeferredTask } = ChromeUtils.importESModule(
"resource://gre/modules/DeferredTask.sys.mjs" "resource://gre/modules/DeferredTask.sys.mjs"
); );
const defaultTools = { const toolsNameMap = {
viewGenaiChatSidebar: "aichat", viewGenaiChatSidebar: "aichat",
viewReviewCheckerSidebar: "reviewchecker",
viewTabsSidebar: "syncedtabs", viewTabsSidebar: "syncedtabs",
viewHistorySidebar: "history", viewHistorySidebar: "history",
viewBookmarksSidebar: "bookmarks", viewBookmarksSidebar: "bookmarks",
viewReviewCheckerSidebar: "reviewchecker",
viewCPMSidebar: "passwords", viewCPMSidebar: "passwords",
}; };
const EXPAND_ON_HOVER_DEBOUNCE_RATE_MS = 200; const EXPAND_ON_HOVER_DEBOUNCE_RATE_MS = 200;
@ -203,6 +203,7 @@ var SidebarController = {
revampL10nId: "sidebar-menu-customize-label", revampL10nId: "sidebar-menu-customize-label",
iconUrl: "chrome://global/skin/icons/settings.svg", iconUrl: "chrome://global/skin/icons/settings.svg",
gleanEvent: Glean.sidebarCustomize.panelToggle, gleanEvent: Glean.sidebarCustomize.panelToggle,
visible: false,
}); });
return this._sidebars; return this._sidebars;
@ -1316,7 +1317,7 @@ var SidebarController = {
let changed = false; let changed = false;
const tools = new Set(this.sidebarRevampTools.split(",")); const tools = new Set(this.sidebarRevampTools.split(","));
this.toolsAndExtensions.forEach((tool, commandID) => { this.toolsAndExtensions.forEach((tool, commandID) => {
const toolID = defaultTools[commandID]; const toolID = toolsNameMap[commandID];
if (toolID) { if (toolID) {
const expected = !tools.has(toolID); const expected = !tools.has(toolID);
if (tool.disabled != expected) { if (tool.disabled != expected) {
@ -1346,13 +1347,13 @@ var SidebarController = {
// Tools are persisted via a pref. // Tools are persisted via a pref.
if (!Object.hasOwn(toggledTool, "extensionId")) { if (!Object.hasOwn(toggledTool, "extensionId")) {
const tools = new Set(this.sidebarRevampTools.split(",")); const tools = new Set(this.sidebarRevampTools.split(","));
const updatedTools = tools.has(defaultTools[commandID]) const updatedTools = tools.has(toolsNameMap[commandID])
? Array.from(tools).filter( ? Array.from(tools).filter(
tool => !!tool && tool != defaultTools[commandID] tool => !!tool && tool != toolsNameMap[commandID]
) )
: [ : [
...Array.from(tools).filter(tool => !!tool), ...Array.from(tools).filter(tool => !!tool),
defaultTools[commandID], toolsNameMap[commandID],
]; ];
Services.prefs.setStringPref(this.TOOLS_PREF, updatedTools.join()); Services.prefs.setStringPref(this.TOOLS_PREF, updatedTools.join());
} }
@ -1532,13 +1533,13 @@ var SidebarController = {
* @returns {Array} * @returns {Array}
*/ */
getTools() { getTools() {
return Object.keys(defaultTools) return Object.keys(toolsNameMap)
.filter(commandID => this.sidebars.get(commandID)) .filter(commandID => this.sidebars.get(commandID))
.map(commandID => { .map(commandID => {
const sidebar = this.sidebars.get(commandID); const sidebar = this.sidebars.get(commandID);
const disabled = !this.sidebarRevampTools const disabled = !this.sidebarRevampTools
.split(",") .split(",")
.includes(defaultTools[commandID]); .includes(toolsNameMap[commandID]);
return { return {
commandID, commandID,
view: commandID, view: commandID,
@ -2117,7 +2118,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
SidebarController, SidebarController,
"sidebarRevampTools", "sidebarRevampTools",
"sidebar.main.tools", "sidebar.main.tools",
"aichat,syncedtabs,history", "",
() => { () => {
if ( if (
!SidebarController.inSingleTabWindow && !SidebarController.inSingleTabWindow &&

View file

@ -9,12 +9,14 @@ The new sidebar builds on existing legacy sidebar code treating ``browser-sideba
Introducing a new panel Introducing a new panel
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
Every panel that is registered and enabled in ``browser-sidebar.js``` and the ```defaultTools``` map will show as an option in the Customize Sidebar menu (which is a sidebar panel that contains settings). Every panel that is registered and enabled in ``browser-sidebar.js``` and the ```toolsNameMap``` map will show as an option in the Customize Sidebar menu (which is a sidebar panel that contains settings).
The launcher is a container for tools (ie, icons that when clicked open or close the associated panel). Registering a panel - which should be behind a pref until it is ready to be introduced - does not automatically add a new icon to the launcher. The launcher is a container for tools (ie, icons that when clicked open or close the associated panel). Registering a panel - which should be behind a pref until it is ready to be introduced - does not automatically add a new icon to the launcher.
A tool can be added once for all users by adding it to the designated pref branch ``sidebar.newTool.migration.`` in ``profile/firefox.js``. So an example would be ``pref("sidebar.newTool.migration.bookmarks", '{}')``. The pref suffix (``bookmarks`` in this example) is the ``toolID`` that should match what you added as the value portion of the relevant entry in the ``defaultTools`` map in ``browser-sidebar.js``. It's important to note that if you have a pref governing the visibility of your sidebar panel, it will need to be enabled at the same time in order to be shown in a user's launcher - either via a nimbus rollout or in-tree. A tool can be added once for all users by adding it to the designated pref branch ``sidebar.newTool.migration.`` in ``profile/firefox.js``. So an example would be ``pref("sidebar.newTool.migration.bookmarks", '{}')``. The pref suffix (``bookmarks`` in this example) is the ``toolID`` that should match what you added as the value portion of the relevant entry in the ``toolsNameMap`` map in ``browser-sidebar.js``. It's important to note that if you have a pref governing the visibility of your sidebar panel, it will need to be enabled at the same time in order to be shown in a user's launcher - either via a nimbus rollout or in-tree.
If you only want to add this item if the pref governing visibility is true, you can pass the pref you want to observe, e.g. ``pref("sidebar.newTool.migration.reviewchecker", '{ "visibilityPref": "browser.shopping.experience2023.integratedSidebar"}')`` where ``browser.shopping.experience2023.integratedSidebar`` is the pref controlling the visibility of the review checker panel. If you only want to add this item if the pref governing visibility is true, you can pass the pref you want to observe, e.g. ``pref("sidebar.newTool.migration.reviewchecker", '{ "visibilityPref": "browser.shopping.experience2023.integratedSidebar"}')`` where ``browser.shopping.experience2023.integratedSidebar`` is the pref controlling the visibility of the review checker panel.
In both cases, the tool will be introduced to the launcher one time (appended to a user's customized list of tools) and any customization after that (ie, removing it) takes precedence. If it's not removed, it will persist after that session. In both cases, the tool will be introduced to the launcher one time (appended to a user's customized list of tools) and any customization after that (ie, removing it) takes precedence. If it's not removed, it will persist after that session.
If you only want to introduce a tool to new users, you can do so by adding it to the ``DEFAULT_LAUNCHER_TOOLS`` list in ``SidebarManager`` and the ``toolsNameMap``. You can do this even if you have previously introduced a tool via a pref branch migration as there is logic that will prevent a tool from being added twice, however the expectation is that when adding it to ``defaultTools`` the pref governing panel visibility is also enabled in-tree.

View file

@ -23,12 +23,6 @@ skip-if = [
["browser_extensions_sidebar.js"] ["browser_extensions_sidebar.js"]
["browser_glean_sidebar.js"] ["browser_glean_sidebar.js"]
skip-if = [
"os == 'linux' && os_version == '18.04' && processor == 'x86_64'", # Bug 1919183
"os == 'mac' && os_version == '10.15' && processor == 'x86_64'", # Bug 1919183
"os == 'mac' && os_version == '11.20' && arch == 'aarch64' && opt", # Bug 1919183
"os == 'win' && os_version == '11.26100'", # Bug 1919183
]
["browser_hide_sidebar_on_popup.js"] ["browser_hide_sidebar_on_popup.js"]
@ -66,15 +60,6 @@ run-if = ["os == 'mac'"] # Mac only feature
["browser_syncedtabs_sidebar.js"] ["browser_syncedtabs_sidebar.js"]
["browser_toolbar_sidebar_button.js"] ["browser_toolbar_sidebar_button.js"]
skip-if = [
"os == 'linux' && os_version == '18.04' && processor == 'x86_64' && asan && swgl", # Bug 1898739
"os == 'linux' && os_version == '18.04' && processor == 'x86_64' && opt", # Bug 1898739
"os == 'linux' && os_version == '18.04' && processor == 'x86_64' && debug && swgl", # Bug 1898739
"os == 'linux' && os_version == '18.04' && processor == 'x86_64' && debug && socketprocess_networking", # Bug 1898739
"os == 'mac' && os_version == '10.15' && processor == 'x86_64' && opt", # Bug 1898739
"os == 'mac' && os_version == '11.20' && arch == 'aarch64' && opt", # Bug 1898739
"os == 'win' && os_version == '11.26100' && opt", # Bug 1898739
]
["browser_tools_migration.js"] ["browser_tools_migration.js"]

View file

@ -66,13 +66,7 @@ add_task(async function test_customize_sidebar_actions() {
4, 4,
"Four default tools are shown in the customize menu" "Four default tools are shown in the customize menu"
); );
let bookmarksInput = Array.from(customizeComponent.toolInputs).find(
input => input.name === "viewBookmarksSidebar"
);
ok(
!bookmarksInput.checked,
"The bookmarks input is unchecked initally as Bookmarks are disabled initially."
);
for (const toolInput of customizeComponent.toolInputs) { for (const toolInput of customizeComponent.toolInputs) {
let toolDisabledInitialState = !toolInput.checked; let toolDisabledInitialState = !toolInput.checked;
toolInput.click(); toolInput.click();

View file

@ -361,8 +361,7 @@ add_task(async function test_customize_history_enabled() {
add_task(async function test_customize_bookmarks_enabled() { add_task(async function test_customize_bookmarks_enabled() {
await testCustomizeToggle( await testCustomizeToggle(
"viewBookmarksSidebar", "viewBookmarksSidebar",
Glean.sidebarCustomize.bookmarksEnabled, Glean.sidebarCustomize.bookmarksEnabled
false
); );
}); });
@ -589,7 +588,6 @@ async function testIconClick(expanded) {
await SpecialPowers.pushPrefEnv({ await SpecialPowers.pushPrefEnv({
set: [ set: [
["browser.ml.chat.enabled", true], ["browser.ml.chat.enabled", true],
["sidebar.main.tools", "aichat,syncedtabs,history,bookmarks"],
[TAB_DIRECTION_PREF, true], [TAB_DIRECTION_PREF, true],
], ],
}); });

View file

@ -9,11 +9,9 @@ const { ExperimentFakes } = ChromeUtils.importESModule(
* Check that enrolling into sidebar experiments sets user prefs * Check that enrolling into sidebar experiments sets user prefs
*/ */
add_task(async function test_nimbus_user_prefs() { add_task(async function test_nimbus_user_prefs() {
const main = "sidebar.main.tools";
const nimbus = "sidebar.nimbus"; const nimbus = "sidebar.nimbus";
const vertical = "sidebar.verticalTabs"; const vertical = "sidebar.verticalTabs";
Assert.ok(!Services.prefs.prefHasUserValue(main), "No user main pref yet");
Assert.ok( Assert.ok(
!Services.prefs.prefHasUserValue(nimbus), !Services.prefs.prefHasUserValue(nimbus),
"No user nimbus pref yet" "No user nimbus pref yet"
@ -22,55 +20,13 @@ add_task(async function test_nimbus_user_prefs() {
let cleanup = await ExperimentFakes.enrollWithFeatureConfig({ let cleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "sidebar", featureId: "sidebar",
value: { value: {
"main.tools": "bar",
},
});
Assert.equal(
Services.prefs.getStringPref(main),
"bar",
"Set user pref with experiment"
);
Assert.ok(Services.prefs.prefHasUserValue(main), "main pref has user value");
const nimbusValue = Services.prefs.getStringPref(nimbus);
Assert.ok(nimbusValue, "Set some nimbus slug");
Assert.ok(
Services.prefs.prefHasUserValue(nimbus),
"nimbus pref has user value"
);
cleanup();
Assert.equal(
Services.prefs.getStringPref(main),
"bar",
"main pref still set"
);
Assert.equal(
Services.prefs.getStringPref(nimbus),
nimbusValue,
"nimbus pref still set"
);
Assert.ok(!Services.prefs.getBoolPref(vertical), "vertical is default value");
Assert.ok(
!Services.prefs.prefHasUserValue(vertical),
"vertical used default value"
);
cleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "sidebar",
value: {
"main.tools": "aichat,syncedtabs,history",
verticalTabs: true, verticalTabs: true,
}, },
}); });
Assert.ok(!Services.prefs.prefHasUserValue(main), "main pref no longer set"); const nimbusValue = Services.prefs.getStringPref(nimbus);
Assert.notEqual(
Services.prefs.getStringPref(nimbus), Assert.ok(nimbusValue, "Set some nimbus slug");
nimbusValue,
"nimbus pref changed"
);
Assert.ok(Services.prefs.getBoolPref(vertical), "vertical set to true"); Assert.ok(Services.prefs.getBoolPref(vertical), "vertical set to true");
Assert.ok( Assert.ok(
Services.prefs.prefHasUserValue(vertical), Services.prefs.prefHasUserValue(vertical),
@ -123,13 +79,11 @@ add_task(async function test_nimbus_rollout_experiment() {
}); });
/** /**
* Check that multi-feature sidebar and chatbot sets prefs * Check that multi-feature chatbot sets prefs
*/ */
add_task(async function test_nimbus_multi_feature() { add_task(async function test_nimbus_multi_feature() {
const chatbot = "browser.ml.chat.test"; const chatbot = "browser.ml.chat.test";
const sidebar = "sidebar.main.tools";
Assert.ok(!Services.prefs.prefHasUserValue(chatbot), "chatbot is default"); Assert.ok(!Services.prefs.prefHasUserValue(chatbot), "chatbot is default");
Assert.ok(!Services.prefs.prefHasUserValue(sidebar), "sidebar is default");
const cleanup = await ExperimentFakes.enrollmentHelper( const cleanup = await ExperimentFakes.enrollmentHelper(
ExperimentFakes.recipe("foo", { ExperimentFakes.recipe("foo", {
@ -137,10 +91,6 @@ add_task(async function test_nimbus_multi_feature() {
{ {
slug: "variant", slug: "variant",
features: [ features: [
{
featureId: "sidebar",
value: { "main.tools": "syncedtabs,history" },
},
{ {
featureId: "chatbot", featureId: "chatbot",
value: { prefs: { test: { value: true } } }, value: { prefs: { test: { value: true } } },
@ -152,15 +102,12 @@ add_task(async function test_nimbus_multi_feature() {
); );
Assert.ok(Services.prefs.prefHasUserValue(chatbot), "chatbot user pref set"); Assert.ok(Services.prefs.prefHasUserValue(chatbot), "chatbot user pref set");
Assert.ok(Services.prefs.prefHasUserValue(sidebar), "sidebar user pref set");
cleanup(); cleanup();
Assert.ok(Services.prefs.prefHasUserValue(chatbot), "chatbot pref still set"); Assert.ok(Services.prefs.prefHasUserValue(chatbot), "chatbot pref still set");
Assert.ok(Services.prefs.prefHasUserValue(sidebar), "sidebar pref still set");
Services.prefs.clearUserPref(chatbot); Services.prefs.clearUserPref(chatbot);
Services.prefs.clearUserPref(sidebar);
Services.prefs.clearUserPref("browser.ml.chat.nimbus"); Services.prefs.clearUserPref("browser.ml.chat.nimbus");
Services.prefs.clearUserPref("sidebar.nimbus"); Services.prefs.clearUserPref("sidebar.nimbus");
}); });

View file

@ -7,12 +7,11 @@ add_task(async function test_tools_prefs() {
const win = await BrowserTestUtils.openNewBrowserWindow(); const win = await BrowserTestUtils.openNewBrowserWindow();
const { document } = win; const { document } = win;
const sidebar = document.querySelector("sidebar-main"); const sidebar = document.querySelector("sidebar-main");
ok(sidebar, "Sidebar is shown.");
await sidebar.updateComplete; await sidebar.updateComplete;
is( is(
Services.prefs.getStringPref("sidebar.main.tools"), Services.prefs.getStringPref("sidebar.main.tools"),
"aichat,syncedtabs,history", "aichat,syncedtabs,history,bookmarks",
"Default tools pref unchanged" "Default tools pref unchanged"
); );
@ -35,11 +34,14 @@ add_task(async function test_tools_prefs() {
input => input.name === "viewBookmarksSidebar" input => input.name === "viewBookmarksSidebar"
); );
ok( ok(
!bookmarksInput.checked, bookmarksInput.checked,
"The bookmarks input is unchecked initially as Bookmarks are disabled initially." "The bookmarks input is checked initially as Bookmarks is a default tool."
); );
for (const toolInput of customizeComponent.toolInputs) { for (const toolInput of customizeComponent.toolInputs) {
let toolDisabledInitialState = !toolInput.checked; let toolDisabledInitialState = !toolInput.checked;
if (toolInput.name == "viewBookmarksSidebar") {
continue;
}
toolInput.click(); toolInput.click();
await BrowserTestUtils.waitForCondition( await BrowserTestUtils.waitForCondition(
() => { () => {
@ -67,7 +69,7 @@ add_task(async function test_tools_prefs() {
is( is(
updatedTools, updatedTools,
"bookmarks", "bookmarks",
"History, aichat and syncedtabs have been removed from the pref, and bookmarks added" "All tools have been removed from the launcher except bookmarks"
); );
await BrowserTestUtils.closeWindow(win); await BrowserTestUtils.closeWindow(win);
@ -143,11 +145,13 @@ add_task(async function test_tool_pref_change() {
}); });
is(sidebar.toolButtons.length, origCount - 1, "Removed tool"); is(sidebar.toolButtons.length, origCount - 1, "Removed tool");
await SpecialPowers.pushPrefEnv({ set: [["sidebar.main.tools", origTools]] }); await SpecialPowers.pushPrefEnv({
set: [["sidebar.main.tools", origTools]],
});
is(sidebar.toolButtons.length, origCount, "Restored tool"); is(sidebar.toolButtons.length, origCount, "Restored tool");
await SpecialPowers.pushPrefEnv({ clear: [["sidebar.main.tools"]] }); await SpecialPowers.pushPrefEnv({ clear: [["sidebar.main.tools"]] });
is(sidebar.toolButtons.length, 3, "Restored default tools"); is(sidebar.toolButtons.length, 0, "Cleared default tools");
}); });
/** /**

View file

@ -6,7 +6,7 @@
add_setup(async () => { add_setup(async () => {
await SpecialPowers.pushPrefEnv({ await SpecialPowers.pushPrefEnv({
set: [ set: [
["sidebar.main.tools", "syncedtabs,history"], ["sidebar.main.tools", "syncedtabs,bookmarks,history"],
["sidebar.newTool.migration.bookmarks", "{}"], ["sidebar.newTool.migration.bookmarks", "{}"],
["browser.ml.chat.enabled", false], ["browser.ml.chat.enabled", false],
[ [
@ -19,7 +19,29 @@ add_setup(async () => {
}); });
}); });
add_task(async function test_duplicate_tool() {
const sidebar = document.querySelector("sidebar-main");
let tools = Services.prefs.getStringPref("sidebar.main.tools").split(",");
is(tools.length, 3, "Three tools are in the sidebar.main.tools pref");
is(
tools.filter(tool => tool == "bookmarks").length,
1,
"Bookmarks has only been added once"
);
is(
sidebar.toolButtons.length,
3,
"Three default tools are visible in the launcher"
);
});
add_task(async function test_one_time_tool_migration() { add_task(async function test_one_time_tool_migration() {
await SpecialPowers.pushPrefEnv({
set: [
["sidebar.main.tools", "syncedtabs,history"],
["sidebar.newTool.migration.bookmarks", "{}"],
],
});
const sidebar = document.querySelector("sidebar-main"); const sidebar = document.querySelector("sidebar-main");
let tools = Services.prefs.getStringPref("sidebar.main.tools"); let tools = Services.prefs.getStringPref("sidebar.main.tools");
is( is(

View file

@ -19,13 +19,11 @@ const kPrefCustomizationHorizontalTabstrip =
"browser.uiCustomization.horizontalTabstrip"; "browser.uiCustomization.horizontalTabstrip";
const kPrefCustomizationNavBarWhenVerticalTabs = const kPrefCustomizationNavBarWhenVerticalTabs =
"browser.uiCustomization.navBarWhenVerticalTabs"; "browser.uiCustomization.navBarWhenVerticalTabs";
const kPrefSidebarTools = "sidebar.main.tools";
const MODIFIED_PREFS = Object.freeze([ const MODIFIED_PREFS = Object.freeze([
kPrefCustomizationState, kPrefCustomizationState,
kPrefCustomizationHorizontalTabstrip, kPrefCustomizationHorizontalTabstrip,
kPrefCustomizationNavBarWhenVerticalTabs, kPrefCustomizationNavBarWhenVerticalTabs,
kPrefSidebarTools,
]); ]);
// Ensure we clear any previous pref values // Ensure we clear any previous pref values

View file

@ -678,7 +678,7 @@ export class SmartTabGroupingManager {
const UPDATE_THRESHOLD_PERCENTAGE = 0.5; const UPDATE_THRESHOLD_PERCENTAGE = 0.5;
const ONE_MB = 1024 * 1024; const ONE_MB = 1024 * 1024;
const START_THRESHOLD_BYTES = ONE_MB; const START_THRESHOLD_BYTES = ONE_MB * 0.2;
const mutliProgressAggregator = new lazy.MultiProgressAggregator({ const mutliProgressAggregator = new lazy.MultiProgressAggregator({
progressCallback: ({ progress, totalLoaded, metadata }) => { progressCallback: ({ progress, totalLoaded, metadata }) => {

View file

@ -5967,6 +5967,16 @@
} }
} }
/**
* Bug 1955388 - prevent pinned tabs from commingling with non-pinned tabs
* when there are hidden tabs present
*/
if (tab.pinned && !targetElement?.pinned) {
// prevent pinned tab from being dragged past a non-pinned tab
targetElement = this.tabs[this.pinnedTabCount - 1];
moveBefore = false;
}
let getContainer = () => { let getContainer = () => {
if (tab.pinned && this.tabContainer.verticalMode) { if (tab.pinned && this.tabContainer.verticalMode) {
return this.tabContainer.verticalPinnedTabsContainer; return this.tabContainer.verticalPinnedTabsContainer;

View file

@ -1017,8 +1017,29 @@
} }
#setMovingTabMode(movingTab) { #setMovingTabMode(movingTab) {
if (movingTab == this.#isMovingTab()) {
return;
}
this.toggleAttribute("movingtab", movingTab); this.toggleAttribute("movingtab", movingTab);
gNavToolbox.toggleAttribute("movingtab", movingTab); gNavToolbox.toggleAttribute("movingtab", movingTab);
if (movingTab) {
// This is a bit of an escape hatch in case a tab drag & drop session
// wasn't ended properly, leaving behind the movingtab attribute, which
// may break the UI (bug 1954163). We don't get mousemove events while
// dragging tabs, so at that point it should be safe to assume that we
// should not be in drag and drop mode, and clean things up if needed.
requestAnimationFrame(() => {
this.addEventListener(
"mousemove",
() => {
this.finishAnimateTabMove();
},
{ once: true }
);
});
}
} }
#isMovingTab() { #isMovingTab() {
@ -1137,7 +1158,10 @@
} }
} }
let shouldTranslate = !gReduceMotion && !shouldCreateGroupOnDrop; let shouldTranslate =
!gReduceMotion &&
!shouldCreateGroupOnDrop &&
!isTabGroupLabel(draggedTab);
if (this.#isContainerVerticalPinnedExpanded(draggedTab)) { if (this.#isContainerVerticalPinnedExpanded(draggedTab)) {
shouldTranslate &&= shouldTranslate &&=
(oldTranslateX && oldTranslateX != newTranslateX) || (oldTranslateX && oldTranslateX != newTranslateX) ||
@ -1161,6 +1185,7 @@
} else { } else {
gBrowser.moveTabsAfter(movingTabs, dropElement); gBrowser.moveTabsAfter(movingTabs, dropElement);
} }
this.#expandGroupOnDrop(draggedTab);
}; };
if (shouldTranslate) { if (shouldTranslate) {
@ -1313,7 +1338,6 @@
} }
if (draggedTab) { if (draggedTab) {
this.#expandGroupOnDrop(draggedTab);
delete draggedTab._dragData; delete draggedTab._dragData;
} }
} }
@ -1336,17 +1360,13 @@
if ( if (
dt.mozUserCancelled || dt.mozUserCancelled ||
dt.dropEffect != "none" || dt.dropEffect != "none" ||
!Services.prefs.getBoolPref("browser.tabs.allowTabDetach") ||
this._isCustomizing this._isCustomizing
) { ) {
delete draggedTab._dragData; delete draggedTab._dragData;
return; return;
} }
// Check if tab detaching is enabled
if (!Services.prefs.getBoolPref("browser.tabs.allowTabDetach")) {
return;
}
// Disable detach within the browser toolbox // Disable detach within the browser toolbox
let [tabAxisPos, tabAxisStart, tabAxisEnd] = this.verticalMode let [tabAxisPos, tabAxisStart, tabAxisEnd] = this.verticalMode
? [event.screenY, window.screenY, window.screenY + window.outerHeight] ? [event.screenY, window.screenY, window.screenY + window.outerHeight]

View file

@ -61,7 +61,14 @@ let currentTab = () =>
lazy.BrowserWindowTracker.getTopWindow()?.gBrowser.selectedTab; lazy.BrowserWindowTracker.getTopWindow()?.gBrowser.selectedTab;
ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () { ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () {
return new Localization(["branding/brand.ftl", "browser/browser.ftl"], true); return new Localization(
[
"branding/brand.ftl",
"browser/browser.ftl",
"toolkit/branding/brandings.ftl",
],
true
);
}); });
const DEFAULT_ACTIONS = { const DEFAULT_ACTIONS = {
@ -83,10 +90,10 @@ const DEFAULT_ACTIONS = {
}, },
clear: { clear: {
l10nCommands: [ l10nCommands: [
"quickactions-cmd-clearhistory", "quickactions-cmd-clearrecenthistory",
"quickactions-clearhistory", "quickactions-clearrecenthistory",
], ],
label: "quickactions-clearhistory", label: "quickactions-clearrecenthistory",
onPick: () => { onPick: () => {
lazy.BrowserWindowTracker.getTopWindow() lazy.BrowserWindowTracker.getTopWindow()
.document.getElementById("Tools:Sanitize") .document.getElementById("Tools:Sanitize")
@ -105,6 +112,22 @@ const DEFAULT_ACTIONS = {
label: "quickactions-extensions", label: "quickactions-extensions",
onPick: openAddonsUrl("addons://list/extension"), onPick: openAddonsUrl("addons://list/extension"),
}, },
help: {
l10nCommands: ["quickactions-cmd-help"],
icon: "chrome://global/skin/icons/help.svg",
label: "quickactions-help",
onPick: openUrlFun(
"https://support.mozilla.org/products/firefox?as=u&utm_source=inproduct"
),
},
firefoxview: {
l10nCommands: ["quickactions-cmd-firefoxview"],
icon: "chrome://browser/skin/firefox-view.svg",
label: "quickactions-firefoxview",
onPick: () => {
lazy.BrowserWindowTracker.getTopWindow().FirefoxViewHandler.openTab();
},
},
inspect: { inspect: {
l10nCommands: ["quickactions-cmd-inspector"], l10nCommands: ["quickactions-cmd-inspector"],
icon: "chrome://devtools/skin/images/open-inspector.svg", icon: "chrome://devtools/skin/images/open-inspector.svg",

View file

@ -125,6 +125,7 @@ add_task(async function test_viewsource() {
"view-source:https://example.com/" "view-source:https://example.com/"
); );
EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Tab", {}, window);
EventUtils.synthesizeKey("KEY_Tab", {}, window);
assertAccessibilityWhenSelected("viewsource"); assertAccessibilityWhenSelected("viewsource");
EventUtils.synthesizeKey("KEY_Enter", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window);
const viewSourceTab = await onLoad; const viewSourceTab = await onLoad;
@ -136,8 +137,10 @@ add_task(async function test_viewsource() {
}); });
Assert.equal( Assert.equal(
hasQuickActions(window), window.document.querySelector(
false, `.urlbarView-action-btn[data-action=viewsource]`
),
null,
"Result for quick actions is hidden" "Result for quick actions is hidden"
); );

View file

@ -16,7 +16,26 @@ add_setup(async function setup() {
}); });
}); });
const LOAD_TYPE = {
CURRENT_TAB: 1,
NEW_TAB: 2,
PRE_LOADED: 3,
};
let COMMANDS_TESTS = [ let COMMANDS_TESTS = [
{
cmd: "open view",
uri: "about:firefoxview",
loadType: LOAD_TYPE.PRE_LOADED,
testFun: async () => {
await BrowserTestUtils.waitForCondition(() => {
return (
window.gBrowser.selectedBrowser.currentURI.spec == "about:firefoxview"
);
});
return true;
},
},
{ {
cmd: "add-ons", cmd: "add-ons",
uri: "about:addons", uri: "about:addons",
@ -52,7 +71,7 @@ let COMMANDS_TESTS = [
await onLoad; await onLoad;
}, },
uri: "about:addons", uri: "about:addons",
isNewTab: true, loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=discover]"), testFun: async () => isSelected("button[name=discover]"),
}, },
{ {
@ -70,7 +89,7 @@ let COMMANDS_TESTS = [
await onLoad; await onLoad;
}, },
uri: "about:addons", uri: "about:addons",
isNewTab: true, loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=plugin]"), testFun: async () => isSelected("button[name=plugin]"),
}, },
{ {
@ -88,7 +107,7 @@ let COMMANDS_TESTS = [
await onLoad; await onLoad;
}, },
uri: "about:addons", uri: "about:addons",
isNewTab: true, loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=extension]"), testFun: async () => isSelected("button[name=extension]"),
}, },
{ {
@ -106,7 +125,7 @@ let COMMANDS_TESTS = [
await onLoad; await onLoad;
}, },
uri: "about:addons", uri: "about:addons",
isNewTab: true, loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=theme]"), testFun: async () => isSelected("button[name=theme]"),
}, },
]; ];
@ -119,7 +138,7 @@ let isSelected = async selector =>
}); });
add_task(async function test_pages() { add_task(async function test_pages() {
for (const { cmd, uri, setup, isNewTab, testFun } of COMMANDS_TESTS) { for (const { cmd, uri, setup, loadType, testFun } of COMMANDS_TESTS) {
info(`Testing ${cmd} command is triggered`); info(`Testing ${cmd} command is triggered`);
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
@ -128,9 +147,10 @@ add_task(async function test_pages() {
await setup(); await setup();
} }
let onLoad = isNewTab let onLoad =
? BrowserTestUtils.waitForNewTab(gBrowser, uri, true) loadType == LOAD_TYPE.NEW_TAB
: BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri); ? BrowserTestUtils.waitForNewTab(gBrowser, uri, true)
: BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
await UrlbarTestUtils.promiseAutocompleteResultPopup({ await UrlbarTestUtils.promiseAutocompleteResultPopup({
window, window,
@ -139,14 +159,15 @@ add_task(async function test_pages() {
EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Tab", {}, window);
EventUtils.synthesizeKey("KEY_Enter", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window);
const newTab = await onLoad; const newTab =
loadType == LOAD_TYPE.PRE_LOADED ? gBrowser.selectedTab : await onLoad;
Assert.ok( Assert.ok(
await testFun(), await testFun(),
`The command "${cmd}" passed completed its test` `The command "${cmd}" passed completed its test`
); );
if (isNewTab) { if ([LOAD_TYPE.NEW_TAB, LOAD_TYPE.PRE_LOADED].includes(loadType)) {
await BrowserTestUtils.removeTab(newTab); await BrowserTestUtils.removeTab(newTab);
} }
await BrowserTestUtils.removeTab(tab); await BrowserTestUtils.removeTab(tab);

View file

@ -131,7 +131,10 @@ const HELP_URL =
add_setup(async function () { add_setup(async function () {
await SpecialPowers.pushPrefEnv({ await SpecialPowers.pushPrefEnv({
set: [["browser.search.suggest.enabled", false]], set: [
["browser.search.suggest.enabled", false],
["browser.urlbar.suggest.quickactions", false],
],
}); });
await QuickSuggestTestUtils.ensureQuickSuggestInit({ await QuickSuggestTestUtils.ensureQuickSuggestInit({

View file

@ -30,6 +30,8 @@ skip-if = [
["browser_autofill_address_housenumber.js"] ["browser_autofill_address_housenumber.js"]
["browser_autofill_address_level.js"]
["browser_autofill_address_select.js"] ["browser_autofill_address_select.js"]
["browser_autofill_address_select_inexact.js"] ["browser_autofill_address_select_inexact.js"]

View file

@ -0,0 +1,171 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_PROFILE_BR = {
"given-name": "Carlos",
"family-name": "Alves",
"street-address": "160 Rua Acores\nApartment 300",
"address-level1": "São Paulo",
"address-level2": "Sampletown",
"address-level3": "Somewhere",
"postal-code": "04829-310",
};
add_autofill_heuristic_tests([
{
description: "Test autofill with address-level3 autocomplete",
fixtureData: `<form>
<input id="name"/>
<input id="address-line1"/>
<input id="address-line2">
<input id="address-level1">
<input id="address-level2">
<input id="postcode">
<input id="extrainfo" autocomplete="address-level3">
</form>`,
profile: TEST_PROFILE_BR,
expectedResult: [
{
default: {
reason: "regex-heuristic",
},
fields: [
{
fieldName: "name",
autofill:
TEST_PROFILE_BR["given-name"] +
" " +
TEST_PROFILE_BR["family-name"],
},
{ fieldName: "address-line1", autofill: "160 Rua Acores" },
{
fieldName: "address-line2",
autofill: "Apartment 300",
reason: "update-heuristic",
},
{
fieldName: "address-level1",
autofill: TEST_PROFILE_BR["address-level1"],
},
{
fieldName: "address-level2",
autofill: TEST_PROFILE_BR["address-level2"],
},
{
fieldName: "postal-code",
autofill: TEST_PROFILE_BR["postal-code"],
},
{
fieldName: "address-level3",
autofill: TEST_PROFILE_BR["address-level3"],
reason: "autocomplete",
},
],
},
],
},
{
description: "Test autofill with address-level3",
fixtureData: `<form>
<input id="name"/>
<input id="address-line1"/>
<input id="address-line2">
<input id="address-level1">
<input id="address-level2">
<input id="address-level3">
<input id="postcode">
</form>`,
profile: TEST_PROFILE_BR,
expectedResult: [
{
default: {
reason: "regex-heuristic",
},
fields: [
{
fieldName: "name",
autofill:
TEST_PROFILE_BR["given-name"] +
" " +
TEST_PROFILE_BR["family-name"],
},
{ fieldName: "address-line1", autofill: "160 Rua Acores" },
{
fieldName: "address-line2",
autofill: "Apartment 300",
reason: "update-heuristic",
},
{
fieldName: "address-level1",
autofill: TEST_PROFILE_BR["address-level1"],
},
{
fieldName: "address-level2",
autofill: TEST_PROFILE_BR["address-level2"],
},
{
fieldName: "address-level3",
autofill: TEST_PROFILE_BR["address-level3"],
},
{
fieldName: "postal-code",
autofill: TEST_PROFILE_BR["postal-code"],
},
],
},
],
},
{
description: "Test autofill with label for neighbourhood",
fixtureData: `<form>
<label>Nome <input id="field1"/></label>
<label>Endereço <input id="field2"/></label>
<label>Apartamento<input id="field3" autocomplete="address-line2"></label>
<label>CEP <input id="field4"></label>
<label>Bairro <input id="field5"></label>
<label>Cidade <input id="field6"></label>
<label>Estado <input id="field7"></label>
</form>`,
profile: TEST_PROFILE_BR,
expectedResult: [
{
default: {
reason: "regex-heuristic",
},
fields: [
{
fieldName: "name",
autofill:
TEST_PROFILE_BR["given-name"] +
" " +
TEST_PROFILE_BR["family-name"],
},
{ fieldName: "address-line1", autofill: "160 Rua Acores" },
{
fieldName: "address-line2",
autofill: "Apartment 300",
reason: "autocomplete",
},
{
fieldName: "postal-code",
autofill: TEST_PROFILE_BR["postal-code"],
},
{
fieldName: "address-level3",
autofill: TEST_PROFILE_BR["address-level3"],
},
{
fieldName: "address-level2",
autofill: TEST_PROFILE_BR["address-level2"],
},
{
fieldName: "address-level1",
autofill: TEST_PROFILE_BR["address-level1"],
},
],
},
],
},
]);

View file

@ -46,6 +46,8 @@ skip-if = ["os == 'mac' && os_version == '11.20' && arch == 'aarch64' && !debug"
["browser_parse_street_address_fields.js"] ["browser_parse_street_address_fields.js"]
["browser_parse_tel_fields.js"]
["browser_section_validation_address.js"] ["browser_section_validation_address.js"]
["browser_sections_by_name.js"] ["browser_sections_by_name.js"]

View file

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* global add_heuristic_tests */
"use strict";
add_heuristic_tests([
{
description:
"Address form with tel-country-code select element (Bug 1951890).",
fixtureData: `
<form>
<input type="text" id="name" autocomplete="name"/>
<input type="text" id="country" autocomplete="country"/>
<input type="text" id="street-address" autocomplete="street-address"/>
<input type="text" id="address-line1" autocomplete="address-line1"/>
<input type="tel" id="tel" autocomplete="tel"/>
<select name="phone_country_select">
</form>`,
expectedResult: [
{
default: {
reason: "autocomplete",
},
fields: [
{ fieldName: "name" },
{ fieldName: "country" },
{ fieldName: "street-address" },
{ fieldName: "address-line1" },
{ fieldName: "tel" },
{ fieldName: "tel-country-code", reason: "regex-heuristic" },
],
},
],
},
]);

View file

@ -61,7 +61,7 @@ add_heuristic_tests(
{ {
invalid: true, invalid: true,
fields: [ fields: [
{ fieldName: "address-level2", reason: "regex-heuristic" }, { fieldName: "postal-code", reason: "regex-heuristic" },
], ],
}, },
{ {
@ -147,7 +147,7 @@ add_heuristic_tests(
{ {
invalid: true, invalid: true,
fields: [ fields: [
{ fieldName: "address-level2", reason: "regex-heuristic" }, { fieldName: "postal-code", reason: "regex-heuristic" },
], ],
}, },
{ {

View file

@ -616,9 +616,6 @@ export class BaseContent extends React.PureComponent {
const enabledSections = { const enabledSections = {
topSitesEnabled: prefs["feeds.topsites"], topSitesEnabled: prefs["feeds.topsites"],
pocketEnabled: prefs["feeds.section.topstories"], pocketEnabled: prefs["feeds.section.topstories"],
highlightsEnabled: prefs["feeds.section.highlights"],
showSponsoredTopSitesEnabled: prefs.showSponsoredTopSites,
showSponsoredPocketEnabled: prefs.showSponsored,
showInferredPersonalizationEnabled: showInferredPersonalizationEnabled:
prefs[PREF_INFERRED_PERSONALIZATION_USER], prefs[PREF_INFERRED_PERSONALIZATION_USER],
showRecentSavesEnabled: prefs.showRecentSaves, showRecentSavesEnabled: prefs.showRecentSaves,

View file

@ -5,7 +5,6 @@
import React from "react"; import React from "react";
import { actionCreators as ac } from "common/Actions.mjs"; import { actionCreators as ac } from "common/Actions.mjs";
import { SectionsMgmtPanel } from "../SectionsMgmtPanel/SectionsMgmtPanel"; import { SectionsMgmtPanel } from "../SectionsMgmtPanel/SectionsMgmtPanel";
import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
import { WallpapersSection } from "../../WallpapersSection/WallpapersSection"; import { WallpapersSection } from "../../WallpapersSection/WallpapersSection";
import { WallpaperCategories } from "../../WallpapersSection/WallpaperCategories"; import { WallpaperCategories } from "../../WallpapersSection/WallpaperCategories";
@ -30,7 +29,7 @@ export class ContentSection extends React.PureComponent {
} }
onPreferenceSelect(e) { onPreferenceSelect(e) {
// eventSource: TOP_SITES | TOP_STORIES | HIGHLIGHTS | WEATHER // eventSource: WEATHER | TOP_SITES | TOP_STORIES
const { preference, eventSource } = e.target.dataset; const { preference, eventSource } = e.target.dataset;
let value; let value;
if (e.target.nodeName === "SELECT") { if (e.target.nodeName === "SELECT") {
@ -86,7 +85,7 @@ export class ContentSection extends React.PureComponent {
if (isOpen) { if (isOpen) {
drawerRef.style.marginTop = "var(--space-large)"; drawerRef.style.marginTop = "var(--space-large)";
} else { } else {
drawerRef.style.marginTop = `-${drawerHeight}px`; drawerRef.style.marginTop = `-${drawerHeight + 3}px`;
} }
} }
} }
@ -94,14 +93,11 @@ export class ContentSection extends React.PureComponent {
render() { render() {
const { const {
enabledSections, enabledSections,
mayHaveSponsoredTopSites,
pocketRegion, pocketRegion,
mayHaveSponsoredStories,
mayHaveInferredPersonalization, mayHaveInferredPersonalization,
mayHaveRecentSaves, mayHaveRecentSaves,
mayHaveWeather, mayHaveWeather,
openPreferences, openPreferences,
spocMessageVariant,
wallpapersEnabled, wallpapersEnabled,
wallpapersV2Enabled, wallpapersV2Enabled,
activeWallpaper, activeWallpaper,
@ -112,10 +108,7 @@ export class ContentSection extends React.PureComponent {
const { const {
topSitesEnabled, topSitesEnabled,
pocketEnabled, pocketEnabled,
highlightsEnabled,
weatherEnabled, weatherEnabled,
showSponsoredTopSitesEnabled,
showSponsoredPocketEnabled,
showInferredPersonalizationEnabled, showInferredPersonalizationEnabled,
showRecentSavesEnabled, showRecentSavesEnabled,
topSitesRowsCount, topSitesRowsCount,
@ -144,6 +137,19 @@ export class ContentSection extends React.PureComponent {
</> </>
)} )}
<div className="settings-toggles"> <div className="settings-toggles">
{mayHaveWeather && (
<div id="weather-section" className="section">
<moz-toggle
id="weather-toggle"
pressed={weatherEnabled || null}
onToggle={this.onPreferenceSelect}
data-preference="showWeather"
data-eventSource="WEATHER"
data-l10n-id="newtab-custom-weather-toggle"
/>
</div>
)}
<div id="shortcuts-section" className="section"> <div id="shortcuts-section" className="section">
<moz-toggle <moz-toggle
id="shortcuts-toggle" id="shortcuts-toggle"
@ -190,25 +196,6 @@ export class ContentSection extends React.PureComponent {
data-l10n-args='{"num": 4}' data-l10n-args='{"num": 4}'
/> />
</select> </select>
{mayHaveSponsoredTopSites && (
<div className="check-wrapper" role="presentation">
<input
id="sponsored-shortcuts"
className="customize-menu-checkbox"
disabled={!topSitesEnabled}
checked={showSponsoredTopSitesEnabled}
type="checkbox"
onChange={this.onPreferenceSelect}
data-preference="showSponsoredTopSites"
data-eventSource="SPONSORED_TOP_SITES"
/>
<label
className="customize-menu-checkbox-label"
htmlFor="sponsored-shortcuts"
data-l10n-id="newtab-custom-sponsored-sites"
/>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@ -227,31 +214,14 @@ export class ContentSection extends React.PureComponent {
data-l10n-id="newtab-custom-stories-toggle" data-l10n-id="newtab-custom-stories-toggle"
> >
<div slot="nested"> <div slot="nested">
{(mayHaveSponsoredStories || mayHaveRecentSaves) && ( {(mayHaveRecentSaves ||
mayHaveInferredPersonalization ||
mayHaveTopicSections) && (
<div className="more-info-pocket-wrapper"> <div className="more-info-pocket-wrapper">
<div <div
className="more-information" className="more-information"
ref={this.pocketDrawerRef} ref={this.pocketDrawerRef}
> >
{mayHaveSponsoredStories && (
<div className="check-wrapper" role="presentation">
<input
id="sponsored-pocket"
className="customize-menu-checkbox"
disabled={!pocketEnabled}
checked={showSponsoredPocketEnabled}
type="checkbox"
onChange={this.onPreferenceSelect}
data-preference="showSponsored"
data-eventSource="POCKET_SPOCS"
/>
<label
className="customize-menu-checkbox-label"
htmlFor="sponsored-pocket"
data-l10n-id="newtab-custom-pocket-sponsored"
/>
</div>
)}
{mayHaveInferredPersonalization && ( {mayHaveInferredPersonalization && (
<div className="check-wrapper" role="presentation"> <div className="check-wrapper" role="presentation">
<input <input
@ -302,47 +272,6 @@ export class ContentSection extends React.PureComponent {
</moz-toggle> </moz-toggle>
</div> </div>
)} )}
<div id="recent-section" className="section">
<moz-toggle
id="highlights-toggle"
pressed={highlightsEnabled || null}
onToggle={this.onPreferenceSelect}
data-preference="feeds.section.highlights"
data-eventSource="HIGHLIGHTS"
data-l10n-id="newtab-custom-recent-toggle"
/>
</div>
{mayHaveWeather && (
<div id="weather-section" className="section">
<moz-toggle
id="weather-toggle"
pressed={weatherEnabled || null}
onToggle={this.onPreferenceSelect}
data-preference="showWeather"
data-eventSource="WEATHER"
data-l10n-id="newtab-custom-weather-toggle"
/>
</div>
)}
{pocketRegion &&
mayHaveSponsoredStories &&
spocMessageVariant === "variant-c" && (
<div className="sponsored-content-info">
<div className="icon icon-help"></div>
<div>
Sponsored content supports our mission to build a better web.{" "}
<SafeAnchor
dispatch={this.props.dispatch}
url="https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
>
Find out how
</SafeAnchor>
</div>
</div>
)}
</div> </div>
<span className="divider" role="separator"></span> <span className="divider" role="separator"></span>

View file

@ -83,14 +83,11 @@ export class _CustomizeMenu extends React.PureComponent {
activeWallpaper={this.props.activeWallpaper} activeWallpaper={this.props.activeWallpaper}
pocketRegion={this.props.pocketRegion} pocketRegion={this.props.pocketRegion}
mayHaveTopicSections={this.props.mayHaveTopicSections} mayHaveTopicSections={this.props.mayHaveTopicSections}
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}
mayHaveInferredPersonalization={ mayHaveInferredPersonalization={
this.props.mayHaveInferredPersonalization this.props.mayHaveInferredPersonalization
} }
mayHaveRecentSaves={this.props.DiscoveryStream.recentSavesEnabled} mayHaveRecentSaves={this.props.DiscoveryStream.recentSavesEnabled}
mayHaveWeather={this.props.mayHaveWeather} mayHaveWeather={this.props.mayHaveWeather}
spocMessageVariant={this.props.spocMessageVariant}
dispatch={this.props.dispatch} dispatch={this.props.dispatch}
exitEventFired={this.state.exitEventFired} exitEventFired={this.state.exitEventFired}
/> />

View file

@ -5,8 +5,21 @@
import React from "react"; import React from "react";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor"; import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats"; import { ImpressionStats } from "../../DiscoveryStreamImpressionStats/ImpressionStats";
import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; import { actionCreators as ac } from "common/Actions.mjs";
import { AdBannerContextMenu } from "../AdBannerContextMenu/AdBannerContextMenu";
/**
* A new banner ad that appears between rows of stories: leaderboard or billboard size.
*
* @param spoc
* @param dispatch
* @param firstVisibleTimestamp
* @param row
* @param type
* @param prefs
* @returns {Element}
* @constructor
*/
export const AdBanner = ({ export const AdBanner = ({
spoc, spoc,
dispatch, dispatch,
@ -39,39 +52,12 @@ export const AdBanner = ({
const { width: imgWidth, height: imgHeight } = getDimensions(spoc.format); const { width: imgWidth, height: imgHeight } = getDimensions(spoc.format);
const handleDismissClick = () => {
dispatch(
ac.AlsoToMain({
type: at.BLOCK_URL,
data: [
{
block_key: spoc.block_key,
fetchTimestamp: spoc.fetchTimestamp,
flight_id: spoc.flight_id,
format: spoc.format,
id: spoc.id,
card_type: "spoc",
is_pocket_card: true,
position: row,
sponsor: spoc.sponsor,
title: spoc.title,
url: spoc.url || spoc.shim.url,
personalization_models: spoc.personalization_models,
priority: spoc.priority,
score: spoc.score,
alt_text: spoc.alt_text,
},
],
})
);
};
const onLinkClick = () => { const onLinkClick = () => {
dispatch( dispatch(
ac.DiscoveryStreamUserEvent({ ac.DiscoveryStreamUserEvent({
event: "CLICK", event: "CLICK",
source: type.toUpperCase(), source: type.toUpperCase(),
// Banner ads dont have a position, but a row number // Banner ads don't have a position, but a row number
action_position: row, action_position: row,
value: { value: {
card_type: "spoc", card_type: "spoc",
@ -98,13 +84,12 @@ export const AdBanner = ({
return ( return (
<aside className="ad-banner-wrapper" style={{ gridRow: clampedRow }}> <aside className="ad-banner-wrapper" style={{ gridRow: clampedRow }}>
<div className={`ad-banner-inner ${spoc.format}`}> <div className={`ad-banner-inner ${spoc.format}`}>
<div className="ad-banner-dismiss"> <AdBannerContextMenu
<button dispatch={dispatch}
className="icon icon-dismiss" spoc={spoc}
onClick={handleDismissClick} position={row}
data-l10n-id="newtab-toast-dismiss-button" type={type}
></button> />
</div>
<SafeAnchor <SafeAnchor
className="ad-banner-link" className="ad-banner-link"
url={spoc.url} url={spoc.url}

View file

@ -33,24 +33,6 @@
.ad-banner-inner { .ad-banner-inner {
margin-inline: auto; margin-inline: auto;
.ad-banner-dismiss {
// Contrast fix for users who have wallpapers set
@include wallpaper-contrast-fix;
margin-block: 0 var(--space-small);
margin-inline: 0 var(--space-xxsmall);
text-align: end;
.icon-dismiss {
background-size: var(--size-item-small);
border: 0;
}
.icon-dismiss:hover {
background-color: var(--newtab-button-hover-background);
}
}
&.leaderboard { &.leaderboard {
max-width: var(--leaderboard-width); max-width: var(--leaderboard-width);
@ -74,7 +56,7 @@
} }
&.billboard { &.billboard {
min-width: var(--billboard-width); width: var(--billboard-width);
.ad-banner-content { .ad-banner-content {
height: var(--billboard-height); height: var(--billboard-height);

View file

@ -0,0 +1,85 @@
/* 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/. */
import React, { useState } from "react";
import { actionCreators as ac } from "common/Actions.mjs";
import { LinkMenu } from "../../LinkMenu/LinkMenu";
/**
* A context menu for IAB banners (e.g. billboard, leaderboard).
*
* Note: MREC ad formats and sponsored stories share the context menu with
* other cards: make sure you also look at DSLinkMenu component
* to keep any updates to ad-related context menu items in sync.
*
* @param dispatch
* @param spoc
* @param position
* @param type
* @returns {Element}
* @constructor
*/
export function AdBannerContextMenu({ dispatch, spoc, position, type }) {
const ADBANNER_CONTEXT_MENU_OPTIONS = [
"BlockAdUrl",
"ManageSponsoredContent",
"OurSponsorsAndYourPrivacy",
];
const [showContextMenu, setShowContextMenu] = useState(false);
const onClick = e => {
e.preventDefault();
setShowContextMenu(!showContextMenu);
};
const onUpdate = () => {
setShowContextMenu(!showContextMenu);
};
return (
<div className="ads-context-menu-wrapper">
<div className="ads-context-menu">
<moz-button
type="icon"
size="default"
iconsrc="chrome://global/skin/icons/more.svg"
onClick={onClick}
/>
{showContextMenu && (
<LinkMenu
onUpdate={onUpdate}
dispatch={dispatch}
options={ADBANNER_CONTEXT_MENU_OPTIONS}
shouldSendImpressionStats={true}
userEvent={ac.DiscoveryStreamUserEvent}
site={{
// Props we want to pass on for new ad types that come from Unified Ads API
block_key: spoc.block_key,
fetchTimestamp: spoc.fetchTimestamp,
flight_id: spoc.flight_id,
format: spoc.format,
id: spoc.id,
guid: spoc.guid,
card_type: "spoc",
// required to record telemetry for an action, see handleBlockUrl in TelemetryFeed.sys.mjs
is_pocket_card: true,
position,
sponsor: spoc.sponsor,
title: spoc.title,
url: spoc.url || spoc.shim.url,
personalization_models: spoc.personalization_models,
priority: spoc.priority,
score: spoc.score,
alt_text: spoc.alt_text,
shim: spoc.shim,
}}
index={position}
source={type.toUpperCase()}
/>
)}
</div>
</div>
);
}

View file

@ -0,0 +1,29 @@
.ads-context-menu-wrapper {
// Contrast fix for users who have wallpapers set
@include wallpaper-contrast-fix;
text-align: end;
}
.ads-context-menu {
float: right;
position: relative;
> moz-button {
padding-block-end: var(--space-small);
}
// Transparent on light backgrounds as specified in Figma designs
// and default moz-button colours on dark backgrounds.
> moz-button::part(button) {
background-color: light-dark(transparent, var(--button-background-color));
}
.context-menu {
width: auto;
/* Position the menu just under and to the right of the context menu button */
top: calc(2.25 * var(--size-item-small));
inset-inline-start: calc(-12.25 * var(--size-item-small));
}
}

View file

@ -11,6 +11,12 @@ import { CSSTransition } from "react-transition-group";
const PREF_WALLPAPER_UPLOADED_PREVIOUSLY = const PREF_WALLPAPER_UPLOADED_PREVIOUSLY =
"newtabWallpapers.customWallpaper.uploadedPreviously"; "newtabWallpapers.customWallpaper.uploadedPreviously";
const PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE =
"newtabWallpapers.customWallpaper.fileSize";
const PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE_ENABLED =
"newtabWallpapers.customWallpaper.fileSize.enabled";
// Returns a function will not be continuously triggered when called. The // Returns a function will not be continuously triggered when called. The
// function will be triggered if called again after `wait` milliseconds. // function will be triggered if called again after `wait` milliseconds.
function debounce(func, wait) { function debounce(func, wait) {
@ -52,6 +58,7 @@ export class _WallpaperCategories extends React.PureComponent {
showColorPicker: false, showColorPicker: false,
inputType: "radio", inputType: "radio",
activeId: null, activeId: null,
isCustomWallpaperError: false,
}; };
} }
@ -121,9 +128,13 @@ export class _WallpaperCategories extends React.PureComponent {
this.props.setPref("newtabWallpapers.wallpaper", id); this.props.setPref("newtabWallpapers.wallpaper", id);
const uploadedPreviously =
this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
this.handleUserEvent(at.WALLPAPER_CLICK, { this.handleUserEvent(at.WALLPAPER_CLICK, {
selected_wallpaper: id, selected_wallpaper: id,
had_previous_wallpaper: !!this.props.activeWallpaper, had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously,
}); });
} }
@ -210,13 +221,14 @@ export class _WallpaperCategories extends React.PureComponent {
} }
handleReset() { handleReset() {
this.props.setPref("newtabWallpapers.wallpaper", "");
const uploadedPreviously = const uploadedPreviously =
this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY]; this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
if (uploadedPreviously) { const selectedWallpaper =
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, false); this.props.Prefs.values["newtabWallpapers.wallpaper"];
// If a custom wallpaper is set, remove it
if (selectedWallpaper === "custom") {
this.props.dispatch( this.props.dispatch(
ac.OnlyToMain({ ac.OnlyToMain({
type: at.WALLPAPER_REMOVE_UPLOAD, type: at.WALLPAPER_REMOVE_UPLOAD,
@ -224,9 +236,14 @@ export class _WallpaperCategories extends React.PureComponent {
); );
} }
// Reset active wallpaper
this.props.setPref("newtabWallpapers.wallpaper", "");
// Fire WALLPAPER_CLICK telemetry event
this.handleUserEvent(at.WALLPAPER_CLICK, { this.handleUserEvent(at.WALLPAPER_CLICK, {
selected_wallpaper: "none", selected_wallpaper: "none",
had_previous_wallpaper: !!this.props.activeWallpaper, had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously,
}); });
} }
@ -255,31 +272,42 @@ export class _WallpaperCategories extends React.PureComponent {
// Custom wallpaper image upload // Custom wallpaper image upload
async handleUpload() { async handleUpload() {
// TODO: Bug 1943663: Add telemetry const wallpaperUploadMaxFileSizeEnabled =
this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE_ENABLED];
// TODO: Bug 1947813: Add image upload error states/UI const wallpaperUploadMaxFileSize =
this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
// TODO: Once Bug 1947813 has landed, we may need a separate event
// for selecting previously uploaded wallpaper, rather than uploading a new one.
// The plan would be to reuse at.WALLPAPER_CLICK for this use case
const uploadedPreviously = const uploadedPreviously =
this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY]; this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
this.handleUserEvent(at.WALLPAPER_UPLOAD, {
had_uploaded_previously: !!uploadedPreviously,
had_previous_wallpaper: !!this.props.activeWallpaper,
});
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, true);
// Create a file input since category buttons are radio inputs // Create a file input since category buttons are radio inputs
const fileInput = document.createElement("input"); const fileInput = document.createElement("input");
fileInput.type = "file"; fileInput.type = "file";
fileInput.accept = "image/*"; // only allow image files fileInput.accept = "image/*"; // only allow image files
// Catch cancel events
fileInput.oncancel = async () => {
this.setState({ isCustomWallpaperError: false });
};
// Reset error state when user begins file selection
this.setState({ isCustomWallpaperError: false });
// Fire when user selects a file
fileInput.onchange = async event => { fileInput.onchange = async event => {
const [file] = event.target.files; const [file] = event.target.files;
// Limit image uploaded to a maximum file size if enabled
// Note: The max file size pref (customWallpaper.fileSize) is converted to megabytes (MB)
// Example: if pref value is 5, max file size is 5 MB
const maxSize = wallpaperUploadMaxFileSize * 1024 * 1024;
if (wallpaperUploadMaxFileSizeEnabled && file && file.size > maxSize) {
console.error("File size exceeds limit");
this.setState({ isCustomWallpaperError: true });
return;
}
if (file) { if (file) {
this.props.dispatch( this.props.dispatch(
ac.OnlyToMain({ ac.OnlyToMain({
@ -287,6 +315,19 @@ export class _WallpaperCategories extends React.PureComponent {
data: file, data: file,
}) })
); );
// Set active wallpaper ID to "custom"
this.props.setPref("newtabWallpapers.wallpaper", "custom");
// Update the uploadedPreviously pref to TRUE
// Note: this pref used for telemetry. Do not reset to false.
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, true);
this.handleUserEvent(at.WALLPAPER_CLICK, {
selected_wallpaper: "custom",
had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously,
});
} }
}; };
@ -332,6 +373,8 @@ export class _WallpaperCategories extends React.PureComponent {
let filteredWallpapers = wallpaperList.filter( let filteredWallpapers = wallpaperList.filter(
wallpaper => wallpaper.category === activeCategory wallpaper => wallpaper.category === activeCategory
); );
const wallpaperUploadMaxFileSize =
this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
function reduceColorsToFitCustomColorInput(arr) { function reduceColorsToFitCustomColorInput(arr) {
// Reduce the amount of custom colors to make space for the custom color picker // Reduce the amount of custom colors to make space for the custom color picker
@ -491,6 +534,9 @@ export class _WallpaperCategories extends React.PureComponent {
: `wallpaper-input theme-custom-wallpaper` : `wallpaper-input theme-custom-wallpaper`
} }
tabIndex={index === 0 ? 0 : -1} tabIndex={index === 0 ? 0 : -1}
{...(category === "custom-wallpaper"
? { "aria-errormessage": "customWallpaperError" }
: {})}
/> />
<label htmlFor={category} data-l10n-id={fluent_id}> <label htmlFor={category} data-l10n-id={fluent_id}>
{fluent_id} {fluent_id}
@ -499,6 +545,15 @@ export class _WallpaperCategories extends React.PureComponent {
); );
})} })}
</fieldset> </fieldset>
{this.state.isCustomWallpaperError && (
<div className="custom-wallpaper-error" id="customWallpaperError">
<span className="icon icon-info"></span>
<span
data-l10n-id="newtab-wallpaper-error-max-file-size"
data-l10n-args={`{"file_size": ${wallpaperUploadMaxFileSize}}`}
></span>
</div>
)}
</div> </div>
<CSSTransition <CSSTransition

View file

@ -287,3 +287,17 @@
} }
} }
// Bug 1947813 - Custom Upload Error Message
.custom-wallpaper-error {
padding-block-start: var(--space-xlarge);
font-size: var(--font-size-small);
display: flex;
gap: var(--space-small);
align-items: center;
.icon {
width: var(--button-size-icon-small);
height: var(--button-size-icon-small);
fill: var(--icon-color-critical);
}
}

View file

@ -151,6 +151,28 @@ export const LinkMenuOptions = {
userEvent: "BLOCK", userEvent: "BLOCK",
}), }),
// This is the "Dismiss" action for leaderboard/billboard ads.
BlockAdUrl: (site, pos, eventSource) => ({
id: "newtab-menu-dismiss",
icon: "dismiss",
action: ac.AlsoToMain({
type: at.BLOCK_URL,
data: [site],
}),
impression: ac.ImpressionStats({
source: eventSource,
block: 0,
tiles: [
{
id: site.guid,
pos,
...(site.shim && site.shim.save ? { shim: site.shim.save } : {}),
},
],
}),
userEvent: "BLOCK",
}),
// This is an option for web extentions which will result in remove items from // This is an option for web extentions which will result in remove items from
// memory and notify the web extenion, rather than using the built-in block list. // memory and notify the web extenion, rather than using the built-in block list.
WebExtDismiss: (site, index, eventSource) => ({ WebExtDismiss: (site, index, eventSource) => ({

View file

@ -194,6 +194,7 @@ input {
@import '../components/DiscoveryStreamComponents/TopicSelection/TopicSelection'; @import '../components/DiscoveryStreamComponents/TopicSelection/TopicSelection';
@import '../components/DiscoveryStreamComponents/ListFeed/ListFeed'; @import '../components/DiscoveryStreamComponents/ListFeed/ListFeed';
@import '../components/DiscoveryStreamComponents/AdBanner/AdBanner'; @import '../components/DiscoveryStreamComponents/AdBanner/AdBanner';
@import '../components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu';
@import '../components/DiscoveryStreamComponents/SectionContextMenu/SectionContextMenu'; @import '../components/DiscoveryStreamComponents/SectionContextMenu/SectionContextMenu';
@import '../components/DiscoveryStreamComponents/InterestPicker/InterestPicker'; @import '../components/DiscoveryStreamComponents/InterestPicker/InterestPicker';

View file

@ -2791,6 +2791,19 @@ main section {
text-decoration: none; text-decoration: none;
} }
.custom-wallpaper-error {
padding-block-start: var(--space-xlarge);
font-size: var(--font-size-small);
display: flex;
gap: var(--space-small);
align-items: center;
}
.custom-wallpaper-error .icon {
width: var(--button-size-icon-small);
height: var(--button-size-icon-small);
fill: var(--icon-color-critical);
}
:root { :root {
--newtab-weather-content-font-size: 11px; --newtab-weather-content-font-size: 11px;
--newtab-weather-sponsor-font-size: 8px; --newtab-weather-sponsor-font-size: 8px;
@ -8024,24 +8037,6 @@ main section {
.ad-banner-wrapper .ad-banner-inner { .ad-banner-wrapper .ad-banner-inner {
margin-inline: auto; margin-inline: auto;
} }
.ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss {
margin-block: 0 var(--space-small);
margin-inline: 0 var(--space-xxsmall);
text-align: end;
}
.lightWallpaper .ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss {
color-scheme: light;
}
.darkWallpaper .ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss {
color-scheme: dark;
}
.ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss .icon-dismiss {
background-size: var(--size-item-small);
border: 0;
}
.ad-banner-wrapper .ad-banner-inner .ad-banner-dismiss .icon-dismiss:hover {
background-color: var(--newtab-button-hover-background);
}
.ad-banner-wrapper .ad-banner-inner.leaderboard { .ad-banner-wrapper .ad-banner-inner.leaderboard {
max-width: var(--leaderboard-width); max-width: var(--leaderboard-width);
} }
@ -8062,7 +8057,7 @@ main section {
} }
} }
.ad-banner-wrapper .ad-banner-inner.billboard { .ad-banner-wrapper .ad-banner-inner.billboard {
min-width: var(--billboard-width); width: var(--billboard-width);
} }
.ad-banner-wrapper .ad-banner-inner.billboard .ad-banner-content { .ad-banner-wrapper .ad-banner-inner.billboard .ad-banner-content {
height: var(--billboard-height); height: var(--billboard-height);
@ -8092,6 +8087,33 @@ main section {
color: var(--newtab-contextual-text-secondary-color); color: var(--newtab-contextual-text-secondary-color);
} }
.ads-context-menu-wrapper {
text-align: end;
}
.lightWallpaper .ads-context-menu-wrapper {
color-scheme: light;
}
.darkWallpaper .ads-context-menu-wrapper {
color-scheme: dark;
}
.ads-context-menu {
float: right;
position: relative;
}
.ads-context-menu > moz-button {
padding-block-end: var(--space-small);
}
.ads-context-menu > moz-button::part(button) {
background-color: light-dark(transparent, var(--button-background-color));
}
.ads-context-menu .context-menu {
width: auto;
/* Position the menu just under and to the right of the context menu button */
top: calc(2.25 * var(--size-item-small));
inset-inline-start: calc(-12.25 * var(--size-item-small));
}
.section-context-menu { .section-context-menu {
position: relative; position: relative;
} }

View file

@ -1828,6 +1828,28 @@ const LinkMenuOptions = {
userEvent: "BLOCK", userEvent: "BLOCK",
}), }),
// This is the "Dismiss" action for leaderboard/billboard ads.
BlockAdUrl: (site, pos, eventSource) => ({
id: "newtab-menu-dismiss",
icon: "dismiss",
action: actionCreators.AlsoToMain({
type: actionTypes.BLOCK_URL,
data: [site],
}),
impression: actionCreators.ImpressionStats({
source: eventSource,
block: 0,
tiles: [
{
id: site.guid,
pos,
...(site.shim && site.shim.save ? { shim: site.shim.save } : {}),
},
],
}),
userEvent: "BLOCK",
}),
// This is an option for web extentions which will result in remove items from // This is an option for web extentions which will result in remove items from
// memory and notify the web extenion, rather than using the built-in block list. // memory and notify the web extenion, rather than using the built-in block list.
WebExtDismiss: (site, index, eventSource) => ({ WebExtDismiss: (site, index, eventSource) => ({
@ -4284,6 +4306,84 @@ function ListFeed({
}, ctaCopy))))); }, ctaCopy)))));
} }
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx
/* 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/. */
/**
* A context menu for IAB banners (e.g. billboard, leaderboard).
*
* Note: MREC ad formats and sponsored stories share the context menu with
* other cards: make sure you also look at DSLinkMenu component
* to keep any updates to ad-related context menu items in sync.
*
* @param dispatch
* @param spoc
* @param position
* @param type
* @returns {Element}
* @constructor
*/
function AdBannerContextMenu({
dispatch,
spoc,
position,
type
}) {
const ADBANNER_CONTEXT_MENU_OPTIONS = ["BlockAdUrl", "ManageSponsoredContent", "OurSponsorsAndYourPrivacy"];
const [showContextMenu, setShowContextMenu] = (0,external_React_namespaceObject.useState)(false);
const onClick = e => {
e.preventDefault();
setShowContextMenu(!showContextMenu);
};
const onUpdate = () => {
setShowContextMenu(!showContextMenu);
};
return /*#__PURE__*/external_React_default().createElement("div", {
className: "ads-context-menu-wrapper"
}, /*#__PURE__*/external_React_default().createElement("div", {
className: "ads-context-menu"
}, /*#__PURE__*/external_React_default().createElement("moz-button", {
type: "icon",
size: "default",
iconsrc: "chrome://global/skin/icons/more.svg",
onClick: onClick
}), showContextMenu && /*#__PURE__*/external_React_default().createElement(LinkMenu, {
onUpdate: onUpdate,
dispatch: dispatch,
options: ADBANNER_CONTEXT_MENU_OPTIONS,
shouldSendImpressionStats: true,
userEvent: actionCreators.DiscoveryStreamUserEvent,
site: {
// Props we want to pass on for new ad types that come from Unified Ads API
block_key: spoc.block_key,
fetchTimestamp: spoc.fetchTimestamp,
flight_id: spoc.flight_id,
format: spoc.format,
id: spoc.id,
guid: spoc.guid,
card_type: "spoc",
// required to record telemetry for an action, see handleBlockUrl in TelemetryFeed.sys.mjs
is_pocket_card: true,
position,
sponsor: spoc.sponsor,
title: spoc.title,
url: spoc.url || spoc.shim.url,
personalization_models: spoc.personalization_models,
priority: spoc.priority,
score: spoc.score,
alt_text: spoc.alt_text,
shim: spoc.shim
},
index: position,
source: type.toUpperCase()
})));
}
;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/AdBanner/AdBanner.jsx ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/AdBanner/AdBanner.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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, * License, v. 2.0. If a copy of the MPL was not distributed with this file,
@ -4293,6 +4393,20 @@ function ListFeed({
/**
* A new banner ad that appears between rows of stories: leaderboard or billboard size.
*
* @param spoc
* @param dispatch
* @param firstVisibleTimestamp
* @param row
* @param type
* @param prefs
* @returns {Element}
* @constructor
*/
const AdBanner = ({ const AdBanner = ({
spoc, spoc,
dispatch, dispatch,
@ -4325,33 +4439,11 @@ const AdBanner = ({
width: imgWidth, width: imgWidth,
height: imgHeight height: imgHeight
} = getDimensions(spoc.format); } = getDimensions(spoc.format);
const handleDismissClick = () => {
dispatch(actionCreators.AlsoToMain({
type: actionTypes.BLOCK_URL,
data: [{
block_key: spoc.block_key,
fetchTimestamp: spoc.fetchTimestamp,
flight_id: spoc.flight_id,
format: spoc.format,
id: spoc.id,
card_type: "spoc",
is_pocket_card: true,
position: row,
sponsor: spoc.sponsor,
title: spoc.title,
url: spoc.url || spoc.shim.url,
personalization_models: spoc.personalization_models,
priority: spoc.priority,
score: spoc.score,
alt_text: spoc.alt_text
}]
}));
};
const onLinkClick = () => { const onLinkClick = () => {
dispatch(actionCreators.DiscoveryStreamUserEvent({ dispatch(actionCreators.DiscoveryStreamUserEvent({
event: "CLICK", event: "CLICK",
source: type.toUpperCase(), source: type.toUpperCase(),
// Banner ads dont have a position, but a row number // Banner ads don't have a position, but a row number
action_position: row, action_position: row,
value: { value: {
card_type: "spoc", card_type: "spoc",
@ -4380,13 +4472,12 @@ const AdBanner = ({
} }
}, /*#__PURE__*/external_React_default().createElement("div", { }, /*#__PURE__*/external_React_default().createElement("div", {
className: `ad-banner-inner ${spoc.format}` className: `ad-banner-inner ${spoc.format}`
}, /*#__PURE__*/external_React_default().createElement("div", { }, /*#__PURE__*/external_React_default().createElement(AdBannerContextMenu, {
className: "ad-banner-dismiss" dispatch: dispatch,
}, /*#__PURE__*/external_React_default().createElement("button", { spoc: spoc,
className: "icon icon-dismiss", position: row,
onClick: handleDismissClick, type: type
"data-l10n-id": "newtab-toast-dismiss-button" }), /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
})), /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
className: "ad-banner-link", className: "ad-banner-link",
url: spoc.url, url: spoc.url,
title: spoc.title, title: spoc.title,
@ -11351,6 +11442,7 @@ const WallpapersSection = (0,external_ReactRedux_namespaceObject.connect)(state
}; };
})(_WallpapersSection); })(_WallpapersSection);
;// CONCATENATED MODULE: ./content-src/components/WallpapersSection/WallpaperCategories.jsx ;// CONCATENATED MODULE: ./content-src/components/WallpapersSection/WallpaperCategories.jsx
function WallpaperCategories_extends() { WallpaperCategories_extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return WallpaperCategories_extends.apply(this, arguments); }
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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, * 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/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -11361,6 +11453,8 @@ const WallpapersSection = (0,external_ReactRedux_namespaceObject.connect)(state
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
const PREF_WALLPAPER_UPLOADED_PREVIOUSLY = "newtabWallpapers.customWallpaper.uploadedPreviously"; const PREF_WALLPAPER_UPLOADED_PREVIOUSLY = "newtabWallpapers.customWallpaper.uploadedPreviously";
const PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE = "newtabWallpapers.customWallpaper.fileSize";
const PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE_ENABLED = "newtabWallpapers.customWallpaper.fileSize.enabled";
// Returns a function will not be continuously triggered when called. The // Returns a function will not be continuously triggered when called. The
// function will be triggered if called again after `wait` milliseconds. // function will be triggered if called again after `wait` milliseconds.
@ -11399,7 +11493,8 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
activeCategoryFluentID: null, activeCategoryFluentID: null,
showColorPicker: false, showColorPicker: false,
inputType: "radio", inputType: "radio",
activeId: null activeId: null,
isCustomWallpaperError: false
}; };
} }
componentDidMount() { componentDidMount() {
@ -11456,9 +11551,11 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
id = `solid-color-picker-${event.target.value}`; id = `solid-color-picker-${event.target.value}`;
} }
this.props.setPref("newtabWallpapers.wallpaper", id); this.props.setPref("newtabWallpapers.wallpaper", id);
const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
this.handleUserEvent(actionTypes.WALLPAPER_CLICK, { this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
selected_wallpaper: id, selected_wallpaper: id,
had_previous_wallpaper: !!this.props.activeWallpaper had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously
}); });
} }
@ -11525,17 +11622,24 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
this.wallpaperRef[nextIndex].click(); this.wallpaperRef[nextIndex].click();
} }
handleReset() { handleReset() {
this.props.setPref("newtabWallpapers.wallpaper", "");
const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY]; const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
if (uploadedPreviously) { const selectedWallpaper = this.props.Prefs.values["newtabWallpapers.wallpaper"];
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, false);
// If a custom wallpaper is set, remove it
if (selectedWallpaper === "custom") {
this.props.dispatch(actionCreators.OnlyToMain({ this.props.dispatch(actionCreators.OnlyToMain({
type: actionTypes.WALLPAPER_REMOVE_UPLOAD type: actionTypes.WALLPAPER_REMOVE_UPLOAD
})); }));
} }
// Reset active wallpaper
this.props.setPref("newtabWallpapers.wallpaper", "");
// Fire WALLPAPER_CLICK telemetry event
this.handleUserEvent(actionTypes.WALLPAPER_CLICK, { this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
selected_wallpaper: "none", selected_wallpaper: "none",
had_previous_wallpaper: !!this.props.activeWallpaper had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously
}); });
} }
handleCategory = event => { handleCategory = event => {
@ -11564,32 +11668,59 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
// Custom wallpaper image upload // Custom wallpaper image upload
async handleUpload() { async handleUpload() {
// TODO: Bug 1943663: Add telemetry const wallpaperUploadMaxFileSizeEnabled = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE_ENABLED];
const wallpaperUploadMaxFileSize = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
// TODO: Bug 1947813: Add image upload error states/UI
// TODO: Once Bug 1947813 has landed, we may need a separate event
// for selecting previously uploaded wallpaper, rather than uploading a new one.
// The plan would be to reuse at.WALLPAPER_CLICK for this use case
const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY]; const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
this.handleUserEvent(actionTypes.WALLPAPER_UPLOAD, {
had_uploaded_previously: !!uploadedPreviously,
had_previous_wallpaper: !!this.props.activeWallpaper
});
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, true);
// Create a file input since category buttons are radio inputs // Create a file input since category buttons are radio inputs
const fileInput = document.createElement("input"); const fileInput = document.createElement("input");
fileInput.type = "file"; fileInput.type = "file";
fileInput.accept = "image/*"; // only allow image files fileInput.accept = "image/*"; // only allow image files
// Catch cancel events
fileInput.oncancel = async () => {
this.setState({
isCustomWallpaperError: false
});
};
// Reset error state when user begins file selection
this.setState({
isCustomWallpaperError: false
});
// Fire when user selects a file
fileInput.onchange = async event => { fileInput.onchange = async event => {
const [file] = event.target.files; const [file] = event.target.files;
// Limit image uploaded to a maximum file size if enabled
// Note: The max file size pref (customWallpaper.fileSize) is converted to megabytes (MB)
// Example: if pref value is 5, max file size is 5 MB
const maxSize = wallpaperUploadMaxFileSize * 1024 * 1024;
if (wallpaperUploadMaxFileSizeEnabled && file && file.size > maxSize) {
console.error("File size exceeds limit");
this.setState({
isCustomWallpaperError: true
});
return;
}
if (file) { if (file) {
this.props.dispatch(actionCreators.OnlyToMain({ this.props.dispatch(actionCreators.OnlyToMain({
type: actionTypes.WALLPAPER_UPLOAD, type: actionTypes.WALLPAPER_UPLOAD,
data: file data: file
})); }));
// Set active wallpaper ID to "custom"
this.props.setPref("newtabWallpapers.wallpaper", "custom");
// Update the uploadedPreviously pref to TRUE
// Note: this pref used for telemetry. Do not reset to false.
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, true);
this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
selected_wallpaper: "custom",
had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously
});
} }
}; };
fileInput.click(); fileInput.click();
@ -11642,6 +11773,7 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
activeCategoryFluentID activeCategoryFluentID
} = this.state; } = this.state;
let filteredWallpapers = wallpaperList.filter(wallpaper => wallpaper.category === activeCategory); let filteredWallpapers = wallpaperList.filter(wallpaper => wallpaper.category === activeCategory);
const wallpaperUploadMaxFileSize = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
function reduceColorsToFitCustomColorInput(arr) { function reduceColorsToFitCustomColorInput(arr) {
// Reduce the amount of custom colors to make space for the custom color picker // Reduce the amount of custom colors to make space for the custom color picker
while (arr.length % 3 !== 2) { while (arr.length % 3 !== 2) {
@ -11755,7 +11887,7 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
} }
return /*#__PURE__*/external_React_default().createElement("div", { return /*#__PURE__*/external_React_default().createElement("div", {
key: category key: category
}, /*#__PURE__*/external_React_default().createElement("input", { }, /*#__PURE__*/external_React_default().createElement("input", WallpaperCategories_extends({
ref: el => { ref: el => {
if (el) { if (el) {
this.categoryRef[index] = el; this.categoryRef[index] = el;
@ -11770,10 +11902,20 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
onClick: category !== "custom-wallpaper" ? this.handleCategory : this.handleUpload, onClick: category !== "custom-wallpaper" ? this.handleCategory : this.handleUpload,
className: category !== "custom-wallpaper" ? `wallpaper-input` : `wallpaper-input theme-custom-wallpaper`, className: category !== "custom-wallpaper" ? `wallpaper-input` : `wallpaper-input theme-custom-wallpaper`,
tabIndex: index === 0 ? 0 : -1 tabIndex: index === 0 ? 0 : -1
}), /*#__PURE__*/external_React_default().createElement("label", { }, category === "custom-wallpaper" ? {
"aria-errormessage": "customWallpaperError"
} : {})), /*#__PURE__*/external_React_default().createElement("label", {
htmlFor: category, htmlFor: category,
"data-l10n-id": fluent_id "data-l10n-id": fluent_id
}, fluent_id)); }, fluent_id));
})), this.state.isCustomWallpaperError && /*#__PURE__*/external_React_default().createElement("div", {
className: "custom-wallpaper-error",
id: "customWallpaperError"
}, /*#__PURE__*/external_React_default().createElement("span", {
className: "icon icon-info"
}), /*#__PURE__*/external_React_default().createElement("span", {
"data-l10n-id": "newtab-wallpaper-error-max-file-size",
"data-l10n-args": `{"file_size": ${wallpaperUploadMaxFileSize}}`
}))), /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, { }))), /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, {
in: !!activeCategory, in: !!activeCategory,
timeout: 300, timeout: 300,
@ -11847,7 +11989,6 @@ const WallpaperCategories = (0,external_ReactRedux_namespaceObject.connect)(stat
class ContentSection extends (external_React_default()).PureComponent { class ContentSection extends (external_React_default()).PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@ -11868,7 +12009,7 @@ class ContentSection extends (external_React_default()).PureComponent {
})); }));
} }
onPreferenceSelect(e) { onPreferenceSelect(e) {
// eventSource: TOP_SITES | TOP_STORIES | HIGHLIGHTS | WEATHER // eventSource: WEATHER | TOP_SITES | TOP_STORIES
const { const {
preference, preference,
eventSource eventSource
@ -11913,21 +12054,18 @@ class ContentSection extends (external_React_default()).PureComponent {
if (isOpen) { if (isOpen) {
drawerRef.style.marginTop = "var(--space-large)"; drawerRef.style.marginTop = "var(--space-large)";
} else { } else {
drawerRef.style.marginTop = `-${drawerHeight}px`; drawerRef.style.marginTop = `-${drawerHeight + 3}px`;
} }
} }
} }
render() { render() {
const { const {
enabledSections, enabledSections,
mayHaveSponsoredTopSites,
pocketRegion, pocketRegion,
mayHaveSponsoredStories,
mayHaveInferredPersonalization, mayHaveInferredPersonalization,
mayHaveRecentSaves, mayHaveRecentSaves,
mayHaveWeather, mayHaveWeather,
openPreferences, openPreferences,
spocMessageVariant,
wallpapersEnabled, wallpapersEnabled,
wallpapersV2Enabled, wallpapersV2Enabled,
activeWallpaper, activeWallpaper,
@ -11938,10 +12076,7 @@ class ContentSection extends (external_React_default()).PureComponent {
const { const {
topSitesEnabled, topSitesEnabled,
pocketEnabled, pocketEnabled,
highlightsEnabled,
weatherEnabled, weatherEnabled,
showSponsoredTopSitesEnabled,
showSponsoredPocketEnabled,
showInferredPersonalizationEnabled, showInferredPersonalizationEnabled,
showRecentSavesEnabled, showRecentSavesEnabled,
topSitesRowsCount topSitesRowsCount
@ -11964,7 +12099,17 @@ class ContentSection extends (external_React_default()).PureComponent {
role: "separator" role: "separator"
})), /*#__PURE__*/external_React_default().createElement("div", { })), /*#__PURE__*/external_React_default().createElement("div", {
className: "settings-toggles" className: "settings-toggles"
}, /*#__PURE__*/external_React_default().createElement("div", { }, mayHaveWeather && /*#__PURE__*/external_React_default().createElement("div", {
id: "weather-section",
className: "section"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
id: "weather-toggle",
pressed: weatherEnabled || null,
onToggle: this.onPreferenceSelect,
"data-preference": "showWeather",
"data-eventSource": "WEATHER",
"data-l10n-id": "newtab-custom-weather-toggle"
})), /*#__PURE__*/external_React_default().createElement("div", {
id: "shortcuts-section", id: "shortcuts-section",
className: "section" className: "section"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", { }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
@ -12006,22 +12151,6 @@ class ContentSection extends (external_React_default()).PureComponent {
value: "4", value: "4",
"data-l10n-id": "newtab-custom-row-selector", "data-l10n-id": "newtab-custom-row-selector",
"data-l10n-args": "{\"num\": 4}" "data-l10n-args": "{\"num\": 4}"
})), mayHaveSponsoredTopSites && /*#__PURE__*/external_React_default().createElement("div", {
className: "check-wrapper",
role: "presentation"
}, /*#__PURE__*/external_React_default().createElement("input", {
id: "sponsored-shortcuts",
className: "customize-menu-checkbox",
disabled: !topSitesEnabled,
checked: showSponsoredTopSitesEnabled,
type: "checkbox",
onChange: this.onPreferenceSelect,
"data-preference": "showSponsoredTopSites",
"data-eventSource": "SPONSORED_TOP_SITES"
}), /*#__PURE__*/external_React_default().createElement("label", {
className: "customize-menu-checkbox-label",
htmlFor: "sponsored-shortcuts",
"data-l10n-id": "newtab-custom-sponsored-sites"
}))))))), pocketRegion && /*#__PURE__*/external_React_default().createElement("div", { }))))))), pocketRegion && /*#__PURE__*/external_React_default().createElement("div", {
id: "pocket-section", id: "pocket-section",
className: "section" className: "section"
@ -12035,28 +12164,12 @@ class ContentSection extends (external_React_default()).PureComponent {
"data-l10n-id": "newtab-custom-stories-toggle" "data-l10n-id": "newtab-custom-stories-toggle"
}, /*#__PURE__*/external_React_default().createElement("div", { }, /*#__PURE__*/external_React_default().createElement("div", {
slot: "nested" slot: "nested"
}, (mayHaveSponsoredStories || mayHaveRecentSaves) && /*#__PURE__*/external_React_default().createElement("div", { }, (mayHaveRecentSaves || mayHaveInferredPersonalization || mayHaveTopicSections) && /*#__PURE__*/external_React_default().createElement("div", {
className: "more-info-pocket-wrapper" className: "more-info-pocket-wrapper"
}, /*#__PURE__*/external_React_default().createElement("div", { }, /*#__PURE__*/external_React_default().createElement("div", {
className: "more-information", className: "more-information",
ref: this.pocketDrawerRef ref: this.pocketDrawerRef
}, mayHaveSponsoredStories && /*#__PURE__*/external_React_default().createElement("div", { }, mayHaveInferredPersonalization && /*#__PURE__*/external_React_default().createElement("div", {
className: "check-wrapper",
role: "presentation"
}, /*#__PURE__*/external_React_default().createElement("input", {
id: "sponsored-pocket",
className: "customize-menu-checkbox",
disabled: !pocketEnabled,
checked: showSponsoredPocketEnabled,
type: "checkbox",
onChange: this.onPreferenceSelect,
"data-preference": "showSponsored",
"data-eventSource": "POCKET_SPOCS"
}), /*#__PURE__*/external_React_default().createElement("label", {
className: "customize-menu-checkbox-label",
htmlFor: "sponsored-pocket",
"data-l10n-id": "newtab-custom-pocket-sponsored"
})), mayHaveInferredPersonalization && /*#__PURE__*/external_React_default().createElement("div", {
className: "check-wrapper", className: "check-wrapper",
role: "presentation" role: "presentation"
}, /*#__PURE__*/external_React_default().createElement("input", { }, /*#__PURE__*/external_React_default().createElement("input", {
@ -12089,34 +12202,7 @@ class ContentSection extends (external_React_default()).PureComponent {
className: "customize-menu-checkbox-label", className: "customize-menu-checkbox-label",
htmlFor: "recent-saves-pocket", htmlFor: "recent-saves-pocket",
"data-l10n-id": "newtab-custom-pocket-show-recent-saves" "data-l10n-id": "newtab-custom-pocket-show-recent-saves"
}))))))), /*#__PURE__*/external_React_default().createElement("div", { })))))))), /*#__PURE__*/external_React_default().createElement("span", {
id: "recent-section",
className: "section"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
id: "highlights-toggle",
pressed: highlightsEnabled || null,
onToggle: this.onPreferenceSelect,
"data-preference": "feeds.section.highlights",
"data-eventSource": "HIGHLIGHTS",
"data-l10n-id": "newtab-custom-recent-toggle"
})), mayHaveWeather && /*#__PURE__*/external_React_default().createElement("div", {
id: "weather-section",
className: "section"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
id: "weather-toggle",
pressed: weatherEnabled || null,
onToggle: this.onPreferenceSelect,
"data-preference": "showWeather",
"data-eventSource": "WEATHER",
"data-l10n-id": "newtab-custom-weather-toggle"
})), pocketRegion && mayHaveSponsoredStories && spocMessageVariant === "variant-c" && /*#__PURE__*/external_React_default().createElement("div", {
className: "sponsored-content-info"
}, /*#__PURE__*/external_React_default().createElement("div", {
className: "icon icon-help"
}), /*#__PURE__*/external_React_default().createElement("div", null, "Sponsored content supports our mission to build a better web.", " ", /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
dispatch: this.props.dispatch,
url: "https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs"
}, "Find out how")))), /*#__PURE__*/external_React_default().createElement("span", {
className: "divider", className: "divider",
role: "separator" role: "separator"
}), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", { }), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", {
@ -12205,12 +12291,9 @@ class _CustomizeMenu extends (external_React_default()).PureComponent {
activeWallpaper: this.props.activeWallpaper, activeWallpaper: this.props.activeWallpaper,
pocketRegion: this.props.pocketRegion, pocketRegion: this.props.pocketRegion,
mayHaveTopicSections: this.props.mayHaveTopicSections, mayHaveTopicSections: this.props.mayHaveTopicSections,
mayHaveSponsoredTopSites: this.props.mayHaveSponsoredTopSites,
mayHaveSponsoredStories: this.props.mayHaveSponsoredStories,
mayHaveInferredPersonalization: this.props.mayHaveInferredPersonalization, mayHaveInferredPersonalization: this.props.mayHaveInferredPersonalization,
mayHaveRecentSaves: this.props.DiscoveryStream.recentSavesEnabled, mayHaveRecentSaves: this.props.DiscoveryStream.recentSavesEnabled,
mayHaveWeather: this.props.mayHaveWeather, mayHaveWeather: this.props.mayHaveWeather,
spocMessageVariant: this.props.spocMessageVariant,
dispatch: this.props.dispatch, dispatch: this.props.dispatch,
exitEventFired: this.state.exitEventFired exitEventFired: this.state.exitEventFired
})))); }))));
@ -13825,9 +13908,6 @@ class BaseContent extends (external_React_default()).PureComponent {
const enabledSections = { const enabledSections = {
topSitesEnabled: prefs["feeds.topsites"], topSitesEnabled: prefs["feeds.topsites"],
pocketEnabled: prefs["feeds.section.topstories"], pocketEnabled: prefs["feeds.section.topstories"],
highlightsEnabled: prefs["feeds.section.highlights"],
showSponsoredTopSitesEnabled: prefs.showSponsoredTopSites,
showSponsoredPocketEnabled: prefs.showSponsored,
showInferredPersonalizationEnabled: prefs[PREF_INFERRED_PERSONALIZATION_USER], showInferredPersonalizationEnabled: prefs[PREF_INFERRED_PERSONALIZATION_USER],
showRecentSavesEnabled: prefs.showRecentSaves, showRecentSavesEnabled: prefs.showRecentSaves,
topSitesRowsCount: prefs.topSitesRows, topSitesRowsCount: prefs.topSitesRows,

View file

@ -219,6 +219,13 @@ module.exports = function (config) {
{ {
branches: 60, branches: 60,
}, },
"content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx":
{
statements: 87.5,
lines: 87.5,
functions: 66.67,
branches: 0,
},
"content-src/components/DiscoveryStreamComponents/**/*.jsx": { "content-src/components/DiscoveryStreamComponents/**/*.jsx": {
statements: 90.48, statements: 90.48,
lines: 90.48, lines: 90.48,

View file

@ -25,7 +25,25 @@ const PREFS_BEFORE_SECTIONS = () => [
feed: "showSearch", feed: "showSearch",
titleString: "home-prefs-search-header", titleString: "home-prefs-search-header",
}, },
icon: "chrome://global/skin/icons/search-glass.svg", },
{
id: "weather",
pref: {
feed: "showWeather",
titleString: "home-prefs-weather-header",
descString: "home-prefs-weather-description",
learnMore: {
link: {
href: "https://support.mozilla.org/kb/customize-items-on-firefox-new-tab-page",
id: "home-prefs-weather-learn-more-link",
},
},
},
eventSource: "WEATHER",
shouldHidePref: !Services.prefs.getBoolPref(
"browser.newtabpage.activity-stream.system.showWeather",
false
),
}, },
{ {
id: "topsites", id: "topsites",
@ -45,31 +63,10 @@ const PREFS_BEFORE_SECTIONS = () => [
: []; : [];
}, },
}, },
icon: "chrome://browser/skin/topsites.svg",
maxRows: 4, maxRows: 4,
rowsPref: "topSitesRows", rowsPref: "topSitesRows",
eventSource: "TOP_SITES", eventSource: "TOP_SITES",
}, },
{
id: "weather",
icon: "chrome://browser/skin/weather/sunny.svg",
pref: {
feed: "showWeather",
titleString: "home-prefs-weather-header",
descString: "home-prefs-weather-description",
learnMore: {
link: {
href: "https://support.mozilla.org/kb/customize-items-on-firefox-new-tab-page",
id: "home-prefs-weather-learn-more-link",
},
},
},
eventSource: "WEATHER",
shouldHidePref: !Services.prefs.getBoolPref(
"browser.newtabpage.activity-stream.system.showWeather",
false
),
},
]; ];
export class AboutPreferences { export class AboutPreferences {
@ -192,7 +189,6 @@ export class AboutPreferences {
const { const {
id, id,
pref: prefData, pref: prefData,
icon = "webextension",
maxRows, maxRows,
rowsPref, rowsPref,
shouldHidePref, shouldHidePref,
@ -210,17 +206,11 @@ export class AboutPreferences {
return; return;
} }
// Use full icon spec for certain protocols or fall back to packaged icon
const iconUrl = !icon.search(/^(chrome|moz-extension|resource):/)
? icon
: `chrome://newtab/content/data/content/assets/glyph-${icon}-16.svg`;
// Add the main preference for turning on/off a section // Add the main preference for turning on/off a section
const sectionVbox = createAppend("vbox", contentsGroup); const sectionVbox = createAppend("vbox", contentsGroup);
sectionVbox.setAttribute("data-subcategory", id); sectionVbox.setAttribute("data-subcategory", id);
const checkbox = createAppend("checkbox", sectionVbox); const checkbox = createAppend("checkbox", sectionVbox);
checkbox.classList.add("section-checkbox"); checkbox.classList.add("section-checkbox");
checkbox.setAttribute("src", iconUrl);
// Setup a user event if we have an event source for this pref. // Setup a user event if we have an event source for this pref.
if (eventSource) { if (eventSource) {
this.setupUserEvent(checkbox, eventSource); this.setupUserEvent(checkbox, eventSource);

View file

@ -465,10 +465,24 @@ export const PREFS_CONFIG = new Map([
"newtabWallpapers.customWallpaper.uploadedPreviously", "newtabWallpapers.customWallpaper.uploadedPreviously",
{ {
title: title:
"Boolean flag to track if a user has previously uploaded a custom wallpaper", "Boolean flag used for telemetry to track if a user has previously uploaded a custom wallpaper",
value: false, value: false,
}, },
], ],
[
"newtabWallpapers.customWallpaper.fileSize.enabled",
{
title: "Boolean flag to enforce a maximum file size for uploaded images",
value: false,
},
],
[
"newtabWallpapers.customWallpaper.fileSize",
{
title: "Number pref of maximum file size (in MB) a user can upload",
value: 0,
},
],
[ [
"newtabAdSize.variant-a", "newtabAdSize.variant-a",
{ {

View file

@ -1151,13 +1151,18 @@ export class TelemetryFeed {
break; break;
case "WALLPAPER_CLICK": case "WALLPAPER_CLICK":
{ {
const { selected_wallpaper, had_previous_wallpaper } = data; const {
selected_wallpaper,
had_previous_wallpaper,
had_uploaded_previously,
} = data;
// if either of the wallpaper prefs are truthy, they had a previous wallpaper // if either of the wallpaper prefs are truthy, they had a previous wallpaper
Glean.newtab.wallpaperClick.record({ Glean.newtab.wallpaperClick.record({
newtab_visit_id: session.session_id, newtab_visit_id: session.session_id,
selected_wallpaper, selected_wallpaper,
had_previous_wallpaper, had_previous_wallpaper,
had_uploaded_previously,
}); });
} }
break; break;
@ -1171,18 +1176,6 @@ export class TelemetryFeed {
newtab_visit_id: session.session_id, newtab_visit_id: session.session_id,
}); });
break; break;
case "WALLPAPER_UPLOAD":
{
const { had_uploaded_previously, had_previous_wallpaper } = data;
Glean.newtab.wallpaperUpload.record({
newtab_visit_id: session.session_id,
had_previous_wallpaper,
had_uploaded_previously,
});
}
break;
default: default:
break; break;
} }

View file

@ -1,5 +1,9 @@
"use strict"; "use strict";
// test_tab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
// Test that we do not set icons in individual tile and card context menus on // Test that we do not set icons in individual tile and card context menus on
// newtab page. // newtab page.
test_newtab({ test_newtab({

View file

@ -1,5 +1,9 @@
"use strict"; "use strict";
// test_newtab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
const { WeatherFeed } = ChromeUtils.importESModule( const { WeatherFeed } = ChromeUtils.importESModule(
"resource://newtab/lib/WeatherFeed.sys.mjs" "resource://newtab/lib/WeatherFeed.sys.mjs"
); );
@ -14,8 +18,7 @@ test_newtab({
async before({ pushPrefs }) { async before({ pushPrefs }) {
await pushPrefs( await pushPrefs(
["browser.newtabpage.activity-stream.feeds.topsites", false], ["browser.newtabpage.activity-stream.feeds.topsites", false],
["browser.newtabpage.activity-stream.feeds.section.topstories", false], ["browser.newtabpage.activity-stream.feeds.section.topstories", false]
["browser.newtabpage.activity-stream.feeds.section.highlights", false]
); );
}, },
test: async function test_render_customizeMenu() { test: async function test_render_customizeMenu() {
@ -32,8 +35,6 @@ test_newtab({
); );
} }
const TOPSITES_PREF = "browser.newtabpage.activity-stream.feeds.topsites"; const TOPSITES_PREF = "browser.newtabpage.activity-stream.feeds.topsites";
const HIGHLIGHTS_PREF =
"browser.newtabpage.activity-stream.feeds.section.highlights";
const TOPSTORIES_PREF = const TOPSTORIES_PREF =
"browser.newtabpage.activity-stream.feeds.section.topstories"; "browser.newtabpage.activity-stream.feeds.section.topstories";
@ -93,26 +94,6 @@ test_newtab({
await sectionShownPromise; await sectionShownPromise;
Assert.ok(getSection("topstories"), "Pocket section is rendered"); Assert.ok(getSection("topstories"), "Pocket section is rendered");
// Test that clicking the recent activity toggle will make the
// recent activity section appear on the newtab page.
//
// We waive XRay wrappers because we want to call the click()
// method defined on the toggle from this context.
let highlightsSwitch = Cu.waiveXrays(
content.document.querySelector("#recent-section moz-toggle")
);
Assert.ok(
!Services.prefs.getBoolPref(HIGHLIGHTS_PREF),
"Highlights pref is turned off"
);
Assert.ok(!getSection("highlights"), "Highlights section is not rendered");
sectionShownPromise = promiseSectionShown("highlights");
highlightsSwitch.click();
await sectionShownPromise;
Assert.ok(getSection("highlights"), "Highlights section is rendered");
}, },
async after() { async after() {
Services.prefs.clearUserPref( Services.prefs.clearUserPref(
@ -121,9 +102,6 @@ test_newtab({
Services.prefs.clearUserPref( Services.prefs.clearUserPref(
"browser.newtabpage.activity-stream.feeds.section.topstories" "browser.newtabpage.activity-stream.feeds.section.topstories"
); );
Services.prefs.clearUserPref(
"browser.newtabpage.activity-stream.feeds.section.highlights"
);
}, },
}); });

View file

@ -1,5 +1,9 @@
"use strict"; "use strict";
// test_newtab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
// Test that the customization menu is rendered. // Test that the customization menu is rendered.
test_newtab({ test_newtab({
async before() { async before() {

View file

@ -1,5 +1,9 @@
"use strict"; "use strict";
// test_newtab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
// Test that the customization menu is rendered. // Test that the customization menu is rendered.
test_newtab({ test_newtab({
test: async function test_render_customizeMenu() { test: async function test_render_customizeMenu() {

View file

@ -1,3 +1,7 @@
// test_newtab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
// If this fails it could be because of schema changes. // If this fails it could be because of schema changes.
// `topstories.json` defines the stories shown // `topstories.json` defines the stories shown
test_newtab({ test_newtab({

View file

@ -1,5 +1,9 @@
"use strict"; "use strict";
// test_newtab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
async function before({ pushPrefs }) { async function before({ pushPrefs }) {
await pushPrefs([ await pushPrefs([
"browser.newtabpage.activity-stream.discoverystream.config", "browser.newtabpage.activity-stream.discoverystream.config",

View file

@ -1,5 +1,9 @@
"use strict"; "use strict";
// test_newtab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
// Tests that: // Tests that:
// 1. Top sites header is hidden and the topsites section is not collapsed on load. // 1. Top sites header is hidden and the topsites section is not collapsed on load.
// 2. Pocket header and section are visible and not collapsed on load. // 2. Pocket header and section are visible and not collapsed on load.

View file

@ -4,6 +4,10 @@
"use strict"; "use strict";
// test_newtab calls SpecialPowers.spawn, which injects ContentTaskUtils in the
// scope of the callback. Eslint doesn't know about that.
/* global ContentTaskUtils */
test_newtab({ test_newtab({
async before() { async before() {
// Some reason test-linux1804-64-qr/debug can end up with example.com, so // Some reason test-linux1804-64-qr/debug can end up with example.com, so

View file

@ -47,31 +47,19 @@ describe("ContentSection", () => {
wrapper.unmount(); wrapper.unmount();
}); });
it("should have data-eventSource attributes on relevent pref changing inputs", () => { it("should have data-eventSource attributes on relevant pref changing inputs", () => {
wrapper = mount(<ContentSection {...DEFAULT_PROPS} />); wrapper = mount(<ContentSection {...DEFAULT_PROPS} />);
assert.equal(
wrapper.find("#weather-toggle").prop("data-eventSource"),
"WEATHER"
);
assert.equal( assert.equal(
wrapper.find("#shortcuts-toggle").prop("data-eventSource"), wrapper.find("#shortcuts-toggle").prop("data-eventSource"),
"TOP_SITES" "TOP_SITES"
); );
assert.equal(
wrapper.find("#sponsored-shortcuts").prop("data-eventSource"),
"SPONSORED_TOP_SITES"
);
assert.equal( assert.equal(
wrapper.find("#pocket-toggle").prop("data-eventSource"), wrapper.find("#pocket-toggle").prop("data-eventSource"),
"TOP_STORIES" "TOP_STORIES"
); );
assert.equal(
wrapper.find("#sponsored-pocket").prop("data-eventSource"),
"POCKET_SPOCS"
);
assert.equal(
wrapper.find("#highlights-toggle").prop("data-eventSource"),
"HIGHLIGHTS"
);
assert.equal(
wrapper.find("#weather-toggle").prop("data-eventSource"),
"WEATHER"
);
}); });
}); });

View file

@ -121,15 +121,12 @@ describe("Discovery Stream <AdBanner>", () => {
assert.deepEqual(aside.prop("style"), { gridRow: clampedRow }); assert.deepEqual(aside.prop("style"), { gridRow: clampedRow });
}); });
it("should have the dismiss button be visible", () => { it("should have the context menu button be visible", () => {
const dismiss = wrapper.find(".ad-banner-dismiss .icon-dismiss"); const dismiss = wrapper.find("moz-button");
assert.ok(dismiss.exists()); assert.ok(dismiss.exists());
dismiss.simulate("click"); // The rest of the context menu functionality is now tested in
// AdBannerContextMenu.test.jsx
let [action] = dispatch.secondCall.args;
assert.equal(action.type, "BLOCK_URL");
assert.equal(action.data[0].id, DEFAULT_PROPS.spoc.id);
}); });
it("should call onLinkClick when banner is clicked", () => { it("should call onLinkClick when banner is clicked", () => {
@ -142,7 +139,7 @@ describe("Discovery Stream <AdBanner>", () => {
ac.DiscoveryStreamUserEvent({ ac.DiscoveryStreamUserEvent({
event: "CLICK", event: "CLICK",
source: "FOO", source: "FOO",
// Banner ads dont have a position, but a row number // Banner ads don't have a position, but a row number
action_position: DEFAULT_PROPS.row, action_position: DEFAULT_PROPS.row,
value: { value: {
card_type: "spoc", card_type: "spoc",

View file

@ -0,0 +1,65 @@
import { shallow } from "enzyme";
import { AdBannerContextMenu } from "content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu";
import { LinkMenu } from "content-src/components/LinkMenu/LinkMenu";
import React from "react";
describe("<AdBannerContextMenu>", () => {
let wrapper;
describe("Ad banner context menu options", () => {
const props = {
spoc: { url: "https://www.test.com/", shim: "aaabbbcccddd" },
position: 1,
type: "billboard",
};
beforeEach(() => {
wrapper = shallow(<AdBannerContextMenu {...props} />);
});
it("should render a context menu button", () => {
assert.ok(wrapper.exists());
assert.ok(
wrapper.find("moz-button").exists(),
"context menu button exists"
);
});
it("should render LinkMenu when context menu button is clicked", () => {
let button = wrapper.find("moz-button");
button.simulate("click", {
preventDefault: () => {},
});
assert.equal(wrapper.find(LinkMenu).length, 1);
});
it("should pass props to LinkMenu", () => {
wrapper.find("moz-button").simulate("click", {
preventDefault: () => {},
});
const linkMenuProps = wrapper.find(LinkMenu).props();
[
"onUpdate",
"dispatch",
"options",
"shouldSendImpressionStats",
"userEvent",
"site",
"index",
"source",
].forEach(prop => assert.property(linkMenuProps, prop));
});
it("should pass through the correct menu options to LinkMenu for ad banners", () => {
wrapper.find("moz-button").simulate("click", {
preventDefault: () => {},
});
const linkMenuProps = wrapper.find(LinkMenu).props();
assert.deepEqual(linkMenuProps.options, [
"BlockAdUrl",
"ManageSponsoredContent",
"OurSponsorsAndYourPrivacy",
]);
});
});
});

View file

@ -125,8 +125,8 @@ describe("AboutPreferences Feed", () => {
assert.calledOnce(stub); assert.calledOnce(stub);
const [, structure] = stub.firstCall.args; const [, structure] = stub.firstCall.args;
assert.equal(structure[0].id, "search"); assert.equal(structure[0].id, "search");
assert.equal(structure[1].id, "topsites"); assert.equal(structure[1].id, "weather");
assert.equal(structure[2].id, "weather"); assert.equal(structure[2].id, "topsites");
assert.equal(structure[3].id, "topstories"); assert.equal(structure[3].id, "topstories");
assert.isEmpty(structure[3].rowsPref); assert.isEmpty(structure[3].rowsPref);
}); });
@ -228,38 +228,6 @@ describe("AboutPreferences Feed", () => {
assert.notCalled(Preferences.add); assert.notCalled(Preferences.add);
}); });
}); });
describe("pref icon", () => {
it("should default to webextension icon", () => {
prefStructure = [{ pref: { feed: "feed" } }];
testRender();
assert.calledWith(
node.setAttribute,
"src",
"chrome://newtab/content/data/content/assets/glyph-webextension-16.svg"
);
});
it("should use desired glyph icon", () => {
prefStructure = [{ icon: "mail", pref: { feed: "feed" } }];
testRender();
assert.calledWith(
node.setAttribute,
"src",
"chrome://newtab/content/data/content/assets/glyph-mail-16.svg"
);
});
it("should use specified chrome icon", () => {
const icon = "chrome://the/icon.svg";
prefStructure = [{ icon, pref: { feed: "feed" } }];
testRender();
assert.calledWith(node.setAttribute, "src", icon);
});
});
describe("title line", () => { describe("title line", () => {
it("should render a title", () => { it("should render a title", () => {
const titleString = "the_title"; const titleString = "the_title";

View file

@ -290,8 +290,8 @@ quickactions-bookmarks2 = Manage bookmarks
quickactions-cmd-bookmarks = bookmarks quickactions-cmd-bookmarks = bookmarks
# Opens a SUMO article explaining how to clear history # Opens a SUMO article explaining how to clear history
quickactions-clearhistory = Clear History quickactions-clearrecenthistory = Clear recent history
quickactions-cmd-clearhistory = clear history quickactions-cmd-clearrecenthistory = clear recent history, history
# Opens about:downloads page # Opens about:downloads page
quickactions-downloads2 = View downloads quickactions-downloads2 = View downloads
@ -301,6 +301,17 @@ quickactions-cmd-downloads = downloads
quickactions-extensions = Manage extensions quickactions-extensions = Manage extensions
quickactions-cmd-extensions = extensions quickactions-cmd-extensions = extensions
# Opens Firefox View
quickactions-firefoxview = Open { -firefoxview-brand-name }
# English is using "view" and "open view", since the feature name is
# "Firefox View". If you have translated the name in your language, you
# should use a word related to the existing translation.
quickactions-cmd-firefoxview = open { -firefoxview-brand-name }, { -firefoxview-brand-name }, open view, view
# Opens SUMO home page
quickactions-help = { -brand-product-name } help
quickactions-cmd-help = help, support
# Opens the devtools web inspector # Opens the devtools web inspector
quickactions-inspector2 = Open Developer Tools quickactions-inspector2 = Open Developer Tools
quickactions-cmd-inspector = inspector, devtools quickactions-cmd-inspector = inspector, devtools

View file

@ -142,7 +142,7 @@ actions-callout-title = Complete common tasks or access basic settings
# These example text inputs correlate to the the following strings # These example text inputs correlate to the the following strings
# (either matching the whole string, or the first word of the string). # (either matching the whole string, or the first word of the string).
# "print" - quickactions-cmd-print # "print" - quickactions-cmd-print
# "clear" - quickactions-cmd-clearhistory # "clear" - quickactions-cmd-clearrecenthistory
# When localizing, ensure the translations match to ensure the action button appears as expected. # When localizing, ensure the translations match to ensure the action button appears as expected.
actions-callout-subtitle = Try typing an action like “print” to print a page, or “clear” to clear your history. actions-callout-subtitle = Try typing an action like “print” to print a page, or “clear” to clear your history.

View file

@ -285,15 +285,10 @@ newtab-custom-row-selector =
[one] { $num } row [one] { $num } row
*[other] { $num } rows *[other] { $num } rows
} }
newtab-custom-sponsored-sites = Sponsored shortcuts
newtab-custom-stories-toggle = newtab-custom-stories-toggle =
.label = Recommended stories .label = Recommended stories
.description = Exceptional content curated by the { -brand-product-name } family .description = Exceptional content curated by the { -brand-product-name } family
newtab-custom-pocket-sponsored = Sponsored stories
newtab-custom-pocket-show-recent-saves = Show recent saves newtab-custom-pocket-show-recent-saves = Show recent saves
newtab-custom-recent-toggle =
.label = Recent activity
.description = A selection of recent sites and content
newtab-custom-weather-toggle = newtab-custom-weather-toggle =
.label = Weather .label = Weather
.description = Todays forecast at a glance .description = Todays forecast at a glance

View file

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

View file

@ -221,7 +221,7 @@
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
#tabbrowser-tabs[movingtab] &[fadein]:not([selected]):not([multiselected]), #tabbrowser-tabs[movingtab] :not(tab-group:active) > &[fadein]:not(:active, [multiselected]),
&[multiselected-move-together], &[multiselected-move-together],
&[tabdrop-samewindow] { &[tabdrop-samewindow] {
transition: var(--tab-dragover-transition); transition: var(--tab-dragover-transition);
@ -1028,9 +1028,7 @@
pointer-events: none; /* avoid blocking dragover events on scroll buttons */ pointer-events: none; /* avoid blocking dragover events on scroll buttons */
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
#tabbrowser-tabs[movingtab] &, #tabbrowser-tabs[movingtab] &:not(:active) {
&[multiselected-move-together],
&[tabdrop-samewindow] {
transition: var(--tab-dragover-transition); transition: var(--tab-dragover-transition);
} }
} }

View file

@ -30,6 +30,12 @@
@media (-moz-windows-mica) { @media (-moz-windows-mica) {
&:not([lwtheme]) { &:not([lwtheme]) {
background-color: transparent; background-color: transparent;
/* stylelint-disable-next-line media-query-no-invalid */
@media -moz-pref("widget.windows.mica.toplevel-backdrop", 2) {
/* For acrylic, do the same we do for popups to guarantee some contrast */
background-color: light-dark(rgba(255, 255, 255, .6), rgba(0, 0, 0, .6));
}
} }
} }

View file

@ -6,7 +6,7 @@ buildscript {
repositories { repositories {
gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
maven { maven {
url repository url = repository
if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
allowInsecureProtocol = true allowInsecureProtocol = true
} }
@ -138,7 +138,7 @@ allprojects {
repositories { repositories {
gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
maven { maven {
url repository url = repository
if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
allowInsecureProtocol = true allowInsecureProtocol = true
} }
@ -172,7 +172,7 @@ allprojects {
} }
task downloadDependencies() { task downloadDependencies() {
description 'Download all dependencies to the Gradle cache' description = 'Download all dependencies to the Gradle cache'
doLast { doLast {
configurations.each { configuration -> configurations.each { configuration ->
if (configuration.canBeResolved) { if (configuration.canBeResolved) {

View file

@ -58,7 +58,7 @@ def android_sdk_version():
# If you think you can't handle the whole set of changes, please reach out to the Release # If you think you can't handle the whole set of changes, please reach out to the Release
# Engineering team. # Engineering team.
return namespace( return namespace(
build_tools_version="35.0.0", build_tools_version="35.0.1",
compile_sdk_version="35", compile_sdk_version="35",
target_sdk_version="35", target_sdk_version="35",
min_sdk_version="21", min_sdk_version="21",

View file

@ -577,6 +577,10 @@ netmonitor.toolbar.priority=Priority
# in the network table toolbar, above the "file" column. # in the network table toolbar, above the "file" column.
netmonitor.toolbar.file=File netmonitor.toolbar.file=File
# LOCALIZATION NOTE (netmonitor.toolbar.path): This is the label displayed
# in the network table toolbar, above the "Path" column.
netmonitor.toolbar.path=Path
# LOCALIZATION NOTE (netmonitor.toolbar.url): This is the label displayed # LOCALIZATION NOTE (netmonitor.toolbar.url): This is the label displayed
# in the network table toolbar, above the "url" column. # in the network table toolbar, above the "url" column.
netmonitor.toolbar.url=URL netmonitor.toolbar.url=URL

View file

@ -70,6 +70,14 @@ perftools-button-add-directory = Add a directory
perftools-button-remove-directory = Remove selected perftools-button-remove-directory = Remove selected
perftools-button-edit-settings = Edit Settings… perftools-button-edit-settings = Edit Settings…
## More actions menu
perftools-menu-more-actions-button =
.title = More actions
perftools-menu-more-actions-restart-with-profiling = Restart { -brand-shorter-name } with startup profiling enabled
perftools-menu-more-actions-copy-for-startup = Copy environment variables for startup profiling
perftools-menu-more-actions-copy-for-perf-tests = Copy parameters for performance tests
## These messages are descriptions of the threads that can be enabled for the profiler. ## These messages are descriptions of the threads that can be enabled for the profiler.
perftools-thread-gecko-main = perftools-thread-gecko-main =

View file

@ -0,0 +1,96 @@
/* 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/. */
"use strict";
const {
Component,
} = require("resource://devtools/client/shared/vendor/react.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const {
L10N,
} = require("resource://devtools/client/netmonitor/src/utils/l10n.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const {
connect,
} = require("resource://devtools/client/shared/vendor/react-redux.js");
const {
propertiesEqual,
} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
const { truncateString } = require("resource://devtools/shared/string.js");
const {
MAX_UI_STRING_LENGTH,
} = require("resource://devtools/client/netmonitor/src/constants.js");
const {
getOverriddenUrl,
} = require("resource://devtools/client/netmonitor/src/selectors/index.js");
const UPDATED_FILE_PROPS = ["urlDetails", "waitingTime"];
class RequestListColumnPath extends Component {
static get propTypes() {
return {
item: PropTypes.object.isRequired,
onWaterfallMouseDown: PropTypes.func,
isOverridden: PropTypes.bool.isRequired,
overriddenUrl: PropTypes.string,
};
}
shouldComponentUpdate(nextProps) {
return (
!propertiesEqual(UPDATED_FILE_PROPS, this.props.item, nextProps.item) ||
nextProps.overriddenUrl !== this.props.overriddenUrl
);
}
render() {
const {
item: { urlDetails },
isOverridden,
overriddenUrl,
} = this.props;
const originalFileURL = urlDetails.url;
const decodedFileURL = urlDetails.unicodeUrl;
const ORIGINAL_FILE_URL = L10N.getFormatStr(
"netRequest.originalFileURL.tooltip",
originalFileURL
);
const DECODED_FILE_URL = L10N.getFormatStr(
"netRequest.decodedFileURL.tooltip",
decodedFileURL
);
const requestedPath = urlDetails.path;
const fileToolTip =
originalFileURL === decodedFileURL
? originalFileURL
: ORIGINAL_FILE_URL + "\n\n" + DECODED_FILE_URL;
// Build extra content for the title if the request is overridden.
const overrideTitle = isOverridden ? `${overriddenUrl}` : "";
return dom.td(
{
className: "requests-list-column requests-list-path",
title:
truncateString(fileToolTip, MAX_UI_STRING_LENGTH) + overrideTitle,
},
dom.div({}, truncateString(requestedPath, MAX_UI_STRING_LENGTH))
);
}
}
module.exports = connect(
(state, props) => {
const overriddenUrl = getOverriddenUrl(state, props.item.urlDetails?.url);
return {
isOverridden: !!overriddenUrl,
overriddenUrl,
};
},
{},
undefined,
{ storeKey: "toolbox-store" }
)(RequestListColumnPath);

View file

@ -26,6 +26,7 @@ const {
RequestListColumnCookies, RequestListColumnCookies,
RequestListColumnDomain, RequestListColumnDomain,
RequestListColumnFile, RequestListColumnFile,
RequestListColumnPath,
RequestListColumnMethod, RequestListColumnMethod,
RequestListColumnProtocol, RequestListColumnProtocol,
RequestListColumnRemoteIP, RequestListColumnRemoteIP,
@ -65,6 +66,11 @@ loader.lazyGetter(this, "RequestListColumnFile", function () {
require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnFile.js") require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnFile.js")
); );
}); });
loader.lazyGetter(this, "RequestListColumnPath", function () {
return createFactory(
require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnPath.js")
);
});
loader.lazyGetter(this, "RequestListColumnUrl", function () { loader.lazyGetter(this, "RequestListColumnUrl", function () {
return createFactory( return createFactory(
require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnUrl.js") require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnUrl.js")
@ -199,6 +205,11 @@ const COLUMN_COMPONENTS = [
ColumnComponent: RequestListColumnFile, ColumnComponent: RequestListColumnFile,
props: ["onWaterfallMouseDown", "slowLimit"], props: ["onWaterfallMouseDown", "slowLimit"],
}, },
{
column: "path",
ColumnComponent: RequestListColumnPath,
props: ["onWaterfallMouseDown"],
},
{ {
column: "url", column: "url",
ColumnComponent: RequestListColumnUrl, ColumnComponent: RequestListColumnUrl,

View file

@ -11,6 +11,7 @@ DevToolsModules(
"RequestListColumnInitiator.js", "RequestListColumnInitiator.js",
"RequestListColumnMethod.js", "RequestListColumnMethod.js",
"RequestListColumnOverride.js", "RequestListColumnOverride.js",
"RequestListColumnPath.js",
"RequestListColumnPriority.js", "RequestListColumnPriority.js",
"RequestListColumnProtocol.js", "RequestListColumnProtocol.js",
"RequestListColumnRemoteIP.js", "RequestListColumnRemoteIP.js",

View file

@ -299,6 +299,10 @@ const HEADERS = [
name: "file", name: "file",
canFilter: false, canFilter: false,
}, },
{
name: "path",
canFilter: false,
},
{ {
name: "url", name: "url",
canFilter: true, canFilter: true,

View file

@ -33,6 +33,7 @@ const cols = {
method: true, method: true,
domain: true, domain: true,
file: true, file: true,
path: false,
url: false, url: false,
protocol: false, protocol: false,
scheme: false, scheme: false,

View file

@ -292,6 +292,18 @@ function getUrlScheme(url) {
return protocol.replace(":", "").toLowerCase(); return protocol.replace(":", "").toLowerCase();
} }
/**
* Helpers for getting the full path portion of a url.
*
* @param {string|URL} url - unvalidated url string or URL instance
* @return {string} string path of a url
*/
function getUrlPath(url) {
const href = getUrlProperty(url, "href");
const origin = getUrlProperty(url, "origin");
return href.replace(origin, "");
}
/** /**
* Extract several details fields from a URL at once. * Extract several details fields from a URL at once.
*/ */
@ -302,6 +314,7 @@ function getUrlDetails(url) {
const hostname = getUrlHostName(urlObject); const hostname = getUrlHostName(urlObject);
const unicodeUrl = getUnicodeUrl(urlObject); const unicodeUrl = getUnicodeUrl(urlObject);
const scheme = getUrlScheme(urlObject); const scheme = getUrlScheme(urlObject);
const path = getUrlPath(urlObject);
// If the hostname contains unreadable ASCII characters, we need to do the // If the hostname contains unreadable ASCII characters, we need to do the
// following two steps: // following two steps:
@ -338,6 +351,7 @@ function getUrlDetails(url) {
unicodeUrl, unicodeUrl,
isLocal, isLocal,
url, url,
path,
}; };
} }

View file

@ -181,6 +181,8 @@ skip-if = [
["browser_net_column_headers_tooltips.js"] ["browser_net_column_headers_tooltips.js"]
["browser_net_column_path.js"]
["browser_net_column_slow-request-indicator.js"] ["browser_net_column_slow-request-indicator.js"]
["browser_net_columns_last_column.js"] ["browser_net_columns_last_column.js"]

View file

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests for path column. Note that the column
* header is visible only if there are requests in the list.
*/
add_task(async function () {
const { monitor, tab } = await initNetMonitor(SIMPLE_URL, {
requestCount: 1,
});
const { document } = monitor.panelWin;
info("Starting test... ");
const onNetworkEvents = waitForNetworkEvents(monitor, 2);
await reloadBrowser();
await ContentTask.spawn(tab.linkedBrowser, null, () => {
content.wrappedJSObject.fetch("data:text/plain,some_text");
});
await onNetworkEvents;
await showColumn(monitor, "path");
const pathColumn = document.querySelector(`.requests-list-path`);
const requestList = document.querySelectorAll(
".network-monitor .request-list-item"
);
ok(pathColumn, "Path column should be visible");
is(
requestList[0].querySelector(".requests-list-path div:first-child")
.textContent,
"/browser/devtools/client/netmonitor/test/html_simple-test-page.html",
"Path content should contain the request url without origin"
);
is(
requestList[1].querySelector(".requests-list-path div:first-child")
.textContent,
"data:text/plain,some_text",
"Path content should contain the data url"
);
await teardown(monitor);
});

View file

@ -186,15 +186,6 @@ export type ReceiveProfile = (
getSymbolTableCallback: GetSymbolTableCallback getSymbolTableCallback: GetSymbolTableCallback
) => void; ) => void;
/**
* This is the type signature for a function to restart the browser with a given
* environment variable. Currently only implemented for the popup.
*/
export type RestartBrowserWithEnvironmentVariable = (
envName: string,
value: string
) => void;
/** /**
* This is the type signature for the event listener that's called once the * This is the type signature for the event listener that's called once the
* profile has been obtained. * profile has been obtained.
@ -397,6 +388,12 @@ export interface PerformancePref {
* button in the customization palette. * button in the customization palette.
*/ */
PopupFeatureFlag: "devtools.performance.popup.feature-flag"; PopupFeatureFlag: "devtools.performance.popup.feature-flag";
/**
* This preference controls whether about:profiling contains some Firefox
* developer-specific options. For example when true the "more actions" menu
* contains items to copy parameters to use with mach try perf.
*/
AboutProfilingHasDeveloperOptions: "devtools.performance.aboutprofiling.has-developer-options";
} }
/* The next 2 types bring some duplication from gecko.d.ts, but this is simpler /* The next 2 types bring some duplication from gecko.d.ts, but this is simpler

View file

@ -4,22 +4,8 @@
// @ts-check // @ts-check
/** /**
* @template P
* @typedef {import("react-redux").ResolveThunks<P>} ResolveThunks<P>
*/
/**
* @typedef {Object} StateProps
* @property {boolean?} isSupportedPlatform
* @property {PageContext} pageContext
* @property {string | null} promptEnvRestart
* @property {(() => void) | undefined} openRemoteDevTools
*/
/**
* @typedef {StateProps} Props
* @typedef {import("../../@types/perf").State} StoreState * @typedef {import("../../@types/perf").State} StoreState
* @typedef {import("../../@types/perf").PageContext} PageContext * @typedef {import("../../@types/perf").PerformancePref} PerformancePref
*/ */
"use strict"; "use strict";
@ -27,6 +13,9 @@
const { const {
PureComponent, PureComponent,
createFactory, createFactory,
createElement: h,
Fragment,
createRef,
} = require("resource://devtools/client/shared/vendor/react.mjs"); } = require("resource://devtools/client/shared/vendor/react.mjs");
const { const {
connect, connect,
@ -51,6 +40,209 @@ const {
restartBrowserWithEnvironmentVariable, restartBrowserWithEnvironmentVariable,
} = require("resource://devtools/client/performance-new/shared/browser.js"); } = require("resource://devtools/client/performance-new/shared/browser.js");
/** @type {PerformancePref["AboutProfilingHasDeveloperOptions"]} */
const ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF =
"devtools.performance.aboutprofiling.has-developer-options";
/**
* This function encodes the parameter so that it can be used as an environment
* variable value.
* Basically it uses single quotes, but replacing any single quote by '"'"':
* 1. close the previous single-quoted string,
* 2. add a double-quoted string containing only a single quote
* 3. start a single-quoted string again.
* so that it's properly retained.
*
* @param {string} value
* @returns {string}
*/
function encodeShellValue(value) {
return "'" + value.replaceAll("'", `'"'"'`) + "'";
}
/**
* @typedef {import("../../@types/perf").RecordingSettings} RecordingSettings
*
* @typedef {Object} ButtonStateProps
* @property {RecordingSettings} recordingSettings
*
* @typedef {ButtonStateProps} ButtonProps
*
* @typedef {Object} ButtonState
* @property {boolean} hasDeveloperOptions
*/
/**
* This component implements the button that triggers the menu that makes it
* possible to show more actions.
* @extends {React.PureComponent<ButtonProps, ButtonState>}
*/
class MoreActionsButtonImpl extends PureComponent {
state = {
hasDeveloperOptions: Services.prefs.getBoolPref(
ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
false
),
};
componentDidMount() {
Services.prefs.addObserver(
ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
this.onHasDeveloperOptionsPrefChanges
);
}
componentWillUnmount() {
Services.prefs.removeObserver(
ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
this.onHasDeveloperOptionsPrefChanges
);
}
_menuRef = createRef();
onHasDeveloperOptionsPrefChanges = () => {
this.setState({
hasDeveloperOptions: Services.prefs.getBoolPref(
ABOUTPROFILING_HAS_DEVELOPER_OPTIONS_PREF,
false
),
});
};
/**
* See the part "Showing the menu" in
* https://searchfox.org/mozilla-central/rev/4bacdbc8ac088f2ee516daf42c535fab2bc24a04/toolkit/content/widgets/panel-list/README.stories.md
* Strangely our React's type doesn't have the `detail` property for
* MouseEvent, so we're defining it manually.
* @param {React.MouseEvent & { detail: number }} e
*/
handleClickOrMousedown = e => {
// The menu is toggled either for a "mousedown", or for a keyboard enter
// (which triggers a "click" event with 0 clicks (detail == 0)).
if (this._menuRef.current && (e.type == "mousedown" || e.detail === 0)) {
this._menuRef.current.toggle(e.nativeEvent, e.currentTarget);
}
};
/**
* @returns {Record<string, string>}
*/
getEnvironmentVariablesForStartupFromRecordingSettings = () => {
const { interval, entries, threads, features } =
this.props.recordingSettings;
return {
MOZ_PROFILER_STARTUP: "1",
MOZ_PROFILER_STARTUP_INTERVAL: String(interval),
MOZ_PROFILER_STARTUP_ENTRIES: String(entries),
MOZ_PROFILER_STARTUP_FEATURES: features.join(","),
MOZ_PROFILER_STARTUP_FILTERS: threads.join(","),
};
};
onRestartWithProfiling = () => {
const envVariables =
this.getEnvironmentVariablesForStartupFromRecordingSettings();
restartBrowserWithEnvironmentVariable(envVariables);
};
onCopyEnvVariables = async () => {
const envVariables =
this.getEnvironmentVariablesForStartupFromRecordingSettings();
const envString = Object.entries(envVariables)
.map(([key, value]) => `${key}=${encodeShellValue(value)}`)
.join(" ");
await navigator.clipboard.writeText(envString);
};
onCopyTestVariables = async () => {
const { interval, entries, threads, features } =
this.props.recordingSettings;
const envString =
"--gecko-profile" +
` --gecko-profile-interval ${interval}` +
` --gecko-profile-entries ${entries}` +
` --gecko-profile-features ${encodeShellValue(features.join(","))}` +
` --gecko-profile-threads ${encodeShellValue(threads.join(","))}`;
await navigator.clipboard.writeText(envString);
};
render() {
return h(
Fragment,
null,
Localized(
{
id: "perftools-menu-more-actions-button",
attrs: { title: true },
},
h("moz-button", {
iconsrc: "chrome://global/skin/icons/more.svg",
"aria-expanded": "false",
"aria-haspopup": "menu",
onClick: this.handleClickOrMousedown,
onMouseDown: this.handleClickOrMousedown,
})
),
h(
"panel-list",
{ ref: this._menuRef },
Localized(
{ id: "perftools-menu-more-actions-restart-with-profiling" },
h(
"panel-item",
{ onClick: this.onRestartWithProfiling },
"Restart Firefox with startup profiling enabled"
)
),
this.state.hasDeveloperOptions
? Localized(
{ id: "perftools-menu-more-actions-copy-for-startup" },
h(
"panel-item",
{ onClick: this.onCopyEnvVariables },
"Copy environment variables for startup profiling"
)
)
: null,
this.state.hasDeveloperOptions
? Localized(
{ id: "perftools-menu-more-actions-copy-for-perf-tests" },
h(
"panel-item",
{ onClick: this.onCopyTestVariables },
"Copy parameters for mach try perf"
)
)
: null
)
);
}
}
/**
* @param {StoreState} state
* @returns {ButtonStateProps}
*/
function mapStateToButtonProps(state) {
return {
recordingSettings: selectors.getRecordingSettings(state),
};
}
const MoreActionsButton = connect(mapStateToButtonProps)(MoreActionsButtonImpl);
/**
* @typedef {import("../../@types/perf").PageContext} PageContext
*
* @typedef {Object} StateProps
* @property {boolean?} isSupportedPlatform
* @property {PageContext} pageContext
* @property {string | null} promptEnvRestart
* @property {(() => void) | undefined} openRemoteDevTools
*
* @typedef {StateProps} Props
*/
/** /**
* This is the top level component for the about:profiling page. It shares components * This is the top level component for the about:profiling page. It shares components
* with the popup and DevTools page. * with the popup and DevTools page.
@ -58,16 +250,6 @@ const {
* @extends {React.PureComponent<Props>} * @extends {React.PureComponent<Props>}
*/ */
class AboutProfiling extends PureComponent { class AboutProfiling extends PureComponent {
handleRestart = () => {
const { promptEnvRestart } = this.props;
if (!promptEnvRestart) {
throw new Error(
"handleRestart() should only be called when promptEnvRestart exists."
);
}
restartBrowserWithEnvironmentVariable(promptEnvRestart, "1");
};
render() { render() {
const { const {
isSupportedPlatform, isSupportedPlatform,
@ -97,7 +279,11 @@ class AboutProfiling extends PureComponent {
{ {
className: "perf-photon-button perf-photon-button-micro", className: "perf-photon-button perf-photon-button-micro",
type: "button", type: "button",
onClick: this.handleRestart, onClick: () => {
restartBrowserWithEnvironmentVariable({
[promptEnvRestart]: "1",
});
},
}, },
Localized({ id: "perftools-button-restart" }) Localized({ id: "perftools-button-restart" })
) )
@ -121,9 +307,13 @@ class AboutProfiling extends PureComponent {
div( div(
{ className: "perf-intro" }, { className: "perf-intro" },
h1( div(
{ className: "perf-intro-title" }, { className: "perf-intro-title-bar" },
Localized({ id: "perftools-intro-title" }) h1(
{ className: "perf-intro-title" },
Localized({ id: "perftools-intro-title" })
),
h(MoreActionsButton)
), ),
div( div(
{ className: "perf-intro-row" }, { className: "perf-intro-row" },

View file

@ -14,7 +14,6 @@
* @typedef {import("../@types/perf").PreferenceFront} PreferenceFront * @typedef {import("../@types/perf").PreferenceFront} PreferenceFront
* @typedef {import("../@types/perf").PerformancePref} PerformancePref * @typedef {import("../@types/perf").PerformancePref} PerformancePref
* @typedef {import("../@types/perf").RecordingSettings} RecordingSettings * @typedef {import("../@types/perf").RecordingSettings} RecordingSettings
* @typedef {import("../@types/perf").RestartBrowserWithEnvironmentVariable} RestartBrowserWithEnvironmentVariable
* @typedef {import("../@types/perf").GetActiveBrowserID} GetActiveBrowserID * @typedef {import("../@types/perf").GetActiveBrowserID} GetActiveBrowserID
* @typedef {import("../@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile * @typedef {import("../@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile
* @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode * @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode
@ -145,10 +144,12 @@ function sharedLibrariesFromProfile(profile) {
/** /**
* Restarts the browser with a given environment variable set to a value. * Restarts the browser with a given environment variable set to a value.
* *
* @type {RestartBrowserWithEnvironmentVariable} * @param {Record<string, string>} env
*/ */
function restartBrowserWithEnvironmentVariable(envName, value) { function restartBrowserWithEnvironmentVariable(env) {
Services.env.set(envName, value); for (const [envName, envValue] of Object.entries(env)) {
Services.env.set(envName, envValue);
}
Services.startup.quit( Services.startup.quit(
Services.startup.eForceQuit | Services.startup.eRestart Services.startup.eForceQuit | Services.startup.eRestart

View file

@ -10,7 +10,6 @@
* @typedef {import("../@types/perf").InitializedValues} InitializedValues * @typedef {import("../@types/perf").InitializedValues} InitializedValues
* @typedef {import("../@types/perf").PerfFront} PerfFront * @typedef {import("../@types/perf").PerfFront} PerfFront
* @typedef {import("../@types/perf").ReceiveProfile} ReceiveProfile * @typedef {import("../@types/perf").ReceiveProfile} ReceiveProfile
* @typedef {import("../@types/perf").RestartBrowserWithEnvironmentVariable} RestartBrowserWithEnvironmentVariable
* @typedef {import("../@types/perf").PageContext} PageContext * @typedef {import("../@types/perf").PageContext} PageContext
* @typedef {import("../@types/perf").Presets} Presets * @typedef {import("../@types/perf").Presets} Presets
*/ */

View file

@ -26,6 +26,8 @@ support-files = [
["browser_aboutprofiling-interval.js"] ["browser_aboutprofiling-interval.js"]
["browser_aboutprofiling-more-actions-menu.js"]
["browser_aboutprofiling-presets-custom.js"] ["browser_aboutprofiling-presets-custom.js"]
["browser_aboutprofiling-presets.js"] ["browser_aboutprofiling-presets.js"]

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