mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Bug 1826608 - Implement open tabs from other device in new Firefox View. r=sclements,kcochrane,fluent-reviewers,fxview-reviewers,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D180836
This commit is contained in:
parent
a084b76ea0
commit
134b5be624
30
browser/components/firefoxview/content/synced-tabs-error.svg
Normal file
30
browser/components/firefoxview/content/synced-tabs-error.svg
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!-- 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/. -->
|
||||||
|
<svg width="156" height="113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g style="mix-blend-mode:luminosity">
|
||||||
|
<path opacity=".12" d="M75.2 88.827c-23.912-4.903-48.215 17.067-58.61 2.777-7.63-10.544 7.165-18.412-11.284-39.68S21.915 2.628 45.501 13.082 97.069-5.053 121.69.999c11.415 2.836 21.976 11.538 18.709 20.606-1.355 3.749-4.912 7.035-5.253 10.907-.66 7.553 13.624 17.47 15.428 24.873C159.05 92.177 99.111 93.73 75.2 88.827Z" fill="url(#a)"/>
|
||||||
|
<path stroke="context-stroke" stroke-linecap="round" d="M3.333 76.431h84.166M13.522 80.072h84.166"/>
|
||||||
|
<path d="M131.941 39.874h4.018c.73 0 1.278.913.73 1.643l-5.296 5.113h-1.643l-5.113-5.295c-.548-.548-.183-1.461.548-1.461h4.017c0-5.844-4.748-10.592-10.591-10.592-2.557 0-4.931.913-6.939 2.557-.548.548-1.461.365-2.009-.183-.183-.365-.183-.547-.183-.913 0-.365.183-.73.548-1.095 2.374-2.009 5.478-3.105 8.765-3.105 7.122 0 13.148 6.026 13.148 13.33ZM109.376 98.527h-4.018c0 5.844 4.748 10.591 10.592 10.591 2.556 0 4.93-.913 6.939-2.556.547-.548 1.461-.365 2.008.183.548.547.366 1.46-.182 2.008-2.374 2.009-5.479 3.105-8.583 3.105-7.304 0-13.33-6.026-13.33-13.33h-4.018c-.73 0-1.278-.914-.73-1.644l5.113-5.113h1.643l5.113 5.295c.548.548.183 1.461-.547 1.461Z" fill="#CB9EFF"/>
|
||||||
|
<path d="M25.308 77.432h80.247a1.5 1.5 0 0 1 1.258.684l4.964 7.646c.648.998-.068 2.317-1.258 2.317H20.344c-1.19 0-1.906-1.319-1.258-2.317l4.964-7.646a1.5 1.5 0 0 1 1.258-.684Z" fill="context-fill" stroke="context-stroke"/>
|
||||||
|
<rect x="24.169" y="22.567" width="82.527" height="57.05" rx="6.5" fill="context-fill" stroke="context-stroke"/>
|
||||||
|
<rect x="27.933" y="26.331" width="74.998" height="49.52" rx="4.375" fill="context-fill" stroke="context-stroke" stroke-width=".75"/>
|
||||||
|
<path d="M28.308 30.706a4 4 0 0 1 4-4h66.247a4 4 0 0 1 4 4v5.463H28.308v-5.462Z" fill="#CB9EFF"/>
|
||||||
|
<path stroke="context-stroke" stroke-width=".75" d="M28.308 36.522h74.975"/>
|
||||||
|
<rect x="31.699" y="29.368" width="19.332" height="4.357" rx=".873" fill="context-fill" stroke="context-stroke" stroke-width=".5"/>
|
||||||
|
<rect x="55.65" y="29.368" width="19.332" height="4.357" rx=".873" fill="context-fill" stroke="context-stroke" stroke-width=".5"/>
|
||||||
|
<path stroke="context-stroke" stroke-linecap="round" d="M135.232 98.218h19.269M134.566 100.365h16.885"/>
|
||||||
|
<rect x="116.823" y="98.075" width="45.403" height="27.933" rx="6.5" transform="rotate(-90 116.823 98.075)" fill="context-fill" stroke="context-stroke"/>
|
||||||
|
<rect x="119.858" y="95.039" width="39.33" height="21.86" rx="4.375" transform="rotate(-90 119.858 95.039)" fill="context-fill" stroke="context-stroke" stroke-width=".75"/>
|
||||||
|
<path d="M120.233 63.363v-3.279a4 4 0 0 1 4-4h13.11a4 4 0 0 1 4 4v3.28h-21.11Z" fill="#CB9EFF"/>
|
||||||
|
<path stroke="context-stroke" stroke-width=".75" d="M120.235 63.716h21.11"/>
|
||||||
|
<rect x="122.669" y="57.791" width="16.242" height="3.868" rx=".5" fill="context-fill" stroke="context-stroke" stroke-width=".5"/>
|
||||||
|
<circle cx="131.156" cy="90.296" r="2.662" fill="#FFBDC5" stroke="context-stroke" stroke-width=".5"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="a" x1=".527" y1="49.627" x2="150.86" y2="44.602" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#7542E4"/>
|
||||||
|
<stop offset="1" stop-color="#FF9AA2"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
@ -200,6 +200,18 @@ export const TabsSetupFlowManager = new (class {
|
|||||||
syncState.syncEnabled
|
syncState.syncEnabled
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get currentDevice() {
|
||||||
|
if (!this.fxaSignedIn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let recentDevices = lazy.fxAccounts.device?.recentDeviceList;
|
||||||
|
if (!recentDevices) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return recentDevices.find(device => device.isCurrentDevice)?.name;
|
||||||
|
}
|
||||||
|
|
||||||
get secondaryDeviceConnected() {
|
get secondaryDeviceConnected() {
|
||||||
if (!this.fxaSignedIn) {
|
if (!this.fxaSignedIn) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
--fxview-text-primary-color: var(--newtab-text-primary-color, var(--in-content-page-color));
|
--fxview-text-primary-color: var(--newtab-text-primary-color, var(--in-content-page-color));
|
||||||
--fxview-text-color-hover: var(--newtab-text-primary-color);
|
--fxview-text-color-hover: var(--newtab-text-primary-color);
|
||||||
--fxview-primary-action-background: var(--newtab-primary-action-background, #0061e0);
|
--fxview-primary-action-background: var(--newtab-primary-action-background, #0061e0);
|
||||||
|
--fxview-border: var(--fc-border-light, #CFCFD8);
|
||||||
|
|
||||||
/* ensure utility button hover states match those of the rest of the page */
|
/* ensure utility button hover states match those of the rest of the page */
|
||||||
--in-content-button-background-hover: var(--fxview-element-background-hover);
|
--in-content-button-background-hover: var(--fxview-element-background-hover);
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
<title data-l10n-id="firefoxview-page-title"></title>
|
<title data-l10n-id="firefoxview-page-title"></title>
|
||||||
<link rel="localization" href="branding/brand.ftl" />
|
<link rel="localization" href="branding/brand.ftl" />
|
||||||
|
<link rel="localization" href="toolkit/branding/accounts.ftl" />
|
||||||
<link rel="localization" href="browser/firefoxView.ftl" />
|
<link rel="localization" href="browser/firefoxView.ftl" />
|
||||||
<link rel="localization" href="toolkit/branding/brandings.ftl" />
|
<link rel="localization" href="toolkit/branding/brandings.ftl" />
|
||||||
<link rel="localization" href="browser/migrationWizard.ftl" />
|
<link rel="localization" href="browser/migrationWizard.ftl" />
|
||||||
@ -40,6 +41,10 @@
|
|||||||
type="module"
|
type="module"
|
||||||
src="chrome://browser/content/firefoxview/fxview-category-navigation.mjs"
|
src="chrome://browser/content/firefoxview/fxview-category-navigation.mjs"
|
||||||
></script>
|
></script>
|
||||||
|
<script
|
||||||
|
type="module"
|
||||||
|
src="chrome://browser/content/firefoxview/syncedtabs.mjs"
|
||||||
|
></script>
|
||||||
<script src="chrome://browser/content/contentTheme.js"></script>
|
<script src="chrome://browser/content/contentTheme.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -95,6 +100,7 @@
|
|||||||
<view-history name="history"></view-history>
|
<view-history name="history"></view-history>
|
||||||
<view-opentabs name="opentabs"></view-opentabs>
|
<view-opentabs name="opentabs"></view-opentabs>
|
||||||
<view-recentlyclosed name="recentlyclosed"></view-recentlyclosed>
|
<view-recentlyclosed name="recentlyclosed"></view-recentlyclosed>
|
||||||
|
<view-syncedtabs name="syncedtabs"></view-syncedtabs>
|
||||||
</named-deck>
|
</named-deck>
|
||||||
</main>
|
</main>
|
||||||
<script src="chrome://browser/content/firefoxview/firefoxview-next.mjs"></script>
|
<script src="chrome://browser/content/firefoxview/firefoxview-next.mjs"></script>
|
||||||
|
@ -29,7 +29,7 @@ class FxviewEmptyState extends MozLitElement {
|
|||||||
headerLabel: { type: String },
|
headerLabel: { type: String },
|
||||||
headerIconUrl: { type: String },
|
headerIconUrl: { type: String },
|
||||||
isSelectedTab: { type: Boolean },
|
isSelectedTab: { type: Boolean },
|
||||||
descriptionLabel: { type: Array },
|
descriptionLabels: { type: Array },
|
||||||
desciptionLink: { type: Object },
|
desciptionLink: { type: Object },
|
||||||
mainImageUrl: { type: String },
|
mainImageUrl: { type: String },
|
||||||
};
|
};
|
||||||
@ -63,12 +63,12 @@ class FxviewEmptyState extends MozLitElement {
|
|||||||
this.descriptionLabels,
|
this.descriptionLabels,
|
||||||
(descLabel, index) => html`<p
|
(descLabel, index) => html`<p
|
||||||
class="description ${index !== 0 ? "secondary" : null}"
|
class="description ${index !== 0 ? "secondary" : null}"
|
||||||
data-l10n-id=${descLabel}
|
data-l10n-id="${descLabel}"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
?hidden=${!this.descriptionLink}
|
?hidden=${!this.descriptionLink}
|
||||||
data-l10n-name=${this.descriptionLink.name}
|
data-l10n-name=${ifDefined(this.descriptionLink?.name)}
|
||||||
href=${this.descriptionLink.url}
|
href=${ifDefined(this.descriptionLink?.url)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
/>
|
/>
|
||||||
</p>`
|
</p>`
|
||||||
|
@ -13,6 +13,8 @@ browser.jar:
|
|||||||
content/browser/firefoxview/history.mjs
|
content/browser/firefoxview/history.mjs
|
||||||
content/browser/firefoxview/opentabs.mjs
|
content/browser/firefoxview/opentabs.mjs
|
||||||
content/browser/firefoxview/view-opentabs.css
|
content/browser/firefoxview/view-opentabs.css
|
||||||
|
content/browser/firefoxview/syncedtabs.mjs
|
||||||
|
content/browser/firefoxview/view-syncedtabs.css
|
||||||
content/browser/firefoxview/overview.mjs
|
content/browser/firefoxview/overview.mjs
|
||||||
content/browser/firefoxview/firefoxview.css
|
content/browser/firefoxview/firefoxview.css
|
||||||
content/browser/firefoxview/firefoxview-next.css
|
content/browser/firefoxview/firefoxview-next.css
|
||||||
@ -38,6 +40,7 @@ browser.jar:
|
|||||||
content/browser/firefoxview/category-recentlyclosed.svg (content/category-recentlyclosed.svg)
|
content/browser/firefoxview/category-recentlyclosed.svg (content/category-recentlyclosed.svg)
|
||||||
content/browser/firefoxview/category-syncedtabs.svg (content/category-syncedtabs.svg)
|
content/browser/firefoxview/category-syncedtabs.svg (content/category-syncedtabs.svg)
|
||||||
content/browser/firefoxview/tab-pickup-empty.svg (content/tab-pickup-empty.svg)
|
content/browser/firefoxview/tab-pickup-empty.svg (content/tab-pickup-empty.svg)
|
||||||
|
content/browser/firefoxview/synced-tabs-error.svg (content/synced-tabs-error.svg)
|
||||||
content/browser/callout-tab-pickup.svg (content/callout-tab-pickup.svg)
|
content/browser/callout-tab-pickup.svg (content/callout-tab-pickup.svg)
|
||||||
content/browser/callout-tab-pickup-dark.svg (content/callout-tab-pickup-dark.svg)
|
content/browser/callout-tab-pickup-dark.svg (content/callout-tab-pickup-dark.svg)
|
||||||
content/browser/cfr-lightning.svg (content/cfr-lightning.svg)
|
content/browser/cfr-lightning.svg (content/cfr-lightning.svg)
|
||||||
|
440
browser/components/firefoxview/syncedtabs.mjs
Normal file
440
browser/components/firefoxview/syncedtabs.mjs
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
const lazy = {};
|
||||||
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
|
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
|
||||||
|
SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
|
||||||
|
});
|
||||||
|
|
||||||
|
const { SyncedTabsErrorHandler } = ChromeUtils.importESModule(
|
||||||
|
"resource:///modules/firefox-view-synced-tabs-error-handler.sys.mjs"
|
||||||
|
);
|
||||||
|
const { TabsSetupFlowManager } = ChromeUtils.importESModule(
|
||||||
|
"resource:///modules/firefox-view-tabs-setup-manager.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
||||||
|
import { ViewPage } from "./viewpage.mjs";
|
||||||
|
|
||||||
|
const SYNCED_TABS_CHANGED = "services.sync.tabs.changed";
|
||||||
|
const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed";
|
||||||
|
const UI_OPEN_STATE = "browser.tabs.firefox-view.ui-state.tab-pickup.open";
|
||||||
|
|
||||||
|
class SyncedTabsInView extends ViewPage {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.boundObserve = (...args) => this.observe(...args);
|
||||||
|
this._currentSetupStateIndex = -1;
|
||||||
|
this.errorState = null;
|
||||||
|
this._id = Math.floor(Math.random() * 10e6);
|
||||||
|
this.currentSyncedTabs = [];
|
||||||
|
if (this.overview) {
|
||||||
|
this.maxTabsLength = 5;
|
||||||
|
} else {
|
||||||
|
// Setting maxTabsLength to -1 for no max
|
||||||
|
this.maxTabsLength = -1;
|
||||||
|
}
|
||||||
|
this.devices = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static properties = {
|
||||||
|
...ViewPage.properties,
|
||||||
|
errorState: { type: Number },
|
||||||
|
currentSyncedTabs: { type: Array },
|
||||||
|
_currentSetupStateIndex: { type: Number },
|
||||||
|
devices: { type: Array },
|
||||||
|
};
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.addEventListener("click", this);
|
||||||
|
this.ownerDocument.addEventListener("visibilitychange", this);
|
||||||
|
Services.obs.addObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
|
||||||
|
Services.obs.addObserver(this.boundObserve, SYNCED_TABS_CHANGED);
|
||||||
|
|
||||||
|
this.updateStates();
|
||||||
|
this.onVisibilityChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
TabsSetupFlowManager.updateViewVisibility(this._id, "unloaded");
|
||||||
|
this.ownerDocument?.removeEventListener("visibilitychange", this);
|
||||||
|
Services.obs.removeObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
|
||||||
|
Services.obs.removeObserver(this.boundObserve, SYNCED_TABS_CHANGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(event) {
|
||||||
|
if (event.type == "click" && event.target.dataset.action) {
|
||||||
|
const { ErrorType } = SyncedTabsErrorHandler;
|
||||||
|
switch (event.target.dataset.action) {
|
||||||
|
case `${ErrorType.SYNC_ERROR}`:
|
||||||
|
case `${ErrorType.NETWORK_OFFLINE}`:
|
||||||
|
case `${ErrorType.PASSWORD_LOCKED}`: {
|
||||||
|
TabsSetupFlowManager.tryToClearError();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case `${ErrorType.SIGNED_OUT}`:
|
||||||
|
case "sign-in": {
|
||||||
|
TabsSetupFlowManager.openFxASignup(event.target.ownerGlobal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "add-device": {
|
||||||
|
TabsSetupFlowManager.openFxAPairDevice(event.target.ownerGlobal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "sync-tabs-disabled": {
|
||||||
|
TabsSetupFlowManager.syncOpenTabs(event.target);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case `${ErrorType.SYNC_DISCONNECTED}`: {
|
||||||
|
const win = event.target.ownerGlobal;
|
||||||
|
const { switchToTabHavingURI } =
|
||||||
|
win.docShell.chromeEventHandler.ownerGlobal;
|
||||||
|
switchToTabHavingURI(
|
||||||
|
"about:preferences?action=choose-what-to-sync#sync",
|
||||||
|
true,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.type == "change") {
|
||||||
|
TabsSetupFlowManager.syncOpenTabs(event.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returning to fxview seems like a likely time for a device check
|
||||||
|
if (event.type == "visibilitychange") {
|
||||||
|
this.onVisibilityChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onVisibilityChange() {
|
||||||
|
const isVisible = document.visibilityState == "visible";
|
||||||
|
const isOpen = this.open;
|
||||||
|
if (isVisible && isOpen) {
|
||||||
|
this.update();
|
||||||
|
TabsSetupFlowManager.updateViewVisibility(this._id, "visible");
|
||||||
|
} else {
|
||||||
|
TabsSetupFlowManager.updateViewVisibility(
|
||||||
|
this._id,
|
||||||
|
isVisible ? "closed" : "hidden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async observe(subject, topic, errorState) {
|
||||||
|
if (topic == TOPIC_SETUPSTATE_CHANGED) {
|
||||||
|
this.updateStates({ errorState });
|
||||||
|
}
|
||||||
|
if (topic == SYNCED_TABS_CHANGED) {
|
||||||
|
this.getSyncedTabData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStates({
|
||||||
|
stateIndex = TabsSetupFlowManager.uiStateIndex,
|
||||||
|
errorState = SyncedTabsErrorHandler.getErrorType(),
|
||||||
|
} = {}) {
|
||||||
|
if (stateIndex == 4 && this._currentSetupStateIndex !== stateIndex) {
|
||||||
|
// trigger an initial request for the synced tabs list
|
||||||
|
this.getSyncedTabData();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._currentSetupStateIndex = stateIndex;
|
||||||
|
this.errorState = errorState;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionMappings = {
|
||||||
|
"sign-in": {
|
||||||
|
header: "firefoxview-syncedtabs-signin-header",
|
||||||
|
description: "firefoxview-syncedtabs-signin-description",
|
||||||
|
buttonLabel: "firefoxview-syncedtabs-signin-primarybutton",
|
||||||
|
},
|
||||||
|
"add-device": {
|
||||||
|
header: "firefoxview-syncedtabs-adddevice-header",
|
||||||
|
description: "firefoxview-syncedtabs-adddevice-description",
|
||||||
|
buttonLabel: "firefoxview-syncedtabs-adddevice-primarybutton",
|
||||||
|
link: "https://support.mozilla.org/kb/how-do-i-set-sync-my-computer#w_connect-additional-devices-to-sync",
|
||||||
|
},
|
||||||
|
"sync-tabs-disabled": {
|
||||||
|
header: "firefoxview-syncedtabs-synctabs-header",
|
||||||
|
description: "firefoxview-syncedtabs-synctabs-description",
|
||||||
|
checkboxLabel: "firefoxview-syncedtabs-synctabs-checkbox",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
generateMessageCard({ error = false, action, errorState }) {
|
||||||
|
errorState = errorState || this.errorState;
|
||||||
|
let header,
|
||||||
|
description,
|
||||||
|
descriptionLink,
|
||||||
|
buttonLabel,
|
||||||
|
checkboxLabel,
|
||||||
|
headerIconUrl,
|
||||||
|
mainImageUrl;
|
||||||
|
let descriptionArray;
|
||||||
|
if (error) {
|
||||||
|
let link;
|
||||||
|
({ header, description, link, buttonLabel } =
|
||||||
|
SyncedTabsErrorHandler.getFluentStringsForErrorType(errorState));
|
||||||
|
action = `${errorState}`;
|
||||||
|
headerIconUrl = "chrome://global/skin/icons/info-filled.svg";
|
||||||
|
mainImageUrl =
|
||||||
|
"chrome://browser/content/firefoxview/synced-tabs-error.svg";
|
||||||
|
descriptionArray = [description];
|
||||||
|
if (errorState == "password-locked") {
|
||||||
|
descriptionLink = {};
|
||||||
|
// This is ugly, but we need to special case this link so we can
|
||||||
|
// coexist with the old view.
|
||||||
|
descriptionArray.push("firefoxview-syncedtab-password-locked-link");
|
||||||
|
descriptionLink.name = "syncedtab-password-locked-link";
|
||||||
|
descriptionLink.url = link.href;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header = this.actionMappings[action].header;
|
||||||
|
description = this.actionMappings[action].description;
|
||||||
|
buttonLabel = this.actionMappings[action].buttonLabel;
|
||||||
|
checkboxLabel = this.actionMappings[action].checkboxLabel;
|
||||||
|
descriptionLink = this.actionMappings[action];
|
||||||
|
mainImageUrl =
|
||||||
|
"chrome://browser/content/firefoxview/synced-tabs-error.svg";
|
||||||
|
descriptionArray = [description];
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<fxview-empty-state
|
||||||
|
headerLabel=${header}
|
||||||
|
.descriptionLabels=${descriptionArray}
|
||||||
|
.descriptionLink=${ifDefined(descriptionLink)}
|
||||||
|
class="empty-state synced-tabs"
|
||||||
|
?isSelectedTab=${this.selectedTab}
|
||||||
|
mainImageUrl="${ifDefined(mainImageUrl)}"
|
||||||
|
headerIconUrl="${ifDefined(headerIconUrl)}"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="primary"
|
||||||
|
slot="primary-action"
|
||||||
|
?hidden=${!buttonLabel}
|
||||||
|
data-l10n-id="${ifDefined(buttonLabel)}"
|
||||||
|
data-action="${action}"
|
||||||
|
@click=${this.handleEvent}
|
||||||
|
></button>
|
||||||
|
<div slot="primary-action"
|
||||||
|
?hidden=${!checkboxLabel} >
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" @change=${this.handleEvent}></input>
|
||||||
|
<span data-l10n-id="${ifDefined(checkboxLabel)}"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fxview-empty-state>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenLink(event) {
|
||||||
|
let currentWindow = this.getWindow();
|
||||||
|
if (currentWindow.openTrustedLinkIn) {
|
||||||
|
let where = lazy.BrowserUtils.whereToOpenLink(
|
||||||
|
event.detail.originalEvent,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (where == "current") {
|
||||||
|
where = "tab";
|
||||||
|
}
|
||||||
|
currentWindow.openTrustedLinkIn(event.originalTarget.url, where);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onContextMenu(event) {
|
||||||
|
//TODO bug 1833664
|
||||||
|
}
|
||||||
|
|
||||||
|
noDeviceTabsTemplate(deviceName, deviceType) {
|
||||||
|
return html`<card-container>
|
||||||
|
<h2 slot="header">
|
||||||
|
<div class="icon ${deviceType}" role="presentation"></div>
|
||||||
|
${deviceName}
|
||||||
|
</h2>
|
||||||
|
<div slot="main" class="blackbox notabs">No tabs open on this device</div>
|
||||||
|
</card-container>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTabList() {
|
||||||
|
let renderArray = [];
|
||||||
|
let renderInfo = {};
|
||||||
|
for (let tab of this.currentSyncedTabs) {
|
||||||
|
if (!(tab.device in renderInfo)) {
|
||||||
|
renderInfo[tab.device] = {
|
||||||
|
deviceType: tab.deviceType,
|
||||||
|
tabs: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
renderInfo[tab.device].tabs.push(tab);
|
||||||
|
}
|
||||||
|
// Add devices without tabs
|
||||||
|
let currentDevice = TabsSetupFlowManager.currentDevice;
|
||||||
|
for (let device of this.devices) {
|
||||||
|
if (device.name == currentDevice) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(device.name in renderInfo)) {
|
||||||
|
renderInfo[device.name] = {
|
||||||
|
deviceType: device.type,
|
||||||
|
tabs: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let deviceName in renderInfo) {
|
||||||
|
if (renderInfo[deviceName].tabs.length) {
|
||||||
|
renderArray.push(html`<card-container>
|
||||||
|
<h2 slot="header">
|
||||||
|
<div
|
||||||
|
class="icon ${renderInfo[deviceName].deviceType}"
|
||||||
|
role="presentation"
|
||||||
|
></div>
|
||||||
|
${deviceName}
|
||||||
|
</h2>
|
||||||
|
<fxview-tab-list
|
||||||
|
slot="main"
|
||||||
|
.tabItems=${ifDefined(
|
||||||
|
this.getTabItems(renderInfo[deviceName].tabs)
|
||||||
|
)}
|
||||||
|
maxTabsLength=${this.maxTabsLength}
|
||||||
|
@fxview-tab-list-secondary-action=${this.onContextMenu}
|
||||||
|
@fxview-tab-list-primary-action=${this.onOpenLink}
|
||||||
|
></fxview-tab-list>
|
||||||
|
</card-container>`);
|
||||||
|
} else {
|
||||||
|
renderArray.push(
|
||||||
|
this.noDeviceTabsTemplate(
|
||||||
|
deviceName,
|
||||||
|
renderInfo[deviceName].deviceType
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return renderArray;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const stateIndex = this._currentSetupStateIndex;
|
||||||
|
|
||||||
|
this.open =
|
||||||
|
!TabsSetupFlowManager.isTabSyncSetupComplete ||
|
||||||
|
Services.prefs.getBoolPref(UI_OPEN_STATE, true);
|
||||||
|
|
||||||
|
let renderArray = [];
|
||||||
|
renderArray.push(html` <link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="chrome://browser/content/firefoxview/view-syncedtabs.css"
|
||||||
|
/>`);
|
||||||
|
renderArray.push(html` <link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="chrome://browser/content/firefoxview/firefoxview-next.css"
|
||||||
|
/>`);
|
||||||
|
if (!this.overview) {
|
||||||
|
renderArray.push(html`<div class="sticky-container bottom-fade">
|
||||||
|
<h2
|
||||||
|
class="page-header"
|
||||||
|
data-l10n-id="firefoxview-synced-tabs-header"
|
||||||
|
></h2>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (stateIndex) {
|
||||||
|
case 0 /* error-state */:
|
||||||
|
if (this.errorState) {
|
||||||
|
renderArray.push(this.generateMessageCard({ error: true }));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1 /* not-signed-in */:
|
||||||
|
if (Services.prefs.prefHasUserValue("services.sync.lastversion")) {
|
||||||
|
// If this pref is set, the user has signed out of sync.
|
||||||
|
// This path is also taken if we are disconnected from sync. See bug 1784055
|
||||||
|
renderArray.push(
|
||||||
|
this.generateMessageCard({ error: true, errorState: "signed-out" })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
renderArray.push(this.generateMessageCard({ action: "sign-in" }));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2 /* connect-secondary-device*/:
|
||||||
|
renderArray.push(this.generateMessageCard({ action: "add-device" }));
|
||||||
|
break;
|
||||||
|
case 3 /* disabled-tab-sync */:
|
||||||
|
renderArray.push(
|
||||||
|
this.generateMessageCard({ action: "sync-tabs-disabled" })
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 4 /* synced-tabs-loaded*/:
|
||||||
|
renderArray = renderArray.concat(this.generateTabList());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return renderArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onReload() {
|
||||||
|
await TabsSetupFlowManager.syncOnPageReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTabItems(tabs) {
|
||||||
|
tabs = tabs || this.tabs;
|
||||||
|
return tabs?.map(tab => ({
|
||||||
|
icon: tab.icon,
|
||||||
|
title: tab.title,
|
||||||
|
time: tab.lastUsed * 1000,
|
||||||
|
url: tab.url,
|
||||||
|
primaryL10nId: "firefoxview-tabs-list-tab-button",
|
||||||
|
primaryL10nArgs: JSON.stringify({ targetURI: tab.url }),
|
||||||
|
secondaryL10nId: "firefoxview-close-button",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTabsList(syncedTabs) {
|
||||||
|
if (!syncedTabs.length) {
|
||||||
|
this.currentSyncedTabs = syncedTabs;
|
||||||
|
this.sendTabTelemetry(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsToRender = syncedTabs;
|
||||||
|
|
||||||
|
// Return early if new tabs are the same as previous ones
|
||||||
|
if (
|
||||||
|
JSON.stringify(tabsToRender) == JSON.stringify(this.currentSyncedTabs)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentSyncedTabs = tabsToRender;
|
||||||
|
// Record the full tab count
|
||||||
|
this.sendTabTelemetry(syncedTabs.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSyncedTabData() {
|
||||||
|
this.devices = await lazy.SyncedTabs.getTabClients();
|
||||||
|
let tabs = await lazy.SyncedTabs.getRecentTabs(50, {
|
||||||
|
removeAllDupes: false,
|
||||||
|
removeDeviceDupes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateTabsList(tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTabTelemetry(numTabs) {
|
||||||
|
Services.telemetry.recordEvent(
|
||||||
|
"firefoxview-next",
|
||||||
|
"synced_tabs",
|
||||||
|
"tabs",
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
count: numTabs.toString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define("view-syncedtabs", SyncedTabsInView);
|
@ -30,6 +30,8 @@ skip-if = true # Bug 1783684
|
|||||||
[browser_setup_state.js]
|
[browser_setup_state.js]
|
||||||
[browser_setup_synced_tabs_loading.js]
|
[browser_setup_synced_tabs_loading.js]
|
||||||
[browser_sync_admin_disabled.js]
|
[browser_sync_admin_disabled.js]
|
||||||
|
[browser_syncedtabs_errors_firefoxview_next.js]
|
||||||
|
[browser_syncedtabs_firefoxview_next.js]
|
||||||
[browser_tab_close_last_tab.js]
|
[browser_tab_close_last_tab.js]
|
||||||
[browser_tab_on_close_warning.js]
|
[browser_tab_on_close_warning.js]
|
||||||
[browser_tab_pickup_device_added_telemetry.js]
|
[browser_tab_pickup_device_added_telemetry.js]
|
||||||
|
@ -17,11 +17,13 @@ function setupRecentDeviceListMocks() {
|
|||||||
name: "My desktop",
|
name: "My desktop",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "My iphone",
|
name: "My iphone",
|
||||||
type: "mobile",
|
type: "mobile",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -14,11 +14,13 @@ async function setupWithDesktopDevices(state = UIState.STATUS_SIGNED_IN) {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Other Device",
|
name: "Other Device",
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -32,11 +32,13 @@ async function setupWithDesktopDevices() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Other Device",
|
name: "Other Device",
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -119,6 +121,7 @@ add_task(async function test_signed_in() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -176,6 +179,7 @@ add_task(async function test_support_links() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -202,11 +206,13 @@ add_task(async function test_2nd_desktop_connected() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Other Device",
|
name: "Other Device",
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -246,11 +252,13 @@ add_task(async function test_mobile_connected() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Other Device",
|
name: "Other Device",
|
||||||
type: "mobile",
|
type: "mobile",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -290,11 +298,13 @@ add_task(async function test_tablet_connected() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Other Device",
|
name: "Other Device",
|
||||||
type: "tablet",
|
type: "tablet",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -334,11 +344,13 @@ add_task(async function test_tab_sync_enabled() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Other Device",
|
name: "Other Device",
|
||||||
type: "mobile",
|
type: "mobile",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -413,6 +425,7 @@ add_task(async function test_mobile_promo() {
|
|||||||
id: 3,
|
id: 3,
|
||||||
name: "Mobile Device",
|
name: "Mobile Device",
|
||||||
type: "mobile",
|
type: "mobile",
|
||||||
|
tabs: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
|
Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
|
||||||
@ -555,6 +568,7 @@ add_task(async function test_mobile_promo_windows() {
|
|||||||
id: 3,
|
id: 3,
|
||||||
name: "Mobile Device",
|
name: "Mobile Device",
|
||||||
type: "mobile",
|
type: "mobile",
|
||||||
|
tabs: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
|
Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
|
||||||
@ -711,6 +725,7 @@ add_task(async function test_close_device_connected_tab() {
|
|||||||
name: "This Device",
|
name: "This Device",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
const { LoginTestUtils } = ChromeUtils.importESModule(
|
||||||
|
"resource://testing-common/LoginTestUtils.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
async function setupWithDesktopDevices(state = UIState.STATUS_SIGNED_IN) {
|
||||||
|
const sandbox = setupSyncFxAMocks({
|
||||||
|
state,
|
||||||
|
fxaDevices: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "This Device",
|
||||||
|
isCurrentDevice: true,
|
||||||
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Other Device",
|
||||||
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return sandbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tearDown(sandbox) {
|
||||||
|
sandbox?.restore();
|
||||||
|
Services.prefs.clearUserPref("services.sync.lastTabFetch");
|
||||||
|
Services.prefs.clearUserPref("identity.fxaccounts.enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_setup(async function () {
|
||||||
|
// gSync.init() is called in a requestIdleCallback. Force its initialization.
|
||||||
|
gSync.init();
|
||||||
|
|
||||||
|
await SpecialPowers.pushPrefEnv({
|
||||||
|
set: [
|
||||||
|
["browser.tabs.firefox-view-next", true],
|
||||||
|
["services.sync.engine.tabs", true],
|
||||||
|
["identity.fxaccounts.enabled", true],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCleanupFunction(async function () {
|
||||||
|
// reset internal state so it doesn't affect the next tests
|
||||||
|
TabsSetupFlowManager.resetInternalState();
|
||||||
|
await tearDown(gSandbox);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_network_offline() {
|
||||||
|
const sandbox = await setupWithDesktopDevices();
|
||||||
|
await withFirefoxView({}, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"network:offline-status-changed",
|
||||||
|
"offline"
|
||||||
|
);
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
await BrowserTestUtils.waitForMutationCondition(
|
||||||
|
syncedTabsComponent.shadowRoot,
|
||||||
|
{ childList: true },
|
||||||
|
() => syncedTabsComponent.shadowRoot.innerHTML.includes("network-offline")
|
||||||
|
);
|
||||||
|
|
||||||
|
let emptyState =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelector("fxview-empty-state");
|
||||||
|
ok(
|
||||||
|
emptyState.getAttribute("headerlabel").includes("network-offline"),
|
||||||
|
"Network offline message is shown"
|
||||||
|
);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"network:offline-status-changed",
|
||||||
|
"online"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_sync_error() {
|
||||||
|
const sandbox = await setupWithDesktopDevices();
|
||||||
|
await withFirefoxView({}, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
Services.obs.notifyObservers(null, "weave:service:sync:error");
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
await BrowserTestUtils.waitForMutationCondition(
|
||||||
|
syncedTabsComponent.shadowRoot,
|
||||||
|
{ childList: true },
|
||||||
|
() => syncedTabsComponent.shadowRoot.innerHTML.includes("sync-error")
|
||||||
|
);
|
||||||
|
|
||||||
|
let emptyState =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelector("fxview-empty-state");
|
||||||
|
ok(
|
||||||
|
emptyState.getAttribute("headerlabel").includes("sync-error"),
|
||||||
|
"Correct message should show when there's a sync service error"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear the error.
|
||||||
|
Services.obs.notifyObservers(null, "weave:service:sync:finish");
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
});
|
@ -0,0 +1,267 @@
|
|||||||
|
add_setup(async function () {
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
// reset internal state so it doesn't affect the next tests
|
||||||
|
TabsSetupFlowManager.resetInternalState();
|
||||||
|
});
|
||||||
|
|
||||||
|
// gSync.init() is called in a requestIdleCallback. Force its initialization.
|
||||||
|
gSync.init();
|
||||||
|
|
||||||
|
registerCleanupFunction(async function () {
|
||||||
|
await tearDown(gSandbox);
|
||||||
|
});
|
||||||
|
await SpecialPowers.pushPrefEnv({
|
||||||
|
set: [["browser.tabs.firefox-view-next", true]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_unconfigured_initial_state() {
|
||||||
|
const sandbox = setupMocks({
|
||||||
|
state: UIState.STATUS_NOT_CONFIGURED,
|
||||||
|
syncEnabled: false,
|
||||||
|
});
|
||||||
|
await withFirefoxView({}, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
|
||||||
|
let emptyState =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelector("fxview-empty-state");
|
||||||
|
ok(
|
||||||
|
emptyState.getAttribute("headerlabel").includes("syncedtabs-signin"),
|
||||||
|
"Signin message is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_signed_in() {
|
||||||
|
const sandbox = setupMocks({
|
||||||
|
state: UIState.STATUS_SIGNED_IN,
|
||||||
|
fxaDevices: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "This Device",
|
||||||
|
isCurrentDevice: true,
|
||||||
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await withFirefoxView({}, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
await syncedTabsComponent.updateComplete;
|
||||||
|
let emptyState =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelector("fxview-empty-state");
|
||||||
|
ok(
|
||||||
|
emptyState.getAttribute("headerlabel").includes("syncedtabs-adddevice"),
|
||||||
|
"Add device message is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_no_synced_tabs() {
|
||||||
|
Services.prefs.setBoolPref("services.sync.engine.tabs", false);
|
||||||
|
const sandbox = setupMocks({
|
||||||
|
state: UIState.STATUS_SIGNED_IN,
|
||||||
|
fxaDevices: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "This Device",
|
||||||
|
isCurrentDevice: true,
|
||||||
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Other Device",
|
||||||
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await withFirefoxView({}, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
await syncedTabsComponent.updateComplete;
|
||||||
|
let emptyState =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelector("fxview-empty-state");
|
||||||
|
ok(
|
||||||
|
emptyState.getAttribute("headerlabel").includes("syncedtabs-synctabs"),
|
||||||
|
"Enable synced tabs message is shown"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
Services.prefs.setBoolPref("services.sync.engine.tabs", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_no_error_for_two_desktop() {
|
||||||
|
const sandbox = setupMocks({
|
||||||
|
state: UIState.STATUS_SIGNED_IN,
|
||||||
|
fxaDevices: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "This Device",
|
||||||
|
isCurrentDevice: true,
|
||||||
|
clientType: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Other Device",
|
||||||
|
clientType: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await withFirefoxView({}, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
await syncedTabsComponent.updateComplete;
|
||||||
|
// I don't love this, but I'm out of ideas
|
||||||
|
await TestUtils.waitForTick();
|
||||||
|
let emptyState =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelector("fxview-empty-state");
|
||||||
|
is(emptyState, null, "No empty state should be shown");
|
||||||
|
let noTabs = syncedTabsComponent.shadowRoot.querySelectorAll(".notabs");
|
||||||
|
is(noTabs.length, 1, "Should be 1 empty device");
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_empty_state() {
|
||||||
|
const sandbox = setupMocks({
|
||||||
|
state: UIState.STATUS_SIGNED_IN,
|
||||||
|
fxaDevices: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "This Device",
|
||||||
|
isCurrentDevice: true,
|
||||||
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Other Desktop",
|
||||||
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Other Mobile",
|
||||||
|
type: "phone",
|
||||||
|
tabs: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await withFirefoxView({ openNewWindow: true }, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
await syncedTabsComponent.updateComplete;
|
||||||
|
// I don't love this, but I'm out of ideas
|
||||||
|
await TestUtils.waitForTick();
|
||||||
|
let noTabs = syncedTabsComponent.shadowRoot.querySelectorAll(".notabs");
|
||||||
|
is(noTabs.length, 2, "Should be 2 empty devices");
|
||||||
|
|
||||||
|
let headers =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelectorAll("h2[slot=header]");
|
||||||
|
ok(
|
||||||
|
headers[0].textContent.includes("Other Desktop"),
|
||||||
|
"Text is correct (Desktop)"
|
||||||
|
);
|
||||||
|
ok(headers[0].innerHTML.includes("icon desktop"), "Icon should be desktop");
|
||||||
|
ok(
|
||||||
|
headers[1].textContent.includes("Other Mobile"),
|
||||||
|
"Text is correct (Mobile)"
|
||||||
|
);
|
||||||
|
ok(headers[1].innerHTML.includes("icon phone"), "Icon should be phone");
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_tabs() {
|
||||||
|
TabsSetupFlowManager.resetInternalState();
|
||||||
|
|
||||||
|
const sandbox = setupRecentDeviceListMocks();
|
||||||
|
const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
|
||||||
|
let mockTabs1 = getMockTabData(syncedTabsData1);
|
||||||
|
let getRecentTabsResult = mockTabs1;
|
||||||
|
syncedTabsMock.callsFake(() => {
|
||||||
|
info(
|
||||||
|
`Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
|
||||||
|
);
|
||||||
|
return Promise.resolve(getRecentTabsResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
await withFirefoxView({ openNewWindow: true }, async browser => {
|
||||||
|
const { document } = browser.contentWindow;
|
||||||
|
navigateToCategory(document, "syncedtabs");
|
||||||
|
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||||
|
|
||||||
|
let syncedTabsComponent = document.querySelector(
|
||||||
|
"view-syncedtabs:not([slot=syncedtabs])"
|
||||||
|
);
|
||||||
|
await syncedTabsComponent.updateComplete;
|
||||||
|
// I don't love this, but I'm out of ideas
|
||||||
|
await TestUtils.waitForTick();
|
||||||
|
|
||||||
|
let headers =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelectorAll("h2[slot=header]");
|
||||||
|
ok(
|
||||||
|
headers[0].textContent.includes("My desktop"),
|
||||||
|
"Text is correct (My desktop)"
|
||||||
|
);
|
||||||
|
ok(headers[0].innerHTML.includes("icon desktop"), "Icon should be desktop");
|
||||||
|
ok(
|
||||||
|
headers[1].textContent.includes("My iphone"),
|
||||||
|
"Text is correct (My iphone)"
|
||||||
|
);
|
||||||
|
ok(headers[1].innerHTML.includes("icon phone"), "Icon should be phone");
|
||||||
|
|
||||||
|
let tabLists =
|
||||||
|
syncedTabsComponent.shadowRoot.querySelectorAll("fxview-tab-list");
|
||||||
|
|
||||||
|
let tabRow1 = tabLists[0].shadowRoot.querySelectorAll("fxview-tab-row");
|
||||||
|
ok(
|
||||||
|
tabRow1[0].shadowRoot.textContent.includes,
|
||||||
|
"Internet for people, not profits - Mozilla"
|
||||||
|
);
|
||||||
|
ok(tabRow1[1].shadowRoot.textContent.includes, "Sandboxes - Sinon.JS");
|
||||||
|
is(tabRow1.length, 2, "Correct number of rows are displayed.");
|
||||||
|
let tabRow2 = tabLists[1].shadowRoot.querySelectorAll("fxview-tab-row");
|
||||||
|
is(tabRow2.length, 2, "Correct number of rows are dispayed.");
|
||||||
|
ok(tabRow1[0].shadowRoot.textContent.includes, "The Guardian");
|
||||||
|
ok(tabRow1[1].shadowRoot.textContent.includes, "The Times");
|
||||||
|
});
|
||||||
|
await tearDown(sandbox);
|
||||||
|
});
|
@ -15,12 +15,14 @@ function setupWithFxaDevices() {
|
|||||||
name: "My desktop",
|
name: "My desktop",
|
||||||
isCurrentDevice: true,
|
isCurrentDevice: true,
|
||||||
type: "desktop",
|
type: "desktop",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: "Other device",
|
name: "Other device",
|
||||||
isCurrentDevice: false,
|
isCurrentDevice: false,
|
||||||
type: "mobile",
|
type: "mobile",
|
||||||
|
tabs: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
@ -243,7 +243,7 @@ function setupRecentDeviceListMocks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getMockTabData(clients) {
|
function getMockTabData(clients) {
|
||||||
return SyncedTabs._internal._createRecentTabsList(clients, 3);
|
return SyncedTabs._internal._createRecentTabsList(clients, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupListState(browser) {
|
async function setupListState(browser) {
|
||||||
@ -336,6 +336,9 @@ function setupMocks({ fxaDevices = null, state, syncEnabled = true }) {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
sandbox.stub(SyncedTabs, "getTabClients").callsFake(() => {
|
||||||
|
return Promise.resolve(fxaDevices);
|
||||||
|
});
|
||||||
return sandbox;
|
return sandbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,3 +573,13 @@ registerCleanupFunction(() => {
|
|||||||
// that might have prevented it
|
// that might have prevented it
|
||||||
gSandbox?.restore();
|
gSandbox?.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function navigateToCategory(document, category) {
|
||||||
|
const navigation = document.querySelector("fxview-category-navigation");
|
||||||
|
let navButton = Array.from(navigation.categoryButtons).filter(
|
||||||
|
categoryButton => {
|
||||||
|
return categoryButton.name === category;
|
||||||
|
}
|
||||||
|
)[0];
|
||||||
|
navButton.buttonEl.click();
|
||||||
|
}
|
||||||
|
59
browser/components/firefoxview/view-syncedtabs.css
Normal file
59
browser/components/firefoxview/view-syncedtabs.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/* 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 url("chrome://global/skin/in-content/common.css");
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
vertical-align: bottom;
|
||||||
|
margin-inline-end: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
-moz-context-properties: fill;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone, .mobile {
|
||||||
|
background-image: url('chrome://browser/skin/device-phone.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.desktop {
|
||||||
|
background-image: url('chrome://browser/skin/device-desktop.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.tablet {
|
||||||
|
background-image: url('chrome://browser/skin/device-tablet.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notabs {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blackbox {
|
||||||
|
border: 1px solid var(--fxview-border);
|
||||||
|
text-align: center;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary {
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
@ -31,16 +31,28 @@ firefoxview-tabpickup-step-signin-header = Switch seamlessly between devices
|
|||||||
firefoxview-tabpickup-step-signin-description = To grab your phone tabs here, first sign in or create an account.
|
firefoxview-tabpickup-step-signin-description = To grab your phone tabs here, first sign in or create an account.
|
||||||
firefoxview-tabpickup-step-signin-primarybutton = Continue
|
firefoxview-tabpickup-step-signin-primarybutton = Continue
|
||||||
|
|
||||||
|
firefoxview-syncedtabs-signin-header = Grab tabs from anywhere
|
||||||
|
firefoxview-syncedtabs-signin-description = To see your tabs from wherever you use { -brand-product-name }, sign in to your account. If you don’t have an account, we’ll take you through the steps to sign up.
|
||||||
|
firefoxview-syncedtabs-signin-primarybutton = Sign in or sign up
|
||||||
|
|
||||||
firefoxview-tabpickup-adddevice-header = Sync { -brand-product-name } on your phone or tablet
|
firefoxview-tabpickup-adddevice-header = Sync { -brand-product-name } on your phone or tablet
|
||||||
firefoxview-tabpickup-adddevice-description = Download { -brand-product-name } for mobile and sign in there.
|
firefoxview-tabpickup-adddevice-description = Download { -brand-product-name } for mobile and sign in there.
|
||||||
firefoxview-tabpickup-adddevice-learn-how = Learn how
|
firefoxview-tabpickup-adddevice-learn-how = Learn how
|
||||||
firefoxview-tabpickup-adddevice-primarybutton = Get { -brand-product-name } for mobile
|
firefoxview-tabpickup-adddevice-primarybutton = Get { -brand-product-name } for mobile
|
||||||
|
|
||||||
|
firefoxview-syncedtabs-adddevice-header = Sign in to { -brand-product-name } on your other devices
|
||||||
|
firefoxview-syncedtabs-adddevice-description = To see your tabs from wherever you use { -brand-product-name }, sign in on all your devices. Learn how to <a data-l10n-name="url">connect additional devices</a>.
|
||||||
|
firefoxview-syncedtabs-adddevice-primarybutton = Try { -brand-product-name } for mobile
|
||||||
|
|
||||||
firefoxview-tabpickup-synctabs-header = Turn on tab syncing
|
firefoxview-tabpickup-synctabs-header = Turn on tab syncing
|
||||||
firefoxview-tabpickup-synctabs-description = Allow { -brand-short-name } to share tabs between devices.
|
firefoxview-tabpickup-synctabs-description = Allow { -brand-short-name } to share tabs between devices.
|
||||||
firefoxview-tabpickup-synctabs-learn-how = Learn how
|
firefoxview-tabpickup-synctabs-learn-how = Learn how
|
||||||
firefoxview-tabpickup-synctabs-primarybutton = Sync open tabs
|
firefoxview-tabpickup-synctabs-primarybutton = Sync open tabs
|
||||||
|
|
||||||
|
firefoxview-syncedtabs-synctabs-header = Update your sync settings
|
||||||
|
firefoxview-syncedtabs-synctabs-description = To see tabs from other devices, you need to sync your open tabs.
|
||||||
|
firefoxview-syncedtabs-synctabs-checkbox = Allow open tabs to sync
|
||||||
|
|
||||||
firefoxview-tabpickup-fxa-admin-disabled-header = Your organization has disabled sync
|
firefoxview-tabpickup-fxa-admin-disabled-header = Your organization has disabled sync
|
||||||
firefoxview-tabpickup-fxa-admin-disabled-description = { -brand-short-name } is not able to sync tabs between devices because your administrator has disabled syncing.
|
firefoxview-tabpickup-fxa-admin-disabled-description = { -brand-short-name } is not able to sync tabs between devices because your administrator has disabled syncing.
|
||||||
|
|
||||||
@ -60,6 +72,7 @@ firefoxview-tabpickup-password-locked-header = Enter your Primary Password to vi
|
|||||||
firefoxview-tabpickup-password-locked-description = To grab your tabs, you’ll need to enter the Primary Password for { -brand-short-name }.
|
firefoxview-tabpickup-password-locked-description = To grab your tabs, you’ll need to enter the Primary Password for { -brand-short-name }.
|
||||||
firefoxview-tabpickup-password-locked-link = Learn more
|
firefoxview-tabpickup-password-locked-link = Learn more
|
||||||
firefoxview-tabpickup-password-locked-primarybutton = Enter Primary Password
|
firefoxview-tabpickup-password-locked-primarybutton = Enter Primary Password
|
||||||
|
firefoxview-syncedtab-password-locked-link = <a data-l10n-name="syncedtab-password-locked-link">Learn more</a>
|
||||||
|
|
||||||
firefoxview-tabpickup-signed-out-header = Sign in to reconnect
|
firefoxview-tabpickup-signed-out-header = Sign in to reconnect
|
||||||
firefoxview-tabpickup-signed-out-description = To reconnect and grab your tabs, sign in to your { -fxaccount-brand-name }.
|
firefoxview-tabpickup-signed-out-description = To reconnect and grab your tabs, sign in to your { -fxaccount-brand-name }.
|
||||||
|
@ -81,17 +81,26 @@ let SyncedTabsInternal = {
|
|||||||
return reFilter.test(tab.url) || reFilter.test(tab.title);
|
return reFilter.test(tab.url) || reFilter.test(tab.title);
|
||||||
},
|
},
|
||||||
|
|
||||||
_createRecentTabsList(clients, maxCount) {
|
_createRecentTabsList(
|
||||||
|
clients,
|
||||||
|
maxCount,
|
||||||
|
extraParams = { removeAllDupes: true, removeDeviceDupes: false }
|
||||||
|
) {
|
||||||
let tabs = [];
|
let tabs = [];
|
||||||
|
|
||||||
for (let client of clients) {
|
for (let client of clients) {
|
||||||
|
if (extraParams.removeDeviceDupes) {
|
||||||
|
client.tabs = this._filterRecentTabsDupes(client.tabs);
|
||||||
|
}
|
||||||
for (let tab of client.tabs) {
|
for (let tab of client.tabs) {
|
||||||
tab.device = client.name;
|
tab.device = client.name;
|
||||||
tab.deviceType = client.clientType;
|
tab.deviceType = client.clientType;
|
||||||
}
|
}
|
||||||
tabs = [...tabs, ...client.tabs.reverse()];
|
tabs = [...tabs, ...client.tabs.reverse()];
|
||||||
}
|
}
|
||||||
tabs = this._filterRecentTabsDupes(tabs);
|
if (extraParams.removeAllDupes) {
|
||||||
|
tabs = this._filterRecentTabsDupes(tabs);
|
||||||
|
}
|
||||||
tabs = tabs.sort((a, b) => b.lastUsed - a.lastUsed).slice(0, maxCount);
|
tabs = tabs.sort((a, b) => b.lastUsed - a.lastUsed).slice(0, maxCount);
|
||||||
return tabs;
|
return tabs;
|
||||||
},
|
},
|
||||||
@ -327,8 +336,8 @@ export var SyncedTabs = {
|
|||||||
// Get list of synced tabs across all devices/clients
|
// Get list of synced tabs across all devices/clients
|
||||||
// truncated by value of maxCount param, sorted by
|
// truncated by value of maxCount param, sorted by
|
||||||
// lastUsed value, and filtered for duplicate URLs
|
// lastUsed value, and filtered for duplicate URLs
|
||||||
async getRecentTabs(maxCount) {
|
async getRecentTabs(maxCount, extraParams) {
|
||||||
let clients = await this.getTabClients();
|
let clients = await this.getTabClients();
|
||||||
return this._internal._createRecentTabsList(clients, maxCount);
|
return this._internal._createRecentTabsList(clients, maxCount, extraParams);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user