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]]
name = "h2"
version = "0.3.22"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",

View file

@ -219,6 +219,11 @@ bool SelectionManager::SelectionRangeChanged(SelectionType aType,
dom::Document* doc = start->OwnerDoc();
MOZ_ASSERT(doc);
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);
if (!acc) {
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) +
presContext->AppUnitsToDevPixels(offset.y);
if (StaticPrefs::dom_popup_experimental()) {
// This isn't needed once bug 1924790 is fixed.
tcElm->OwnerDoc()->NotifyUserGestureActivation();
}
// This isn't needed once bug 1924790 is fixed.
tcElm->OwnerDoc()->NotifyUserGestureActivation();
// XUL is just desktop, so there is no real reason for senfing touch events.
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,
widget);
if (StaticPrefs::dom_popup_experimental()) {
// This isn't needed once bug 1924790 is fixed.
mContent->OwnerDoc()->NotifyUserGestureActivation();
}
// This isn't needed once bug 1924790 is fixed.
mContent->OwnerDoc()->NotifyUserGestureActivation();
nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, mContent, frame, presShell,
widget);
nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, mContent, frame, presShell,

View file

@ -381,19 +381,6 @@ between
<div id="popover2" popover>popover2</div>
<button id="toggle5">toggle5</button>
</template></div>
<script>
const toggle1 = document.getElementById("toggle1");
const popover1 = document.getElementById("popover1");
toggle1.popoverTargetElement = popover1;
const toggle3 = document.getElementById("toggle3");
const shadow = document.getElementById("shadowHost").shadowRoot;
const toggle4 = shadow.getElementById("toggle4");
const popover2 = shadow.getElementById("popover2");
toggle3.popoverTargetElement = popover2;
toggle4.popoverTargetElement = popover2;
const toggle5 = shadow.getElementById("toggle5");
toggle5.popoverTargetElement = popover1;
</script>
`,
async function testPopoverIdl(browser, docAcc) {
// No popover is showing, so there shouldn't be any details relations.
@ -465,7 +452,23 @@ between
await hidden;
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,
...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) {
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
const srcURL = new URL(`http://example.net/document-builder.sjs`);
@ -418,6 +424,11 @@ function snippetToURL(doc, options = {}) {
if (gIsIframe) {
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(
@ -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, {
script: "Common.sys.mjs",
symbol: "CommonUtils",
@ -670,6 +696,22 @@ function accessibleTask(doc, task, options = {}) {
* - {CacheDomain} cacheDomains
* The set of cache domains that should be present at the start of the
* 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 = {}) {
const {

View file

@ -170,18 +170,7 @@ add_task(async function testTextFragmentSamePage() {
* Test custom highlight mutations.
*/
addAccessibleTask(
`
${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>
`,
snippet,
async function testCustomHighlightMutations(browser, docAcc) {
info("Checking initial highlight");
const first = findAccessibleChildByID(docAcc, "first");
@ -272,41 +261,26 @@ ${snippet}
});
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.
*/
addAccessibleTask(
`
${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>
`,
snippet,
async function testCustomHighlightTypes(browser, docAcc) {
const first = findAccessibleChildByID(docAcc, "first");
ok(
@ -345,85 +319,42 @@ ${snippet}
"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.
*/
addAccessibleTask(
`
${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>
`,
snippet,
async function testCustomHighlightOverlapping(browser, docAcc) {
const first = findAccessibleChildByID(docAcc, "first");
ok(
@ -484,5 +415,78 @@ ${snippet}
"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
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.enabled", false);
// Current new tab page background images.
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.duration-ms", 200);
pref("sidebar.animation.expand-on-hover.duration-ms", 400);
// The sidebar.main.tools pref cannot 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 instructions.
pref("sidebar.main.tools", "aichat,syncedtabs,history");
// This pref is used to store user customized tools in the sidebar launcher and shouldn't be changed.
// See https://firefox-source-docs.mozilla.org/browser/components/sidebar/docs/index.html for ways
// you can introduce a new tool to the sidebar launcher.
pref("sidebar.main.tools", "");
pref("sidebar.verticalTabs", false);
pref("sidebar.visibility", "always-show");
// 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;
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 awaitLoad = ContentTaskUtils.waitForEvent(iframe, "load", false);
iframe.src = "https://example.org/";

View file

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

View file

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

View file

@ -3698,7 +3698,7 @@ BrowserGlue.prototype = {
_migrateUI() {
// Use an increasing number to keep track of the current migration state.
// Completely unrelated to the current Firefox release number.
const UI_VERSION = 152;
const UI_VERSION = 153;
const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
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.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
},

View file

@ -1894,10 +1894,8 @@ const MESSAGES = () => {
{
type: "action",
label: {
raw: {
string_id:
"shopping-callout-not-opted-in-integrated-reminder-do-not-show",
},
string_id:
"shopping-callout-not-opted-in-integrated-reminder-do-not-show",
},
action: {
type: "SET_PREF",
@ -1914,10 +1912,8 @@ const MESSAGES = () => {
{
type: "action",
label: {
raw: {
string_id:
"shopping-callout-not-opted-in-integrated-reminder-show-fewer",
},
string_id:
"shopping-callout-not-opted-in-integrated-reminder-show-fewer",
},
action: {
type: "MULTI_ACTION",
@ -1954,10 +1950,8 @@ const MESSAGES = () => {
{
type: "action",
label: {
raw: {
string_id:
"shopping-callout-not-opted-in-integrated-reminder-manage-settings",
},
string_id:
"shopping-callout-not-opted-in-integrated-reminder-manage-settings",
},
action: {
type: "OPEN_ABOUT_PAGE",
@ -2150,10 +2144,8 @@ const MESSAGES = () => {
{
type: "action",
label: {
raw: {
string_id:
"shopping-callout-not-opted-in-integrated-reminder-do-not-show",
},
string_id:
"shopping-callout-not-opted-in-integrated-reminder-do-not-show",
},
action: {
type: "SET_PREF",
@ -2170,10 +2162,8 @@ const MESSAGES = () => {
{
type: "action",
label: {
raw: {
string_id:
"shopping-callout-not-opted-in-integrated-reminder-show-fewer",
},
string_id:
"shopping-callout-not-opted-in-integrated-reminder-show-fewer",
},
action: {
type: "MULTI_ACTION",
@ -2210,10 +2200,8 @@ const MESSAGES = () => {
{
type: "action",
label: {
raw: {
string_id:
"shopping-callout-not-opted-in-integrated-reminder-manage-settings",
},
string_id:
"shopping-callout-not-opted-in-integrated-reminder-manage-settings",
},
action: {
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");
Services.prefs.setStringPref("sidebar.main.tools", "aichat");
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");

View file

@ -341,6 +341,7 @@ newtab:
- interaction
notification_emails:
- nbarrett@mozilla.com
- mcrawford@mozilla.com
expires: never
extra_keys:
selected_wallpaper:
@ -353,28 +354,6 @@ newtab:
description: >
Whether or not user had a previously set wallpaper
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:
description: >
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);
}
#profile-content h2[data-l10n-id="edit-profile-page-header"] {
margin-block: 0;
}
#header-avatar {
-moz-context-properties: fill, stroke;
width: var(--header-avatar-size);
height: var(--header-avatar-size);
border-radius: var(--border-radius-circle);
margin-inline-end: var(--space-xxlarge);
}
#profile-name-area {
display: flex;
flex-direction: column;
gap: var(--space-xsmall);
margin-block: 0 var(--space-large);
}
#profile-name-area label {
margin-bottom: var(--space-xsmall);
}
#profile-name {
@ -68,8 +78,18 @@
color: var(--icon-color-success);
}
#themes::part(inputs) {
margin-top: var(--space-medium);
}
#avatars::part(inputs) {
margin-top: var(--space-medium);
}
#avatars::part(inputs),
#themes::part(inputs) {
flex-direction: row;
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>
<a
is="moz-support-link"
support-page="profiles"
support-page="profile-management"
data-l10n-id="new-profile-page-learn-more"
></a>
</p>

View file

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

View file

@ -52,7 +52,8 @@ new-profile-card {
#delete-profile-card {
display: flex;
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) {
flex-direction: column;

View file

@ -20,12 +20,37 @@ export class ProfilesThemeCard extends MozLitElement {
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() {
super.updated();
this.backgroundImg.style.fill = this.theme.chromeColor;
this.backgroundImg.style.stroke = this.theme.toolbarColor;
this.imgHolder.style.backgroundColor = this.theme.contentColor;
this.updateThemeImage();
}
render() {
@ -42,9 +67,7 @@ export class ProfilesThemeCard extends MozLitElement {
<moz-card class="theme-card">
<div class="theme-content">
<div class="img-holder">
<img
src="chrome://browser/content/profiles/assets/theme-selector-background.svg"
/>
<img />
</div>
<div
class="theme-name"

View file

@ -36,6 +36,7 @@ head = "../unit/head.js head.js"
run-if = ["os != 'linux'"] # Linux clients cannot remote themselves.
["browser_preferences.js"]
fail-if = ["a11y_checks"] # Bug 1955503
["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) &&
this.isProductPage;
let canShowKeepClosedMessage =
this.showingKeepClosedMessage &&
RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, true);
this.showingKeepClosedMessage && this.isProductPage;
if (canShowNotificationCard) {
return this.newPositionNotificationCardTemplate();
@ -730,7 +729,9 @@ export class ShoppingContainer extends MozLitElement {
if (
yetToSeeNotificationCard ||
!RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false)
!RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false) ||
this.showOnboarding ||
!this.isProductPage
) {
return false;
}

View file

@ -5,6 +5,10 @@
/* 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";
add_setup(async function setup() {

View file

@ -4,6 +4,10 @@
"use strict";
/* 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 NON_PDP_PAGE = "about:about";

View file

@ -4,6 +4,12 @@
"use strict";
/* 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() {
await withReviewCheckerSidebar(async _args => {
@ -228,3 +234,93 @@ add_task(
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 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 = {};
ChromeUtils.defineESModuleGetters(lazy, {
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
@ -40,7 +44,15 @@ XPCOMUtils.defineLazyPreferenceGetter(
() => 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 = {
/**
@ -84,7 +96,7 @@ export const SidebarManager = {
}
};
setPref("nimbus", slug);
["main.tools", "revamp", "verticalTabs", "visibility"].forEach(pref =>
["revamp", "verticalTabs", "visibility"].forEach(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
* to the sidebar.main.tools pref one time as a way of introducing a new tool
* to the launcher without overwriting what a user had previously customized.
* Prepopulates default tools for new sidebar users and appends any new tools defined
* on the sidebar.newTool.migration pref branch to the sidebar.main.tools pref.
*/
updateDefaultTools() {
if (!lazy.sidebarRevampEnabled) {
return;
}
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(
"sidebar.newTool.migration."
)) {
@ -136,8 +152,7 @@ export const SidebarManager = {
let options = JSON.parse(Services.prefs.getStringPref(pref));
let newTool = pref.split(".")[3];
// ensure we only add this tool once
if (options?.alreadyShown || lazy.sidebarTools.includes(newTool)) {
if (options?.alreadyShown) {
continue;
}
@ -155,7 +170,10 @@ export const SidebarManager = {
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;
Services.prefs.setStringPref(pref, JSON.stringify(options));
} catch (ex) {

View file

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

View file

@ -9,12 +9,14 @@ The new sidebar builds on existing legacy sidebar code treating ``browser-sideba
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.
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.
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_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"]
@ -66,15 +60,6 @@ run-if = ["os == 'mac'"] # Mac only feature
["browser_syncedtabs_sidebar.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"]

View file

@ -66,13 +66,7 @@ add_task(async function test_customize_sidebar_actions() {
4,
"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) {
let toolDisabledInitialState = !toolInput.checked;
toolInput.click();

View file

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

View file

@ -9,11 +9,9 @@ const { ExperimentFakes } = ChromeUtils.importESModule(
* Check that enrolling into sidebar experiments sets user prefs
*/
add_task(async function test_nimbus_user_prefs() {
const main = "sidebar.main.tools";
const nimbus = "sidebar.nimbus";
const vertical = "sidebar.verticalTabs";
Assert.ok(!Services.prefs.prefHasUserValue(main), "No user main pref yet");
Assert.ok(
!Services.prefs.prefHasUserValue(nimbus),
"No user nimbus pref yet"
@ -22,55 +20,13 @@ add_task(async function test_nimbus_user_prefs() {
let cleanup = await ExperimentFakes.enrollWithFeatureConfig({
featureId: "sidebar",
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,
},
});
Assert.ok(!Services.prefs.prefHasUserValue(main), "main pref no longer set");
Assert.notEqual(
Services.prefs.getStringPref(nimbus),
nimbusValue,
"nimbus pref changed"
);
const nimbusValue = Services.prefs.getStringPref(nimbus);
Assert.ok(nimbusValue, "Set some nimbus slug");
Assert.ok(Services.prefs.getBoolPref(vertical), "vertical set to true");
Assert.ok(
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() {
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(sidebar), "sidebar is default");
const cleanup = await ExperimentFakes.enrollmentHelper(
ExperimentFakes.recipe("foo", {
@ -137,10 +91,6 @@ add_task(async function test_nimbus_multi_feature() {
{
slug: "variant",
features: [
{
featureId: "sidebar",
value: { "main.tools": "syncedtabs,history" },
},
{
featureId: "chatbot",
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(sidebar), "sidebar user pref set");
cleanup();
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(sidebar);
Services.prefs.clearUserPref("browser.ml.chat.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 { document } = win;
const sidebar = document.querySelector("sidebar-main");
ok(sidebar, "Sidebar is shown.");
await sidebar.updateComplete;
is(
Services.prefs.getStringPref("sidebar.main.tools"),
"aichat,syncedtabs,history",
"aichat,syncedtabs,history,bookmarks",
"Default tools pref unchanged"
);
@ -35,11 +34,14 @@ add_task(async function test_tools_prefs() {
input => input.name === "viewBookmarksSidebar"
);
ok(
!bookmarksInput.checked,
"The bookmarks input is unchecked initially as Bookmarks are disabled initially."
bookmarksInput.checked,
"The bookmarks input is checked initially as Bookmarks is a default tool."
);
for (const toolInput of customizeComponent.toolInputs) {
let toolDisabledInitialState = !toolInput.checked;
if (toolInput.name == "viewBookmarksSidebar") {
continue;
}
toolInput.click();
await BrowserTestUtils.waitForCondition(
() => {
@ -67,7 +69,7 @@ add_task(async function test_tools_prefs() {
is(
updatedTools,
"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);
@ -143,11 +145,13 @@ add_task(async function test_tool_pref_change() {
});
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");
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 () => {
await SpecialPowers.pushPrefEnv({
set: [
["sidebar.main.tools", "syncedtabs,history"],
["sidebar.main.tools", "syncedtabs,bookmarks,history"],
["sidebar.newTool.migration.bookmarks", "{}"],
["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() {
await SpecialPowers.pushPrefEnv({
set: [
["sidebar.main.tools", "syncedtabs,history"],
["sidebar.newTool.migration.bookmarks", "{}"],
],
});
const sidebar = document.querySelector("sidebar-main");
let tools = Services.prefs.getStringPref("sidebar.main.tools");
is(

View file

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

View file

@ -678,7 +678,7 @@ export class SmartTabGroupingManager {
const UPDATE_THRESHOLD_PERCENTAGE = 0.5;
const ONE_MB = 1024 * 1024;
const START_THRESHOLD_BYTES = ONE_MB;
const START_THRESHOLD_BYTES = ONE_MB * 0.2;
const mutliProgressAggregator = new lazy.MultiProgressAggregator({
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 = () => {
if (tab.pinned && this.tabContainer.verticalMode) {
return this.tabContainer.verticalPinnedTabsContainer;

View file

@ -1017,8 +1017,29 @@
}
#setMovingTabMode(movingTab) {
if (movingTab == this.#isMovingTab()) {
return;
}
this.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() {
@ -1137,7 +1158,10 @@
}
}
let shouldTranslate = !gReduceMotion && !shouldCreateGroupOnDrop;
let shouldTranslate =
!gReduceMotion &&
!shouldCreateGroupOnDrop &&
!isTabGroupLabel(draggedTab);
if (this.#isContainerVerticalPinnedExpanded(draggedTab)) {
shouldTranslate &&=
(oldTranslateX && oldTranslateX != newTranslateX) ||
@ -1161,6 +1185,7 @@
} else {
gBrowser.moveTabsAfter(movingTabs, dropElement);
}
this.#expandGroupOnDrop(draggedTab);
};
if (shouldTranslate) {
@ -1313,7 +1338,6 @@
}
if (draggedTab) {
this.#expandGroupOnDrop(draggedTab);
delete draggedTab._dragData;
}
}
@ -1336,17 +1360,13 @@
if (
dt.mozUserCancelled ||
dt.dropEffect != "none" ||
!Services.prefs.getBoolPref("browser.tabs.allowTabDetach") ||
this._isCustomizing
) {
delete draggedTab._dragData;
return;
}
// Check if tab detaching is enabled
if (!Services.prefs.getBoolPref("browser.tabs.allowTabDetach")) {
return;
}
// Disable detach within the browser toolbox
let [tabAxisPos, tabAxisStart, tabAxisEnd] = this.verticalMode
? [event.screenY, window.screenY, window.screenY + window.outerHeight]

View file

@ -61,7 +61,14 @@ let currentTab = () =>
lazy.BrowserWindowTracker.getTopWindow()?.gBrowser.selectedTab;
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 = {
@ -83,10 +90,10 @@ const DEFAULT_ACTIONS = {
},
clear: {
l10nCommands: [
"quickactions-cmd-clearhistory",
"quickactions-clearhistory",
"quickactions-cmd-clearrecenthistory",
"quickactions-clearrecenthistory",
],
label: "quickactions-clearhistory",
label: "quickactions-clearrecenthistory",
onPick: () => {
lazy.BrowserWindowTracker.getTopWindow()
.document.getElementById("Tools:Sanitize")
@ -105,6 +112,22 @@ const DEFAULT_ACTIONS = {
label: "quickactions-extensions",
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: {
l10nCommands: ["quickactions-cmd-inspector"],
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/"
);
EventUtils.synthesizeKey("KEY_Tab", {}, window);
EventUtils.synthesizeKey("KEY_Tab", {}, window);
assertAccessibilityWhenSelected("viewsource");
EventUtils.synthesizeKey("KEY_Enter", {}, window);
const viewSourceTab = await onLoad;
@ -136,8 +137,10 @@ add_task(async function test_viewsource() {
});
Assert.equal(
hasQuickActions(window),
false,
window.document.querySelector(
`.urlbarView-action-btn[data-action=viewsource]`
),
null,
"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 = [
{
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",
uri: "about:addons",
@ -52,7 +71,7 @@ let COMMANDS_TESTS = [
await onLoad;
},
uri: "about:addons",
isNewTab: true,
loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=discover]"),
},
{
@ -70,7 +89,7 @@ let COMMANDS_TESTS = [
await onLoad;
},
uri: "about:addons",
isNewTab: true,
loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=plugin]"),
},
{
@ -88,7 +107,7 @@ let COMMANDS_TESTS = [
await onLoad;
},
uri: "about:addons",
isNewTab: true,
loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=extension]"),
},
{
@ -106,7 +125,7 @@ let COMMANDS_TESTS = [
await onLoad;
},
uri: "about:addons",
isNewTab: true,
loadType: LOAD_TYPE.NEW_TAB,
testFun: async () => isSelected("button[name=theme]"),
},
];
@ -119,7 +138,7 @@ let isSelected = async selector =>
});
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`);
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
@ -128,9 +147,10 @@ add_task(async function test_pages() {
await setup();
}
let onLoad = isNewTab
? BrowserTestUtils.waitForNewTab(gBrowser, uri, true)
: BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
let onLoad =
loadType == LOAD_TYPE.NEW_TAB
? BrowserTestUtils.waitForNewTab(gBrowser, uri, true)
: BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
@ -139,14 +159,15 @@ add_task(async function test_pages() {
EventUtils.synthesizeKey("KEY_Tab", {}, window);
EventUtils.synthesizeKey("KEY_Enter", {}, window);
const newTab = await onLoad;
const newTab =
loadType == LOAD_TYPE.PRE_LOADED ? gBrowser.selectedTab : await onLoad;
Assert.ok(
await testFun(),
`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(tab);

View file

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

View file

@ -30,6 +30,8 @@ skip-if = [
["browser_autofill_address_housenumber.js"]
["browser_autofill_address_level.js"]
["browser_autofill_address_select.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_tel_fields.js"]
["browser_section_validation_address.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,
fields: [
{ fieldName: "address-level2", reason: "regex-heuristic" },
{ fieldName: "postal-code", reason: "regex-heuristic" },
],
},
{
@ -147,7 +147,7 @@ add_heuristic_tests(
{
invalid: true,
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 = {
topSitesEnabled: prefs["feeds.topsites"],
pocketEnabled: prefs["feeds.section.topstories"],
highlightsEnabled: prefs["feeds.section.highlights"],
showSponsoredTopSitesEnabled: prefs.showSponsoredTopSites,
showSponsoredPocketEnabled: prefs.showSponsored,
showInferredPersonalizationEnabled:
prefs[PREF_INFERRED_PERSONALIZATION_USER],
showRecentSavesEnabled: prefs.showRecentSaves,

View file

@ -5,7 +5,6 @@
import React from "react";
import { actionCreators as ac } from "common/Actions.mjs";
import { SectionsMgmtPanel } from "../SectionsMgmtPanel/SectionsMgmtPanel";
import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
import { WallpapersSection } from "../../WallpapersSection/WallpapersSection";
import { WallpaperCategories } from "../../WallpapersSection/WallpaperCategories";
@ -30,7 +29,7 @@ export class ContentSection extends React.PureComponent {
}
onPreferenceSelect(e) {
// eventSource: TOP_SITES | TOP_STORIES | HIGHLIGHTS | WEATHER
// eventSource: WEATHER | TOP_SITES | TOP_STORIES
const { preference, eventSource } = e.target.dataset;
let value;
if (e.target.nodeName === "SELECT") {
@ -86,7 +85,7 @@ export class ContentSection extends React.PureComponent {
if (isOpen) {
drawerRef.style.marginTop = "var(--space-large)";
} else {
drawerRef.style.marginTop = `-${drawerHeight}px`;
drawerRef.style.marginTop = `-${drawerHeight + 3}px`;
}
}
}
@ -94,14 +93,11 @@ export class ContentSection extends React.PureComponent {
render() {
const {
enabledSections,
mayHaveSponsoredTopSites,
pocketRegion,
mayHaveSponsoredStories,
mayHaveInferredPersonalization,
mayHaveRecentSaves,
mayHaveWeather,
openPreferences,
spocMessageVariant,
wallpapersEnabled,
wallpapersV2Enabled,
activeWallpaper,
@ -112,10 +108,7 @@ export class ContentSection extends React.PureComponent {
const {
topSitesEnabled,
pocketEnabled,
highlightsEnabled,
weatherEnabled,
showSponsoredTopSitesEnabled,
showSponsoredPocketEnabled,
showInferredPersonalizationEnabled,
showRecentSavesEnabled,
topSitesRowsCount,
@ -144,6 +137,19 @@ export class ContentSection extends React.PureComponent {
</>
)}
<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">
<moz-toggle
id="shortcuts-toggle"
@ -190,25 +196,6 @@ export class ContentSection extends React.PureComponent {
data-l10n-args='{"num": 4}'
/>
</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>
@ -227,31 +214,14 @@ export class ContentSection extends React.PureComponent {
data-l10n-id="newtab-custom-stories-toggle"
>
<div slot="nested">
{(mayHaveSponsoredStories || mayHaveRecentSaves) && (
{(mayHaveRecentSaves ||
mayHaveInferredPersonalization ||
mayHaveTopicSections) && (
<div className="more-info-pocket-wrapper">
<div
className="more-information"
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 && (
<div className="check-wrapper" role="presentation">
<input
@ -302,47 +272,6 @@ export class ContentSection extends React.PureComponent {
</moz-toggle>
</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>
<span className="divider" role="separator"></span>

View file

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

View file

@ -5,8 +5,21 @@
import React from "react";
import { SafeAnchor } from "../SafeAnchor/SafeAnchor";
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 = ({
spoc,
dispatch,
@ -39,39 +52,12 @@ export const AdBanner = ({
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 = () => {
dispatch(
ac.DiscoveryStreamUserEvent({
event: "CLICK",
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,
value: {
card_type: "spoc",
@ -98,13 +84,12 @@ export const AdBanner = ({
return (
<aside className="ad-banner-wrapper" style={{ gridRow: clampedRow }}>
<div className={`ad-banner-inner ${spoc.format}`}>
<div className="ad-banner-dismiss">
<button
className="icon icon-dismiss"
onClick={handleDismissClick}
data-l10n-id="newtab-toast-dismiss-button"
></button>
</div>
<AdBannerContextMenu
dispatch={dispatch}
spoc={spoc}
position={row}
type={type}
/>
<SafeAnchor
className="ad-banner-link"
url={spoc.url}

View file

@ -33,24 +33,6 @@
.ad-banner-inner {
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 {
max-width: var(--leaderboard-width);
@ -74,7 +56,7 @@
}
&.billboard {
min-width: var(--billboard-width);
width: var(--billboard-width);
.ad-banner-content {
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 =
"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
// function will be triggered if called again after `wait` milliseconds.
function debounce(func, wait) {
@ -52,6 +58,7 @@ export class _WallpaperCategories extends React.PureComponent {
showColorPicker: false,
inputType: "radio",
activeId: null,
isCustomWallpaperError: false,
};
}
@ -121,9 +128,13 @@ export class _WallpaperCategories extends React.PureComponent {
this.props.setPref("newtabWallpapers.wallpaper", id);
const uploadedPreviously =
this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
this.handleUserEvent(at.WALLPAPER_CLICK, {
selected_wallpaper: id,
had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously,
});
}
@ -210,13 +221,14 @@ export class _WallpaperCategories extends React.PureComponent {
}
handleReset() {
this.props.setPref("newtabWallpapers.wallpaper", "");
const uploadedPreviously =
this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
if (uploadedPreviously) {
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, false);
const selectedWallpaper =
this.props.Prefs.values["newtabWallpapers.wallpaper"];
// If a custom wallpaper is set, remove it
if (selectedWallpaper === "custom") {
this.props.dispatch(
ac.OnlyToMain({
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, {
selected_wallpaper: "none",
had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously,
});
}
@ -255,31 +272,42 @@ export class _WallpaperCategories extends React.PureComponent {
// Custom wallpaper image upload
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 =
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
const fileInput = document.createElement("input");
fileInput.type = "file";
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 => {
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) {
this.props.dispatch(
ac.OnlyToMain({
@ -287,6 +315,19 @@ export class _WallpaperCategories extends React.PureComponent {
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(
wallpaper => wallpaper.category === activeCategory
);
const wallpaperUploadMaxFileSize =
this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
function reduceColorsToFitCustomColorInput(arr) {
// 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`
}
tabIndex={index === 0 ? 0 : -1}
{...(category === "custom-wallpaper"
? { "aria-errormessage": "customWallpaperError" }
: {})}
/>
<label htmlFor={category} data-l10n-id={fluent_id}>
{fluent_id}
@ -499,6 +545,15 @@ export class _WallpaperCategories extends React.PureComponent {
);
})}
</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>
<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",
}),
// 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
// memory and notify the web extenion, rather than using the built-in block list.
WebExtDismiss: (site, index, eventSource) => ({

View file

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

View file

@ -2791,6 +2791,19 @@ main section {
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 {
--newtab-weather-content-font-size: 11px;
--newtab-weather-sponsor-font-size: 8px;
@ -8024,24 +8037,6 @@ main section {
.ad-banner-wrapper .ad-banner-inner {
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 {
max-width: var(--leaderboard-width);
}
@ -8062,7 +8057,7 @@ main section {
}
}
.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 {
height: var(--billboard-height);
@ -8092,6 +8087,33 @@ main section {
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 {
position: relative;
}

View file

@ -1828,6 +1828,28 @@ const LinkMenuOptions = {
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
// memory and notify the web extenion, rather than using the built-in block list.
WebExtDismiss: (site, index, eventSource) => ({
@ -4284,6 +4306,84 @@ function ListFeed({
}, 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
/* 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,
@ -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 = ({
spoc,
dispatch,
@ -4325,33 +4439,11 @@ const AdBanner = ({
width: imgWidth,
height: imgHeight
} = 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 = () => {
dispatch(actionCreators.DiscoveryStreamUserEvent({
event: "CLICK",
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,
value: {
card_type: "spoc",
@ -4380,13 +4472,12 @@ const AdBanner = ({
}
}, /*#__PURE__*/external_React_default().createElement("div", {
className: `ad-banner-inner ${spoc.format}`
}, /*#__PURE__*/external_React_default().createElement("div", {
className: "ad-banner-dismiss"
}, /*#__PURE__*/external_React_default().createElement("button", {
className: "icon icon-dismiss",
onClick: handleDismissClick,
"data-l10n-id": "newtab-toast-dismiss-button"
})), /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
}, /*#__PURE__*/external_React_default().createElement(AdBannerContextMenu, {
dispatch: dispatch,
spoc: spoc,
position: row,
type: type
}), /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
className: "ad-banner-link",
url: spoc.url,
title: spoc.title,
@ -11351,6 +11442,7 @@ const WallpapersSection = (0,external_ReactRedux_namespaceObject.connect)(state
};
})(_WallpapersSection);
;// 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
* 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/. */
@ -11361,6 +11453,8 @@ const WallpapersSection = (0,external_ReactRedux_namespaceObject.connect)(state
// eslint-disable-next-line no-shadow
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
// function will be triggered if called again after `wait` milliseconds.
@ -11399,7 +11493,8 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
activeCategoryFluentID: null,
showColorPicker: false,
inputType: "radio",
activeId: null
activeId: null,
isCustomWallpaperError: false
};
}
componentDidMount() {
@ -11456,9 +11551,11 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
id = `solid-color-picker-${event.target.value}`;
}
this.props.setPref("newtabWallpapers.wallpaper", id);
const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
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();
}
handleReset() {
this.props.setPref("newtabWallpapers.wallpaper", "");
const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
if (uploadedPreviously) {
this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, false);
const selectedWallpaper = this.props.Prefs.values["newtabWallpapers.wallpaper"];
// If a custom wallpaper is set, remove it
if (selectedWallpaper === "custom") {
this.props.dispatch(actionCreators.OnlyToMain({
type: actionTypes.WALLPAPER_REMOVE_UPLOAD
}));
}
// Reset active wallpaper
this.props.setPref("newtabWallpapers.wallpaper", "");
// Fire WALLPAPER_CLICK telemetry event
this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
selected_wallpaper: "none",
had_previous_wallpaper: !!this.props.activeWallpaper
had_previous_wallpaper: !!this.props.activeWallpaper,
had_uploaded_previously: !!uploadedPreviously
});
}
handleCategory = event => {
@ -11564,32 +11668,59 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
// Custom wallpaper image upload
async handleUpload() {
// TODO: Bug 1943663: Add telemetry
// 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 wallpaperUploadMaxFileSizeEnabled = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE_ENABLED];
const wallpaperUploadMaxFileSize = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
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
const fileInput = document.createElement("input");
fileInput.type = "file";
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 => {
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) {
this.props.dispatch(actionCreators.OnlyToMain({
type: actionTypes.WALLPAPER_UPLOAD,
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();
@ -11642,6 +11773,7 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
activeCategoryFluentID
} = this.state;
let filteredWallpapers = wallpaperList.filter(wallpaper => wallpaper.category === activeCategory);
const wallpaperUploadMaxFileSize = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
function reduceColorsToFitCustomColorInput(arr) {
// Reduce the amount of custom colors to make space for the custom color picker
while (arr.length % 3 !== 2) {
@ -11755,7 +11887,7 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
}
return /*#__PURE__*/external_React_default().createElement("div", {
key: category
}, /*#__PURE__*/external_React_default().createElement("input", {
}, /*#__PURE__*/external_React_default().createElement("input", WallpaperCategories_extends({
ref: el => {
if (el) {
this.categoryRef[index] = el;
@ -11770,10 +11902,20 @@ class _WallpaperCategories extends (external_React_default()).PureComponent {
onClick: category !== "custom-wallpaper" ? this.handleCategory : this.handleUpload,
className: category !== "custom-wallpaper" ? `wallpaper-input` : `wallpaper-input theme-custom-wallpaper`,
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,
"data-l10n-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, {
in: !!activeCategory,
timeout: 300,
@ -11847,7 +11989,6 @@ const WallpaperCategories = (0,external_ReactRedux_namespaceObject.connect)(stat
class ContentSection extends (external_React_default()).PureComponent {
constructor(props) {
super(props);
@ -11868,7 +12009,7 @@ class ContentSection extends (external_React_default()).PureComponent {
}));
}
onPreferenceSelect(e) {
// eventSource: TOP_SITES | TOP_STORIES | HIGHLIGHTS | WEATHER
// eventSource: WEATHER | TOP_SITES | TOP_STORIES
const {
preference,
eventSource
@ -11913,21 +12054,18 @@ class ContentSection extends (external_React_default()).PureComponent {
if (isOpen) {
drawerRef.style.marginTop = "var(--space-large)";
} else {
drawerRef.style.marginTop = `-${drawerHeight}px`;
drawerRef.style.marginTop = `-${drawerHeight + 3}px`;
}
}
}
render() {
const {
enabledSections,
mayHaveSponsoredTopSites,
pocketRegion,
mayHaveSponsoredStories,
mayHaveInferredPersonalization,
mayHaveRecentSaves,
mayHaveWeather,
openPreferences,
spocMessageVariant,
wallpapersEnabled,
wallpapersV2Enabled,
activeWallpaper,
@ -11938,10 +12076,7 @@ class ContentSection extends (external_React_default()).PureComponent {
const {
topSitesEnabled,
pocketEnabled,
highlightsEnabled,
weatherEnabled,
showSponsoredTopSitesEnabled,
showSponsoredPocketEnabled,
showInferredPersonalizationEnabled,
showRecentSavesEnabled,
topSitesRowsCount
@ -11964,7 +12099,17 @@ class ContentSection extends (external_React_default()).PureComponent {
role: "separator"
})), /*#__PURE__*/external_React_default().createElement("div", {
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",
className: "section"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
@ -12006,22 +12151,6 @@ class ContentSection extends (external_React_default()).PureComponent {
value: "4",
"data-l10n-id": "newtab-custom-row-selector",
"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", {
id: "pocket-section",
className: "section"
@ -12035,28 +12164,12 @@ class ContentSection extends (external_React_default()).PureComponent {
"data-l10n-id": "newtab-custom-stories-toggle"
}, /*#__PURE__*/external_React_default().createElement("div", {
slot: "nested"
}, (mayHaveSponsoredStories || mayHaveRecentSaves) && /*#__PURE__*/external_React_default().createElement("div", {
}, (mayHaveRecentSaves || mayHaveInferredPersonalization || mayHaveTopicSections) && /*#__PURE__*/external_React_default().createElement("div", {
className: "more-info-pocket-wrapper"
}, /*#__PURE__*/external_React_default().createElement("div", {
className: "more-information",
ref: this.pocketDrawerRef
}, mayHaveSponsoredStories && /*#__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", {
}, mayHaveInferredPersonalization && /*#__PURE__*/external_React_default().createElement("div", {
className: "check-wrapper",
role: "presentation"
}, /*#__PURE__*/external_React_default().createElement("input", {
@ -12089,34 +12202,7 @@ class ContentSection extends (external_React_default()).PureComponent {
className: "customize-menu-checkbox-label",
htmlFor: "recent-saves-pocket",
"data-l10n-id": "newtab-custom-pocket-show-recent-saves"
}))))))), /*#__PURE__*/external_React_default().createElement("div", {
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", {
})))))))), /*#__PURE__*/external_React_default().createElement("span", {
className: "divider",
role: "separator"
}), /*#__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,
pocketRegion: this.props.pocketRegion,
mayHaveTopicSections: this.props.mayHaveTopicSections,
mayHaveSponsoredTopSites: this.props.mayHaveSponsoredTopSites,
mayHaveSponsoredStories: this.props.mayHaveSponsoredStories,
mayHaveInferredPersonalization: this.props.mayHaveInferredPersonalization,
mayHaveRecentSaves: this.props.DiscoveryStream.recentSavesEnabled,
mayHaveWeather: this.props.mayHaveWeather,
spocMessageVariant: this.props.spocMessageVariant,
dispatch: this.props.dispatch,
exitEventFired: this.state.exitEventFired
}))));
@ -13825,9 +13908,6 @@ class BaseContent extends (external_React_default()).PureComponent {
const enabledSections = {
topSitesEnabled: prefs["feeds.topsites"],
pocketEnabled: prefs["feeds.section.topstories"],
highlightsEnabled: prefs["feeds.section.highlights"],
showSponsoredTopSitesEnabled: prefs.showSponsoredTopSites,
showSponsoredPocketEnabled: prefs.showSponsored,
showInferredPersonalizationEnabled: prefs[PREF_INFERRED_PERSONALIZATION_USER],
showRecentSavesEnabled: prefs.showRecentSaves,
topSitesRowsCount: prefs.topSitesRows,

View file

@ -219,6 +219,13 @@ module.exports = function (config) {
{
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": {
statements: 90.48,
lines: 90.48,

View file

@ -25,7 +25,25 @@ const PREFS_BEFORE_SECTIONS = () => [
feed: "showSearch",
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",
@ -45,31 +63,10 @@ const PREFS_BEFORE_SECTIONS = () => [
: [];
},
},
icon: "chrome://browser/skin/topsites.svg",
maxRows: 4,
rowsPref: "topSitesRows",
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 {
@ -192,7 +189,6 @@ export class AboutPreferences {
const {
id,
pref: prefData,
icon = "webextension",
maxRows,
rowsPref,
shouldHidePref,
@ -210,17 +206,11 @@ export class AboutPreferences {
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
const sectionVbox = createAppend("vbox", contentsGroup);
sectionVbox.setAttribute("data-subcategory", id);
const checkbox = createAppend("checkbox", sectionVbox);
checkbox.classList.add("section-checkbox");
checkbox.setAttribute("src", iconUrl);
// Setup a user event if we have an event source for this pref.
if (eventSource) {
this.setupUserEvent(checkbox, eventSource);

View file

@ -465,10 +465,24 @@ export const PREFS_CONFIG = new Map([
"newtabWallpapers.customWallpaper.uploadedPreviously",
{
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,
},
],
[
"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",
{

View file

@ -1151,13 +1151,18 @@ export class TelemetryFeed {
break;
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
Glean.newtab.wallpaperClick.record({
newtab_visit_id: session.session_id,
selected_wallpaper,
had_previous_wallpaper,
had_uploaded_previously,
});
}
break;
@ -1171,18 +1176,6 @@ export class TelemetryFeed {
newtab_visit_id: session.session_id,
});
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:
break;
}

View file

@ -1,5 +1,9 @@
"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
// newtab page.
test_newtab({

View file

@ -1,5 +1,9 @@
"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(
"resource://newtab/lib/WeatherFeed.sys.mjs"
);
@ -14,8 +18,7 @@ test_newtab({
async before({ pushPrefs }) {
await pushPrefs(
["browser.newtabpage.activity-stream.feeds.topsites", false],
["browser.newtabpage.activity-stream.feeds.section.topstories", false],
["browser.newtabpage.activity-stream.feeds.section.highlights", false]
["browser.newtabpage.activity-stream.feeds.section.topstories", false]
);
},
test: async function test_render_customizeMenu() {
@ -32,8 +35,6 @@ test_newtab({
);
}
const TOPSITES_PREF = "browser.newtabpage.activity-stream.feeds.topsites";
const HIGHLIGHTS_PREF =
"browser.newtabpage.activity-stream.feeds.section.highlights";
const TOPSTORIES_PREF =
"browser.newtabpage.activity-stream.feeds.section.topstories";
@ -93,26 +94,6 @@ test_newtab({
await sectionShownPromise;
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() {
Services.prefs.clearUserPref(
@ -121,9 +102,6 @@ test_newtab({
Services.prefs.clearUserPref(
"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";
// 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_newtab({
async before() {

View file

@ -1,5 +1,9 @@
"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_newtab({
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.
// `topstories.json` defines the stories shown
test_newtab({

View file

@ -1,5 +1,9 @@
"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 }) {
await pushPrefs([
"browser.newtabpage.activity-stream.discoverystream.config",

View file

@ -1,5 +1,9 @@
"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:
// 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.

View file

@ -4,6 +4,10 @@
"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({
async before() {
// Some reason test-linux1804-64-qr/debug can end up with example.com, so

View file

@ -47,31 +47,19 @@ describe("ContentSection", () => {
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} />);
assert.equal(
wrapper.find("#weather-toggle").prop("data-eventSource"),
"WEATHER"
);
assert.equal(
wrapper.find("#shortcuts-toggle").prop("data-eventSource"),
"TOP_SITES"
);
assert.equal(
wrapper.find("#sponsored-shortcuts").prop("data-eventSource"),
"SPONSORED_TOP_SITES"
);
assert.equal(
wrapper.find("#pocket-toggle").prop("data-eventSource"),
"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 });
});
it("should have the dismiss button be visible", () => {
const dismiss = wrapper.find(".ad-banner-dismiss .icon-dismiss");
it("should have the context menu button be visible", () => {
const dismiss = wrapper.find("moz-button");
assert.ok(dismiss.exists());
dismiss.simulate("click");
let [action] = dispatch.secondCall.args;
assert.equal(action.type, "BLOCK_URL");
assert.equal(action.data[0].id, DEFAULT_PROPS.spoc.id);
// The rest of the context menu functionality is now tested in
// AdBannerContextMenu.test.jsx
});
it("should call onLinkClick when banner is clicked", () => {
@ -142,7 +139,7 @@ describe("Discovery Stream <AdBanner>", () => {
ac.DiscoveryStreamUserEvent({
event: "CLICK",
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,
value: {
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);
const [, structure] = stub.firstCall.args;
assert.equal(structure[0].id, "search");
assert.equal(structure[1].id, "topsites");
assert.equal(structure[2].id, "weather");
assert.equal(structure[1].id, "weather");
assert.equal(structure[2].id, "topsites");
assert.equal(structure[3].id, "topstories");
assert.isEmpty(structure[3].rowsPref);
});
@ -228,38 +228,6 @@ describe("AboutPreferences Feed", () => {
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", () => {
it("should render a title", () => {
const titleString = "the_title";

View file

@ -290,8 +290,8 @@ quickactions-bookmarks2 = Manage bookmarks
quickactions-cmd-bookmarks = bookmarks
# Opens a SUMO article explaining how to clear history
quickactions-clearhistory = Clear History
quickactions-cmd-clearhistory = clear history
quickactions-clearrecenthistory = Clear recent history
quickactions-cmd-clearrecenthistory = clear recent history, history
# Opens about:downloads page
quickactions-downloads2 = View downloads
@ -301,6 +301,17 @@ quickactions-cmd-downloads = downloads
quickactions-extensions = Manage 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
quickactions-inspector2 = Open Developer Tools
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
# (either matching the whole string, or the first word of the string).
# "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.
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
*[other] { $num } rows
}
newtab-custom-sponsored-sites = Sponsored shortcuts
newtab-custom-stories-toggle =
.label = Recommended stories
.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-recent-toggle =
.label = Recent activity
.description = A selection of recent sites and content
newtab-custom-weather-toggle =
.label = Weather
.description = Todays forecast at a glance

View file

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

View file

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

View file

@ -30,6 +30,12 @@
@media (-moz-windows-mica) {
&:not([lwtheme]) {
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 {
gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
maven {
url repository
url = repository
if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
allowInsecureProtocol = true
}
@ -138,7 +138,7 @@ allprojects {
repositories {
gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
maven {
url repository
url = repository
if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
allowInsecureProtocol = true
}
@ -172,7 +172,7 @@ allprojects {
}
task downloadDependencies() {
description 'Download all dependencies to the Gradle cache'
description = 'Download all dependencies to the Gradle cache'
doLast {
configurations.each { configuration ->
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
# Engineering team.
return namespace(
build_tools_version="35.0.0",
build_tools_version="35.0.1",
compile_sdk_version="35",
target_sdk_version="35",
min_sdk_version="21",

View file

@ -577,6 +577,10 @@ netmonitor.toolbar.priority=Priority
# in the network table toolbar, above the "file" column.
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
# in the network table toolbar, above the "url" column.
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-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.
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,
RequestListColumnDomain,
RequestListColumnFile,
RequestListColumnPath,
RequestListColumnMethod,
RequestListColumnProtocol,
RequestListColumnRemoteIP,
@ -65,6 +66,11 @@ loader.lazyGetter(this, "RequestListColumnFile", function () {
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 () {
return createFactory(
require("resource://devtools/client/netmonitor/src/components/request-list/RequestListColumnUrl.js")
@ -199,6 +205,11 @@ const COLUMN_COMPONENTS = [
ColumnComponent: RequestListColumnFile,
props: ["onWaterfallMouseDown", "slowLimit"],
},
{
column: "path",
ColumnComponent: RequestListColumnPath,
props: ["onWaterfallMouseDown"],
},
{
column: "url",
ColumnComponent: RequestListColumnUrl,

View file

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

View file

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

View file

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

View file

@ -292,6 +292,18 @@ function getUrlScheme(url) {
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.
*/
@ -302,6 +314,7 @@ function getUrlDetails(url) {
const hostname = getUrlHostName(urlObject);
const unicodeUrl = getUnicodeUrl(urlObject);
const scheme = getUrlScheme(urlObject);
const path = getUrlPath(urlObject);
// If the hostname contains unreadable ASCII characters, we need to do the
// following two steps:
@ -338,6 +351,7 @@ function getUrlDetails(url) {
unicodeUrl,
isLocal,
url,
path,
};
}

View file

@ -181,6 +181,8 @@ skip-if = [
["browser_net_column_headers_tooltips.js"]
["browser_net_column_path.js"]
["browser_net_column_slow-request-indicator.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
) => 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
* profile has been obtained.
@ -397,6 +388,12 @@ export interface PerformancePref {
* button in the customization palette.
*/
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

View file

@ -4,22 +4,8 @@
// @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").PageContext} PageContext
* @typedef {import("../../@types/perf").PerformancePref} PerformancePref
*/
"use strict";
@ -27,6 +13,9 @@
const {
PureComponent,
createFactory,
createElement: h,
Fragment,
createRef,
} = require("resource://devtools/client/shared/vendor/react.mjs");
const {
connect,
@ -51,6 +40,209 @@ const {
restartBrowserWithEnvironmentVariable,
} = 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
* with the popup and DevTools page.
@ -58,16 +250,6 @@ const {
* @extends {React.PureComponent<Props>}
*/
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() {
const {
isSupportedPlatform,
@ -97,7 +279,11 @@ class AboutProfiling extends PureComponent {
{
className: "perf-photon-button perf-photon-button-micro",
type: "button",
onClick: this.handleRestart,
onClick: () => {
restartBrowserWithEnvironmentVariable({
[promptEnvRestart]: "1",
});
},
},
Localized({ id: "perftools-button-restart" })
)
@ -121,9 +307,13 @@ class AboutProfiling extends PureComponent {
div(
{ className: "perf-intro" },
h1(
{ className: "perf-intro-title" },
Localized({ id: "perftools-intro-title" })
div(
{ className: "perf-intro-title-bar" },
h1(
{ className: "perf-intro-title" },
Localized({ id: "perftools-intro-title" })
),
h(MoreActionsButton)
),
div(
{ className: "perf-intro-row" },

View file

@ -14,7 +14,6 @@
* @typedef {import("../@types/perf").PreferenceFront} PreferenceFront
* @typedef {import("../@types/perf").PerformancePref} PerformancePref
* @typedef {import("../@types/perf").RecordingSettings} RecordingSettings
* @typedef {import("../@types/perf").RestartBrowserWithEnvironmentVariable} RestartBrowserWithEnvironmentVariable
* @typedef {import("../@types/perf").GetActiveBrowserID} GetActiveBrowserID
* @typedef {import("../@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile
* @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.
*
* @type {RestartBrowserWithEnvironmentVariable}
* @param {Record<string, string>} env
*/
function restartBrowserWithEnvironmentVariable(envName, value) {
Services.env.set(envName, value);
function restartBrowserWithEnvironmentVariable(env) {
for (const [envName, envValue] of Object.entries(env)) {
Services.env.set(envName, envValue);
}
Services.startup.quit(
Services.startup.eForceQuit | Services.startup.eRestart

View file

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

View file

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

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