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:
Sarah Clements 2022-04-26 11:00:30 +00:00
Родитель 4930002631
Коммит eaa5d9e321
9 изменённых файлов: 358 добавлений и 16 удалений

Просмотреть файл

@ -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 youve 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);