Bug 1768695 - Add error states handling to FirefoxView r=sfoster,fluent-reviewers,desktop-theme-reviewers

* Add new card and styling for network offline, sync error and sync disabled by admin errors
* Change loading spinner to rotating sync svg
* Add tests

Differential Revision: https://phabricator.services.mozilla.com/D153069
This commit is contained in:
Sarah Clements 2022-08-02 11:56:23 +00:00
Родитель dc4af7919e
Коммит f0e1d0730e
8 изменённых файлов: 359 добавлений и 69 удалений

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

@ -19,12 +19,20 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
UIState: "resource://services-sync/UIState.jsm",
SyncedTabs: "resource://services-sync/SyncedTabs.jsm",
});
XPCOMUtils.defineLazyGetter(lazy, "fxAccounts", () => {
return ChromeUtils.import(
"resource://gre/modules/FxAccounts.jsm"
).getFxAccountsSingleton();
});
XPCOMUtils.defineLazyServiceGetter(
lazy,
"gNetworkLinkService",
"@mozilla.org/network/network-link-service;1",
"nsINetworkLinkService"
);
const SYNC_TABS_PREF = "services.sync.engine.tabs";
const RECENT_TABS_SYNC = "services.sync.lastTabFetch";
const MOBILE_PROMO_DISMISSED_PREF =
@ -32,6 +40,10 @@ const MOBILE_PROMO_DISMISSED_PREF =
const LOGGING_PREF = "browser.tabs.firefox-view.logLevel";
const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed";
const TOPIC_DEVICELIST_UPDATED = "fxaccounts:devicelist_updated";
const NETWORK_STATUS_CHANGED = "network:offline-status-changed";
const SYNC_SERVICE_ERROR = "weave:service:sync:error";
const FXA_ENABLED = "identity.fxaccounts.enabled";
const SYNC_SERVICE_FINISHED = "weave:service:sync:finished";
function openTabInWindow(window, url) {
const {
@ -46,31 +58,46 @@ export const TabsSetupFlowManager = new (class {
this.setupState = new Map();
this.resetInternalState();
this._currentSetupStateName = "";
this.networkIsOnline =
lazy.gNetworkLinkService.linkStatusKnown &&
lazy.gNetworkLinkService.isLinkUp;
this.syncIsWorking = true;
this.registerSetupState({
uiStateIndex: 0,
name: "error-state",
exitConditions: () => {
return (
this.networkIsOnline &&
this.syncIsWorking &&
!Services.prefs.prefIsLocked(FXA_ENABLED)
);
},
});
this.registerSetupState({
uiStateIndex: 1,
name: "not-signed-in",
exitConditions: () => {
return this.fxaSignedIn;
},
});
// TODO: handle offline, sync service not ready or available
this.registerSetupState({
uiStateIndex: 1,
uiStateIndex: 2,
name: "connect-secondary-device",
exitConditions: () => {
return this.secondaryDeviceConnected;
},
});
this.registerSetupState({
uiStateIndex: 2,
uiStateIndex: 3,
name: "disabled-tab-sync",
exitConditions: () => {
return this.syncTabsPrefEnabled;
},
});
this.registerSetupState({
uiStateIndex: 3,
uiStateIndex: 4,
name: "synced-tabs-not-ready",
enter: () => {
if (!this.didRecentTabSync) {
@ -82,7 +109,7 @@ export const TabsSetupFlowManager = new (class {
},
});
this.registerSetupState({
uiStateIndex: 4,
uiStateIndex: 5,
name: "synced-tabs-loaded",
exitConditions: () => {
// This is the end state
@ -92,6 +119,9 @@ export const TabsSetupFlowManager = new (class {
Services.obs.addObserver(this, lazy.UIState.ON_UPDATE);
Services.obs.addObserver(this, TOPIC_DEVICELIST_UPDATED);
Services.obs.addObserver(this, NETWORK_STATUS_CHANGED);
Services.obs.addObserver(this, SYNC_SERVICE_ERROR);
Services.obs.addObserver(this, SYNC_SERVICE_FINISHED);
// this.syncTabsPrefEnabled will track the value of the tabs pref
XPCOMUtils.defineLazyPreferenceGetter(
@ -145,9 +175,29 @@ export const TabsSetupFlowManager = new (class {
secondaryDeviceConnected: this.secondaryDeviceConnected,
};
}
getErrorType() {
// this ordering is important for dealing with multiple errors at once
const errorStates = {
"network-offline": !this.networkIsOnline,
"sync-error": !this.syncIsWorking,
"fxa-admin-disabled": Services.prefs.prefIsLocked(FXA_ENABLED),
};
for (let [type, value] of Object.entries(errorStates)) {
if (value) {
return type;
}
}
return null;
}
uninit() {
Services.obs.removeObserver(this, lazy.UIState.ON_UPDATE);
Services.obs.removeObserver(this, TOPIC_DEVICELIST_UPDATED);
Services.obs.removeObserver(this, NETWORK_STATUS_CHANGED);
Services.obs.removeObserver(this, SYNC_SERVICE_ERROR);
Services.obs.removeObserver(this, SYNC_SERVICE_FINISHED);
}
get currentSetupState() {
return this.setupState.get(this._currentSetupStateName);
@ -225,6 +275,24 @@ export const TabsSetupFlowManager = new (class {
this.refreshDevices();
this.maybeUpdateUI();
break;
case "fxaccounts:device_connected":
case "fxaccounts:device_disconnected":
await lazy.fxAccounts.device.refreshDeviceList();
this.maybeUpdateUI();
break;
case SYNC_SERVICE_ERROR:
this.syncIsWorking = false;
this.maybeUpdateUI();
break;
case NETWORK_STATUS_CHANGED:
this.networkIsOnline = data == "online";
this.maybeUpdateUI();
break;
case SYNC_SERVICE_FINISHED:
if (!this.syncIsWorking) {
this.syncIsWorking = true;
this.maybeUpdateUI();
}
}
}
@ -273,6 +341,7 @@ export const TabsSetupFlowManager = new (class {
maybeUpdateUI() {
let nextSetupStateName = this._currentSetupStateName;
let errorState = null;
// state transition conditions
for (let state of this.setupState.values()) {
@ -283,8 +352,14 @@ export const TabsSetupFlowManager = new (class {
}
let setupState = this.currentSetupState;
if (nextSetupStateName != this._currentSetupStateName) {
setupState = this.setupState.get(nextSetupStateName);
const state = this.setupState.get(nextSetupStateName);
const uiStateIndex = state.uiStateIndex;
if (
uiStateIndex == 0 ||
nextSetupStateName != this._currentSetupStateName
) {
setupState = state;
this._currentSetupStateName = nextSetupStateName;
this._uiUpdateNeeded = true;
}
@ -294,7 +369,10 @@ export const TabsSetupFlowManager = new (class {
if (this.shouldShowMobilePromo) {
this._didShowMobilePromo = true;
}
Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED);
if (uiStateIndex == 0) {
errorState = this.getErrorType();
}
Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED, errorState);
}
if ("function" == typeof setupState.enter) {
setupState.enter();

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

@ -45,6 +45,17 @@ firefoxview-tabpickup-synctabs-description = Allow { -brand-short-name } to shar
firefoxview-tabpickup-synctabs-learn-how = Learn how
firefoxview-tabpickup-synctabs-primarybutton = Sync open tabs
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-network-offline-header = Check your internet connection
firefoxview-tabpickup-network-offline-description = If youre using a firewall or proxy, check that { -brand-short-name } has permission to access the web.
firefoxview-tabpickup-network-offline-primarybutton = Try again
firefoxview-tabpickup-sync-error-header = Were having trouble syncing
firefoxview-tabpickup-sync-error-description = { -brand-short-name } cant reach the service right now. Try again in a few moments.
firefoxview-tabpickup-sync-error-primarybutton = Try again
firefoxview-tabpickup-syncing = Sit tight while your tabs sync. Itll be just a moment.
firefoxview-mobile-promo-header = Grab tabs from your phone or tablet

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

@ -24,7 +24,7 @@
--colorways-grid-template-rows: auto auto auto;
--colorways-figure-display: flex;
--colorways-header-flex-direction: column;
--info-icon-background-color: #0090ED;
--card-border-zap-gradient: linear-gradient(90deg, #9059FF 0%, #FF4AA2 52.08%, #FFBD4F 100%);
}
@ -282,6 +282,19 @@ button.close {
border-radius: 4px;
}
.error-state {
text-align: center;
padding: 0 0 20px;
border: 1px solid var(--fxview-contrast-border);
border-radius: 4px;
}
.error-state > h3 {
font-weight: 600;
display: inline-block;
margin-bottom: 0;
}
.placeholder-content {
text-align: center;
padding: 24px 16px;
@ -423,12 +436,9 @@ button.close {
.synced-tabs-container > .loading-content {
text-align: center;
-moz-context-properties: fill;
fill: currentColor;
background: url(chrome://global/skin/icons/loading-dial.svg) no-repeat center top;
background-size: 32px;
margin-top: 32px;
padding: 48px 16px 16px;
color: var(--fxview-text-secondary-color);
margin-top: 40px;
padding: 20px 16px 16px;
}
.closed-tabs-list {
@ -694,6 +704,17 @@ button.close {
background-image: url('chrome://browser/skin/synced-tabs.svg');
}
.info {
background-image: url('chrome://global/skin/icons/info-filled.svg');
}
.error-state > .info {
vertical-align: text-top;
margin-inline-end: 7px;
margin-top: 1px;
color: var(--info-icon-background-color);
}
.favicon {
background-size: cover;
-moz-context-properties: fill;
@ -705,6 +726,25 @@ button.close {
height: 16px;
}
.sync {
background-image: url(chrome://browser/skin/sync.svg);
background-size: cover;
height: 19px;
width: 19px;
color: var(--fxview-text-secondary-color);
}
@keyframes syncRotate {
from { transform: rotate(0); }
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: no-preference) {
.sync {
animation: syncRotate 0.8s linear infinite;
}
}
.last-active-badge {
height: 1.25em;
width: 6em;

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

@ -33,52 +33,61 @@
<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 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>
<div name="sync-setup-view0" data-prefix="id:-view0" class="card error-state" data-prefix="aria-labelledby:-view0-header">
<icon class="icon info primary"></icon><h3 data-prefix="id:-view0-header" class="card-header"></h3>
<section>
<p>
<span id="error-state-description"></span>
</p>
<button id="error-state-button" class="primary"></button>
</section>
</div>
<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-step-signin-header" class="card-header"></h2>
<section class="step-body">
<p class="step-content" data-l10n-id="firefoxview-tabpickup-step-signin-description"></p>
<button class="primary" data-action="view0-primary-action" data-l10n-id="firefoxview-tabpickup-step-signin-primarybutton"></button>
<button class="primary" data-action="view1-primary-action" data-l10n-id="firefoxview-tabpickup-step-signin-primarybutton"></button>
</section>
<footer>
<progress data-prefix="id:-view0-progress" class="step-progress" max="100" value="11"></progress>
<progress data-prefix="id:-view1-progress" class="step-progress" max="100" value="11"></progress>
<label
data-prefix="for:-view0-progress"
data-prefix="for:-view1-progress"
data-l10n-id="firefoxview-tabpickup-progress-label"
data-l10n-args='{"percentValue":"11"}'></label>
</footer>
</div>
<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>
<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-adddevice-header" class="card-header"></h2>
<section class="step-body">
<p class="step-content">
<span data-l10n-id="firefoxview-tabpickup-adddevice-description"></span>
<br/>
<a target="_blank" data-support-url="tab-pickup-firefox-view" data-l10n-id="firefoxview-tabpickup-adddevice-learn-how"></a>
</p>
<button class="primary" data-action="view1-primary-action" data-l10n-id="firefoxview-tabpickup-adddevice-primarybutton"></button>
<button class="primary" data-action="view2-primary-action" data-l10n-id="firefoxview-tabpickup-adddevice-primarybutton"></button>
</section>
<footer>
<progress data-prefix="id:-view1-progress" class="step-progress" max="100" value="33"></progress>
<progress data-prefix="id:-view2-progress" class="step-progress" max="100" value="33"></progress>
<label
data-prefix="for:-view1-progress"
data-prefix="for:-view2-progress"
data-l10n-id="firefoxview-tabpickup-progress-label"
data-l10n-args='{"percentValue":"33"}'></label>
</footer>
</div>
<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>
<div name="sync-setup-view3" data-prefix="id:-view3" class="card zap-card setup-step" data-prefix="aria-labelledby:-view3-header">
<h2 data-prefix="id:-view3-header" data-l10n-id="firefoxview-tabpickup-synctabs-header" class="card-header"></h2>
<section class="step-body">
<p class="step-content">
<span data-l10n-id="firefoxview-tabpickup-synctabs-description"></span>
<br/>
<a target="_blank" data-support-url="tab-pickup-firefox-view" data-l10n-id="firefoxview-tabpickup-synctabs-learn-how"></a>
</p>
<button class="primary" data-action="view2-primary-action" data-l10n-id="firefoxview-tabpickup-synctabs-primarybutton"></button>
<button class="primary" data-action="view3-primary-action" data-l10n-id="firefoxview-tabpickup-synctabs-primarybutton"></button>
</section>
<footer>
<progress data-prefix="id:-view2-progress" class="step-progress" max="100" value="66"></progress>
<progress data-prefix="id:-view3-progress" class="step-progress" max="100" value="66"></progress>
<label
data-prefix="for:-view2-progress"
data-prefix="for:-view3-progress"
data-l10n-id="firefoxview-tabpickup-progress-label"
data-l10n-args='{"percentValue":"66"}'></label>
</footer>
@ -94,7 +103,10 @@
<icon class="icon synced-tabs"></icon>
<p data-l10n-id="firefoxview-synced-tabs-placeholder" class="placeholder-text"></p>
</div>
<p class="loading-content" data-l10n-id="firefoxview-tabpickup-syncing"></p>
<div class="loading-content">
<icon class="icon sync"></icon>
<p data-l10n-id="firefoxview-tabpickup-syncing"></p>
</div>
</div>
</template>

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

@ -16,6 +16,7 @@ class TabPickupContainer extends HTMLElement {
super();
this.boundObserve = (...args) => this.observe(...args);
this._currentSetupStateIndex = -1;
this.errorState = null;
}
get setupContainerElem() {
return this.querySelector(".sync-setup-container");
@ -29,6 +30,11 @@ class TabPickupContainer extends HTMLElement {
return this.querySelector("#collapsible-synced-tabs-button");
}
getWindow() {
return this.ownerGlobal.browsingContext.embedderWindowGlobal.browsingContext
.window;
}
connectedCallback() {
this.addEventListener("click", this);
this.addEventListener("visibilitychange", this);
@ -51,15 +57,20 @@ class TabPickupContainer extends HTMLElement {
}
if (event.type == "click" && event.target.dataset.action) {
switch (event.target.dataset.action) {
case "view0-primary-action": {
TabsSetupFlowManager.openFxASignup(event.target.ownerGlobal);
case "view0-sync-error-action":
case "view0-network-offline-action": {
this.getWindow().gBrowser.reload();
break;
}
case "view1-primary-action": {
TabsSetupFlowManager.openSyncPreferences(event.target.ownerGlobal);
TabsSetupFlowManager.openFxASignup(event.target.ownerGlobal);
break;
}
case "view2-primary-action": {
TabsSetupFlowManager.openSyncPreferences(event.target.ownerGlobal);
break;
}
case "view3-primary-action": {
TabsSetupFlowManager.syncOpenTabs(event.target);
break;
}
@ -86,9 +97,9 @@ class TabPickupContainer extends HTMLElement {
}
}
async observe(subject, topic, data) {
async observe(subject, topic, errorState) {
if (topic == TOPIC_SETUPSTATE_CHANGED) {
this.update();
this.update({ errorState });
}
}
@ -132,6 +143,7 @@ class TabPickupContainer extends HTMLElement {
stateIndex = TabsSetupFlowManager.uiStateIndex,
showMobilePromo = TabsSetupFlowManager.shouldShowMobilePromo,
showMobilePairSuccess = TabsSetupFlowManager.shouldShowMobileConnectedSuccess,
errorState = TabsSetupFlowManager.getErrorType(),
} = {}) {
let needsRender = false;
if (showMobilePromo !== this._showMobilePromo) {
@ -142,12 +154,46 @@ class TabPickupContainer extends HTMLElement {
this._showMobilePairSuccess = showMobilePairSuccess;
needsRender = true;
}
if (stateIndex !== this._currentSetupStateIndex) {
if (stateIndex !== this._currentSetupStateIndex || stateIndex == 0) {
this._currentSetupStateIndex = stateIndex;
needsRender = true;
this.errorState = errorState;
}
needsRender && this.render();
}
generateErrorMessage() {
const errorStateHeader = this.querySelector(
"#tabpickup-steps-view0-header"
);
const errorStateDescription = this.querySelector(
"#error-state-description"
);
const errorStateButton = this.querySelector("#error-state-button");
document.l10n.setAttributes(
errorStateHeader,
`firefoxview-tabpickup-${this.errorState}-header`
);
document.l10n.setAttributes(
errorStateDescription,
`firefoxview-tabpickup-${this.errorState}-description`
);
errorStateButton.hidden = this.errorState == "fxa-admin-disabled";
if (this.errorState != "fxa-admin-disabled") {
document.l10n.setAttributes(
errorStateButton,
`firefoxview-tabpickup-${this.errorState}-primarybutton`
);
errorStateButton.setAttribute(
"data-action",
`view0-${this.errorState}-action`
);
}
}
render() {
if (!this.isConnected) {
return;
@ -159,10 +205,10 @@ class TabPickupContainer extends HTMLElement {
let mobileSuccessElem = this.mobileSuccessElem;
const stateIndex = this._currentSetupStateIndex;
const isLoading = stateIndex == 3;
const isLoading = stateIndex == 4;
// show/hide either the setup or tab list containers, creating each as necessary
if (stateIndex < 3) {
if (stateIndex < 4) {
if (!setupElem) {
this.insertTemplatedElement(
"sync-setup-template",
@ -176,6 +222,10 @@ class TabPickupContainer extends HTMLElement {
}
setupElem.hidden = false;
setupElem.selectedViewName = `sync-setup-view${stateIndex}`;
if (stateIndex == 0 && this.errorState) {
this.generateErrorMessage();
}
return;
}
@ -193,7 +243,7 @@ class TabPickupContainer extends HTMLElement {
tabsElem.hidden = false;
tabsElem.classList.toggle("loading", isLoading);
if (stateIndex == 4) {
if (stateIndex == 5) {
this.collapsibleButton.hidden = false;
}
mobilePromoElem.hidden = !this._showMobilePromo;

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

@ -62,7 +62,7 @@ async function setupWithDesktopDevices() {
},
],
});
// ensure tab sync is false so we don't skip onto next step
await SpecialPowers.pushPrefEnv({
set: [["services.sync.engine.tabs", true]],
});
@ -139,6 +139,9 @@ async function tearDown(sandbox) {
}
add_setup(async function() {
// we only use this for the first test, then we reset it
Services.prefs.lockPref("identity.fxaccounts.enabled");
if (!Services.prefs.getBoolPref("browser.tabs.firefox-view")) {
info(
"firefox-view pref was off, toggling it on and adding the tabstrip widget"
@ -170,12 +173,55 @@ add_setup(async function() {
});
});
add_task(async function test_sync_admin_disabled() {
const sandbox = setupMocks({ state: UIState.STATUS_NOT_CONFIGURED });
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
is(
Services.prefs.getBoolPref("identity.fxaccounts.enabled"),
true,
"Expected identity.fxaccounts.enabled pref to be false"
);
is(
Services.prefs.prefIsLocked("identity.fxaccounts.enabled"),
true,
"Expected identity.fxaccounts.enabled pref to be locked"
);
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view0",
});
const errorStateHeader = document.querySelector(
"#tabpickup-steps-view0-header"
);
await BrowserTestUtils.waitForMutationCondition(
errorStateHeader,
{ childList: true },
() => errorStateHeader.textContent.includes("disabled")
);
ok(
errorStateHeader
.getAttribute("data-l10n-id")
.includes("fxa-admin-disabled"),
"Correct message should show when fxa is disabled by an admin"
);
});
Services.prefs.unlockPref("identity.fxaccounts.enabled");
await tearDown(sandbox);
});
add_task(async function test_unconfigured_initial_state() {
const sandbox = setupMocks({ state: UIState.STATUS_NOT_CONFIGURED });
await withFirefoxView({}, async browser => {
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view0",
expectedVisible: "#tabpickup-steps-view1",
});
checkMobilePromo(browser, {
mobilePromo: false,
@ -197,10 +243,11 @@ add_task(async function test_signed_in() {
},
],
});
await withFirefoxView({}, async browser => {
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view1",
expectedVisible: "#tabpickup-steps-view2",
});
is(
@ -242,7 +289,7 @@ add_task(async function test_2nd_desktop_connected() {
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view2",
expectedVisible: "#tabpickup-steps-view3",
});
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
@ -286,7 +333,7 @@ add_task(async function test_mobile_connected() {
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view2",
expectedVisible: "#tabpickup-steps-view3",
});
is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
@ -326,7 +373,7 @@ add_task(async function test_tab_sync_enabled() {
// test initial state, with the pref not enabled
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view2",
expectedVisible: "#tabpickup-steps-view3",
});
checkMobilePromo(browser, {
mobilePromo: false,
@ -346,7 +393,7 @@ add_task(async function test_tab_sync_enabled() {
// reset and test clicking the action button
await SpecialPowers.popPrefEnv();
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view2",
expectedVisible: "#tabpickup-steps-view3",
});
checkMobilePromo(browser, {
mobilePromo: false,
@ -354,7 +401,7 @@ add_task(async function test_tab_sync_enabled() {
});
const actionButton = browser.contentWindow.document.querySelector(
"#tabpickup-steps-view2 button.primary"
"#tabpickup-steps-view3 button.primary"
);
actionButton.click();
@ -673,3 +720,76 @@ add_task(async function test_mobile_promo_windows() {
});
await tearDown(sandbox);
});
add_task(async function test_network_offline() {
const sandbox = await setupWithDesktopDevices();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
Services.obs.notifyObservers(
null,
"network:offline-status-changed",
"offline"
);
await waitForElementVisible(browser, "#tabpickup-steps", true);
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view0",
});
const errorStateHeader = document.querySelector(
"#tabpickup-steps-view0-header"
);
await BrowserTestUtils.waitForMutationCondition(
errorStateHeader,
{ childList: true },
() => errorStateHeader.textContent.includes("connection")
);
ok(
errorStateHeader.getAttribute("data-l10n-id").includes("network-offline"),
"Correct message should show when network connection is lost"
);
Services.obs.notifyObservers(
null,
"network:offline-status-changed",
"online"
);
await waitForElementVisible(browser, "#tabpickup-tabs-container", true);
});
await tearDown(sandbox);
});
add_task(async function test_sync_error() {
const sandbox = await setupWithDesktopDevices();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
Services.obs.notifyObservers(null, "weave:service:sync:error");
await waitForElementVisible(browser, "#tabpickup-steps", true);
await waitForVisibleStep(browser, {
expectedVisible: "#tabpickup-steps-view0",
});
const errorStateHeader = document.querySelector(
"#tabpickup-steps-view0-header"
);
await BrowserTestUtils.waitForMutationCondition(
errorStateHeader,
{ childList: true },
() => errorStateHeader.textContent.includes("trouble syncing")
);
ok(
errorStateHeader.getAttribute("data-l10n-id").includes("sync-error"),
"Correct message should show when there's a sync service error"
);
Services.obs.notifyObservers(null, "weave:service:sync:finished");
});
await tearDown(sandbox);
});

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

@ -77,9 +77,6 @@
skin/classic/global/icons/link.svg (../../shared/icons/link.svg)
skin/classic/global/icons/loading.png (../../shared/icons/loading.png)
skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png)
#ifdef NIGHTLY_BUILD
skin/classic/global/icons/loading-dial.svg (../../shared/icons/loading-dial.svg)
#endif
skin/classic/global/icons/more.svg (../../shared/icons/more.svg)
skin/classic/global/icons/open-in-new.svg (../../shared/icons/open-in-new.svg)
skin/classic/global/icons/page-portrait.svg (../../shared/icons/page-portrait.svg)

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

@ -1,18 +0,0 @@
<!-- 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 xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity" style="animation:spinIcon 1.2s steps(12,end) infinite">
<style>@keyframes spinIcon{to{transform:rotate(360deg)}}</style>
<path d="m7 3 0-2s0-1 1-1 1 1 1 1l0 2s0 1-1 1-1-1-1-1z"/>
<path d="m4.634 4.17-1-1.732s-.5-.866.366-1.366 1.366.366 1.366.366l1 1.732s.5.866-.366 1.366-1.366-.366-1.366-.366z" opacity=".93"/>
<path d="m3.17 6.366-1.732-1S.572 4.866 1.072 4s1.366-.366 1.366-.366l1.732 1s.866.5.366 1.366-1.366.366-1.366.366z" opacity=".86"/>
<path d="M3 9 1 9S0 9 0 8s1-1 1-1l2 0s1 0 1 1-1 1-1 1z" opacity=".79"/>
<path d="m4.17 11.366-1.732 1s-.866.5-1.366-.366.366-1.366.366-1.366l1.732-1s.866-.5 1.366.366-.366 1.366-.366 1.366z" opacity=".72"/>
<path d="m6.366 12.83-1 1.732s-.5.866-1.366.366-.366-1.366-.366-1.366l1-1.732s.5-.866 1.366-.366.366 1.366.366 1.366z" opacity=".65"/>
<path d="m9 13 0 2s0 1-1 1-1-1-1-1l0-2s0-1 1-1 1 1 1 1z" opacity=".58"/>
<path d="m11.366 11.83 1 1.732s.5.866-.366 1.366-1.366-.366-1.366-.366l-1-1.732s-.5-.866.366-1.366 1.366.366 1.366.366z" opacity=".51"/>
<path d="m12.83 9.634 1.732 1s.866.5.366 1.366-1.366.366-1.366.366l-1.732-1s-.866-.5-.366-1.366 1.366-.366 1.366-.366z" opacity=".44"/>
<path d="m13 7 2 0s1 0 1 1-1 1-1 1l-2 0s-1 0-1-1 1-1 1-1z" opacity=".37"/>
<path d="m11.83 4.634 1.732-1s.866-.5 1.366.366-.366 1.366-.366 1.366l-1.732 1s-.866.5-1.366-.366.366-1.366.366-1.366z" opacity=".5"/>
<path d="m9.634 3.17 1-1.732s.5-.866 1.366-.366.366 1.366.366 1.366l-1 1.732s-.5.866-1.366.366-.366-1.366-.366-1.366z" opacity=".75"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 1.8 KiB