Update On Sat Jan 22 19:33:01 CET 2022

This commit is contained in:
github-action[bot] 2022-01-22 19:33:02 +01:00
parent 0d42a856e4
commit 52bfb1dfd4
322 changed files with 8106 additions and 2958 deletions

12
Cargo.lock generated
View file

@ -1197,9 +1197,9 @@ dependencies = [
[[package]]
name = "darling"
version = "0.10.2"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4"
dependencies = [
"darling_core",
"darling_macro",
@ -1207,9 +1207,9 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.10.2"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324"
dependencies = [
"fnv",
"ident_case",
@ -1220,9 +1220,9 @@ dependencies = [
[[package]]
name = "darling_macro"
version = "0.10.2"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b"
dependencies = [
"darling_core",
"quote",

View file

@ -579,6 +579,10 @@ pref("browser.privatebrowsing.vpnpromourl", "https://vpn.mozilla.org/?utm_source
// Whether the user is opted-in to privacy segmentation.
pref("browser.privacySegmentation.enabled", false);
// Use dark theme variant for PBM windows. This is only supported if the theme
// sets darkTheme data.
pref("browser.theme.dark-private-windows", false);
pref("browser.sessionhistory.max_entries", 50);
// Built-in default permissions.
@ -1475,7 +1479,10 @@ pref("browser.newtabpage.activity-stream.asrouter.useRemoteL10n", true);
// These prefs control if Discovery Stream is enabled.
pref("browser.newtabpage.activity-stream.discoverystream.enabled", true);
pref("browser.newtabpage.activity-stream.discoverystream.hardcoded-basic-layout", false);
// A preset compact layout, similar to setting a collection of prefs to build a complete layout.
pref("browser.newtabpage.activity-stream.discoverystream.compactLayout.enabled", false);
pref("browser.newtabpage.activity-stream.discoverystream.hideCardBackground.enabled", false);
pref("browser.newtabpage.activity-stream.discoverystream.fourCardLayout.enabled", false);
pref("browser.newtabpage.activity-stream.discoverystream.loadMore.enabled", false);
pref("browser.newtabpage.activity-stream.discoverystream.lastCardMessage.enabled", false);
pref("browser.newtabpage.activity-stream.discoverystream.newFooterSection.enabled", false);

View file

@ -870,24 +870,11 @@ var FullScreen = {
for (let el of document.querySelectorAll(
"toolbar[fullscreentoolbar=true]"
)) {
// Set the inFullscreen attribute to allow specific styling
// in fullscreen mode
if (aEnterFS) {
// Give the main nav bar and the tab bar the fullscreen context menu,
// otherwise remove context menu to prevent breakage
el.setAttribute("saved-context", el.getAttribute("context"));
if (el.id == "nav-bar" || el.id == "TabsToolbar") {
el.setAttribute("context", "autohide-context");
} else {
el.removeAttribute("context");
}
// Set the inFullscreen attribute to allow specific styling
// in fullscreen mode
el.setAttribute("inFullscreen", true);
} else {
if (el.hasAttribute("saved-context")) {
el.setAttribute("context", el.getAttribute("saved-context"));
el.removeAttribute("saved-context");
}
el.removeAttribute("inFullscreen");
}
}

View file

@ -6367,6 +6367,16 @@ nsBrowserAccess.prototype = {
},
};
function showFullScreenViewContextMenuItems(popup) {
for (let node of popup.querySelectorAll('[contexttype="fullscreen"]')) {
node.hidden = !window.fullScreen;
}
let autoHide = popup.querySelector(".fullscreen-context-autohide");
if (autoHide) {
FullScreen.getAutohide(autoHide);
}
}
function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
var popup = aEvent.target;
if (popup != aEvent.currentTarget) {
@ -6418,6 +6428,9 @@ function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
let removeFromToolbar = popup.querySelector(
".customize-context-removeFromToolbar"
);
// Show/hide fullscreen context menu items and set the
// autohide item's checked state to mirror the autohide pref.
showFullScreenViewContextMenuItems(popup);
// View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
if (!moveToPanel || !removeFromToolbar) {
return;

View file

@ -92,6 +92,15 @@
data-lazy-l10n-id="tab-context-reopen-closed-tabs"
data-l10n-args='{"tabCount": 1}'
observes="History:UndoCloseTab"/>
<menuseparator contexttype="fullscreen"/>
<menuitem class="fullscreen-context-autohide"
contexttype="fullscreen"
type="checkbox"
data-lazy-l10n-id="full-screen-autohide"
oncommand="FullScreen.setAutohide();"/>
<menuitem contexttype="fullscreen"
data-lazy-l10n-id="full-screen-exit"
oncommand="BrowserFullScreen();"/>
</menupopup>
<!-- bug 415444/582485: event.stopPropagation is here for the cloned version
@ -394,6 +403,15 @@
observes="cmd_CustomizeToolbars"
class="viewCustomizeToolbar"
data-lazy-l10n-id="toolbar-context-menu-view-customize-toolbar-2"/>
<menuseparator contexttype="fullscreen"/>
<menuitem class="fullscreen-context-autohide"
contexttype="fullscreen"
type="checkbox"
data-lazy-l10n-id="full-screen-autohide"
oncommand="FullScreen.setAutohide();"/>
<menuitem contexttype="fullscreen"
data-lazy-l10n-id="full-screen-exit"
oncommand="BrowserFullScreen();"/>
</menupopup>
<menupopup id="blockedPopupOptions"
@ -411,15 +429,6 @@
<menuseparator id="blockedPopupsSeparator"/>
</menupopup>
<menupopup id="autohide-context"
onpopupshowing="FullScreen.getAutohide(this.firstChild);">
<menuitem type="checkbox" data-l10n-id="full-screen-autohide"
oncommand="FullScreen.setAutohide();"/>
<menuseparator/>
<menuitem data-l10n-id="full-screen-exit"
oncommand="BrowserFullScreen();"/>
</menupopup>
<menupopup id="contentAreaContextMenu" pagemenu="#page-menu-separator"
onpopupshowing="if (event.target != this)
return true;

View file

@ -7026,6 +7026,10 @@ var TabContextMenu = {
document.getElementById("context_undoCloseTab").disabled =
SessionStore.getClosedTabCount(window) == 0;
// Show/hide fullscreen context menu items and set the
// autohide item's checked state to mirror the autohide pref.
showFullScreenViewContextMenuItems(aPopupMenu);
// Only one of Reload_Tab/Reload_Selected_Tabs should be visible.
document.getElementById("context_reloadTab").hidden = multiselectionContext;
document.getElementById(

View file

@ -22,3 +22,4 @@ https_first_disabled = true
support-files = fullscreen.html FullscreenFrame.jsm
[browser_fullscreen_warning.js]
support-files = fullscreen.html
[browser_fullscreen_context_menu.js]

View file

@ -0,0 +1,142 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function openContextMenu(itemElement, win = window) {
let popupShownPromise = BrowserTestUtils.waitForEvent(
itemElement.ownerDocument,
"popupshown"
);
EventUtils.synthesizeMouseAtCenter(
itemElement,
{
type: "contextmenu",
button: 2,
},
win
);
let { target } = await popupShownPromise;
return target;
}
async function testContextMenu() {
await BrowserTestUtils.withNewTab("about:blank", async () => {
let panelUIMenuButton = document.getElementById("PanelUI-menu-button");
let contextMenu = await openContextMenu(panelUIMenuButton);
let array1 = AppConstants.MENUBAR_CAN_AUTOHIDE
? [
".customize-context-moveToPanel",
".customize-context-removeFromToolbar",
"#toolbarItemsMenuSeparator",
"#toggle_toolbar-menubar",
"#toggle_PersonalToolbar",
"#viewToolbarsMenuSeparator",
".viewCustomizeToolbar",
]
: [
".customize-context-moveToPanel",
".customize-context-removeFromToolbar",
"#toolbarItemsMenuSeparator",
"#toggle_PersonalToolbar",
"#viewToolbarsMenuSeparator",
".viewCustomizeToolbar",
];
let result1 = verifyContextMenu(contextMenu, array1);
ok(!result1, "Expected no errors verifying context menu items");
contextMenu.hidePopup();
let onFullscreen = Promise.all([
BrowserTestUtils.waitForEvent(window, "fullscreen"),
BrowserTestUtils.waitForEvent(
window,
"sizemodechange",
false,
e => window.fullScreen
),
BrowserTestUtils.waitForPopupEvent(contextMenu, "hidden"),
]);
document.getElementById("View:FullScreen").doCommand();
contextMenu.hidePopup();
info("waiting for fullscreen");
await onFullscreen;
// make sure the toolbox is visible if it's autohidden
document.getElementById("Browser:OpenLocation").doCommand();
info("trigger the context menu");
let contextMenu2 = await openContextMenu(panelUIMenuButton);
info("context menu should be open, verify its menu items");
let array2 = AppConstants.MENUBAR_CAN_AUTOHIDE
? [
".customize-context-moveToPanel",
".customize-context-removeFromToolbar",
"#toolbarItemsMenuSeparator",
"#toggle_toolbar-menubar",
"#toggle_PersonalToolbar",
"#viewToolbarsMenuSeparator",
".viewCustomizeToolbar",
`menuseparator[contexttype="fullscreen"]`,
`.fullscreen-context-autohide`,
`menuitem[contexttype="fullscreen"]`,
]
: [
".customize-context-moveToPanel",
".customize-context-removeFromToolbar",
"#toolbarItemsMenuSeparator",
"#toggle_PersonalToolbar",
"#viewToolbarsMenuSeparator",
".viewCustomizeToolbar",
`menuseparator[contexttype="fullscreen"]`,
`.fullscreen-context-autohide`,
`menuitem[contexttype="fullscreen"]`,
];
let result2 = verifyContextMenu(contextMenu2, array2);
ok(!result2, "Expected no errors verifying context menu items");
let onExitFullscreen = Promise.all([
BrowserTestUtils.waitForEvent(window, "fullscreen"),
BrowserTestUtils.waitForEvent(
window,
"sizemodechange",
false,
e => !window.fullScreen
),
BrowserTestUtils.waitForPopupEvent(contextMenu2, "hidden"),
]);
document.getElementById("View:FullScreen").doCommand();
contextMenu2.hidePopup();
await onExitFullscreen;
});
}
function verifyContextMenu(contextMenu, itemSelectors) {
// Ignore hidden nodes
let items = Array.from(contextMenu.children).filter(n =>
BrowserTestUtils.is_visible(n)
);
let menuAsText = items
.map(n => {
return n.nodeName == "menuseparator"
? "---"
: `${n.label} (${n.command})`;
})
.join("\n");
info("Got actual context menu items: \n" + menuAsText);
try {
is(
items.length,
itemSelectors.length,
"Context menu has the expected number of items"
);
for (let i = 0; i < items.length; i++) {
let selector = itemSelectors[i];
ok(
items[i].matches(selector),
`Item at ${i} matches expected selector: ${selector}`
);
}
} catch (ex) {
return ex;
}
return null;
}
add_task(testContextMenu);

View file

@ -280,6 +280,11 @@ var whitelist = [
{ file: "resource://app/modules/SnapshotSelector.jsm" },
// Bug 1751275
{
file: "resource://app/modules/SnapshotGroups.jsm",
},
// toolkit/xre/MacRunFromDmgUtils.mm
{ file: "resource://gre/localization/en-US/toolkit/global/run-from-dmg.ftl" },
];

View file

@ -30,6 +30,10 @@ async function createDownloadFiles() {
});
}
registerCleanupFunction(async function() {
await task_resetState();
});
add_task(async function test_download_deleteFile() {
await SpecialPowers.pushPrefEnv({
set: [

View file

@ -2313,8 +2313,11 @@ function addAllowDenyPermissions(permissionName, allowList, blockList) {
Ci.nsIPermissionManager.EXPIRE_POLICY
);
} catch (ex) {
log.error(`Added by default for ${permissionName} permission in the permission
manager - ${origin.href}`);
// It's possible if the origin was invalid, we'll have a string instead of an origin.
log.error(
`Unable to add ${permissionName} permission for ${origin.href ||
origin}`
);
}
}

View file

@ -84,7 +84,7 @@
},
"AutoLaunchProtocolsFromOrigins": {
"type": "array",
"type": ["array", "JSON"],
"items": {
"type": "object",
"properties": {
@ -486,7 +486,7 @@
},
"ExtensionSettings": {
"type": "object",
"type": ["object", "JSON"],
"properties": {
"*": {
"type": "object",
@ -606,7 +606,7 @@
},
"Handlers": {
"type": "object",
"type": ["object", "JSON"],
"patternProperties": {
"^(mimeTypes|extensions|schemes)$": {
"type": "object",
@ -748,7 +748,7 @@
},
"type": "object"
},
"type": "array"
"type": ["array", "JSON"]
},
"ManualAppUpdateOnly": {
@ -1013,7 +1013,7 @@
},
"Preferences": {
"type": "object",
"type": ["object", "JSON"],
"patternProperties": {
"^.*$": {
"type": ["number", "boolean", "string", "object"],

View file

@ -198,6 +198,34 @@ add_task(async function test_addon_normalinstalled() {
await addon.uninstall();
});
add_task(async function test_extensionsettings_string() {
await setupPolicyEngineWithJson({
policies: {
ExtensionSettings: '{"*": {"installation_mode": "blocked"}}',
},
});
let extensionSettings = Services.policies.getExtensionSettings("*");
equal(extensionSettings.installation_mode, "blocked");
});
add_task(async function test_extensionsettings_string() {
let restrictedDomains = Services.prefs.getCharPref(
"extensions.webextensions.restrictedDomains"
);
await setupPolicyEngineWithJson({
policies: {
ExtensionSettings:
'{"*": {"restricted_domains": ["example.com","example.org"]}}',
},
});
let newRestrictedDomains = Services.prefs.getCharPref(
"extensions.webextensions.restrictedDomains"
);
equal(newRestrictedDomains, restrictedDomains + ",example.com,example.org");
});
add_task(async function test_theme() {
let themeFile = AddonTestUtils.createTempWebExtensionFile({
manifest: {

View file

@ -214,6 +214,17 @@ add_task(async function test_security_preference() {
checkUnsetPref("security.this.should.not.work");
});
add_task(async function test_JSON_preferences() {
await setupPolicyEngineWithJson({
policies: {
Preferences:
'{"browser.policies.test.default.boolean.json": {"Value": true,"Status": "default"}}',
},
});
checkDefaultPref("browser.policies.test.default.boolean.json", true);
});
add_task(async function test_bug_1666836() {
await setupPolicyEngineWithJson({
policies: {

View file

@ -210,7 +210,8 @@ export class _DiscoveryStreamBase extends React.PureComponent {
type={component.type}
dispatch={this.props.dispatch}
items={component.properties.items}
compact={component.properties.compact}
hideCardBackground={component.properties.hideCardBackground}
fourCardLayout={component.properties.fourCardLayout}
hideDescriptions={component.properties.hideDescriptions}
compactGrid={component.properties.compactGrid}
compactImages={component.properties.compactImages}
@ -221,7 +222,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
essentialReadsHeader={component.properties.essentialReadsHeader}
editorsPicksHeader={component.properties.editorsPicksHeader}
readTime={component.properties.readTime}
loadMoreEnabled={component.loadMoreEnabled}
loadMore={component.loadMore}
lastCardMessageEnabled={component.lastCardMessageEnabled}
saveToPocketCard={component.saveToPocketCard}
cta_variant={component.cta_variant}

View file

@ -30,9 +30,9 @@ export class CardGrid extends React.PureComponent {
}
get showLoadMore() {
const { loadMoreEnabled, data, loadMoreThreshold } = this.props;
const { loadMore, data, loadMoreThreshold } = this.props;
return (
loadMoreEnabled &&
loadMore &&
data.recommendations.length > loadMoreThreshold &&
!this.state.moreLoaded
);
@ -51,8 +51,10 @@ export class CardGrid extends React.PureComponent {
}
renderCards() {
let { items, compact } = this.props;
let { items } = this.props;
const {
hideCardBackground,
fourCardLayout,
hideDescriptions,
lastCardMessageEnabled,
saveToPocketCard,
@ -127,8 +129,8 @@ export class CardGrid extends React.PureComponent {
// If we have both header, inject the second one after the second row.
// For now this is English only.
if (essentialReadsHeader && editorsPicksHeader) {
// For compact second row is 8 cards, and regular it is 6 cards.
if (compact) {
// For 4 card row layouts, second row is 8 cards, and regular it is 6 cards.
if (fourCardLayout) {
cards.splice(8, 0, this.renderDSSubHeader("Editors Picks"));
} else {
cards.splice(6, 0, this.renderDSSubHeader("Editors Picks"));
@ -149,7 +151,12 @@ export class CardGrid extends React.PureComponent {
? `ds-card-grid-${this.props.display_variant}`
: ``;
const compactClass = compact ? `ds-card-grid-compact-variant` : ``;
const hideCardBackgroundClass = hideCardBackground
? `ds-card-grid-hide-background`
: ``;
const fourCardLayoutClass = fourCardLayout
? `ds-card-grid-four-card-variant`
: ``;
const hideDescriptionsClassName = !hideDescriptions
? `ds-card-grid-include-descriptions`
@ -159,7 +166,7 @@ export class CardGrid extends React.PureComponent {
return (
<div
className={`ds-card-grid ds-card-grid-${this.props.border} ${variantClass} ${compactClass} ${hideDescriptionsClassName} ${compactGridClassName}`}
className={`ds-card-grid ds-card-grid-${this.props.border} ${variantClass} ${hideCardBackgroundClass} ${fourCardLayoutClass} ${hideDescriptionsClassName} ${compactGridClassName}`}
>
{cards}
</div>

View file

@ -153,7 +153,7 @@ $col4-header-font-size: 14;
grid-template-columns: auto;
}
&.ds-card-grid-compact-variant {
&.ds-card-grid-four-card-variant {
// "Full width layout"
.ds-column-9 &,
.ds-column-10 &,
@ -180,8 +180,6 @@ $col4-header-font-size: 14;
}
.meta {
padding: 12px 0 0;
.story-footer {
margin-top: 8px;
}
@ -214,7 +212,6 @@ $col4-header-font-size: 14;
.title {
font-size: 14px;
-webkit-line-clamp: 3;
line-height: 20px;
}
@ -226,8 +223,8 @@ $col4-header-font-size: 14;
}
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card {
&:not(.placeholder) {
box-shadow: none;
background: none;
@ -250,6 +247,10 @@ $col4-header-font-size: 14;
object-fit: contain;
}
}
.meta {
padding: 12px 0 0;
}
}
}

View file

@ -2740,90 +2740,90 @@ main.has-snippet {
.ds-card-grid.empty {
grid-template-columns: auto;
}
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(1, 1fr);
}
@media (min-width: 610px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 866px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1122px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(4, 1fr);
}
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card.placeholder {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card.placeholder {
min-height: 247px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta {
padding: 12px 0 0;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-footer {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-footer {
margin-top: 8px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-sponsored-label,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .status-message .story-context-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-sponsored-label,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .status-message .story-context-label {
color: var(--newtab-text-secondary-color);
-webkit-line-clamp: 2;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-sponsored-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-sponsored-label {
font-size: 13px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .status-message .story-context-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .status-message .story-context-label {
font-size: 11.7px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-badge-icon {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-badge-icon {
margin-inline-end: 2px;
margin-bottom: 2px;
height: 14px;
width: 14px;
background-size: 14px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .title {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .title {
font-size: 14px;
-webkit-line-clamp: 3;
line-height: 20px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .info-wrap {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .info-wrap {
flex-grow: 0;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder),
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder),
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) {
box-shadow: none;
background: none;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus {
box-shadow: none;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img {
border: 0;
outline: 0;
box-shadow: 0 0 0 3px var(--newtab-primary-action-background-dimmed), 0 0 0 1px var(--newtab-primary-action-background);
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image {
background: transparent;
box-shadow: none;
object-fit: contain;
}
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .meta,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .meta {
padding: 12px 0 0;
}
.ds-layout .ds-card-grid-load-more-button {
display: block;

View file

@ -2744,90 +2744,90 @@ main.has-snippet {
.ds-card-grid.empty {
grid-template-columns: auto;
}
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(1, 1fr);
}
@media (min-width: 610px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 866px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1122px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(4, 1fr);
}
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card.placeholder {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card.placeholder {
min-height: 247px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta {
padding: 12px 0 0;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-footer {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-footer {
margin-top: 8px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-sponsored-label,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .status-message .story-context-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-sponsored-label,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .status-message .story-context-label {
color: var(--newtab-text-secondary-color);
-webkit-line-clamp: 2;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-sponsored-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-sponsored-label {
font-size: 13px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .status-message .story-context-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .status-message .story-context-label {
font-size: 11.7px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-badge-icon {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-badge-icon {
margin-inline-end: 2px;
margin-bottom: 2px;
height: 14px;
width: 14px;
background-size: 14px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .title {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .title {
font-size: 14px;
-webkit-line-clamp: 3;
line-height: 20px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .info-wrap {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .info-wrap {
flex-grow: 0;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder),
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder),
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) {
box-shadow: none;
background: none;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus {
box-shadow: none;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img {
border: 0;
outline: 0;
box-shadow: 0 0 0 3px var(--newtab-primary-action-background-dimmed), 0 0 0 1px var(--newtab-primary-action-background);
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image {
background: transparent;
box-shadow: none;
object-fit: contain;
}
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .meta,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .meta {
padding: 12px 0 0;
}
.ds-layout .ds-card-grid-load-more-button {
display: block;

View file

@ -2740,90 +2740,90 @@ main.has-snippet {
.ds-card-grid.empty {
grid-template-columns: auto;
}
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(1, 1fr);
}
@media (min-width: 610px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 866px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1122px) {
.ds-column-9 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-10 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-11 .ds-card-grid.ds-card-grid-compact-variant, .ds-column-12 .ds-card-grid.ds-card-grid-compact-variant {
.ds-column-9 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-10 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-11 .ds-card-grid.ds-card-grid-four-card-variant, .ds-column-12 .ds-card-grid.ds-card-grid-four-card-variant {
grid-template-columns: repeat(4, 1fr);
}
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card.placeholder {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card.placeholder {
min-height: 247px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta {
padding: 12px 0 0;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-footer {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-footer {
margin-top: 8px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-sponsored-label,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .status-message .story-context-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-sponsored-label,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .status-message .story-context-label {
color: var(--newtab-text-secondary-color);
-webkit-line-clamp: 2;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-sponsored-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .source,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .ds-last-card-desc,
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-sponsored-label {
font-size: 13px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .status-message .story-context-label {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .status-message .story-context-label {
font-size: 11.7px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .story-badge-icon {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .story-badge-icon {
margin-inline-end: 2px;
margin-bottom: 2px;
height: 14px;
width: 14px;
background-size: 14px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .title {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .title {
font-size: 14px;
-webkit-line-clamp: 3;
line-height: 20px;
}
.ds-card-grid.ds-card-grid-compact-variant .ds-card .meta .info-wrap {
.ds-card-grid.ds-card-grid-four-card-variant .ds-card .meta .info-wrap {
flex-grow: 0;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder),
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder),
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) {
box-shadow: none;
background: none;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus {
box-shadow: none;
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .ds-card-link:focus .img-wrapper .img img {
border: 0;
outline: 0;
box-shadow: 0 0 0 3px var(--newtab-primary-action-background-dimmed), 0 0 0 1px var(--newtab-primary-action-background);
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img {
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.outer-wrapper .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-compact-variant.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image {
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .img-wrapper .img img.last-card-message-image {
background: transparent;
box-shadow: none;
object-fit: contain;
}
.outer-wrapper .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .meta,
.outer-wrapper.newtab-experience .ds-card-grid.ds-card-grid-hide-background.ds-card-grid-border .ds-card:not(.placeholder) .meta {
padding: 12px 0 0;
}
.ds-layout .ds-card-grid-load-more-button {
display: block;

View file

@ -3290,7 +3290,8 @@ class _DiscoveryStreamBase extends react__WEBPACK_IMPORTED_MODULE_12___default.a
type: component.type,
dispatch: this.props.dispatch,
items: component.properties.items,
compact: component.properties.compact,
hideCardBackground: component.properties.hideCardBackground,
fourCardLayout: component.properties.fourCardLayout,
hideDescriptions: component.properties.hideDescriptions,
compactGrid: component.properties.compactGrid,
compactImages: component.properties.compactImages,
@ -3301,7 +3302,7 @@ class _DiscoveryStreamBase extends react__WEBPACK_IMPORTED_MODULE_12___default.a
essentialReadsHeader: component.properties.essentialReadsHeader,
editorsPicksHeader: component.properties.editorsPicksHeader,
readTime: component.properties.readTime,
loadMoreEnabled: component.loadMoreEnabled,
loadMore: component.loadMore,
lastCardMessageEnabled: component.lastCardMessageEnabled,
saveToPocketCard: component.saveToPocketCard,
cta_variant: component.cta_variant,
@ -13775,11 +13776,11 @@ class CardGrid_CardGrid extends external_React_default.a.PureComponent {
get showLoadMore() {
const {
loadMoreEnabled,
loadMore,
data,
loadMoreThreshold
} = this.props;
return loadMoreEnabled && data.recommendations.length > loadMoreThreshold && !this.state.moreLoaded;
return loadMore && data.recommendations.length > loadMoreThreshold && !this.state.moreLoaded;
}
renderDSSubHeader(title) {
@ -13796,10 +13797,11 @@ class CardGrid_CardGrid extends external_React_default.a.PureComponent {
renderCards() {
let {
items,
compact
items
} = this.props;
const {
hideCardBackground,
fourCardLayout,
hideDescriptions,
lastCardMessageEnabled,
saveToPocketCard,
@ -13871,8 +13873,8 @@ class CardGrid_CardGrid extends external_React_default.a.PureComponent {
if (essentialReadsHeader && editorsPicksHeader) {
// For compact second row is 8 cards, and regular it is 6 cards.
if (compact) {
// For 4 card row layouts, second row is 8 cards, and regular it is 6 cards.
if (fourCardLayout) {
cards.splice(8, 0, this.renderDSSubHeader("Editors Picks"));
} else {
cards.splice(6, 0, this.renderDSSubHeader("Editors Picks"));
@ -13888,11 +13890,12 @@ class CardGrid_CardGrid extends external_React_default.a.PureComponent {
const variantClass = this.props.display_variant ? `ds-card-grid-${this.props.display_variant}` : ``;
const compactClass = compact ? `ds-card-grid-compact-variant` : ``;
const hideCardBackgroundClass = hideCardBackground ? `ds-card-grid-hide-background` : ``;
const fourCardLayoutClass = fourCardLayout ? `ds-card-grid-four-card-variant` : ``;
const hideDescriptionsClassName = !hideDescriptions ? `ds-card-grid-include-descriptions` : ``;
const compactGridClassName = compactGrid ? `ds-card-grid-compact` : ``;
return /*#__PURE__*/external_React_default.a.createElement("div", {
className: `ds-card-grid ds-card-grid-${this.props.border} ${variantClass} ${compactClass} ${hideDescriptionsClassName} ${compactGridClassName}`
className: `ds-card-grid ds-card-grid-${this.props.border} ${variantClass} ${hideCardBackgroundClass} ${fourCardLayoutClass} ${hideDescriptionsClassName} ${compactGridClassName}`
}, cards);
}

View file

@ -463,7 +463,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
this.store.getState().Prefs.values?.pocketConfig || {};
let items = isBasicLayout ? 3 : 21;
if (pocketConfig.compactLayout) {
if (pocketConfig.compactLayout || pocketConfig.fourCardLayout) {
items = isBasicLayout ? 4 : 24;
}
@ -476,6 +476,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
pocketConfig.spocPositions?.split(`,`)
),
compactLayout: pocketConfig.compactLayout,
hideCardBackground: pocketConfig.hideCardBackground,
fourCardLayout: pocketConfig.fourCardLayout,
loadMore: pocketConfig.loadMore,
lastCardMessageEnabled: pocketConfig.lastCardMessageEnabled,
saveToPocketCard: pocketConfig.saveToPocketCard,
@ -1868,6 +1870,8 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
`spocPositions` Changes the position of spoc cards.
`sponsoredCollectionsEnabled` Tuns on and off the sponsored collection section.
`compactLayout` Changes cards to smaller more compact cards.
`hideCardBackground` Removes Pocket card background and borders.
`fourCardLayout` Enable four Pocket cards per row.
`loadMore` Hide half the Pocket stories behind a load more button.
`lastCardMessageEnabled` Shows a message card at the end of the feed.
`newFooterSection` Changes the layout of the topics section.
@ -1887,6 +1891,8 @@ getHardcodedLayout = ({
spocPositions = [2, 4, 11, 20],
sponsoredCollectionsEnabled = false,
compactLayout = false,
hideCardBackground = false,
fourCardLayout = false,
loadMore = false,
lastCardMessageEnabled = false,
newFooterSection = false,
@ -1976,12 +1982,13 @@ getHardcodedLayout = ({
type: "CardGrid",
properties: {
items,
compact: compactLayout,
hideCardBackground: hideCardBackground || compactLayout,
fourCardLayout: fourCardLayout || compactLayout,
hideDescriptions: hideDescriptions || compactLayout,
compactImages,
imageGradient,
newSponsoredLabel: newSponsoredLabel || compactLayout,
titleLines,
titleLines: (compactLayout && 3) || titleLines,
descLines,
compactGrid,
essentialReadsHeader,

View file

@ -42,13 +42,22 @@ describe("<CardGrid>", () => {
assert.ok(wrapper.find(".ds-card-grid-hero").exists());
});
it("should add compact classname to card grid", () => {
it("should add 4 card classname to card grid", () => {
wrapper.setProps({
compact: true,
fourCardLayout: true,
data: { recommendations: [{}, {}] },
});
assert.ok(wrapper.find(".ds-card-grid-compact-variant").exists());
assert.ok(wrapper.find(".ds-card-grid-four-card-variant").exists());
});
it("should add no description classname to card grid", () => {
wrapper.setProps({
hideCardBackground: true,
data: { recommendations: [{}, {}] },
});
assert.ok(wrapper.find(".ds-card-grid-hide-background").exists());
});
it("should render sub header in the middle of the card grid for both regular and compact", () => {
@ -87,7 +96,7 @@ describe("<CardGrid>", () => {
wrapper.setProps({
dispatch,
compact: true,
loadMoreEnabled: true,
loadMore: true,
lastCardMessageEnabled: true,
loadMoreThreshold: 2,
data: {
@ -114,7 +123,7 @@ describe("<CardGrid>", () => {
it("should only show load more with more than threshold number of stories", () => {
wrapper.setProps({
loadMoreEnabled: true,
loadMore: true,
loadMoreThreshold: 2,
data: {
recommendations: [{}, {}, {}],

View file

@ -0,0 +1,278 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["SnapshotGroups"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
Snapshots: "resource:///modules/Snapshots.jsm",
});
/**
* @typedef {object} SnapshotGroup
* This object represents a group of snapshots.
*
* @property {string} id
* The group id. The id property is ignored when adding a group.
* @property {string} title
* The title of the group, this may be automatically generated or
* user assigned.
* @property {string} builder
* The builder that was used to create the group (e.g. "domain", "pinned").
* @property {object} builderMetadata
* The metadata from the builder for the SnapshotGroup.
* This is for use by the builder only and should otherwise be considered opaque.
* @property {string} imageUrl
* The image url to use for the group.
* @property {number} lastAccessed
* The last access time of the most recently accessed snapshot.
* Stored as the number of milliseconds since the epoch.
* @property {number} snapshotCount
* The number of snapshots contained within the group.
*/
/**
* Handles storing and retrieving of snapshot groups in the Places database.
*
* Notifications of updates are sent via the observer service:
* places-snapshot-group-added, data: id of the snapshot group.
* places-snapshot-group-updated, data: id of the snapshot group.
* places-snapshot-group-deleted, data: id of the snapshot group.
*/
const SnapshotGroups = new (class SnapshotGroups {
constructor() {}
/**
* Adds a new snapshot group.
* Note: Not currently for use from UI code.
*
* @param {SnapshotGroup} group
* The details of the group to add.
* @param {string[]} urls
* An array of snapshot urls to add to the group. If the urls do not have associated snapshots, then they are ignored.
* @returns {number} id
* The id of the newly added group, or -1 on failure
*/
async add(group, urls) {
let id = -1;
await PlacesUtils.withConnectionWrapper(
"SnapshotsGroups.jsm:add",
async db => {
// Create the new group
let row = await db.executeCached(
`
INSERT INTO moz_places_metadata_snapshots_groups (title, builder, builder_data)
VALUES (:title, :builder, :builder_data)
RETURNING id
`,
{
title: group.title,
builder: group.builder,
builder_data: JSON.stringify(group.builderMetadata),
}
);
id = row[0].getResultByIndex(0);
// Construct the sql parameters for the urls
let params = {};
let SQLInFragment = [];
let i = 0;
for (let url of urls) {
params[`url${i}`] = url;
SQLInFragment.push(`hash(:url${i})`);
i++;
}
params.id = id;
await db.execute(
`
INSERT INTO moz_places_metadata_groups_to_snapshots (group_id, place_id)
SELECT :id, s.place_id
FROM moz_places h
JOIN moz_places_metadata_snapshots s
ON h.id = s.place_id
WHERE h.url_hash IN (${SQLInFragment.join(",")})
`,
params
);
}
);
Services.obs.notifyObservers(null, "places-snapshot-group-added");
return id;
}
/**
* Modifies the metadata for a snapshot group.
*
* @param {SnapshotGroup} group
* The details of the group to modify. If lastAccessed and SnapshotCount are specified, then they are ignored.
*/
async updateMetadata(group) {
await PlacesUtils.withConnectionWrapper(
"SnapshotsGroups.jsm:updateMetadata",
db => {
return db.executeCached(
`
UPDATE moz_places_metadata_snapshots_groups
SET title = :title, builder = :builder, builder_data = :builder_data
WHERE id = :id
`,
{
id: group.id,
title: group.title,
builder: group.builder,
builder_data: JSON.stringify(group.builderMetadata),
}
);
}
);
Services.obs.notifyObservers(null, "places-snapshot-group-updated");
}
/**
* Modifies the urls for a snapshot group.
*
* @param {number} id
* The id of the group to modify.
* @param {string[]} [urls]
* An array of snapshot urls for the group. If the urls do not have associated snapshots, then they are ignored.
*/
async updateUrls(id, urls) {
// TODO
Services.obs.notifyObservers(null, "places-snapshot-group-updated");
}
/**
* Deletes a snapshot group.
* Note: Not currently for use from UI code.
*
* @param {number} id
* The id of the group to delete.
*/
async delete(id) {
await PlacesUtils.withConnectionWrapper(
"SnapshotsGroups.jsm:delete",
async db => {
await db.executeCached(
`
DELETE FROM moz_places_metadata_snapshots_groups
WHERE id = :id;
`,
{ id }
);
}
);
Services.obs.notifyObservers(null, "places-snapshot-group-deleted");
}
/**
* Queries the list of SnapshotGroups.
*
* @param {object} [options]
* @param {number} [options.limit]
* A numerical limit to the number of snapshots to retrieve, defaults to 50.
* @param {string} [options.builder]
* Limit searching snapshot groups to results from a particular builder.
* @returns {SnapshotGroup[]}
* An array of snapshot groups, in descending order of last access time.
*/
async query({ limit = 50, builder = "" } = {}) {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.executeCached(
`
SELECT g.id, g.title, g.builder, g.builder_data, COUNT(h.url) AS snapshot_count, MAX(h.last_visit_date) AS last_access
FROM moz_places_metadata_snapshots_groups g
LEFT JOIN moz_places_metadata_groups_to_snapshots s ON s.group_id = g.id
LEFT JOIN moz_places h ON h.id = s.place_id
WHERE builder = :builder OR :builder = ""
GROUP BY g.id
ORDER BY last_access DESC
LIMIT :limit
`,
{ builder, limit }
);
return rows.map(row => this.#translateSnapshotGroupRow(row));
}
/**
* Obtains the snapshots for a particular group. This is designed
* for batch use to avoid potentially pulling a large number of
* snapshots across to the content process at one time.
*
* @param {object} options
* @param {number} options.id
* The id of the snapshot group to get the snapshots for.
* @param {number} options.startIndex
* The start index of the snapshots to return.
* @param {number} options.count
* The number of snapshots to return.
* @param {boolean} [sortDescending]
* Whether or not to sortDescending. Defaults to true.
* @param {string} [sortBy]
* A string to choose what to sort the snapshots by, e.g. "last_interaction_at"
* @returns {Snapshots[]}
* An array of snapshots, in descending order of last interaction time
*/
async getSnapshots({
id = "",
startIndex = 0,
count = 50,
sortDescending = true,
sortBy = "last_interaction_at",
} = {}) {
let db = await PlacesUtils.promiseDBConnection();
let urlRows = await db.executeCached(
`
SELECT h.url
FROM moz_places_metadata_groups_to_snapshots s
JOIN moz_places h ON h.id = s.place_id
WHERE s.group_id = :group_id
ORDER BY h.last_visit_date DESC
`,
{ group_id: id }
);
let snapshots = [];
let urls = urlRows.map(row => row.getResultByName("url"));
let start = Math.max(0, startIndex);
let end = Math.min(urls.length, count + start);
for (let i = start; i < end; i++) {
let snapShot = await Snapshots.get(urls[i]);
snapshots.push(snapShot);
}
return snapshots;
}
/**
* Translates a snapshot group database row to a SnapshotGroup.
*
* @param {object} row
* The database row to translate.
* @returns {SnapshotGroup}
*/
#translateSnapshotGroupRow(row) {
let snapshotGroup = {
id: row.getResultByName("id"),
title: row.getResultByName("title"),
builder: row.getResultByName("builder"),
builderMetadata: JSON.parse(row.getResultByName("builder_data")),
snapshotCount: row.getResultByName("snapshot_count"),
};
return snapshotGroup;
}
})();

View file

@ -21,6 +21,7 @@ EXTRA_JS_MODULES += [
"Interactions.jsm",
"InteractionsBlocklist.jsm",
"PlacesUIUtils.jsm",
"SnapshotGroups.jsm",
"Snapshots.jsm",
"SnapshotScorer.jsm",
"SnapshotSelector.jsm",

View file

@ -12,6 +12,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
setTimeout: "resource://gre/modules/Timer.jsm",
Services: "resource://gre/modules/Services.jsm",
SnapshotGroups: "resource:///modules/SnapshotGroups.jsm",
Snapshots: "resource:///modules/Snapshots.jsm",
SnapshotScorer: "resource:///modules/SnapshotScorer.jsm",
SnapshotSelector: "resource:///modules/SnapshotSelector.jsm",
@ -286,6 +287,41 @@ async function assertSnapshots(expected, options) {
await assertSnapshotList(snapshots, expected);
}
/**
* Asserts that the snapshot groups match the expected values.
*
* @param {SnapshotGroup} group
* The actual snapshot groups.
* @param {SnapshotGroup} expected
* The expected snapshot group.
*/
function assertSnapshotGroup(group, expected) {
if (expected.title != null) {
Assert.equal(group.title, expected.title, "Should have the expected title");
}
if (expected.builder != null) {
Assert.equal(
group.builder,
expected.builder,
"Should have the expected builder"
);
}
if (expected.builderMetadata != null) {
Assert.deepEqual(
group.builderMetadata,
expected.builderMetadata,
"Should have the expected builderMetadata"
);
}
if (expected.snapshotCount != null) {
Assert.equal(
group.snapshotCount,
expected.snapshotCount,
"Should have the expected snapshotCount"
);
}
}
/**
* Queries overlapping snapshots from the database and asserts their expected values.
*

View file

@ -0,0 +1,245 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests for snapshot groups addition, update, and removal.
*/
const TEST_URL1 = "https://example.com/";
const TEST_URL2 = "https://example.com/12345";
const TEST_URL3 = "https://example.com/67890";
async function delete_all_groups() {
let groups = await SnapshotGroups.query();
for (let group of groups) {
await SnapshotGroups.delete(group.id);
}
}
async function addInteractionsAndSnapshots(urls) {
for (let url of urls) {
await addInteractions([
{
url,
},
]);
await Snapshots.add({ url });
}
}
add_task(async function setup() {});
// A group where the urls are not snapshots should exclude those urls
add_task(async function test_add_and_query_no_snapshots() {
let urls = [TEST_URL1, TEST_URL2, TEST_URL3];
let id = await SnapshotGroups.add(
{ title: "Group", builder: "domain" },
urls
);
Assert.equal(id, 1, "id of newly added group should be 1");
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
assertSnapshotGroup(groups[0], {
title: "Group",
builder: "domain",
urls: [],
});
});
add_task(async function test_add_and_query() {
delete_all_groups();
let urls = [TEST_URL1, TEST_URL2, TEST_URL3];
await addInteractionsAndSnapshots(urls);
let newGroup = { title: "Test Group", builder: "domain" };
await SnapshotGroups.add(newGroup, urls);
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
assertSnapshotGroup(groups[0], {
title: "Test Group",
builder: "domain",
snapshotCount: urls.length,
});
});
add_task(async function test_add_and_query_builderMetadata() {
delete_all_groups();
let urls = [TEST_URL1, TEST_URL2, TEST_URL3];
let newGroup = {
title: "Test Group",
builder: "domain",
builderMetadata: { domain: "example.com" },
};
await SnapshotGroups.add(newGroup, urls);
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
assertSnapshotGroup(groups[0], {
title: "Test Group",
builder: "domain",
builderMetadata: { domain: "example.com" },
snapshotCount: urls.length,
});
});
add_task(async function test_add_and_query_with_builder() {
delete_all_groups();
let urls = [TEST_URL1, TEST_URL2, TEST_URL3];
let newGroup = {
title: "Test Group",
builder: "domain",
builderMetadata: { domain: "example.com" },
};
await SnapshotGroups.add(newGroup, urls);
// A query with the incorrect builder shouldn't return any groups
let groups = await SnapshotGroups.query({ builder: "incorrect builder" });
Assert.equal(groups.length, 0, "Should return 0 SnapshotGroups");
groups = await SnapshotGroups.query({ builder: "domain" });
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
assertSnapshotGroup(groups[0], {
title: "Test Group",
builder: "domain",
builderMetadata: { domain: "example.com" },
snapshotCount: urls.length,
});
});
add_task(async function test_update_metadata() {
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 snapshot group");
Assert.equal(
groups[0].title,
"Test Group",
"SnapshotGroup title should be retrieved"
);
groups[0].title = "Modified title";
groups[0].builder = "pinned";
await SnapshotGroups.updateMetadata(groups[0]);
let updated_groups = await SnapshotGroups.query();
Assert.equal(updated_groups.length, 1, "Should return 1 SnapshotGroup");
assertSnapshotGroup(groups[0], {
title: "Modified title",
builder: "pinned",
snapshotCount: [TEST_URL3, TEST_URL2, TEST_URL1].length,
});
});
add_task(async function test_delete_group() {
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
await SnapshotGroups.delete(groups[0].id);
groups = await SnapshotGroups.query();
Assert.equal(
groups.length,
0,
"After deletion, no SnapshotGroups should be found"
);
});
add_task(async function test_add_multiple_and_query_snapshot() {
let urls = [TEST_URL1, TEST_URL2, TEST_URL3];
await addInteractionsAndSnapshots(urls);
let id = await SnapshotGroups.add(
{ title: "First Group", builder: "domain" },
[TEST_URL1]
);
Assert.equal(id, 1, "id of next newly added group should be 1");
let second_id = await SnapshotGroups.add(
{ title: "Second Group", builder: "domain" },
[TEST_URL2]
);
Assert.equal(second_id, 2, "id of next newly added group should be 2");
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 2, "Should return 2 SnapshotGroups");
assertSnapshotGroup(groups[0], {
title: "Second Group",
builder: "domain",
snapshotCount: 1,
});
assertSnapshotGroup(groups[1], {
title: "First Group",
builder: "domain",
snapshotCount: 1,
});
});
add_task(async function test_add_and_query_no_url() {
await delete_all_groups();
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 0, "Should return 0 SnapshotGroups");
await SnapshotGroups.add({ title: "No url group", builder: "domain" }, []);
let newGroups = await SnapshotGroups.query();
Assert.equal(newGroups.length, 1, "Should return 1 SnapshotGroups");
assertSnapshotGroup(newGroups[0], {
title: "No url group",
builder: "domain",
snapshotCount: 0,
});
});
add_task(async function test_get_snapshots() {
await delete_all_groups();
let urls = [TEST_URL1, TEST_URL2, TEST_URL3];
await addInteractionsAndSnapshots(urls);
let newGroup = { title: "Test Group", builder: "domain" };
await SnapshotGroups.add(newGroup, urls);
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
let snapshots = await SnapshotGroups.getSnapshots({ id: groups[0].id });
Assert.equal(snapshots.length, 3, "Should return 3 Snapshots");
await assertSnapshotList(snapshots, [
{ url: TEST_URL3 },
{ url: TEST_URL2 },
{ url: TEST_URL1 },
]);
});
add_task(async function test_get_snapshots_count() {
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
let snapshots = await SnapshotGroups.getSnapshots({
id: groups[0].id,
count: 2,
});
Assert.equal(snapshots.length, 2, "Should return 2 Snapshots");
await assertSnapshotList(snapshots, [{ url: TEST_URL3 }, { url: TEST_URL2 }]);
});
add_task(async function test_get_snapshots_startIndex() {
let groups = await SnapshotGroups.query();
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
let snapshots = await SnapshotGroups.getSnapshots({
id: groups[0].id,
startIndex: 1,
});
Assert.equal(snapshots.length, 2, "Should return 2 Snapshots");
await assertSnapshotList(snapshots, [{ url: TEST_URL2 }, { url: TEST_URL1 }]);
});

View file

@ -11,6 +11,7 @@ skip-if = toolkit == 'android' # bug 1730213
[test_commonNames.js]
[test_snapshot_added_no_interaction.js]
[test_snapshot_groups.js]
[test_snapshots_basics.js]
[test_snapshots_common_referrer_queries.js]
[test_snapshots_create_allow_protocols.js]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,3 +4,8 @@
@import "./signup";
@import "./home";
@import "./styleguide";
// Components
@import "../js/components/ArticleList/ArticleList";
@import "../js/components/Header/Header";

View file

@ -7,4 +7,9 @@
.stp_superheader {
margin: 0;
}
.stp_styleguide_h4 {
border-bottom: 1px solid #ccc;
margin: 20px 0;
}
}

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
function ArticleList(props) {
return (
<ul className="stp_article_list">
{props.articles.map(article => (
<li className="stp_article_list_item">
<a className="stp_article_list_link" href={article.url}>
<img
className="stp_article_list_thumb"
src={article.thumbnail}
alt={article.alt}
/>
<div className="stp_article_list_meta">
<header className="stp_article_list_header">
{article.title}
</header>
<p className="stp_article_list_publisher">{article.publisher}</p>
</div>
</a>
</li>
))}
</ul>
);
}
export default ArticleList;

View file

@ -0,0 +1,40 @@
.stp_article_list {
padding: 0;
list-style: none;
.stp_article_list_link {
display: flex;
border-radius: 4px;
padding: 8px;
&:hover {
text-decoration: none;
background-color: #ECECEE;
}
}
.stp_article_list_thumb {
width: 40px;
height: 40px;
border-radius: 4px;
margin-inline-end: 8px;
}
.stp_article_list_header {
font-style: normal;
font-weight: 600;
font-size: 0.85em;
line-height: 1.27em;
color: #15141A;
margin: 4px 0px;
}
.stp_article_list_publisher {
font-style: normal;
font-weight: normal;
font-size: 0.85em;
line-height: 1.27em;
color: #52525E;
margin: 4px 0px;
}
}

View file

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
function Header(props) {
return (
<h1 className="stp_header">
<div className="stp_header_logo" />
{props.children}
</h1>
);
}
export default Header;

View file

@ -0,0 +1,16 @@
.stp_header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 16px;
padding: 10px 0;
font-size: 1em;
font-weight: 600;
.stp_header_logo {
background: url(../img/pocketlogo.svg) bottom center no-repeat;
background-size: contain;
height: 32px;
width: 121px;
}
}

View file

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
import Header from "../Header/Header";
function Home(props) {
return (
<>
<Header>
<a>
<span data-l10n-id="pocket-panel-header-my-list"></span>
</a>
</Header>
</>
);
}
export default Home;

View file

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
import Header from "../Header/Header";
function Saved(props) {
return (
<>
<Header>
<a>
<span data-l10n-id="pocket-panel-header-my-list"></span>
</a>
</Header>
</>
);
}
export default Saved;

View file

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from "react";
import Header from "../Header/Header";
function Signup(props) {
return (
<>
<Header>
<a>
<span data-l10n-id="pocket-panel-header-sign-in"></span>
</a>
</Header>
</>
);
}
export default Signup;

View file

@ -7,13 +7,13 @@ It does not contain any logic for saving or communication with the extension or
import React from "react";
import ReactDOM from "react-dom";
import PopularTopics from "../components/PopularTopics";
import PopularTopics from "../components/PopularTopics/PopularTopics";
import Home from "../components/Home/Home";
import pktPanelMessaging from "../messages.js";
var HomeOverlay = function(options) {
this.inited = false;
this.active = false;
this.pockethost = "getpocket.com";
this.parseHTML = function(htmlString) {
const parser = new DOMParser();
return parser.parseFromString(htmlString, `text/html`).documentElement;
@ -38,67 +38,64 @@ var HomeOverlay = function(options) {
HomeOverlay.prototype = {
create() {
var host = window.location.href.match(/pockethost=([\w|\.]*)&?/);
if (host && host.length > 1) {
this.pockethost = host[1];
}
var locale = window.location.href.match(/locale=([\w|\.]*)&?/);
if (locale && locale.length > 1) {
this.locale = locale[1].toLowerCase();
}
const { searchParams } = new URL(window.location.href);
const pockethost = searchParams.get(`pockethost`) || `getpocket.com`;
const locale = searchParams.get(`locale`) || ``;
const layoutRefresh = searchParams.get(`layoutRefresh`) === `true`;
if (this.active) {
return;
}
this.active = true;
// For English, we have a discover topics link.
// For non English, we don't have a link yet for this.
// When we do, we can consider flipping this on.
const enableLocalizedExploreMore = false;
const templateData = {
pockethost: this.pockethost,
utmsource: "firefox-button",
};
if (layoutRefresh) {
// Create actual content
ReactDOM.render(
<Home pockethost={pockethost} />,
document.querySelector(`body`)
);
} else {
// For English, we have a discover topics link.
// For non English, we don't have a link yet for this.
// When we do, we can consider flipping this on.
const enableLocalizedExploreMore = false;
const templateData = {
pockethost,
utmsource: `firefox-button`,
};
// extra modifier class for language
if (this.locale) {
// Create actual content
document
.querySelector(`body`)
.classList.add(`pkt_ext_home_${this.locale}`);
.append(this.parseHTML(Handlebars.templates.home_shell(templateData)));
// We only have topic pages in English,
// so ensure we only show a topics section for English browsers.
if (locale.startsWith("en")) {
ReactDOM.render(
<PopularTopics
pockethost={templateData.pockethost}
utmsource={templateData.utmsource}
topics={[
{ title: "Self Improvement", topic: "self-improvement" },
{ title: "Food", topic: "food" },
{ title: "Entertainment", topic: "entertainment" },
{ title: "Science", topic: "science" },
]}
/>,
document.querySelector(`.pkt_ext_more`)
);
} else if (enableLocalizedExploreMore) {
// For non English, we have a slightly different component to the page.
document
.querySelector(`.pkt_ext_more`)
.append(this.parseHTML(Handlebars.templates.explore_more()));
}
// click events
this.setupClickEvents();
}
// Create actual content
document
.querySelector(`body`)
.append(this.parseHTML(Handlebars.templates.home_shell(templateData)));
// We only have topic pages in English,
// so ensure we only show a topics section for English browsers.
if (this.locale.startsWith("en")) {
ReactDOM.render(
<PopularTopics
pockethost={templateData.pockethost}
utmsource={templateData.utmsource}
topics={[
{ title: "Self Improvement", topic: "self-improvement" },
{ title: "Food", topic: "food" },
{ title: "Entertainment", topic: "entertainment" },
{ title: "Science", topic: "science" },
]}
/>,
document.querySelector(`.pkt_ext_more`)
);
} else if (enableLocalizedExploreMore) {
// For non English, we have a slightly different component to the page.
document
.querySelector(`.pkt_ext_more`)
.append(this.parseHTML(Handlebars.templates.explore_more()));
}
// click events
this.setupClickEvents();
// tell back end we're ready
pktPanelMessaging.sendMessage("PKT_show_home");
},

View file

@ -2,7 +2,7 @@
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 883:
/***/ 318:
/***/ ((__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) => {
@ -10,7 +10,7 @@
var react = __webpack_require__(294);
// EXTERNAL MODULE: ./node_modules/react-dom/index.js
var react_dom = __webpack_require__(935);
;// CONCATENATED MODULE: ./content/panels/js/components/PopularTopics.jsx
;// CONCATENATED MODULE: ./content/panels/js/components/PopularTopics/PopularTopics.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -33,7 +33,36 @@ function PopularTopics(props) {
}));
}
/* harmony default export */ const components_PopularTopics = (PopularTopics);
/* harmony default export */ const PopularTopics_PopularTopics = (PopularTopics);
;// CONCATENATED MODULE: ./content/panels/js/components/Header/Header.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function Header(props) {
return /*#__PURE__*/react.createElement("h1", {
className: "stp_header"
}, /*#__PURE__*/react.createElement("div", {
className: "stp_header_logo"
}), props.children);
}
/* harmony default export */ const Header_Header = (Header);
;// CONCATENATED MODULE: ./content/panels/js/components/Home/Home.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function Home(props) {
return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement(Header_Header, null, /*#__PURE__*/react.createElement("a", null, /*#__PURE__*/react.createElement("span", {
"data-l10n-id": "pocket-panel-header-my-list"
}))));
}
/* harmony default export */ const Home_Home = (Home);
;// CONCATENATED MODULE: ./content/panels/js/messages.js
/* global RPMRemoveMessageListener:false, RPMAddMessageListener:false, RPMSendAsyncMessage:false */
var pktPanelMessaging = {
@ -100,10 +129,10 @@ It does not contain any logic for saving or communication with the extension or
var HomeOverlay = function (options) {
this.inited = false;
this.active = false;
this.pockethost = "getpocket.com";
this.parseHTML = function (htmlString) {
const parser = new DOMParser();
@ -128,71 +157,84 @@ var HomeOverlay = function (options) {
HomeOverlay.prototype = {
create() {
var host = window.location.href.match(/pockethost=([\w|\.]*)&?/);
if (host && host.length > 1) {
this.pockethost = host[1];
}
var locale = window.location.href.match(/locale=([\w|\.]*)&?/);
if (locale && locale.length > 1) {
this.locale = locale[1].toLowerCase();
}
const {
searchParams
} = new URL(window.location.href);
const pockethost = searchParams.get(`pockethost`) || `getpocket.com`;
const locale = searchParams.get(`locale`) || ``;
const layoutRefresh = searchParams.get(`layoutRefresh`) === `true`;
if (this.active) {
return;
}
this.active = true; // For English, we have a discover topics link.
// For non English, we don't have a link yet for this.
// When we do, we can consider flipping this on.
this.active = true;
const enableLocalizedExploreMore = false;
const templateData = {
pockethost: this.pockethost,
utmsource: "firefox-button"
}; // extra modifier class for language
if (layoutRefresh) {
// Create actual content
react_dom.render( /*#__PURE__*/react.createElement(Home_Home, {
pockethost: pockethost
}), document.querySelector(`body`));
} else {
// For English, we have a discover topics link.
// For non English, we don't have a link yet for this.
// When we do, we can consider flipping this on.
const enableLocalizedExploreMore = false;
const templateData = {
pockethost,
utmsource: `firefox-button`
}; // Create actual content
if (this.locale) {
document.querySelector(`body`).classList.add(`pkt_ext_home_${this.locale}`);
} // Create actual content
document.querySelector(`body`).append(this.parseHTML(Handlebars.templates.home_shell(templateData))); // We only have topic pages in English,
// so ensure we only show a topics section for English browsers.
if (locale.startsWith("en")) {
react_dom.render( /*#__PURE__*/react.createElement(PopularTopics_PopularTopics, {
pockethost: templateData.pockethost,
utmsource: templateData.utmsource,
topics: [{
title: "Self Improvement",
topic: "self-improvement"
}, {
title: "Food",
topic: "food"
}, {
title: "Entertainment",
topic: "entertainment"
}, {
title: "Science",
topic: "science"
}]
}), document.querySelector(`.pkt_ext_more`));
} else if (enableLocalizedExploreMore) {
// For non English, we have a slightly different component to the page.
document.querySelector(`.pkt_ext_more`).append(this.parseHTML(Handlebars.templates.explore_more()));
} // click events
document.querySelector(`body`).append(this.parseHTML(Handlebars.templates.home_shell(templateData))); // We only have topic pages in English,
// so ensure we only show a topics section for English browsers.
this.setupClickEvents();
} // tell back end we're ready
if (this.locale.startsWith("en")) {
react_dom.render( /*#__PURE__*/react.createElement(components_PopularTopics, {
pockethost: templateData.pockethost,
utmsource: templateData.utmsource,
topics: [{
title: "Self Improvement",
topic: "self-improvement"
}, {
title: "Food",
topic: "food"
}, {
title: "Entertainment",
topic: "entertainment"
}, {
title: "Science",
topic: "science"
}]
}), document.querySelector(`.pkt_ext_more`));
} else if (enableLocalizedExploreMore) {
// For non English, we have a slightly different component to the page.
document.querySelector(`.pkt_ext_more`).append(this.parseHTML(Handlebars.templates.explore_more()));
} // click events
this.setupClickEvents(); // tell back end we're ready
messages.sendMessage("PKT_show_home");
}
};
/* harmony default export */ const overlay = (HomeOverlay);
;// CONCATENATED MODULE: ./content/panels/js/components/Signup/Signup.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function Signup(props) {
return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement(Header_Header, null, /*#__PURE__*/react.createElement("a", null, /*#__PURE__*/react.createElement("span", {
"data-l10n-id": "pocket-panel-header-sign-in"
}))));
}
/* harmony default export */ const Signup_Signup = (Signup);
;// CONCATENATED MODULE: ./content/panels/js/signup/overlay.js
/* global Handlebars:false */
@ -202,6 +244,9 @@ It does not contain any logic for saving or communication with the extension or
*/
var SignupOverlay = function (options) {
this.inited = false;
this.active = false;
@ -225,43 +270,70 @@ var SignupOverlay = function (options) {
const parser = new DOMParser();
let elBody = document.querySelector(`body`); // Extract local variables passed into template via URL query params
let queryParams = new URL(window.location.href).searchParams;
let isEmailSignupEnabled = queryParams.get(`emailButton`) === `true`;
let pockethost = queryParams.get(`pockethost`) || `getpocket.com`;
let utmCampaign = queryParams.get(`utmCampaign`) || `firefox_door_hanger_menu`;
let utmSource = queryParams.get(`utmSource`) || `control`;
let language = queryParams.get(`locale`)?.split(`-`)[0].toLowerCase();
const {
searchParams
} = new URL(window.location.href);
const isEmailSignupEnabled = searchParams.get(`emailButton`) === `true`;
const pockethost = searchParams.get(`pockethost`) || `getpocket.com`;
const utmCampaign = searchParams.get(`utmCampaign`) || `firefox_door_hanger_menu`;
const utmSource = searchParams.get(`utmSource`) || `control`;
const language = searchParams.get(`locale`)?.split(`-`)[0].toLowerCase();
const layoutRefresh = searchParams.get(`layoutRefresh`) === `true`;
if (this.active) {
return;
}
this.active = true;
const templateData = {
pockethost,
utmCampaign,
utmSource
}; // extra modifier class for language
if (language) {
elBody.classList.add(`pkt_ext_signup_${language}`);
} // Create actual content
if (layoutRefresh) {
// Create actual content
document.querySelector(`.pkt_ext_containersignup`)?.classList.remove(`pkt_ext_containersignup`);
react_dom.render( /*#__PURE__*/react.createElement(Signup_Signup, {
pockethost: pockethost
}), document.querySelector(`body`));
} else {
const templateData = {
pockethost,
utmCampaign,
utmSource
}; // extra modifier class for language
if (language) {
elBody.classList.add(`pkt_ext_signup_${language}`);
} // Create actual content
elBody.append(parser.parseFromString(Handlebars.templates.signup_shell(templateData), `text/html`).documentElement); // Remove email button based on `extensions.pocket.refresh.emailButton.enabled` pref
elBody.append(parser.parseFromString(Handlebars.templates.signup_shell(templateData), `text/html`).documentElement); // Remove email button based on `extensions.pocket.refresh.emailButton.enabled` pref
if (!isEmailSignupEnabled) {
document.querySelector(`.btn-container-email`).remove();
} // click events
if (!isEmailSignupEnabled) {
document.querySelector(`.btn-container-email`).remove();
} // click events
this.setupClickEvents(); // tell back end we're ready
this.setupClickEvents();
} // tell back end we're ready
messages.sendMessage("PKT_show_signup");
};
};
/* harmony default export */ const signup_overlay = (SignupOverlay);
;// CONCATENATED MODULE: ./content/panels/js/components/Saved/Saved.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function Saved(props) {
return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement(Header_Header, null, /*#__PURE__*/react.createElement("a", null, /*#__PURE__*/react.createElement("span", {
"data-l10n-id": "pocket-panel-header-my-list"
}))));
}
/* harmony default export */ const Saved_Saved = (Saved);
;// CONCATENATED MODULE: ./content/panels/js/saved/overlay.js
/* global Handlebars:false */
@ -271,17 +343,17 @@ It does not contain any logic for saving or communication with the extension or
*/
var SavedOverlay = function (options) {
var myself = this;
this.inited = false;
this.active = false;
this.pockethost = "getpocket.com";
this.savedItemId = 0;
this.savedUrl = "";
this.premiumStatus = false;
this.userTags = [];
this.tagsDropdownOpen = false;
this.fxasignedin = false;
this.parseHTML = function (htmlString) {
const parser = new DOMParser();
@ -776,96 +848,120 @@ SavedOverlay.prototype = {
this.active = true;
var myself = this;
var url = window.location.href.match(/premiumStatus=([\w|\d|\.]*)&?/);
const {
searchParams
} = new URL(window.location.href);
const pockethost = searchParams.get(`pockethost`) || `getpocket.com`;
const premiumStatus = searchParams.get(`premiumStatus`) == `1`;
const locale = searchParams.get(`locale`) || ``;
const language = locale.split(`-`)[0].toLowerCase();
const layoutRefresh = searchParams.get(`layoutRefresh`) === `true`;
if (url && url.length > 1) {
this.premiumStatus = url[1] == "1";
}
if (layoutRefresh) {
// Create actual content
react_dom.render( /*#__PURE__*/react.createElement(Saved_Saved, {
pockethost: pockethost
}), document.querySelector(`body`));
} else {
// set host
const templateData = {
pockethost
}; // extra modifier class for language
var fxasignedin = window.location.href.match(/fxasignedin=([\w|\d|\.]*)&?/);
if (fxasignedin && fxasignedin.length > 1) {
this.fxasignedin = fxasignedin[1] == "1";
}
var host = window.location.href.match(/pockethost=([\w|\.]*)&?/);
if (host && host.length > 1) {
this.pockethost = host[1];
}
var locale = window.location.href.match(/locale=([\w|\.]*)&?/);
if (locale && locale.length > 1) {
this.locale = locale[1].toLowerCase();
} // set host
const templateData = {
pockethost: this.pockethost
}; // extra modifier class for language
if (this.locale) {
document.querySelector(`body`).classList.add(`pkt_ext_saved_${this.locale}`);
}
const parser = new DOMParser(); // Create actual content
document.querySelector(`body`).append(...parser.parseFromString(Handlebars.templates.saved_shell(templateData), `text/html`).body.childNodes); // Add in premium content (if applicable based on premium status)
if (this.premiumStatus && !document.querySelector(`.pkt_ext_suggestedtag_detail`)) {
let elSubshell = document.querySelector(`body .pkt_ext_subshell`);
let elPremiumShellElements = parser.parseFromString(Handlebars.templates.saved_premiumshell(templateData), `text/html`).body.childNodes; // Convert NodeList to Array and reverse it
elPremiumShellElements = [].slice.call(elPremiumShellElements).reverse();
elPremiumShellElements.forEach(el => {
elSubshell.insertBefore(el, elSubshell.firstChild);
});
} // Initialize functionality for overlay
this.initTagInput();
this.initAddTagInput();
this.initRemovePageInput();
this.initOpenListInput(); // wait confirmation of save before flipping to final saved state
messages.addMessageListener("PKT_saveLink", function (resp) {
const {
data
} = resp;
if (data.status == "error") {
// Fallback to a generic catch all error.
let errorLocalizedKey = data?.error?.localizedKey || "pocket-panel-saved-error-generic";
myself.showStateLocalizedError("pocket-panel-saved-error-not-saved", errorLocalizedKey);
return;
if (language) {
document.querySelector(`body`).classList.add(`pkt_ext_saved_${language}`);
}
myself.showStateSaved(data);
});
messages.addMessageListener("PKT_renderItemRecs", function (resp) {
const {
data
} = resp;
myself.renderItemRecs(data);
}); // tell back end we're ready
const parser = new DOMParser(); // Create actual content
document.querySelector(`body`).append(...parser.parseFromString(Handlebars.templates.saved_shell(templateData), `text/html`).body.childNodes); // Add in premium content (if applicable based on premium status)
if (premiumStatus && !document.querySelector(`.pkt_ext_suggestedtag_detail`)) {
let elSubshell = document.querySelector(`body .pkt_ext_subshell`);
let elPremiumShellElements = parser.parseFromString(Handlebars.templates.saved_premiumshell(templateData), `text/html`).body.childNodes; // Convert NodeList to Array and reverse it
elPremiumShellElements = [].slice.call(elPremiumShellElements).reverse();
elPremiumShellElements.forEach(el => {
elSubshell.insertBefore(el, elSubshell.firstChild);
});
} // Initialize functionality for overlay
this.initTagInput();
this.initAddTagInput();
this.initRemovePageInput();
this.initOpenListInput(); // wait confirmation of save before flipping to final saved state
messages.addMessageListener("PKT_saveLink", function (resp) {
const {
data
} = resp;
if (data.status == "error") {
// Fallback to a generic catch all error.
let errorLocalizedKey = data?.error?.localizedKey || "pocket-panel-saved-error-generic";
myself.showStateLocalizedError("pocket-panel-saved-error-not-saved", errorLocalizedKey);
return;
}
myself.showStateSaved(data);
});
messages.addMessageListener("PKT_renderItemRecs", function (resp) {
const {
data
} = resp;
myself.renderItemRecs(data);
});
} // tell back end we're ready
messages.sendMessage("PKT_show_saved");
}
};
/* harmony default export */ const saved_overlay = (SavedOverlay);
;// CONCATENATED MODULE: ./content/panels/js/components/ArticleList/ArticleList.jsx
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function ArticleList(props) {
return /*#__PURE__*/react.createElement("ul", {
className: "stp_article_list"
}, props.articles.map(article => /*#__PURE__*/react.createElement("li", {
className: "stp_article_list_item"
}, /*#__PURE__*/react.createElement("a", {
className: "stp_article_list_link",
href: article.url
}, /*#__PURE__*/react.createElement("img", {
className: "stp_article_list_thumb",
src: article.thumbnail,
alt: article.alt
}), /*#__PURE__*/react.createElement("div", {
className: "stp_article_list_meta"
}, /*#__PURE__*/react.createElement("header", {
className: "stp_article_list_header"
}, article.title), /*#__PURE__*/react.createElement("p", {
className: "stp_article_list_publisher"
}, article.publisher))))));
}
/* harmony default export */ const ArticleList_ArticleList = (ArticleList);
;// CONCATENATED MODULE: ./content/panels/js/style-guide/overlay.js
var StyleGuideOverlay = function (options) {};
StyleGuideOverlay.prototype = {
create() {
// TODO: Wrap popular topics component in JSX to work without needing an explicit container hierarchy for styling
react_dom.render( /*#__PURE__*/react.createElement(components_PopularTopics, {
react_dom.render( /*#__PURE__*/react.createElement("div", null, /*#__PURE__*/react.createElement("h3", null, "JSX Components:"), /*#__PURE__*/react.createElement("h4", {
className: "stp_styleguide_h4"
}, "PopularTopics"), /*#__PURE__*/react.createElement(PopularTopics_PopularTopics, {
pockethost: `getpocket.com`,
utmsource: `styleguide`,
topics: [{
@ -881,7 +977,29 @@ StyleGuideOverlay.prototype = {
title: "Science",
topic: "science"
}]
}), document.querySelector(`#stp_style_guide_components`));
}), /*#__PURE__*/react.createElement("h4", {
className: "stp_styleguide_h4"
}, "ArticleList"), /*#__PURE__*/react.createElement(ArticleList_ArticleList, {
articles: [{
title: "Article Title",
publisher: "Publisher",
thumbnail: "https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg",
url: "https://example.org",
alt: "Alt Text"
}, {
title: "Article Title",
publisher: "Publisher",
thumbnail: "https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg",
url: "https://example.org",
alt: "Alt Text"
}, {
title: "Article Title",
publisher: "Publisher",
thumbnail: "https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg",
url: "https://example.org",
alt: "Alt Text"
}]
})), document.querySelector(`#stp_style_guide_components`));
}
};
@ -1133,7 +1251,7 @@ window.pktPanelMessaging = messages;
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module depends on other loaded chunks and execution need to be delayed
/******/ var __webpack_exports__ = __webpack_require__.O(undefined, [736], () => (__webpack_require__(883)))
/******/ var __webpack_exports__ = __webpack_require__.O(undefined, [736], () => (__webpack_require__(318)))
/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__);
/******/
/******/ })()

View file

@ -5,20 +5,20 @@ SavedOverlay is the view itself and contains all of the methods to manipute the
It does not contain any logic for saving or communication with the extension or server.
*/
import React from "react";
import ReactDOM from "react-dom";
import pktPanelMessaging from "../messages.js";
import Saved from "../components/Saved/Saved";
var SavedOverlay = function(options) {
var myself = this;
this.inited = false;
this.active = false;
this.pockethost = "getpocket.com";
this.savedItemId = 0;
this.savedUrl = "";
this.premiumStatus = false;
this.userTags = [];
this.tagsDropdownOpen = false;
this.fxasignedin = false;
this.parseHTML = function(htmlString) {
const parser = new DOMParser();
@ -659,94 +659,96 @@ SavedOverlay.prototype = {
this.active = true;
var myself = this;
var url = window.location.href.match(/premiumStatus=([\w|\d|\.]*)&?/);
if (url && url.length > 1) {
this.premiumStatus = url[1] == "1";
}
var fxasignedin = window.location.href.match(/fxasignedin=([\w|\d|\.]*)&?/);
if (fxasignedin && fxasignedin.length > 1) {
this.fxasignedin = fxasignedin[1] == "1";
}
var host = window.location.href.match(/pockethost=([\w|\.]*)&?/);
if (host && host.length > 1) {
this.pockethost = host[1];
}
var locale = window.location.href.match(/locale=([\w|\.]*)&?/);
if (locale && locale.length > 1) {
this.locale = locale[1].toLowerCase();
}
const { searchParams } = new URL(window.location.href);
const pockethost = searchParams.get(`pockethost`) || `getpocket.com`;
const premiumStatus = searchParams.get(`premiumStatus`) == `1`;
const locale = searchParams.get(`locale`) || ``;
const language = locale.split(`-`)[0].toLowerCase();
const layoutRefresh = searchParams.get(`layoutRefresh`) === `true`;
// set host
const templateData = {
pockethost: this.pockethost,
};
// extra modifier class for language
if (this.locale) {
document
.querySelector(`body`)
.classList.add(`pkt_ext_saved_${this.locale}`);
}
const parser = new DOMParser();
// Create actual content
document
.querySelector(`body`)
.append(
...parser.parseFromString(
Handlebars.templates.saved_shell(templateData),
`text/html`
).body.childNodes
if (layoutRefresh) {
// Create actual content
ReactDOM.render(
<Saved pockethost={pockethost} />,
document.querySelector(`body`)
);
} else {
// set host
const templateData = {
pockethost,
};
// Add in premium content (if applicable based on premium status)
if (
this.premiumStatus &&
!document.querySelector(`.pkt_ext_suggestedtag_detail`)
) {
let elSubshell = document.querySelector(`body .pkt_ext_subshell`);
let elPremiumShellElements = parser.parseFromString(
Handlebars.templates.saved_premiumshell(templateData),
`text/html`
).body.childNodes;
// Convert NodeList to Array and reverse it
elPremiumShellElements = [].slice.call(elPremiumShellElements).reverse();
elPremiumShellElements.forEach(el => {
elSubshell.insertBefore(el, elSubshell.firstChild);
});
}
// Initialize functionality for overlay
this.initTagInput();
this.initAddTagInput();
this.initRemovePageInput();
this.initOpenListInput();
// wait confirmation of save before flipping to final saved state
pktPanelMessaging.addMessageListener("PKT_saveLink", function(resp) {
const { data } = resp;
if (data.status == "error") {
// Fallback to a generic catch all error.
let errorLocalizedKey =
data?.error?.localizedKey || "pocket-panel-saved-error-generic";
myself.showStateLocalizedError(
"pocket-panel-saved-error-not-saved",
errorLocalizedKey
);
return;
// extra modifier class for language
if (language) {
document
.querySelector(`body`)
.classList.add(`pkt_ext_saved_${language}`);
}
myself.showStateSaved(data);
});
const parser = new DOMParser();
pktPanelMessaging.addMessageListener("PKT_renderItemRecs", function(resp) {
const { data } = resp;
myself.renderItemRecs(data);
});
// Create actual content
document
.querySelector(`body`)
.append(
...parser.parseFromString(
Handlebars.templates.saved_shell(templateData),
`text/html`
).body.childNodes
);
// Add in premium content (if applicable based on premium status)
if (
premiumStatus &&
!document.querySelector(`.pkt_ext_suggestedtag_detail`)
) {
let elSubshell = document.querySelector(`body .pkt_ext_subshell`);
let elPremiumShellElements = parser.parseFromString(
Handlebars.templates.saved_premiumshell(templateData),
`text/html`
).body.childNodes;
// Convert NodeList to Array and reverse it
elPremiumShellElements = [].slice
.call(elPremiumShellElements)
.reverse();
elPremiumShellElements.forEach(el => {
elSubshell.insertBefore(el, elSubshell.firstChild);
});
}
// Initialize functionality for overlay
this.initTagInput();
this.initAddTagInput();
this.initRemovePageInput();
this.initOpenListInput();
// wait confirmation of save before flipping to final saved state
pktPanelMessaging.addMessageListener("PKT_saveLink", function(resp) {
const { data } = resp;
if (data.status == "error") {
// Fallback to a generic catch all error.
let errorLocalizedKey =
data?.error?.localizedKey || "pocket-panel-saved-error-generic";
myself.showStateLocalizedError(
"pocket-panel-saved-error-not-saved",
errorLocalizedKey
);
return;
}
myself.showStateSaved(data);
});
pktPanelMessaging.addMessageListener("PKT_renderItemRecs", function(
resp
) {
const { data } = resp;
myself.renderItemRecs(data);
});
}
// tell back end we're ready
pktPanelMessaging.sendMessage("PKT_show_saved");

View file

@ -5,7 +5,10 @@ SignupOverlay is the view itself and contains all of the methods to manipute the
It does not contain any logic for saving or communication with the extension or server.
*/
import React from "react";
import ReactDOM from "react-dom";
import pktPanelMessaging from "../messages.js";
import Signup from "../components/Signup/Signup";
var SignupOverlay = function(options) {
this.inited = false;
@ -36,49 +39,61 @@ var SignupOverlay = function(options) {
let elBody = document.querySelector(`body`);
// Extract local variables passed into template via URL query params
let queryParams = new URL(window.location.href).searchParams;
let isEmailSignupEnabled = queryParams.get(`emailButton`) === `true`;
let pockethost = queryParams.get(`pockethost`) || `getpocket.com`;
let utmCampaign =
queryParams.get(`utmCampaign`) || `firefox_door_hanger_menu`;
let utmSource = queryParams.get(`utmSource`) || `control`;
let language = queryParams
const { searchParams } = new URL(window.location.href);
const isEmailSignupEnabled = searchParams.get(`emailButton`) === `true`;
const pockethost = searchParams.get(`pockethost`) || `getpocket.com`;
const utmCampaign =
searchParams.get(`utmCampaign`) || `firefox_door_hanger_menu`;
const utmSource = searchParams.get(`utmSource`) || `control`;
const language = searchParams
.get(`locale`)
?.split(`-`)[0]
.toLowerCase();
const layoutRefresh = searchParams.get(`layoutRefresh`) === `true`;
if (this.active) {
return;
}
this.active = true;
const templateData = {
pockethost,
utmCampaign,
utmSource,
};
if (layoutRefresh) {
// Create actual content
document
.querySelector(`.pkt_ext_containersignup`)
?.classList.remove(`pkt_ext_containersignup`);
ReactDOM.render(
<Signup pockethost={pockethost} />,
document.querySelector(`body`)
);
} else {
const templateData = {
pockethost,
utmCampaign,
utmSource,
};
// extra modifier class for language
if (language) {
elBody.classList.add(`pkt_ext_signup_${language}`);
// extra modifier class for language
if (language) {
elBody.classList.add(`pkt_ext_signup_${language}`);
}
// Create actual content
elBody.append(
parser.parseFromString(
Handlebars.templates.signup_shell(templateData),
`text/html`
).documentElement
);
// Remove email button based on `extensions.pocket.refresh.emailButton.enabled` pref
if (!isEmailSignupEnabled) {
document.querySelector(`.btn-container-email`).remove();
}
// click events
this.setupClickEvents();
}
// Create actual content
elBody.append(
parser.parseFromString(
Handlebars.templates.signup_shell(templateData),
`text/html`
).documentElement
);
// Remove email button based on `extensions.pocket.refresh.emailButton.enabled` pref
if (!isEmailSignupEnabled) {
document.querySelector(`.btn-container-email`).remove();
}
// click events
this.setupClickEvents();
// tell back end we're ready
pktPanelMessaging.sendMessage("PKT_show_signup");
};

View file

@ -1,6 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import PopularTopics from "../components/PopularTopics";
import ArticleList from "../components/ArticleList/ArticleList";
import PopularTopics from "../components/PopularTopics/PopularTopics";
var StyleGuideOverlay = function(options) {};
@ -8,16 +9,49 @@ StyleGuideOverlay.prototype = {
create() {
// TODO: Wrap popular topics component in JSX to work without needing an explicit container hierarchy for styling
ReactDOM.render(
<PopularTopics
pockethost={`getpocket.com`}
utmsource={`styleguide`}
topics={[
{ title: "Self Improvement", topic: "self-improvement" },
{ title: "Food", topic: "food" },
{ title: "Entertainment", topic: "entertainment" },
{ title: "Science", topic: "science" },
]}
/>,
<div>
<h3>JSX Components:</h3>
<h4 className="stp_styleguide_h4">PopularTopics</h4>
<PopularTopics
pockethost={`getpocket.com`}
utmsource={`styleguide`}
topics={[
{ title: "Self Improvement", topic: "self-improvement" },
{ title: "Food", topic: "food" },
{ title: "Entertainment", topic: "entertainment" },
{ title: "Science", topic: "science" },
]}
/>
<h4 className="stp_styleguide_h4">ArticleList</h4>
<ArticleList
articles={[
{
title: "Article Title",
publisher: "Publisher",
thumbnail:
"https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg",
url: "https://example.org",
alt: "Alt Text",
},
{
title: "Article Title",
publisher: "Publisher",
thumbnail:
"https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg",
url: "https://example.org",
alt: "Alt Text",
},
{
title: "Article Title",
publisher: "Publisher",
thumbnail:
"https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg",
url: "https://example.org",
alt: "Alt Text",
},
]}
/>
</div>,
document.querySelector(`#stp_style_guide_components`)
);
},

View file

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
<meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'; img-src https://img-getpocket.cdn.mozilla.net;" />
<base href="chrome://pocket/content/panels/">
<link rel="localization" href="browser/branding/brandings.ftl" />
<link rel="localization" href="browser/aboutPocket.ftl" />
@ -13,12 +13,12 @@
<script src="js/vendor/jquery-2.1.1.min.js"></script>
<script src="js/vendor/handlebars.runtime.js"></script>
<script src="js/tmpl.js"></script>
<script src="js/vendor.bundle.js"></script>
<script src="js/main.bundle.js"></script>
<script src="js/style-guide/entry.js"></script>
<div id="stp_style_guide">
<p class="stp_superheader"><strong>Save To Pocket</strong></p>
<h1>Style Guide</h1>
<h1>Save To Pocket:<br/> Style Guide</h1>
<div id="stp_style_guide_components"></div>
</div>
</body>

View file

@ -11,7 +11,7 @@
"watch": "npx run-p watch:*",
"watch:handlebars": "npx chokidar \"content/panels/tmpl/**/*.handlebars\" -c \"npm run build:handlebars\"",
"watch:webpack": "npm run build:webpack -- --env development -w",
"watch:sass": "npx chokidar \"content/panels/css/**/*.scss\" -c \"npm run build:sass\"",
"watch:sass": "npx chokidar \"content/panels/**/*.scss\" -c \"npm run build:sass\"",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Mozilla (https://mozilla.org/)",

View file

@ -46,3 +46,8 @@ pocket-panel-home-paragraph = You can use { -pocket-brand-name } to explore and
pocket-panel-home-explore-popular-topics = Explore Popular Topics
pocket-panel-home-discover-more = Discover More
pocket-panel-home-explore-more = Explore
## Pocket panel header component
pocket-panel-header-my-list = View My List
pocket-panel-header-sign-in = Sign In

View file

@ -69,7 +69,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "3be417e9326ebc9e369fc77f1fd98481a39b07cf"
"revision": "968a1de3326e836cdde355b670bbdd5ad4c232ee"
},
"ast": {
"pin": false,
@ -411,7 +411,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "22d62ecd4c214d52af254d87c2da7410bd571fb2"
"revision": "b913aeed9ceb986afc646e4a5a377173b0c4c3c3"
},
"en-CA": {
"pin": false,
@ -627,7 +627,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "6e37410128f2d675c3414ba9c6341af81b4521ff"
"revision": "c2f2328574c9e0d48e037c43e4c16ff45b0f9522"
},
"fr": {
"pin": false,
@ -663,7 +663,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "5e4350e39e2a7797d6bd8e1c1bb0b15252397010"
"revision": "104ef9b7199645cb95e79704a357da3873609102"
},
"ga-IE": {
"pin": false,
@ -843,7 +843,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "39e18e316a85b5aaada8c560d51317b5e28b77d8"
"revision": "ed2786177013615335280809caf6e99e3ea53716"
},
"hy-AM": {
"pin": false,
@ -1029,7 +1029,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "c68316baa4a0dd041abb4e8efd557339fb0d9cb4"
"revision": "3e3bfda2771f60db16cbeb71e35e1f3fbb750f88"
},
"km": {
"pin": false,
@ -1317,7 +1317,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "3fbbd92a2f116150e9921f13e4c9fe37d6948464"
"revision": "b524525e938ac4e5829ca8d22ef3f77fb14521f7"
},
"nn-NO": {
"pin": false,
@ -1335,7 +1335,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "4de80ca84f4965bdf7ef791cb7c748f07ff3c2a1"
"revision": "4260063f4c62c33c7e769c4f870002caf401d56d"
},
"oc": {
"pin": false,
@ -1749,7 +1749,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "4fdfd727279b4b58c3365296122462fc1fde9405"
"revision": "3c840935c225dbb6b51df2e4f4807a36147e69e4"
},
"th": {
"pin": false,
@ -1875,7 +1875,7 @@
"win64-aarch64-devedition",
"win64-devedition"
],
"revision": "f8e8cc127eaf07ab77bf51c37ec494d2721e8d2e"
"revision": "13d4beb05fc480a58c078305b2979a0b18be65c9"
},
"vi": {
"pin": false,

View file

@ -133,7 +133,7 @@ def check_symbol(symbol, language="C", flags=None, when=None, onerror=lambda: No
@depends(check_symbol_flags, dependable(flags))
def flags(base_flags, extra_flags):
if base_flags and extra_flags:
return base_flags + extra_flags
return base_flags + list(extra_flags)
if extra_flags:
return extra_flags
return base_flags

View file

@ -1,2 +1,2 @@
vendored:third_party/python/glean_parser
pypi:pytest==3.6.2
pypi:pytest==4.6.6

View file

@ -313,7 +313,7 @@ def main(*args, **kwargs):
args.extend(
[
"--rootdir",
topsrcdir,
str(topsrcdir),
"-c",
os.path.join(here, "pytest.ini"),
"-vv",

View file

@ -10,7 +10,7 @@
import { isOriginalId } from "devtools-source-map";
import { getSourceFromId, getSourceWithContent } from "../../reducers/sources";
import { tabExists } from "../../reducers/tabs";
import { tabExists } from "../../selectors/tabs";
import { setSymbols } from "./symbols";
import { setInScopeLines } from "../ast";
import { closeActiveSearch, updateActiveFileSearch } from "../ui";

View file

@ -51,7 +51,7 @@ import {
getAllThreadsBySource,
getBreakableLinesForSourceActors,
} from "./source-actors";
import { getAllThreads } from "./threads";
import { getAllThreads } from "../selectors/threads";
export function initialSourcesState(state) {
return {

View file

@ -7,19 +7,11 @@
* @module reducers/tabs
*/
import { createSelector } from "reselect";
import { isOriginalId } from "devtools-source-map";
import { isSimilarTab, persistTabs } from "../utils/tabs";
import { makeShallowQuery } from "../utils/resource";
import { getPrettySourceURL } from "../utils/source";
import {
getSource,
getSpecificSourceByURL,
getSources,
resourceAsSourceBase,
} from "./sources";
import { getSource, getSpecificSourceByURL } from "./sources";
export function initialTabState() {
return { tabs: [] };
@ -249,34 +241,4 @@ function moveTab(tabs, currentIndex, newIndex) {
return { tabs: newTabs };
}
// Selectors
export const getTabs = state => state.tabs.tabs;
export const getSourceTabs = createSelector(
state => state.tabs,
({ tabs }) => tabs.filter(tab => tab.sourceId)
);
export const getSourcesForTabs = state => {
const tabs = getSourceTabs(state);
const sources = getSources(state);
return querySourcesForTabs(sources, tabs);
};
const querySourcesForTabs = makeShallowQuery({
filter: (_, tabs) => tabs.map(({ sourceId }) => sourceId),
map: resourceAsSourceBase,
reduce: items => items,
});
export function tabExists(state, sourceId) {
return !!getSourceTabs(state).find(tab => tab.sourceId == sourceId);
}
export function hasPrettyTab(state, sourceUrl) {
const prettyUrl = getPrettySourceURL(sourceUrl);
return !!getSourceTabs(state).find(tab => tab.url === prettyUrl);
}
export default update;

View file

@ -7,8 +7,6 @@
* @module reducers/threads
*/
import { createSelector } from "reselect";
export function initialThreadsState() {
return {
threads: [],
@ -53,52 +51,3 @@ export default function update(state = initialThreadsState(), action) {
return state;
}
}
export const getWorkerCount = state => getThreads(state).length;
export function getWorkerByThread(state, thread) {
return getThreads(state).find(worker => worker.actor == thread);
}
function isMainThread(thread) {
return thread.isTopLevel;
}
export function getMainThread(state) {
return state.threads.threads.find(isMainThread);
}
export function getDebuggeeUrl(state) {
return getMainThread(state)?.url || "";
}
export const getThreads = createSelector(
state => state.threads.threads,
threads => threads.filter(thread => !isMainThread(thread))
);
export const getAllThreads = createSelector(
getMainThread,
getThreads,
(mainThread, threads) => {
const orderedThreads = Array.from(threads).sort((threadA, threadB) => {
if (threadA.name === threadB.name) {
return 0;
}
return threadA.name < threadB.name ? -1 : 1;
});
return [mainThread, ...orderedThreads].filter(Boolean);
}
);
export function getThread(state, threadActor) {
return getAllThreads(state).find(thread => thread.actor === threadActor);
}
// checks if a path begins with a thread actor
// e.g "server1.conn0.child1/workerTarget22/context1/dbg-workers.glitch.me"
export function startsWithThreadActor(state, path) {
const threadActors = getAllThreads(state).map(t => t.actor);
const match = path.match(new RegExp(`(${threadActors.join("|")})\/(.*)`));
return match?.[1];
}

View file

@ -53,6 +53,8 @@ export {
getSelectedFrames,
getVisibleSelectedFrame,
} from "./pause";
export * from "./tabs";
export * from "./threads";
import { objectInspector } from "devtools/client/shared/components/reps/index";

View file

@ -15,6 +15,8 @@ CompiledModules(
"isLineInScope.js",
"isSelectedFrameVisible.js",
"pause.js",
"tabs.js",
"threads.js",
"visibleBreakpoints.js",
"visibleColumnBreakpoints.js",
)

View file

@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
import { createSelector } from "reselect";
import { makeShallowQuery } from "../utils/resource";
import { getPrettySourceURL } from "../utils/source";
import { getSources, resourceAsSourceBase } from "../reducers/sources";
export const getTabs = state => state.tabs.tabs;
export const getSourceTabs = createSelector(
state => state.tabs,
({ tabs }) => tabs.filter(tab => tab.sourceId)
);
export const getSourcesForTabs = state => {
const tabs = getSourceTabs(state);
const sources = getSources(state);
return querySourcesForTabs(sources, tabs);
};
const querySourcesForTabs = makeShallowQuery({
filter: (_, tabs) => tabs.map(({ sourceId }) => sourceId),
map: resourceAsSourceBase,
reduce: items => items,
});
export function tabExists(state, sourceId) {
return !!getSourceTabs(state).find(tab => tab.sourceId == sourceId);
}
export function hasPrettyTab(state, sourceUrl) {
const prettyUrl = getPrettySourceURL(sourceUrl);
return !!getSourceTabs(state).find(tab => tab.url === prettyUrl);
}

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
import { createSelector } from "reselect";
export const getThreads = createSelector(
state => state.threads.threads,
threads => threads.filter(thread => !isMainThread(thread))
);
export const getAllThreads = createSelector(
getMainThread,
getThreads,
(mainThread, threads) => {
const orderedThreads = Array.from(threads).sort((threadA, threadB) => {
if (threadA.name === threadB.name) {
return 0;
}
return threadA.name < threadB.name ? -1 : 1;
});
return [mainThread, ...orderedThreads].filter(Boolean);
}
);
function isMainThread(thread) {
return thread.isTopLevel;
}
export function getMainThread(state) {
return state.threads.threads.find(isMainThread);
}
export function getDebuggeeUrl(state) {
return getMainThread(state)?.url || "";
}
export function getThread(state, threadActor) {
return getAllThreads(state).find(thread => thread.actor === threadActor);
}
// checks if a path begins with a thread actor
// e.g "server1.conn0.child1/workerTarget22/context1/dbg-workers.glitch.me"
export function startsWithThreadActor(state, path) {
const threadActors = getAllThreads(state).map(t => t.actor);
const match = path.match(new RegExp(`(${threadActors.join("|")})\/(.*)`));
return match?.[1];
}

View file

@ -10,6 +10,7 @@
#include "jsapi.h"
#include "nsISupportsImpl.h"
#include "nsTObserverArray.h"
#include "mozilla/WeakPtr.h"
namespace mozilla {
namespace dom {
@ -23,8 +24,13 @@ class AbortFollower : public nsISupports {
public:
virtual void RunAbortAlgorithm() = 0;
// This adds strong reference to this follower on the signal, which means
// you'll need to call Unfollow() to prevent your object from living
// needlessly longer.
void Follow(AbortSignalImpl* aSignal);
// Explicitly call this to let garbage collection happen sooner when the
// follower finished its work and cannot be aborted anymore.
void Unfollow();
bool IsFollowing() const;
@ -32,21 +38,16 @@ class AbortFollower : public nsISupports {
AbortSignalImpl* Signal() const { return mFollowingSignal; }
protected:
// Subclasses of this class must call these Traverse and Unlink functions
// during corresponding cycle collection operations.
static void Traverse(AbortFollower* aFollower,
nsCycleCollectionTraversalCallback& cb);
static void Unlink(AbortFollower* aFollower) { aFollower->Unfollow(); }
virtual ~AbortFollower();
friend class AbortSignalImpl;
RefPtr<AbortSignalImpl> mFollowingSignal;
WeakPtr<AbortSignalImpl> mFollowingSignal;
};
class AbortSignalImpl : public nsISupports {
class AbortSignalImpl : public nsISupports, public SupportsWeakPtr {
public:
explicit AbortSignalImpl(bool aAborted, JS::Handle<JS::Value> aReason);
@ -67,7 +68,7 @@ class AbortSignalImpl : public nsISupports {
static void Unlink(AbortSignalImpl* aSignal);
virtual ~AbortSignalImpl() = default;
virtual ~AbortSignalImpl() { UnlinkFollowers(); }
JS::Heap<JS::Value> mReason;
@ -76,11 +77,13 @@ class AbortSignalImpl : public nsISupports {
void MaybeAssignAbortError(JSContext* aCx);
void UnlinkFollowers();
// Raw pointers. |AbortFollower::Follow| adds to this array, and
// |AbortFollower::Unfollow| (also callbed by the destructor) will remove
// |AbortFollower::Unfollow| (also called by the destructor) will remove
// from this array. Finally, calling |SignalAbort()| will (after running all
// abort algorithms) empty this and make all contained followers |Unfollow()|.
nsTObserverArray<AbortFollower*> mFollowers;
nsTObserverArray<RefPtr<AbortFollower>> mFollowers;
bool mAborted;
};

View file

@ -53,26 +53,23 @@ void AbortSignalImpl::SignalAbort(JS::Handle<JS::Value> aReason) {
// https://dom.spec.whatwg.org/#abortsignal-remove could be invoked in an
// earlier algorithm to remove a later algorithm, so |mFollowers| must be a
// |nsTObserverArray| to defend against mutation.
for (RefPtr<AbortFollower> follower : mFollowers.ForwardRange()) {
for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
MOZ_ASSERT(follower->mFollowingSignal == this);
follower->RunAbortAlgorithm();
}
// Step 4.
// Clear follower->signal links, then clear signal->follower links.
for (AbortFollower* follower : mFollowers.ForwardRange()) {
follower->mFollowingSignal = nullptr;
}
mFollowers.Clear();
UnlinkFollowers();
}
void AbortSignalImpl::Traverse(AbortSignalImpl* aSignal,
nsCycleCollectionTraversalCallback& cb) {
// To be filled in shortly.
ImplCycleCollectionTraverse(cb, aSignal->mFollowers, "mFollowers", 0);
}
void AbortSignalImpl::Unlink(AbortSignalImpl* aSignal) {
aSignal->mReason.setUndefined();
aSignal->UnlinkFollowers();
}
void AbortSignalImpl::MaybeAssignAbortError(JSContext* aCx) {
@ -91,6 +88,15 @@ void AbortSignalImpl::MaybeAssignAbortError(JSContext* aCx) {
mReason.set(exception);
}
void AbortSignalImpl::UnlinkFollowers() {
// Manually unlink all followers before destructing the array, or otherwise
// the array will be accessed by Unfollow() while being destructed.
for (RefPtr<AbortFollower>& follower : mFollowers.ForwardRange()) {
follower->mFollowingSignal = nullptr;
}
mFollowers.Clear();
}
// AbortSignal
// ----------------------------------------------------------------------------
@ -99,7 +105,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(AbortSignal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AbortSignal,
DOMEventTargetHelper)
AbortSignalImpl::Traverse(static_cast<AbortSignalImpl*>(tmp), cb);
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AbortSignal,
@ -213,10 +218,4 @@ void AbortFollower::Unfollow() {
bool AbortFollower::IsFollowing() const { return !!mFollowingSignal; }
/* static */ void AbortFollower::Traverse(
AbortFollower* aFollower, nsCycleCollectionTraversalCallback& cb) {
ImplCycleCollectionTraverse(cb, aFollower->mFollowingSignal,
"mFollowingSignal", 0);
}
} // namespace mozilla::dom

View file

@ -874,9 +874,33 @@ uint32_t Navigator::MaxTouchPoints(CallerType aCallerType) {
// https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers
// If you change this list, please also update the copy in E10SUtils.jsm.
static const char* const kSafeSchemes[] = {
"bitcoin", "geo", "im", "irc", "ircs", "magnet", "mailto",
"matrix", "mms", "news", "nntp", "openpgp4fpr", "sip", "sms",
"smsto", "ssh", "tel", "urn", "webcal", "wtai", "xmpp"};
// clang-format off
"bitcoin",
"ftp",
"ftps",
"geo",
"im",
"irc",
"ircs",
"magnet",
"mailto",
"matrix",
"mms",
"news",
"nntp",
"openpgp4fpr",
"sftp",
"sip",
"sms",
"smsto",
"ssh",
"tel",
"urn",
"webcal",
"wtai",
"xmpp",
// clang-format on
};
void Navigator::CheckProtocolHandlerAllowed(const nsAString& aScheme,
nsIURI* aHandlerURI,

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class="reftest-wait">
<meta charset="utf-8">
<script>
window.onload = () => {
window.requestIdleCallback(() => {
SpecialPowers.wrap(self).printPreview()
setTimeout(() => {
document.documentElement.classList.remove("reftest-wait");
}, 250)
})
}
</script>
<select autofocus='true'>

View file

@ -261,6 +261,7 @@ load 1656925.html
skip-if(Android) load 1665792.html # Print preview on android doesn't fly
skip-if(ThreadSanitizer) load 1681729.html
skip-if(ThreadSanitizer) load 1693049.html
skip-if(winWidget||Android) pref(print.always_print_silent,true) pref(print.print_to_file,true) pref(print.print_to_filename,"/dev/null") load 1697256.html # Windows seemingly requires pref(print_printer,"Mozilla Save to PDF") but pref() doesn't allow space. Android doesn't support print.
skip-if(ThreadSanitizer||Android) load 1697525.html
skip-if(ThreadSanitizer||Android) load 1712198.html # Mysterious failure that should be investigated (bug 1712866).
skip-if(Android) HTTP load 1728670-1.html

View file

@ -427,6 +427,16 @@ nsDOMWindowUtils::GetViewportFitInfo(nsAString& aViewportFit) {
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SetMousewheelAutodir(Element* aElement, bool aEnabled,
bool aHonourRoot) {
aElement->SetProperty(nsGkAtoms::forceMousewheelAutodir,
reinterpret_cast<void*>(aEnabled));
aElement->SetProperty(nsGkAtoms::forceMousewheelAutodirHonourRoot,
reinterpret_cast<void*>(aHonourRoot));
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SetDisplayPortForElement(float aXPx, float aYPx,
float aWidthPx, float aHeightPx,

View file

@ -1759,11 +1759,11 @@ void nsFocusManager::SetFocusInner(Element* aNewContent, int32_t aFlags,
(aFlags & (FLAG_SHOWRING | FLAG_NOSHOWRING));
newWindow->SetFocusedElement(elementToFocus, focusMethod);
if (aFocusChanged) {
nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell();
RefPtr<PresShell> presShell = docShell->GetPresShell();
if (presShell && presShell->DidInitialize()) {
ScrollIntoView(presShell, elementToFocus, aFlags);
if (nsCOMPtr<nsIDocShell> docShell = newWindow->GetDocShell()) {
RefPtr<PresShell> presShell = docShell->GetPresShell();
if (presShell && presShell->DidInitialize()) {
ScrollIntoView(presShell, elementToFocus, aFlags);
}
}
}

View file

@ -2144,7 +2144,6 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(EventListenerManager::ListenerSignalFollower)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
EventListenerManager::ListenerSignalFollower)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(

View file

@ -2399,6 +2399,104 @@ function doTestAutoDirScroll2(aSettings, aAutoDirTrait,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: true,
expected: kAdjustedForLeft.result,
cleanup (cb) {
SpecialPowers.pushPrefEnv({"set":
[["mousewheel.autodir.enabled",
false]]},
cb);
} },
// Tests: Test that autodir scrolling can be force-enabled using windowUtils.
// This only tests vertical wheel scrolls being adjusted to be
// horizontal, rather than re-testing all autodir behaviours just for
// this way of enabling it.
// Results: Vertical wheel scrolls are adjusted to be horizontal whereas the
// horizontal wheel scrolls are unadjusted.
// Reason: Auto-dir adjustment applies to a target if the target overflows
// in only one direction and the direction is orthogonal to the
// wheel and deltaZ is zero.
{ description: "force-enabled auto-dir scroll to " + kAdjustedForDown.desc +
"(originally bottom) by pixel scroll even if lineOrPageDelta is 0, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0,
lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: true,
expected: kAdjustedForDown.result,
prepare (cb) {
gScrollableElement.style.overflowX = "auto";
gScrollableElement.style.overflowY = "hidden";
resetScrollPosition(gScrollableElement);
winUtils.setMousewheelAutodir(gScrollableElement, true, kHonoursRoot)
cb();
} },
{ description: "force-enabled auto-dir scroll to " + kAdjustedForDown.desc +
"(originally bottom) by pixel scroll when lineOrPageDelta is 1, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 0.0, deltaY: 8.0, deltaZ: 0.0,
lineOrPageDeltaX: 0, lineOrPageDeltaY: 1, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: true,
expected: kAdjustedForDown.result },
{ description: "force-enabled auto-dir scroll to " + kAdjustedForUp.desc +
"(originally top) by pixel scroll even if lineOrPageDelta is 0, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0,
lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: true,
expected: kAdjustedForUp.result },
{ description: "force-enabled auto-dir scroll to " + kAdjustedForUp.desc +
"(originally top) by pixel scroll when lineOrPageDelta is -1, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 0.0, deltaY: -8.0, deltaZ: 0.0,
lineOrPageDeltaX: 0, lineOrPageDeltaY: -1, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: true,
expected: kAdjustedForUp.result },
{ description: "force-enabled auto-dir scroll to right by pixel scroll even if lineOrPageDelta is 0, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0,
lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: false,
expected: kScrollRight },
{ description: "force-enabled auto-dir scroll to right by pixel scroll when lineOrPageDelta is 1, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 8.0, deltaY: 0.0, deltaZ: 0.0,
lineOrPageDeltaX: 1, lineOrPageDeltaY: 0, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: false,
expected: kScrollRight },
{ description: "force-enabled auto-dir scroll to left by pixel scroll even if lineOrPageDelta is 0, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0,
lineOrPageDeltaX: 0, lineOrPageDeltaY: 0, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: false,
expected: kScrollLeft },
{ description: "force-enabled auto-dir scroll to left by pixel scroll when lineOrPageDelta is -1, " +
"no vertical scrollbar",
event: { deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: -8.0, deltaY: 0.0, deltaZ: 0.0,
lineOrPageDeltaX: -1, lineOrPageDeltaY: 0, isMomentum: false,
expectedOverflowDeltaX: 0, expectedOverflowDeltaY: 0,
shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, osKey: false },
adjusted: false,
expected: kScrollLeft,
cleanup (cb) {
gScrollableElement.style.position = "static";
gScrollableElement.style.top = "auto";
@ -2406,11 +2504,9 @@ function doTestAutoDirScroll2(aSettings, aAutoDirTrait,
gScrollableElement.style.overflow = "auto";
Object.assign(document.body.style, kOldStyleForRoot);
Object.assign(gScrollableElement.style, kOldStyleForTarget);
SpecialPowers.pushPrefEnv({"set":
[["mousewheel.autodir.enabled",
false]]},
cb);
} },
winUtils.setMousewheelAutodir(gScrollableElement, false, false);
cb();
} },
];
let styleDescForRoot = "";

View file

@ -175,7 +175,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WorkerSignalFollower)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WorkerSignalFollower)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerSignalFollower)
@ -1816,7 +1815,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EmptyBody,
FetchBody<EmptyBody>)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortSignalImpl)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)

View file

@ -909,6 +909,7 @@ void FetchDriver::FailWithNetworkError(nsresult rv) {
}
mChannel = nullptr;
Unfollow();
}
NS_IMETHODIMP
@ -1438,6 +1439,7 @@ void FetchDriver::FinishOnStopRequest(
}
mChannel = nullptr;
Unfollow();
}
NS_IMETHODIMP

View file

@ -14,7 +14,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(FetchObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FetchObserver,
DOMEventTargetHelper)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FetchObserver,

View file

@ -53,7 +53,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Request, FetchBody<Request>)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignal)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Request, FetchBody<Request>)

View file

@ -56,7 +56,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response, FetchBody<Response>)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response, FetchBody<Response>)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl)

View file

@ -1374,8 +1374,8 @@ void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
void HTMLInputElement::ResultForDialogSubmit(nsAString& aResult) {
if (mType == FormControlType::InputImage) {
// Get a property set by the frame to find out where it was clicked.
nsIntPoint* lastClickedPoint =
static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
const auto* lastClickedPoint =
static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
int32_t x, y;
if (lastClickedPoint) {
x = lastClickedPoint->x;
@ -5669,8 +5669,8 @@ HTMLInputElement::SubmitNamesValues(FormData* aFormData) {
// Submit .x, .y for input type=image
if (mType == FormControlType::InputImage) {
// Get a property set by the frame to find out where it was clicked.
nsIntPoint* lastClickedPoint =
static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
const auto* lastClickedPoint =
static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
int32_t x, y;
if (lastClickedPoint) {
// Convert the values to strings for submission

View file

@ -126,6 +126,17 @@ interface nsIDOMWindowUtils : nsISupports {
*/
void getContentViewerSize(out uint32_t aDisplayWidth, out uint32_t aDisplayHeight);
/**
* For any scrollable element, this allows you to override the default
* scroll behaviour and force autodir (which allows a mousewheel to
* horizontally scroll regions that only scroll on that one axis).
*
* See the documentation for mousewheel.autodir.enabled and
* mousewheel.autodir.honourroot for a more thorough explanation of
* what these behaviours do.
*/
void setMousewheelAutodir(in Element aElement, in boolean aEnabled, in boolean aHonourRoot);
/**
* For any scrollable element, this allows you to override the
* visible region and draw more than what is visible, which is

View file

@ -252,6 +252,7 @@ prefs =
[test_base-uri.html]
[test_blob_data_schemes.html]
[test_blob_uri_blocks_modals.html]
skip-if = xorigin && os == "linux" && (asan || tsan) # alert should be blocked by CSP - got false, expected true
[test_connect-src.html]
[test_CSP.html]
[test_bug1452037.html]

View file

@ -53,7 +53,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebAuthnManager,
WebAuthnManagerBase)
AbortFollower::Traverse(static_cast<AbortFollower*>(tmp), cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

View file

@ -12,7 +12,6 @@
// implement this yet.
[Exposed=Window]
interface CSSPageRule : CSSRule {
// selectorText not implemented yet
// attribute DOMString selectorText;
[Pref="layout.css.named-pages.enabled"] attribute UTF8String selectorText;
[SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style;
};

View file

@ -208,5 +208,7 @@ scheme=https
[test_bug1317725.html]
support-files = test_bug1317725.js
[test_sharedworker_event_listener_leaks.html]
skip-if = (bits == 64 && os == 'linux' && asan && !debug) # Disabled on Linux64 opt asan, bug 1493563
skip-if =
(bits == 64 && os == 'linux' && asan && !debug) # Disabled on Linux64 opt asan, bug 1493563
os == "win" && debug && xorigin # high frequency intermittent
[test_fileReaderSync_when_closing.html]

View file

@ -171,6 +171,8 @@ skip-if = os == 'android'
[test_bug1140105.html]
[test_bug1140617.html]
[test_bug1151186.html]
skip-if =
os == "win" && ccov && xorigin # high frequency intermittent
[test_bug1153237.html]
[test_bug1162952.html]
[test_bug1181130-1.html]

View file

@ -35,6 +35,8 @@ void gfxConfigManager::Init() {
StaticPrefs::gfx_webrender_compositor_force_enabled_AtStartup();
mGPUProcessAllowSoftware =
StaticPrefs::layers_gpu_process_allow_software_AtStartup();
mWrForcePartialPresent =
StaticPrefs::gfx_webrender_force_partial_present_AtStartup();
mWrPartialPresent =
StaticPrefs::gfx_webrender_max_partial_present_rects_AtStartup() > 0;
EmplaceUserPref(StaticPrefs::GetPrefName_gfx_webrender_program_binary_disk(),
@ -330,24 +332,15 @@ void gfxConfigManager::ConfigureWebRender() {
// Initialize WebRender partial present config.
// Partial present is used only when WebRender compositor is not used.
if (mWrPartialPresent) {
if (mFeatureWr->IsEnabled() || mFeatureWrSoftware->IsEnabled()) {
mFeatureWrPartial->EnableByDefault();
nsString adapter;
mGfxInfo->GetAdapterDeviceID(adapter);
// Block partial present on some devices due to rendering issues.
// On Mali-Txxx due to bug 1680087 and bug 1707815.
// On Adreno 3xx GPUs due to bug 1695771.
if (adapter.Find("Mali-T", /*ignoreCase*/ true) >= 0 ||
adapter.Find("Adreno (TM) 3", /*ignoreCase*/ true) >= 0) {
mFeatureWrPartial->Disable(
FeatureStatus::Blocked, "Partial present blocked",
"FEATURE_FAILURE_PARTIAL_PRESENT_BLOCKED"_ns);
}
}
mFeatureWrPartial->SetDefault(mWrPartialPresent, FeatureStatus::Disabled,
"User disabled via pref");
if (mWrForcePartialPresent) {
mFeatureWrPartial->UserForceEnable("Force enabled by pref");
}
ConfigureFromBlocklist(nsIGfxInfo::FEATURE_WEBRENDER_PARTIAL_PRESENT,
mFeatureWrPartial);
mFeatureWrShaderCache->SetDefaultFromPref(
StaticPrefs::GetPrefName_gfx_webrender_program_binary_disk(), true,
StaticPrefs::GetPrefDefault_gfx_webrender_program_binary_disk(),

View file

@ -38,6 +38,7 @@ class gfxConfigManager {
mWrForceAngleNoGPUProcess(false),
mWrDCompWinEnabled(false),
mWrCompositorDCompRequired(false),
mWrForcePartialPresent(false),
mWrPartialPresent(false),
mWrOptimizedShaders(false),
mGPUProcessAllowSoftware(false),
@ -86,6 +87,7 @@ class gfxConfigManager {
bool mWrForceAngleNoGPUProcess;
bool mWrDCompWinEnabled;
bool mWrCompositorDCompRequired;
bool mWrForcePartialPresent;
bool mWrPartialPresent;
Maybe<bool> mWrShaderCache;
bool mWrOptimizedShaders;

View file

@ -806,6 +806,8 @@ struct ScrollMetadata {
mIsRDMTouchSimulationActive(false),
mDidContentGetPainted(true),
mPrefersReducedMotion(false),
mForceMousewheelAutodir(false),
mForceMousewheelAutodirHonourRoot(false),
mOverscrollBehavior() {}
bool operator==(const ScrollMetadata& aOther) const {
@ -823,6 +825,9 @@ struct ScrollMetadata {
mIsRDMTouchSimulationActive == aOther.mIsRDMTouchSimulationActive &&
mDidContentGetPainted == aOther.mDidContentGetPainted &&
mPrefersReducedMotion == aOther.mPrefersReducedMotion &&
mForceMousewheelAutodir == aOther.mForceMousewheelAutodir &&
mForceMousewheelAutodirHonourRoot ==
aOther.mForceMousewheelAutodirHonourRoot &&
mDisregardedDirection == aOther.mDisregardedDirection &&
mOverscrollBehavior == aOther.mOverscrollBehavior &&
mScrollUpdates == aOther.mScrollUpdates;
@ -897,6 +902,18 @@ struct ScrollMetadata {
void SetPrefersReducedMotion(bool aValue) { mPrefersReducedMotion = aValue; }
bool PrefersReducedMotion() const { return mPrefersReducedMotion; }
void SetForceMousewheelAutodir(bool aValue) {
mForceMousewheelAutodir = aValue;
}
bool ForceMousewheelAutodir() const { return mForceMousewheelAutodir; }
void SetForceMousewheelAutodirHonourRoot(bool aValue) {
mForceMousewheelAutodirHonourRoot = aValue;
}
bool ForceMousewheelAutodirHonourRoot() const {
return mForceMousewheelAutodirHonourRoot;
}
bool DidContentGetPainted() const { return mDidContentGetPainted; }
private:
@ -1007,6 +1024,11 @@ struct ScrollMetadata {
// media query).
bool mPrefersReducedMotion : 1;
// Whether privileged code has requested that autodir behaviour be
// enabled for the scroll frame.
bool mForceMousewheelAutodir : 1;
bool mForceMousewheelAutodirHonourRoot : 1;
// The disregarded direction means the direction which is disregarded anyway,
// even if the scroll frame overflows in that direction and the direction is
// specified as scrollable. This could happen in some scenarios, for instance,

View file

@ -2128,12 +2128,13 @@ bool AsyncPanZoomController::CanScroll(const InputData& aEvent) const {
// to checking if it is scrollable without adjusting its delta.
// 2. For a non-auto-dir scroll, simply check if it is scrollable without
// adjusting its delta.
if (scrollWheelInput.IsAutoDir()) {
if (scrollWheelInput.IsAutoDir(mScrollMetadata.ForceMousewheelAutodir())) {
RecursiveMutexAutoLock lock(mRecursiveMutex);
auto deltaX = scrollWheelInput.mDeltaX;
auto deltaY = scrollWheelInput.mDeltaY;
bool isRTL =
IsContentOfHonouredTargetRightToLeft(scrollWheelInput.HonoursRoot());
IsContentOfHonouredTargetRightToLeft(scrollWheelInput.HonoursRoot(
mScrollMetadata.ForceMousewheelAutodirHonourRoot()));
APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
if (adjuster.ShouldBeAdjusted()) {
// If we detect that the delta values should be adjusted for an auto-dir
@ -2288,11 +2289,12 @@ nsEventStatus AsyncPanZoomController::OnScrollWheel(
auto deltaX = aEvent.mDeltaX;
auto deltaY = aEvent.mDeltaY;
ParentLayerPoint delta;
if (aEvent.IsAutoDir()) {
if (aEvent.IsAutoDir(mScrollMetadata.ForceMousewheelAutodir())) {
// It's an auto-dir scroll, so check if its delta should be adjusted, if so,
// adjust it.
RecursiveMutexAutoLock lock(mRecursiveMutex);
bool isRTL = IsContentOfHonouredTargetRightToLeft(aEvent.HonoursRoot());
bool isRTL = IsContentOfHonouredTargetRightToLeft(
aEvent.HonoursRoot(mScrollMetadata.ForceMousewheelAutodirHonourRoot()));
APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
if (adjuster.ShouldBeAdjusted()) {
adjuster.Adjust();
@ -5048,6 +5050,10 @@ void AsyncPanZoomController::NotifyLayersUpdated(
aScrollMetadata.GetIsRDMTouchSimulationActive());
mScrollMetadata.SetPrefersReducedMotion(
aScrollMetadata.PrefersReducedMotion());
mScrollMetadata.SetForceMousewheelAutodir(
aScrollMetadata.ForceMousewheelAutodir());
mScrollMetadata.SetForceMousewheelAutodirHonourRoot(
aScrollMetadata.ForceMousewheelAutodirHonourRoot());
mScrollMetadata.SetDisregardedDirection(
aScrollMetadata.GetDisregardedDirection());
mScrollMetadata.SetOverscrollBehavior(

View file

@ -434,6 +434,8 @@ struct ParamTraits<mozilla::layers::ScrollMetadata>
WriteParam(aMsg, aParam.mIsRDMTouchSimulationActive);
WriteParam(aMsg, aParam.mDidContentGetPainted);
WriteParam(aMsg, aParam.mPrefersReducedMotion);
WriteParam(aMsg, aParam.mForceMousewheelAutodir);
WriteParam(aMsg, aParam.mForceMousewheelAutodirHonourRoot);
WriteParam(aMsg, aParam.mDisregardedDirection);
WriteParam(aMsg, aParam.mOverscrollBehavior);
WriteParam(aMsg, aParam.mScrollUpdates);
@ -474,6 +476,11 @@ struct ParamTraits<mozilla::layers::ScrollMetadata>
&paramType::SetDidContentGetPainted) &&
ReadBoolForBitfield(aMsg, aIter, aResult,
&paramType::SetPrefersReducedMotion) &&
ReadBoolForBitfield(aMsg, aIter, aResult,
&paramType::SetForceMousewheelAutodir) &&
ReadBoolForBitfield(
aMsg, aIter, aResult,
&paramType::SetForceMousewheelAutodirHonourRoot) &&
ReadParam(aMsg, aIter, &aResult->mDisregardedDirection) &&
ReadParam(aMsg, aIter, &aResult->mOverscrollBehavior) &&
ReadParam(aMsg, aIter, &aResult->mScrollUpdates);

View file

@ -23,6 +23,7 @@ class MockGfxInfo final : public nsIGfxInfo {
int32_t mStatusWrCompositor;
int32_t mStatusWrShaderCache;
int32_t mStatusWrOptimizedShaders;
int32_t mStatusWrPartialPresent;
int32_t mMaxRefreshRate;
bool mHasMixedRefreshRate;
Maybe<bool> mHasBattery;
@ -35,6 +36,7 @@ class MockGfxInfo final : public nsIGfxInfo {
mStatusWrCompositor(nsIGfxInfo::FEATURE_STATUS_OK),
mStatusWrShaderCache(nsIGfxInfo::FEATURE_STATUS_OK),
mStatusWrOptimizedShaders(nsIGfxInfo::FEATURE_STATUS_OK),
mStatusWrPartialPresent(nsIGfxInfo::FEATURE_STATUS_OK),
mMaxRefreshRate(-1),
mHasMixedRefreshRate(false),
mHasBattery(Some(false)),
@ -56,6 +58,9 @@ class MockGfxInfo final : public nsIGfxInfo {
case nsIGfxInfo::FEATURE_WEBRENDER_OPTIMIZED_SHADERS:
*_retval = mStatusWrOptimizedShaders;
break;
case nsIGfxInfo::FEATURE_WEBRENDER_PARTIAL_PRESENT:
*_retval = mStatusWrPartialPresent;
break;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
@ -375,14 +380,22 @@ TEST_F(GfxConfigManager, WebRenderNoPartialPresent) {
EXPECT_TRUE(mFeatures.mWrSoftware.IsEnabled());
}
TEST_F(GfxConfigManager, WebRenderPartialPresentMali) {
TEST_F(GfxConfigManager, WebRenderPartialBlocked) {
mWrPartialPresent = true;
mMockGfxInfo->mDeviceId = "Mali-T760";
mMockGfxInfo->mStatusWrPartialPresent = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
ConfigureWebRender();
EXPECT_FALSE(mFeatures.mWrPartial.IsEnabled());
}
TEST_F(GfxConfigManager, WebRenderForcePartialBlocked) {
mWrForcePartialPresent = true;
mMockGfxInfo->mStatusWrPartialPresent = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
ConfigureWebRender();
EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled());
}
TEST_F(GfxConfigManager, WebRenderScaledResolutionWithHwStretching) {
mScaledResolution = true;
ConfigureWebRender();

View file

@ -829,6 +829,11 @@ pub enum ReferenceTransformBinding {
/// Computed reference frame which dynamically calculates the transform
/// based on the given parameters. The reference is the content size of
/// the parent iframe, which is affected by snapping.
///
/// This is used when a transform depends on the layout size of an
/// element, otherwise the difference between the unsnapped size
/// used in the transform, and the snapped size calculated during scene
/// building can cause seaming.
Computed {
scale_from: Option<LayoutSize>,
vertical_flip: bool,

View file

@ -0,0 +1,33 @@
// Finishing a loop will try to minimize phi nodes. We must properly replace
// phi nodes that escape a loop via catch block control flow patches.
wasmEvalText(`(module
(func)
(func (local i32)
try
loop
call 0
i32.const 0
br_if 0
end
catch_all
end
)
)`);
// Same as above, but ensure that we check every enclosing try block for
// control flow patches, as delegate can tunnel outwards.
wasmEvalText(`(module
(func)
(func (local i32)
try
try
loop
call 0
i32.const 0
br_if 0
end
delegate 0
catch_all
end
)
)`);

View file

@ -884,8 +884,10 @@ bool LazyStubTier::createOneEntryStub(uint32_t funcExportIndex,
// This uses the funcIndex as the major key and the tls pointer value as the
// minor key, the same as the < and == predicates used in RemoveDuplicates.
// However, since we only ever use this to search tables where every entry has
// the same tls, there is no actual code for tls comparison here.
auto IndirectStubComparator = [](uint32_t funcIndex, void* tlsData,
auto IndirectStubComparator = [](uint32_t funcIndex,
const IndirectStub& stub) -> int {
if (funcIndex < stub.funcIndex) {
return -1;
@ -894,12 +896,6 @@ auto IndirectStubComparator = [](uint32_t funcIndex, void* tlsData,
return 1;
}
// Function indices are equal.
if (uintptr_t(tlsData) < uintptr_t(stub.tls)) {
return -1;
}
if (uintptr_t(tlsData) > uintptr_t(stub.tls)) {
return 1;
}
return 0;
};
@ -982,24 +978,63 @@ bool LazyStubTier::createManyIndirectStubs(
}
// Record the runtime info about generated indirect stubs.
if (!indirectStubVector_.reserve(indirectStubVector_.length() +
targets.length())) {
return false;
// Count the number of new slots needed for the different tls values in the
// table. While there may be multiple tls values in the target set, the
// typical number is one or two.
struct Counter {
explicit Counter(void* tls) : tls(tls), counter(0) {}
void* tls;
size_t counter;
};
Vector<Counter, 8, SystemAllocPolicy> counters{};
for (const auto& target : targets) {
size_t i = 0;
while (i < counters.length() && target.tls != counters[i].tls) {
i++;
}
if (i == counters.length() && !counters.emplaceBack(target.tls)) {
return false;
}
counters[i].counter++;
}
// Reserve space in the tables, creating new tables as necessary. Do this
// first to avoid OOM while we're midway through installing stubs in the
// tables.
for (const auto& counter : counters) {
auto probe = indirectStubTable_.lookupForAdd(counter.tls);
if (!probe) {
IndirectStubVector v{};
if (!indirectStubTable_.add(probe, counter.tls, std::move(v))) {
return false;
}
}
IndirectStubVector& indirectStubVector = probe->value();
if (!indirectStubVector.reserve(indirectStubVector.length() +
counter.counter)) {
return false;
}
}
// We have storage, so now we can commit.
for (const auto& target : targets) {
auto stub = IndirectStub{target.functionIdx, lastStubSegmentIndex_,
indirectStubRangeIndex, target.tls};
indirectStubRangeIndex};
auto probe = indirectStubTable_.lookup(target.tls);
MOZ_RELEASE_ASSERT(probe);
IndirectStubVector& indirectStubVector = probe->value();
size_t indirectStubIndex;
MOZ_ALWAYS_FALSE(BinarySearchIf(
indirectStubVector_, 0, indirectStubVector_.length(),
indirectStubVector, 0, indirectStubVector.length(),
[&stub](const IndirectStub& otherStub) {
return IndirectStubComparator(stub.funcIndex, stub.tls, otherStub);
return IndirectStubComparator(stub.funcIndex, otherStub);
},
&indirectStubIndex));
MOZ_ALWAYS_TRUE(indirectStubVector_.insert(
indirectStubVector_.begin() + indirectStubIndex, std::move(stub)));
MOZ_ALWAYS_TRUE(indirectStubVector.insert(
indirectStubVector.begin() + indirectStubIndex, std::move(stub)));
++indirectStubRangeIndex;
}
@ -1078,16 +1113,20 @@ void* LazyStubTier::lookupInterpEntry(uint32_t funcIndex) const {
void* LazyStubTier::lookupIndirectStub(uint32_t funcIndex, void* tls) const {
size_t match;
auto probe = indirectStubTable_.lookup(tls);
if (!probe) {
return nullptr;
}
const IndirectStubVector& indirectStubVector = probe->value();
if (!BinarySearchIf(
indirectStubVector_, 0, indirectStubVector_.length(),
[funcIndex, tls](const IndirectStub& stub) {
return IndirectStubComparator(funcIndex, tls, stub);
indirectStubVector, 0, indirectStubVector.length(),
[funcIndex](const IndirectStub& stub) {
return IndirectStubComparator(funcIndex, stub);
},
&match)) {
return nullptr;
}
const IndirectStub& indirectStub = indirectStubVector_[match];
const IndirectStub& indirectStub = indirectStubVector[match];
const LazyStubSegment& segment = *stubSegments_[indirectStub.segmentIndex];
return segment.base() +

View file

@ -573,28 +573,48 @@ struct LazyFuncExport {
using LazyFuncExportVector = Vector<LazyFuncExport, 0, SystemAllocPolicy>;
// IndirectStub provides a mapping between function indices and
// indirect stubs code ranges.
// The function index is the index of the function *within a specific module*,
// IndirectStub provides a mapping between a function index and an indirect stub
// code range.
//
// The function index is the index of the function *within its defining module*,
// not necessarily in the module that owns the stub. That module's and
// function's instance is provided by the tls field.
// function's instance is provided by the tls field of the IndirectStubTable
// entry within which this IndirectStub is found.
struct IndirectStub {
size_t funcIndex;
size_t segmentIndex;
size_t codeRangeIndex;
void* tls;
IndirectStub(size_t funcIndex, size_t segmentIndex, size_t codeRangeIndex,
TlsData* tls)
IndirectStub(size_t funcIndex, size_t segmentIndex, size_t codeRangeIndex)
: funcIndex(funcIndex),
segmentIndex(segmentIndex),
codeRangeIndex(codeRangeIndex),
tls(tls) {}
codeRangeIndex(codeRangeIndex) {}
};
// IndirectStubVector represents a set of IndirectStubs. These stubs all belong
// to the same IndirectStubTable entry, and so all have the same tls value.
//
// The IndirectStubVector is ordered by IndirectStubComparator (WasmCode.cpp):
// the sort key is the funcIndex. The vector is binary-searched by that
// predicate when an entry is needed.
//
// Creating an indirect stub is not an idempotent operation! There must be NO
// duplicate entries in the table, or equivalently, an entry that is in the
// table must always be found by a binary search.
using IndirectStubVector = Vector<IndirectStub, 0, SystemAllocPolicy>;
// An IndirectStubTable represents a set of indirect stubs belonging to a
// module. There table is keyed uniquely by tls and there is one
// IndirectStubVector per tls value represented in the set.
//
// While the set is usually very small, its can grow with the product of the
// number of instances and the number of threads in a system, and we therefore
// use a hash table.
using IndirectStubTable =
HashMap<void*, IndirectStubVector, DefaultHasher<void*>, SystemAllocPolicy>;
// LazyStubTier contains all the necessary information for lazy function entry
// stubs and indirect stubs that are generated at runtime.
// None of its data are ever serialized.
@ -606,15 +626,7 @@ using IndirectStubVector = Vector<IndirectStub, 0, SystemAllocPolicy>;
class LazyStubTier {
LazyStubSegmentVector stubSegments_;
LazyFuncExportVector exports_;
// The indirectStubVector_ is totally ordered by IndirectStubComparator (in
// WasmCode.cpp): the primary index is the funcIndex, the secondary index the
// pointer value of the tls. The vector is binary-searched by that predicate
// when an entry is needed.
//
// Creating an indirect stub is not an idempotent operation! There must be NO
// duplicate entries in the table, which is another way of saying that an
// entry that is in the table must always be found by a lookup.
IndirectStubVector indirectStubVector_;
IndirectStubTable indirectStubTable_;
size_t lastStubSegmentIndex_;
[[nodiscard]] bool createManyEntryStubs(const Uint32Vector& funcExportIndices,

View file

@ -2482,6 +2482,22 @@ class FunctionCompiler {
fixupRedundantPhis(loopBody);
}
// Pending jumps to an enclosing try-catch may reference the recycled phis.
// We have to search above all enclosing try blocks, as a delegate may move
// patches around.
for (uint32_t depth = 0; depth < iter().controlStackDepth(); depth++) {
if (iter().controlKind(depth) != LabelKind::Try) {
continue;
}
Control& control = iter().controlItem(depth);
for (MControlInstruction* patch : control.tryPadPatches) {
MBasicBlock* block = patch->block();
if (block->loopDepth() >= loopEntry->loopDepth()) {
fixupRedundantPhis(block);
}
}
}
// Discard redundant phis and add to the free list.
for (MPhiIterator phi = loopEntry->phisBegin();
phi != loopEntry->phisEnd();) {

View file

@ -721,6 +721,9 @@ class MOZ_STACK_CLASS OpIter : private Policy {
// end of the function body.
bool controlStackEmpty() const { return controlStack_.empty(); }
// Return the depth of the control stack.
size_t controlStackDepth() const { return controlStack_.length(); }
// Find the innermost control item of a specific kind, starting to search from
// a certain relative depth, and returning true if such innermost control item
// is found. The relative depth of the found item is returned via a parameter.

View file

@ -253,17 +253,6 @@ bool mozJSSubScriptLoader::ReadStencil(
len = buf.Length();
}
#ifdef DEBUG
int64_t currentLength = -1;
// if getting content length succeeded above, it should not fail now
MOZ_ASSERT(chan->GetContentLength(&currentLength) == NS_OK);
// if content length was not known when GetContentLength() was called before,
// 'len' would be set to -1 until NS_ReadInputStreamToString() set its correct
// value. Every subsequent call to GetContentLength() should return the same
// length as that value.
MOZ_ASSERT(currentLength == len);
#endif
Maybe<JSAutoRealm> ar;
// Note that when using the ScriptPreloader cache with loadSubScript, there

View file

@ -49,6 +49,10 @@ static bool XrayWrapperConstructor(JSContext* cx, unsigned argc, Value* vp) {
}
if (!args[0].isObject()) {
if (args.isConstructing()) {
return ThrowException(NS_ERROR_XPC_BAD_CONVERT_JS, cx);
}
args.rval().set(args[0]);
return true;
}

View file

@ -8,6 +8,11 @@ function run_test() {
try { result = XPCNativeWrapper(2); } catch (e) {}
Assert.equal(result, 2);
// Make sure we throw when using `new` with primitives.
result = null;
try { result = new XPCNativeWrapper(2); } catch (e) { result = "catch"; }
Assert.equal(result, "catch");
// Make sure that we can waive on a non-Xrayable object, and that we preserve
// transitive waiving behavior.
var sb = new Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ["XMLHttpRequest"] });

View file

@ -8570,6 +8570,18 @@ ScrollMetadata nsLayoutUtils::ComputeScrollMetadata(
}
}
// Note: GetProperty() will return nullptr both in the case where
// the property hasn't been set, and in the case where the property
// has been set to false (in which case the property value is
// `reinterpret_cast<void*>(false)` which is nullptr.
if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodir)) {
metadata.SetForceMousewheelAutodir(true);
}
if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodirHonourRoot)) {
metadata.SetForceMousewheelAutodirHonourRoot(true);
}
if (IsAPZTestLoggingEnabled()) {
LogTestDataForPaint(aLayerManager, scrollId, "displayport",
metrics.GetDisplayPort());

View file

@ -74,8 +74,8 @@ void nsImageControlFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
return;
}
mContent->SetProperty(nsGkAtoms::imageClickedPoint, new nsIntPoint(0, 0),
nsINode::DeleteProperty<nsIntPoint>);
mContent->SetProperty(nsGkAtoms::imageClickedPoint, new CSSIntPoint(0, 0),
nsINode::DeleteProperty<CSSIntPoint>);
}
NS_QUERYFRAME_HEAD(nsImageControlFrame)
@ -123,13 +123,13 @@ nsresult nsImageControlFrame::HandleEvent(nsPresContext* aPresContext,
aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
// Store click point for HTMLInputElement::SubmitNamesValues
// Do this on MouseUp because the specs don't say and that's what IE does
nsIntPoint* lastClickPoint = static_cast<nsIntPoint*>(
auto* lastClickedPoint = static_cast<CSSIntPoint*>(
mContent->GetProperty(nsGkAtoms::imageClickedPoint));
if (lastClickPoint) {
if (lastClickedPoint) {
// normally lastClickedPoint is not null, as it's allocated in Init()
nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
aEvent, RelativeTo{this});
TranslateEventCoords(pt, *lastClickPoint);
*lastClickedPoint = TranslateEventCoords(pt);
}
}
return nsImageFrame::HandleEvent(aPresContext, aEvent, aEventStatus);

View file

@ -95,8 +95,7 @@ class nsDisplayCanvas final : public nsPaintedDisplayItem {
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) const override {
*aSnap = true;
nsHTMLCanvasFrame* f = static_cast<nsHTMLCanvasFrame*>(Frame());
return f->GetInnerArea() + ToReferenceFrame();
return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
}
virtual bool CreateWebRenderCommands(
@ -509,19 +508,7 @@ void nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext,
MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
WritingMode wm = aReflowInput.GetWritingMode();
LogicalSize finalSize = aReflowInput.ComputedSize();
// stash this away so we can compute our inner area later
mBorderPadding = aReflowInput.ComputedLogicalBorderPadding(wm);
finalSize.ISize(wm) += mBorderPadding.IStartEnd(wm);
finalSize.BSize(wm) += mBorderPadding.BStartEnd(wm);
if (GetPrevInFlow()) {
nscoord y = GetContinuationOffset(&finalSize.ISize(wm));
finalSize.BSize(wm) -= y + mBorderPadding.BStart(wm);
finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
}
const LogicalSize finalSize = aReflowInput.ComputedSizeWithBorderPadding(wm);
aMetrics.SetSize(wm, finalSize);
aMetrics.SetOverflowAreasToDesiredBounds();
@ -548,20 +535,6 @@ void nsHTMLCanvasFrame::Reflow(nsPresContext* aPresContext,
NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
}
// FIXME taken from nsImageFrame, but then had splittable frame stuff
// removed. That needs to be fixed.
// XXXdholbert As in nsImageFrame, this function's clients should probably
// just be calling GetContentRectRelativeToSelf().
nsRect nsHTMLCanvasFrame::GetInnerArea() const {
nsMargin bp = mBorderPadding.GetPhysicalMargin(GetWritingMode());
nsRect r;
r.x = bp.left;
r.y = bp.top;
r.width = mRect.width - bp.left - bp.right;
r.height = mRect.height - bp.top - bp.bottom;
return r;
}
bool nsHTMLCanvasFrame::UpdateWebRenderCanvasData(
nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
HTMLCanvasElement* element = static_cast<HTMLCanvasElement*>(GetContent());
@ -588,29 +561,6 @@ void nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
nsISelectionDisplay::DISPLAY_IMAGES);
}
// get the offset into the content area of the image where aImg starts if it is
// a continuation. from nsImageFrame
nscoord nsHTMLCanvasFrame::GetContinuationOffset(nscoord* aWidth) const {
nscoord offset = 0;
if (aWidth) {
*aWidth = 0;
}
if (GetPrevInFlow()) {
for (nsIFrame* prevInFlow = GetPrevInFlow(); prevInFlow;
prevInFlow = prevInFlow->GetPrevInFlow()) {
nsRect rect = prevInFlow->GetRect();
if (aWidth) {
*aWidth = rect.width;
}
offset += rect.height;
}
offset -= mBorderPadding.GetPhysicalMargin(GetWritingMode()).top;
offset = std::max(0, offset);
}
return offset;
}
void nsHTMLCanvasFrame::AppendDirectlyOwnedAnonBoxes(
nsTArray<OwnedAnonBox>& aResult) {
MOZ_ASSERT(mFrames.FirstChild(), "Must have our canvas content anon box");

View file

@ -38,9 +38,8 @@ class nsHTMLCanvasFrame final : public nsContainerFrame {
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsHTMLCanvasFrame)
explicit nsHTMLCanvasFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
: nsContainerFrame(aStyle, aPresContext, kClassID),
mBorderPadding(GetWritingMode()) {}
nsHTMLCanvasFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
: nsContainerFrame(aStyle, aPresContext, kClassID) {}
virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
nsIFrame* aPrevInFlow) override;
@ -73,8 +72,6 @@ class nsHTMLCanvasFrame final : public nsContainerFrame {
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
nsRect GetInnerArea() const;
#ifdef ACCESSIBILITY
virtual mozilla::a11y::AccType AccessibleType() override;
#endif
@ -98,10 +95,6 @@ class nsHTMLCanvasFrame final : public nsContainerFrame {
protected:
virtual ~nsHTMLCanvasFrame();
nscoord GetContinuationOffset(nscoord* aWidth = 0) const;
mozilla::LogicalMargin mBorderPadding;
};
#endif /* nsHTMLCanvasFrame_h___ */

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