mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
Merge mozilla-central to mozilla-inbound. a=merge on a CLOSED TREE
This commit is contained in:
commit
2fb9019d41
@ -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;"
|
||||
|
@ -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));
|
||||
});
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -278,9 +278,18 @@ EnterprisePoliciesManager.prototype = {
|
||||
getActivePolicies() {
|
||||
return this._parsedPolicies;
|
||||
},
|
||||
|
||||
setSupportMenu(supportMenu) {
|
||||
SupportMenu = supportMenu;
|
||||
},
|
||||
|
||||
getSupportMenu() {
|
||||
return SupportMenu;
|
||||
},
|
||||
};
|
||||
|
||||
let DisallowedFeatures = {};
|
||||
let SupportMenu = null;
|
||||
|
||||
/**
|
||||
* areEnterpriseOnlyPoliciesAllowed
|
||||
|
@ -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 || []);
|
||||
|
@ -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": {
|
||||
|
@ -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]
|
||||
|
@ -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.");
|
||||
});
|
@ -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};
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -155,5 +155,10 @@
|
||||
.ds-component {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.optOutNote {
|
||||
font-size: 12px;
|
||||
margin-inline-start: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
) : (
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
@ -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
@ -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
@ -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
@ -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,
|
||||
|
@ -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",
|
||||
});
|
||||
},
|
||||
}],
|
||||
|
@ -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;
|
||||
|
@ -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).
|
||||
|
@ -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=பட்டியைத் திற
|
||||
|
@ -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
File diff suppressed because one or more lines are too long
@ -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": "பட்டியைத் திற"
|
||||
};
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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(() => {
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
148
browser/modules/test/unit/test_SiteDataManager.js
Normal file
148
browser/modules/test/unit/test_SiteDataManager.js
Normal 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");
|
||||
});
|
@ -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]
|
||||
|
@ -35,3 +35,4 @@ unset MOZ_NO_PIE_COMPAT
|
||||
unset AR
|
||||
unset NM
|
||||
unset RANLIB
|
||||
unset NASM
|
||||
|
@ -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"}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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, {
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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"),
|
||||
|
@ -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,
|
||||
|
@ -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, {
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user