Update On Sat Jan 22 19:33:01 CET 2022
This commit is contained in:
parent
0d42a856e4
commit
52bfb1dfd4
322 changed files with 8106 additions and 2958 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
|
@ -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" },
|
||||
];
|
||||
|
|
|
@ -30,6 +30,10 @@ async function createDownloadFiles() {
|
|||
});
|
||||
}
|
||||
|
||||
registerCleanupFunction(async function() {
|
||||
await task_resetState();
|
||||
});
|
||||
|
||||
add_task(async function test_download_deleteFile() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
|
|
|
@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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("Editor’s Picks"));
|
||||
} else {
|
||||
cards.splice(6, 0, this.renderDSSubHeader("Editor’s 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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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("Editor’s Picks"));
|
||||
} else {
|
||||
cards.splice(6, 0, this.renderDSSubHeader("Editor’s 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: [{}, {}, {}],
|
||||
|
|
278
browser/components/places/SnapshotGroups.jsm
Normal file
278
browser/components/places/SnapshotGroups.jsm
Normal 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;
|
||||
}
|
||||
})();
|
|
@ -21,6 +21,7 @@ EXTRA_JS_MODULES += [
|
|||
"Interactions.jsm",
|
||||
"InteractionsBlocklist.jsm",
|
||||
"PlacesUIUtils.jsm",
|
||||
"SnapshotGroups.jsm",
|
||||
"Snapshots.jsm",
|
||||
"SnapshotScorer.jsm",
|
||||
"SnapshotSelector.jsm",
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 }]);
|
||||
});
|
|
@ -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
|
@ -4,3 +4,8 @@
|
|||
@import "./signup";
|
||||
@import "./home";
|
||||
@import "./styleguide";
|
||||
|
||||
// Components
|
||||
|
||||
@import "../js/components/ArticleList/ArticleList";
|
||||
@import "../js/components/Header/Header";
|
||||
|
|
|
@ -7,4 +7,9 @@
|
|||
.stp_superheader {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stp_styleguide_h4 {
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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");
|
||||
},
|
||||
|
|
|
@ -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__);
|
||||
/******/
|
||||
/******/ })()
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
};
|
||||
|
|
|
@ -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`)
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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/)",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
vendored:third_party/python/glean_parser
|
||||
pypi:pytest==3.6.2
|
||||
pypi:pytest==4.6.6
|
||||
|
|
|
@ -313,7 +313,7 @@ def main(*args, **kwargs):
|
|||
args.extend(
|
||||
[
|
||||
"--rootdir",
|
||||
topsrcdir,
|
||||
str(topsrcdir),
|
||||
"-c",
|
||||
os.path.join(here, "pytest.ini"),
|
||||
"-vv",
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -51,7 +51,7 @@ import {
|
|||
getAllThreadsBySource,
|
||||
getBreakableLinesForSourceActors,
|
||||
} from "./source-actors";
|
||||
import { getAllThreads } from "./threads";
|
||||
import { getAllThreads } from "../selectors/threads";
|
||||
|
||||
export function initialSourcesState(state) {
|
||||
return {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -53,6 +53,8 @@ export {
|
|||
getSelectedFrames,
|
||||
getVisibleSelectedFrame,
|
||||
} from "./pause";
|
||||
export * from "./tabs";
|
||||
export * from "./threads";
|
||||
|
||||
import { objectInspector } from "devtools/client/shared/components/reps/index";
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ CompiledModules(
|
|||
"isLineInScope.js",
|
||||
"isSelectedFrameVisible.js",
|
||||
"pause.js",
|
||||
"tabs.js",
|
||||
"threads.js",
|
||||
"visibleBreakpoints.js",
|
||||
"visibleColumnBreakpoints.js",
|
||||
)
|
||||
|
|
37
devtools/client/debugger/src/selectors/tabs.js
Normal file
37
devtools/client/debugger/src/selectors/tabs.js
Normal 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);
|
||||
}
|
48
devtools/client/debugger/src/selectors/threads.js
Normal file
48
devtools/client/debugger/src/selectors/threads.js
Normal 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];
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
14
dom/base/crashtests/1697256.html
Normal file
14
dom/base/crashtests/1697256.html
Normal 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'>
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 = "";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
|||
¶mType::SetDidContentGetPainted) &&
|
||||
ReadBoolForBitfield(aMsg, aIter, aResult,
|
||||
¶mType::SetPrefersReducedMotion) &&
|
||||
ReadBoolForBitfield(aMsg, aIter, aResult,
|
||||
¶mType::SetForceMousewheelAutodir) &&
|
||||
ReadBoolForBitfield(
|
||||
aMsg, aIter, aResult,
|
||||
¶mType::SetForceMousewheelAutodirHonourRoot) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mDisregardedDirection) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mOverscrollBehavior) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mScrollUpdates);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
33
js/src/jit-test/tests/wasm/exceptions/ion-loop-phi.js
Normal file
33
js/src/jit-test/tests/wasm/exceptions/ion-loop-phi.js
Normal 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
|
||||
)
|
||||
)`);
|
|
@ -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() +
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(¤tLength) == 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"] });
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue