Bug 1881588 - Add Wallpaper component r=home-newtab-reviewers,fluent-reviewers,bolsson,thecount,maxxcrawford

Differential Revision: https://phabricator.services.mozilla.com/D205373
This commit is contained in:
Nathan Barrett 2024-04-10 22:42:12 +00:00
parent 76ccf69421
commit d57c2801d9
22 changed files with 1187 additions and 15 deletions

View File

@ -1692,6 +1692,9 @@ pref("browser.partnerlink.campaign.topsites", "amzn_2020_a1");
// Activates preloading of the new tab url.
pref("browser.newtab.preload", true);
// Preference to enable wallpaper selection in the Customize Menu of new tab page
pref("browser.newtabpage.activity-stream.newtabWallpapers.enabled", false);
// Current new tab page background image.
pref("browser.newtabpage.activity-stream.newtabWallpapers.wallpaper", "");

View File

@ -160,6 +160,7 @@ for (const type of [
"UPDATE_PINNED_SEARCH_SHORTCUTS",
"UPDATE_SEARCH_SHORTCUTS",
"UPDATE_SECTION_PREFS",
"WALLPAPERS_SET",
"WEBEXT_CLICK",
"WEBEXT_DISMISS",
]) {

View File

@ -101,6 +101,9 @@ export const INITIAL_STATE = {
// Hide the search box after handing off to AwesomeBar and user starts typing.
hide: false,
},
Wallpapers: {
wallpaperList: [],
},
};
function App(prevState = INITIAL_STATE.App, action) {
@ -841,6 +844,15 @@ function Search(prevState = INITIAL_STATE.Search, action) {
}
}
function Wallpapers(prevState = INITIAL_STATE.Wallpapers, action) {
switch (action.type) {
case at.WALLPAPERS_SET:
return { wallpaperList: action.data };
default:
return prevState;
}
}
export const reducers = {
TopSites,
App,
@ -852,4 +864,5 @@ export const reducers = {
Personalization,
DiscoveryStream,
Search,
Wallpapers,
};

View File

@ -110,6 +110,7 @@ export class BaseContent extends React.PureComponent {
this.handleOnKeyDown = this.handleOnKeyDown.bind(this);
this.onWindowScroll = debounce(this.onWindowScroll.bind(this), 5);
this.setPref = this.setPref.bind(this);
this.updateWallpaper = this.updateWallpaper.bind(this);
this.state = { fixedSearch: false, firstVisibleTimestamp: null };
}
@ -192,11 +193,64 @@ export class BaseContent extends React.PureComponent {
this.props.dispatch(ac.SetPref(pref, value));
}
renderWallpaperAttribution() {
const activeWallpaper =
this.props.Prefs.values["newtabWallpapers.wallpaper"];
const { wallpaperList } = this.props.Wallpapers;
const selected = wallpaperList.find(wp => wp.title === activeWallpaper);
// make sure a wallpaper is selected and that the attribution also exists
if (!selected?.attribution) {
return null;
}
const { name, webpage } = selected.attribution;
if (activeWallpaper && wallpaperList && name.url) {
return (
<p
className={`wallpaper-attribution`}
key={name}
data-l10n-id="newtab-wallpaper-attribution"
data-l10n-args={JSON.stringify({
author_string: name.string,
author_url: name.url,
webpage_string: webpage.string,
webpage_url: webpage.url,
})}
>
<a data-l10n-name="name-link" href={name.url}>
{name.string}
</a>
<a data-l10n-name="webpage-link" href={webpage.url}>
{webpage.string}
</a>
</p>
);
}
return null;
}
async updateWallpaper() {
const prefs = this.props.Prefs.values;
const activeWallpaper = prefs["newtabWallpapers.wallpaper"];
const { wallpaperList } = this.props.Wallpapers;
if (wallpaperList) {
const wallpaper =
wallpaperList.find(wp => wp.title === activeWallpaper) || "";
global.document?.body.style.setProperty(
"--newtab-wallpaper",
`url(${wallpaper?.wallpaperUrl || ""})`
);
}
}
render() {
const { props } = this;
const { App } = props;
const { initialized, customizeMenuVisible } = App;
const prefs = props.Prefs.values;
const activeWallpaper = prefs["newtabWallpapers.wallpaper"];
const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
const { pocketConfig } = prefs;
const isDiscoveryStream =
@ -247,6 +301,9 @@ export class BaseContent extends React.PureComponent {
]
.filter(v => v)
.join(" ");
if (wallpapersEnabled) {
this.updateWallpaper();
}
return (
<div>
@ -256,6 +313,8 @@ export class BaseContent extends React.PureComponent {
openPreferences={this.openPreferences}
setPref={this.setPref}
enabledSections={enabledSections}
wallpapersEnabled={wallpapersEnabled}
activeWallpaper={activeWallpaper}
pocketRegion={pocketRegion}
mayHaveSponsoredTopSites={mayHaveSponsoredTopSites}
mayHaveSponsoredStories={mayHaveSponsoredStories}
@ -292,6 +351,7 @@ export class BaseContent extends React.PureComponent {
)}
</div>
<ConfirmDialog />
{wallpapersEnabled && this.renderWallpaperAttribution()}
</main>
</div>
</div>
@ -309,4 +369,5 @@ export const Base = connect(state => ({
Sections: state.Sections,
DiscoveryStream: state.DiscoveryStream,
Search: state.Search,
Wallpapers: state.Wallpapers,
}))(_Base);

View File

@ -24,10 +24,17 @@
}
main {
margin: auto;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
width: $wrapper-default-width;
padding: 0;
.vertical-center-wrapper {
margin: auto 0;
}
section {
margin-bottom: $section-spacing;
position: relative;
@ -124,3 +131,32 @@ main {
}
}
}
.wallpaper-attribution {
padding: 0 $section-horizontal-padding;
font-size: 14px;
&.theme-light {
display: inline-block;
@include dark-theme-only {
display: none;
}
}
&.theme-dark {
display: none;
@include dark-theme-only {
display: inline-block;
}
}
a {
color: var(--newtab-element-color);
&:hover {
text-decoration: none;
}
}
}

View File

@ -5,6 +5,7 @@
import React from "react";
import { actionCreators as ac } from "common/Actions.mjs";
import { SafeAnchor } from "../../DiscoveryStreamComponents/SafeAnchor/SafeAnchor";
import { WallpapersSection } from "../../WallpapersSection/WallpapersSection";
export class ContentSection extends React.PureComponent {
constructor(props) {
@ -98,6 +99,9 @@ export class ContentSection extends React.PureComponent {
mayHaveRecentSaves,
openPreferences,
spocMessageVariant,
wallpapersEnabled,
activeWallpaper,
setPref,
} = this.props;
const {
topSitesEnabled,
@ -111,6 +115,15 @@ export class ContentSection extends React.PureComponent {
return (
<div className="home-section">
{wallpapersEnabled && (
<div className="wallpapers-section">
<h2 data-l10n-id="newtab-wallpaper-title"></h2>
<WallpapersSection
setPref={setPref}
activeWallpaper={activeWallpaper}
/>
</div>
)}
<div id="shortcuts-section" className="section">
<moz-toggle
id="shortcuts-toggle"

View File

@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { BackgroundsSection } from "content-src/components/CustomizeMenu/BackgroundsSection/BackgroundsSection";
import { ContentSection } from "content-src/components/CustomizeMenu/ContentSection/ContentSection";
import { connect } from "react-redux";
import React from "react";
@ -62,11 +61,12 @@ export class _CustomizeMenu extends React.PureComponent {
data-l10n-id="newtab-custom-close-button"
ref={c => (this.closeButton = c)}
/>
<BackgroundsSection />
<ContentSection
openPreferences={this.props.openPreferences}
setPref={this.props.setPref}
enabledSections={this.props.enabledSections}
wallpapersEnabled={this.props.wallpapersEnabled}
activeWallpaper={this.props.activeWallpaper}
pocketRegion={this.props.pocketRegion}
mayHaveSponsoredTopSites={this.props.mayHaveSponsoredTopSites}
mayHaveSponsoredStories={this.props.mayHaveSponsoredStories}

View File

@ -119,6 +119,10 @@
grid-row-gap: 32px;
padding: 0 16px;
.wallpapers-section h2 {
font-size: inherit;
}
.section {
moz-toggle {
margin-bottom: 10px;

View File

@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
import { connect } from "react-redux";
export class _WallpapersSection extends React.PureComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleReset = this.handleReset.bind(this);
this.prefersHighContrastQuery = null;
this.prefersDarkQuery = null;
}
componentDidMount() {
this.prefersHighContrastQuery = globalThis.matchMedia(
"(forced-colors: active)"
);
this.prefersDarkQuery = globalThis.matchMedia(
"(prefers-color-scheme: dark)"
);
[this.prefersHighContrastQuery, this.prefersDarkQuery].forEach(
colorTheme => {
colorTheme.addEventListener("change", this.handleReset);
}
);
}
componentWillUnmount() {
[this.prefersHighContrastQuery, this.prefersDarkQuery].forEach(
colorTheme => {
colorTheme.removeEventListener("change", this.handleReset);
}
);
}
handleChange(event) {
const { id } = event.target;
this.props.setPref("newtabWallpapers.wallpaper", id);
}
handleReset() {
this.props.setPref("newtabWallpapers.wallpaper", "i");
}
render() {
const { wallpaperList } = this.props.Wallpapers;
const { activeWallpaper } = this.props;
return (
<div>
<fieldset className="wallpaper-list">
{wallpaperList.map(({ title, theme, fluent_id }) => {
return (
<>
<input
onChange={this.handleChange}
type="radio"
name="wallpaper"
id={title}
value={title}
checked={title === activeWallpaper}
aria-checked={title === activeWallpaper}
className={`wallpaper-input theme-${theme} ${title}`}
/>
<label
htmlFor={title}
className="sr-only"
data-l10n-id={fluent_id}
>
{fluent_id}
</label>
</>
);
})}
</fieldset>
<button
className="wallpapers-reset"
onClick={this.handleReset}
data-l10n-id="newtab-wallpaper-reset"
/>
</div>
);
}
}
export const WallpapersSection = connect(state => {
return {
Wallpapers: state.Wallpapers,
Prefs: state.Prefs,
};
})(_WallpapersSection);

View File

@ -0,0 +1,82 @@
.wallpaper-list {
display: grid;
gap: 16px;
grid-template-columns: 1fr 1fr 1fr;
grid-auto-rows: 86px;
margin: 16px 0;
padding: 0;
border: none;
.wallpaper-input,
.sr-only {
&.theme-light {
display: inline-block;
@include dark-theme-only {
display: none;
}
}
&.theme-dark {
display: none;
@include dark-theme-only {
display: inline-block;
}
}
}
.wallpaper-input {
appearance: none;
margin: 0;
padding: 0;
height: 86px;
width: 100%;
box-shadow: $shadow-secondary;
border-radius: 8px;
background-clip: content-box;
background-repeat: no-repeat;
background-size: cover;
cursor: pointer;
outline: 2px solid transparent;
$wallpapers: dark-aurora, dark-city, dark-color, dark-mountain, dark-panda, dark-sky, light-beach, light-color, light-landscape, light-mountain, light-redpanda, light-sky;
@each $wallpaper in $wallpapers {
&.#{$wallpaper} {
background-image: url('chrome://activity-stream/content/data/content/assets/wallpapers/#{$wallpaper}.avif')
}
}
&:checked {
outline-color: var(--newtab-primary-action-background);
}
&:hover {
filter: brightness(55%);
outline-color: transparent;
}
}
// visually hide label, but still read by screen readers
.sr-only {
opacity: 0;
overflow: hidden;
position: absolute;
}
}
.wallpapers-reset {
background: none;
border: none;
text-decoration: underline;
margin-inline: auto;
display: block;
font-size: var(--font-size-small);
color: var(--newtab-text-primary-color);
cursor: pointer;
&:hover {
text-decoration: none;
}
}

View File

@ -21,6 +21,12 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Ubuntu, 'Helvetica Neue', sans-serif;
font-size: 16px;
// rules for HNT wallpapers
background-image: var(--newtab-wallpaper, '');
background-repeat: no-repeat;
background-size: cover;
background-position: center;
background-attachment: fixed;
}
.no-scroll {
@ -137,6 +143,7 @@ input {
@import '../components/ContextMenu/ContextMenu';
@import '../components/ConfirmDialog/ConfirmDialog';
@import '../components/CustomizeMenu/CustomizeMenu';
@import '../components/WallpapersSection/WallpapersSection';
@import '../components/Card/Card';
@import '../components/CollapsibleSection/CollapsibleSection';
@import '../components/DiscoveryStreamAdmin/DiscoveryStreamAdmin';

View File

@ -276,6 +276,11 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif;
font-size: 16px;
background-image: var(--newtab-wallpaper, "");
background-repeat: no-repeat;
background-size: cover;
background-position: center;
background-attachment: fixed;
}
.no-scroll {
@ -405,10 +410,16 @@ input[type=text], input[type=search] {
}
main {
margin: auto;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
width: 274px;
padding: 0;
}
main .vertical-center-wrapper {
margin: auto 0;
}
main section {
margin-bottom: 20px;
position: relative;
@ -489,6 +500,29 @@ main section {
background-color: var(--newtab-element-active-color);
}
.wallpaper-attribution {
padding: 0 25px;
font-size: 14px;
}
.wallpaper-attribution.theme-light {
display: inline-block;
}
[lwt-newtab-brighttext] .wallpaper-attribution.theme-light {
display: none;
}
.wallpaper-attribution.theme-dark {
display: none;
}
[lwt-newtab-brighttext] .wallpaper-attribution.theme-dark {
display: inline-block;
}
.wallpaper-attribution a {
color: var(--newtab-element-color);
}
.wallpaper-attribution a:hover {
text-decoration: none;
}
.as-error-fallback {
align-items: center;
border-radius: 3px;
@ -1694,6 +1728,9 @@ main section {
grid-row-gap: 32px;
padding: 0 16px;
}
.home-section .wallpapers-section h2 {
font-size: inherit;
}
.home-section .section moz-toggle {
margin-bottom: 10px;
}
@ -1830,6 +1867,108 @@ main section {
box-shadow: 0 0 0 2px var(--newtab-primary-action-background-dimmed);
}
.wallpaper-list {
display: grid;
gap: 16px;
grid-template-columns: 1fr 1fr 1fr;
grid-auto-rows: 86px;
margin: 16px 0;
padding: 0;
border: none;
}
.wallpaper-list .wallpaper-input.theme-light,
.wallpaper-list .sr-only.theme-light {
display: inline-block;
}
[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-light,
[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-light {
display: none;
}
.wallpaper-list .wallpaper-input.theme-dark,
.wallpaper-list .sr-only.theme-dark {
display: none;
}
[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-dark,
[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-dark {
display: inline-block;
}
.wallpaper-list .wallpaper-input {
appearance: none;
margin: 0;
padding: 0;
height: 86px;
width: 100%;
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
border-radius: 8px;
background-clip: content-box;
background-repeat: no-repeat;
background-size: cover;
cursor: pointer;
outline: 2px solid transparent;
}
.wallpaper-list .wallpaper-input.dark-aurora {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-aurora.avif");
}
.wallpaper-list .wallpaper-input.dark-city {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-city.avif");
}
.wallpaper-list .wallpaper-input.dark-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif");
}
.wallpaper-list .wallpaper-input.dark-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif");
}
.wallpaper-list .wallpaper-input.dark-panda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif");
}
.wallpaper-list .wallpaper-input.dark-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif");
}
.wallpaper-list .wallpaper-input.light-beach {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif");
}
.wallpaper-list .wallpaper-input.light-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif");
}
.wallpaper-list .wallpaper-input.light-landscape {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif");
}
.wallpaper-list .wallpaper-input.light-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif");
}
.wallpaper-list .wallpaper-input.light-redpanda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-redpanda.avif");
}
.wallpaper-list .wallpaper-input.light-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif");
}
.wallpaper-list .wallpaper-input:checked {
outline-color: var(--newtab-primary-action-background);
}
.wallpaper-list .wallpaper-input:hover {
filter: brightness(55%);
outline-color: transparent;
}
.wallpaper-list .sr-only {
opacity: 0;
overflow: hidden;
position: absolute;
}
.wallpapers-reset {
background: none;
border: none;
text-decoration: underline;
margin-inline: auto;
display: block;
font-size: var(--font-size-small);
color: var(--newtab-text-primary-color);
cursor: pointer;
}
.wallpapers-reset:hover {
text-decoration: none;
}
/* stylelint-disable max-nesting-depth */
.card-outer {
background: var(--newtab-background-color-secondary);

View File

@ -280,6 +280,11 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif;
font-size: 16px;
background-image: var(--newtab-wallpaper, "");
background-repeat: no-repeat;
background-size: cover;
background-position: center;
background-attachment: fixed;
}
.no-scroll {
@ -409,10 +414,16 @@ input[type=text], input[type=search] {
}
main {
margin: auto;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
width: 274px;
padding: 0;
}
main .vertical-center-wrapper {
margin: auto 0;
}
main section {
margin-bottom: 20px;
position: relative;
@ -493,6 +504,29 @@ main section {
background-color: var(--newtab-element-active-color);
}
.wallpaper-attribution {
padding: 0 25px;
font-size: 14px;
}
.wallpaper-attribution.theme-light {
display: inline-block;
}
[lwt-newtab-brighttext] .wallpaper-attribution.theme-light {
display: none;
}
.wallpaper-attribution.theme-dark {
display: none;
}
[lwt-newtab-brighttext] .wallpaper-attribution.theme-dark {
display: inline-block;
}
.wallpaper-attribution a {
color: var(--newtab-element-color);
}
.wallpaper-attribution a:hover {
text-decoration: none;
}
.as-error-fallback {
align-items: center;
border-radius: 3px;
@ -1698,6 +1732,9 @@ main section {
grid-row-gap: 32px;
padding: 0 16px;
}
.home-section .wallpapers-section h2 {
font-size: inherit;
}
.home-section .section moz-toggle {
margin-bottom: 10px;
}
@ -1834,6 +1871,108 @@ main section {
box-shadow: 0 0 0 2px var(--newtab-primary-action-background-dimmed);
}
.wallpaper-list {
display: grid;
gap: 16px;
grid-template-columns: 1fr 1fr 1fr;
grid-auto-rows: 86px;
margin: 16px 0;
padding: 0;
border: none;
}
.wallpaper-list .wallpaper-input.theme-light,
.wallpaper-list .sr-only.theme-light {
display: inline-block;
}
[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-light,
[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-light {
display: none;
}
.wallpaper-list .wallpaper-input.theme-dark,
.wallpaper-list .sr-only.theme-dark {
display: none;
}
[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-dark,
[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-dark {
display: inline-block;
}
.wallpaper-list .wallpaper-input {
appearance: none;
margin: 0;
padding: 0;
height: 86px;
width: 100%;
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
border-radius: 8px;
background-clip: content-box;
background-repeat: no-repeat;
background-size: cover;
cursor: pointer;
outline: 2px solid transparent;
}
.wallpaper-list .wallpaper-input.dark-aurora {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-aurora.avif");
}
.wallpaper-list .wallpaper-input.dark-city {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-city.avif");
}
.wallpaper-list .wallpaper-input.dark-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif");
}
.wallpaper-list .wallpaper-input.dark-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif");
}
.wallpaper-list .wallpaper-input.dark-panda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif");
}
.wallpaper-list .wallpaper-input.dark-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif");
}
.wallpaper-list .wallpaper-input.light-beach {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif");
}
.wallpaper-list .wallpaper-input.light-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif");
}
.wallpaper-list .wallpaper-input.light-landscape {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif");
}
.wallpaper-list .wallpaper-input.light-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif");
}
.wallpaper-list .wallpaper-input.light-redpanda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-redpanda.avif");
}
.wallpaper-list .wallpaper-input.light-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif");
}
.wallpaper-list .wallpaper-input:checked {
outline-color: var(--newtab-primary-action-background);
}
.wallpaper-list .wallpaper-input:hover {
filter: brightness(55%);
outline-color: transparent;
}
.wallpaper-list .sr-only {
opacity: 0;
overflow: hidden;
position: absolute;
}
.wallpapers-reset {
background: none;
border: none;
text-decoration: underline;
margin-inline: auto;
display: block;
font-size: var(--font-size-small);
color: var(--newtab-text-primary-color);
cursor: pointer;
}
.wallpapers-reset:hover {
text-decoration: none;
}
/* stylelint-disable max-nesting-depth */
.card-outer {
background: var(--newtab-background-color-secondary);

View File

@ -276,6 +276,11 @@ body {
background-color: var(--newtab-background-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif;
font-size: 16px;
background-image: var(--newtab-wallpaper, "");
background-repeat: no-repeat;
background-size: cover;
background-position: center;
background-attachment: fixed;
}
.no-scroll {
@ -405,10 +410,16 @@ input[type=text], input[type=search] {
}
main {
margin: auto;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
width: 274px;
padding: 0;
}
main .vertical-center-wrapper {
margin: auto 0;
}
main section {
margin-bottom: 20px;
position: relative;
@ -489,6 +500,29 @@ main section {
background-color: var(--newtab-element-active-color);
}
.wallpaper-attribution {
padding: 0 25px;
font-size: 14px;
}
.wallpaper-attribution.theme-light {
display: inline-block;
}
[lwt-newtab-brighttext] .wallpaper-attribution.theme-light {
display: none;
}
.wallpaper-attribution.theme-dark {
display: none;
}
[lwt-newtab-brighttext] .wallpaper-attribution.theme-dark {
display: inline-block;
}
.wallpaper-attribution a {
color: var(--newtab-element-color);
}
.wallpaper-attribution a:hover {
text-decoration: none;
}
.as-error-fallback {
align-items: center;
border-radius: 3px;
@ -1694,6 +1728,9 @@ main section {
grid-row-gap: 32px;
padding: 0 16px;
}
.home-section .wallpapers-section h2 {
font-size: inherit;
}
.home-section .section moz-toggle {
margin-bottom: 10px;
}
@ -1830,6 +1867,108 @@ main section {
box-shadow: 0 0 0 2px var(--newtab-primary-action-background-dimmed);
}
.wallpaper-list {
display: grid;
gap: 16px;
grid-template-columns: 1fr 1fr 1fr;
grid-auto-rows: 86px;
margin: 16px 0;
padding: 0;
border: none;
}
.wallpaper-list .wallpaper-input.theme-light,
.wallpaper-list .sr-only.theme-light {
display: inline-block;
}
[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-light,
[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-light {
display: none;
}
.wallpaper-list .wallpaper-input.theme-dark,
.wallpaper-list .sr-only.theme-dark {
display: none;
}
[lwt-newtab-brighttext] .wallpaper-list .wallpaper-input.theme-dark,
[lwt-newtab-brighttext] .wallpaper-list .sr-only.theme-dark {
display: inline-block;
}
.wallpaper-list .wallpaper-input {
appearance: none;
margin: 0;
padding: 0;
height: 86px;
width: 100%;
box-shadow: 0 1px 4px 0 rgba(12, 12, 13, 0.2);
border-radius: 8px;
background-clip: content-box;
background-repeat: no-repeat;
background-size: cover;
cursor: pointer;
outline: 2px solid transparent;
}
.wallpaper-list .wallpaper-input.dark-aurora {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-aurora.avif");
}
.wallpaper-list .wallpaper-input.dark-city {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-city.avif");
}
.wallpaper-list .wallpaper-input.dark-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-color.avif");
}
.wallpaper-list .wallpaper-input.dark-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-mountain.avif");
}
.wallpaper-list .wallpaper-input.dark-panda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-panda.avif");
}
.wallpaper-list .wallpaper-input.dark-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/dark-sky.avif");
}
.wallpaper-list .wallpaper-input.light-beach {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-beach.avif");
}
.wallpaper-list .wallpaper-input.light-color {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-color.avif");
}
.wallpaper-list .wallpaper-input.light-landscape {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-landscape.avif");
}
.wallpaper-list .wallpaper-input.light-mountain {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-mountain.avif");
}
.wallpaper-list .wallpaper-input.light-redpanda {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-redpanda.avif");
}
.wallpaper-list .wallpaper-input.light-sky {
background-image: url("chrome://activity-stream/content/data/content/assets/wallpapers/light-sky.avif");
}
.wallpaper-list .wallpaper-input:checked {
outline-color: var(--newtab-primary-action-background);
}
.wallpaper-list .wallpaper-input:hover {
filter: brightness(55%);
outline-color: transparent;
}
.wallpaper-list .sr-only {
opacity: 0;
overflow: hidden;
position: absolute;
}
.wallpapers-reset {
background: none;
border: none;
text-decoration: underline;
margin-inline: auto;
display: block;
font-size: var(--font-size-small);
color: var(--newtab-text-primary-color);
cursor: pointer;
}
.wallpapers-reset:hover {
text-decoration: none;
}
/* stylelint-disable max-nesting-depth */
.card-outer {
background: var(--newtab-background-color-secondary);

View File

@ -233,6 +233,7 @@ for (const type of [
"UPDATE_PINNED_SEARCH_SHORTCUTS",
"UPDATE_SEARCH_SHORTCUTS",
"UPDATE_SECTION_PREFS",
"WALLPAPERS_SET",
"WEBEXT_CLICK",
"WEBEXT_DISMISS",
]) {
@ -5530,6 +5531,9 @@ const INITIAL_STATE = {
// Hide the search box after handing off to AwesomeBar and user starts typing.
hide: false,
},
Wallpapers: {
wallpaperList: [],
},
};
function App(prevState = INITIAL_STATE.App, action) {
@ -6270,6 +6274,15 @@ function Search(prevState = INITIAL_STATE.Search, action) {
}
}
function Wallpapers(prevState = INITIAL_STATE.Wallpapers, action) {
switch (action.type) {
case actionTypes.WALLPAPERS_SET:
return { wallpaperList: action.data };
default:
return prevState;
}
}
const reducers = {
TopSites,
App,
@ -6281,6 +6294,7 @@ const reducers = {
Personalization: Reducers_sys_Personalization,
DiscoveryStream,
Search,
Wallpapers,
};
;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteFormInput.jsx
@ -8833,17 +8847,83 @@ const DiscoveryStreamBase = (0,external_ReactRedux_namespaceObject.connect)(stat
document: globalThis.document,
App: state.App
}))(_DiscoveryStreamBase);
;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/BackgroundsSection/BackgroundsSection.jsx
;// CONCATENATED MODULE: ./content-src/components/WallpapersSection/WallpapersSection.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 http://mozilla.org/MPL/2.0/. */
class BackgroundsSection extends (external_React_default()).PureComponent {
class _WallpapersSection extends (external_React_default()).PureComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleReset = this.handleReset.bind(this);
this.prefersHighContrastQuery = null;
this.prefersDarkQuery = null;
}
componentDidMount() {
this.prefersHighContrastQuery = globalThis.matchMedia("(forced-colors: active)");
this.prefersDarkQuery = globalThis.matchMedia("(prefers-color-scheme: dark)");
[this.prefersHighContrastQuery, this.prefersDarkQuery].forEach(colorTheme => {
colorTheme.addEventListener("change", this.handleReset);
});
}
componentWillUnmount() {
[this.prefersHighContrastQuery, this.prefersDarkQuery].forEach(colorTheme => {
colorTheme.removeEventListener("change", this.handleReset);
});
}
handleChange(event) {
const {
id
} = event.target;
this.props.setPref("newtabWallpapers.wallpaper", id);
}
handleReset() {
this.props.setPref("newtabWallpapers.wallpaper", "i");
}
render() {
return /*#__PURE__*/external_React_default().createElement("div", null);
const {
wallpaperList
} = this.props.Wallpapers;
const {
activeWallpaper
} = this.props;
return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("fieldset", {
className: "wallpaper-list"
}, wallpaperList.map(({
title,
theme,
fluent_id
}) => {
return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("input", {
onChange: this.handleChange,
type: "radio",
name: "wallpaper",
id: title,
value: title,
checked: title === activeWallpaper,
"aria-checked": title === activeWallpaper,
className: `wallpaper-input theme-${theme} ${title}`
}), /*#__PURE__*/external_React_default().createElement("label", {
htmlFor: title,
className: "sr-only",
"data-l10n-id": fluent_id
}, fluent_id));
})), /*#__PURE__*/external_React_default().createElement("button", {
className: "wallpapers-reset",
onClick: this.handleReset,
"data-l10n-id": "newtab-wallpaper-reset"
}));
}
}
const WallpapersSection = (0,external_ReactRedux_namespaceObject.connect)(state => {
return {
Wallpapers: state.Wallpapers,
Prefs: state.Prefs
};
})(_WallpapersSection);
;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/ContentSection/ContentSection.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,
@ -8852,6 +8932,7 @@ class BackgroundsSection extends (external_React_default()).PureComponent {
class ContentSection extends (external_React_default()).PureComponent {
constructor(props) {
super(props);
@ -8930,7 +9011,10 @@ class ContentSection extends (external_React_default()).PureComponent {
mayHaveSponsoredStories,
mayHaveRecentSaves,
openPreferences,
spocMessageVariant
spocMessageVariant,
wallpapersEnabled,
activeWallpaper,
setPref
} = this.props;
const {
topSitesEnabled,
@ -8943,7 +9027,14 @@ class ContentSection extends (external_React_default()).PureComponent {
} = enabledSections;
return /*#__PURE__*/external_React_default().createElement("div", {
className: "home-section"
}, /*#__PURE__*/external_React_default().createElement("div", {
}, wallpapersEnabled && /*#__PURE__*/external_React_default().createElement("div", {
className: "wallpapers-section"
}, /*#__PURE__*/external_React_default().createElement("h2", {
"data-l10n-id": "newtab-wallpaper-title"
}), /*#__PURE__*/external_React_default().createElement(WallpapersSection, {
setPref: setPref,
activeWallpaper: activeWallpaper
})), /*#__PURE__*/external_React_default().createElement("div", {
id: "shortcuts-section",
className: "section"
}, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
@ -9091,7 +9182,6 @@ class ContentSection extends (external_React_default()).PureComponent {
class _CustomizeMenu extends (external_React_default()).PureComponent {
constructor(props) {
super(props);
@ -9135,10 +9225,12 @@ class _CustomizeMenu extends (external_React_default()).PureComponent {
className: "close-button",
"data-l10n-id": "newtab-custom-close-button",
ref: c => this.closeButton = c
}), /*#__PURE__*/external_React_default().createElement(BackgroundsSection, null), /*#__PURE__*/external_React_default().createElement(ContentSection, {
}), /*#__PURE__*/external_React_default().createElement(ContentSection, {
openPreferences: this.props.openPreferences,
setPref: this.props.setPref,
enabledSections: this.props.enabledSections,
wallpapersEnabled: this.props.wallpapersEnabled,
activeWallpaper: this.props.activeWallpaper,
pocketRegion: this.props.pocketRegion,
mayHaveSponsoredTopSites: this.props.mayHaveSponsoredTopSites,
mayHaveSponsoredStories: this.props.mayHaveSponsoredStories,
@ -9453,6 +9545,7 @@ class BaseContent extends (external_React_default()).PureComponent {
this.handleOnKeyDown = this.handleOnKeyDown.bind(this);
this.onWindowScroll = debounce(this.onWindowScroll.bind(this), 5);
this.setPref = this.setPref.bind(this);
this.updateWallpaper = this.updateWallpaper.bind(this);
this.state = {
fixedSearch: false,
firstVisibleTimestamp: null
@ -9535,6 +9628,52 @@ class BaseContent extends (external_React_default()).PureComponent {
setPref(pref, value) {
this.props.dispatch(actionCreators.SetPref(pref, value));
}
renderWallpaperAttribution() {
const activeWallpaper = this.props.Prefs.values["newtabWallpapers.wallpaper"];
const {
wallpaperList
} = this.props.Wallpapers;
const selected = wallpaperList.find(wp => wp.title === activeWallpaper);
// make sure a wallpaper is selected and that the attribution also exists
if (!selected?.attribution) {
return null;
}
const {
name,
webpage
} = selected.attribution;
if (activeWallpaper && wallpaperList && name.url) {
return /*#__PURE__*/external_React_default().createElement("p", {
className: `wallpaper-attribution`,
key: name,
"data-l10n-id": "newtab-wallpaper-attribution",
"data-l10n-args": JSON.stringify({
author_string: name.string,
author_url: name.url,
webpage_string: webpage.string,
webpage_url: webpage.url
})
}, /*#__PURE__*/external_React_default().createElement("a", {
"data-l10n-name": "name-link",
href: name.url
}, name.string), /*#__PURE__*/external_React_default().createElement("a", {
"data-l10n-name": "webpage-link",
href: webpage.url
}, webpage.string));
}
return null;
}
async updateWallpaper() {
const prefs = this.props.Prefs.values;
const activeWallpaper = prefs["newtabWallpapers.wallpaper"];
const {
wallpaperList
} = this.props.Wallpapers;
if (wallpaperList) {
const wallpaper = wallpaperList.find(wp => wp.title === activeWallpaper) || "";
__webpack_require__.g.document?.body.style.setProperty("--newtab-wallpaper", `url(${wallpaper?.wallpaperUrl || ""})`);
}
}
render() {
const {
props
@ -9547,6 +9686,8 @@ class BaseContent extends (external_React_default()).PureComponent {
customizeMenuVisible
} = App;
const prefs = props.Prefs.values;
const activeWallpaper = prefs["newtabWallpapers.wallpaper"];
const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
const {
pocketConfig
} = prefs;
@ -9574,12 +9715,17 @@ class BaseContent extends (external_React_default()).PureComponent {
mayHaveSponsoredTopSites
} = prefs;
const outerClassName = ["outer-wrapper", isDiscoveryStream && pocketEnabled && "ds-outer-wrapper-search-alignment", isDiscoveryStream && "ds-outer-wrapper-breakpoint-override", prefs.showSearch && this.state.fixedSearch && !noSectionsEnabled && "fixed-search", prefs.showSearch && noSectionsEnabled && "only-search", prefs["logowordmark.alwaysVisible"] && "visible-logo"].filter(v => v).join(" ");
if (wallpapersEnabled) {
this.updateWallpaper();
}
return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement(CustomizeMenu, {
onClose: this.closeCustomizationMenu,
onOpen: this.openCustomizationMenu,
openPreferences: this.openPreferences,
setPref: this.setPref,
enabledSections: enabledSections,
wallpapersEnabled: wallpapersEnabled,
activeWallpaper: activeWallpaper,
pocketRegion: pocketRegion,
mayHaveSponsoredTopSites: mayHaveSponsoredTopSites,
mayHaveSponsoredStories: mayHaveSponsoredStories,
@ -9601,7 +9747,7 @@ class BaseContent extends (external_React_default()).PureComponent {
locale: props.App.locale,
mayHaveSponsoredStories: mayHaveSponsoredStories,
firstVisibleTimestamp: this.state.firstVisibleTimestamp
})) : /*#__PURE__*/external_React_default().createElement(Sections_Sections, null)), /*#__PURE__*/external_React_default().createElement(ConfirmDialog, null))));
})) : /*#__PURE__*/external_React_default().createElement(Sections_Sections, null)), /*#__PURE__*/external_React_default().createElement(ConfirmDialog, null), wallpapersEnabled && this.renderWallpaperAttribution())));
}
}
BaseContent.defaultProps = {
@ -9612,7 +9758,8 @@ const Base = (0,external_ReactRedux_namespaceObject.connect)(state => ({
Prefs: state.Prefs,
Sections: state.Sections,
DiscoveryStream: state.DiscoveryStream,
Search: state.Search
Search: state.Search,
Wallpapers: state.Wallpapers
}))(_Base);
;// CONCATENATED MODULE: ./content-src/lib/detect-user-session-start.mjs
/* This Source Code Form is subject to the terms of the Mozilla Public

View File

@ -158,6 +158,15 @@ module.exports = function (config) {
functions: 0,
branches: 0,
},
/**
* WallpaperFeed.sys.mjs is tested via an xpcshell test
*/
"lib/WallpaperFeed.sys.mjs": {
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/components/DiscoveryStreamComponents/**/*.jsx": {
statements: 90.48,
lines: 90.48,
@ -170,6 +179,15 @@ module.exports = function (config) {
functions: 60,
branches: 50,
},
/**
* WallpaperSection.jsx is tested via an xpcshell test
*/
"content-src/components/WallpapersSection/*.jsx": {
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/components/DiscoveryStreamAdmin/*.jsx": {
statements: 0,
lines: 0,

View File

@ -36,6 +36,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
TelemetryFeed: "resource://activity-stream/lib/TelemetryFeed.sys.mjs",
TopSitesFeed: "resource://activity-stream/lib/TopSitesFeed.sys.mjs",
TopStoriesFeed: "resource://activity-stream/lib/TopStoriesFeed.sys.mjs",
WallpaperFeed: "resource://activity-stream/lib/WallpaperFeed.sys.mjs",
});
// NB: Eagerly load modules that will be loaded/constructed/initialized in the
@ -232,6 +233,20 @@ export const PREFS_CONFIG = new Map([
value: "topsites,topstories,highlights",
},
],
[
"newtabWallpapers.enabled",
{
title: "Boolean flag to turn wallpaper functionality on and off",
value: true,
},
],
[
"newtabWallpapers.wallpaper",
{
title: "Currently set wallpaper",
value: "",
},
],
[
"improvesearch.noDefaultSearchTile",
{
@ -524,6 +539,12 @@ const FEEDS_DATA = [
title: "Handles new pocket ui for the new tab page",
value: true,
},
{
name: "wallpaperfeed",
factory: () => new lazy.WallpaperFeed(),
title: "Handles fetching and managing wallpaper data from RemoteSettings",
value: true,
},
];
const FEEDS_CONFIG = new Map();

View File

@ -0,0 +1,114 @@
/* 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/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
Utils: "resource://services-settings/Utils.sys.mjs",
});
import {
actionTypes as at,
actionCreators as ac,
} from "resource://activity-stream/common/Actions.mjs";
const PREF_WALLPAPERS_ENABLED =
"browser.newtabpage.activity-stream.newtabWallpapers.enabled";
export class WallpaperFeed {
constructor() {
this.loaded = false;
this.wallpaperClient = "";
this.wallpaperDB = "";
this.baseAttachmentURL = "";
}
/**
* This thin wrapper around global.fetch makes it easier for us to write
* automated tests that simulate responses from this fetch.
*/
fetch(...args) {
return fetch(...args);
}
/**
* This thin wrapper around lazy.RemoteSettings makes it easier for us to write
* automated tests that simulate responses from this fetch.
*/
RemoteSettings(...args) {
return lazy.RemoteSettings(...args);
}
async wallpaperSetup() {
const wallpapersEnabled = Services.prefs.getBoolPref(
PREF_WALLPAPERS_ENABLED
);
if (wallpapersEnabled) {
if (!this.wallpaperClient) {
this.wallpaperClient = this.RemoteSettings("newtab-wallpapers");
}
await this.getBaseAttachment();
this.wallpaperClient.on("sync", () => this.updateWallpapers());
this.updateWallpapers();
}
}
async getBaseAttachment() {
if (!this.baseAttachmentURL) {
const SERVER = lazy.Utils.SERVER_URL;
const serverInfo = await (
await this.fetch(`${SERVER}/`, {
credentials: "omit",
})
).json();
const { base_url } = serverInfo.capabilities.attachments;
this.baseAttachmentURL = base_url;
}
}
async updateWallpapers() {
const records = await this.wallpaperClient.get();
if (!records?.length) {
return;
}
if (!this.baseAttachmentURL) {
await this.getBaseAttachment();
}
const wallpapers = records.map(record => {
return {
...record,
wallpaperUrl: `${this.baseAttachmentURL}${record.attachment.location}`,
};
});
this.store.dispatch(
ac.BroadcastToContent({
type: at.WALLPAPERS_SET,
data: wallpapers,
})
);
}
async onAction(action) {
switch (action.type) {
case at.INIT:
await this.wallpaperSetup();
break;
case at.UNINIT:
break;
case at.SYSTEM_TICK:
break;
case at.PREF_CHANGED:
if (action.data.name === "newtabWallpapers.enabled") {
await this.wallpaperSetup();
}
break;
case at.WALLPAPERS_SET:
break;
}
}
}

View File

@ -0,0 +1,111 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { WallpaperFeed } = ChromeUtils.importESModule(
"resource://activity-stream/lib/WallpaperFeed.sys.mjs"
);
const { actionCreators: ac, actionTypes: at } = ChromeUtils.importESModule(
"resource://activity-stream/common/Actions.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
Utils: "resource://services-settings/Utils.sys.mjs",
sinon: "resource://testing-common/Sinon.sys.mjs",
});
const PREF_WALLPAPERS_ENABLED =
"browser.newtabpage.activity-stream.newtabWallpapers.enabled";
add_task(async function test_construction() {
let feed = new WallpaperFeed();
info("WallpaperFeed constructor should create initial values");
Assert.ok(feed, "Could construct a WallpaperFeed");
Assert.ok(feed.loaded === false, "WallpaperFeed is not loaded");
Assert.ok(
feed.wallpaperClient === "",
"wallpaperClient is initialized as an empty string"
);
Assert.ok(
feed.wallpaperDB === "",
"wallpaperDB is initialized as an empty string"
);
Assert.ok(
feed.baseAttachmentURL === "",
"baseAttachmentURL is initialized as an empty string"
);
});
add_task(async function test_onAction_INIT() {
let sandbox = sinon.createSandbox();
let feed = new WallpaperFeed();
Services.prefs.setBoolPref(PREF_WALLPAPERS_ENABLED, true);
const attachment = {
attachment: {
location: "attachment",
},
};
sandbox.stub(feed, "RemoteSettings").returns({
get: () => [attachment],
on: () => {},
});
sandbox.stub(Utils, "SERVER_URL").returns("http://localhost:8888/v1");
feed.store = {
dispatch: sinon.spy(),
};
sandbox.stub(feed, "fetch").resolves({
json: () => ({
capabilities: {
attachments: {
base_url: "http://localhost:8888/base_url/",
},
},
}),
});
info("WallpaperFeed.onAction INIT should initialize wallpapers");
await feed.onAction({
type: at.INIT,
});
Assert.ok(feed.store.dispatch.calledOnce);
Assert.ok(
feed.store.dispatch.calledWith(
ac.BroadcastToContent({
type: at.WALLPAPERS_SET,
data: [
{
...attachment,
wallpaperUrl: "http://localhost:8888/base_url/attachment",
},
],
})
)
);
Services.prefs.clearUserPref(PREF_WALLPAPERS_ENABLED);
sandbox.restore();
});
add_task(async function test_onAction_PREF_CHANGED() {
let sandbox = sinon.createSandbox();
let feed = new WallpaperFeed();
Services.prefs.setBoolPref(PREF_WALLPAPERS_ENABLED, true);
sandbox.stub(feed, "wallpaperSetup").returns();
info("WallpaperFeed.onAction PREF_CHANGED should call wallpaperSetup");
feed.onAction({
type: at.PREF_CHANGED,
data: { name: "newtabWallpapers.enabled" },
});
Assert.ok(feed.wallpaperSetup.calledOnce);
Services.prefs.clearUserPref(PREF_WALLPAPERS_ENABLED);
sandbox.restore();
});

View File

@ -26,3 +26,5 @@ support-files = ["../schemas/*.schema.json"]
["test_TopSitesFeed.js"]
["test_TopSitesFeed_glean.js"]
["test_WallpaperFeed.js"]

View File

@ -272,3 +272,25 @@ newtab-custom-recent-toggle =
.description = A selection of recent sites and content
newtab-custom-close-button = Close
newtab-custom-settings = Manage more settings
## New Tab Wallpapers
newtab-wallpaper-title = Wallpapers
newtab-wallpaper-reset = Reset to default
newtab-wallpaper-light-red-panda = Red panda
newtab-wallpaper-light-mountain = White mountain
newtab-wallpaper-light-sky = Sky with purple and pink clouds
newtab-wallpaper-light-color = Blue, pink and yellow shapes
newtab-wallpaper-light-landscape = Blue mist mountain landscape
newtab-wallpaper-light-beach = Beach with palm tree
newtab-wallpaper-dark-aurora = Aurora Borealis
newtab-wallpaper-dark-color = Red and blue shapes
newtab-wallpaper-dark-panda = Red panda hidden in forest
newtab-wallpaper-dark-sky = City landscape with a night sky
newtab-wallpaper-dark-mountain = Landscape mountain
newtab-wallpaper-dark-city = Purple city landscape
# Variables
# $author_string (String) - The name of the creator of the photo.
# $webpage_string (String) - The name of the webpage where the photo is located.
newtab-wallpaper-attribution = Photo by <a data-l10n-name="name-link">{ $author_string }</a> on <a data-l10n-name="webpage-link">{ $webpage_string }</a>

View File

@ -840,6 +840,13 @@ pocketNewtab:
browser.newtabpage.activity-stream.discoverystream.sendToPocket.enabled
description: >-
Decides what to do when a logged out user click "Save to Pocket" from a Pocket card.
wallpapers:
type: boolean
setPref:
branch: user
pref: browser.newtabpage.activity-stream.newtabWallpapers.enabled
description: >-
Turns on and off wallpaper support.
recsPersonalized:
type: boolean
fallbackPref: >-