зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1776779 - Add mobile promo and success confirmation banner. r=Gijs,markh
* Add mobile promo element to the Fxa/sync setup flow, and logic to show/hide it * Add success confirmation for the mobile sync connection with logic to show/hide it * Watch a new pref 'browser.tabs.firefox-view.mobilePromo.dismissed' for the promo * Add a new notification in FxAccountsDevice when the devicelist cache is updated * Use the devicelist updated notification drive the state changes in the setup flow manager * Add test coverage for the mobile promo Differential Revision: https://phabricator.services.mozilla.com/D151895
This commit is contained in:
Родитель
bc85adb480
Коммит
886cd16954
|
@ -15,6 +15,7 @@ const { XPCOMUtils } = ChromeUtils.importESModule(
|
|||
|
||||
const lazy = {};
|
||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
Log: "resource://gre/modules/Log.jsm",
|
||||
UIState: "resource://services-sync/UIState.jsm",
|
||||
SyncedTabs: "resource://services-sync/SyncedTabs.jsm",
|
||||
});
|
||||
|
@ -26,7 +27,11 @@ XPCOMUtils.defineLazyGetter(lazy, "fxAccounts", () => {
|
|||
|
||||
const SYNC_TABS_PREF = "services.sync.engine.tabs";
|
||||
const RECENT_TABS_SYNC = "services.sync.lastTabFetch";
|
||||
const MOBILE_PROMO_DISMISSED_PREF =
|
||||
"browser.tabs.firefox-view.mobilePromo.dismissed";
|
||||
const LOGGING_PREF = "browser.tabs.firefox-view.logLevel";
|
||||
const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed";
|
||||
const TOPIC_DEVICELIST_UPDATED = "fxaccounts:devicelist_updated";
|
||||
|
||||
function openTabInWindow(window, url) {
|
||||
const {
|
||||
|
@ -40,31 +45,7 @@ export const TabsSetupFlowManager = new (class {
|
|||
this.QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
|
||||
|
||||
this.setupState = new Map();
|
||||
this._currentSetupStateName = "";
|
||||
|
||||
Services.obs.addObserver(this, lazy.UIState.ON_UPDATE);
|
||||
Services.obs.addObserver(this, "fxaccounts:device_connected");
|
||||
Services.obs.addObserver(this, "fxaccounts:device_disconnected");
|
||||
|
||||
// this.syncTabsPrefEnabled will track the value of the tabs pref
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"syncTabsPrefEnabled",
|
||||
SYNC_TABS_PREF,
|
||||
false,
|
||||
() => {
|
||||
this.maybeUpdateUI();
|
||||
}
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"lastTabFetch",
|
||||
RECENT_TABS_SYNC,
|
||||
false,
|
||||
() => {
|
||||
this.maybeUpdateUI();
|
||||
}
|
||||
);
|
||||
this.resetInternalState();
|
||||
|
||||
this.registerSetupState({
|
||||
uiStateIndex: 0,
|
||||
|
@ -76,7 +57,7 @@ export const TabsSetupFlowManager = new (class {
|
|||
// TODO: handle offline, sync service not ready or available
|
||||
this.registerSetupState({
|
||||
uiStateIndex: 1,
|
||||
name: "connect-mobile-device",
|
||||
name: "connect-secondary-device",
|
||||
exitConditions: () => {
|
||||
return this.secondaryDeviceConnected;
|
||||
},
|
||||
|
@ -109,26 +90,81 @@ export const TabsSetupFlowManager = new (class {
|
|||
},
|
||||
});
|
||||
|
||||
// attempting to resolve the fxa user is a proxy for readiness
|
||||
lazy.fxAccounts.getSignedInUser().then(() => {
|
||||
this.maybeUpdateUI();
|
||||
});
|
||||
Services.obs.addObserver(this, lazy.UIState.ON_UPDATE);
|
||||
Services.obs.addObserver(this, TOPIC_DEVICELIST_UPDATED);
|
||||
|
||||
// this.syncTabsPrefEnabled will track the value of the tabs pref
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"syncTabsPrefEnabled",
|
||||
SYNC_TABS_PREF,
|
||||
false,
|
||||
() => {
|
||||
this._uiUpdateNeeded = true;
|
||||
this.maybeUpdateUI();
|
||||
}
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"lastTabFetch",
|
||||
RECENT_TABS_SYNC,
|
||||
false,
|
||||
() => {
|
||||
this._uiUpdateNeeded = true;
|
||||
this.maybeUpdateUI();
|
||||
}
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"mobilePromoDismissedPref",
|
||||
MOBILE_PROMO_DISMISSED_PREF,
|
||||
false,
|
||||
() => {
|
||||
this._uiUpdateNeeded = true;
|
||||
this.maybeUpdateUI();
|
||||
}
|
||||
);
|
||||
|
||||
this._uiUpdateNeeded = true;
|
||||
if (this.fxaSignedIn) {
|
||||
this.refreshDevices();
|
||||
}
|
||||
this.maybeUpdateUI();
|
||||
}
|
||||
|
||||
resetInternalState() {
|
||||
// assign initial values for all the managed internal properties
|
||||
this._currentSetupStateName = "not-signed-in";
|
||||
this._shouldShowSuccessConfirmation = false;
|
||||
this._didShowMobilePromo = false;
|
||||
this._uiUpdateNeeded = true;
|
||||
|
||||
// keep track of what is connected so we can respond to changes
|
||||
this._deviceStateSnapshot = {
|
||||
mobileDeviceConnected: this.mobileDeviceConnected,
|
||||
secondaryDeviceConnected: this.secondaryDeviceConnected,
|
||||
};
|
||||
}
|
||||
uninit() {
|
||||
Services.obs.removeObserver(this, lazy.UIState.ON_UPDATE);
|
||||
Services.obs.removeObserver(this, "fxaccounts:device_connected");
|
||||
Services.obs.removeObserver(this, "fxaccounts:device_disconnected");
|
||||
Services.obs.removeObserver(this, TOPIC_DEVICELIST_UPDATED);
|
||||
}
|
||||
get currentSetupState() {
|
||||
return this.setupState.get(this._currentSetupStateName);
|
||||
}
|
||||
get uiStateIndex() {
|
||||
const state =
|
||||
this._currentSetupStateName &&
|
||||
this.setupState.get(this._currentSetupStateName);
|
||||
return state ? state.uiStateIndex : -1;
|
||||
return this.currentSetupState.uiStateIndex;
|
||||
}
|
||||
get fxaSignedIn() {
|
||||
return lazy.UIState.get().status === lazy.UIState.STATUS_SIGNED_IN;
|
||||
let { UIState } = lazy;
|
||||
return (
|
||||
UIState.isReady() && UIState.get().status === UIState.STATUS_SIGNED_IN
|
||||
);
|
||||
}
|
||||
get secondaryDeviceConnected() {
|
||||
if (!this.fxaSignedIn) {
|
||||
return false;
|
||||
}
|
||||
let recentDevices = lazy.fxAccounts.device?.recentDeviceList?.length;
|
||||
return recentDevices > 1;
|
||||
}
|
||||
|
@ -139,6 +175,41 @@ export const TabsSetupFlowManager = new (class {
|
|||
lazy.SyncedTabs.TABS_FRESH_ENOUGH_INTERVAL_SECONDS
|
||||
);
|
||||
}
|
||||
get mobileDeviceConnected() {
|
||||
if (!this.fxaSignedIn) {
|
||||
return false;
|
||||
}
|
||||
let mobileClients = lazy.fxAccounts.device.recentDeviceList?.filter(
|
||||
device => device.type == "mobile"
|
||||
);
|
||||
return mobileClients?.length > 0;
|
||||
}
|
||||
get shouldShowMobilePromo() {
|
||||
return (
|
||||
this.currentSetupState.uiStateIndex >= 3 &&
|
||||
!this.mobileDeviceConnected &&
|
||||
!this.mobilePromoDismissedPref
|
||||
);
|
||||
}
|
||||
get shouldShowMobileConnectedSuccess() {
|
||||
return (
|
||||
this.currentSetupState.uiStateIndex >= 3 &&
|
||||
this._shouldShowSuccessConfirmation &&
|
||||
this.mobileDeviceConnected
|
||||
);
|
||||
}
|
||||
get logger() {
|
||||
if (!this._log) {
|
||||
let setupLog = lazy.Log.repository.getLogger("FirefoxView.TabsSetup");
|
||||
setupLog.manageLevelFromPref(LOGGING_PREF);
|
||||
setupLog.addAppender(
|
||||
new lazy.Log.ConsoleAppender(new lazy.Log.BasicFormatter())
|
||||
);
|
||||
this._log = setupLog;
|
||||
}
|
||||
return this._log;
|
||||
}
|
||||
|
||||
registerSetupState(state) {
|
||||
this.setupState.set(state.name, state);
|
||||
}
|
||||
|
@ -146,16 +217,60 @@ export const TabsSetupFlowManager = new (class {
|
|||
async observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case lazy.UIState.ON_UPDATE:
|
||||
this.logger.debug("Handling UIState update");
|
||||
this.maybeUpdateUI();
|
||||
break;
|
||||
case "fxaccounts:device_connected":
|
||||
case "fxaccounts:device_disconnected":
|
||||
await lazy.fxAccounts.device.refreshDeviceList();
|
||||
case TOPIC_DEVICELIST_UPDATED:
|
||||
this.logger.debug("Handling observer notification:", topic, data);
|
||||
this.refreshDevices();
|
||||
this.maybeUpdateUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
refreshDevices() {
|
||||
// compare new values to the previous values
|
||||
const mobileDeviceConnected = this.mobileDeviceConnected;
|
||||
const secondaryDeviceConnected = this.secondaryDeviceConnected;
|
||||
|
||||
this.logger.debug(
|
||||
`refreshDevices, mobileDeviceConnected: ${mobileDeviceConnected}, `,
|
||||
`secondaryDeviceConnected: ${secondaryDeviceConnected}`
|
||||
);
|
||||
|
||||
let didDeviceStateChange =
|
||||
this._deviceStateSnapshot.mobileDeviceConnected !=
|
||||
mobileDeviceConnected ||
|
||||
this._deviceStateSnapshot.secondaryDeviceConnected !=
|
||||
secondaryDeviceConnected;
|
||||
if (
|
||||
mobileDeviceConnected &&
|
||||
!this._deviceStateSnapshot.mobileDeviceConnected
|
||||
) {
|
||||
// a mobile device was added, show success if we previously showed the promo
|
||||
this._shouldShowSuccessConfirmation = this._didShowMobilePromo;
|
||||
} else if (
|
||||
!mobileDeviceConnected &&
|
||||
this._deviceStateSnapshot.mobileDeviceConnected
|
||||
) {
|
||||
// no mobile device connected now, reset
|
||||
Services.prefs.clearUserPref(MOBILE_PROMO_DISMISSED_PREF);
|
||||
this._shouldShowSuccessConfirmation = false;
|
||||
}
|
||||
this._deviceStateSnapshot = {
|
||||
mobileDeviceConnected,
|
||||
secondaryDeviceConnected,
|
||||
};
|
||||
if (didDeviceStateChange) {
|
||||
this.logger.debug(
|
||||
"refreshDevices: device state did change, call maybeUpdateUI"
|
||||
);
|
||||
this._uiUpdateNeeded = true;
|
||||
} else {
|
||||
this.logger.debug("refreshDevices: no device state change");
|
||||
}
|
||||
}
|
||||
|
||||
maybeUpdateUI() {
|
||||
let nextSetupStateName = this._currentSetupStateName;
|
||||
|
||||
|
@ -167,19 +282,34 @@ export const TabsSetupFlowManager = new (class {
|
|||
}
|
||||
}
|
||||
|
||||
if (nextSetupStateName !== this._currentSetupStateName) {
|
||||
const state = this.setupState.get(nextSetupStateName);
|
||||
let setupState = this.currentSetupState;
|
||||
if (nextSetupStateName != this._currentSetupStateName) {
|
||||
setupState = this.setupState.get(nextSetupStateName);
|
||||
this._currentSetupStateName = nextSetupStateName;
|
||||
|
||||
Services.obs.notifyObservers(
|
||||
null,
|
||||
TOPIC_SETUPSTATE_CHANGED,
|
||||
state.uiStateIndex
|
||||
);
|
||||
if ("function" == typeof state.enter) {
|
||||
state.enter();
|
||||
}
|
||||
this._uiUpdateNeeded = true;
|
||||
}
|
||||
this.logger.debug("maybeUpdateUI, _uiUpdateNeeded:", this._uiUpdateNeeded);
|
||||
if (this._uiUpdateNeeded) {
|
||||
this._uiUpdateNeeded = false;
|
||||
if (this.shouldShowMobilePromo) {
|
||||
this._didShowMobilePromo = true;
|
||||
}
|
||||
Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED);
|
||||
}
|
||||
if ("function" == typeof setupState.enter) {
|
||||
setupState.enter();
|
||||
}
|
||||
}
|
||||
|
||||
dismissMobilePromo() {
|
||||
Services.prefs.setBoolPref(MOBILE_PROMO_DISMISSED_PREF, true);
|
||||
}
|
||||
|
||||
dismissMobileConfirmation() {
|
||||
this._shouldShowSuccessConfirmation = false;
|
||||
this._didShowMobilePromo = false;
|
||||
this._uiUpdateNeeded = true;
|
||||
this.maybeUpdateUI();
|
||||
}
|
||||
|
||||
async openFxASignup(window) {
|
||||
|
@ -188,10 +318,12 @@ export const TabsSetupFlowManager = new (class {
|
|||
);
|
||||
openTabInWindow(window, url, true);
|
||||
}
|
||||
|
||||
openSyncPreferences(window) {
|
||||
const url = "about:preferences?action=pair#sync";
|
||||
openTabInWindow(window, url, true);
|
||||
}
|
||||
|
||||
syncOpenTabs(containerElem) {
|
||||
// Flip the pref on.
|
||||
// The observer should trigger re-evaluating state and advance to next step
|
||||
|
|
|
@ -14,6 +14,10 @@ menu-tools-firefox-view =
|
|||
|
||||
firefoxview-page-title = { -firefoxview-brand-name }
|
||||
|
||||
firefoxview-close-button =
|
||||
.title = Close
|
||||
.aria-label = Close
|
||||
|
||||
# Used instead of the localized relative time when a timestamp is within a minute or so of now
|
||||
firefoxview-just-now-timestamp = Just now
|
||||
|
||||
|
@ -43,6 +47,13 @@ firefoxview-tabpickup-synctabs-primarybutton = Sync open tabs
|
|||
|
||||
firefoxview-tabpickup-syncing = Sit tight while your tabs sync. It’ll be just a moment.
|
||||
|
||||
firefoxview-mobile-promo-header = Grab tabs from your phone or tablet
|
||||
firefoxview-mobile-promo-description = To view your latest mobile tabs, sign in to { -brand-product-name } on iOS or Android.
|
||||
firefoxview-mobile-promo-primarybutton = Get { -brand-product-name } for mobile
|
||||
|
||||
firefoxview-mobile-confirmation-header = 🎉 Good to go!
|
||||
firefoxview-mobile-confirmation-description = Now you can grab your { -brand-product-name } tabs from your tablet or phone.
|
||||
|
||||
firefoxview-closed-tabs-title = Recently closed
|
||||
firefoxview-closed-tabs-collapse-button =
|
||||
.title = Show or hide recently closed tabs list
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
--sidebar-width: 200px;
|
||||
--header-spacing: 40px;
|
||||
--footer-spacing: 80px;
|
||||
--card-border-zap-gradient: linear-gradient(90deg, #9059FF 0%, #FF4AA2 52.08%, #FFBD4F 100%);
|
||||
|
||||
--success-background-color: #87FFD1;
|
||||
--success-border-color: #2AC3A2;
|
||||
--success-color: #15141A;
|
||||
|
||||
--colorways-figure-size: 225px;
|
||||
--colorways-grid-template-areas:
|
||||
|
@ -254,6 +259,17 @@ body > main > aside {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
white-space: nowrap;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
button.close {
|
||||
background-image: url(chrome://global/skin/icons/close.svg);
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.card,
|
||||
.synced-tab-li,
|
||||
.synced-tab-li-placeholder,
|
||||
|
@ -306,12 +322,12 @@ body > main > aside {
|
|||
}
|
||||
|
||||
/* Bug 1770534 - Only use the zap-gradient for built-in, color-neutral themes */
|
||||
.setup-step {
|
||||
.zap-card {
|
||||
border: none;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
.setup-step::before {
|
||||
.zap-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
@ -339,11 +355,6 @@ body > main > aside {
|
|||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.setup-step > .step-body > button.primary {
|
||||
white-space: nowrap;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.setup-step > footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -354,6 +365,44 @@ body > main > aside {
|
|||
margin-block: 0 8px;
|
||||
}
|
||||
|
||||
.message-box {
|
||||
display: flex;
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
margin-block: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.message-content > .message-header {
|
||||
font-size: 1.12em;
|
||||
margin-block: 0 0.33em;
|
||||
}
|
||||
|
||||
.message-content > .message-description {
|
||||
margin-block: 0 0.33em;
|
||||
}
|
||||
|
||||
.confirmation-message-box {
|
||||
background-color: var(--success-background-color);
|
||||
color: var(--success-color);
|
||||
border-color: var(--success-border-color);
|
||||
}
|
||||
.confirmation-message-box > .message-content {
|
||||
text-align: center;
|
||||
}
|
||||
.confirmation-message-box > .message-content > .message-header {
|
||||
font-size: inherit;
|
||||
display: inline;
|
||||
}
|
||||
.confirmation-message-box > .icon-button {
|
||||
/* ensure we get the local color value as container doesnt change color with theme */
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* 117px is the total height of the collapsible-tabs-container; setting that size
|
||||
for the second row stabilizes the layout so it doesn't shift when collapsibled */
|
||||
#recently-closed-tabs-container {
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<main>
|
||||
<template id="sync-setup-template">
|
||||
<named-deck class="sync-setup-container" data-prefix="id:">
|
||||
<div name="sync-setup-view0" data-prefix="id:-view0" class="card setup-step" data-prefix="aria-labelledby:-view0-header">
|
||||
<div name="sync-setup-view0" data-prefix="id:-view0" class="card zap-card setup-step" data-prefix="aria-labelledby:-view0-header">
|
||||
<h2 data-prefix="id:-view0-header" data-l10n-id="firefoxview-tabpickup-step-signin-header" class="card-header"></h2>
|
||||
<section class="step-body">
|
||||
<p class="step-content" data-l10n-id="firefoxview-tabpickup-step-signin-description"></p>
|
||||
|
@ -47,7 +47,7 @@
|
|||
data-l10n-args='{"percentValue":"11"}'></label>
|
||||
</footer>
|
||||
</div>
|
||||
<div name="sync-setup-view1" data-prefix="id:-view1" class="card setup-step" data-prefix="aria-labelledby:-view1-header">
|
||||
<div name="sync-setup-view1" data-prefix="id:-view1" class="card zap-card setup-step" data-prefix="aria-labelledby:-view1-header">
|
||||
<h2 data-prefix="id:-view1-header" data-l10n-id="firefoxview-tabpickup-adddevice-header" class="card-header"></h2>
|
||||
<section class="step-body">
|
||||
<p class="step-content">
|
||||
|
@ -65,7 +65,7 @@
|
|||
data-l10n-args='{"percentValue":"33"}'></label>
|
||||
</footer>
|
||||
</div>
|
||||
<div name="sync-setup-view2" data-prefix="id:-view2" class="card setup-step" data-prefix="aria-labelledby:-view2-header">
|
||||
<div name="sync-setup-view2" data-prefix="id:-view2" class="card zap-card setup-step" data-prefix="aria-labelledby:-view2-header">
|
||||
<h2 data-prefix="id:-view2-header" data-l10n-id="firefoxview-tabpickup-synctabs-header" class="card-header"></h2>
|
||||
<section class="step-body">
|
||||
<p class="step-content">
|
||||
|
@ -104,6 +104,25 @@
|
|||
<button id="collapsible-synced-tabs-button" class="ghost-button twisty icon arrow-up" hidden></button>
|
||||
<p class="section-description" data-l10n-id="firefoxview-tabpickup-description"></p>
|
||||
</header>
|
||||
|
||||
<div class="confirmation-message-box message-box card card-no-hover" hidden>
|
||||
<div class="message-content">
|
||||
<h2 data-l10n-id="firefoxview-mobile-confirmation-header" class="message-header"></h2>
|
||||
<span class="message-description" data-l10n-id="firefoxview-mobile-confirmation-description"></span>
|
||||
</div>
|
||||
<button data-action="mobile-confirmation-dismiss" class="close icon-button ghost-button" data-l10n-id="firefoxview-close-button"></button>
|
||||
</div>
|
||||
<div id="sync-setup-placeholder" hidden></div>
|
||||
<div id="synced-tabs-placeholder" hidden></div>
|
||||
<div class="promo-box message-box zap-card card-no-hover card" hidden>
|
||||
<div class="message-content">
|
||||
<h2 data-l10n-id="firefoxview-mobile-promo-header" class="message-header"></h2>
|
||||
<p class="message-description" data-l10n-id="firefoxview-mobile-promo-description"></p>
|
||||
</div>
|
||||
<button class="primary" data-action="mobile-promo-primary-action" data-l10n-id="firefoxview-mobile-promo-primarybutton"></button>
|
||||
<button data-action="mobile-promo-dismiss" class="close icon-button ghost-button" data-l10n-id="firefoxview-close-button"></button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<aside class="content-container" is="colorways-card">
|
||||
|
|
|
@ -31,8 +31,9 @@ class TabPickupContainer extends HTMLElement {
|
|||
|
||||
connectedCallback() {
|
||||
this.addEventListener("click", this);
|
||||
this.addEventListener("visibilitychange", this);
|
||||
Services.obs.addObserver(this.boundObserve, TOPIC_SETUPSTATE_CHANGED);
|
||||
this.updateSetupState(TabsSetupFlowManager.uiStateIndex);
|
||||
this.update();
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
|
@ -62,17 +63,43 @@ class TabPickupContainer extends HTMLElement {
|
|||
TabsSetupFlowManager.syncOpenTabs(event.target);
|
||||
break;
|
||||
}
|
||||
case "mobile-promo-dismiss": {
|
||||
TabsSetupFlowManager.dismissMobilePromo(event.target);
|
||||
break;
|
||||
}
|
||||
case "mobile-promo-primary-action": {
|
||||
TabsSetupFlowManager.openSyncPreferences(event.target.ownerGlobal);
|
||||
break;
|
||||
}
|
||||
case "mobile-confirmation-dismiss": {
|
||||
TabsSetupFlowManager.dismissMobileConfirmation(event.target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async observe(subject, topic, stateIndex) {
|
||||
if (topic == TOPIC_SETUPSTATE_CHANGED) {
|
||||
this.updateSetupState(TabsSetupFlowManager.uiStateIndex);
|
||||
// Returning to fxview seems like a likely time for a device check
|
||||
if (
|
||||
event.type == "visibilitychange" &&
|
||||
document.visibilityState === "visible"
|
||||
) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
appendTemplatedElement(templateId, elementId) {
|
||||
async observe(subject, topic, data) {
|
||||
if (topic == TOPIC_SETUPSTATE_CHANGED) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
get mobilePromoElem() {
|
||||
return this.querySelector(".promo-box");
|
||||
}
|
||||
get mobileSuccessElem() {
|
||||
return this.querySelector(".confirmation-message-box");
|
||||
}
|
||||
|
||||
insertTemplatedElement(templateId, elementId, replaceNode) {
|
||||
const template = document.getElementById(templateId);
|
||||
const templateContent = template.content;
|
||||
const cloned = templateContent.cloneNode(true);
|
||||
|
@ -91,32 +118,57 @@ class TabPickupContainer extends HTMLElement {
|
|||
elem.dataset.supportUrl;
|
||||
}
|
||||
}
|
||||
this.appendChild(cloned);
|
||||
if (replaceNode) {
|
||||
if (typeof replaceNode == "string") {
|
||||
replaceNode = document.getElementById(replaceNode);
|
||||
}
|
||||
this.replaceChild(cloned, replaceNode);
|
||||
} else {
|
||||
this.appendChild(cloned);
|
||||
}
|
||||
}
|
||||
updateSetupState(stateIndex) {
|
||||
const currStateIndex = this._currentSetupStateIndex;
|
||||
if (stateIndex === undefined) {
|
||||
stateIndex = currStateIndex;
|
||||
|
||||
update({
|
||||
stateIndex = TabsSetupFlowManager.uiStateIndex,
|
||||
showMobilePromo = TabsSetupFlowManager.shouldShowMobilePromo,
|
||||
showMobilePairSuccess = TabsSetupFlowManager.shouldShowMobileConnectedSuccess,
|
||||
} = {}) {
|
||||
let needsRender = false;
|
||||
if (showMobilePromo !== this._showMobilePromo) {
|
||||
this._showMobilePromo = showMobilePromo;
|
||||
needsRender = true;
|
||||
}
|
||||
if (stateIndex === this._currentSetupStateIndex) {
|
||||
return;
|
||||
if (showMobilePairSuccess !== this._showMobilePairSuccess) {
|
||||
this._showMobilePairSuccess = showMobilePairSuccess;
|
||||
needsRender = true;
|
||||
}
|
||||
this._currentSetupStateIndex = stateIndex;
|
||||
this.render();
|
||||
if (stateIndex !== this._currentSetupStateIndex) {
|
||||
this._currentSetupStateIndex = stateIndex;
|
||||
needsRender = true;
|
||||
}
|
||||
needsRender && this.render();
|
||||
}
|
||||
render() {
|
||||
if (!this.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
let setupElem = this.setupContainerElem;
|
||||
let tabsElem = this.tabsContainerElem;
|
||||
let mobilePromoElem = this.mobilePromoElem;
|
||||
let mobileSuccessElem = this.mobileSuccessElem;
|
||||
|
||||
const stateIndex = this._currentSetupStateIndex;
|
||||
const isLoading = stateIndex == 3;
|
||||
|
||||
// show/hide either the setup or tab list containers, creating each as necessary
|
||||
if (stateIndex < 3) {
|
||||
if (!setupElem) {
|
||||
this.appendTemplatedElement("sync-setup-template", "tabpickup-steps");
|
||||
this.insertTemplatedElement(
|
||||
"sync-setup-template",
|
||||
"tabpickup-steps",
|
||||
"sync-setup-placeholder"
|
||||
);
|
||||
setupElem = this.setupContainerElem;
|
||||
}
|
||||
if (tabsElem) {
|
||||
|
@ -128,9 +180,10 @@ class TabPickupContainer extends HTMLElement {
|
|||
}
|
||||
|
||||
if (!tabsElem) {
|
||||
this.appendTemplatedElement(
|
||||
this.insertTemplatedElement(
|
||||
"synced-tabs-template",
|
||||
"tabpickup-tabs-container"
|
||||
"tabpickup-tabs-container",
|
||||
"synced-tabs-placeholder"
|
||||
);
|
||||
tabsElem = this.tabsContainerElem;
|
||||
}
|
||||
|
@ -143,6 +196,8 @@ class TabPickupContainer extends HTMLElement {
|
|||
if (stateIndex == 4) {
|
||||
this.collapsibleButton.hidden = false;
|
||||
}
|
||||
mobilePromoElem.hidden = !this._showMobilePromo;
|
||||
mobileSuccessElem.hidden = !this._showMobilePairSuccess;
|
||||
}
|
||||
}
|
||||
customElements.define("tab-pickup-container", TabPickupContainer);
|
||||
|
|
|
@ -17,46 +17,6 @@ add_setup(async function() {
|
|||
}
|
||||
});
|
||||
|
||||
function assertFirefoxViewTab(w = window) {
|
||||
ok(w.FirefoxViewHandler.tab, "Firefox View tab exists");
|
||||
ok(w.FirefoxViewHandler.tab?.hidden, "Firefox View tab is hidden");
|
||||
is(
|
||||
w.gBrowser.tabs.indexOf(w.FirefoxViewHandler.tab),
|
||||
0,
|
||||
"Firefox View tab is the first tab"
|
||||
);
|
||||
is(
|
||||
w.gBrowser.visibleTabs.indexOf(w.FirefoxViewHandler.tab),
|
||||
-1,
|
||||
"Firefox View tab is not in the list of visible tabs"
|
||||
);
|
||||
}
|
||||
|
||||
async function openFirefoxViewTab(w = window) {
|
||||
ok(
|
||||
!w.FirefoxViewHandler.tab,
|
||||
"Firefox View tab doesn't exist prior to clicking the button"
|
||||
);
|
||||
info("Clicking the Firefox View button");
|
||||
await EventUtils.synthesizeMouseAtCenter(
|
||||
w.document.getElementById("firefox-view-button"),
|
||||
{},
|
||||
w
|
||||
);
|
||||
assertFirefoxViewTab(w);
|
||||
is(w.gBrowser.tabContainer.selectedIndex, 0, "Firefox View tab is selected");
|
||||
await BrowserTestUtils.browserLoaded(w.FirefoxViewHandler.tab.linkedBrowser);
|
||||
return w.FirefoxViewHandler.tab;
|
||||
}
|
||||
|
||||
function closeFirefoxViewTab(w = window) {
|
||||
w.gBrowser.removeTab(w.FirefoxViewHandler.tab);
|
||||
ok(
|
||||
!w.FirefoxViewHandler.tab,
|
||||
"Reference to Firefox View tab got removed when closing the tab"
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function load_opens_new_tab() {
|
||||
await openFirefoxViewTab();
|
||||
gURLBar.focus();
|
||||
|
|
|
@ -5,28 +5,41 @@ const { UIState } = ChromeUtils.import("resource://services-sync/UIState.jsm");
|
|||
const { TabsSetupFlowManager } = ChromeUtils.importESModule(
|
||||
"resource:///modules/firefox-view-tabs-setup-manager.sys.mjs"
|
||||
);
|
||||
const MOBILE_PROMO_DISMISSED_PREF =
|
||||
"browser.tabs.firefox-view.mobilePromo.dismissed";
|
||||
|
||||
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
|
||||
|
||||
var gMockFxaDevices = null;
|
||||
|
||||
function promiseSyncReady() {
|
||||
let service = Cc["@mozilla.org/weave/service;1"].getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
return service.whenLoaded();
|
||||
}
|
||||
|
||||
async function touchLastTabFetch() {
|
||||
// lastTabFetch stores a timestamp in *seconds*.
|
||||
const nowSeconds = Math.floor(Date.now() / 1000);
|
||||
info("updating lastFetch:" + nowSeconds);
|
||||
Services.prefs.setIntPref("services.sync.lastTabFetch", nowSeconds);
|
||||
// wait so all pref observers can complete
|
||||
await TestUtils.waitForTick();
|
||||
}
|
||||
|
||||
function setupMocks({ fxaDevices = null, state = UIState.STATUS_SIGNED_IN }) {
|
||||
const sandbox = sinon.createSandbox();
|
||||
gMockFxaDevices = fxaDevices;
|
||||
sandbox.stub(fxAccounts.device, "recentDeviceList").get(() => fxaDevices);
|
||||
sandbox.stub(UIState, "get").returns({
|
||||
status: state,
|
||||
syncEnabled: true,
|
||||
});
|
||||
sandbox.stub(fxAccounts.device, "refreshDeviceList").resolves(true);
|
||||
|
||||
sandbox
|
||||
.stub(Weave.Service.clientsEngine, "getClientByFxaDeviceId")
|
||||
.callsFake(fxaDeviceId => {
|
||||
let target = fxaDevices.find(c => c.id == fxaDeviceId);
|
||||
let target = gMockFxaDevices.find(c => c.id == fxaDeviceId);
|
||||
return target ? target.clientRecord : null;
|
||||
});
|
||||
sandbox.stub(Weave.Service.clientsEngine, "getClientType").returns("desktop");
|
||||
|
@ -34,6 +47,30 @@ function setupMocks({ fxaDevices = null, state = UIState.STATUS_SIGNED_IN }) {
|
|||
return sandbox;
|
||||
}
|
||||
|
||||
async function setupWithDesktopDevices() {
|
||||
const sandbox = setupMocks({
|
||||
state: UIState.STATUS_SIGNED_IN,
|
||||
fxaDevices: [
|
||||
{
|
||||
id: 1,
|
||||
name: "This Device",
|
||||
isCurrentDevice: true,
|
||||
type: "desktop",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Other Device",
|
||||
type: "desktop",
|
||||
},
|
||||
],
|
||||
});
|
||||
// ensure tab sync is false so we don't skip onto next step
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["services.sync.engine.tabs", true]],
|
||||
});
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
async function waitForVisibleStep(browser, expected) {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
|
@ -66,37 +103,68 @@ async function waitForVisibleStep(browser, expected) {
|
|||
}
|
||||
}
|
||||
|
||||
async function waitForElementVisible(browser, selector, isVisible = true) {
|
||||
function checkMobilePromo(browser, expected = {}) {
|
||||
const { document } = browser.contentWindow;
|
||||
const elem = document.querySelector(selector);
|
||||
ok(elem, `Got element with selector: ${selector}`);
|
||||
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
elem,
|
||||
{
|
||||
attributeFilter: ["hidden"],
|
||||
},
|
||||
() => {
|
||||
return isVisible
|
||||
? BrowserTestUtils.is_visible(elem)
|
||||
: BrowserTestUtils.is_hidden(elem);
|
||||
}
|
||||
const promoElem = document.querySelector(
|
||||
"#tab-pickup-container > .promo-box"
|
||||
);
|
||||
const successElem = document.querySelector(
|
||||
"#tab-pickup-container > .confirmation-message-box"
|
||||
);
|
||||
|
||||
info("checkMobilePromo: " + JSON.stringify(expected));
|
||||
if (expected.mobilePromo) {
|
||||
ok(BrowserTestUtils.is_visible(promoElem), "Mobile promo is visible");
|
||||
} else {
|
||||
ok(
|
||||
!promoElem || BrowserTestUtils.is_hidden(promoElem),
|
||||
"Mobile promo is hidden"
|
||||
);
|
||||
}
|
||||
if (expected.mobileConfirmation) {
|
||||
ok(
|
||||
BrowserTestUtils.is_visible(successElem),
|
||||
"Success confirmation is visible"
|
||||
);
|
||||
} else {
|
||||
ok(
|
||||
!successElem || BrowserTestUtils.is_hidden(successElem),
|
||||
"Success confirmation is hidden"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function tearDown(sandbox) {
|
||||
sandbox?.restore();
|
||||
Services.prefs.clearUserPref("services.sync.lastTabFetch");
|
||||
Services.prefs.clearUserPref(MOBILE_PROMO_DISMISSED_PREF);
|
||||
}
|
||||
|
||||
add_setup(async function() {
|
||||
if (!Services.prefs.getBoolPref("browser.tabs.firefox-view")) {
|
||||
info(
|
||||
"firefox-view pref was off, toggling it on and adding the tabstrip widget"
|
||||
);
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.tabs.firefox-view", true]],
|
||||
});
|
||||
CustomizableUI.addWidgetToArea(
|
||||
"firefox-view-button",
|
||||
CustomizableUI.AREA_TABSTRIP,
|
||||
0
|
||||
);
|
||||
registerCleanupFunction(() => {
|
||||
CustomizableUI.removeWidgetFromArea("firefox-view-button");
|
||||
});
|
||||
}
|
||||
|
||||
await promiseSyncReady();
|
||||
// gSync.init() is called in a requestIdleCallback. Force its initialization.
|
||||
gSync.init();
|
||||
|
||||
const tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:firefoxview"
|
||||
);
|
||||
registerCleanupFunction(async function() {
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
Services.prefs.clearUserPref("services.sync.engine.tabs");
|
||||
Services.prefs.clearUserPref("services.sync.lastTabFetch");
|
||||
await tearDown();
|
||||
});
|
||||
// set tab sync false so we don't skip setup states
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
|
@ -105,19 +173,21 @@ add_setup(async function() {
|
|||
});
|
||||
|
||||
add_task(async function test_unconfigured_initial_state() {
|
||||
const browser = gBrowser.selectedBrowser;
|
||||
const sandbox = setupMocks({ state: UIState.STATUS_NOT_CONFIGURED });
|
||||
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view0",
|
||||
await withFirefoxView({}, async browser => {
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view0",
|
||||
});
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
});
|
||||
|
||||
sandbox.restore();
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_signed_in() {
|
||||
const browser = gBrowser.selectedBrowser;
|
||||
const sandbox = setupMocks({
|
||||
state: UIState.STATUS_SIGNED_IN,
|
||||
fxaDevices: [
|
||||
|
@ -129,18 +199,26 @@ add_task(async function test_signed_in() {
|
|||
},
|
||||
],
|
||||
});
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view1",
|
||||
await withFirefoxView({}, async browser => {
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view1",
|
||||
});
|
||||
|
||||
is(
|
||||
fxAccounts.device.recentDeviceList?.length,
|
||||
1,
|
||||
"Just 1 device connected"
|
||||
);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
});
|
||||
|
||||
is(fxAccounts.device.recentDeviceList?.length, 1, "Just 1 device connected");
|
||||
|
||||
sandbox.restore();
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_2nd_desktop_connected() {
|
||||
const browser = gBrowser.selectedBrowser;
|
||||
const sandbox = setupMocks({
|
||||
state: UIState.STATUS_SIGNED_IN,
|
||||
fxaDevices: [
|
||||
|
@ -157,31 +235,34 @@ add_task(async function test_2nd_desktop_connected() {
|
|||
},
|
||||
],
|
||||
});
|
||||
await withFirefoxView({}, async browser => {
|
||||
// ensure tab sync is false so we don't skip onto next step
|
||||
ok(
|
||||
!Services.prefs.getBoolPref("services.sync.engine.tabs", false),
|
||||
"services.sync.engine.tabs is initially false"
|
||||
);
|
||||
|
||||
// ensure tab sync is false so we don't skip onto next step
|
||||
ok(
|
||||
!Services.prefs.getBoolPref("services.sync.engine.tabs", false),
|
||||
"services.sync.engine.tabs is initially false"
|
||||
);
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
});
|
||||
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
|
||||
ok(
|
||||
fxAccounts.device.recentDeviceList?.every(
|
||||
device => device.type !== "mobile"
|
||||
),
|
||||
"No connected device is type:mobile"
|
||||
);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
});
|
||||
|
||||
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
|
||||
ok(
|
||||
fxAccounts.device.recentDeviceList?.every(
|
||||
device => device.type !== "mobile"
|
||||
),
|
||||
"No connected device is type:mobile"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_mobile_connected() {
|
||||
const browser = gBrowser.selectedBrowser;
|
||||
const sandbox = setupMocks({
|
||||
state: UIState.STATUS_SIGNED_IN,
|
||||
fxaDevices: [
|
||||
|
@ -198,30 +279,34 @@ add_task(async function test_mobile_connected() {
|
|||
},
|
||||
],
|
||||
});
|
||||
// ensure tab sync is false so we don't skip onto next step
|
||||
ok(
|
||||
!Services.prefs.getBoolPref("services.sync.engine.tabs", false),
|
||||
"services.sync.engine.tabs is initially false"
|
||||
);
|
||||
await withFirefoxView({}, async browser => {
|
||||
// ensure tab sync is false so we don't skip onto next step
|
||||
ok(
|
||||
!Services.prefs.getBoolPref("services.sync.engine.tabs", false),
|
||||
"services.sync.engine.tabs is initially false"
|
||||
);
|
||||
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
});
|
||||
|
||||
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
|
||||
ok(
|
||||
fxAccounts.device.recentDeviceList?.some(
|
||||
device => device.type !== "mobile"
|
||||
),
|
||||
"A connected device is type:mobile"
|
||||
);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
});
|
||||
|
||||
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
|
||||
ok(
|
||||
fxAccounts.device.recentDeviceList?.some(
|
||||
device => device.type !== "mobile"
|
||||
),
|
||||
"A connected device is type:mobile"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_tab_sync_enabled() {
|
||||
const browser = gBrowser.selectedBrowser;
|
||||
const sandbox = setupMocks({
|
||||
state: UIState.STATUS_SIGNED_IN,
|
||||
fxaDevices: [
|
||||
|
@ -238,43 +323,58 @@ add_task(async function test_tab_sync_enabled() {
|
|||
},
|
||||
],
|
||||
});
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await withFirefoxView({}, async browser => {
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
|
||||
// test initial state, with the pref not enabled
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
// test initial state, with the pref not enabled
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
});
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
// test with the pref toggled on
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["services.sync.engine.tabs", true]],
|
||||
});
|
||||
await waitForElementVisible(browser, "#tabpickup-steps", false);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
// reset and test clicking the action button
|
||||
await SpecialPowers.popPrefEnv();
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
});
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
const actionButton = browser.contentWindow.document.querySelector(
|
||||
"#tabpickup-steps-view2 button.primary"
|
||||
);
|
||||
actionButton.click();
|
||||
|
||||
await waitForElementVisible(browser, "#tabpickup-steps", false);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
ok(
|
||||
Services.prefs.getBoolPref("services.sync.engine.tabs", false),
|
||||
"tab sync pref should be enabled after button click"
|
||||
);
|
||||
});
|
||||
|
||||
// test with the pref toggled on
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["services.sync.engine.tabs", true]],
|
||||
});
|
||||
await waitForElementVisible(browser, "#tabpickup-steps", false);
|
||||
|
||||
// reset and test clicking the action button
|
||||
await SpecialPowers.popPrefEnv();
|
||||
await waitForVisibleStep(browser, {
|
||||
expectedVisible: "#tabpickup-steps-view2",
|
||||
});
|
||||
|
||||
const actionButton = browser.contentWindow.document.querySelector(
|
||||
"#tabpickup-steps-view2 button.primary"
|
||||
);
|
||||
actionButton.click();
|
||||
|
||||
await waitForElementVisible(browser, "#tabpickup-steps", false);
|
||||
|
||||
ok(
|
||||
Services.prefs.getBoolPref("services.sync.engine.tabs", false),
|
||||
"tab sync pref should be enabled after button click"
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
Services.prefs.clearUserPref("services.sync.engine.tabs");
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_tab_sync_loading() {
|
||||
const browser = gBrowser.selectedBrowser;
|
||||
const sandbox = setupMocks({
|
||||
state: UIState.STATUS_SIGNED_IN,
|
||||
fxaDevices: [
|
||||
|
@ -291,95 +391,287 @@ add_task(async function test_tab_sync_loading() {
|
|||
},
|
||||
],
|
||||
});
|
||||
const { document } = browser.contentWindow;
|
||||
await withFirefoxView({}, async browser => {
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["services.sync.engine.tabs", true]],
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["services.sync.engine.tabs", true]],
|
||||
});
|
||||
|
||||
const { document } = browser.contentWindow;
|
||||
const tabsContainer = document.querySelector("#tabpickup-tabs-container");
|
||||
const tabsList = document.querySelector(
|
||||
"#tabpickup-tabs-container tab-pickup-list"
|
||||
);
|
||||
const loadingElem = document.querySelector(
|
||||
"#tabpickup-tabs-container .loading-content"
|
||||
);
|
||||
const setupElem = document.querySelector("#tabpickup-steps");
|
||||
|
||||
await waitForElementVisible(browser, "#tabpickup-steps", false);
|
||||
await waitForElementVisible(browser, "#tabpickup-tabs-container", true);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
function checkLoadingState(isLoading = false) {
|
||||
if (isLoading) {
|
||||
ok(
|
||||
tabsContainer.classList.contains("loading"),
|
||||
"Tabs container has loading class"
|
||||
);
|
||||
BrowserTestUtils.is_visible(
|
||||
loadingElem,
|
||||
"Loading content is visible when loading"
|
||||
);
|
||||
!tabsList ||
|
||||
BrowserTestUtils.is_hidden(
|
||||
tabsList,
|
||||
"Synced tabs list is not visible when loading"
|
||||
);
|
||||
!setupElem ||
|
||||
BrowserTestUtils.is_hidden(
|
||||
setupElem,
|
||||
"Setup content is not visible when loading"
|
||||
);
|
||||
} else {
|
||||
ok(
|
||||
!tabsContainer.classList.contains("loading"),
|
||||
"Tabs container has no loading class"
|
||||
);
|
||||
!loadingElem ||
|
||||
BrowserTestUtils.is_hidden(
|
||||
loadingElem,
|
||||
"Loading content is not visible when tabs are loaded"
|
||||
);
|
||||
BrowserTestUtils.is_visible(
|
||||
tabsList,
|
||||
"Synced tabs list is visible when loaded"
|
||||
);
|
||||
!setupElem ||
|
||||
BrowserTestUtils.is_hidden(
|
||||
setupElem,
|
||||
"Setup content is not visible when tabs are loaded"
|
||||
);
|
||||
}
|
||||
}
|
||||
checkLoadingState(true);
|
||||
|
||||
await touchLastTabFetch();
|
||||
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
tabsContainer,
|
||||
{ attributeFilter: ["class"], attributes: true },
|
||||
() => {
|
||||
return !tabsContainer.classList.contains("loading");
|
||||
}
|
||||
);
|
||||
checkLoadingState(false);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
// Simulate stale data by setting lastTabFetch to 10mins ago
|
||||
const TEN_MINUTES_MS = 1000 * 60 * 10;
|
||||
const staleFetchSeconds = Math.floor((Date.now() - TEN_MINUTES_MS) / 1000);
|
||||
info("updating lastFetch:" + staleFetchSeconds);
|
||||
Services.prefs.setIntPref("services.sync.lastTabFetch", staleFetchSeconds);
|
||||
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
tabsContainer,
|
||||
{ attributeFilter: ["class"], attributes: true },
|
||||
() => {
|
||||
return tabsContainer.classList.contains("loading");
|
||||
}
|
||||
);
|
||||
checkLoadingState(true);
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
});
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
|
||||
await waitForElementVisible(browser, "#tabpickup-steps", false);
|
||||
await waitForElementVisible(browser, "#tabpickup-tabs-container", true);
|
||||
|
||||
const tabsContainer = document.querySelector("#tabpickup-tabs-container");
|
||||
const tabsList = document.querySelector(
|
||||
"#tabpickup-tabs-container tab-pickup-list"
|
||||
);
|
||||
const loadingElem = document.querySelector(
|
||||
"#tabpickup-tabs-container .loading-content"
|
||||
);
|
||||
const setupElem = document.querySelector("#tabpickup-steps");
|
||||
|
||||
function checkLoadingState(isLoading = false) {
|
||||
if (isLoading) {
|
||||
ok(
|
||||
tabsContainer.classList.contains("loading"),
|
||||
"Tabs container has loading class"
|
||||
);
|
||||
BrowserTestUtils.is_visible(
|
||||
loadingElem,
|
||||
"Loading content is visible when loading"
|
||||
);
|
||||
BrowserTestUtils.is_hidden(
|
||||
tabsList,
|
||||
"Synced tabs list is not visible when loading"
|
||||
);
|
||||
BrowserTestUtils.is_hidden(
|
||||
setupElem,
|
||||
"Setup content is not visible when loading"
|
||||
);
|
||||
} else {
|
||||
ok(
|
||||
!tabsContainer.classList.contains("loading"),
|
||||
"Tabs container has no loading class"
|
||||
);
|
||||
BrowserTestUtils.is_hidden(
|
||||
loadingElem,
|
||||
"Loading content is not visible when tabs are loaded"
|
||||
);
|
||||
BrowserTestUtils.is_visible(
|
||||
tabsList,
|
||||
"Synced tabs list is visible when loaded"
|
||||
);
|
||||
BrowserTestUtils.is_hidden(
|
||||
setupElem,
|
||||
"Setup content is not visible when tabs are loaded"
|
||||
);
|
||||
}
|
||||
}
|
||||
checkLoadingState(true);
|
||||
|
||||
// lastTabFetch stores a timestamp in *seconds*.
|
||||
const nowSeconds = Math.floor(Date.now() / 1000);
|
||||
info("updating lastFetch:" + nowSeconds);
|
||||
Services.prefs.setIntPref("services.sync.lastTabFetch", nowSeconds);
|
||||
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
tabsContainer,
|
||||
{ attributeFilter: ["class"], attributes: true },
|
||||
() => {
|
||||
return !tabsContainer.classList.contains("loading");
|
||||
}
|
||||
);
|
||||
checkLoadingState(false);
|
||||
|
||||
// Simulate stale data by setting lastTabFetch to 10mins ago
|
||||
const TEN_MINUTES_MS = 1000 * 60 * 10;
|
||||
const staleFetchSeconds = Math.floor((Date.now() - TEN_MINUTES_MS) / 1000);
|
||||
info("updating lastFetch:" + staleFetchSeconds);
|
||||
Services.prefs.setIntPref("services.sync.lastTabFetch", staleFetchSeconds);
|
||||
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
tabsContainer,
|
||||
{ attributeFilter: ["class"], attributes: true },
|
||||
() => {
|
||||
return tabsContainer.classList.contains("loading");
|
||||
}
|
||||
);
|
||||
checkLoadingState(true);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
sandbox.restore();
|
||||
Services.prefs.clearUserPref("services.sync.engine.tabs");
|
||||
Services.prefs.clearUserPref("services.sync.lastTabFetch");
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_mobile_promo() {
|
||||
const sandbox = await setupWithDesktopDevices();
|
||||
await withFirefoxView({}, async browser => {
|
||||
// ensure last tab fetch was just now so we don't get the loading state
|
||||
await touchLastTabFetch();
|
||||
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForElementVisible(browser, ".synced-tabs-container");
|
||||
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
|
||||
|
||||
info("checking mobile promo, should be visible now");
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: true,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
gMockFxaDevices.push({
|
||||
id: 3,
|
||||
name: "Mobile Device",
|
||||
type: "mobile",
|
||||
});
|
||||
|
||||
Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
|
||||
|
||||
// Wait for the async refreshDeviceList(),
|
||||
// which should result in the promo being hidden
|
||||
await waitForElementVisible(
|
||||
browser,
|
||||
"#tab-pickup-container > .promo-box",
|
||||
false
|
||||
);
|
||||
is(fxAccounts.device.recentDeviceList?.length, 3, "3 devices connected");
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: true,
|
||||
});
|
||||
});
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_mobile_promo_pref() {
|
||||
const sandbox = await setupWithDesktopDevices();
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[MOBILE_PROMO_DISMISSED_PREF, true]],
|
||||
});
|
||||
await withFirefoxView({}, async browser => {
|
||||
// ensure tab sync is false so we don't skip onto next step
|
||||
info("starting test, will notify of UIState update");
|
||||
// ensure last tab fetch was just now so we don't get the loading state
|
||||
await touchLastTabFetch();
|
||||
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForElementVisible(browser, ".synced-tabs-container");
|
||||
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
|
||||
|
||||
info("checking mobile promo, should be still hidden because of the pref");
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
// reset the dismissed pref, which should case the promo to get shown
|
||||
await SpecialPowers.popPrefEnv();
|
||||
await waitForElementVisible(
|
||||
browser,
|
||||
"#tab-pickup-container > .promo-box",
|
||||
true
|
||||
);
|
||||
|
||||
const promoElem = browser.contentWindow.document.querySelector(
|
||||
"#tab-pickup-container > .promo-box"
|
||||
);
|
||||
const promoElemClose = promoElem.querySelector(".close");
|
||||
ok(promoElemClose.hasAttribute("aria-label"), "Button has an a11y name");
|
||||
// check that dismissing the promo sets the pref
|
||||
info("Clicking the promo close button: " + promoElemClose);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, promoElemClose);
|
||||
|
||||
info("Check the promo box got hidden");
|
||||
BrowserTestUtils.is_hidden(promoElem);
|
||||
ok(
|
||||
SpecialPowers.getBoolPref(MOBILE_PROMO_DISMISSED_PREF),
|
||||
"Promo pref is updated when close is clicked"
|
||||
);
|
||||
});
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
||||
add_task(async function test_mobile_promo_windows() {
|
||||
// make sure interacting with the promo and success confirmation in one window
|
||||
// also updates the others
|
||||
const sandbox = await setupWithDesktopDevices();
|
||||
await withFirefoxView({}, async browser => {
|
||||
// ensure last tab fetch was just now so we don't get the loading state
|
||||
await touchLastTabFetch();
|
||||
|
||||
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
|
||||
await waitForElementVisible(browser, ".synced-tabs-container");
|
||||
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
|
||||
|
||||
info("checking mobile promo is visible");
|
||||
checkMobilePromo(browser, {
|
||||
mobilePromo: true,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
info(
|
||||
"opening new window, pref is: " +
|
||||
SpecialPowers.getBoolPref("browser.tabs.firefox-view")
|
||||
);
|
||||
|
||||
let win2 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
info("Got window, now opening Firefox View in it");
|
||||
await withFirefoxView(
|
||||
{ resetFlowManager: false, win: win2 },
|
||||
async win2Browser => {
|
||||
info("In withFirefoxView taskFn for win2");
|
||||
// promo should be visible in the 2nd window too
|
||||
info("check mobile promo is visible in the new window");
|
||||
checkMobilePromo(win2Browser, {
|
||||
mobilePromo: true,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
|
||||
// add the mobile device to get the success confirmation in both instances
|
||||
info("add a mobile device and send device_connected notification");
|
||||
gMockFxaDevices.push({
|
||||
id: 3,
|
||||
name: "Mobile Device",
|
||||
type: "mobile",
|
||||
});
|
||||
|
||||
Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
|
||||
is(
|
||||
fxAccounts.device.recentDeviceList?.length,
|
||||
3,
|
||||
"3 devices connected"
|
||||
);
|
||||
|
||||
// Wait for the async refreshDevices(),
|
||||
// which should result in the promo being hidden
|
||||
info("waiting for the confirmation box to be visible");
|
||||
await waitForElementVisible(
|
||||
win2Browser,
|
||||
"#tab-pickup-container > .promo-box",
|
||||
false
|
||||
);
|
||||
|
||||
for (let fxviewBrowser of [browser, win2Browser]) {
|
||||
info(
|
||||
"checking promo is hidden and confirmation is visible in each window"
|
||||
);
|
||||
checkMobilePromo(fxviewBrowser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: true,
|
||||
});
|
||||
}
|
||||
|
||||
// dismiss the confirmation and check its gone from both instances
|
||||
const confirmBox = win2Browser.contentWindow.document.querySelector(
|
||||
"#tab-pickup-container > .confirmation-message-box"
|
||||
);
|
||||
const closeButton = confirmBox.querySelector(".close");
|
||||
ok(closeButton.hasAttribute("aria-label"), "Button has an a11y name");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, closeButton, win2);
|
||||
BrowserTestUtils.is_hidden(confirmBox);
|
||||
|
||||
for (let fxviewBrowser of [browser, win2Browser]) {
|
||||
checkMobilePromo(fxviewBrowser, {
|
||||
mobilePromo: false,
|
||||
mobileConfirmation: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
await BrowserTestUtils.closeWindow(win2);
|
||||
});
|
||||
await tearDown(sandbox);
|
||||
});
|
||||
|
|
|
@ -22,6 +22,9 @@ function testVisibility(browser, expected) {
|
|||
async function waitForElementVisible(browser, selector, isVisible = true) {
|
||||
const { document } = browser.contentWindow;
|
||||
const elem = document.querySelector(selector);
|
||||
if (!isVisible && !elem) {
|
||||
return;
|
||||
}
|
||||
ok(elem, `Got element with selector: ${selector}`);
|
||||
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
|
@ -36,3 +39,73 @@ async function waitForElementVisible(browser, selector, isVisible = true) {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
function assertFirefoxViewTab(w = window) {
|
||||
ok(w.FirefoxViewHandler.tab, "Firefox View tab exists");
|
||||
ok(w.FirefoxViewHandler.tab?.hidden, "Firefox View tab is hidden");
|
||||
is(
|
||||
w.gBrowser.tabs.indexOf(w.FirefoxViewHandler.tab),
|
||||
0,
|
||||
"Firefox View tab is the first tab"
|
||||
);
|
||||
is(
|
||||
w.gBrowser.visibleTabs.indexOf(w.FirefoxViewHandler.tab),
|
||||
-1,
|
||||
"Firefox View tab is not in the list of visible tabs"
|
||||
);
|
||||
}
|
||||
|
||||
async function openFirefoxViewTab(w = window) {
|
||||
ok(
|
||||
!w.FirefoxViewHandler.tab,
|
||||
"Firefox View tab doesn't exist prior to clicking the button"
|
||||
);
|
||||
info("Clicking the Firefox View button");
|
||||
await EventUtils.synthesizeMouseAtCenter(
|
||||
w.document.getElementById("firefox-view-button"),
|
||||
{},
|
||||
w
|
||||
);
|
||||
assertFirefoxViewTab(w);
|
||||
is(w.gBrowser.tabContainer.selectedIndex, 0, "Firefox View tab is selected");
|
||||
await BrowserTestUtils.browserLoaded(w.FirefoxViewHandler.tab.linkedBrowser);
|
||||
return w.FirefoxViewHandler.tab;
|
||||
}
|
||||
|
||||
function closeFirefoxViewTab(w = window) {
|
||||
w.gBrowser.removeTab(w.FirefoxViewHandler.tab);
|
||||
ok(
|
||||
!w.FirefoxViewHandler.tab,
|
||||
"Reference to Firefox View tab got removed when closing the tab"
|
||||
);
|
||||
}
|
||||
|
||||
async function withFirefoxView(
|
||||
{ resetFlowManager = true, win = window },
|
||||
taskFn
|
||||
) {
|
||||
if (resetFlowManager) {
|
||||
const { TabsSetupFlowManager } = ChromeUtils.importESModule(
|
||||
"resource:///modules/firefox-view-tabs-setup-manager.sys.mjs"
|
||||
);
|
||||
// reset internal state so we aren't reacting to whatever state the last invocation left behind
|
||||
TabsSetupFlowManager.resetInternalState();
|
||||
}
|
||||
let tab = await openFirefoxViewTab(win);
|
||||
let originalWindow = tab.ownerGlobal;
|
||||
let result = await taskFn(tab.linkedBrowser);
|
||||
let finalWindow = tab.ownerGlobal;
|
||||
if (originalWindow == finalWindow && !tab.closing && tab.linkedBrowser) {
|
||||
// taskFn may resolve within a tick after opening a new tab.
|
||||
// We shouldn't remove the newly opened tab in the same tick.
|
||||
// Wait for the next tick here.
|
||||
await TestUtils.waitForTick();
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
} else {
|
||||
Services.console.logStringMessage(
|
||||
"withFirefoxView: Tab was already closed before " +
|
||||
"removeTab would have been called"
|
||||
);
|
||||
}
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ exports.FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";
|
|||
exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange"; // WebChannel
|
||||
exports.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange";
|
||||
exports.ON_NEW_DEVICE_ID = "fxaccounts:new_device_id";
|
||||
exports.ON_DEVICELIST_UPDATED = "fxaccounts:devicelist_updated";
|
||||
|
||||
// The common prefix for all commands.
|
||||
exports.COMMAND_PREFIX = "https://identity.mozilla.com/cmd/";
|
||||
|
|
|
@ -12,6 +12,7 @@ const {
|
|||
ERRNO_DEVICE_SESSION_CONFLICT,
|
||||
ERRNO_UNKNOWN_DEVICE,
|
||||
ON_NEW_DEVICE_ID,
|
||||
ON_DEVICELIST_UPDATED,
|
||||
ON_DEVICE_CONNECTED_NOTIFICATION,
|
||||
ON_DEVICE_DISCONNECTED_NOTIFICATION,
|
||||
ONVERIFIED_NOTIFICATION,
|
||||
|
@ -255,6 +256,7 @@ class FxAccountsDevice {
|
|||
lastFetch: this._fxai.now(),
|
||||
devices,
|
||||
};
|
||||
Services.obs.notifyObservers(null, ON_DEVICELIST_UPDATED);
|
||||
return true;
|
||||
} finally {
|
||||
this._fetchAndCacheDeviceListPromise = null;
|
||||
|
|
|
@ -18,6 +18,7 @@ const {
|
|||
ERRNO_UNKNOWN_DEVICE,
|
||||
ON_DEVICE_CONNECTED_NOTIFICATION,
|
||||
ON_DEVICE_DISCONNECTED_NOTIFICATION,
|
||||
ON_DEVICELIST_UPDATED,
|
||||
} = ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
var { AccountState } = ChromeUtils.import(
|
||||
"resource://gre/modules/FxAccounts.jsm"
|
||||
|
@ -789,6 +790,14 @@ add_task(async function test_refreshDeviceList() {
|
|||
let spy = {
|
||||
getDeviceList: { count: 0 },
|
||||
};
|
||||
const deviceListUpdateObserver = {
|
||||
count: 0,
|
||||
observe(subject, topic, data) {
|
||||
this.count++;
|
||||
},
|
||||
};
|
||||
Services.obs.addObserver(deviceListUpdateObserver, ON_DEVICELIST_UPDATED);
|
||||
|
||||
fxAccountsClient.getDeviceList = (function(old) {
|
||||
return function getDeviceList() {
|
||||
spy.getDeviceList.count += 1;
|
||||
|
@ -826,6 +835,11 @@ add_task(async function test_refreshDeviceList() {
|
|||
"Should not have device list initially"
|
||||
);
|
||||
Assert.ok(await device.refreshDeviceList(), "Should refresh list");
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
1,
|
||||
`${ON_DEVICELIST_UPDATED} was notified`
|
||||
);
|
||||
Assert.deepEqual(
|
||||
device.recentDeviceList,
|
||||
[
|
||||
|
@ -847,6 +861,11 @@ add_task(async function test_refreshDeviceList() {
|
|||
!(await device.refreshDeviceList()),
|
||||
"Should not refresh device list if fresh"
|
||||
);
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
1,
|
||||
`${ON_DEVICELIST_UPDATED} was not notified`
|
||||
);
|
||||
|
||||
fxai._now += device.TIME_BETWEEN_FXA_DEVICES_FETCH_MS;
|
||||
|
||||
|
@ -861,6 +880,11 @@ add_task(async function test_refreshDeviceList() {
|
|||
2,
|
||||
"Should only make one request if called with pending request"
|
||||
);
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
2,
|
||||
`${ON_DEVICELIST_UPDATED} only notified once`
|
||||
);
|
||||
|
||||
device.observe(null, ON_DEVICE_CONNECTED_NOTIFICATION);
|
||||
await device.refreshDeviceList();
|
||||
|
@ -869,6 +893,11 @@ add_task(async function test_refreshDeviceList() {
|
|||
3,
|
||||
"Should refresh device list after connecting new device"
|
||||
);
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
3,
|
||||
`${ON_DEVICELIST_UPDATED} notified when new device connects`
|
||||
);
|
||||
device.observe(
|
||||
null,
|
||||
ON_DEVICE_DISCONNECTED_NOTIFICATION,
|
||||
|
@ -880,6 +909,11 @@ add_task(async function test_refreshDeviceList() {
|
|||
4,
|
||||
"Should refresh device list after disconnecting device"
|
||||
);
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
4,
|
||||
`${ON_DEVICELIST_UPDATED} notified when device disconnects`
|
||||
);
|
||||
device.observe(
|
||||
null,
|
||||
ON_DEVICE_DISCONNECTED_NOTIFICATION,
|
||||
|
@ -891,11 +925,21 @@ add_task(async function test_refreshDeviceList() {
|
|||
4,
|
||||
"Should not refresh device list after disconnecting this device"
|
||||
);
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
4,
|
||||
`${ON_DEVICELIST_UPDATED} not notified again`
|
||||
);
|
||||
|
||||
let refreshBeforeResetPromise = device.refreshDeviceList({
|
||||
ignoreCached: true,
|
||||
});
|
||||
fxai._generation++;
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
4,
|
||||
`${ON_DEVICELIST_UPDATED} not notified`
|
||||
);
|
||||
await Assert.rejects(refreshBeforeResetPromise, /Another user has signed in/);
|
||||
|
||||
device.reset();
|
||||
|
@ -908,6 +952,12 @@ add_task(async function test_refreshDeviceList() {
|
|||
await device.refreshDeviceList(),
|
||||
"Should fetch new list after resetting"
|
||||
);
|
||||
Assert.equal(
|
||||
deviceListUpdateObserver.count,
|
||||
5,
|
||||
`${ON_DEVICELIST_UPDATED} notified after reset`
|
||||
);
|
||||
Services.obs.removeObserver(deviceListUpdateObserver, ON_DEVICELIST_UPDATED);
|
||||
});
|
||||
|
||||
add_task(async function test_checking_remote_availableCommands_mismatch() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче