зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1761784 - Firefox View Recently closed tabs implementation r=fluent-reviewers,dao,flod
* Sketch in recently-closed-tabs section and listing * Add some styles and suggested markup for the page-level sections & headers Differential Revision: https://phabricator.services.mozilla.com/D143365
This commit is contained in:
Родитель
4930002631
Коммит
eaa5d9e321
|
@ -22,6 +22,7 @@ const kESModuleList = new Set([
|
|||
/browser\/vpn-card.js$/,
|
||||
/browser\/content\/browser\/certerror\/aboutNetError\.js$/,
|
||||
/browser\/content\/browser\/myfirefox\.js$/,
|
||||
/browser\/content\/browser\/recently-closed-tabs\.js$/,
|
||||
/browser\/content\/browser\/tabs-pickup\.js$/,
|
||||
/toolkit\/content\/global\/certviewer\/components\/.*\.js$/,
|
||||
/toolkit\/content\/global\/certviewer\/.*\.js$/,
|
||||
|
|
|
@ -7,3 +7,5 @@ browser.jar:
|
|||
content/browser/myfirefox.js
|
||||
content/browser/myfirefox.css
|
||||
content/browser/tabs-pickup.js
|
||||
content/browser/recently-closed-tabs.js
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ myfirefox-page-title = My Firefox
|
|||
|
||||
myfirefox-colorway-button = Colorway Closet
|
||||
|
||||
# Used instead of the localized relative time when a timestamp is within a minute or so of now
|
||||
myfirefox-just-now-timestamp = Just now
|
||||
|
||||
myfirefox-tabpickup-header = Tab Pickup
|
||||
myfirefox-tabpickup-description = Pick up where you left off on another device.
|
||||
|
||||
|
@ -29,3 +32,15 @@ myfirefox-tabpickup-synctabs-primarybutton = Step 3 Action
|
|||
myfirefox-tabpickup-setupsuccess-header = Step 4 Header
|
||||
myfirefox-tabpickup-setupsuccess-description = Step 4 description.
|
||||
myfirefox-tabpickup-setupsuccess-primarybutton = Step 4 Action
|
||||
|
||||
myfirefox-closed-tabs-title = Recently closed
|
||||
myfirefox-closed-tabs-collapse-button =
|
||||
.title = Show or hide recently closed tabs list
|
||||
|
||||
myfirefox-closed-tabs-description = Reopen pages you’ve closed on this device.
|
||||
myfirefox-closed-tabs-placeholder = History is empty <br/> The next time you close a tab, you can reopen it here.
|
||||
|
||||
# Variables:
|
||||
# $targetURI (string) - URL that will be opened in the new tab
|
||||
myfirefox-closed-tabs-tab-button =
|
||||
.title = Open { $targetURI } in a new tab
|
||||
|
|
|
@ -6,6 +6,12 @@ body {
|
|||
display: flex;
|
||||
align-items: stretch;
|
||||
padding-block: 40px 80px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Ubuntu", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body > nav {
|
||||
|
@ -33,15 +39,116 @@ body > main > aside {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
.setup-step > h2 {
|
||||
margin-block: 4px;
|
||||
.empty-container {
|
||||
background-color: rgba(240, 240, 244, 0.4);
|
||||
}
|
||||
|
||||
.setup-step > .step-body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.placeholder-content {
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.page-section-header {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
grid-template-rows: 1fr auto;
|
||||
column-gap: 16px;
|
||||
|
||||
grid-template-areas:
|
||||
"head head head head head head head twisty"
|
||||
"desc desc desc desc desc desc desc desc";
|
||||
}
|
||||
|
||||
.page-section-header > h1 {
|
||||
color: var(--in-content-deemphasized-text);
|
||||
grid-area: head;
|
||||
margin: 0;
|
||||
padding-block: 4px;
|
||||
}
|
||||
|
||||
.page-section-header > .twisty {
|
||||
color: var(--in-content-deemphasized-text);
|
||||
grid-area: twisty;
|
||||
justify-self: end;
|
||||
margin-block: 0;
|
||||
min-width: 32px;
|
||||
padding-inline: 7px;
|
||||
}
|
||||
|
||||
.page-section-header > .section-description {
|
||||
grid-area: desc;
|
||||
}
|
||||
|
||||
.setup-step > h2 {
|
||||
margin-block: 4px;
|
||||
}
|
||||
|
||||
.setup-step > .step-body > :is(.step-content, button.primary) {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
#recently-closed-tabs-container {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
#recently-closed-tabs-container > p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.closed-tabs-list {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.closed-tab-li {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
column-gap: 16px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.closed-tab-li:hover {
|
||||
background-color: var(--in-content-button-background-hover);
|
||||
color: var(--in-content-button-text-color-hover);
|
||||
}
|
||||
|
||||
.closed-tab-li:hover:active {
|
||||
background-color: var(--in-content-button-background-active);
|
||||
}
|
||||
|
||||
.closed-tab-li-title {
|
||||
grid-column: span 5;
|
||||
padding-inline-start: 2px;
|
||||
}
|
||||
|
||||
.closed-tab-li-url {
|
||||
grid-column: span 2;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.closed-tab-li-time {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.icon {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.icon.arrow-down {
|
||||
background-image: url("chrome://global/skin/icons/arrow-down.svg");
|
||||
}
|
||||
|
||||
.icon.arrow-up {
|
||||
background-image: url("chrome://global/skin/icons/arrow-up.svg");
|
||||
}
|
||||
|
||||
.icon.history {
|
||||
background-image: url('chrome://browser/skin/history.svg');
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<link rel="stylesheet" href="chrome://browser/content/myfirefox.css">
|
||||
<script type="module" src="chrome://browser/content/tabs-pickup.js"></script>
|
||||
<script type="module" src="chrome://browser/content/myfirefox.js"></script>
|
||||
<script type="module" src="chrome://browser/content/recently-closed-tabs.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -71,6 +72,22 @@
|
|||
<aside>
|
||||
<button data-l10n-id="myfirefox-colorway-button" id="colorway-closet-button"></button>
|
||||
</aside>
|
||||
<section is="recently-closed-tabs-container" id="recently-closed-tabs-container">
|
||||
<header class="page-section-header">
|
||||
<h1 data-l10n-id="myfirefox-closed-tabs-title" ></h1>
|
||||
<button data-l10n-id="myfirefox-closed-tabs-collapse-button" id="collapsible-tabs-button" class="ghost-button twisty icon arrow-up"></button>
|
||||
<p class="section-description" data-l10n-id="myfirefox-closed-tabs-description"></p>
|
||||
</header>
|
||||
<div id="collapsible-tabs-container">
|
||||
<recently-closed-tabs-list>
|
||||
<ol hidden="true" class="closed-tabs-list"></ol>
|
||||
</recently-closed-tabs-list>
|
||||
<div hidden="true" id="recently-closed-tabs-placeholder" class="placeholder-content">
|
||||
<icon class="icon history"></icon>
|
||||
<p data-l10n-id="myfirefox-closed-tabs-placeholder"></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
import { tabsSetupFlowManager } from "./tabs-pickup.js";
|
||||
import "./recently-closed-tabs.js";
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
|
@ -23,4 +24,5 @@ window.addEventListener("load", () => {
|
|||
tabsSetupFlowManager.initialize(
|
||||
document.getElementById("tabs-pickup-container")
|
||||
);
|
||||
document.getElementById("recently-closed-tabs-container").onLoad();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
/* 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";
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
XPCOMUtils.defineLazyModuleGetters(globalThis, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
|
||||
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
|
||||
});
|
||||
|
||||
const relativeTimeFormat = new Services.intl.RelativeTimeFormat(undefined, {});
|
||||
|
||||
class RecentlyClosedTabsList extends HTMLElement {
|
||||
get tabsList() {
|
||||
return this.querySelector("ol");
|
||||
}
|
||||
|
||||
get fluentStrings() {
|
||||
if (!this._fluentStrings) {
|
||||
this._fluentStrings = new Localization(["preview/myFirefox.ftl"], true);
|
||||
}
|
||||
return this._fluentStrings;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.addEventListener("click", this);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type == "click") {
|
||||
const item = event.target.closest(".closed-tab-li");
|
||||
event.preventDefault();
|
||||
this.openTab(item.dataset.targetURI);
|
||||
}
|
||||
}
|
||||
|
||||
getWindow() {
|
||||
return window.windowRoot.ownerGlobal;
|
||||
}
|
||||
|
||||
convertTimestamp(timestamp) {
|
||||
const elapsed = Date.now() - timestamp;
|
||||
const nowThresholdMs = 91000;
|
||||
let formattedTime;
|
||||
if (elapsed <= nowThresholdMs) {
|
||||
// Use a different string for very recent timestamps
|
||||
formattedTime = this.fluentStrings.formatValueSync(
|
||||
"myfirefox-just-now-timestamp"
|
||||
);
|
||||
} else {
|
||||
formattedTime = relativeTimeFormat.formatBestUnit(new Date(timestamp));
|
||||
}
|
||||
return formattedTime;
|
||||
}
|
||||
|
||||
formatURIForDisplay(uriString) {
|
||||
// TODO: Bug 1764816: Make sure we handle file:///, jar:, blob, IP4/IP6 etc. addresses
|
||||
let uri;
|
||||
try {
|
||||
uri = Services.io.newURI(uriString);
|
||||
} catch (ex) {
|
||||
return uriString;
|
||||
}
|
||||
if (!uri.asciiHost) {
|
||||
return uriString;
|
||||
}
|
||||
let displayHost;
|
||||
try {
|
||||
// This might fail if it's an IP address or doesn't have more than 1 part
|
||||
displayHost = Services.eTLD.getBaseDomain(uri);
|
||||
} catch (ex) {
|
||||
return uri.displayHostPort;
|
||||
}
|
||||
return displayHost.length ? displayHost : uriString;
|
||||
}
|
||||
|
||||
getTargetURI(tab) {
|
||||
let targetURI = "";
|
||||
const tabEntries = tab.state.entries;
|
||||
const activeIndex = tabEntries.length - 1;
|
||||
|
||||
if (activeIndex >= 0 && tabEntries[activeIndex]) {
|
||||
targetURI = tabEntries[activeIndex].url;
|
||||
}
|
||||
|
||||
return targetURI;
|
||||
}
|
||||
|
||||
openTab(targetURI) {
|
||||
window.open(targetURI, "_blank");
|
||||
}
|
||||
|
||||
generateTabs() {
|
||||
let closedTabs = SessionStore.getClosedTabData(this.getWindow());
|
||||
closedTabs = closedTabs.slice(0, 25);
|
||||
|
||||
for (const tab of closedTabs) {
|
||||
let li = document.createElement("li");
|
||||
li.classList.add("closed-tab-li");
|
||||
|
||||
if (tab.image) {
|
||||
// TODO - figure out how to render this properly
|
||||
PlacesUIUtils.setImage(tab, li);
|
||||
}
|
||||
|
||||
let title = document.createElement("span");
|
||||
title.textContent = `${tab.title}`;
|
||||
title.classList.add("closed-tab-li-title");
|
||||
|
||||
const targetURI = this.getTargetURI(tab);
|
||||
li.dataset.targetURI = targetURI;
|
||||
document.l10n.setAttributes(li, "myfirefox-closed-tabs-tab-button", {
|
||||
targetURI,
|
||||
});
|
||||
|
||||
let url = document.createElement("span");
|
||||
|
||||
if (targetURI) {
|
||||
url.textContent = this.formatURIForDisplay(targetURI);
|
||||
url.classList.add("closed-tab-li-url");
|
||||
}
|
||||
|
||||
let time = document.createElement("span");
|
||||
time.textContent = this.convertTimestamp(tab.closedAt);
|
||||
time.classList.add("closed-tab-li-time");
|
||||
|
||||
li.append(title, url, time);
|
||||
this.tabsList.appendChild(li);
|
||||
}
|
||||
this.tabsList.hidden = false;
|
||||
}
|
||||
}
|
||||
customElements.define("recently-closed-tabs-list", RecentlyClosedTabsList);
|
||||
|
||||
class RecentlyClosedTabsContainer extends HTMLElement {
|
||||
getWindow = () => window.windowRoot.ownerGlobal;
|
||||
|
||||
connectedCallback() {
|
||||
this.noTabsElement = this.querySelector(
|
||||
"#recently-closed-tabs-placeholder"
|
||||
);
|
||||
this.list = this.querySelector("recently-closed-tabs-list");
|
||||
this.collapsibleContainer = this.querySelector(
|
||||
"#collapsible-tabs-container"
|
||||
);
|
||||
this.collapsibleButton = this.querySelector("#collapsible-tabs-button");
|
||||
|
||||
this.collapsibleButton.addEventListener("click", this);
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
if (this.getClosedTabCount() == 0) {
|
||||
this.noTabsElement.hidden = false;
|
||||
this.collapsibleContainer.classList.add("empty-container");
|
||||
} else {
|
||||
this.list.generateTabs();
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type == "click" && event.target == this.collapsibleButton) {
|
||||
this.toggleTabs();
|
||||
}
|
||||
}
|
||||
|
||||
getClosedTabCount = () => {
|
||||
try {
|
||||
return SessionStore.getClosedTabCount(this.getWindow());
|
||||
} catch (ex) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
toggleTabs = () => {
|
||||
const arrowUp = "arrow-up";
|
||||
const arrowDown = "arrow-down";
|
||||
const isOpen = this.collapsibleButton.classList.contains(arrowUp);
|
||||
|
||||
this.collapsibleButton.classList.toggle(arrowUp, !isOpen);
|
||||
this.collapsibleButton.classList.toggle(arrowDown, isOpen);
|
||||
this.collapsibleContainer.hidden = isOpen;
|
||||
};
|
||||
}
|
||||
customElements.define(
|
||||
"recently-closed-tabs-container",
|
||||
RecentlyClosedTabsContainer
|
||||
);
|
|
@ -1808,6 +1808,16 @@ var PlacesUIUtils = {
|
|||
// Can't setup speculative connection for this url, just ignore it.
|
||||
}
|
||||
},
|
||||
|
||||
setImage(aItem, aElement) {
|
||||
let iconURL = aItem.image;
|
||||
// don't initiate a connection just to fetch a favicon (see bug 467828)
|
||||
if (/^https?:/.test(iconURL)) {
|
||||
iconURL = "moz-anno:favicon:" + iconURL;
|
||||
}
|
||||
|
||||
aElement.setAttribute("image", iconURL);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,12 @@ ChromeUtils.defineModuleGetter(
|
|||
"resource:///modules/sessionstore/SessionStore.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"PlacesUIUtils",
|
||||
"resource:///modules/PlacesUIUtils.jsm"
|
||||
);
|
||||
|
||||
var navigatorBundle = Services.strings.createBundle(
|
||||
"chrome://browser/locale/browser.properties"
|
||||
);
|
||||
|
@ -156,16 +162,6 @@ var RecentlyClosedTabsAndWindowsMenuUtils = {
|
|||
},
|
||||
};
|
||||
|
||||
function setImage(aItem, aElement) {
|
||||
let iconURL = aItem.image;
|
||||
// don't initiate a connection just to fetch a favicon (see bug 467828)
|
||||
if (/^https?:/.test(iconURL)) {
|
||||
iconURL = "moz-anno:favicon:" + iconURL;
|
||||
}
|
||||
|
||||
aElement.setAttribute("image", iconURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a UI entry for a recently closed tab or window.
|
||||
* @param aTagName
|
||||
|
@ -196,7 +192,7 @@ function createEntry(
|
|||
|
||||
element.setAttribute("label", aMenuLabel);
|
||||
if (aClosedTab.image) {
|
||||
setImage(aClosedTab, element);
|
||||
PlacesUIUtils.setImage(aClosedTab, element);
|
||||
}
|
||||
if (!aIsWindowsFragment) {
|
||||
element.setAttribute("value", aIndex);
|
||||
|
|
Загрузка…
Ссылка в новой задаче