Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE

This commit is contained in:
Razvan Maries 2019-02-15 11:59:08 +02:00
commit 2fb9019d41
167 changed files with 2105 additions and 1232 deletions

View File

@ -606,6 +606,12 @@
oncommand="ReportFalseDeceptiveSite();"
onclick="checkForMiddleClick(this, event);"
hidden="true"/>
<menuseparator id="helpPolicySeparator"
hidden="true"/>
<menuitem id="helpPolicySupport"
hidden="true"
oncommand="openUILink(this.getAttribute('href'), event, {triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({})});"
onclick="checkForMiddleClick(this, event);"/>
<menuseparator id="aboutSeparator"/>
<menuitem id="aboutName"
accesskey="&aboutProduct2.accesskey;"

View File

@ -15,6 +15,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "swm",
"@mozilla.org/serviceworkers/manager;1",
"nsIServiceWorkerManager");
XPCOMUtils.defineLazyGlobalGetters(this, ["indexedDB", "Blob"]);
/**
* This module assists with tasks around testing functionality that shows
* or clears site data.
@ -25,31 +27,44 @@ XPCOMUtils.defineLazyServiceGetter(this, "swm",
var SiteDataTestUtils = {
/**
* Adds a new entry to a dummy indexedDB database for the specified origin.
* Makes an origin have persistent data storage.
*
* @param {String} origin - the origin of the site to give persistent storage
*
* @returns a Promise that resolves when storage was persisted
*/
persist(origin, value = Services.perms.ALLOW_ACTION) {
return new Promise(resolve => {
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
Services.perms.addFromPrincipal(principal, "persistent-storage", value);
Services.qms.persist(principal).callback = () => resolve();
});
},
/**
* Adds a new blob entry to a dummy indexedDB database for the specified origin.
*
* @param {String} origin - the origin of the site to add test data for
* @param {String} name [optional] - the entry key
* @param {String} value [optional] - the entry value
* @param {Object} originAttributes [optional] - the originAttributes
* @param {Number} size [optional] - the size of the entry in bytes
*
* @returns a Promise that resolves when the data was added successfully.
*/
addToIndexedDB(origin, key = "foo", value = "bar", originAttributes = {}) {
addToIndexedDB(origin, size = 1024) {
return new Promise(resolve => {
let uri = Services.io.newURI(origin);
let principal =
Services.scriptSecurityManager.createCodebasePrincipal(uri, originAttributes);
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
request.onupgradeneeded = function(e) {
let db = e.target.result;
db.createObjectStore("TestStore", { keyPath: "id" });
db.createObjectStore("TestStore");
};
request.onsuccess = function(e) {
let db = e.target.result;
let tx = db.transaction("TestStore", "readwrite");
let store = tx.objectStore("TestStore");
tx.oncomplete = resolve;
store.put({ id: key, description: value});
let buffer = new ArrayBuffer(size);
let blob = new Blob([buffer]);
store.add(blob, Cu.now());
};
});
},
@ -63,8 +78,8 @@ var SiteDataTestUtils = {
* @param {String} value [optional] - the cookie value
*/
addToCookies(origin, name = "foo", value = "bar") {
let uri = Services.io.newURI(origin);
Services.cookies.add(uri.host, uri.pathQueryRef, name, value,
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
Services.cookies.add(principal.URI.host, principal.URI.pathQueryRef, name, value,
false, false, false, Date.now() + 24000 * 60 * 60, {},
Ci.nsICookie2.SAMESITE_UNSET);
},
@ -121,8 +136,7 @@ var SiteDataTestUtils = {
*/
getQuotaUsage(origin) {
return new Promise(resolve => {
let uri = Services.io.newURI(origin);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
Services.qms.getUsageForPrincipal(principal, request => resolve(request.result.usage));
});
},

View File

@ -18,8 +18,9 @@ function checkDataForAboutURL() {
}
function createIndexedDB(host, originAttributes) {
return SiteDataTestUtils.addToIndexedDB("https://" + host, "foo", "bar",
originAttributes);
let uri = Services.io.newURI("https://" + host);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, originAttributes);
return SiteDataTestUtils.addToIndexedDB(principal.origin);
}
function checkIndexedDB(host, originAttributes) {

View File

@ -915,6 +915,18 @@ function buildHelpMenu() {
document.getElementById("helpSafeMode")
.disabled = !Services.policies.isAllowed("safeMode");
let supportMenu = Services.policies.getSupportMenu();
if (supportMenu) {
let menuitem = document.getElementById("helpPolicySupport");
menuitem.hidden = false;
menuitem.setAttribute("label", supportMenu.Title);
menuitem.setAttribute("href", supportMenu.URL);
if ("AccessKey" in supportMenu) {
menuitem.setAttribute("accesskey", supportMenu.AccessKey);
}
document.getElementById("helpPolicySeparator").hidden = false;
}
// Enable/disable the "Report Web Forgery" menu item.
if (typeof gSafeBrowsing != "undefined") {
gSafeBrowsing.setReportPhishingMenu();

View File

@ -278,9 +278,18 @@ EnterprisePoliciesManager.prototype = {
getActivePolicies() {
return this._parsedPolicies;
},
setSupportMenu(supportMenu) {
SupportMenu = supportMenu;
},
getSupportMenu() {
return SupportMenu;
},
};
let DisallowedFeatures = {};
let SupportMenu = null;
/**
* areEnterpriseOnlyPoliciesAllowed

View File

@ -961,6 +961,12 @@ var Policies = {
},
},
"SupportMenu": {
onProfileAfterChange(manager, param) {
manager.setSupportMenu(param);
},
},
"WebsiteFilter": {
onBeforeUIStartup(manager, param) {
this.filter = new WebsiteFilter(param.Block || [], param.Exceptions || []);

View File

@ -676,6 +676,22 @@
"enum": ["tls1", "tls1.1", "tls1.2", "tls1.3"]
},
"SupportMenu": {
"type": "object",
"properties": {
"Title": {
"type": "string"
},
"URL": {
"type": "URL"
},
"AccessKey": {
"type": "string"
}
},
"required": ["Title", "URL"],
},
"WebsiteFilter": {
"type": "object",
"properties": {

View File

@ -59,4 +59,5 @@ skip-if = (verify && debug && (os == 'mac'))
[browser_policy_search_engine.js]
[browser_policy_searchbar.js]
[browser_policy_set_homepage.js]
[browser_policy_support_menu.js]
[browser_policy_websitefilter.js]

View File

@ -0,0 +1,28 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function setup() {
await setupPolicyEngineWithJson({
"policies": {
"SupportMenu": {
"Title": "Title",
"URL": "https://example.com/",
"AccessKey": "T",
},
},
});
});
add_task(async function test_help_menu() {
buildHelpMenu();
let supportMenu = document.getElementById("helpPolicySupport");
is(supportMenu.hidden, false,
"The policy menu should be visible.");
is(supportMenu.getAttribute("label"), "Title",
"The policy menu should have the correct title.");
is(supportMenu.getAttribute("href"), "https://example.com/",
"The policy menu should have the correct URL.");
is(supportMenu.getAttribute("accesskey"), "T",
"The policy menu should have the correct access key.");
});

View File

@ -522,7 +522,9 @@ class ViewPopup extends BasePopup {
]),
]);
if (!this.destroyed && !this.panel) {
const {panel} = this;
if (!this.destroyed && !panel) {
this.destroy();
}
@ -533,36 +535,45 @@ class ViewPopup extends BasePopup {
this.attached = true;
// Store the initial height of the view, so that we never resize menu panel
// sub-views smaller than the initial height of the menu.
this.viewHeight = this.viewNode.boxObject.height;
// Calculate the extra height available on the screen above and below the
// menu panel. Use that to calculate the how much the sub-view may grow.
let popupRect = this.panel.getBoundingClientRect();
this.setBackground(this.background);
let win = this.window;
let popupBottom = win.mozInnerScreenY + popupRect.bottom;
let popupTop = win.mozInnerScreenY + popupRect.top;
let flushPromise = this.window.promiseDocumentFlushed(() => {
let win = this.window;
let screenBottom = win.screen.availTop + win.screen.availHeight;
this.extraHeight = {
bottom: Math.max(0, screenBottom - popupBottom),
top: Math.max(0, popupTop - win.screen.availTop),
};
// Calculate the extra height available on the screen above and below the
// menu panel. Use that to calculate the how much the sub-view may grow.
let popupRect = panel.getBoundingClientRect();
let screenBottom = win.screen.availTop + win.screen.availHeight;
let popupBottom = win.mozInnerScreenY + popupRect.bottom;
let popupTop = win.mozInnerScreenY + popupRect.top;
// Store the initial height of the view, so that we never resize menu panel
// sub-views smaller than the initial height of the menu.
this.viewHeight = viewNode.boxObject.height;
this.extraHeight = {
bottom: Math.max(0, screenBottom - popupBottom),
top: Math.max(0, popupTop - win.screen.availTop),
};
});
// Create a new browser in the real popup.
let browser = this.browser;
await this.createBrowser(this.viewNode);
this.ignoreResizes = false;
this.browser.swapDocShells(browser);
this.destroyBrowser(browser);
await flushPromise;
// Check if the popup has been destroyed while we were waiting for the
// document flush promise to be resolve.
if (this.destroyed) {
this.closePopup();
this.destroy();
return false;
}
if (this.dimensions) {
if (this.fixedWidth) {
delete this.dimensions.width;
@ -570,6 +581,8 @@ class ViewPopup extends BasePopup {
this.resizeBrowser(this.dimensions);
}
this.ignoreResizes = false;
this.viewNode.customRectGetter = () => {
return {height: this.lastCalculatedInViewHeight || this.viewHeight};
};

View File

@ -25,10 +25,21 @@ function relativeTime(timestamp) {
return new Date(timestamp).toLocaleString();
}
const OPT_OUT_PREF = "discoverystream.optOut.0";
const LAYOUT_VARIANTS = {
"basic": "Basic default layout (on by default in nightly)",
"dev-test-all": "A little bit of everything. Good layout for testing all components",
"dev-test-feeds": "Stress testing for slow feeds",
};
class DiscoveryStreamAdmin extends React.PureComponent {
constructor(props) {
super(props);
this.onEnableToggle = this.onEnableToggle.bind(this);
this.changeEndpointVariant = this.changeEndpointVariant.bind(this);
}
get isOptedOut() {
return this.props.otherPrefs[OPT_OUT_PREF];
}
setConfigValue(name, value) {
@ -39,6 +50,13 @@ class DiscoveryStreamAdmin extends React.PureComponent {
this.setConfigValue("enabled", event.target.checked);
}
changeEndpointVariant(event) {
const endpoint = this.props.state.config.layout_endpoint;
if (endpoint) {
this.setConfigValue("layout_endpoint", endpoint.replace(/layout_variant=.+/, `layout_variant=${event.target.value}`));
}
}
renderComponent(width, component) {
return (
<table><tbody>
@ -55,6 +73,12 @@ class DiscoveryStreamAdmin extends React.PureComponent {
);
}
isCurrentVariant(id) {
const endpoint = this.props.state.config.layout_endpoint;
const isMatch = endpoint && !!endpoint.match(`layout_variant=${id}`);
return isMatch;
}
renderFeed(feed) {
const {feeds} = this.props.state;
if (!feed.url) {
@ -75,13 +99,27 @@ class DiscoveryStreamAdmin extends React.PureComponent {
}
render() {
const {isOptedOut} = this;
const {config, lastUpdated, layout} = this.props.state;
return (<div>
<div className="dsEnabled"><input type="checkbox" checked={config.enabled} onChange={this.onEnableToggle} /> enabled</div>
<div className="dsEnabled"><input type="checkbox" checked={config.enabled} onChange={this.onEnableToggle} /> enabled
{isOptedOut ? (<span className="optOutNote">(Note: User has opted-out. Check this box to reset)</span>) : ""}</div>
<h3>Endpoint variant</h3>
<p>You can also change this manually by changing this pref: <code>browser.newtabpage.activity-stream.discoverystream.config</code></p>
<table style={config.enabled ? null : {opacity: 0.5}}><tbody>
{Object.keys(LAYOUT_VARIANTS).map(id => (<Row key={id}>
<td className="min"><input type="radio" value={id} checked={this.isCurrentVariant(id)} onChange={this.changeEndpointVariant} /></td>
<td className="min">{id}</td>
<td>{LAYOUT_VARIANTS[id]}</td>
</Row>))}
</tbody></table>
<h3>Caching info</h3>
<table style={config.enabled ? null : {opacity: 0.5}}><tbody>
<Row><td className="min">Data last fetched</td><td>{relativeTime(lastUpdated) || "(no data)"}</td></Row>
<Row><td className="min">Endpoint</td><td>{config.layout_endpoint || "(empty)"}</td></Row>
</tbody></table>
<h3>Layout</h3>
@ -542,7 +580,7 @@ export class ASRouterAdminInner extends React.PureComponent {
case "ds":
return (<React.Fragment>
<h2>Discovery Stream</h2>
<DiscoveryStreamAdmin state={this.props.DiscoveryStream} dispatch={this.props.dispatch} />
<DiscoveryStreamAdmin state={this.props.DiscoveryStream} otherPrefs={this.props.Prefs.values} dispatch={this.props.dispatch} />
</React.Fragment>);
default:
return (<React.Fragment>
@ -584,4 +622,4 @@ export class ASRouterAdminInner extends React.PureComponent {
}
export const _ASRouterAdmin = props => (<SimpleHashRouter><ASRouterAdminInner {...props} /></SimpleHashRouter>);
export const ASRouterAdmin = connect(state => ({Sections: state.Sections, DiscoveryStream: state.DiscoveryStream}))(_ASRouterAdmin);
export const ASRouterAdmin = connect(state => ({Sections: state.Sections, DiscoveryStream: state.DiscoveryStream, Prefs: state.Prefs}))(_ASRouterAdmin);

View File

@ -155,5 +155,10 @@
.ds-component {
margin-bottom: 20px;
}
.optOutNote {
font-size: 12px;
margin-inline-start: 4px;
}
}

View File

@ -69,6 +69,12 @@ main {
// and Activity Stream breakpoints do some wonky things.
width: 1042px;
}
&:not(.fixed-search) {
.search-wrapper .search-inner-wrapper {
width: $searchbar-width-large;
}
}
}
.base-content-fallback {

View File

@ -25,9 +25,13 @@
}
.ds-header {
font-size: 17px;
color: $grey-50;
font-size: 13px;
font-weight: 600;
line-height: 24px;
color: $grey-90;
margin: 24px 0 12px;
line-height: 20px;
margin: 8px 0;
.icon {
fill: var(--newtab-text-secondary-color);
}
}

View File

@ -4,6 +4,7 @@ $col4-header-font-size: 14;
.ds-card-grid {
display: grid;
grid-gap: 24px;
margin: 16px 0;
.ds-card {
background: $white;
@ -51,10 +52,6 @@ $col4-header-font-size: 14;
.ds-column-12 & {
grid-template-columns: repeat(4, 1fr);
.meta .title {
@include limit-visibile-lines(2, $col4-header-line-height, $col4-header-font-size);
}
&.ds-card-grid-divisible-by-3 {
grid-template-columns: repeat(3, 1fr);
@ -63,5 +60,9 @@ $col4-header-font-size: 14;
line-height: 24px;
}
}
&.ds-card-grid-divisible-by-4 .title {
@include limit-visibile-lines(3, 20, 14);
}
}
}

View File

@ -41,19 +41,20 @@ $excerpt-line-height: 20;
}
.title {
// show only 2 lines of copy
@include limit-visibile-lines(2, $header-line-height, $header-font-size);
// show only 3 lines of copy
@include limit-visibile-lines(3, $header-line-height, $header-font-size);
font-weight: 600;
}
.excerpt {
// show only 4 lines of copy
@include limit-visibile-lines(4, $excerpt-line-height, $excerpt-font-size);
// show only 3 lines of copy
@include limit-visibile-lines(3, $excerpt-line-height, $excerpt-font-size);
}
.context,
.source {
font-size: 13px;
color: $teal-80;
}
}

View File

@ -1,25 +1,15 @@
import React from "react";
import {SafeAnchor} from "../SafeAnchor/SafeAnchor";
export class DSMessage extends React.PureComponent {
render() {
let hasSubtitleAndOrLink = this.props.link_text && this.props.link_url;
hasSubtitleAndOrLink = hasSubtitleAndOrLink || this.props.subtitle;
return (
<div className="ds-message">
{this.props.title && (
<header className="title">
{this.props.icon && (<img src={this.props.icon} />)}
<span>{this.props.title}</span>
</header>
)}
{ hasSubtitleAndOrLink && (
<p className="subtitle">
{this.props.subtitle && (<span>{this.props.subtitle}</span>)}
{this.props.link_text && this.props.link_url && (<a href={this.props.link_url}>{this.props.link_text}</a>)}
</p>
)}
<hr className="ds-hr" />
<header className="title">
{this.props.icon && (<div className="glyph" style={{backgroundImage: `url(${this.props.icon})`}} />)}
{this.props.title && (<span className="title-text">{this.props.title}</span>)}
{this.props.link_text && this.props.link_url && (<SafeAnchor className="link" url={this.props.link_url}>{this.props.link_text}</SafeAnchor>)}
</header>
</div>
);
}

View File

@ -5,37 +5,33 @@
display: flex;
align-items: center;
img {
.glyph {
width: 16px;
height: 16px;
margin: 0 6px 0 0;
-moz-context-properties: fill;
fill: var(--newtab-icon-secondary-color);
background-position: center center;
background-size: 16px;
background-repeat: no-repeat;
}
span {
line-height: 24px;
font-size: 17px;
color: $grey-90;
.title-text {
line-height: 20px;
font-size: 13px;
color: $grey-50;
font-weight: 600;
}
}
.subtitle {
line-height: 20px;
font-size: 14px;
color: $grey-50;
margin: 0;
span::after {
content: ' ';
padding-right: 12px;
}
a:hover,
a:focus {
text-decoration: underline;
}
}
.link {
line-height: 20px;
font-size: 13px;
.ds-hr {
margin: 16px 0 8px;
&:hover,
&:focus {
text-decoration: underline;
}
}
}
}

View File

@ -74,8 +74,10 @@ export class Hero extends React.PureComponent {
<div className="img" style={{backgroundImage: `url(${heroRec.image_src})`}} />
</div>
<div className="meta">
<header>{heroRec.title}</header>
<p className="excerpt">{heroRec.excerpt}</p>
<div className="header-and-excerpt">
<header>{heroRec.title}</header>
<p className="excerpt">{heroRec.excerpt}</p>
</div>
{heroRec.context ? (
<p className="context">{heroRec.context}</p>
) : (

View File

@ -16,7 +16,7 @@ $card-header-in-hero-line-height: 20;
}
.excerpt {
@include limit-visibile-lines(4, 23, 15);
@include limit-visibile-lines(3, 20, 14);
margin: 4px 0 8px;
}
@ -27,6 +27,7 @@ $card-header-in-hero-line-height: 20;
.ds-card {
border: 0;
padding-bottom: 20px;
p {
margin-top: 4px;
@ -40,11 +41,6 @@ $card-header-in-hero-line-height: 20;
.meta {
padding: 0;
.title {
// show only 2 lines of copy
@include limit-visibile-lines(2, $card-header-in-hero-line-height, $card-header-in-hero-font-size);
}
}
.img-wrapper {
@ -89,21 +85,22 @@ $card-header-in-hero-line-height: 20;
}
.meta {
display: block;
flex-direction: column;
justify-content: space-between;
header {
@include limit-visibile-lines(2, 28, 22);
@include limit-visibile-lines(4, 28, 22);
color: $grey-90;
}
p {
font-size: 14px;
&.context {
color: $teal-70;
}
.context {
color: $teal-70;
}
.source {
font-size: 13px;
color: $teal-80;
margin-bottom: 0;
overflow-x: hidden;
text-overflow: ellipsis;
@ -130,6 +127,7 @@ $card-header-in-hero-line-height: 20;
.meta {
grid-column: 1;
grid-row: 1;
display: flex;
}
.img {
@ -150,40 +148,62 @@ $card-header-in-hero-line-height: 20;
.ds-column-10 &,
.ds-column-11 &,
.ds-column-12 & {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px;
&.ds-hero-border {
border-top: $border-secondary;
padding: 20px 0;
.ds-card:nth-child(-n+2) {
border-bottom: $border-secondary;
margin-bottom: 20px;
}
}
.wrapper {
border-top: 0;
border-bottom: 0;
margin: 0;
padding: 0 0 20px;
display: flex;
align-items: flex-start;
flex-direction: row-reverse;
flex-direction: column;
.img-wrapper {
width: 67%;
margin: 0;
}
.img {
margin-bottom: 16px;
height: 0;
padding-top: 50%; // 2:1 aspect ratio
}
.meta {
width: 33%;
flex-grow: 1;
display: flex;
padding: 0 24px 0 0;
header {
@include limit-visibile-lines(6, 28, 22);
margin: 0 0 4px;
@include limit-visibile-lines(3, 28, 22);
margin: 0 0 8px;
}
p {
line-height: 1.6;
.source {
margin-bottom: 0;
}
}
}
.cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px;
.title {
@include limit-visibile-lines(3, 20, 14);
}
}
}
}

View File

@ -24,7 +24,7 @@ $item-line-height: 20;
}
.ds-list-item-title {
@include limit-visibile-lines(2, $line-height, $font-size);
@include limit-visibile-lines(3, $line-height, $font-size);
}
.ds-list-image {
@ -179,7 +179,7 @@ $item-line-height: 20;
.ds-list-item-info,
.ds-list-item-context {
@include limit-visibile-lines(1, $item-line-height, $item-font-size);
color: $grey-50;
color: $teal-80;
font-size: 13px;
text-overflow: ellipsis;
}

View File

@ -1,9 +1,10 @@
import React from "react";
import {SafeAnchor} from "../SafeAnchor/SafeAnchor";
export class Topic extends React.PureComponent {
render() {
const {url, name} = this.props;
return (<li><a key={name} href={url}>{name}</a></li>);
return (<li><SafeAnchor key={name} url={url}>{name}</SafeAnchor></li>);
}
}

View File

@ -12,7 +12,7 @@ export class SafeAnchor extends React.PureComponent {
"https:",
].includes(protocol);
if (!isAllowed) {
console.warn(`${protocol} is not allowed for anchor targets.`); // eslint-disable-line no-console
console.warn(`${url} is not allowed for anchor targets.`); // eslint-disable-line no-console
return "";
}
return url;

View File

@ -14,6 +14,7 @@ $grey-70: #38383D;
$grey-80: #2A2A2E;
$grey-90: #0C0C0D;
$teal-70: #008EA4;
$teal-80: #005A71;
$red-60: #D70022;
$yellow-50: #FFE900;

View File

@ -365,6 +365,9 @@ main {
.ds-outer-wrapper-breakpoint-override main {
width: 1042px; }
.ds-outer-wrapper-breakpoint-override:not(.fixed-search) .search-wrapper .search-inner-wrapper {
width: 736px; }
.base-content-fallback {
height: 100vh; }
@ -1733,6 +1736,9 @@ main {
border: 1px solid var(--newtab-border-secondary-color); }
.asrouter-admin .ds-component {
margin-bottom: 20px; }
.asrouter-admin .optOutNote {
font-size: 12px;
margin-inline-start: 4px; }
.pocket-logged-in-cta {
font-size: 13px;
@ -1831,11 +1837,13 @@ main {
grid-row-gap: var(--gridRowGap); }
.ds-header {
font-size: 17px;
color: #737373;
font-size: 13px;
font-weight: 600;
line-height: 24px;
color: #0C0C0D;
margin: 24px 0 12px; }
line-height: 20px;
margin: 8px 0; }
.ds-header .icon {
fill: var(--newtab-text-secondary-color); }
.ds-message-container {
display: none;
@ -1872,7 +1880,8 @@ main {
.ds-card-grid {
display: grid;
grid-gap: 24px; }
grid-gap: 24px;
margin: 16px 0; }
.ds-card-grid .ds-card {
background: #FFF;
border-radius: 4px; }
@ -1898,14 +1907,6 @@ main {
.ds-column-11 .ds-card-grid,
.ds-column-12 .ds-card-grid {
grid-template-columns: repeat(4, 1fr); }
.ds-column-9 .ds-card-grid .meta .title,
.ds-column-10 .ds-card-grid .meta .title,
.ds-column-11 .ds-card-grid .meta .title,
.ds-column-12 .ds-card-grid .meta .title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
overflow: hidden; }
.ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-3,
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-3,
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-3,
@ -1917,6 +1918,14 @@ main {
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-3 .title {
font-size: 17px;
line-height: 24px; }
.ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden; }
.ds-hero .img {
background-color: var(--newtab-card-placeholder-color);
@ -1934,9 +1943,9 @@ main {
margin: 8px 0; }
.ds-hero .excerpt {
font-size: 15px;
line-height: 23px;
max-height: 6.13333em;
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden;
margin: 4px 0 8px; }
@ -1945,7 +1954,8 @@ main {
padding-top: 0; }
.ds-hero .ds-card {
border: 0; }
border: 0;
padding-bottom: 20px; }
.ds-hero .ds-card p {
margin-top: 4px; }
.ds-hero .ds-card:hover {
@ -1954,11 +1964,6 @@ main {
border-radius: 0; }
.ds-hero .ds-card .meta {
padding: 0; }
.ds-hero .ds-card .meta .title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
overflow: hidden; }
.ds-hero .ds-card .img-wrapper {
margin: 0 0 12px; }
@ -1985,21 +1990,24 @@ main {
.ds-hero .wrapper .img {
height: 0;
padding-top: 50%; }
.ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 2.54545em;
overflow: hidden;
color: #0C0C0D; }
.ds-hero .wrapper .meta p {
font-size: 14px; }
.ds-hero .wrapper .meta p.context {
.ds-hero .wrapper .meta {
display: block;
flex-direction: column;
justify-content: space-between; }
.ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 5.09091em;
overflow: hidden;
color: #0C0C0D; }
.ds-hero .wrapper .meta .context {
color: #008EA4; }
.ds-hero .wrapper .meta .source {
font-size: 13px;
margin-bottom: 0;
overflow-x: hidden;
text-overflow: ellipsis; }
.ds-hero .wrapper .meta .source {
font-size: 13px;
color: #005A71;
margin-bottom: 0;
overflow-x: hidden;
text-overflow: ellipsis; }
.ds-column-5 .ds-hero .wrapper,
.ds-column-6 .ds-hero .wrapper,
@ -2020,7 +2028,8 @@ main {
.ds-column-7 .ds-hero .wrapper .meta,
.ds-column-8 .ds-hero .wrapper .meta {
grid-column: 1;
grid-row: 1; }
grid-row: 1;
display: flex; }
.ds-column-5 .ds-hero .wrapper .img,
.ds-column-6 .ds-hero .wrapper .img,
.ds-column-7 .ds-hero .wrapper .img,
@ -2036,53 +2045,83 @@ main {
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero .wrapper,
.ds-column-10 .ds-hero .wrapper,
.ds-column-11 .ds-hero .wrapper,
.ds-column-12 .ds-hero .wrapper {
display: flex;
align-items: flex-start;
flex-direction: row-reverse; }
.ds-column-9 .ds-hero .wrapper .img-wrapper,
.ds-column-10 .ds-hero .wrapper .img-wrapper,
.ds-column-11 .ds-hero .wrapper .img-wrapper,
.ds-column-12 .ds-hero .wrapper .img-wrapper {
width: 67%;
margin: 0; }
.ds-column-9 .ds-hero .wrapper .img,
.ds-column-10 .ds-hero .wrapper .img,
.ds-column-11 .ds-hero .wrapper .img,
.ds-column-12 .ds-hero .wrapper .img {
height: 0;
padding-top: 50%; }
.ds-column-9 .ds-hero .wrapper .meta,
.ds-column-10 .ds-hero .wrapper .meta,
.ds-column-11 .ds-hero .wrapper .meta,
.ds-column-12 .ds-hero .wrapper .meta {
width: 33%;
padding: 0 24px 0 0; }
.ds-column-9 .ds-hero .wrapper .meta header,
.ds-column-10 .ds-hero .wrapper .meta header,
.ds-column-11 .ds-hero .wrapper .meta header,
.ds-column-12 .ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 7.63636em;
overflow: hidden;
margin: 0 0 4px; }
.ds-column-9 .ds-hero .wrapper .meta p,
.ds-column-10 .ds-hero .wrapper .meta p,
.ds-column-11 .ds-hero .wrapper .meta p,
.ds-column-12 .ds-hero .wrapper .meta p {
line-height: 1.6; }
.ds-column-9 .ds-hero .cards,
.ds-column-10 .ds-hero .cards,
.ds-column-11 .ds-hero .cards,
.ds-column-12 .ds-hero .cards {
.ds-column-9 .ds-hero,
.ds-column-10 .ds-hero,
.ds-column-11 .ds-hero,
.ds-column-12 .ds-hero {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero.ds-hero-border,
.ds-column-10 .ds-hero.ds-hero-border,
.ds-column-11 .ds-hero.ds-hero-border,
.ds-column-12 .ds-hero.ds-hero-border {
border-top: 1px solid var(--newtab-border-secondary-color);
padding: 20px 0; }
.ds-column-9 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-10 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-11 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-12 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2) {
border-bottom: 1px solid var(--newtab-border-secondary-color);
margin-bottom: 20px; }
.ds-column-9 .ds-hero .wrapper,
.ds-column-10 .ds-hero .wrapper,
.ds-column-11 .ds-hero .wrapper,
.ds-column-12 .ds-hero .wrapper {
border-top: 0;
border-bottom: 0;
margin: 0;
padding: 0 0 20px;
display: flex;
flex-direction: column; }
.ds-column-9 .ds-hero .wrapper .img-wrapper,
.ds-column-10 .ds-hero .wrapper .img-wrapper,
.ds-column-11 .ds-hero .wrapper .img-wrapper,
.ds-column-12 .ds-hero .wrapper .img-wrapper {
margin: 0; }
.ds-column-9 .ds-hero .wrapper .img,
.ds-column-10 .ds-hero .wrapper .img,
.ds-column-11 .ds-hero .wrapper .img,
.ds-column-12 .ds-hero .wrapper .img {
margin-bottom: 16px;
height: 0;
padding-top: 50%; }
.ds-column-9 .ds-hero .wrapper .meta,
.ds-column-10 .ds-hero .wrapper .meta,
.ds-column-11 .ds-hero .wrapper .meta,
.ds-column-12 .ds-hero .wrapper .meta {
flex-grow: 1;
display: flex;
padding: 0 24px 0 0; }
.ds-column-9 .ds-hero .wrapper .meta header,
.ds-column-10 .ds-hero .wrapper .meta header,
.ds-column-11 .ds-hero .wrapper .meta header,
.ds-column-12 .ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 3.81818em;
overflow: hidden;
margin: 0 0 8px; }
.ds-column-9 .ds-hero .wrapper .meta .source,
.ds-column-10 .ds-hero .wrapper .meta .source,
.ds-column-11 .ds-hero .wrapper .meta .source,
.ds-column-12 .ds-hero .wrapper .meta .source {
margin-bottom: 0; }
.ds-column-9 .ds-hero .cards,
.ds-column-10 .ds-hero .cards,
.ds-column-11 .ds-hero .cards,
.ds-column-12 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero .cards .title,
.ds-column-10 .ds-hero .cards .title,
.ds-column-11 .ds-hero .cards .title,
.ds-column-12 .ds-hero .cards .title {
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden; }
.ds-hr {
border: 0;
@ -2100,7 +2139,7 @@ main {
.ds-list:not(.ds-list-full-width) .ds-list-item-title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
max-height: 4.28571em;
overflow: hidden; }
.ds-list:not(.ds-list-full-width) .ds-list-image {
min-width: 72px;
@ -2179,7 +2218,7 @@ main {
.ds-list-full-width .ds-list-item-title {
font-size: 17px;
line-height: 24px;
max-height: 2.82353em;
max-height: 4.23529em;
overflow: hidden; }
.ds-list-full-width .ds-list-image {
@ -2210,7 +2249,7 @@ main {
line-height: 20px;
max-height: 1.42857em;
overflow: hidden;
color: #737373;
color: #005A71;
font-size: 13px;
text-overflow: ellipsis; }
.ds-list-item .ds-list-item-title {
@ -2384,17 +2423,18 @@ main {
.ds-card .meta .title {
font-size: 17px;
line-height: 24px;
max-height: 2.82353em;
max-height: 4.23529em;
overflow: hidden;
font-weight: 600; }
.ds-card .meta .excerpt {
font-size: 14px;
line-height: 20px;
max-height: 5.71429em;
max-height: 4.28571em;
overflow: hidden; }
.ds-card .meta .context,
.ds-card .meta .source {
font-size: 13px; }
font-size: 13px;
color: #005A71; }
.ds-card header {
line-height: 24px;
font-size: 17px;
@ -2410,27 +2450,26 @@ main {
.ds-message .title {
display: flex;
align-items: center; }
.ds-message .title img {
.ds-message .title .glyph {
width: 16px;
height: 16px;
margin: 0 6px 0 0; }
.ds-message .title span {
line-height: 24px;
font-size: 17px;
color: #0C0C0D;
font-weight: 600; }
.ds-message .subtitle {
line-height: 20px;
font-size: 14px;
color: #737373;
margin: 0; }
.ds-message .subtitle span::after {
content: ' '; }
.ds-message .subtitle a:hover,
.ds-message .subtitle a:focus {
text-decoration: underline; }
.ds-message .ds-hr {
margin: 16px 0 8px; }
margin: 0 6px 0 0;
-moz-context-properties: fill;
fill: var(--newtab-icon-secondary-color);
background-position: center center;
background-size: 16px;
background-repeat: no-repeat; }
.ds-message .title .title-text {
line-height: 20px;
font-size: 13px;
color: #737373;
font-weight: 600;
padding-right: 12px; }
.ds-message .title .link {
line-height: 20px;
font-size: 13px; }
.ds-message .title .link:hover, .ds-message .title .link:focus {
text-decoration: underline; }
.ASRouterButton {
font-weight: 600;

File diff suppressed because one or more lines are too long

View File

@ -368,6 +368,9 @@ main {
.ds-outer-wrapper-breakpoint-override main {
width: 1042px; }
.ds-outer-wrapper-breakpoint-override:not(.fixed-search) .search-wrapper .search-inner-wrapper {
width: 736px; }
.base-content-fallback {
height: 100vh; }
@ -1736,6 +1739,9 @@ main {
border: 1px solid var(--newtab-border-secondary-color); }
.asrouter-admin .ds-component {
margin-bottom: 20px; }
.asrouter-admin .optOutNote {
font-size: 12px;
margin-inline-start: 4px; }
.pocket-logged-in-cta {
font-size: 13px;
@ -1834,11 +1840,13 @@ main {
grid-row-gap: var(--gridRowGap); }
.ds-header {
font-size: 17px;
color: #737373;
font-size: 13px;
font-weight: 600;
line-height: 24px;
color: #0C0C0D;
margin: 24px 0 12px; }
line-height: 20px;
margin: 8px 0; }
.ds-header .icon {
fill: var(--newtab-text-secondary-color); }
.ds-message-container {
display: none;
@ -1875,7 +1883,8 @@ main {
.ds-card-grid {
display: grid;
grid-gap: 24px; }
grid-gap: 24px;
margin: 16px 0; }
.ds-card-grid .ds-card {
background: #FFF;
border-radius: 4px; }
@ -1901,14 +1910,6 @@ main {
.ds-column-11 .ds-card-grid,
.ds-column-12 .ds-card-grid {
grid-template-columns: repeat(4, 1fr); }
.ds-column-9 .ds-card-grid .meta .title,
.ds-column-10 .ds-card-grid .meta .title,
.ds-column-11 .ds-card-grid .meta .title,
.ds-column-12 .ds-card-grid .meta .title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
overflow: hidden; }
.ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-3,
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-3,
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-3,
@ -1920,6 +1921,14 @@ main {
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-3 .title {
font-size: 17px;
line-height: 24px; }
.ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden; }
.ds-hero .img {
background-color: var(--newtab-card-placeholder-color);
@ -1937,9 +1946,9 @@ main {
margin: 8px 0; }
.ds-hero .excerpt {
font-size: 15px;
line-height: 23px;
max-height: 6.13333em;
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden;
margin: 4px 0 8px; }
@ -1948,7 +1957,8 @@ main {
padding-top: 0; }
.ds-hero .ds-card {
border: 0; }
border: 0;
padding-bottom: 20px; }
.ds-hero .ds-card p {
margin-top: 4px; }
.ds-hero .ds-card:hover {
@ -1957,11 +1967,6 @@ main {
border-radius: 0; }
.ds-hero .ds-card .meta {
padding: 0; }
.ds-hero .ds-card .meta .title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
overflow: hidden; }
.ds-hero .ds-card .img-wrapper {
margin: 0 0 12px; }
@ -1988,21 +1993,24 @@ main {
.ds-hero .wrapper .img {
height: 0;
padding-top: 50%; }
.ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 2.54545em;
overflow: hidden;
color: #0C0C0D; }
.ds-hero .wrapper .meta p {
font-size: 14px; }
.ds-hero .wrapper .meta p.context {
.ds-hero .wrapper .meta {
display: block;
flex-direction: column;
justify-content: space-between; }
.ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 5.09091em;
overflow: hidden;
color: #0C0C0D; }
.ds-hero .wrapper .meta .context {
color: #008EA4; }
.ds-hero .wrapper .meta .source {
font-size: 13px;
margin-bottom: 0;
overflow-x: hidden;
text-overflow: ellipsis; }
.ds-hero .wrapper .meta .source {
font-size: 13px;
color: #005A71;
margin-bottom: 0;
overflow-x: hidden;
text-overflow: ellipsis; }
.ds-column-5 .ds-hero .wrapper,
.ds-column-6 .ds-hero .wrapper,
@ -2023,7 +2031,8 @@ main {
.ds-column-7 .ds-hero .wrapper .meta,
.ds-column-8 .ds-hero .wrapper .meta {
grid-column: 1;
grid-row: 1; }
grid-row: 1;
display: flex; }
.ds-column-5 .ds-hero .wrapper .img,
.ds-column-6 .ds-hero .wrapper .img,
.ds-column-7 .ds-hero .wrapper .img,
@ -2039,53 +2048,83 @@ main {
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero .wrapper,
.ds-column-10 .ds-hero .wrapper,
.ds-column-11 .ds-hero .wrapper,
.ds-column-12 .ds-hero .wrapper {
display: flex;
align-items: flex-start;
flex-direction: row-reverse; }
.ds-column-9 .ds-hero .wrapper .img-wrapper,
.ds-column-10 .ds-hero .wrapper .img-wrapper,
.ds-column-11 .ds-hero .wrapper .img-wrapper,
.ds-column-12 .ds-hero .wrapper .img-wrapper {
width: 67%;
margin: 0; }
.ds-column-9 .ds-hero .wrapper .img,
.ds-column-10 .ds-hero .wrapper .img,
.ds-column-11 .ds-hero .wrapper .img,
.ds-column-12 .ds-hero .wrapper .img {
height: 0;
padding-top: 50%; }
.ds-column-9 .ds-hero .wrapper .meta,
.ds-column-10 .ds-hero .wrapper .meta,
.ds-column-11 .ds-hero .wrapper .meta,
.ds-column-12 .ds-hero .wrapper .meta {
width: 33%;
padding: 0 24px 0 0; }
.ds-column-9 .ds-hero .wrapper .meta header,
.ds-column-10 .ds-hero .wrapper .meta header,
.ds-column-11 .ds-hero .wrapper .meta header,
.ds-column-12 .ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 7.63636em;
overflow: hidden;
margin: 0 0 4px; }
.ds-column-9 .ds-hero .wrapper .meta p,
.ds-column-10 .ds-hero .wrapper .meta p,
.ds-column-11 .ds-hero .wrapper .meta p,
.ds-column-12 .ds-hero .wrapper .meta p {
line-height: 1.6; }
.ds-column-9 .ds-hero .cards,
.ds-column-10 .ds-hero .cards,
.ds-column-11 .ds-hero .cards,
.ds-column-12 .ds-hero .cards {
.ds-column-9 .ds-hero,
.ds-column-10 .ds-hero,
.ds-column-11 .ds-hero,
.ds-column-12 .ds-hero {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero.ds-hero-border,
.ds-column-10 .ds-hero.ds-hero-border,
.ds-column-11 .ds-hero.ds-hero-border,
.ds-column-12 .ds-hero.ds-hero-border {
border-top: 1px solid var(--newtab-border-secondary-color);
padding: 20px 0; }
.ds-column-9 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-10 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-11 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-12 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2) {
border-bottom: 1px solid var(--newtab-border-secondary-color);
margin-bottom: 20px; }
.ds-column-9 .ds-hero .wrapper,
.ds-column-10 .ds-hero .wrapper,
.ds-column-11 .ds-hero .wrapper,
.ds-column-12 .ds-hero .wrapper {
border-top: 0;
border-bottom: 0;
margin: 0;
padding: 0 0 20px;
display: flex;
flex-direction: column; }
.ds-column-9 .ds-hero .wrapper .img-wrapper,
.ds-column-10 .ds-hero .wrapper .img-wrapper,
.ds-column-11 .ds-hero .wrapper .img-wrapper,
.ds-column-12 .ds-hero .wrapper .img-wrapper {
margin: 0; }
.ds-column-9 .ds-hero .wrapper .img,
.ds-column-10 .ds-hero .wrapper .img,
.ds-column-11 .ds-hero .wrapper .img,
.ds-column-12 .ds-hero .wrapper .img {
margin-bottom: 16px;
height: 0;
padding-top: 50%; }
.ds-column-9 .ds-hero .wrapper .meta,
.ds-column-10 .ds-hero .wrapper .meta,
.ds-column-11 .ds-hero .wrapper .meta,
.ds-column-12 .ds-hero .wrapper .meta {
flex-grow: 1;
display: flex;
padding: 0 24px 0 0; }
.ds-column-9 .ds-hero .wrapper .meta header,
.ds-column-10 .ds-hero .wrapper .meta header,
.ds-column-11 .ds-hero .wrapper .meta header,
.ds-column-12 .ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 3.81818em;
overflow: hidden;
margin: 0 0 8px; }
.ds-column-9 .ds-hero .wrapper .meta .source,
.ds-column-10 .ds-hero .wrapper .meta .source,
.ds-column-11 .ds-hero .wrapper .meta .source,
.ds-column-12 .ds-hero .wrapper .meta .source {
margin-bottom: 0; }
.ds-column-9 .ds-hero .cards,
.ds-column-10 .ds-hero .cards,
.ds-column-11 .ds-hero .cards,
.ds-column-12 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero .cards .title,
.ds-column-10 .ds-hero .cards .title,
.ds-column-11 .ds-hero .cards .title,
.ds-column-12 .ds-hero .cards .title {
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden; }
.ds-hr {
border: 0;
@ -2103,7 +2142,7 @@ main {
.ds-list:not(.ds-list-full-width) .ds-list-item-title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
max-height: 4.28571em;
overflow: hidden; }
.ds-list:not(.ds-list-full-width) .ds-list-image {
min-width: 72px;
@ -2182,7 +2221,7 @@ main {
.ds-list-full-width .ds-list-item-title {
font-size: 17px;
line-height: 24px;
max-height: 2.82353em;
max-height: 4.23529em;
overflow: hidden; }
.ds-list-full-width .ds-list-image {
@ -2213,7 +2252,7 @@ main {
line-height: 20px;
max-height: 1.42857em;
overflow: hidden;
color: #737373;
color: #005A71;
font-size: 13px;
text-overflow: ellipsis; }
.ds-list-item .ds-list-item-title {
@ -2387,17 +2426,18 @@ main {
.ds-card .meta .title {
font-size: 17px;
line-height: 24px;
max-height: 2.82353em;
max-height: 4.23529em;
overflow: hidden;
font-weight: 600; }
.ds-card .meta .excerpt {
font-size: 14px;
line-height: 20px;
max-height: 5.71429em;
max-height: 4.28571em;
overflow: hidden; }
.ds-card .meta .context,
.ds-card .meta .source {
font-size: 13px; }
font-size: 13px;
color: #005A71; }
.ds-card header {
line-height: 24px;
font-size: 17px;
@ -2413,27 +2453,26 @@ main {
.ds-message .title {
display: flex;
align-items: center; }
.ds-message .title img {
.ds-message .title .glyph {
width: 16px;
height: 16px;
margin: 0 6px 0 0; }
.ds-message .title span {
line-height: 24px;
font-size: 17px;
color: #0C0C0D;
font-weight: 600; }
.ds-message .subtitle {
line-height: 20px;
font-size: 14px;
color: #737373;
margin: 0; }
.ds-message .subtitle span::after {
content: ' '; }
.ds-message .subtitle a:hover,
.ds-message .subtitle a:focus {
text-decoration: underline; }
.ds-message .ds-hr {
margin: 16px 0 8px; }
margin: 0 6px 0 0;
-moz-context-properties: fill;
fill: var(--newtab-icon-secondary-color);
background-position: center center;
background-size: 16px;
background-repeat: no-repeat; }
.ds-message .title .title-text {
line-height: 20px;
font-size: 13px;
color: #737373;
font-weight: 600;
padding-right: 12px; }
.ds-message .title .link {
line-height: 20px;
font-size: 13px; }
.ds-message .title .link:hover, .ds-message .title .link:focus {
text-decoration: underline; }
.ASRouterButton {
font-weight: 600;

File diff suppressed because one or more lines are too long

View File

@ -365,6 +365,9 @@ main {
.ds-outer-wrapper-breakpoint-override main {
width: 1042px; }
.ds-outer-wrapper-breakpoint-override:not(.fixed-search) .search-wrapper .search-inner-wrapper {
width: 736px; }
.base-content-fallback {
height: 100vh; }
@ -1733,6 +1736,9 @@ main {
border: 1px solid var(--newtab-border-secondary-color); }
.asrouter-admin .ds-component {
margin-bottom: 20px; }
.asrouter-admin .optOutNote {
font-size: 12px;
margin-inline-start: 4px; }
.pocket-logged-in-cta {
font-size: 13px;
@ -1831,11 +1837,13 @@ main {
grid-row-gap: var(--gridRowGap); }
.ds-header {
font-size: 17px;
color: #737373;
font-size: 13px;
font-weight: 600;
line-height: 24px;
color: #0C0C0D;
margin: 24px 0 12px; }
line-height: 20px;
margin: 8px 0; }
.ds-header .icon {
fill: var(--newtab-text-secondary-color); }
.ds-message-container {
display: none;
@ -1872,7 +1880,8 @@ main {
.ds-card-grid {
display: grid;
grid-gap: 24px; }
grid-gap: 24px;
margin: 16px 0; }
.ds-card-grid .ds-card {
background: #FFF;
border-radius: 4px; }
@ -1898,14 +1907,6 @@ main {
.ds-column-11 .ds-card-grid,
.ds-column-12 .ds-card-grid {
grid-template-columns: repeat(4, 1fr); }
.ds-column-9 .ds-card-grid .meta .title,
.ds-column-10 .ds-card-grid .meta .title,
.ds-column-11 .ds-card-grid .meta .title,
.ds-column-12 .ds-card-grid .meta .title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
overflow: hidden; }
.ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-3,
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-3,
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-3,
@ -1917,6 +1918,14 @@ main {
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-3 .title {
font-size: 17px;
line-height: 24px; }
.ds-column-9 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-10 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-11 .ds-card-grid.ds-card-grid-divisible-by-4 .title,
.ds-column-12 .ds-card-grid.ds-card-grid-divisible-by-4 .title {
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden; }
.ds-hero .img {
background-color: var(--newtab-card-placeholder-color);
@ -1934,9 +1943,9 @@ main {
margin: 8px 0; }
.ds-hero .excerpt {
font-size: 15px;
line-height: 23px;
max-height: 6.13333em;
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden;
margin: 4px 0 8px; }
@ -1945,7 +1954,8 @@ main {
padding-top: 0; }
.ds-hero .ds-card {
border: 0; }
border: 0;
padding-bottom: 20px; }
.ds-hero .ds-card p {
margin-top: 4px; }
.ds-hero .ds-card:hover {
@ -1954,11 +1964,6 @@ main {
border-radius: 0; }
.ds-hero .ds-card .meta {
padding: 0; }
.ds-hero .ds-card .meta .title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
overflow: hidden; }
.ds-hero .ds-card .img-wrapper {
margin: 0 0 12px; }
@ -1985,21 +1990,24 @@ main {
.ds-hero .wrapper .img {
height: 0;
padding-top: 50%; }
.ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 2.54545em;
overflow: hidden;
color: #0C0C0D; }
.ds-hero .wrapper .meta p {
font-size: 14px; }
.ds-hero .wrapper .meta p.context {
.ds-hero .wrapper .meta {
display: block;
flex-direction: column;
justify-content: space-between; }
.ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 5.09091em;
overflow: hidden;
color: #0C0C0D; }
.ds-hero .wrapper .meta .context {
color: #008EA4; }
.ds-hero .wrapper .meta .source {
font-size: 13px;
margin-bottom: 0;
overflow-x: hidden;
text-overflow: ellipsis; }
.ds-hero .wrapper .meta .source {
font-size: 13px;
color: #005A71;
margin-bottom: 0;
overflow-x: hidden;
text-overflow: ellipsis; }
.ds-column-5 .ds-hero .wrapper,
.ds-column-6 .ds-hero .wrapper,
@ -2020,7 +2028,8 @@ main {
.ds-column-7 .ds-hero .wrapper .meta,
.ds-column-8 .ds-hero .wrapper .meta {
grid-column: 1;
grid-row: 1; }
grid-row: 1;
display: flex; }
.ds-column-5 .ds-hero .wrapper .img,
.ds-column-6 .ds-hero .wrapper .img,
.ds-column-7 .ds-hero .wrapper .img,
@ -2036,53 +2045,83 @@ main {
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero .wrapper,
.ds-column-10 .ds-hero .wrapper,
.ds-column-11 .ds-hero .wrapper,
.ds-column-12 .ds-hero .wrapper {
display: flex;
align-items: flex-start;
flex-direction: row-reverse; }
.ds-column-9 .ds-hero .wrapper .img-wrapper,
.ds-column-10 .ds-hero .wrapper .img-wrapper,
.ds-column-11 .ds-hero .wrapper .img-wrapper,
.ds-column-12 .ds-hero .wrapper .img-wrapper {
width: 67%;
margin: 0; }
.ds-column-9 .ds-hero .wrapper .img,
.ds-column-10 .ds-hero .wrapper .img,
.ds-column-11 .ds-hero .wrapper .img,
.ds-column-12 .ds-hero .wrapper .img {
height: 0;
padding-top: 50%; }
.ds-column-9 .ds-hero .wrapper .meta,
.ds-column-10 .ds-hero .wrapper .meta,
.ds-column-11 .ds-hero .wrapper .meta,
.ds-column-12 .ds-hero .wrapper .meta {
width: 33%;
padding: 0 24px 0 0; }
.ds-column-9 .ds-hero .wrapper .meta header,
.ds-column-10 .ds-hero .wrapper .meta header,
.ds-column-11 .ds-hero .wrapper .meta header,
.ds-column-12 .ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 7.63636em;
overflow: hidden;
margin: 0 0 4px; }
.ds-column-9 .ds-hero .wrapper .meta p,
.ds-column-10 .ds-hero .wrapper .meta p,
.ds-column-11 .ds-hero .wrapper .meta p,
.ds-column-12 .ds-hero .wrapper .meta p {
line-height: 1.6; }
.ds-column-9 .ds-hero .cards,
.ds-column-10 .ds-hero .cards,
.ds-column-11 .ds-hero .cards,
.ds-column-12 .ds-hero .cards {
.ds-column-9 .ds-hero,
.ds-column-10 .ds-hero,
.ds-column-11 .ds-hero,
.ds-column-12 .ds-hero {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero.ds-hero-border,
.ds-column-10 .ds-hero.ds-hero-border,
.ds-column-11 .ds-hero.ds-hero-border,
.ds-column-12 .ds-hero.ds-hero-border {
border-top: 1px solid var(--newtab-border-secondary-color);
padding: 20px 0; }
.ds-column-9 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-10 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-11 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2),
.ds-column-12 .ds-hero.ds-hero-border .ds-card:nth-child(-n+2) {
border-bottom: 1px solid var(--newtab-border-secondary-color);
margin-bottom: 20px; }
.ds-column-9 .ds-hero .wrapper,
.ds-column-10 .ds-hero .wrapper,
.ds-column-11 .ds-hero .wrapper,
.ds-column-12 .ds-hero .wrapper {
border-top: 0;
border-bottom: 0;
margin: 0;
padding: 0 0 20px;
display: flex;
flex-direction: column; }
.ds-column-9 .ds-hero .wrapper .img-wrapper,
.ds-column-10 .ds-hero .wrapper .img-wrapper,
.ds-column-11 .ds-hero .wrapper .img-wrapper,
.ds-column-12 .ds-hero .wrapper .img-wrapper {
margin: 0; }
.ds-column-9 .ds-hero .wrapper .img,
.ds-column-10 .ds-hero .wrapper .img,
.ds-column-11 .ds-hero .wrapper .img,
.ds-column-12 .ds-hero .wrapper .img {
margin-bottom: 16px;
height: 0;
padding-top: 50%; }
.ds-column-9 .ds-hero .wrapper .meta,
.ds-column-10 .ds-hero .wrapper .meta,
.ds-column-11 .ds-hero .wrapper .meta,
.ds-column-12 .ds-hero .wrapper .meta {
flex-grow: 1;
display: flex;
padding: 0 24px 0 0; }
.ds-column-9 .ds-hero .wrapper .meta header,
.ds-column-10 .ds-hero .wrapper .meta header,
.ds-column-11 .ds-hero .wrapper .meta header,
.ds-column-12 .ds-hero .wrapper .meta header {
font-size: 22px;
line-height: 28px;
max-height: 3.81818em;
overflow: hidden;
margin: 0 0 8px; }
.ds-column-9 .ds-hero .wrapper .meta .source,
.ds-column-10 .ds-hero .wrapper .meta .source,
.ds-column-11 .ds-hero .wrapper .meta .source,
.ds-column-12 .ds-hero .wrapper .meta .source {
margin-bottom: 0; }
.ds-column-9 .ds-hero .cards,
.ds-column-10 .ds-hero .cards,
.ds-column-11 .ds-hero .cards,
.ds-column-12 .ds-hero .cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 24px; }
.ds-column-9 .ds-hero .cards .title,
.ds-column-10 .ds-hero .cards .title,
.ds-column-11 .ds-hero .cards .title,
.ds-column-12 .ds-hero .cards .title {
font-size: 14px;
line-height: 20px;
max-height: 4.28571em;
overflow: hidden; }
.ds-hr {
border: 0;
@ -2100,7 +2139,7 @@ main {
.ds-list:not(.ds-list-full-width) .ds-list-item-title {
font-size: 14px;
line-height: 20px;
max-height: 2.85714em;
max-height: 4.28571em;
overflow: hidden; }
.ds-list:not(.ds-list-full-width) .ds-list-image {
min-width: 72px;
@ -2179,7 +2218,7 @@ main {
.ds-list-full-width .ds-list-item-title {
font-size: 17px;
line-height: 24px;
max-height: 2.82353em;
max-height: 4.23529em;
overflow: hidden; }
.ds-list-full-width .ds-list-image {
@ -2210,7 +2249,7 @@ main {
line-height: 20px;
max-height: 1.42857em;
overflow: hidden;
color: #737373;
color: #005A71;
font-size: 13px;
text-overflow: ellipsis; }
.ds-list-item .ds-list-item-title {
@ -2384,17 +2423,18 @@ main {
.ds-card .meta .title {
font-size: 17px;
line-height: 24px;
max-height: 2.82353em;
max-height: 4.23529em;
overflow: hidden;
font-weight: 600; }
.ds-card .meta .excerpt {
font-size: 14px;
line-height: 20px;
max-height: 5.71429em;
max-height: 4.28571em;
overflow: hidden; }
.ds-card .meta .context,
.ds-card .meta .source {
font-size: 13px; }
font-size: 13px;
color: #005A71; }
.ds-card header {
line-height: 24px;
font-size: 17px;
@ -2410,27 +2450,26 @@ main {
.ds-message .title {
display: flex;
align-items: center; }
.ds-message .title img {
.ds-message .title .glyph {
width: 16px;
height: 16px;
margin: 0 6px 0 0; }
.ds-message .title span {
line-height: 24px;
font-size: 17px;
color: #0C0C0D;
font-weight: 600; }
.ds-message .subtitle {
line-height: 20px;
font-size: 14px;
color: #737373;
margin: 0; }
.ds-message .subtitle span::after {
content: ' '; }
.ds-message .subtitle a:hover,
.ds-message .subtitle a:focus {
text-decoration: underline; }
.ds-message .ds-hr {
margin: 16px 0 8px; }
margin: 0 6px 0 0;
-moz-context-properties: fill;
fill: var(--newtab-icon-secondary-color);
background-position: center center;
background-size: 16px;
background-repeat: no-repeat; }
.ds-message .title .title-text {
line-height: 20px;
font-size: 13px;
color: #737373;
font-weight: 600;
padding-right: 12px; }
.ds-message .title .link {
line-height: 20px;
font-size: 13px; }
.ds-message .title .link:hover, .ds-message .title .link:focus {
text-decoration: underline; }
.ASRouterButton {
font-weight: 600;

File diff suppressed because one or more lines are too long

View File

@ -2500,10 +2500,21 @@ function relativeTime(timestamp) {
return new Date(timestamp).toLocaleString();
}
const OPT_OUT_PREF = "discoverystream.optOut.0";
const LAYOUT_VARIANTS = {
"basic": "Basic default layout (on by default in nightly)",
"dev-test-all": "A little bit of everything. Good layout for testing all components",
"dev-test-feeds": "Stress testing for slow feeds"
};
class DiscoveryStreamAdmin extends react__WEBPACK_IMPORTED_MODULE_4___default.a.PureComponent {
constructor(props) {
super(props);
this.onEnableToggle = this.onEnableToggle.bind(this);
this.changeEndpointVariant = this.changeEndpointVariant.bind(this);
}
get isOptedOut() {
return this.props.otherPrefs[OPT_OUT_PREF];
}
setConfigValue(name, value) {
@ -2514,6 +2525,13 @@ class DiscoveryStreamAdmin extends react__WEBPACK_IMPORTED_MODULE_4___default.a.
this.setConfigValue("enabled", event.target.checked);
}
changeEndpointVariant(event) {
const endpoint = this.props.state.config.layout_endpoint;
if (endpoint) {
this.setConfigValue("layout_endpoint", endpoint.replace(/layout_variant=.+/, `layout_variant=${event.target.value}`));
}
}
renderComponent(width, component) {
return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"table",
@ -2554,6 +2572,12 @@ class DiscoveryStreamAdmin extends react__WEBPACK_IMPORTED_MODULE_4___default.a.
);
}
isCurrentVariant(id) {
const endpoint = this.props.state.config.layout_endpoint;
const isMatch = endpoint && !!endpoint.match(`layout_variant=${id}`);
return isMatch;
}
renderFeed(feed) {
const { feeds } = this.props.state;
if (!feed.url) {
@ -2594,6 +2618,8 @@ class DiscoveryStreamAdmin extends react__WEBPACK_IMPORTED_MODULE_4___default.a.
}
render() {
const { isOptedOut } = this;
const { config, lastUpdated, layout } = this.props.state;
return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"div",
@ -2602,7 +2628,59 @@ class DiscoveryStreamAdmin extends react__WEBPACK_IMPORTED_MODULE_4___default.a.
"div",
{ className: "dsEnabled" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", { type: "checkbox", checked: config.enabled, onChange: this.onEnableToggle }),
" enabled"
" enabled",
isOptedOut ? react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"span",
{ className: "optOutNote" },
"(Note: User has opted-out. Check this box to reset)"
) : ""
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"h3",
null,
"Endpoint variant"
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"p",
null,
"You can also change this manually by changing this pref: ",
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"code",
null,
"browser.newtabpage.activity-stream.discoverystream.config"
)
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"table",
{ style: config.enabled ? null : { opacity: 0.5 } },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"tbody",
null,
Object.keys(LAYOUT_VARIANTS).map(id => react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
Row,
{ key: id },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"td",
{ className: "min" },
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement("input", { type: "radio", value: id, checked: this.isCurrentVariant(id), onChange: this.changeEndpointVariant })
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"td",
{ className: "min" },
id
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"td",
null,
LAYOUT_VARIANTS[id]
)
))
)
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"h3",
null,
"Caching info"
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"table",
@ -2623,20 +2701,6 @@ class DiscoveryStreamAdmin extends react__WEBPACK_IMPORTED_MODULE_4___default.a.
null,
relativeTime(lastUpdated) || "(no data)"
)
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
Row,
null,
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"td",
{ className: "min" },
"Endpoint"
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
"td",
null,
config.layout_endpoint || "(empty)"
)
)
)
),
@ -3446,7 +3510,7 @@ class ASRouterAdminInner extends react__WEBPACK_IMPORTED_MODULE_4___default.a.Pu
null,
"Discovery Stream"
),
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(DiscoveryStreamAdmin, { state: this.props.DiscoveryStream, dispatch: this.props.dispatch })
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(DiscoveryStreamAdmin, { state: this.props.DiscoveryStream, otherPrefs: this.props.Prefs.values, dispatch: this.props.dispatch })
);
default:
return react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(
@ -3558,7 +3622,7 @@ const _ASRouterAdmin = props => react__WEBPACK_IMPORTED_MODULE_4___default.a.cre
null,
react__WEBPACK_IMPORTED_MODULE_4___default.a.createElement(ASRouterAdminInner, props)
);
const ASRouterAdmin = Object(react_redux__WEBPACK_IMPORTED_MODULE_2__["connect"])(state => ({ Sections: state.Sections, DiscoveryStream: state.DiscoveryStream }))(_ASRouterAdmin);
const ASRouterAdmin = Object(react_redux__WEBPACK_IMPORTED_MODULE_2__["connect"])(state => ({ Sections: state.Sections, DiscoveryStream: state.DiscoveryStream, Prefs: state.Prefs }))(_ASRouterAdmin);
/***/ }),
/* 27 */
@ -7208,7 +7272,7 @@ class SafeAnchor_SafeAnchor extends external_React_default.a.PureComponent {
const isAllowed = ["http:", "https:"].includes(protocol);
if (!isAllowed) {
console.warn(`${protocol} is not allowed for anchor targets.`); // eslint-disable-line no-console
console.warn(`${url} is not allowed for anchor targets.`); // eslint-disable-line no-console
return "";
}
return url;
@ -7370,39 +7434,27 @@ var external_ReactRedux_ = __webpack_require__(24);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage.jsx
class DSMessage_DSMessage extends external_React_default.a.PureComponent {
render() {
let hasSubtitleAndOrLink = this.props.link_text && this.props.link_url;
hasSubtitleAndOrLink = hasSubtitleAndOrLink || this.props.subtitle;
return external_React_default.a.createElement(
"div",
{ className: "ds-message" },
this.props.title && external_React_default.a.createElement(
external_React_default.a.createElement(
"header",
{ className: "title" },
this.props.icon && external_React_default.a.createElement("img", { src: this.props.icon }),
external_React_default.a.createElement(
this.props.icon && external_React_default.a.createElement("div", { className: "glyph", style: { backgroundImage: `url(${this.props.icon})` } }),
this.props.title && external_React_default.a.createElement(
"span",
null,
{ className: "title-text" },
this.props.title
)
),
hasSubtitleAndOrLink && external_React_default.a.createElement(
"p",
{ className: "subtitle" },
this.props.subtitle && external_React_default.a.createElement(
"span",
null,
this.props.subtitle
),
this.props.link_text && this.props.link_url && external_React_default.a.createElement(
"a",
{ href: this.props.link_url },
SafeAnchor_SafeAnchor,
{ className: "link", url: this.props.link_url },
this.props.link_text
)
),
external_React_default.a.createElement("hr", { className: "ds-hr" })
)
);
}
}
@ -7623,14 +7675,18 @@ class Hero_Hero extends external_React_default.a.PureComponent {
"div",
{ className: "meta" },
external_React_default.a.createElement(
"header",
null,
heroRec.title
),
external_React_default.a.createElement(
"p",
{ className: "excerpt" },
heroRec.excerpt
"div",
{ className: "header-and-excerpt" },
external_React_default.a.createElement(
"header",
null,
heroRec.title
),
external_React_default.a.createElement(
"p",
{ className: "excerpt" },
heroRec.excerpt
)
),
heroRec.context ? external_React_default.a.createElement(
"p",
@ -7672,6 +7728,7 @@ var ImpressionStats = __webpack_require__(31);
// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/Navigation/Navigation.jsx
class Navigation_Topic extends external_React_default.a.PureComponent {
render() {
const { url, name } = this.props;
@ -7679,8 +7736,8 @@ class Navigation_Topic extends external_React_default.a.PureComponent {
"li",
null,
external_React_default.a.createElement(
"a",
{ key: name, href: url },
SafeAnchor_SafeAnchor,
{ key: name, url: url },
name
)
);

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,7 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
const PREFERENCES_LOADED_EVENT = "home-pane-loaded";
const DISCOVERY_STREAM_CONFIG_PREF_NAME = "browser.newtabpage.activity-stream.discoverystream.config";
const PREF_SHOW_SPONSORED = "showSponsored";
// These "section" objects are formatted in a way to be similar to the ones from
// SectionsManager to construct the preferences view.
@ -105,7 +106,7 @@ this.AboutPreferences = class AboutPreferences {
async observe(window) {
this.renderPreferences(window, await this.strings, [...PREFS_BEFORE_SECTIONS,
...this.store.getState().Sections, ...PREFS_AFTER_SECTIONS], this.store.getState().DiscoveryStream.config.enabled);
...this.store.getState().Sections, ...PREFS_AFTER_SECTIONS], this.store.getState().DiscoveryStream.config);
}
/**
@ -133,7 +134,7 @@ this.AboutPreferences = class AboutPreferences {
* Render preferences to an about:preferences content window with the provided
* strings and preferences structure.
*/
renderPreferences({document, Preferences, gHomePane}, strings, prefStructure, discoveryStreamEnabled) {
renderPreferences({document, Preferences, gHomePane}, strings, prefStructure, discoveryStreamConfig) {
// Helper to create a new element and append it
const createAppend = (tag, parent, options) => parent.appendChild(
document.createXULElement(tag, options));
@ -167,6 +168,10 @@ this.AboutPreferences = class AboutPreferences {
`href="data:text/css,${encodeURIComponent(CUSTOM_CSS)}" type="text/css"`),
document.documentElement);
// Both Topstories and Discovery Stream need to toggle the same pref but
// we can't have two elements linked to the same pref so we reuse the same.
let sponsoredStoriesCheckbox = null;
// Insert a new group immediately after the homepage one
const homeGroup = document.getElementById("homepageGroup");
const contentsGroup = homeGroup.insertAdjacentElement("afterend", homeGroup.cloneNode());
@ -264,12 +269,15 @@ this.AboutPreferences = class AboutPreferences {
subcheck.classList.add("indent");
subcheck.setAttribute("label", formatString(nested.titleString));
linkPref(subcheck, nested.name, "bool");
if (nested.name === PREF_SHOW_SPONSORED) {
sponsoredStoriesCheckbox = subcheck;
}
});
});
if (discoveryStreamEnabled) {
if (discoveryStreamConfig.enabled) {
// If Discovery Stream is enabled hide Home Content options
contentsGroup.style.visibility = "hidden";
contentsGroup.style.visibility = "collapse";
const discoveryGroup = homeGroup.insertAdjacentElement("afterend", homeGroup.cloneNode());
discoveryGroup.id = "discoveryContentsGroup";
@ -277,8 +285,31 @@ this.AboutPreferences = class AboutPreferences {
createAppend("label", discoveryGroup)
.appendChild(document.createElementNS(HTML_NS, "h2"))
.textContent = formatString("prefs_content_discovery_header");
createAppend("description", discoveryGroup)
.textContent = formatString("prefs_content_discovery_description");
const descriptionHbox = createAppend("hbox", discoveryGroup);
const discoveryGroupDescription = createAppend("description", descriptionHbox);
discoveryGroupDescription.textContent = formatString("prefs_content_discovery_description");
discoveryGroupDescription.classList.add("tail-with-learn-more");
// Add the Learn more link in the description
const topstoriesSection = prefStructure.find(s => s.id === "topstories");
const learnMoreURL = topstoriesSection && topstoriesSection.learnMore.link.href;
const link = createAppend("label", descriptionHbox);
link.classList.add("learn-sponsored");
link.classList.add("text-link");
link.setAttribute("href", learnMoreURL);
link.textContent = formatString("prefs_topstories_sponsored_learn_more");
if (discoveryStreamConfig.show_spocs && sponsoredStoriesCheckbox) {
sponsoredStoriesCheckbox.remove();
sponsoredStoriesCheckbox.classList.remove("indent");
discoveryGroup.appendChild(sponsoredStoriesCheckbox);
} else if (discoveryStreamConfig.show_spocs) {
// If there is no element to reuse create one
const discoveryDetails = createAppend("vbox", discoveryGroup);
const subcheck = createAppend("checkbox", discoveryDetails);
subcheck.setAttribute("label", formatString("prefs_topstories_options_sponsored_label"));
linkPref(subcheck, PREF_SHOW_SPONSORED, "bool");
}
const contentDiscoveryButton = document.createElementNS(HTML_NS, "button");
contentDiscoveryButton.classList.add("contentDiscoveryButton");
@ -292,7 +323,14 @@ this.AboutPreferences = class AboutPreferences {
// Unconditionally update the UI for a fast user response and in
// order to help with testing
discoveryGroup.style.display = "none";
contentsGroup.style.visibility = "visible";
contentsGroup.style.visibility = "";
if (sponsoredStoriesCheckbox) {
// If we reused the checkbox element we need to restore it
sponsoredStoriesCheckbox.remove();
sponsoredStoriesCheckbox.classList.add("indent");
const topstoriesDetails = document.querySelector("[data-subcategory='topstories'] .indent");
topstoriesDetails.appendChild(sponsoredStoriesCheckbox);
}
if (experiment) {
await PreferenceExperiments.stop(experiment.name, {
resetValue: true,

View File

@ -222,10 +222,11 @@ const PREFS_CONFIG = new Map([
})[geo];
const isEnabled = IS_NIGHTLY_OR_UNBRANDED_BUILD && locales && locales.includes(locale);
return JSON.stringify({
api_key_pref: "extensions.pocket.oAuthConsumerKey",
enabled: isEnabled,
show_spocs: geo === "US",
// This is currently an exmple layout used for dev purposes.
layout_endpoint: "https://getpocket.com/v3/newtab/layout?version=1&consumer_key=40249-e88c401e1b1f2242d9e441c4&layout_variant=basic",
layout_endpoint: "https://getpocket.com/v3/newtab/layout?version=1&consumer_key=$apiKey&layout_variant=basic",
});
},
}],

View File

@ -4,6 +4,7 @@
"use strict";
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
ChromeUtils.defineModuleGetter(this, "perfService", "resource://activity-stream/common/PerfService.jsm");
@ -32,12 +33,25 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
this._prefCache = {};
}
finalLayoutEndpoint(url, apiKey) {
if (url.includes("$apiKey") && !apiKey) {
throw new Error(`Layout Endpoint - An API key was specified but none configured: ${url}`);
}
return url.replace("$apiKey", apiKey);
}
get config() {
if (this._prefCache.config) {
return this._prefCache.config;
}
try {
this._prefCache.config = JSON.parse(this.store.getState().Prefs.values[PREF_CONFIG]);
const layoutUrl = this._prefCache.config.layout_endpoint;
const apiKeyPref = this._prefCache.config.api_key_pref;
if (layoutUrl && apiKeyPref) {
const apiKey = Services.prefs.getCharPref(apiKeyPref, "");
this._prefCache.config.layout_endpoint = this.finalLayoutEndpoint(layoutUrl, apiKey);
}
// Modify the cached config with the user set opt-out for other consumers
this._prefCache.config.enabled = this._prefCache.config.enabled &&
@ -46,7 +60,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
// istanbul ignore next
this._prefCache.config = {};
// istanbul ignore next
Cu.reportError(`Could not parse preference. Try resetting ${PREF_CONFIG} in about:config.`);
Cu.reportError(`Could not parse preference. Try resetting ${PREF_CONFIG} in about:config. ${e}`);
}
return this._prefCache.config;
}
@ -641,7 +655,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
break;
// Check if spocs was disabled. Remove them if they were.
case PREF_SHOW_SPONSORED:
await this.loadSpocs();
await this.loadSpocs(update => this.store.dispatch(ac.BroadcastToContent(update)));
break;
}
break;

View File

@ -93,6 +93,8 @@ prefs_home_header=Endalc'had Degemer Firefox
prefs_home_description=Dibabit peseurt endalc'had a fell deoc'h kaout war ho skramm Firefox Degemer.
prefs_content_discovery_header=Degemer Firefox
prefs_content_discovery_description=Gant ann dizoloadenn endalc'hadoù e Firefox Home e c'hallit dizoloiñ pennadoù a berzhded uhel eus pep lec'h er web.
prefs_content_discovery_button=Diweredekaat an dizoloadenn endalc'hadoù
# LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
# plural forms used in a drop down of multiple row options (1 row, 2 rows).

View File

@ -92,6 +92,9 @@ section_disclaimer_topstories_buttontext=சரி, புரிந்தது
prefs_home_header=Firefox முகப்பு உள்ளடக்கம்
prefs_home_description=உங்கள் பயர்பாக்ஸ் முகப்புத் திரையில் என்ன உள்ளடக்கம் வேண்டுமென்று தேர்ந்தெடு.
prefs_content_discovery_header=பயர்பாஃசு முகப்பு
prefs_content_discovery_description=பயர்பாஃசு முகப்பில் உள்ள உள்ளடக்க கண்டுபிடிப்பு, வலைத்தளங்களில் உள்ள உயர் தர, தொடர்புடைய கட்டுரைகளைக் கண்டறிய அனுமதிக்கிறது.
prefs_content_discovery_button=உள்ளடக்க கண்டுபிடிப்பை முடக்கு
# LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
# plural forms used in a drop down of multiple row options (1 row, 2 rows).
@ -145,7 +148,10 @@ pocket_read_more=பிரபலமான தலைப்புகள்:
# LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
# end of the list of popular topic links.
pocket_read_even_more=இன்னும் கதைகளைப் பார்க்கவும்
pocket_more_reccommendations=மேலும் பரிந்துரைகள்
pocket_how_it_works=இது எப்படி செயல்படுகிறது
pocket_cta_button=பாக்கெட் பெறுக
pocket_cta_text=பாக்கெட்டில் நீங்கள் விரும்பும் கதையைச் சேமித்தால், அதுவே உங்கள் மனதை வெள்ளும் வாசித்தலைத் தரும்.
highlights_empty_state=உலாவலைத் தொடங்கவும், மேலும் நாங்கள் சில சிறந்த கட்டுரைகள், காணொளிகள், மற்றும் நீங்கள் சமீபத்தில் பார்த்த அல்லது புத்தகக்குறியிட்ட பக்கங்களை இங்கே காட்டுவோம்.
# LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
@ -206,3 +212,4 @@ firstrun_continue_to_login=தொடர்க
firstrun_skip_login=இந்த படிநிலையைத் தவிர்
# LOCALIZATION NOTE (context_menu_title): Action tooltip to open a context menu
context_menu_title=பட்டியைத் திற

View File

@ -41,8 +41,8 @@ window.gActivityStreamStrings = {
"prefs_home_header": "Endalc'had Degemer Firefox",
"prefs_home_description": "Dibabit peseurt endalc'had a fell deoc'h kaout war ho skramm Firefox Degemer.",
"prefs_content_discovery_header": "Degemer Firefox",
"prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
"prefs_content_discovery_button": "Turn Off Content Discovery",
"prefs_content_discovery_description": "Gant ann dizoloadenn endalc'hadoù e Firefox Home e c'hallit dizoloiñ pennadoù a berzhded uhel eus pep lec'h er web.",
"prefs_content_discovery_button": "Diweredekaat an dizoloadenn endalc'hadoù",
"prefs_section_rows_option": "{num} renk;{num} renk;{num} renk;{num} a renkoù;{num} renk",
"prefs_search_header": "Klask web",
"prefs_topsites_description": "Al lec'hiennoù a weladennit ar muiañ",

File diff suppressed because one or more lines are too long

View File

@ -40,9 +40,9 @@ window.gActivityStreamStrings = {
"section_disclaimer_topstories_buttontext": "சரி, புரிந்தது",
"prefs_home_header": "Firefox முகப்பு உள்ளடக்கம்",
"prefs_home_description": "உங்கள் பயர்பாக்ஸ் முகப்புத் திரையில் என்ன உள்ளடக்கம் வேண்டுமென்று தேர்ந்தெடு.",
"prefs_content_discovery_header": "Firefox Home",
"prefs_content_discovery_description": "Content Discovery in Firefox Home allows you to discover high-quality, relevant articles from across the web.",
"prefs_content_discovery_button": "Turn Off Content Discovery",
"prefs_content_discovery_header": "பயர்பாஃசு முகப்பு",
"prefs_content_discovery_description": "பயர்பாஃசு முகப்பில் உள்ள உள்ளடக்க கண்டுபிடிப்பு, வலைத்தளங்களில் உள்ள உயர் தர, தொடர்புடைய கட்டுரைகளைக் கண்டறிய அனுமதிக்கிறது.",
"prefs_content_discovery_button": "உள்ளடக்க கண்டுபிடிப்பை முடக்கு",
"prefs_section_rows_option": "{num} வரிசை;{num} வரிசைகள்",
"prefs_search_header": "வலைதள தேடல்",
"prefs_topsites_description": "நீங்கள் மிகவும் பார்வையிடும் தளங்கள்",
@ -77,10 +77,10 @@ window.gActivityStreamStrings = {
"topsites_form_image_validation": "படத்தை ஏற்றுவதில் தோல்வி. வேறு URL ஐ முயற்சிக்கவும்.",
"pocket_read_more": "பிரபலமான தலைப்புகள்:",
"pocket_read_even_more": "இன்னும் கதைகளைப் பார்க்கவும்",
"pocket_more_reccommendations": "More Recommendations",
"pocket_more_reccommendations": "மேலும் பரிந்துரைகள்",
"pocket_how_it_works": "இது எப்படி செயல்படுகிறது",
"pocket_cta_button": "Get Pocket",
"pocket_cta_text": "Save the stories you love in Pocket, and fuel your mind with fascinating reads.",
"pocket_cta_button": "பாக்கெட் பெறுக",
"pocket_cta_text": "பாக்கெட்டில் நீங்கள் விரும்பும் கதையைச் சேமித்தால், அதுவே உங்கள் மனதை வெள்ளும் வாசித்தலைத் தரும்.",
"highlights_empty_state": "உலாவலைத் தொடங்கவும், மேலும் நாங்கள் சில சிறந்த கட்டுரைகள், காணொளிகள், மற்றும் நீங்கள் சமீபத்தில் பார்த்த அல்லது புத்தகக்குறியிட்ட பக்கங்களை இங்கே காட்டுவோம்.",
"topstories_empty_state": "நீங்கள் முடித்துவிட்டீர்கள். {provider} இலிருந்து கூடுதல் கதைகளுக்கு பின்னர் பாருங்கள். காத்திருக்க முடியவில்லையா? இணையத்திலிருந்து கூடுதலான கதைகளைக் கண்டுபிடிக்க பிரபலமான தலைப்பைத் தேர்ந்தெடுங்கள்.",
"manual_migration_explanation2": "மற்றொரு உலாவியின் புத்தகக்குறிகள், வரலாறு மற்றும் கடவுச்சொற்களுடன் பயர்பாக்சை முயற்சித்துப் பாருங்கள்.",
@ -110,5 +110,5 @@ window.gActivityStreamStrings = {
"firstrun_privacy_notice": "தனியுரிமை அறிவிப்பு",
"firstrun_continue_to_login": "தொடர்க",
"firstrun_skip_login": "இந்த படிநிலையைத் தவிர்",
"context_menu_title": "Open menu"
"context_menu_title": "பட்டியைத் திற"
};

View File

@ -142,18 +142,20 @@ describe("AboutPreferences Feed", () => {
createElementNS: sandbox.stub().callsFake((NS, el) => node),
getElementById: sandbox.stub().returns(node),
insertBefore: sandbox.stub().returnsArg(0),
querySelector: sandbox.stub().returns({appendChild: sandbox.stub()}),
},
Preferences,
gHomePane,
}, strings, prefStructure, DiscoveryStream.config.enabled);
}, strings, prefStructure, DiscoveryStream.config);
beforeEach(() => {
node = {
appendChild: sandbox.stub().returnsArg(0),
addEventListener: sandbox.stub(),
classList: {add: sandbox.stub()},
classList: {add: sandbox.stub(), remove: sandbox.stub()},
cloneNode: sandbox.stub().returnsThis(),
insertAdjacentElement: sandbox.stub().returnsArg(1),
setAttribute: sandbox.stub(),
remove: sandbox.stub(),
style: {},
};
strings = {};
@ -298,7 +300,7 @@ describe("AboutPreferences Feed", () => {
describe("#DiscoveryStream", () => {
let PreferenceExperimentsStub;
beforeEach(() => {
DiscoveryStream = {config: {enabled: true}};
DiscoveryStream = {config: {enabled: true, show_spocs: false}};
PreferenceExperimentsStub = {
getAllActive: sandbox.stub().resolves([{name: "discoverystream", preferenceName: "browser.newtabpage.activity-stream.discoverystream.config"}]),
stop: sandbox.stub().resolves(),
@ -321,9 +323,9 @@ describe("AboutPreferences Feed", () => {
assert.equal(node.textContent, "prefs_content_discovery_button");
assert.calledWith(documentStub.createElementNS, "http://www.w3.org/1999/xhtml", "button");
// node points to contentsGroup, style is set to hidden if Discovery
// node points to contentsGroup, style is set to collapse if Discovery
// Stream is enabled
assert.propertyVal(node.style, "visibility", "hidden");
assert.propertyVal(node.style, "visibility", "collapse");
});
it("should request Discovery Stream opt-out on button click", async () => {
testRender();
@ -345,6 +347,49 @@ describe("AboutPreferences Feed", () => {
assert.calledOnce(PreferenceExperimentsStub.stop);
assert.calledWithExactly(PreferenceExperimentsStub.stop, "discoverystream", {resetValue: true, reason: "individual-opt-out"});
});
it("should render the spocs opt out checkbox if show_spocs is true", () => {
const spy = sandbox.spy(instance, "renderPreferences");
DiscoveryStream = {config: {enabled: true, show_spocs: true}};
testRender();
const {createXULElement} = spy.firstCall.args[0].document;
assert.calledWith(createXULElement, "checkbox");
assert.calledWith(Preferences.add, {id: "browser.newtabpage.activity-stream.showSponsored", type: "bool"});
});
it("should not render the spocs opt out checkbox if show_spocs is false", () => {
const spy = sandbox.spy(instance, "renderPreferences");
DiscoveryStream = {config: {enabled: true, show_spocs: false}};
testRender();
const {createXULElement} = spy.firstCall.args[0].document;
assert.neverCalledWith(createXULElement, "checkbox");
assert.neverCalledWith(Preferences.add, {id: "browser.newtabpage.activity-stream.showSponsored", type: "bool"});
});
describe("spocs pref checkbox", () => {
beforeEach(() => {
DiscoveryStream = {config: {enabled: true, show_spocs: true}};
prefStructure = [{pref: {nestedPrefs: [{titleString: "spocs", name: "showSponsored"}]}}];
});
it("should remove the topstories spocs checkbox", () => {
testRender();
assert.calledOnce(node.remove);
assert.calledOnce(node.classList.remove);
assert.calledWith(node.classList.remove, "indent");
});
it("should restore the checkbox when leaving the experiment", async () => {
const spy = sandbox.spy(instance, "renderPreferences");
testRender();
const [{document}] = spy.firstCall.args;
// Trigger the button click listener
await node.addEventListener.firstCall.args[1]();
assert.calledOnce(document.querySelector);
assert.calledWith(document.querySelector, "[data-subcategory='topstories'] .indent");
});
});
});
});
});

View File

@ -109,6 +109,71 @@ describe("DiscoveryStreamFeed", () => {
});
});
describe("#loadLayoutEndPointUsingPref", () => {
it("should return endpoint if valid key", async () => {
const endpoint = feed.finalLayoutEndpoint("https://somedomain.org/stories?consumer_key=$apiKey", "test_key_val");
assert.equal("https://somedomain.org/stories?consumer_key=test_key_val", endpoint);
});
it("should throw error if key is empty", async () => {
assert.throws(() => {
feed.finalLayoutEndpoint("https://somedomain.org/stories?consumer_key=$apiKey", "");
});
});
it("should return url if $apiKey is missing in layout_endpoint", async () => {
const endpoint = feed.finalLayoutEndpoint("https://somedomain.org/stories?consumer_key=", "test_key_val");
assert.equal("https://somedomain.org/stories?consumer_key=", endpoint);
});
it("should update config layout_endpoint based on api_key_pref value", async () => {
feed.store.getState = () => ({
Prefs: {
values: {
[CONFIG_PREF_NAME]: JSON.stringify({
api_key_pref: "test_api_key_pref",
enabled: true,
layout_endpoint: "https://somedomain.org/stories?consumer_key=$apiKey",
}),
},
},
});
sandbox.stub(global.Services.prefs, "getCharPref").returns("test_api_key_val");
assert.equal("https://somedomain.org/stories?consumer_key=test_api_key_val", feed.config.layout_endpoint);
});
it("should not update config layout_endpoint if api_key_pref missing", async () => {
feed.store.getState = () => ({
Prefs: {
values: {
[CONFIG_PREF_NAME]: JSON.stringify({
enabled: true,
layout_endpoint: "https://somedomain.org/stories?consumer_key=1234",
}),
},
},
});
sandbox.stub(global.Services.prefs, "getCharPref").returns("test_api_key_val");
assert.notCalled(global.Services.prefs.getCharPref);
assert.equal("https://somedomain.org/stories?consumer_key=1234", feed.config.layout_endpoint);
});
it("should not set config layout_endpoint if layout_endpoint missing in prefs", async () => {
feed.store.getState = () => ({
Prefs: {
values: {
[CONFIG_PREF_NAME]: JSON.stringify({
enabled: true,
}),
},
},
});
sandbox.stub(global.Services.prefs, "getCharPref").returns("test_api_key_val");
assert.notCalled(global.Services.prefs.getCharPref);
assert.isUndefined(feed.config.layout_endpoint);
});
});
describe("#loadComponentFeeds", () => {
let fakeCache;
let fakeDiscoveryStream;
@ -793,6 +858,20 @@ describe("DiscoveryStreamFeed", () => {
});
});
describe("#onAction: PREF_SHOW_SPONSORED", () => {
it("should call loadSpocs when preference changes", async () => {
sandbox.stub(feed, "loadSpocs").resolves();
sandbox.stub(feed.store, "dispatch");
await feed.onAction({type: at.PREF_CHANGED, data: {name: "showSponsored"}});
assert.calledOnce(feed.loadSpocs);
const [dispatchFn] = feed.loadSpocs.firstCall.args;
dispatchFn({});
assert.calledWith(feed.store.dispatch, ac.BroadcastToContent({}));
});
});
describe("#isExpired", () => {
it("should throw if the key is not valid", () => {
assert.throws(() => {

View File

@ -12,8 +12,8 @@ function assertAllSitesNotListed(win) {
}
// Test selecting and removing all sites one by one
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
add_task(async function test_selectRemove() {
let hosts = await addTestData([
{
usage: 1024,
origin: "https://account.xyz.com",
@ -22,7 +22,6 @@ add_task(async function() {
{
usage: 1024,
origin: "https://shopping.xyz.com",
persisted: false,
},
{
usage: 1024,
@ -32,12 +31,11 @@ add_task(async function() {
{
usage: 1024,
origin: "http://email.bar.com",
persisted: false,
},
]);
let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
let updatePromise = promiseSiteDataManagerSitesUpdated();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatePromise;
await openSiteDataSettingsDialog();
@ -50,7 +48,7 @@ add_task(async function() {
let settingsDialogClosePromise = null;
// Test the initial state
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
// Test the "Cancel" button
settingsDialogClosePromise = promiseSettingsDialogClose();
@ -61,7 +59,7 @@ add_task(async function() {
cancelBtn.doCommand();
await settingsDialogClosePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
// Test the "Save Changes" button but cancelling save
let cancelPromise = BrowserTestUtils.promiseAlertDialogOpen("cancel");
@ -76,7 +74,7 @@ add_task(async function() {
cancelBtn.doCommand();
await settingsDialogClosePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
// Test the "Save Changes" button and accepting save
let acceptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept");
@ -93,7 +91,7 @@ add_task(async function() {
await openSiteDataSettingsDialog();
assertAllSitesNotListed(win);
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
function removeAllSitesOneByOne() {
@ -109,8 +107,8 @@ add_task(async function() {
});
// Test selecting and removing partial sites
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
add_task(async function test_removePartialSites() {
let hosts = await addTestData([
{
usage: 1024,
origin: "https://account.xyz.com",
@ -141,15 +139,10 @@ add_task(async function() {
origin: "https://127.0.0.1",
persisted: false,
},
{
usage: 1024,
origin: "https://[0:0:0:0:0:0:0:1]",
persisted: true,
},
]);
let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
let updatePromise = promiseSiteDataManagerSitesUpdated();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatePromise;
await openSiteDataSettingsDialog();
@ -163,18 +156,18 @@ add_task(async function() {
let settingsDialogClosePromise = null;
// Test the initial state
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
// Test the "Cancel" button
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
cancelBtn = frameDoc.getElementById("cancel");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
await removeSelectedSite(hosts.slice(0, 2));
assertSitesListed(doc, hosts.slice(2));
cancelBtn.doCommand();
await settingsDialogClosePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
// Test the "Save Changes" button but canceling save
removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen("cancel", REMOVE_DIALOG_URL);
@ -182,44 +175,49 @@ add_task(async function() {
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
cancelBtn = frameDoc.getElementById("cancel");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
await removeSelectedSite(hosts.slice(0, 2));
assertSitesListed(doc, hosts.slice(2));
saveBtn.doCommand();
await removeDialogOpenPromise;
cancelBtn.doCommand();
await settingsDialogClosePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
// Test the "Save Changes" button and accepting save
removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen("accept", REMOVE_DIALOG_URL);
settingsDialogClosePromise = promiseSettingsDialogClose();
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
saveBtn = frameDoc.getElementById("save");
removeSelectedSite(fakeHosts.slice(0, 2));
assertSitesListed(doc, fakeHosts.slice(2));
await removeSelectedSite(hosts.slice(0, 2));
assertSitesListed(doc, hosts.slice(2));
saveBtn.doCommand();
await removeDialogOpenPromise;
await settingsDialogClosePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts.slice(2));
assertSitesListed(doc, hosts.slice(2));
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
function removeSelectedSite(hosts) {
function removeSelectedSite(removeHosts) {
frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
is(removeBtn.disabled, true, "Should start with disabled removeSelected button");
let sitesList = frameDoc.getElementById("sitesList");
hosts.forEach(host => {
removeHosts.forEach(host => {
let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
if (site) {
site.click();
let currentSelectedIndex = sitesList.selectedIndex;
is(removeBtn.disabled, false, "Should enable the removeSelected button");
removeBtn.doCommand();
is(sitesList.selectedIndex, currentSelectedIndex);
let newSelectedIndex = sitesList.selectedIndex;
if (currentSelectedIndex >= sitesList.itemCount) {
is(newSelectedIndex, currentSelectedIndex - 1);
} else {
is(newSelectedIndex, currentSelectedIndex);
}
} else {
ok(false, `Should not select and remove inexistent site of ${host}`);
}
@ -229,7 +227,7 @@ add_task(async function() {
// Test searching and then removing only visible sites
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
let hosts = await addTestData([
{
usage: 1024,
origin: "https://account.xyz.com",
@ -251,9 +249,9 @@ add_task(async function() {
persisted: false,
},
]);
let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
let updatePromise = promiseSiteDataManagerSitesUpdated();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatePromise;
await openSiteDataSettingsDialog();
@ -265,7 +263,7 @@ add_task(async function() {
let searchBox = frameDoc.getElementById("searchBox");
searchBox.value = "xyz";
searchBox.doCommand();
assertSitesListed(doc, fakeHosts.filter(host => host.includes("xyz")));
assertSitesListed(doc, hosts.filter(host => host.includes("xyz")));
// Test only removing all visible sites listed
updatePromise = promiseSiteDataManagerSitesUpdated();
@ -279,15 +277,15 @@ add_task(async function() {
await settingsDialogClosePromise;
await updatePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts.filter(host => !host.includes("xyz")));
assertSitesListed(doc, hosts.filter(host => !host.includes("xyz")));
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Test dynamically clearing all site data
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
let hosts = await addTestData([
{
usage: 1024,
origin: "https://account.xyz.com",
@ -299,28 +297,28 @@ add_task(async function() {
persisted: false,
},
]);
let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
let updatePromise = promiseSiteDataManagerSitesUpdated();
// Test the initial state
let updatePromise = promiseSiteDataManagerSitesUpdated();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatePromise;
await openSiteDataSettingsDialog();
let doc = gBrowser.selectedBrowser.contentDocument;
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
// Add more sites dynamically
mockSiteDataManager.fakeSites.push({
usage: 1024,
principal: Services.scriptSecurityManager
.createCodebasePrincipalFromOrigin("http://cinema.bar.com"),
persisted: true,
}, {
usage: 1024,
principal: Services.scriptSecurityManager
.createCodebasePrincipalFromOrigin("http://email.bar.com"),
persisted: false,
});
await addTestData([
{
usage: 1024,
origin: "http://cinema.bar.com",
persisted: true,
},
{
usage: 1024,
origin: "http://email.bar.com",
persisted: false,
},
]);
// Test clearing all site data dynamically
let win = gBrowser.selectedBrowser.contentWindow;
@ -338,6 +336,6 @@ add_task(async function() {
await openSiteDataSettingsDialog();
assertAllSitesNotListed(win);
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

View File

@ -1,8 +1,8 @@
"use strict";
// Test not displaying sites which store 0 byte and don't have persistent storage.
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
add_task(async function test_exclusions() {
let hosts = await addTestData([
{
usage: 0,
origin: "https://account.xyz.com",
@ -30,23 +30,22 @@ add_task(async function() {
persisted: false,
},
]);
let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
let updatePromise = promiseSiteDataManagerSitesUpdated();
let doc = gBrowser.selectedBrowser.contentDocument;
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts.filter(host => host != "shopping.xyz.com"));
assertSitesListed(doc, hosts.filter(host => host != "shopping.xyz.com"));
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Test grouping and listing sites across scheme, port and origin attributes by host
add_task(async function() {
const quotaUsage = 1024;
mockSiteDataManager.register(SiteDataManager, [
add_task(async function test_grouping() {
let quotaUsage = 7000000;
await addTestData([
{
usage: quotaUsage,
origin: "https://account.xyz.com^userContextId=1",
@ -91,40 +90,51 @@ add_task(async function() {
is(columns[1].value, "5", "Should group cookies across scheme, port and origin attributes");
let [value, unit] = DownloadUtils.convertByteUnits(quotaUsage * mockSiteDataManager.fakeSites.length);
Assert.deepEqual(frameDoc.l10n.getAttributes(columns[2]), {
id: "site-usage-persistent",
args: { value, unit },
}, "Should sum up usages across scheme, port, origin attributes and persistent status");
let [value, unit] = DownloadUtils.convertByteUnits(quotaUsage * 4);
let l10nAttributes = frameDoc.l10n.getAttributes(columns[2]);
is(l10nAttributes.id, "site-usage-persistent",
"Should show the site as persistent if one origin is persistent.");
// The shown quota can be slightly larger than the raw data we put in (though it should
// never be smaller), but that doesn't really matter to us since we only want to test that
// the site data dialog accumulates this into a single column.
ok(parseFloat(l10nAttributes.args.value) >= parseFloat(value),
"Should show the correct accumulated quota size.");
is(l10nAttributes.args.unit, unit, "Should show the correct quota size unit.");
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Test sorting
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
add_task(async function test_sorting() {
let testData = [
{
baseDomain: "xyz.com",
usage: 1024,
origin: "https://account.xyz.com",
cookies: 6,
persisted: true,
},
{
baseDomain: "foo.com",
usage: 1024 * 2,
origin: "https://books.foo.com",
cookies: 0,
persisted: false,
},
{
baseDomain: "bar.com",
usage: 1024 * 3,
origin: "http://cinema.bar.com",
cookies: 3,
persisted: true,
},
]);
];
await addTestData(testData);
let updatePromise = promiseSiteDataManagerSitesUpdated();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatePromise;
await openSiteDataSettingsDialog();
@ -137,144 +147,39 @@ add_task(async function() {
let cookiesCol = frameDoc.getElementById("cookiesCol");
let sitesList = frameDoc.getElementById("sitesList");
// Test default sorting
assertSortByUsage("descending");
function getHostOrder() {
let siteItems = sitesList.getElementsByTagName("richlistitem");
return Array.from(siteItems).map(item => item.getAttribute("host"));
}
// Test default sorting by usage, descending.
Assert.deepEqual(getHostOrder(),
["cinema.bar.com", "books.foo.com", "account.xyz.com"], "Has sorted descending by usage");
// Test sorting on the usage column
usageCol.click();
assertSortByUsage("ascending");
Assert.deepEqual(getHostOrder(),
["account.xyz.com", "books.foo.com", "cinema.bar.com"], "Has sorted ascending by usage");
usageCol.click();
assertSortByUsage("descending");
Assert.deepEqual(getHostOrder(),
["cinema.bar.com", "books.foo.com", "account.xyz.com"], "Has sorted descending by usage");
// Test sorting on the host column
hostCol.click();
assertSortByBaseDomain("ascending");
Assert.deepEqual(getHostOrder(),
["cinema.bar.com", "books.foo.com", "account.xyz.com"], "Has sorted ascending by base domain");
hostCol.click();
assertSortByBaseDomain("descending");
Assert.deepEqual(getHostOrder(),
["account.xyz.com", "books.foo.com", "cinema.bar.com"], "Has sorted descending by base domain");
// Test sorting on the cookies column
cookiesCol.click();
assertSortByCookies("ascending");
Assert.deepEqual(getHostOrder(),
["books.foo.com", "cinema.bar.com", "account.xyz.com"], "Has sorted ascending by cookies");
cookiesCol.click();
assertSortByCookies("descending");
Assert.deepEqual(getHostOrder(),
["account.xyz.com", "cinema.bar.com", "books.foo.com"], "Has sorted descending by cookies");
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
function assertSortByBaseDomain(order) {
let siteItems = sitesList.getElementsByTagName("richlistitem");
for (let i = 0; i < siteItems.length - 1; ++i) {
let aHost = siteItems[i].getAttribute("host");
let bHost = siteItems[i + 1].getAttribute("host");
let a = findSiteByHost(aHost);
let b = findSiteByHost(bHost);
let result = a.baseDomain.localeCompare(b.baseDomain);
if (order == "ascending") {
Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by host");
} else {
Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order by host");
}
}
}
function assertSortByUsage(order) {
let siteItems = sitesList.getElementsByTagName("richlistitem");
for (let i = 0; i < siteItems.length - 1; ++i) {
let aHost = siteItems[i].getAttribute("host");
let bHost = siteItems[i + 1].getAttribute("host");
let a = findSiteByHost(aHost);
let b = findSiteByHost(bHost);
let result = a.usage - b.usage;
if (order == "ascending") {
Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by usage");
} else {
Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order by usage");
}
}
}
function assertSortByCookies(order) {
let siteItems = sitesList.getElementsByTagName("richlistitem");
for (let i = 0; i < siteItems.length - 1; ++i) {
let aHost = siteItems[i].getAttribute("host");
let bHost = siteItems[i + 1].getAttribute("host");
let a = findSiteByHost(aHost);
let b = findSiteByHost(bHost);
let result = a.cookies.length - b.cookies.length;
if (order == "ascending") {
Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by number of cookies");
} else {
Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order by number of cookies");
}
}
}
function findSiteByHost(host) {
return mockSiteDataManager.fakeSites.find(site => site.principal.URI.host == host);
}
});
// Test sorting based on access date (separate from cookies for simplicity,
// since cookies access date affects this as well, but we don't mock our cookies)
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
{
usage: 1024,
origin: "https://account.xyz.com",
persisted: true,
lastAccessed: (Date.now() - 120 * 1000) * 1000,
},
{
usage: 1024 * 2,
origin: "https://books.foo.com",
persisted: false,
lastAccessed: (Date.now() - 240 * 1000) * 1000,
},
{
usage: 1024 * 3,
origin: "http://cinema.bar.com",
persisted: true,
lastAccessed: Date.now() * 1000,
},
]);
let updatePromise = promiseSiteDataManagerSitesUpdated();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
await updatePromise;
await openSiteDataSettingsDialog();
let dialog = content.gSubDialog._topDialog;
let dialogFrame = dialog._frame;
let frameDoc = dialogFrame.contentDocument;
let lastAccessedCol = frameDoc.getElementById("lastAccessedCol");
let sitesList = frameDoc.getElementById("sitesList");
// Test sorting on the date column
lastAccessedCol.click();
assertSortByDate("ascending");
lastAccessedCol.click();
assertSortByDate("descending");
await mockSiteDataManager.unregister();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
function assertSortByDate(order) {
let siteItems = sitesList.getElementsByTagName("richlistitem");
for (let i = 0; i < siteItems.length - 1; ++i) {
let aHost = siteItems[i].getAttribute("host");
let bHost = siteItems[i + 1].getAttribute("host");
let a = findSiteByHost(aHost);
let b = findSiteByHost(bHost);
let result = a.lastAccessed - b.lastAccessed;
if (order == "ascending") {
Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by date");
} else {
Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order date");
}
}
}
function findSiteByHost(host) {
return mockSiteDataManager.fakeSites.find(site => site.principal.URI.host == host);
}
});

View File

@ -2,44 +2,40 @@
// Test selecting and removing partial sites
add_task(async function() {
mockSiteDataManager.register(SiteDataManager, [
{
usage: 1024,
origin: "https://account.xyz.com",
persisted: true,
},
{
usage: 1024,
origin: "https://shopping.xyz.com",
persisted: false,
},
{
usage: 1024,
origin: "http://cinema.bar.com",
persisted: true,
},
{
usage: 1024,
origin: "http://email.bar.com",
persisted: false,
},
{
usage: 1024,
origin: "https://s3-us-west-2.amazonaws.com",
persisted: true,
},
await SiteDataTestUtils.clear();
let hosts = await addTestData([
{
usage: 1024,
origin: "https://127.0.0.1",
persisted: false,
},
{
usage: 1024,
origin: "https://[0:0:0:0:0:0:0:1]",
usage: 1024 * 4,
origin: "http://cinema.bar.com",
persisted: true,
},
{
usage: 1024 * 3,
origin: "http://email.bar.com",
persisted: false,
},
{
usage: 1024 * 2,
origin: "https://s3-us-west-2.amazonaws.com",
persisted: true,
},
{
usage: 1024 * 6,
origin: "https://account.xyz.com",
persisted: true,
},
{
usage: 1024 * 5,
origin: "https://shopping.xyz.com",
persisted: false,
},
]);
let fakeHosts = mockSiteDataManager.fakeSites.map(site => site.principal.URI.host);
let updatePromise = promiseSiteDataManagerSitesUpdated();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
@ -49,18 +45,21 @@ add_task(async function() {
let doc = gBrowser.selectedBrowser.contentDocument;
// Test the initial state
assertSitesListed(doc, fakeHosts);
assertSitesListed(doc, hosts);
let win = gBrowser.selectedBrowser.contentWindow;
let frameDoc = win.gSubDialog._topDialog._frame.contentDocument;
let removeBtn = frameDoc.getElementById("removeSelected");
is(removeBtn.disabled, true, "Should start with disabled removeSelected button");
let hostCol = frameDoc.getElementById("hostCol");
hostCol.click();
let removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen("accept", REMOVE_DIALOG_URL);
let settingsDialogClosePromise = promiseSettingsDialogClose();
// Select some sites to remove.
let sitesList = frameDoc.getElementById("sitesList");
fakeHosts.slice(0, 2).forEach(host => {
hosts.slice(0, 2).forEach(host => {
let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
sitesList.addItemToSelection(site);
});
@ -68,10 +67,10 @@ add_task(async function() {
is(removeBtn.disabled, false, "Should enable the removeSelected button");
removeBtn.doCommand();
is(sitesList.selectedIndex, 0, "Should select next item");
assertSitesListed(doc, fakeHosts.slice(2));
assertSitesListed(doc, hosts.slice(2));
// Select some other sites to remove with Delete.
fakeHosts.slice(2, 4).forEach(host => {
hosts.slice(2, 4).forEach(host => {
let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
sitesList.addItemToSelection(site);
});
@ -79,17 +78,21 @@ add_task(async function() {
is(removeBtn.disabled, false, "Should enable the removeSelected button");
EventUtils.synthesizeKey("VK_DELETE");
is(sitesList.selectedIndex, 0, "Should select next item");
assertSitesListed(doc, fakeHosts.slice(4));
assertSitesListed(doc, hosts.slice(4));
updatePromise = promiseSiteDataManagerSitesUpdated();
let saveBtn = frameDoc.getElementById("save");
saveBtn.doCommand();
await removeDialogOpenPromise;
await settingsDialogClosePromise;
await updatePromise;
await openSiteDataSettingsDialog();
assertSitesListed(doc, fakeHosts.slice(4));
assertSitesListed(doc, hosts.slice(4));
await mockSiteDataManager.unregister();
await SiteDataTestUtils.clear();
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

View File

@ -127,66 +127,29 @@ function assertSitesListed(doc, hosts) {
is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
}
const mockSiteDataManager = {
async function addTestData(data) {
let hosts = [];
_SiteDataManager: null,
_originalQMS: null,
_originalRemoveQuotaUsage: null,
getUsage(onUsageResult) {
let result = this.fakeSites.map(site => ({
origin: site.principal.origin,
usage: site.usage,
persisted: site.persisted,
lastAccessed: site.lastAccessed,
}));
onUsageResult({ result, resultCode: Cr.NS_OK });
},
_removeQuotaUsage(site) {
var target = site.principals[0].URI.host;
this.fakeSites = this.fakeSites.filter(fakeSite => {
return fakeSite.principal.URI.host != target;
});
},
register(siteDataManager, fakeSites) {
this._SiteDataManager = siteDataManager;
this._originalQMS = this._SiteDataManager._qms;
this._SiteDataManager._qms = this;
this._originalRemoveQuotaUsage = this._SiteDataManager._removeQuotaUsage;
this._SiteDataManager._removeQuotaUsage = this._removeQuotaUsage.bind(this);
// Add some fake data.
this.fakeSites = fakeSites;
for (let site of fakeSites) {
if (!site.principal) {
site.principal = Services.scriptSecurityManager
.createCodebasePrincipalFromOrigin(site.origin);
}
let uri = site.principal.URI;
try {
site.baseDomain = Services.eTLD.getBaseDomainFromHost(uri.host);
} catch (e) {
site.baseDomain = uri.host;
}
// Add some cookies if needed.
for (let i = 0; i < (site.cookies || 0); i++) {
Services.cookies.add(uri.host, uri.pathQueryRef, Cu.now(), i,
false, false, false, Date.now() + 1000 * 60 * 60, {},
Ci.nsICookie2.SAMESITE_UNSET);
}
for (let site of data) {
is(typeof site.origin, "string", "Passed an origin string into addTestData.");
if (site.persisted) {
await SiteDataTestUtils.persist(site.origin);
}
},
async unregister() {
await this._SiteDataManager.removeAll();
this.fakeSites = null;
this._SiteDataManager._qms = this._originalQMS;
this._SiteDataManager._removeQuotaUsage = this._originalRemoveQuotaUsage;
},
};
if (site.usage) {
await SiteDataTestUtils.addToIndexedDB(site.origin, site.usage);
}
for (let i = 0; i < (site.cookies || 0); i++) {
SiteDataTestUtils.addToCookies(site.origin, Cu.now());
}
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(site.origin);
hosts.push(principal.URI.host);
}
return hosts;
}
function promiseCookiesCleared() {
return TestUtils.topicObserved("cookie-changed", (subj, data) => {

View File

@ -339,7 +339,7 @@ class UrlbarView {
favicon.className = "urlbarView-favicon";
if (result.type == UrlbarUtils.RESULT_TYPE.SEARCH ||
result.type == UrlbarUtils.RESULT_TYPE.KEYWORD) {
favicon.src = UrlbarUtils.ICON.SEARCH_GLASS;
favicon.src = result.payload.icon || UrlbarUtils.ICON.SEARCH_GLASS;
} else {
favicon.src = result.payload.icon || UrlbarUtils.ICON.DEFAULT;
}

View File

@ -37,12 +37,7 @@ add_task(async function() {
await promiseAutocompleteResultPopup("moz open a search");
let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
if (UrlbarPrefs.get("quantumbar")) {
Assert.equal(result.image, UrlbarUtils.ICON.SEARCH_GLASS,
"Should have the search icon image");
} else {
Assert.equal(result.image, ICON_URI, "Should have the correct image");
}
Assert.equal(result.image, ICON_URI, "Should have the correct image");
let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
EventUtils.synthesizeKey("KEY_Enter");

View File

@ -126,5 +126,7 @@ policy-SSLVersionMax = Set the maximum SSL version.
policy-SSLVersionMin = Set the minimum SSL version.
policy-SupportMenu = Add a custom support menu item to the help menu.
# “format” refers to the format used for the value of this policy.
policy-WebsiteFilter = Block websites from being visited. See documentation for more details on the format.

View File

@ -3,11 +3,6 @@
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "OfflineAppCacheHelper",
"resource://gre/modules/offlineAppCache.jsm");
ChromeUtils.defineModuleGetter(this, "ServiceWorkerCleanUp",
"resource://gre/modules/ServiceWorkerCleanUp.jsm");
var EXPORTED_SYMBOLS = [
"SiteDataManager",
];
@ -325,6 +320,23 @@ var SiteDataManager = {
site.cookies = [];
},
// Returns a list of permissions from the permission manager that
// we consider part of "site data and cookies".
_getDeletablePermissions() {
let perms = [];
let enumerator = Services.perms.enumerator;
while (enumerator.hasMoreElements()) {
let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
if (permission.type == "persistent-storage" ||
permission.type == "storage-access") {
perms.push(permission);
}
}
return perms;
},
/**
* Removes all site data for the specified list of hosts.
*
@ -333,35 +345,28 @@ var SiteDataManager = {
* manager has been updated.
*/
async remove(hosts) {
// Make sure we have up-to-date information.
await this._getQuotaUsage();
this._updateAppCache();
let unknownHost = "";
let perms = this._getDeletablePermissions();
let promises = [];
for (let host of hosts) {
let site = this._sites.get(host);
if (site) {
// Clear localStorage & sessionStorage
Services.obs.notifyObservers(null, "extension:purge-localStorage", host);
Services.obs.notifyObservers(null, "browser:purge-sessionStorage", host);
this._removePermission(site);
this._removeAppCache(site);
this._removeCookies(site);
promises.push(ServiceWorkerCleanUp.removeFromHost(host));
promises.push(this._removeQuotaUsage(site));
} else {
unknownHost = host;
break;
promises.push(new Promise(function(resolve) {
Services.clearData.deleteDataFromHost(host, true,
Ci.nsIClearDataService.CLEAR_COOKIES |
Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
Ci.nsIClearDataService.CLEAR_SECURITY_SETTINGS |
Ci.nsIClearDataService.CLEAR_PLUGIN_DATA |
Ci.nsIClearDataService.CLEAR_EME |
Ci.nsIClearDataService.CLEAR_ALL_CACHES, resolve);
}));
for (let perm of perms) {
if (Services.eTLD.hasRootDomain(perm.principal.URI.host, host)) {
Services.perms.removePermission(perm);
}
}
}
await Promise.all(promises);
if (unknownHost) {
throw `SiteDataManager: removing unknown site of ${unknownHost}`;
}
return this.updateSites();
},
@ -405,54 +410,41 @@ var SiteDataManager = {
* @returns a Promise that resolves when the data is cleared.
*/
async removeAll() {
this.removeCache();
await this.removeCache();
return this.removeSiteData();
},
/**
* Clears the entire network cache.
* Clears all caches.
*
* @returns a Promise that resolves when the data is cleared.
*/
removeCache() {
Services.cache2.clear();
return new Promise(function(resolve) {
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL_CACHES, resolve);
});
},
/**
* Clears all site data, which currently means
* - Cookies
* - AppCache
* - LocalStorage
* - ServiceWorkers
* - Quota Managed Storage
* - persistent-storage permissions
* Clears all site data, but not cache, because the UI offers
* that functionality separately.
*
* @returns a Promise that resolves with the cache size on disk in bytes
* @returns a Promise that resolves when the data is cleared.
*/
async removeSiteData() {
// LocalStorage
Services.obs.notifyObservers(null, "extension:purge-localStorage");
await new Promise(function(resolve) {
Services.clearData.deleteData(
Ci.nsIClearDataService.CLEAR_COOKIES |
Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
Ci.nsIClearDataService.CLEAR_SECURITY_SETTINGS |
Ci.nsIClearDataService.CLEAR_EME |
Ci.nsIClearDataService.CLEAR_PLUGIN_DATA, resolve);
});
Services.cookies.removeAll();
OfflineAppCacheHelper.clear();
await ServiceWorkerCleanUp.removeAll();
// Refresh sites using quota usage again.
// This is for the case:
// 1. User goes to the about:preferences Site Data section.
// 2. With the about:preferences opened, user visits another website.
// 3. The website saves to quota usage, like indexedDB.
// 4. User goes back to the Site Data section and commands to clear all site data.
// For this case, we should refresh the site list so not to miss the website in the step 3.
// We don't do "Clear All" on the quota manager like the cookie, appcache, http cache above
// because that would clear browser data as well too,
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1312361#c9
this._sites.clear();
await this._getQuotaUsage();
let promises = [];
for (let site of this._sites.values()) {
this._removePermission(site);
promises.push(this._removeQuotaUsage(site));
for (let permission of this._getDeletablePermissions()) {
Services.perms.removePermission(permission);
}
return Promise.all(promises).then(() => this.updateSites());
return this.updateSites();
},
};

View File

@ -0,0 +1,148 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
const EXAMPLE_ORIGIN = "https://www.example.com";
const EXAMPLE_ORIGIN_2 = "https://example.org";
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {SiteDataManager} = ChromeUtils.import("resource:///modules/SiteDataManager.jsm");
const {SiteDataTestUtils} = ChromeUtils.import("resource://testing-common/SiteDataTestUtils.jsm");
ChromeUtils.defineModuleGetter(this, "setTimeout", "resource://gre/modules/Timer.jsm");
ChromeUtils.defineModuleGetter(this, "TestUtils", "resource://testing-common/TestUtils.jsm");
add_task(function setup() {
do_get_profile();
});
add_task(async function testGetSites() {
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo1", "bar1");
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo2", "bar2");
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN_2, "foo", "bar");
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
await SiteDataTestUtils.persist(EXAMPLE_ORIGIN_2);
await SiteDataManager.updateSites();
let sites = await SiteDataManager.getSites();
let site1 = sites.find((site) => site.baseDomain == "example.com");
let site2 = sites.find((site) => site.baseDomain == "example.org");
Assert.equal(site1.baseDomain, "example.com", "Has the correct base domain for example.com");
Assert.equal(site1.host, "www.example.com", "Has the correct host for example.com");
Assert.greater(site1.usage, 4096, "Has correct usage for example.com");
Assert.equal(site1.persisted, false, "example.com is not persisted");
Assert.equal(site1.cookies.length, 2, "Has correct number of cookies for example.com");
Assert.ok(typeof site1.lastAccessed.getDate == "function", "lastAccessed for example.com is a Date");
Assert.ok(site1.lastAccessed > Date.now() - 60 * 1000, "lastAccessed for example.com happened recently");
Assert.equal(site2.baseDomain, "example.org", "Has the correct base domain for example.org");
Assert.equal(site2.host, "example.org", "Has the correct host for example.org");
Assert.greater(site2.usage, 2048, "Has correct usage for example.org");
Assert.equal(site2.persisted, true, "example.org is persisted");
Assert.equal(site2.cookies.length, 1, "Has correct number of cookies for example.org");
Assert.ok(typeof site2.lastAccessed.getDate == "function", "lastAccessed for example.org is a Date");
Assert.ok(site2.lastAccessed > Date.now() - 60 * 1000, "lastAccessed for example.org happened recently");
await SiteDataTestUtils.clear();
});
add_task(async function testGetTotalUsage() {
await SiteDataManager.updateSites();
let sites = await SiteDataManager.getSites();
Assert.equal(sites.length, 0, "SiteDataManager is empty");
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
await SiteDataManager.updateSites();
let usage = await SiteDataManager.getTotalUsage();
Assert.greater(usage, 4096 + 2048, "Has the correct total usage.");
await SiteDataTestUtils.clear();
});
add_task(async function testRemove() {
await SiteDataManager.updateSites();
let uri = Services.io.newURI(EXAMPLE_ORIGIN);
Services.perms.add(uri, "camera", Services.perms.ALLOW_ACTION);
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo1", "bar1");
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo2", "bar2");
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN_2, "foo", "bar");
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
await SiteDataTestUtils.persist(EXAMPLE_ORIGIN_2);
await SiteDataManager.updateSites();
let sites = await SiteDataManager.getSites();
Assert.equal(sites.length, 2, "Has two sites.");
await SiteDataManager.remove(["www.example.com"]);
sites = await SiteDataManager.getSites();
Assert.equal(sites.length, 1, "Has one site.");
Assert.equal(sites[0].host, "example.org", "Has not cleared data for example.org");
let usage = await SiteDataTestUtils.getQuotaUsage(EXAMPLE_ORIGIN);
Assert.equal(usage, 0, "Has cleared quota usage for example.com");
let cookies = Services.cookies.countCookiesFromHost("example.com");
Assert.equal(cookies, 0, "Has cleared cookies for example.com");
let perm = Services.perms.testPermission(uri, "persistent-storage");
Assert.equal(perm, Services.perms.UNKNOWN_ACTION, "Cleared the persistent-storage permission.");
perm = Services.perms.testPermission(uri, "camera");
Assert.equal(perm, Services.perms.ALLOW_ACTION, "Did not clear other permissions.");
Services.perms.remove(uri, "camera");
});
add_task(async function testRemoveSiteData() {
let uri = Services.io.newURI(EXAMPLE_ORIGIN);
Services.perms.add(uri, "camera", Services.perms.ALLOW_ACTION);
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo1", "bar1");
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN, "foo2", "bar2");
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN, 4096);
SiteDataTestUtils.addToCookies(EXAMPLE_ORIGIN_2, "foo", "bar");
await SiteDataTestUtils.addToIndexedDB(EXAMPLE_ORIGIN_2, 2048);
await SiteDataTestUtils.persist(EXAMPLE_ORIGIN_2);
await SiteDataManager.updateSites();
let sites = await SiteDataManager.getSites();
Assert.equal(sites.length, 2, "Has two sites.");
await SiteDataManager.removeSiteData();
sites = await SiteDataManager.getSites();
Assert.equal(sites.length, 0, "Has no sites.");
let usage = await SiteDataTestUtils.getQuotaUsage(EXAMPLE_ORIGIN);
Assert.equal(usage, 0, "Has cleared quota usage for example.com");
usage = await SiteDataTestUtils.getQuotaUsage(EXAMPLE_ORIGIN_2);
Assert.equal(usage, 0, "Has cleared quota usage for example.org");
let cookies = Services.cookies.countCookiesFromHost("example.org");
Assert.equal(cookies, 0, "Has cleared cookies for example.org");
let perm = Services.perms.testPermission(uri, "persistent-storage");
Assert.equal(perm, Services.perms.UNKNOWN_ACTION, "Cleared the persistent-storage permission.");
perm = Services.perms.testPermission(uri, "camera");
Assert.equal(perm, Services.perms.ALLOW_ACTION, "Did not clear other permissions.");
Services.perms.remove(uri, "camera");
});

View File

@ -8,5 +8,6 @@ skip-if = toolkit == 'android'
[test_LiveBookmarkMigrator.js]
[test_Sanitizer_interrupted.js]
[test_SitePermissions.js]
[test_SiteDataManager.js]
[test_LaterRun.js]
[test_discovery.js]

View File

@ -35,3 +35,4 @@ unset MOZ_NO_PIE_COMPAT
unset AR
unset NM
unset RANLIB
unset NASM

View File

@ -137,17 +137,12 @@ class App extends PureComponent {
path: "/runtime/:runtimeId",
render: routeProps => this.renderRuntime(routeProps),
}),
// default route when there's no match which includes "/"
// TODO: the url does not match "/" means invalid URL,
// in this case maybe we'd like to do something else than a redirect.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1509897
Route({
path: "/",
exact: true,
// will redirect to This Firefox
render: routeProps => this.renderRuntime(routeProps),
}),
// default route when there's no match
// TODO: maybe we'd like to do something else than a redirect. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1509897
Route({
render: () => Redirect({ to: "/"}),
render: () => Redirect({ to: "/connect"}),
})
);
}

View File

@ -119,17 +119,6 @@ class Sidebar extends PureComponent {
},
dom.ul(
{},
Localized(
{ id: "about-debugging-sidebar-this-firefox", attrs: { name: true } },
SidebarFixedItem({
icon: FIREFOX_ICON,
isSelected: PAGE_TYPES.RUNTIME === selectedPage &&
selectedRuntimeId === RUNTIMES.THIS_FIREFOX,
key: RUNTIMES.THIS_FIREFOX,
name: "This Firefox",
to: `/runtime/${RUNTIMES.THIS_FIREFOX}`,
})
),
Localized(
{ id: "about-debugging-sidebar-connect", attrs: { name: true } },
SidebarFixedItem({
@ -141,6 +130,17 @@ class Sidebar extends PureComponent {
to: "/connect",
})
),
Localized(
{ id: "about-debugging-sidebar-this-firefox", attrs: { name: true } },
SidebarFixedItem({
icon: FIREFOX_ICON,
isSelected: PAGE_TYPES.RUNTIME === selectedPage &&
selectedRuntimeId === RUNTIMES.THIS_FIREFOX,
key: RUNTIMES.THIS_FIREFOX,
name: "This Firefox",
to: `/runtime/${RUNTIMES.THIS_FIREFOX}`,
})
),
SidebarItem(
{
isSelected: false,

View File

@ -30,7 +30,8 @@ const {
*/
add_task(async function testWebExtensionsToolboxWebConsole() {
await enableExtensionDebugging();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installTemporaryExtensionFromXPI({
background: function() {

View File

@ -30,7 +30,8 @@ const {
*/
add_task(async function testWebExtensionsToolboxWebConsole() {
await enableExtensionDebugging();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installTemporaryExtensionFromXPI({
background: function() {

View File

@ -26,7 +26,8 @@ const {
*/
add_task(async function testWebExtensionsToolboxNoBackgroundPage() {
await enableExtensionDebugging();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installTemporaryExtensionFromXPI({
// Do not pass any `background` script.

View File

@ -78,7 +78,9 @@ add_task(async function testWebExtensionsToolboxWebConsole() {
onPopupCustomMessage = waitForExtensionTestMessage("popupPageFunctionCalled");
});
const { document, tab } = await openAboutDebugging();
const { document, tab, window: aboutDebuggingWindow } = await openAboutDebugging();
await selectThisFirefoxPage(document, aboutDebuggingWindow.AboutDebugging.store);
await installTemporaryExtensionFromXPI({
background: function() {
const {browser} = this;
@ -115,6 +117,7 @@ add_task(async function testWebExtensionsToolboxWebConsole() {
id: ADDON_ID,
name: ADDON_NAME,
}, document);
const target = findDebugTargetByText(ADDON_NAME, document);
info("Setup the toolbox test function as environment variable");

View File

@ -18,6 +18,7 @@ add_task(async function() {
await checkAdbNotRunning();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const usbStatusElement = document.querySelector(".js-sidebar-usb-status");
info("Install ADB");

View File

@ -13,7 +13,8 @@ const USB_RUNTIME_APP_NAME = "TestUsbApp";
add_task(async function() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Prepare USB client mock");
const usbClient = mocks.createUSBRuntime(USB_RUNTIME_ID, {

View File

@ -7,7 +7,8 @@ Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-addons.js", this);
// Test that the reload button updates the addon list with the correct metadata.
add_task(async function() {
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const ORIGINAL_EXTENSION_NAME = "Temporary web extension (original)";
const UPDATED_EXTENSION_NAME = "Temporary web extension (updated)";
@ -53,7 +54,8 @@ add_task(async function() {
const PACKAGED_EXTENSION_ID = "packaged-extension@tests";
const PACKAGED_EXTENSION_NAME = "Packaged extension";
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installRegularExtension("resources/packaged-extension/packaged-extension.xpi");

View File

@ -11,7 +11,8 @@ add_task(async function() {
const EXTENSION_NAME = "Temporary web extension";
const EXTENSION_ID = "test-devtools@mozilla.org";
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installTemporaryExtensionFromXPI({
id: EXTENSION_ID,
@ -38,7 +39,8 @@ add_task(async function() {
const PACKAGED_EXTENSION_ID = "packaged-extension@tests";
const PACKAGED_EXTENSION_NAME = "Packaged extension";
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installRegularExtension("resources/packaged-extension/packaged-extension.xpi");

View File

@ -16,7 +16,8 @@ const EXTENSION_PATH = "resources/test-temporary-extension/manifest.json";
const EXTENSION_NAME = "test-temporary-extension";
add_task(async function() {
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Install a bad extension");
// Do not use installTemporaryAddon here since the install will fail.

View File

@ -11,7 +11,8 @@ add_task(async function() {
const EXTENSION_NAME = "Temporary web extension";
const EXTENSION_ID = "test-devtools@mozilla.org";
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await installTemporaryExtensionFromXPI({
id: EXTENSION_ID,

View File

@ -18,7 +18,8 @@ add_task(async function() {
await pushPref("devtools.debugger.prompt-connection", true);
// open a remote runtime page
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
mocks.emitUSBUpdate();
await connectToRuntime("Fancy Phone", document);

View File

@ -13,7 +13,8 @@ Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-collapsibilities.j
add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
for (const { title } of TARGET_PANES) {
info("Check whether this pane is collapsed after clicking the title");

View File

@ -13,7 +13,8 @@ Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-collapsibilities.j
add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Collapse all pane");
for (const { title } of TARGET_PANES) {

View File

@ -19,7 +19,8 @@ const EXTENSION_NAME = "test-temporary-extension";
add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Check that the temporary extensions pane is empty");
const temporaryExtensionPane = getDebugTargetPane("Temporary Extensions", document);

View File

@ -14,7 +14,8 @@ const RUNTIME_APP_NAME = "TestApp";
add_task(async function() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
mocks.createUSBRuntime(RUNTIME_ID, {
deviceName: RUNTIME_DEVICE_NAME,

View File

@ -15,6 +15,7 @@ add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const connectSidebarItem = findSidebarItemByText("Connect", document);
const connectLink = connectSidebarItem.querySelector(".js-sidebar-link");

View File

@ -14,6 +14,7 @@ add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const { devtoolsBrowser, devtoolsTab } =
await openAboutDevtoolsToolbox(document, tab, window);

View File

@ -14,6 +14,7 @@ add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const { devtoolsDocument, devtoolsTab, devtoolsWindow } =
await openAboutDevtoolsToolbox(document, tab, window);

View File

@ -14,6 +14,7 @@ add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const { devtoolsTab } = await openAboutDevtoolsToolbox(document, tab, window);
info("Check whether the menu items are disabled");

View File

@ -14,6 +14,7 @@ add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const { devtoolsBrowser, devtoolsTab, devtoolsWindow } =
await openAboutDevtoolsToolbox(document, tab, window);

View File

@ -14,6 +14,7 @@ add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const { devtoolsBrowser, devtoolsTab } =
await openAboutDevtoolsToolbox(document, tab, window);

View File

@ -14,6 +14,7 @@ add_task(async function() {
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const { devtoolsDocument, devtoolsTab, devtoolsWindow } =
await openAboutDevtoolsToolbox(document, tab, window);

View File

@ -20,6 +20,7 @@ add_task(async function() {
const { document, tab, window } = await openAboutDebugging();
const AboutDebugging = window.AboutDebugging;
await selectThisFirefoxPage(document, AboutDebugging.store);
const connectSidebarItem = findSidebarItemByText("Connect", document);
const connectLink = connectSidebarItem.querySelector(".js-sidebar-link");
@ -52,7 +53,7 @@ add_task(async function() {
const backgroundTab2 = await addTab(TAB_URL_2, { background: true });
info("Click on the ThisFirefox item in the sidebar");
const requestsSuccess = waitForRequestsSuccess(window);
const requestsSuccess = waitForRequestsSuccess(AboutDebugging.store);
thisFirefoxLink.click();
info("Wait for all target requests to complete");

View File

@ -42,7 +42,9 @@ add_task(async function() {
async function testRemoteClientPersistConnection(mocks,
{ client, id, runtimeName, sidebarName }) {
info("Open about:debugging and connect to the test runtime");
let { document, tab } = await openAboutDebugging();
let { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await connectToRuntime(sidebarName, document);
await selectRuntime(sidebarName, runtimeName, document);

View File

@ -4,12 +4,12 @@
"use strict";
/**
* Test that the initial route is /runtime/this-firefox
* Test that the initial route is /connect
*/
add_task(async function() {
info("Check root route redirects to 'This Firefox'");
info("Check root route redirects to connect page");
const { document, tab } = await openAboutDebugging();
is(document.location.hash, "#/runtime/this-firefox");
is(document.location.hash, "#/connect");
await removeTab(tab);
});
@ -34,7 +34,7 @@ add_task(async function() {
document.title,
"Debugging - Runtime / this-firefox",
"Checking title for 'runtime' page"
);
);
info("Check 'Connect' page");
document.location.hash = "#/connect";
@ -44,7 +44,7 @@ add_task(async function() {
document.title,
"Debugging - Connect",
"Checking title for 'connect' page"
);
);
info("Check 'USB device runtime' page");
// connect to a mocked USB runtime
@ -62,7 +62,7 @@ add_task(async function() {
document.title,
"Debugging - Runtime / 1337id",
"Checking title for 'runtime' page with USB device"
);
);
ok(runtimeLabel.includes("Lorem ipsum"), "Runtime is displayed with the mocked name");
await removeTab(tab);
@ -75,19 +75,19 @@ add_task(async function() {
info("Check an invalid route redirects to root");
const { document, tab } = await openAboutDebugging();
info("Waiting for a non-runtime page to load");
document.location.hash = "#/connect";
await waitUntil(() => document.querySelector(".js-connect-page"));
info("Update hash & wait for a redirect to root ('This Firefox')");
document.location.hash = "#/lorem-ipsum";
info("Waiting for a non connect page to load");
document.location.hash = "#/runtime/this-firefox";
await waitUntil(() => document.querySelector(".js-runtime-page"));
info("Update hash & wait for a redirect to root (connect page)");
document.location.hash = "#/lorem-ipsum";
await waitUntil(() => document.querySelector(".js-connect-page"));
is(
document.title,
"Debugging - Runtime / this-firefox",
"Checking title for 'runtime' page after redirect to root"
);
is(document.location.hash, "#/runtime/this-firefox", "Redirected to root");
"Debugging - Connect",
"Checking title for 'connect' page"
);
is(document.location.hash, "#/connect", "Redirected to root");
await removeTab(tab);
});

View File

@ -14,7 +14,8 @@ add_task(async function() {
name: "Lorem ipsum",
});
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Checking This Firefox");
ok(!document.querySelector(".js-connection-prompt-toggle-button"),

View File

@ -13,7 +13,8 @@ const USB_APP_NAME = "TestApp";
// unplugged.
add_task(async function testUsbDeviceUnplugged() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
mocks.createUSBRuntime(USB_RUNTIME_ID, {
deviceName: USB_DEVICE_NAME,
@ -42,7 +43,8 @@ add_task(async function testUsbDeviceUnplugged() {
// current USB runtime is closed.
add_task(async function testUsbClientDisconnected() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const usbClient = mocks.createUSBRuntime(USB_RUNTIME_ID, {
deviceName: USB_DEVICE_NAME,
@ -73,7 +75,8 @@ add_task(async function testUsbClientDisconnected() {
// current network runtime is closed.
add_task(async function testNetworkClientDisconnected() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {
name: NETWORK_RUNTIME_APP_NAME,

View File

@ -12,7 +12,8 @@ const NETWORK_RUNTIME_VERSION = "12.3";
add_task(async function() {
const mocks = new Mocks();
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Prepare Network client mock");
const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {

View File

@ -17,6 +17,7 @@ add_task(async function() {
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Prepare Network client mock");
const networkClient = mocks.createNetworkRuntime(NETWORK_RUNTIME_HOST, {
@ -46,7 +47,7 @@ add_task(async function() {
const thisFirefoxSidebarItem = findSidebarItemByText("This Firefox", document);
const thisFirefoxLink = thisFirefoxSidebarItem.querySelector(".js-sidebar-link");
info("Click on the ThisFirefox item in the sidebar");
const requestsSuccess = waitForRequestsSuccess(window);
const requestsSuccess = waitForRequestsSuccess(window.AboutDebugging.store);
thisFirefoxLink.click();
info("Wait for all target requests to complete");

View File

@ -17,7 +17,9 @@ const EMPTY_SW_HTML = URL_ROOT + "resources/service-workers/empty-sw.html";
*/
add_task(async function() {
await enableServiceWorkerDebugging();
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Test fetch status for a service worker listening to fetch events");
await testServiceWorkerFetchStatus(document, FETCH_SW_HTML, FETCH_SW_JS, true);

View File

@ -35,7 +35,9 @@ async function testDebuggingSW(enableMultiE10sFn, disableMultiE10sFn) {
// enable service workers
await pushPref("dom.serviceWorkers.testing.enabled", true);
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// disable multi e10s
info("Disabling multi e10s");

View File

@ -44,7 +44,9 @@ add_task(async function testLocalRuntime() {
await pushPref("dom.serviceWorkers.enabled", serviceWorkersEnabled);
await pushPref("browser.privatebrowsing.autostart", privateBrowsingEnabled);
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
assertWarningMessage(document, expectedMessage);
await removeTab(tab);
}
@ -65,13 +67,15 @@ add_task(async function testRemoteRuntime() {
const { serviceWorkersEnabled, privateBrowsingEnabled, expectedMessage } = testData;
info(`Test warning message on mocked USB runtime ` +
`with serviceWorkersEnabled: ${serviceWorkersEnabled} ` +
`and with privateBrowsingEnabled: ${privateBrowsingEnabled}`);
`with serviceWorkersEnabled: ${serviceWorkersEnabled} ` +
`and with privateBrowsingEnabled: ${privateBrowsingEnabled}`);
client.setPreference("dom.serviceWorkers.enabled", serviceWorkersEnabled);
client.setPreference("browser.privatebrowsing.autostart", privateBrowsingEnabled);
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Checking a USB runtime");
mocks.emitUSBUpdate();

View File

@ -13,7 +13,9 @@ const TAB_URL = URL_ROOT + "resources/service-workers/push-sw.html";
// It should trigger a "push" notification in the worker.
add_task(async function() {
await enableServiceWorkerDebugging();
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// Open a tab that registers a push service worker.
const swTab = await addTab(TAB_URL);

View File

@ -19,7 +19,9 @@ add_task(async function() {
info("Mock the push service");
mockPushService(FAKE_ENDPOINT);
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// Open a tab that registers a push service worker.
const swTab = await addTab(TAB_URL);

View File

@ -36,12 +36,11 @@ add_task(async function() {
// open a tab and register service worker
info("Register a service worker");
const swTab = await addTab(SW_TAB_URL);
// check that service worker is rendered
info("Wait for sw to appear in the debug pane list");
await waitUntil(() => {
swPane = getDebugTargetPane("Service Workers", document);
return swPane.querySelectorAll(".js-debug-target-item").length > 0;
});
info("Wait until the service worker appears and is running");
await waitForServiceWorkerRunning(SW_URL, document);
swPane = getDebugTargetPane("Service Workers", document);
ok(swPane.querySelectorAll(".js-debug-target-item").length === 1,
"Service worker list has one element");

View File

@ -25,7 +25,9 @@ add_task(async function() {
await pushPref("dom.serviceWorkers.idle_timeout", 1000);
await pushPref("dom.serviceWorkers.idle_extended_timeout", 1000);
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// Open a tab that registers a basic service worker.
const swTab = await addTab(SW_TAB_URL);

View File

@ -16,7 +16,9 @@ const SW_URL = URL_ROOT + "resources/service-workers/controlled-sw.js";
add_task(async function() {
await enableServiceWorkerDebugging();
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
info("Open tab with a service worker that never leaves `registering` status");
const swTab = await addTab(SW_TAB_URL);

View File

@ -31,7 +31,9 @@ add_task(async function() {
await pushPref("dom.serviceWorkers.idle_timeout", SW_TIMEOUT);
await pushPref("dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT);
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// Open a tab that registers a basic service worker.
const swTab = await addTab(SW_TAB_URL);

View File

@ -18,7 +18,9 @@ const SW_URL = URL_ROOT + "resources/service-workers/empty-sw.js";
add_task(async function() {
await enableServiceWorkerDebugging();
const { document, tab } = await openAboutDebugging({ enableWorkerUpdates: true });
const { document, tab, window } =
await openAboutDebugging({ enableWorkerUpdates: true });
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// Open a tab that registers a basic service worker.
const swTab = await addTab(SW_TAB_URL);

View File

@ -18,7 +18,8 @@ add_task(async function testShowSystemAddonsFalse() {
info("Hide system addons in aboutdebugging via preference");
await pushPref("devtools.aboutdebugging.showSystemAddons", false);
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const hasSystemAddon = !!findDebugTargetByText("System Addon", document);
const hasInstalledAddon = !!findDebugTargetByText("Installed Addon", document);
@ -35,7 +36,9 @@ add_task(async function testShowSystemAddonsTrue() {
info("Show system addons in aboutdebugging via preference");
await pushPref("devtools.aboutdebugging.showSystemAddons", true);
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const hasSystemAddon = !!findDebugTargetByText("System Addon", document);
const hasInstalledAddon = !!findDebugTargetByText("Installed Addon", document);
ok(hasSystemAddon, "System addon is displayed when system addon pref is true");

View File

@ -21,7 +21,8 @@ const EXPECTED_FAVICON = "data:image/png;base64," +
add_task(async function() {
const faviconTab = await addTab(TAB_URL, { background: true });
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
await waitUntil(() => findDebugTargetByText("Favicon tab", document));
const faviconTabTarget = findDebugTargetByText("Favicon tab", document);

View File

@ -15,6 +15,7 @@ add_task(async function() {
setupTelemetryTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const sessionId = getOpenEventSessionId();
ok(!isNaN(sessionId), "Open event has a valid session id");

View File

@ -15,7 +15,8 @@ add_task(async function() {
setupTelemetryTest();
const { tab, document } = await openAboutDebugging();
const { tab, document, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const sessionId = getOpenEventSessionId();
ok(!isNaN(sessionId), "Open event has a valid session id");
@ -28,6 +29,7 @@ add_task(async function() {
info("Navigate to 'USB device runtime' page");
await navigateToUSBRuntime(mocks, document);
checkSelectPageEvent("runtime", sessionId);
await waitForRequestsToSettle(window.AboutDebugging.store);
await removeTab(tab);
});

View File

@ -18,7 +18,8 @@ const EXPECTED_TARGET_PANES = [
];
add_task(async function() {
const { document, tab } = await openAboutDebugging();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
// Check that the selected sidebar item is "This Firefox"
const selectedSidebarItem = document.querySelector(".js-sidebar-item-selected");

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