Backed out changeset 0a0138825fb5 (bug 1353571)

--HG--
rename : browser/base/content/test/sync/accounts_testRemoteCommands.html => browser/base/content/test/general/accounts_testRemoteCommands.html
rename : browser/base/content/test/sync/browser_fxa_web_channel.html => browser/base/content/test/general/browser_fxa_web_channel.html
rename : browser/base/content/test/sync/content_aboutAccounts.js => browser/base/content/test/general/content_aboutAccounts.js
This commit is contained in:
Sebastian Hengst 2017-05-03 21:56:44 +02:00
parent 4f4ee59962
commit 18253e3fa2
46 changed files with 1568 additions and 1587 deletions

View File

@ -285,7 +285,7 @@
accesskey="&sendPageToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendpagetodevice-popup"
onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gSync.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gFxAccounts.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
</menu>
<menuseparator id="context-sep-viewbgimage"/>
<menuitem id="context-viewbgimage"
@ -332,7 +332,7 @@
accesskey="&sendLinkToDevice.accesskey;"
hidden="true">
<menupopup id="context-sendlinktodevice-popup"
onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
</menu>
<menuitem id="context-shareselect"
label="&shareSelect.label;"

View File

@ -0,0 +1,398 @@
/* 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/. */
var gFxAccounts = {
_initialized: false,
_cachedProfile: null,
get weave() {
delete this.weave;
return this.weave = Cc["@mozilla.org/weave/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
},
get topics() {
// Do all this dance to lazy-load FxAccountsCommon.
delete this.topics;
return this.topics = [
"weave:service:ready",
"weave:service:login:change",
"weave:service:setup-complete",
"weave:service:sync:error",
"weave:ui:login:error",
this.FxAccountsCommon.ONLOGIN_NOTIFICATION,
this.FxAccountsCommon.ONLOGOUT_NOTIFICATION,
this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
];
},
get panelUIFooter() {
delete this.panelUIFooter;
return this.panelUIFooter = document.getElementById("PanelUI-footer-fxa");
},
get panelUIStatus() {
delete this.panelUIStatus;
return this.panelUIStatus = document.getElementById("PanelUI-fxa-status");
},
get panelUIAvatar() {
delete this.panelUIAvatar;
return this.panelUIAvatar = document.getElementById("PanelUI-fxa-avatar");
},
get panelUILabel() {
delete this.panelUILabel;
return this.panelUILabel = document.getElementById("PanelUI-fxa-label");
},
get panelUIIcon() {
delete this.panelUIIcon;
return this.panelUIIcon = document.getElementById("PanelUI-fxa-icon");
},
get strings() {
delete this.strings;
return this.strings = Services.strings.createBundle(
"chrome://browser/locale/accounts.properties"
);
},
get loginFailed() {
// Referencing Weave.Service will implicitly initialize sync, and we don't
// want to force that - so first check if it is ready.
if (!this.weaveService.ready) {
return false;
}
// LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
// All other login failures are assumed to be transient and should go
// away by themselves, so aren't reflected here.
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
get sendTabToDeviceEnabled() {
return Services.prefs.getBoolPref("services.sync.sendTabToDevice.enabled");
},
isSendableURI(aURISpec) {
if (!aURISpec) {
return false;
}
// Disallow sending tabs with more than 65535 characters.
if (aURISpec.length > 65535) {
return false;
}
try {
// Filter out un-sendable URIs -- things like local files, object urls, etc.
const unsendableRegexp = new RegExp(
Services.prefs.getCharPref("services.sync.engine.tabs.filteredUrls"), "i");
return !unsendableRegexp.test(aURISpec);
} catch (e) {
// The preference has been removed, or is an invalid regexp, so we log an
// error and treat it as a valid URI -- and the more problematic case is
// the length, which we've already addressed.
Cu.reportError(`Failed to build url filter regexp for send tab: ${e}`);
return true;
}
},
get remoteClients() {
return Weave.Service.clientsEngine.remoteClients
.sort((a, b) => a.name.localeCompare(b.name));
},
init() {
// Bail out if we're already initialized and for pop-up windows.
if (this._initialized || !window.toolbar.visible) {
return;
}
for (let topic of this.topics) {
Services.obs.addObserver(this, topic);
}
EnsureFxAccountsWebChannel();
this._initialized = true;
this.updateUI();
},
uninit() {
if (!this._initialized) {
return;
}
for (let topic of this.topics) {
Services.obs.removeObserver(this, topic);
}
this._initialized = false;
},
observe(subject, topic, data) {
switch (topic) {
case this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION:
this._cachedProfile = null;
// Fallthrough intended
default:
this.updateUI();
break;
}
},
// Note that updateUI() returns a Promise that's only used by tests.
updateUI() {
this.panelUIFooter.hidden = false;
let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
// The localization string is for the signed in text, but it's the default text as well
let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
let updateWithUserData = (userData) => {
// Window might have been closed while fetching data.
if (window.closed) {
return;
}
// Reset the button to its original state.
this.panelUILabel.setAttribute("label", defaultLabel);
this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
this.panelUIFooter.removeAttribute("fxastatus");
this.panelUIAvatar.style.removeProperty("list-style-image");
let showErrorBadge = false;
if (userData) {
// At this point we consider the user as logged-in (but still can be in an error state)
if (this.loginFailed) {
let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [userData.email], 1);
this.panelUIFooter.setAttribute("fxastatus", "error");
this.panelUILabel.setAttribute("label", errorLabel);
this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
showErrorBadge = true;
} else if (!userData.verified) {
let tooltipDescription = this.strings.formatStringFromName("verifyDescription", [userData.email], 1);
this.panelUIFooter.setAttribute("fxastatus", "error");
this.panelUIFooter.setAttribute("unverified", "true");
this.panelUILabel.setAttribute("label", unverifiedLabel);
this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
showErrorBadge = true;
} else {
this.panelUIFooter.setAttribute("fxastatus", "signedin");
this.panelUILabel.setAttribute("label", userData.email);
}
}
if (showErrorBadge) {
PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
} else {
PanelUI.removeNotification("fxa-needs-authentication");
}
}
let updateWithProfile = (profile) => {
if (profile.displayName) {
this.panelUILabel.setAttribute("label", profile.displayName);
}
if (profile.avatar) {
let bgImage = "url(\"" + profile.avatar + "\")";
this.panelUIAvatar.style.listStyleImage = bgImage;
let img = new Image();
img.onerror = () => {
// Clear the image if it has trouble loading. Since this callback is asynchronous
// we check to make sure the image is still the same before we clear it.
if (this.panelUIAvatar.style.listStyleImage === bgImage) {
this.panelUIAvatar.style.removeProperty("list-style-image");
}
};
img.src = profile.avatar;
}
}
return fxAccounts.getSignedInUser().then(userData => {
// userData may be null here when the user is not signed-in, but that's expected
updateWithUserData(userData);
// unverified users cause us to spew log errors fetching an OAuth token
// to fetch the profile, so don't even try in that case.
if (!userData || !userData.verified) {
return null; // don't even try to grab the profile.
}
if (this._cachedProfile) {
return this._cachedProfile;
}
return fxAccounts.getSignedInUserProfile().catch(err => {
// Not fetching the profile is sad but the FxA logs will already have noise.
return null;
});
}).then(profile => {
if (!profile) {
return;
}
updateWithProfile(profile);
this._cachedProfile = profile; // Try to avoid fetching the profile on every UI update
}).catch(error => {
// This is most likely in tests, were we quickly log users in and out.
// The most likely scenario is a user logged out, so reflect that.
// Bug 995134 calls for better errors so we could retry if we were
// sure this was the failure reason.
this.FxAccountsCommon.log.error("Error updating FxA account info", error);
updateWithUserData(null);
});
},
onMenuPanelCommand() {
switch (this.panelUIFooter.getAttribute("fxastatus")) {
case "signedin":
this.openPreferences();
break;
case "error":
if (this.panelUIFooter.getAttribute("unverified")) {
this.openPreferences();
} else {
this.openSignInAgainPage("menupanel");
}
break;
default:
this.openPreferences();
break;
}
PanelUI.hide();
},
openPreferences() {
openPreferences("paneSync", { urlParams: { entrypoint: "menupanel" } });
},
openAccountsPage(action, urlParams = {}) {
let params = new URLSearchParams();
if (action) {
params.set("action", action);
}
for (let name in urlParams) {
if (urlParams[name] !== undefined) {
params.set(name, urlParams[name]);
}
}
let url = "about:accounts?" + params;
switchToTabHavingURI(url, true, {
replaceQueryString: true
});
},
openSignInAgainPage(entryPoint) {
this.openAccountsPage("reauth", { entrypoint: entryPoint });
},
async openDevicesManagementPage(entryPoint) {
let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
switchToTabHavingURI(url, true, {
replaceQueryString: true
});
},
sendTabToDevice(url, clientId, title) {
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
},
populateSendTabToDevicesMenu(devicesPopup, url, title) {
// remove existing menu items
while (devicesPopup.hasChildNodes()) {
devicesPopup.firstChild.remove();
}
const fragment = document.createDocumentFragment();
const onTargetDeviceCommand = (event) => {
let clients = event.target.getAttribute("clientId") ?
[event.target.getAttribute("clientId")] :
this.remoteClients.map(client => client.id);
clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
}
function addTargetDevice(clientId, name) {
const targetDevice = document.createElement("menuitem");
targetDevice.addEventListener("command", onTargetDeviceCommand, true);
targetDevice.setAttribute("class", "sendtab-target");
targetDevice.setAttribute("clientId", clientId);
targetDevice.setAttribute("label", name);
fragment.appendChild(targetDevice);
}
const clients = this.remoteClients;
for (let client of clients) {
addTargetDevice(client.id, client.name);
}
// "All devices" menu item
if (clients.length > 1) {
const separator = document.createElement("menuseparator");
fragment.appendChild(separator);
const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
addTargetDevice("", allDevicesLabel);
}
devicesPopup.appendChild(fragment);
},
updateTabContextMenu(aPopupMenu, aTargetTab) {
if (!this.sendTabToDeviceEnabled ||
!this.weaveService.ready) {
return;
}
const targetURI = aTargetTab.linkedBrowser.currentURI.spec;
const showSendTab = this.remoteClients.length > 0 && this.isSendableURI(targetURI);
["context_sendTabToDevice", "context_sendTabToDevice_separator"]
.forEach(id => { document.getElementById(id).hidden = !showSendTab });
},
initPageContextMenu(contextMenu) {
if (!this.sendTabToDeviceEnabled ||
!this.weaveService.ready) {
return;
}
const remoteClientPresent = this.remoteClients.length > 0;
// showSendLink and showSendPage are mutually exclusive
let showSendLink = remoteClientPresent
&& (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
const showSendPage = !showSendLink && remoteClientPresent
&& !(contextMenu.isContentSelected ||
contextMenu.onImage || contextMenu.onCanvas ||
contextMenu.onVideo || contextMenu.onAudio ||
contextMenu.onLink || contextMenu.onTextInput)
&& this.isSendableURI(contextMenu.browser.currentURI.spec);
if (showSendLink) {
// This isn't part of the condition above since we don't want to try and
// send the page if a link is clicked on or selected but is not sendable.
showSendLink = this.isSendableURI(contextMenu.linkURL);
}
["context-sendpagetodevice", "context-sep-sendpagetodevice"]
.forEach(id => contextMenu.showItem(id, showSendPage));
["context-sendlinktodevice", "context-sep-sendlinktodevice"]
.forEach(id => contextMenu.showItem(id, showSendLink));
}
};
XPCOMUtils.defineLazyGetter(gFxAccounts, "FxAccountsCommon", function() {
return Cu.import("resource://gre/modules/FxAccountsCommon.js", {});
});
XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
"resource://gre/modules/FxAccountsWebChannel.jsm");
XPCOMUtils.defineLazyGetter(gFxAccounts, "weaveService", function() {
return Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
});

View File

@ -475,7 +475,12 @@
label="&toolsMenu.label;"
accesskey="&toolsMenu.accesskey;"
onpopupshowing="mirrorShow(this)">
<menupopup id="menu_ToolsPopup">
<menupopup id="menu_ToolsPopup"
# We have to use setTimeout() here to avoid a flickering menu bar when opening
# the Tools menu, see bug 970769. This can be removed once we got rid of the
# event loop spinning in Weave.Status._authManager.
onpopupshowing="setTimeout(() => gSyncUI.updateUI());"
>
<menuitem id="menu_openDownloads"
label="&downloads.label;"
accesskey="&downloads.accesskey;"
@ -492,17 +497,17 @@
label="&syncSignIn.label;"
accesskey="&syncSignIn.accesskey;"
observes="sync-setup-state"
oncommand="gSync.openPrefs('menubar')"/>
oncommand="gSyncUI.openPrefs('menubar')"/>
<menuitem id="sync-syncnowitem"
label="&syncSyncNowItem.label;"
accesskey="&syncSyncNowItem.accesskey;"
observes="sync-syncnow-state"
oncommand="gSync.doSync(event);"/>
oncommand="gSyncUI.doSync(event);"/>
<menuitem id="sync-reauthitem"
label="&syncReAuthItem.label;"
accesskey="&syncReAuthItem.accesskey;"
observes="sync-reauth-state"
oncommand="gSync.openSignInAgainPage('menubar');"/>
oncommand="gSyncUI.openSignInAgainPage('menubar');"/>
<menuseparator id="devToolsSeparator"/>
<menu id="webDeveloperMenu"
label="&webDeveloperMenu.label;"

View File

@ -1,574 +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/. */
Cu.import("resource://services-sync/UIState.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
"resource://gre/modules/FxAccountsWebChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
"resource://services-sync/main.js");
if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
"resource://gre/modules/CloudSync.jsm");
}
const MIN_STATUS_ANIMATION_DURATION = 1600;
var gSync = {
_initialized: false,
// The last sync start time. Used to calculate the leftover animation time
// once syncing completes (bug 1239042).
_syncStartTime: 0,
_syncAnimationTimer: 0,
_obs: [
"weave:engine:sync:finish",
"quit-application",
UIState.ON_UPDATE
],
get panelUIFooter() {
delete this.panelUIFooter;
return this.panelUIFooter = document.getElementById("PanelUI-footer-fxa");
},
get panelUIStatus() {
delete this.panelUIStatus;
return this.panelUIStatus = document.getElementById("PanelUI-fxa-status");
},
get panelUIAvatar() {
delete this.panelUIAvatar;
return this.panelUIAvatar = document.getElementById("PanelUI-fxa-avatar");
},
get panelUILabel() {
delete this.panelUILabel;
return this.panelUILabel = document.getElementById("PanelUI-fxa-label");
},
get panelUIIcon() {
delete this.panelUIIcon;
return this.panelUIIcon = document.getElementById("PanelUI-fxa-icon");
},
get fxaStrings() {
delete this.fxaStrings;
return this.fxaStrings = Services.strings.createBundle(
"chrome://browser/locale/accounts.properties"
);
},
get syncStrings() {
delete this.syncStrings;
// XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
// but for now just make it work
return this.syncStrings = Services.strings.createBundle(
"chrome://weave/locale/sync.properties"
);
},
get sendTabToDeviceEnabled() {
return Services.prefs.getBoolPref("services.sync.sendTabToDevice.enabled");
},
get remoteClients() {
return Weave.Service.clientsEngine.remoteClients
.sort((a, b) => a.name.localeCompare(b.name));
},
init() {
// Bail out if we're already initialized or for pop-up windows.
if (this._initialized || !window.toolbar.visible) {
return;
}
for (let topic of this._obs) {
Services.obs.addObserver(this, topic, true);
}
// initial label for the sync buttons.
let broadcaster = document.getElementById("sync-status");
broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
// Update the UI
if (UIState.isReady()) {
const state = UIState.get();
// If we are not configured, the UI is already in the right state when
// we open the window. We can avoid a repaint.
if (state.status != UIState.STATUS_NOT_CONFIGURED) {
this.updateAllUI(state);
}
}
this.maybeMoveSyncedTabsButton();
EnsureFxAccountsWebChannel();
this._initialized = true;
},
uninit() {
if (!this._initialized) {
return;
}
for (let topic of this._obs) {
Services.obs.removeObserver(this, topic);
}
this._initialized = false;
},
observe(subject, topic, data) {
if (!this._initialized) {
Cu.reportError("browser-sync observer called after unload: " + topic);
return;
}
switch (topic) {
case UIState.ON_UPDATE:
const state = UIState.get();
this.updateAllUI(state);
break;
case "quit-application":
// Stop the animation timer on shutdown, since we can't update the UI
// after this.
clearTimeout(this._syncAnimationTimer);
break;
case "weave:engine:sync:finish":
if (data != "clients") {
return;
}
this.onClientsSynced();
break;
}
},
updateAllUI(state) {
this.updatePanelBadge(state);
this.updatePanelPopup(state);
this.updateStateBroadcasters(state);
this.updateSyncButtonsTooltip(state);
this.updateSyncStatus(state);
},
updatePanelPopup(state) {
let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
// The localization string is for the signed in text, but it's the default text as well
let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
const status = state.status;
// Reset the status bar to its original state.
this.panelUILabel.setAttribute("label", defaultLabel);
this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
this.panelUIFooter.removeAttribute("fxastatus");
this.panelUIAvatar.style.removeProperty("list-style-image");
if (status == UIState.STATUS_NOT_CONFIGURED) {
return;
}
// At this point we consider sync to be configured (but still can be in an error state).
if (status == UIState.STATUS_LOGIN_FAILED) {
let tooltipDescription = this.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
this.panelUIFooter.setAttribute("fxastatus", "login-failed");
this.panelUILabel.setAttribute("label", errorLabel);
this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
return;
} else if (status == UIState.STATUS_NOT_VERIFIED) {
let tooltipDescription = this.fxaStrings.formatStringFromName("verifyDescription", [state.email], 1);
let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
this.panelUIFooter.setAttribute("fxastatus", "unverified");
this.panelUILabel.setAttribute("label", unverifiedLabel);
this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
return;
}
// At this point we consider sync to be logged-in.
this.panelUIFooter.setAttribute("fxastatus", "signedin");
this.panelUILabel.setAttribute("label", state.displayName || state.email);
if (state.avatarURL) {
let bgImage = "url(\"" + state.avatarURL + "\")";
this.panelUIAvatar.style.listStyleImage = bgImage;
let img = new Image();
img.onerror = () => {
// Clear the image if it has trouble loading. Since this callback is asynchronous
// we check to make sure the image is still the same before we clear it.
if (this.panelUIAvatar.style.listStyleImage === bgImage) {
this.panelUIAvatar.style.removeProperty("list-style-image");
}
};
img.src = state.avatarURL;
}
},
updatePanelBadge(state) {
if (state.status == UIState.STATUS_LOGIN_FAILED ||
state.status == UIState.STATUS_NOT_VERIFIED) {
PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
} else {
PanelUI.removeNotification("fxa-needs-authentication");
}
},
updateStateBroadcasters(state) {
const status = state.status;
// Start off with a clean slate
document.getElementById("sync-reauth-state").hidden = true;
document.getElementById("sync-setup-state").hidden = true;
document.getElementById("sync-syncnow-state").hidden = true;
if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
document.getElementById("sync-syncnow-state").hidden = false;
} else if (status == UIState.STATUS_LOGIN_FAILED) {
// unhiding this element makes the menubar show the login failure state.
document.getElementById("sync-reauth-state").hidden = false;
} else if (status == UIState.STATUS_NOT_CONFIGURED ||
status == UIState.STATUS_NOT_VERIFIED) {
document.getElementById("sync-setup-state").hidden = false;
} else {
document.getElementById("sync-syncnow-state").hidden = false;
}
},
updateSyncStatus(state) {
const broadcaster = document.getElementById("sync-status");
const syncingUI = broadcaster.getAttribute("syncstatus") == "active";
if (state.syncing != syncingUI) { // Do we need to update the UI?
state.syncing ? this.onActivityStart() : this.onActivityStop();
}
},
onMenuPanelCommand() {
switch (this.panelUIFooter.getAttribute("fxastatus")) {
case "signedin":
this.openPrefs("menupanel");
break;
case "error":
if (this.panelUIFooter.getAttribute("fxastatus") == "unverified") {
this.openPrefs("menupanel");
} else {
this.openSignInAgainPage("menupanel");
}
break;
default:
this.openPrefs("menupanel");
break;
}
PanelUI.hide();
},
openAccountsPage(action, urlParams = {}) {
let params = new URLSearchParams();
if (action) {
params.set("action", action);
}
for (let name in urlParams) {
if (urlParams[name] !== undefined) {
params.set(name, urlParams[name]);
}
}
let url = "about:accounts?" + params;
switchToTabHavingURI(url, true, {
replaceQueryString: true
});
},
openSignInAgainPage(entryPoint) {
this.openAccountsPage("reauth", { entrypoint: entryPoint });
},
async openDevicesManagementPage(entryPoint) {
let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
switchToTabHavingURI(url, true, {
replaceQueryString: true
});
},
sendTabToDevice(url, clientId, title) {
Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
},
populateSendTabToDevicesMenu(devicesPopup, url, title) {
// remove existing menu items
while (devicesPopup.hasChildNodes()) {
devicesPopup.firstChild.remove();
}
const fragment = document.createDocumentFragment();
const onTargetDeviceCommand = (event) => {
let clients = event.target.getAttribute("clientId") ?
[event.target.getAttribute("clientId")] :
this.remoteClients.map(client => client.id);
clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
}
function addTargetDevice(clientId, name) {
const targetDevice = document.createElement("menuitem");
targetDevice.addEventListener("command", onTargetDeviceCommand, true);
targetDevice.setAttribute("class", "sendtab-target");
targetDevice.setAttribute("clientId", clientId);
targetDevice.setAttribute("label", name);
fragment.appendChild(targetDevice);
}
const clients = this.remoteClients;
for (let client of clients) {
addTargetDevice(client.id, client.name);
}
// "All devices" menu item
if (clients.length > 1) {
const separator = document.createElement("menuseparator");
fragment.appendChild(separator);
const allDevicesLabel = this.fxaStrings.GetStringFromName("sendTabToAllDevices.menuitem");
addTargetDevice("", allDevicesLabel);
}
devicesPopup.appendChild(fragment);
},
isSendableURI(aURISpec) {
if (!aURISpec) {
return false;
}
// Disallow sending tabs with more than 65535 characters.
if (aURISpec.length > 65535) {
return false;
}
try {
// Filter out un-sendable URIs -- things like local files, object urls, etc.
const unsendableRegexp = new RegExp(
Services.prefs.getCharPref("services.sync.engine.tabs.filteredUrls"), "i");
return !unsendableRegexp.test(aURISpec);
} catch (e) {
// The preference has been removed, or is an invalid regexp, so we log an
// error and treat it as a valid URI -- and the more problematic case is
// the length, which we've already addressed.
Cu.reportError(`Failed to build url filter regexp for send tab: ${e}`);
return true;
}
},
updateTabContextMenu(aPopupMenu, aTargetTab) {
if (!this.sendTabToDeviceEnabled || !this.weaveService.ready) {
return;
}
const targetURI = aTargetTab.linkedBrowser.currentURI.spec;
const showSendTab = this.remoteClients.length > 0 && this.isSendableURI(targetURI);
["context_sendTabToDevice", "context_sendTabToDevice_separator"]
.forEach(id => { document.getElementById(id).hidden = !showSendTab });
},
initPageContextMenu(contextMenu) {
if (!this.sendTabToDeviceEnabled || !this.weaveService.ready) {
return;
}
const remoteClientPresent = this.remoteClients.length > 0;
// showSendLink and showSendPage are mutually exclusive
let showSendLink = remoteClientPresent
&& (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
const showSendPage = !showSendLink && remoteClientPresent
&& !(contextMenu.isContentSelected ||
contextMenu.onImage || contextMenu.onCanvas ||
contextMenu.onVideo || contextMenu.onAudio ||
contextMenu.onLink || contextMenu.onTextInput)
&& this.isSendableURI(contextMenu.browser.currentURI.spec);
if (showSendLink) {
// This isn't part of the condition above since we don't want to try and
// send the page if a link is clicked on or selected but is not sendable.
showSendLink = this.isSendableURI(contextMenu.linkURL);
}
["context-sendpagetodevice", "context-sep-sendpagetodevice"]
.forEach(id => contextMenu.showItem(id, showSendPage));
["context-sendlinktodevice", "context-sep-sendlinktodevice"]
.forEach(id => contextMenu.showItem(id, showSendLink));
},
// Functions called by observers
onActivityStart() {
clearTimeout(this._syncAnimationTimer);
this._syncStartTime = Date.now();
let broadcaster = document.getElementById("sync-status");
broadcaster.setAttribute("syncstatus", "active");
broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncing2.label"));
broadcaster.setAttribute("disabled", "true");
},
_onActivityStop() {
if (!gBrowser)
return;
let broadcaster = document.getElementById("sync-status");
broadcaster.removeAttribute("syncstatus");
broadcaster.removeAttribute("disabled");
broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
Services.obs.notifyObservers(null, "test:browser-sync:activity-stop");
},
onActivityStop() {
let now = Date.now();
let syncDuration = now - this._syncStartTime;
if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
let animationTime = MIN_STATUS_ANIMATION_DURATION - syncDuration;
clearTimeout(this._syncAnimationTimer);
this._syncAnimationTimer = setTimeout(() => this._onActivityStop(), animationTime);
} else {
this._onActivityStop();
}
},
// doSync forces a sync - it *does not* return a promise as it is called
// via the various UI components.
doSync() {
if (!UIState.isReady()) {
return;
}
const state = UIState.get();
if (state.status == UIState.STATUS_SIGNED_IN) {
setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
}
Services.obs.notifyObservers(null, "cloudsync:user-sync");
},
openPrefs(entryPoint = "syncbutton") {
window.openPreferences("paneSync", { urlParams: { entrypoint: entryPoint } });
},
openSyncedTabsPanel() {
let placement = CustomizableUI.getPlacementOfWidget("sync-button");
let area = placement ? placement.area : CustomizableUI.AREA_NAVBAR;
let anchor = document.getElementById("sync-button") ||
document.getElementById("PanelUI-menu-button");
if (area == CustomizableUI.AREA_PANEL) {
// The button is in the panel, so we need to show the panel UI, then our
// subview.
PanelUI.show().then(() => {
PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
}).catch(Cu.reportError);
} else {
// It is placed somewhere else - just try and show it.
PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
}
},
/* After we are initialized we perform a once-only check for the sync
button being in "customize purgatory" and if so, move it to the panel.
This is done primarily for profiles created before SyncedTabs landed,
where the button defaulted to being in that purgatory.
We use a preference to ensure we only do it once, so people can still
customize it away and have it stick.
*/
maybeMoveSyncedTabsButton() {
const prefName = "browser.migrated-sync-button";
let migrated = Services.prefs.getBoolPref(prefName, false);
if (migrated) {
return;
}
if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
}
Services.prefs.setBoolPref(prefName, true);
},
/* Update the tooltip for the sync-status broadcaster (which will update the
Sync Toolbar button and the Sync spinner in the FxA hamburger area.)
If Sync is configured, the tooltip is when the last sync occurred,
otherwise the tooltip reflects the fact that Sync needs to be
(re-)configured.
*/
updateSyncButtonsTooltip(state) {
const status = state.status;
// This is a little messy as the Sync buttons are 1/2 Sync related and
// 1/2 FxA related - so for some strings we use Sync strings, but for
// others we reach into gSync for strings.
let tooltiptext;
if (status == UIState.STATUS_NOT_VERIFIED) {
// "needs verification"
tooltiptext = this.fxaStrings.formatStringFromName("verifyDescription", [state.email], 1);
} else if (status == UIState.STATUS_NOT_CONFIGURED) {
// "needs setup".
tooltiptext = this.syncStrings.GetStringFromName("signInToSync.description");
} else if (status == UIState.STATUS_LOGIN_FAILED) {
// "need to reconnect/re-enter your password"
tooltiptext = this.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
} else {
// Sync appears configured - format the "last synced at" time.
tooltiptext = this.formatLastSyncDate(state.lastSync);
}
let broadcaster = document.getElementById("sync-status");
if (broadcaster) {
if (tooltiptext) {
broadcaster.setAttribute("tooltiptext", tooltiptext);
} else {
broadcaster.removeAttribute("tooltiptext");
}
}
},
get withinLastWeekFormat() {
delete this.withinLastWeekFormat;
return this.withinLastWeekFormat = new Intl.DateTimeFormat(undefined,
{weekday: "long", hour: "numeric", minute: "numeric"});
},
get oneWeekOrOlderFormat() {
delete this.oneWeekOrOlderFormat;
return this.oneWeekOrOlderFormat = new Intl.DateTimeFormat(undefined,
{month: "long", day: "numeric"});
},
formatLastSyncDate(date) {
let sixDaysAgo = (() => {
let tempDate = new Date();
tempDate.setDate(tempDate.getDate() - 6);
tempDate.setHours(0, 0, 0, 0);
return tempDate;
})();
// It may be confusing for the user to see "Last Sync: Monday" when the last
// sync was indeed a Monday, but 3 weeks ago.
let dateFormat = date < sixDaysAgo ? this.oneWeekOrOlderFormat : this.withinLastWeekFormat;
let lastSyncDateString = dateFormat.format(date);
return this.syncStrings.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
},
onClientsSynced() {
let broadcaster = document.getElementById("sync-syncnow-state");
if (broadcaster) {
if (Weave.Service.clientsEngine.stats.numClients > 1) {
broadcaster.setAttribute("devices-status", "multi");
} else {
broadcaster.setAttribute("devices-status", "single");
}
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference
])
};
XPCOMUtils.defineLazyGetter(gSync, "weaveService", function() {
return Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
});

View File

@ -0,0 +1,498 @@
/* 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/. */
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
"resource://gre/modules/CloudSync.jsm");
}
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
const MIN_STATUS_ANIMATION_DURATION = 1600;
// gSyncUI handles updating the tools menu and displaying notifications.
var gSyncUI = {
_obs: ["weave:service:sync:start",
"weave:service:sync:finish",
"weave:service:sync:error",
"weave:service:setup-complete",
"weave:service:login:start",
"weave:service:login:finish",
"weave:service:login:error",
"weave:service:logout:finish",
"weave:service:start-over",
"weave:service:start-over:finish",
"weave:ui:login:error",
"weave:ui:sync:error",
"weave:ui:sync:finish",
"weave:ui:clear-error",
"weave:engine:sync:finish"
],
_unloaded: false,
// The last sync start time. Used to calculate the leftover animation time
// once syncing completes (bug 1239042).
_syncStartTime: 0,
_syncAnimationTimer: 0,
_withinLastWeekFormat: null,
_oneWeekOrOlderFormat: null,
init() {
// Proceed to set up the UI if Sync has already started up.
// Otherwise we'll do it when Sync is firing up.
if (this.weaveService.ready) {
this.initUI();
return;
}
// Sync isn't ready yet, but we can still update the UI with an initial
// state - we haven't called initUI() yet, but that's OK - that's more
// about observers for state changes, and will be called once Sync is
// ready to start sending notifications.
this.updateUI();
Services.obs.addObserver(this, "weave:service:ready", true);
Services.obs.addObserver(this, "quit-application", true);
// Remove the observer if the window is closed before the observer
// was triggered.
window.addEventListener("unload", () => {
this._unloaded = true;
Services.obs.removeObserver(this, "weave:service:ready");
Services.obs.removeObserver(this, "quit-application");
if (this.weaveService.ready) {
this._obs.forEach(topic => {
Services.obs.removeObserver(this, topic);
});
}
}, { once: true });
},
initUI: function SUI_initUI() {
// If this is a browser window?
if (gBrowser) {
this._obs.push("weave:notification:added");
}
this._obs.forEach(function(topic) {
Services.obs.addObserver(this, topic, true);
}, this);
// initial label for the sync buttons.
let broadcaster = document.getElementById("sync-status");
broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
this.maybeMoveSyncedTabsButton();
this.updateUI();
},
// Returns a promise that resolves with true if Sync needs to be configured,
// false otherwise.
_needsSetup() {
return fxAccounts.getSignedInUser().then(user => {
// We want to treat "account needs verification" as "needs setup".
return !(user && user.verified);
});
},
// Returns a promise that resolves with true if the user currently signed in
// to Sync needs to be verified, false otherwise.
_needsVerification() {
return fxAccounts.getSignedInUser().then(user => {
// If there is no user, they can't be in a "needs verification" state.
if (!user) {
return false;
}
return !user.verified;
});
},
// Note that we don't show login errors in a notification bar here, but do
// still need to track a login-failed state so the "Tools" menu updates
// with the correct state.
loginFailed() {
// If Sync isn't already ready, we don't want to force it to initialize
// by referencing Weave.Status - and it isn't going to be accurate before
// Sync is ready anyway.
if (!this.weaveService.ready) {
this.log.debug("loginFailed has sync not ready, so returning false");
return false;
}
this.log.debug("loginFailed has sync state=${sync}",
{ sync: Weave.Status.login});
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
// Kick off an update of the UI - does *not* return a promise.
updateUI() {
this._promiseUpdateUI().catch(err => {
this.log.error("updateUI failed", err);
})
},
// Updates the UI - returns a promise.
_promiseUpdateUI() {
return this._needsSetup().then(needsSetup => {
if (!gBrowser)
return Promise.resolve();
let loginFailed = this.loginFailed();
// Start off with a clean slate
document.getElementById("sync-reauth-state").hidden = true;
document.getElementById("sync-setup-state").hidden = true;
document.getElementById("sync-syncnow-state").hidden = true;
if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
document.getElementById("sync-syncnow-state").hidden = false;
} else if (loginFailed) {
// unhiding this element makes the menubar show the login failure state.
document.getElementById("sync-reauth-state").hidden = false;
} else if (needsSetup) {
document.getElementById("sync-setup-state").hidden = false;
} else {
document.getElementById("sync-syncnow-state").hidden = false;
}
return this._updateSyncButtonsTooltip();
});
},
// Functions called by observers
onActivityStart() {
if (!gBrowser)
return;
this.log.debug("onActivityStart");
clearTimeout(this._syncAnimationTimer);
this._syncStartTime = Date.now();
let broadcaster = document.getElementById("sync-status");
broadcaster.setAttribute("syncstatus", "active");
broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncing2.label"));
broadcaster.setAttribute("disabled", "true");
this.updateUI();
},
_updateSyncStatus() {
if (!gBrowser)
return;
let broadcaster = document.getElementById("sync-status");
broadcaster.removeAttribute("syncstatus");
broadcaster.removeAttribute("disabled");
broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
this.updateUI();
},
onActivityStop() {
if (!gBrowser)
return;
this.log.debug("onActivityStop");
let now = Date.now();
let syncDuration = now - this._syncStartTime;
if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
let animationTime = MIN_STATUS_ANIMATION_DURATION - syncDuration;
clearTimeout(this._syncAnimationTimer);
this._syncAnimationTimer = setTimeout(() => this._updateSyncStatus(), animationTime);
} else {
this._updateSyncStatus();
}
},
onLoginError: function SUI_onLoginError() {
this.log.debug("onLoginError: login=${login}, sync=${sync}", Weave.Status);
// We don't show any login errors here; browser-fxaccounts shows them in
// the hamburger menu.
this.updateUI();
},
onLogout: function SUI_onLogout() {
this.updateUI();
},
_getAppName() {
let brand = Services.strings.createBundle(
"chrome://branding/locale/brand.properties");
return brand.GetStringFromName("brandShortName");
},
// Commands
// doSync forces a sync - it *does not* return a promise as it is called
// via the various UI components.
doSync() {
this._needsSetup().then(needsSetup => {
if (!needsSetup) {
setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
}
Services.obs.notifyObservers(null, "cloudsync:user-sync");
}).catch(err => {
this.log.error("Failed to force a sync", err);
});
},
// Handle clicking the toolbar button - which either opens the Sync setup
// pages or forces a sync now. Does *not* return a promise as it is called
// via the UI.
handleToolbarButton() {
this._needsSetup().then(needsSetup => {
if (needsSetup || this.loginFailed()) {
this.openPrefs();
} else {
this.doSync();
}
}).catch(err => {
this.log.error("Failed to handle toolbar button command", err);
});
},
/**
* Open the Sync preferences.
*
* @param entryPoint
* Indicates the entrypoint from where this method was called.
*/
openPrefs(entryPoint = "syncbutton") {
openPreferences("paneSync", { urlParams: { entrypoint: entryPoint } });
},
openSignInAgainPage(entryPoint = "syncbutton") {
gFxAccounts.openSignInAgainPage(entryPoint);
},
openSyncedTabsPanel() {
let placement = CustomizableUI.getPlacementOfWidget("sync-button");
let area = placement ? placement.area : CustomizableUI.AREA_NAVBAR;
let anchor = document.getElementById("sync-button") ||
document.getElementById("PanelUI-menu-button");
if (area == CustomizableUI.AREA_PANEL) {
// The button is in the panel, so we need to show the panel UI, then our
// subview.
PanelUI.show().then(() => {
PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
}).catch(Cu.reportError);
} else {
// It is placed somewhere else - just try and show it.
PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
}
},
/* After Sync is initialized we perform a once-only check for the sync
button being in "customize purgatory" and if so, move it to the panel.
This is done primarily for profiles created before SyncedTabs landed,
where the button defaulted to being in that purgatory.
We use a preference to ensure we only do it once, so people can still
customize it away and have it stick.
*/
maybeMoveSyncedTabsButton() {
const prefName = "browser.migrated-sync-button";
let migrated = Services.prefs.getBoolPref(prefName, false);
if (migrated) {
return;
}
if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
}
Services.prefs.setBoolPref(prefName, true);
},
/* Update the tooltip for the sync-status broadcaster (which will update the
Sync Toolbar button and the Sync spinner in the FxA hamburger area.)
If Sync is configured, the tooltip is when the last sync occurred,
otherwise the tooltip reflects the fact that Sync needs to be
(re-)configured.
*/
_updateSyncButtonsTooltip: Task.async(function* () {
if (!gBrowser)
return;
let email;
let user = yield fxAccounts.getSignedInUser();
if (user) {
email = user.email;
}
let needsSetup = yield this._needsSetup();
let needsVerification = yield this._needsVerification();
let loginFailed = this.loginFailed();
// This is a little messy as the Sync buttons are 1/2 Sync related and
// 1/2 FxA related - so for some strings we use Sync strings, but for
// others we reach into gFxAccounts for strings.
let tooltiptext;
if (needsVerification) {
// "needs verification"
tooltiptext = gFxAccounts.strings.formatStringFromName("verifyDescription", [email], 1);
} else if (needsSetup) {
// "needs setup".
tooltiptext = this._stringBundle.GetStringFromName("signInToSync.description");
} else if (loginFailed) {
// "need to reconnect/re-enter your password"
tooltiptext = gFxAccounts.strings.formatStringFromName("reconnectDescription", [email], 1);
} else {
// Sync appears configured - format the "last synced at" time.
try {
let lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync"));
tooltiptext = this.formatLastSyncDate(lastSync);
} catch (e) {
// pref doesn't exist (which will be the case until we've seen the
// first successful sync) or is invalid (which should be impossible!)
// Just leave tooltiptext as the empty string in these cases, which
// will cause the tooltip to be removed below.
}
}
// We've done all our promise-y work and ready to update the UI - make
// sure it hasn't been torn down since we started.
if (!gBrowser)
return;
let broadcaster = document.getElementById("sync-status");
if (broadcaster) {
if (tooltiptext) {
broadcaster.setAttribute("tooltiptext", tooltiptext);
} else {
broadcaster.removeAttribute("tooltiptext");
}
}
}),
getWithinLastWeekFormat() {
return this._withinLastWeekFormat ||
(this._withinLastWeekFormat =
new Intl.DateTimeFormat(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}));
},
getOneWeekOrOlderFormat() {
return this._oneWeekOrOlderFormat ||
(this._oneWeekOrOlderFormat =
new Intl.DateTimeFormat(undefined, {month: "long", day: "numeric"}));
},
formatLastSyncDate(date) {
let sixDaysAgo = (() => {
let tempDate = new Date();
tempDate.setDate(tempDate.getDate() - 6);
tempDate.setHours(0, 0, 0, 0);
return tempDate;
})();
// It may be confusing for the user to see "Last Sync: Monday" when the last
// sync was indeed a Monday, but 3 weeks ago.
let dateFormat = date < sixDaysAgo ? this.getOneWeekOrOlderFormat() : this.getWithinLastWeekFormat();
let lastSyncDateString = dateFormat.format(date);
return this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
},
onClientsSynced() {
let broadcaster = document.getElementById("sync-syncnow-state");
if (broadcaster) {
if (Weave.Service.clientsEngine.stats.numClients > 1) {
broadcaster.setAttribute("devices-status", "multi");
} else {
broadcaster.setAttribute("devices-status", "single");
}
}
},
observe: function SUI_observe(subject, topic, data) {
this.log.debug("observed", topic);
if (this._unloaded) {
Cu.reportError("SyncUI observer called after unload: " + topic);
return;
}
// Unwrap, just like Svc.Obs, but without pulling in that dependency.
if (subject && typeof subject == "object" &&
("wrappedJSObject" in subject) &&
("observersModuleSubjectWrapper" in subject.wrappedJSObject)) {
subject = subject.wrappedJSObject.object;
}
// First handle "activity" only.
switch (topic) {
case "weave:service:sync:start":
this.onActivityStart();
break;
case "weave:service:sync:finish":
case "weave:service:sync:error":
this.onActivityStop();
break;
}
// Now non-activity state (eg, enabled, errors, etc)
// Note that sync uses the ":ui:" notifications for errors because sync.
switch (topic) {
case "weave:ui:sync:finish":
// Do nothing.
break;
case "weave:ui:sync:error":
case "weave:service:setup-complete":
case "weave:service:login:finish":
case "weave:service:login:start":
case "weave:service:start-over":
this.updateUI();
break;
case "weave:ui:login:error":
case "weave:service:login:error":
this.onLoginError();
break;
case "weave:service:logout:finish":
this.onLogout();
break;
case "weave:service:start-over:finish":
this.updateUI();
break;
case "weave:service:ready":
this.initUI();
break;
case "weave:notification:added":
this.initNotifications();
break;
case "weave:engine:sync:finish":
if (data != "clients") {
return;
}
this.onClientsSynced();
break;
case "quit-application":
// Stop the animation timer on shutdown, since we can't update the UI
// after this.
clearTimeout(this._syncAnimationTimer);
break;
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference
])
};
XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
// XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
// but for now just make it work
return Services.strings.createBundle(
"chrome://weave/locale/sync.properties");
});
XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
return Log.repository.getLogger("browserwindow.syncui");
});
XPCOMUtils.defineLazyGetter(gSyncUI, "weaveService", function() {
return Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
});

View File

@ -1507,7 +1507,8 @@ var gBrowserInit = {
PointerLock.init();
// initialize the sync UI
gSync.init();
gSyncUI.init();
gFxAccounts.init();
if (AppConstants.MOZ_DATA_REPORTING)
gDataNotificationInfoBar.init();
@ -1654,7 +1655,7 @@ var gBrowserInit = {
FullScreen.uninit();
gSync.uninit();
gFxAccounts.uninit();
gExtensionsNotifications.uninit();
@ -1817,7 +1818,7 @@ if (AppConstants.platform == "macosx") {
gPrivateBrowsingUI.init();
// initialize the sync UI
gSync.init();
gSyncUI.init();
if (AppConstants.E10S_TESTING_ONLY) {
gRemoteTabsUI.init();
@ -6803,7 +6804,7 @@ function checkEmptyPageOrigin(browser = gBrowser.selectedBrowser,
}
function BrowserOpenSyncTabs() {
gSync.openSyncedTabsPanel();
gSyncUI.openSyncedTabsPanel();
}
function ReportFalseDeceptiveSite() {
@ -8177,7 +8178,7 @@ var TabContextMenu = {
this.contextTab.addEventListener("TabAttrModified", this);
aPopupMenu.addEventListener("popuphiding", this);
gSync.updateTabContextMenu(aPopupMenu, this.contextTab);
gFxAccounts.updateTabContextMenu(aPopupMenu, this.contextTab);
},
handleEvent(aEvent) {
switch (aEvent.type) {

View File

@ -106,7 +106,7 @@
<menu id="context_sendTabToDevice" label="&sendTabToDevice.label;"
accesskey="&sendTabToDevice.accesskey;" hidden="true">
<menupopup id="context_sendTabToDevicePopupMenu"
onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
</menu>
<menuseparator/>
<menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
@ -492,7 +492,7 @@
<menuitem label="&syncedTabs.context.managedevices.label;"
accesskey="&syncedTabs.context.managedevices.accesskey;"
id="syncedTabsManageDevices"
oncommand="gSync.openDevicesManagementPage('syncedtabs-sidebar');"/>
oncommand="gFxAccounts.openDevicesManagementPage('syncedtabs-sidebar');"/>
<menuitem label="&syncSyncNowItem.label;"
accesskey="&syncSyncNowItem.accesskey;"
id="syncedTabsRefresh"/>

View File

@ -30,7 +30,7 @@
<script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sidebar.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-syncui.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-tabsintitlebar.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-thumbnails.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-trackingprotection.js"/>
@ -38,3 +38,5 @@
#ifdef MOZ_DATA_REPORTING
<script type="application/javascript" src="chrome://browser/content/browser-data-submission-info-bar.js"/>
#endif
<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>

View File

@ -121,6 +121,9 @@ with Files("browser-feeds.js"):
with Files("browser-fullZoom.js"):
BUG_COMPONENT = ("Firefox", "Tabbed Browsing")
with Files("browser-fxaccounts.js"):
BUG_COMPONENT = ("Core", "FxAccounts")
with Files("browser-gestureSupport.js"):
BUG_COMPONENT = ("Core", "Widget: Cocoa")
@ -142,7 +145,7 @@ with Files("browser-safebrowsing.js"):
with Files("*social*"):
BUG_COMPONENT = ("Firefox", "SocialAPI")
with Files("browser-sync.js"):
with Files("browser-syncui.js"):
BUG_COMPONENT = ("Firefox", "Sync")
with Files("browser-tabPreviews.xml"):

View File

@ -601,7 +601,7 @@ nsContextMenu.prototype = {
},
initSyncItems() {
gSync.initPageContextMenu(this);
gFxAccounts.initPageContextMenu(this);
},
openPasswordManager() {

View File

@ -8,6 +8,7 @@
[DEFAULT]
support-files =
POSTSearchEngine.xml
accounts_testRemoteCommands.html
alltabslistener.html
app_bug575561.html
app_subframe_bug575561.html
@ -17,6 +18,7 @@ support-files =
browser_bug678392-1.html
browser_bug678392-2.html
browser_bug970746.xhtml
browser_fxa_web_channel.html
browser_registerProtocolHandler_notification.html
browser_star_hsts.sjs
browser_tab_dragdrop2_frame1.xul
@ -91,6 +93,11 @@ support-files =
!/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_aboutAccounts.js]
skip-if = os == "linux" # Bug 958026
support-files =
content_aboutAccounts.js
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_aboutCertError.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_aboutNetError.js]
@ -368,6 +375,11 @@ skip-if = true # browser_drag.js is disabled, as it needs to be updated for the
tags = fullscreen
skip-if = os == "linux" # Linux: Intermittent failures - bug 941575.
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_fxaccounts.js]
support-files = fxa_profile_handler.sjs
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_fxa_web_channel.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_gestureSupport.js]
skip-if = e10s # Bug 863514 - no gesture support.
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
@ -484,6 +496,9 @@ support-files =
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_subframe_favicons_not_used.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_syncui.js]
skip-if = os == 'linux' # Bug 1304272
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tab_close_dependent_window.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tabDrop.js]

View File

@ -17,7 +17,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/sync/";
const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
// Preference helpers.
var changedPrefs = new Set();
@ -42,7 +42,7 @@ var gTests = [
},
*run() {
setPref("identity.fxaccounts.remote.signup.uri",
"https://example.com/browser/browser/base/content/test/sync/accounts_testRemoteCommands.html");
"https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
let tab = yield promiseNewTabLoadEvent("about:accounts");
let mm = tab.linkedBrowser.messageManager;

View File

@ -903,7 +903,7 @@ add_task(function* test_input_spell_false() {
const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
add_task(function* test_plaintext_sendpagetodevice() {
if (!gSync.sendTabToDeviceEnabled) {
if (!gFxAccounts.sendTabToDeviceEnabled) {
return;
}
yield ensureSyncReady();
@ -942,7 +942,7 @@ add_task(function* test_plaintext_sendpagetodevice() {
});
add_task(function* test_link_sendlinktodevice() {
if (!gSync.sendTabToDeviceEnabled) {
if (!gFxAccounts.sendTabToDeviceEnabled) {
return;
}
yield ensureSyncReady();

View File

@ -17,7 +17,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
var {FxAccountsWebChannel} = Components.utils.import("resource://gre/modules/FxAccountsWebChannel.jsm", {});
const TEST_HTTP_PATH = "http://example.com";
const TEST_BASE_URL = TEST_HTTP_PATH + "/browser/browser/base/content/test/sync/browser_fxa_web_channel.html";
const TEST_BASE_URL = TEST_HTTP_PATH + "/browser/browser/base/content/test/general/browser_fxa_web_channel.html";
const TEST_CHANNEL_ID = "account_updates_test";
var gTests = [

View File

@ -0,0 +1,258 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
var {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
var FxAccountsCommon = {};
Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/";
// instrument gFxAccounts to send observer notifications when it's done
// what it does.
(function() {
let unstubs = {}; // The original functions we stub out.
// The stub functions.
let stubs = {
updateUI() {
return unstubs["updateUI"].call(gFxAccounts).then(() => {
Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateUI");
});
},
// Opening preferences is trickier than it should be as leaks are reported
// due to the promises it fires off at load time and there's no clear way to
// know when they are done.
// So just ensure openPreferences is called rather than whether it opens.
openPreferences() {
Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences");
}
};
for (let name in stubs) {
unstubs[name] = gFxAccounts[name];
gFxAccounts[name] = stubs[name];
}
// and undo our damage at the end.
registerCleanupFunction(() => {
for (let name in unstubs) {
gFxAccounts[name] = unstubs[name];
}
stubs = unstubs = null;
});
})();
// Other setup/cleanup
var newTab;
Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri",
TEST_ROOT + "accounts_testRemoteCommands.html");
registerCleanupFunction(() => {
Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
Services.prefs.clearUserPref("identity.fxaccounts.remote.profile.uri");
gBrowser.removeTab(newTab);
});
add_task(function* initialize() {
// Set a new tab with something other than about:blank, so it doesn't get reused.
// We must wait for it to load or the promiseTabOpen() call in the next test
// gets confused.
newTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
yield promiseTabLoaded(newTab);
});
// The elements we care about.
var panelUILabel = document.getElementById("PanelUI-fxa-label");
var panelUIStatus = document.getElementById("PanelUI-fxa-status");
var panelUIFooter = document.getElementById("PanelUI-footer-fxa");
// The tests
add_task(function* test_nouser() {
let user = yield fxAccounts.getSignedInUser();
Assert.strictEqual(user, null, "start with no user signed in");
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI");
Services.obs.notifyObservers(null, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION);
yield promiseUpdateDone;
// Check the world - the FxA footer area is visible as it is offering a signin.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), panelUIStatus.getAttribute("defaultlabel"));
Assert.equal(panelUIStatus.getAttribute("tooltiptext"), panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.ok(!panelUIFooter.hasAttribute("fxastatus"), "no fxsstatus when signed out");
Assert.ok(!panelUIFooter.hasAttribute("fxaprofileimage"), "no fxaprofileimage when signed out");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened;
});
/*
XXX - Bug 1191162 - need a better hawk mock story or this will leak in debug builds.
add_task(function* test_unverifiedUser() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI");
yield setSignedInUser(false); // this will fire the observer that does the update.
yield promiseUpdateDone;
// Check the world.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened
yield signOut();
});
*/
add_task(function* test_verifiedUserEmptyProfile() {
// We see 2 updateUI() calls - one for the signedInUser and one after
// we first fetch the profile. We want them both to fire or we aren't testing
// the state we think we are testing.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 2);
gFxAccounts._cachedProfile = null;
configureProfileURL({}); // successful but empty profile.
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
// Check the world.
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
panelUIStatus.click();
yield promisePreferencesOpened;
yield signOut();
});
add_task(function* test_verifiedUserDisplayName() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 2);
gFxAccounts._cachedProfile = null;
configureProfileURL({ displayName: "Test User Display Name" });
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
Assert.ok(isFooterVisible())
Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
yield signOut();
});
add_task(function* test_profileNotificationsClearsCache() {
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 1);
gFxAccounts._cachedProfile = { foo: "bar" };
Services.obs.notifyObservers(null, this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION);
Assert.ok(!gFxAccounts._cachedProfile);
yield promiseUpdateDone;
});
add_task(function* test_verifiedUserProfileFailure() {
// profile failure means only one observer fires.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 1);
gFxAccounts._cachedProfile = null;
configureProfileURL(null, 500);
yield setSignedInUser(true); // this will fire the observer that does the update.
yield promiseUpdateDone;
Assert.ok(isFooterVisible());
Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
panelUIStatus.getAttribute("signedinTooltiptext"));
Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
yield signOut();
});
// Helpers.
function isFooterVisible() {
let style = window.getComputedStyle(panelUIFooter);
return style.getPropertyValue("display") == "flex";
}
function configureProfileURL(profile, responseStatus = 200) {
let responseBody = profile ? JSON.stringify(profile) : "";
let url = TEST_ROOT + "fxa_profile_handler.sjs?" +
"responseStatus=" + responseStatus +
"responseBody=" + responseBody +
// This is a bit cheeky - the FxA code will just append "/profile"
// to the preference value. We arrange for this to be seen by our
// .sjs as part of the query string.
"&path=";
Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", url);
}
function promiseObserver(topic, count = 1) {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
if (--count == 0) {
Services.obs.removeObserver(obs, aTopic);
resolve(aSubject);
}
}
Services.obs.addObserver(obs, topic);
});
}
var promiseTabOpen = Task.async(function*(urlBase) {
info("Waiting for tab to open...");
let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
let tab = event.target;
yield promiseTabLoadEvent(tab);
ok(tab.linkedBrowser.currentURI.spec.startsWith(urlBase),
"Got " + tab.linkedBrowser.currentURI.spec + ", expecting " + urlBase);
let whenUnloaded = promiseTabUnloaded(tab);
gBrowser.removeTab(tab);
yield whenUnloaded;
});
function promiseTabUnloaded(tab) {
return new Promise(resolve => {
info("Wait for tab to unload");
function handle(event) {
tab.linkedBrowser.removeEventListener("unload", handle, true);
info("Got unload event");
resolve(event);
}
tab.linkedBrowser.addEventListener("unload", handle, true, true);
});
}
// FxAccounts helpers.
function setSignedInUser(verified) {
let data = {
email: "foo@example.com",
uid: "1234@lcip.org",
assertion: "foobar",
sessionToken: "dead",
kA: "beef",
kB: "cafe",
verified,
oauthTokens: {
// a token for the profile server.
profile: "key value",
}
}
return fxAccounts.setSignedInUser(data);
}
var signOut = Task.async(function* () {
// This test needs to make sure that any updates for the logout have
// completed before starting the next test, or we see the observer
// notifications get out of sync.
let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI");
// we always want a "localOnly" signout here...
yield fxAccounts.signOut(true);
yield promiseUpdateDone;
});

View File

@ -0,0 +1,205 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
var {Weave} = Cu.import("resource://services-sync/main.js", {});
var stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://weave/locale/sync.properties");
// ensure test output sees log messages.
Log.repository.getLogger("browserwindow.syncui").addAppender(new Log.DumpAppender());
// Send the specified sync-related notification and return a promise that
// resolves once gSyncUI._promiseUpateUI is complete and the UI is ready to check.
function notifyAndPromiseUIUpdated(topic) {
return new Promise(resolve => {
// Instrument gSyncUI so we know when the update is complete.
let oldPromiseUpdateUI = gSyncUI._promiseUpdateUI.bind(gSyncUI);
gSyncUI._promiseUpdateUI = function() {
return oldPromiseUpdateUI().then(() => {
// Restore our override.
gSyncUI._promiseUpdateUI = oldPromiseUpdateUI;
// Resolve the promise so the caller knows the update is done.
resolve();
});
};
// Now send the notification.
Services.obs.notifyObservers(null, topic);
});
}
// Sync manages 3 broadcasters so the menus correctly reflect the Sync state.
// Only one of these 3 should ever be visible - pass the ID of the broadcaster
// you expect to be visible and it will check it's the only one that is.
function checkBroadcasterVisible(broadcasterId) {
let all = ["sync-reauth-state", "sync-setup-state", "sync-syncnow-state"];
Assert.ok(all.indexOf(broadcasterId) >= 0, "valid id");
for (let check of all) {
let eltHidden = document.getElementById(check).hidden;
Assert.equal(eltHidden, check == broadcasterId ? false : true, check);
}
}
function promiseObserver(topic) {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
Services.obs.removeObserver(obs, aTopic);
resolve(aSubject);
}
Services.obs.addObserver(obs, topic);
});
}
function checkButtonTooltips(stringPrefix) {
for (let butId of ["PanelUI-remotetabs-syncnow", "PanelUI-fxa-icon"]) {
let text = document.getElementById(butId).getAttribute("tooltiptext");
let desc = `Text is "${text}", expecting it to start with "${stringPrefix}"`
Assert.ok(text.startsWith(stringPrefix), desc);
}
}
add_task(function* prepare() {
// add the Sync button to the toolbar so we can get it!
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_NAVBAR);
registerCleanupFunction(() => {
CustomizableUI.removeWidgetFromArea("sync-button");
});
let xps = Components.classes["@mozilla.org/weave/service;1"]
.getService(Components.interfaces.nsISupports)
.wrappedJSObject;
yield xps.whenLoaded();
// Put Sync and the UI into a known state.
Weave.Status.login = Weave.LOGIN_FAILED_NO_USERNAME;
yield notifyAndPromiseUIUpdated("weave:service:login:error");
checkBroadcasterVisible("sync-setup-state");
checkButtonTooltips("Sign In To Sync");
// mock out the "_needsSetup()" function so we don't short-circuit.
let oldNeedsSetup = window.gSyncUI._needsSetup;
window.gSyncUI._needsSetup = () => Promise.resolve(false);
registerCleanupFunction(() => {
window.gSyncUI._needsSetup = oldNeedsSetup;
// and an observer to set the state back to what it should be now we've
// restored the stub.
Services.obs.notifyObservers(null, "weave:service:login:finish");
});
// and a notification to have the state change away from "needs setup"
yield notifyAndPromiseUIUpdated("weave:service:login:finish");
checkBroadcasterVisible("sync-syncnow-state");
// open the sync-button panel so we can check elements in that.
document.getElementById("sync-button").click();
});
add_task(function* testSyncNeedsVerification() {
// mock out the "_needsVerification()" function
let oldNeedsVerification = window.gSyncUI._needsVerification;
window.gSyncUI._needsVerification = () => true;
try {
// a notification for the state change
yield notifyAndPromiseUIUpdated("weave:service:login:finish");
checkButtonTooltips("Verify");
} finally {
window.gSyncUI._needsVerification = oldNeedsVerification;
}
});
add_task(function* testSyncLoginError() {
checkBroadcasterVisible("sync-syncnow-state");
// Pretend we are in a "login failed" error state
Weave.Status.sync = Weave.LOGIN_FAILED;
Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED;
yield notifyAndPromiseUIUpdated("weave:ui:sync:error");
// But the menu *should* reflect the login error.
checkBroadcasterVisible("sync-reauth-state");
// The tooltips for the buttons should also reflect it.
checkButtonTooltips("Reconnect");
// Now pretend we just had a successful login - the error notification should go away.
Weave.Status.sync = Weave.STATUS_OK;
Weave.Status.login = Weave.LOGIN_SUCCEEDED;
yield notifyAndPromiseUIUpdated("weave:service:login:start");
yield notifyAndPromiseUIUpdated("weave:service:login:finish");
// The menus should be back to "all good"
checkBroadcasterVisible("sync-syncnow-state");
});
function checkButtonsStatus(shouldBeActive) {
for (let eid of [
"sync-status", // the broadcaster itself.
"sync-button", // the main sync button which observes the broadcaster
"PanelUI-fxa-icon", // the sync icon in the fxa footer that observes it.
]) {
let elt = document.getElementById(eid);
if (shouldBeActive) {
Assert.equal(elt.getAttribute("syncstatus"), "active", `${eid} should be active`);
} else {
Assert.ok(!elt.hasAttribute("syncstatus"), `${eid} should have no status attr`);
}
}
}
function* testButtonActions(startNotification, endNotification, expectActive = true) {
checkButtonsStatus(false);
// pretend a sync is starting.
yield notifyAndPromiseUIUpdated(startNotification);
checkButtonsStatus(expectActive);
// and has stopped
yield notifyAndPromiseUIUpdated(endNotification);
checkButtonsStatus(false);
}
function *doTestButtonActivities() {
// logins do not "activate" the spinner/button as they may block and make
// the UI look like Sync is never completing.
yield testButtonActions("weave:service:login:start", "weave:service:login:finish", false);
yield testButtonActions("weave:service:login:start", "weave:service:login:error", false);
// But notifications for Sync itself should activate it.
yield testButtonActions("weave:service:sync:start", "weave:service:sync:finish");
yield testButtonActions("weave:service:sync:start", "weave:service:sync:error");
// and ensure the counters correctly handle multiple in-flight syncs
yield notifyAndPromiseUIUpdated("weave:service:sync:start");
checkButtonsStatus(true);
// sync stops.
yield notifyAndPromiseUIUpdated("weave:service:sync:finish");
// Button should not be active.
checkButtonsStatus(false);
}
add_task(function* testButtonActivitiesInNavBar() {
// check the button's functionality while the button is in the NavBar - which
// it already is.
yield doTestButtonActivities();
});
add_task(function* testFormatLastSyncDateNow() {
let now = new Date();
let nowString = gSyncUI.formatLastSyncDate(now);
Assert.equal(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}));
});
add_task(function* testFormatLastSyncDateMonthAgo() {
let monthAgo = new Date();
monthAgo.setMonth(monthAgo.getMonth() - 1);
let monthAgoString = gSyncUI.formatLastSyncDate(monthAgo);
Assert.equal(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: "long", day: "numeric"}));
});
add_task(function* testButtonActivitiesInPanel() {
// check the button's functionality while the button is in the panel - it's
// currently in the NavBar - move it to the panel and open it.
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
yield PanelUI.show();
try {
yield doTestButtonActivities();
} finally {
PanelUI.hide();
}
});

View File

@ -17,9 +17,9 @@ add_task(function* test() {
is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled");
if (gSync.sendTabToDeviceEnabled) {
const origIsSendableURI = gSync.isSendableURI;
gSync.isSendableURI = () => true;
if (gFxAccounts.sendTabToDeviceEnabled) {
const origIsSendableURI = gFxAccounts.isSendableURI;
gFxAccounts.isSendableURI = () => true;
// Check the send tab to device menu item
yield ensureSyncReady();
const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
@ -31,11 +31,11 @@ add_task(function* test() {
is(targets[0].getAttribute("label"), "Foo", "Foo target is present");
is(targets[1].getAttribute("label"), "Bar", "Bar target is present");
is(targets[3].getAttribute("label"), "All Devices", "All Devices target is present");
gSync.isSendableURI = () => false;
gFxAccounts.isSendableURI = () => false;
updateTabContextMenu(origTab);
is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
restoreRemoteClients(oldGetter);
gSync.isSendableURI = origIsSendableURI;
gFxAccounts.isSendableURI = origIsSendableURI;
}
// Hide the original tab.

View File

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// This is basically an echo server!
// We just grab responseStatus and responseBody query params!
function reallyHandleRequest(request, response) {
var query = "?" + request.queryString;
var responseStatus = 200;
var match = /responseStatus=([^&]*)/.exec(query);
if (match) {
responseStatus = parseInt(match[1]);
}
var responseBody = "";
match = /responseBody=([^&]*)/.exec(query);
if (match) {
responseBody = decodeURIComponent(match[1]);
}
response.setStatusLine("1.0", responseStatus, "OK");
response.write(responseBody);
}
function handleRequest(request, response)
{
try {
reallyHandleRequest(request, response);
} catch (e) {
response.setStatusLine("1.0", 500, "NotOK");
response.write("Error handling request: " + e);
}
}

View File

@ -836,16 +836,16 @@ function getCertExceptionDialog(aLocation) {
function setupRemoteClientsFixture(fixture) {
let oldRemoteClientsGetter =
Object.getOwnPropertyDescriptor(gSync, "remoteClients").get;
Object.getOwnPropertyDescriptor(gFxAccounts, "remoteClients").get;
Object.defineProperty(gSync, "remoteClients", {
Object.defineProperty(gFxAccounts, "remoteClients", {
get() { return fixture; }
});
return oldRemoteClientsGetter;
}
function restoreRemoteClients(getter) {
Object.defineProperty(gSync, "remoteClients", {
Object.defineProperty(gFxAccounts, "remoteClients", {
get: getter
});
}

View File

@ -1,7 +0,0 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test"
]
};

View File

@ -1,9 +0,0 @@
[browser_sync.js]
[browser_fxa_web_channel.js]
support-files=
browser_fxa_web_channel.html
[browser_aboutAccounts.js]
skip-if = os == "linux" # Bug 958026
support-files =
content_aboutAccounts.js
accounts_testRemoteCommands.html

View File

@ -1,241 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_ui_state_notification_calls_updateAllUI() {
let called = false;
let updateAllUI = gSync.updateAllUI;
gSync.updateAllUI = () => { called = true; };
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
ok(called);
gSync.updateAllUI = updateAllUI;
});
add_task(async function test_ui_state_signedin() {
let state = {
status: UIState.STATUS_SIGNED_IN,
email: "foo@bar.com",
displayName: "Foo Bar",
avatarURL: "https://foo.bar",
lastSync: new Date(),
syncing: false
};
gSync.updateAllUI(state);
checkFxABadge(false);
let statusBarTooltip = gSync.panelUIStatus.getAttribute("signedinTooltiptext");
let lastSyncTooltip = gSync.formatLastSyncDate(new Date(state.lastSync));
checkPanelUIStatusBar({
label: "Foo Bar",
tooltip: statusBarTooltip,
fxastatus: "signedin",
avatarURL: "https://foo.bar",
syncing: false,
syncNowTooltip: lastSyncTooltip
});
checkRemoteTabsPanel("PanelUI-remotetabs-main", false);
checkMenuBarItem("sync-syncnowitem");
});
add_task(async function test_ui_state_syncing() {
let state = {
status: UIState.STATUS_SIGNED_IN,
email: "foo@bar.com",
displayName: "Foo Bar",
avatarURL: "https://foo.bar",
lastSync: new Date(),
syncing: true
};
gSync.updateAllUI(state);
checkSyncNowButton("PanelUI-fxa-icon", true);
checkSyncNowButton("PanelUI-remotetabs-syncnow", true);
// Be good citizens and remove the "syncing" state.
gSync.updateAllUI({
status: UIState.STATUS_SIGNED_IN,
email: "foo@bar.com",
lastSync: new Date(),
syncing: false
});
// Because we switch from syncing to non-syncing, and there's a timeout involved.
await promiseObserver("test:browser-sync:activity-stop");
});
add_task(async function test_ui_state_unconfigured() {
let state = {
status: UIState.STATUS_NOT_CONFIGURED
};
gSync.updateAllUI(state);
checkFxABadge(false);
let signedOffLabel = gSync.panelUIStatus.getAttribute("defaultlabel");
let statusBarTooltip = gSync.panelUIStatus.getAttribute("signedinTooltiptext");
checkPanelUIStatusBar({
label: signedOffLabel,
tooltip: statusBarTooltip
});
checkRemoteTabsPanel("PanelUI-remotetabs-setupsync");
checkMenuBarItem("sync-setup");
});
add_task(async function test_ui_state_unverified() {
let state = {
status: UIState.STATUS_NOT_VERIFIED,
email: "foo@bar.com",
lastSync: new Date(),
syncing: false
};
gSync.updateAllUI(state);
checkFxABadge(true);
let expectedLabel = gSync.panelUIStatus.getAttribute("unverifiedlabel");
let tooltipText = gSync.fxaStrings.formatStringFromName("verifyDescription", [state.email], 1);
checkPanelUIStatusBar({
label: expectedLabel,
tooltip: tooltipText,
fxastatus: "unverified",
avatarURL: null,
syncing: false,
syncNowTooltip: tooltipText
});
checkRemoteTabsPanel("PanelUI-remotetabs-setupsync", false);
checkMenuBarItem("sync-setup");
});
add_task(async function test_ui_state_loginFailed() {
let state = {
status: UIState.STATUS_LOGIN_FAILED,
email: "foo@bar.com"
};
gSync.updateAllUI(state);
checkFxABadge(true);
let expectedLabel = gSync.panelUIStatus.getAttribute("errorlabel");
let tooltipText = gSync.fxaStrings.formatStringFromName("reconnectDescription", [state.email], 1);
checkPanelUIStatusBar({
label: expectedLabel,
tooltip: tooltipText,
fxastatus: "login-failed",
avatarURL: null,
syncing: false,
syncNowTooltip: tooltipText
});
checkRemoteTabsPanel("PanelUI-remotetabs-reauthsync", false);
checkMenuBarItem("sync-reauthitem");
});
add_task(async function test_FormatLastSyncDateNow() {
let now = new Date();
let nowString = gSync.formatLastSyncDate(now);
is(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}),
"The date is correctly formatted");
});
add_task(async function test_FormatLastSyncDateMonthAgo() {
let monthAgo = new Date();
monthAgo.setMonth(monthAgo.getMonth() - 1);
let monthAgoString = gSync.formatLastSyncDate(monthAgo);
is(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: "long", day: "numeric"}),
"The date is correctly formatted");
});
function checkFxABadge(shouldBeShown) {
let isShown = false;
for (let notification of PanelUI.notifications) {
if (notification.id == "fxa-needs-authentication") {
isShown = true;
break;
}
}
is(isShown, shouldBeShown, "the fxa badge has the right visibility");
}
function checkPanelUIStatusBar({label, tooltip, fxastatus, avatarURL, syncing, syncNowTooltip}) {
let labelNode = document.getElementById("PanelUI-fxa-label");
let tooltipNode = document.getElementById("PanelUI-fxa-status");
let statusNode = document.getElementById("PanelUI-footer-fxa");
let avatar = document.getElementById("PanelUI-fxa-avatar");
is(labelNode.getAttribute("label"), label, "panelUI-fxa label has the right value");
is(tooltipNode.getAttribute("tooltiptext"), tooltip, "panelUI-fxa tooltip has the right value");
if (fxastatus) {
is(statusNode.getAttribute("fxastatus"), fxastatus, "panelUI-fxa fxastatus has the right value");
} else {
ok(!statusNode.hasAttribute("fxastatus"), "panelUI-fxa fxastatus is unset")
}
if (avatarURL) {
is(avatar.style.listStyleImage, `url("${avatarURL}")`, "panelUI-fxa avatar URL is set");
} else {
ok(!statusNode.style.listStyleImage, "panelUI-fxa avatar URL is unset");
}
if (syncing != undefined && syncNowTooltip != undefined) {
checkSyncNowButton("PanelUI-fxa-icon", syncing, syncNowTooltip);
}
}
function checkRemoteTabsPanel(expectedShownItemId, syncing, syncNowTooltip) {
checkItemsVisiblities(["PanelUI-remotetabs-main",
"PanelUI-remotetabs-setupsync",
"PanelUI-remotetabs-reauthsync"],
expectedShownItemId);
if (syncing != undefined && syncNowTooltip != undefined) {
checkSyncNowButton("PanelUI-remotetabs-syncnow", syncing, syncNowTooltip);
}
}
function checkMenuBarItem(expectedShownItemId) {
checkItemsVisiblities(["sync-setup", "sync-syncnowitem", "sync-reauthitem"],
expectedShownItemId);
}
function checkSyncNowButton(buttonId, syncing, tooltip = null) {
const remoteTabsButton = document.getElementById(buttonId);
is(remoteTabsButton.getAttribute("syncstatus"), syncing ? "active" : "", "button active has the right value");
if (tooltip) {
is(remoteTabsButton.getAttribute("tooltiptext"), tooltip, "button tooltiptext is set to the right value");
}
if (buttonId == "PanelUI-fxa-icon") {
return;
}
is(remoteTabsButton.hasAttribute("disabled"), syncing, "disabled has the right value");
if (syncing) {
is(remoteTabsButton.getAttribute("label"), gSync.syncStrings.GetStringFromName("syncing2.label"), "label is set to the right value");
} else {
is(remoteTabsButton.getAttribute("label"), gSync.syncStrings.GetStringFromName("syncnow.label"), "label is set to the right value");
}
}
// Only one item visible at a time.
function checkItemsVisiblities(itemsIds, expectedShownItemId) {
for (let id of itemsIds) {
if (id == expectedShownItemId) {
ok(!document.getElementById(id).hidden, "menuitem " + id + " should be visible");
} else {
ok(document.getElementById(id).hidden, "menuitem " + id + " should be hidden");
}
}
}
function promiseObserver(topic) {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
Services.obs.removeObserver(obs, aTopic);
resolve(aSubject);
}
Services.obs.addObserver(obs, topic);
});
}

View File

@ -24,7 +24,7 @@
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
<script type="application/javascript" src="chrome://browser/content/web-panels.js"/>

View File

@ -24,7 +24,7 @@
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
<script type="application/javascript" src="chrome://browser/content/webext-panels.js"/>

View File

@ -73,6 +73,7 @@ browser.jar:
content/browser/browser-feeds.js (content/browser-feeds.js)
content/browser/browser-fullScreenAndPointerLock.js (content/browser-fullScreenAndPointerLock.js)
content/browser/browser-fullZoom.js (content/browser-fullZoom.js)
content/browser/browser-fxaccounts.js (content/browser-fxaccounts.js)
content/browser/browser-gestureSupport.js (content/browser-gestureSupport.js)
content/browser/browser-media.js (content/browser-media.js)
content/browser/browser-places.js (content/browser-places.js)
@ -81,7 +82,7 @@ browser.jar:
content/browser/browser-safebrowsing.js (content/browser-safebrowsing.js)
content/browser/browser-sidebar.js (content/browser-sidebar.js)
content/browser/browser-social.js (content/browser-social.js)
content/browser/browser-sync.js (content/browser-sync.js)
content/browser/browser-syncui.js (content/browser-syncui.js)
* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
#ifdef CAN_DRAW_IN_TITLEBAR
content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar.js)

View File

@ -29,7 +29,6 @@ BROWSER_CHROME_MANIFESTS += [
'content/test/siteIdentity/browser.ini',
'content/test/social/browser.ini',
'content/test/static/browser.ini',
'content/test/sync/browser.ini',
'content/test/tabcrashed/browser.ini',
'content/test/tabPrompts/browser.ini',
'content/test/tabs/browser.ini',

View File

@ -472,7 +472,7 @@ const CustomizableWidgets = [
clientItem.setAttribute("itemtype", "client");
let window = doc.defaultView;
clientItem.setAttribute("tooltiptext",
window.gSync.formatLastSyncDate(new Date(client.lastModified)));
window.gSyncUI.formatLastSyncDate(new Date(client.lastModified)));
clientItem.textContent = client.name;
attachFragment.appendChild(clientItem);

View File

@ -31,21 +31,19 @@
hidden="true"/>
<hbox id="PanelUI-footer-fxa">
<hbox id="PanelUI-fxa-status"
label="&fxaSignedIn.tooltip;"
defaultlabel="&fxaSignIn.label;"
signedinTooltiptext="&fxaSignedIn.tooltip;"
tooltiptext="&fxaSignedIn.tooltip;"
errorlabel="&fxaSignInError.label;"
unverifiedlabel="&fxaUnverified.label;"
onclick="if (event.which == 1) gSync.onMenuPanelCommand();">
onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
<image id="PanelUI-fxa-avatar"/>
<toolbarbutton id="PanelUI-fxa-label"
label="&fxaSignIn.label;"
fxabrandname="&syncBrand.fxAccount.label;"/>
</hbox>
<toolbarseparator/>
<toolbarbutton id="PanelUI-fxa-icon"
oncommand="gSync.doSync();"
oncommand="gSyncUI.doSync();"
closemenu="none">
<observes element="sync-status" attribute="syncstatus"/>
<observes element="sync-status" attribute="tooltiptext"/>
@ -127,11 +125,11 @@
<toolbarbutton id="PanelUI-remotetabs-view-managedevices"
class="subviewbutton"
label="&appMenuRemoteTabs.managedevices.label;"
oncommand="gSync.openDevicesManagementPage('syncedtabs-menupanel');"/>
oncommand="gFxAccounts.openDevicesManagementPage('syncedtabs-menupanel');"/>
<toolbarbutton id="PanelUI-remotetabs-syncnow"
observes="sync-status"
class="subviewbutton"
oncommand="gSync.doSync();"
oncommand="gSyncUI.doSync();"
closemenu="none"/>
<menuseparator id="PanelUI-remotetabs-separator"/>
</vbox>
@ -156,7 +154,7 @@
<hbox pack="center">
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.openprefs.label;"
oncommand="gSync.openPrefs('synced-tabs');"/>
oncommand="gSyncUI.openPrefs('synced-tabs');"/>
</hbox>
</vbox>
</hbox>
@ -190,7 +188,7 @@
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSync.openPrefs('synced-tabs');"/>
oncommand="gSyncUI.openPrefs('synced-tabs');"/>
</vbox>
<!-- When Sync needs re-authentication. This uses the exact same messaging
as "Sync is not configured" but remains a separate box so we get
@ -204,7 +202,7 @@
<label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
<toolbarbutton class="PanelUI-remotetabs-prefs-button"
label="&appMenuRemoteTabs.signin.label;"
oncommand="gSync.openPrefs('synced-tabs');"/>
oncommand="gSyncUI.openPrefs('synced-tabs');"/>
</vbox>
</hbox>
</vbox>

View File

@ -122,6 +122,7 @@ skip-if = os == "linux"
[browser_985815_propagate_setToolbarVisibility.js]
[browser_987177_destroyWidget_xul.js]
[browser_987177_xul_wrapper_updating.js]
[browser_987185_syncButton.js]
[browser_987492_window_api.js]
[browser_987640_charEncoding.js]
[browser_988072_sidebar_events.js]
@ -156,4 +157,3 @@ skip-if = os == "mac"
[browser_check_tooltips_in_navbar.js]
[browser_editcontrols_update.js]
subsuite = clipboard
[browser_remote_tabs_button.js]

View File

@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
"use strict";
var syncService = {};
Components.utils.import("resource://services-sync/service.js", syncService);
var needsSetup;
var originalSync;
var service = syncService.Service;
var syncWasCalled = false;
add_task(function* testSyncButtonFunctionality() {
info("Check Sync button functionality");
storeInitialValues();
mockFunctions();
// add the Sync button to the panel
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
// check the button's functionality
yield PanelUI.show();
info("The panel menu was opened");
let syncButton = document.getElementById("sync-button");
ok(syncButton, "The Sync button was added to the Panel Menu");
// click the button - the panel should open.
syncButton.click();
let syncPanel = document.getElementById("PanelUI-remotetabs");
ok(syncPanel.getAttribute("current"), "Sync Panel is in view");
// Find and click the "setup" button.
let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
syncNowButton.click();
info("The sync button was clicked");
yield waitForCondition(() => syncWasCalled);
});
add_task(function* asyncCleanup() {
// reset the panel UI to the default state
yield resetCustomization();
ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
if (isPanelUIOpen()) {
let panelHidePromise = promisePanelHidden(window);
PanelUI.hide();
yield panelHidePromise;
}
restoreValues();
});
function mockFunctions() {
// mock needsSetup
gSyncUI._needsSetup = () => Promise.resolve(false);
// mock service.errorHandler.syncAndReportErrors()
service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
}
function mocked_syncAndReportErrors() {
syncWasCalled = true;
}
function restoreValues() {
gSyncUI._needsSetup = needsSetup;
service.sync = originalSync;
}
function storeInitialValues() {
needsSetup = gSyncUI._needsSetup;
originalSync = service.sync;
}

View File

@ -1,84 +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/.
*/
"use strict";
let syncService = {};
Components.utils.import("resource://services-sync/service.js", syncService);
const service = syncService.Service;
Components.utils.import("resource://services-sync/UIState.jsm");
let getState;
let originalSync;
let syncWasCalled = false;
// TODO: This test should probably be re-written, we don't really test much here.
add_task(async function testSyncRemoteTabsButtonFunctionality() {
info("Test the Sync Remote Tabs button in the PanelUI");
storeInitialValues();
mockFunctions();
// Force UI update.
Services.obs.notifyObservers(null, UIState.ON_UPDATE);
// add the sync remote tabs button to the panel
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
// check the button's functionality
await PanelUI.show();
info("The panel menu was opened");
let syncRemoteTabsBtn = document.getElementById("sync-button");
ok(syncRemoteTabsBtn, "The sync remote tabs button was added to the Panel Menu");
// click the button - the panel should open.
syncRemoteTabsBtn.click();
let remoteTabsPanel = document.getElementById("PanelUI-remotetabs");
ok(remoteTabsPanel.getAttribute("current"), "Sync Panel is in view");
// Find and click the "setup" button.
let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
syncNowButton.click();
info("The sync now button was clicked");
await waitForCondition(() => syncWasCalled);
});
add_task(async function asyncCleanup() {
// reset the panel UI to the default state
await resetCustomization();
ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
if (isPanelUIOpen()) {
let panelHidePromise = promisePanelHidden(window);
PanelUI.hide();
await panelHidePromise;
}
restoreValues();
});
function mockFunctions() {
// mock UIState.get()
UIState.get = () => ({
status: UIState.STATUS_SIGNED_IN,
email: "user@mozilla.com"
});
// mock service.errorHandler.syncAndReportErrors()
service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
}
function mocked_syncAndReportErrors() {
syncWasCalled = true;
}
function restoreValues() {
UIState.get = getState;
service.syncAndReportErrors = originalSync;
}
function storeInitialValues() {
getState = UIState.get;
originalSync = service.syncAndReportErrors;
}

View File

@ -229,11 +229,11 @@ add_task(function* () {
let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
let didSync = false;
let oldDoSync = gSync.doSync;
gSync.doSync = function() {
let oldDoSync = gSyncUI.doSync;
gSyncUI.doSync = function() {
didSync = true;
mockedInternal.hasSyncedThisSession = true;
gSync.doSync = oldDoSync;
gSyncUI.doSync = oldDoSync;
}
syncNowButton.click();
ok(didSync, "clicking the button called the correct function");

View File

@ -122,7 +122,7 @@ SyncedTabsDeckComponent.prototype = {
getPanelStatus() {
return this._accountStatus().then(exists => {
if (!exists || this._SyncedTabs.loginFailed) {
if (!exists || this._getChromeWindow(this._window).gSyncUI.loginFailed()) {
return this.PANELS.NOT_AUTHED_INFO;
}
if (!this._SyncedTabs.isConfiguredToSyncTabs) {
@ -166,7 +166,7 @@ SyncedTabsDeckComponent.prototype = {
},
openSyncPrefs() {
this._getChromeWindow(this._window).gSync.openPrefs("tabs-sidebar");
this._getChromeWindow(this._window).gSyncUI.openPrefs("tabs-sidebar");
}
};

View File

@ -213,7 +213,7 @@ TabListView.prototype = {
_updateClient(item, itemNode) {
itemNode.setAttribute("id", "item-" + item.id);
let lastSync = new Date(item.lastModified);
let lastSyncTitle = getChromeWindow(this._window).gSync.formatLastSyncDate(lastSync);
let lastSyncTitle = getChromeWindow(this._window).gSyncUI.formatLastSyncDate(lastSync);
itemNode.setAttribute("title", lastSyncTitle);
if (item.closed) {
itemNode.classList.add("closed");

View File

@ -140,6 +140,16 @@ add_task(function* testPanelStatus() {
let SyncedTabsMock = {
getTabClients() {}
};
let loginFailed = false;
let chromeWindowMock = {
gSyncUI: {
loginFailed() {
return loginFailed;
}
}
};
let getChromeWindowMock = sinon.stub();
getChromeWindowMock.returns(chromeWindowMock);
sinon.stub(listStore, "getData");
@ -148,7 +158,8 @@ add_task(function* testPanelStatus() {
fxAccounts,
deckStore,
listComponent,
SyncedTabs: SyncedTabsMock
SyncedTabs: SyncedTabsMock,
getChromeWindowMock
});
let isAuthed = false;
@ -158,10 +169,10 @@ add_task(function* testPanelStatus() {
isAuthed = true;
SyncedTabsMock.loginFailed = true;
loginFailed = true;
result = yield component.getPanelStatus();
Assert.equal(result, component.PANELS.NOT_AUTHED_INFO);
SyncedTabsMock.loginFailed = false;
loginFailed = false;
SyncedTabsMock.isConfiguredToSyncTabs = false;
result = yield component.getPanelStatus();
@ -200,12 +211,12 @@ add_task(function* testActions() {
openUILink() {},
};
let chromeWindowMock = {
gSync: {
gSyncUI: {
openPrefs() {}
}
};
sinon.spy(windowMock, "openUILink");
sinon.spy(chromeWindowMock.gSync, "openPrefs");
sinon.spy(chromeWindowMock.gSyncUI, "openPrefs");
let getChromeWindowMock = sinon.stub();
getChromeWindowMock.returns(chromeWindowMock);
@ -225,5 +236,5 @@ add_task(function* testActions() {
component.openSyncPrefs();
Assert.ok(getChromeWindowMock.calledWith(windowMock));
Assert.ok(chromeWindowMock.gSync.openPrefs.called);
Assert.ok(chromeWindowMock.gSyncUI.openPrefs.called);
});

View File

@ -18,7 +18,7 @@ function test() {
registerCleanupFunction(function*() {
yield signOut();
gSync.updateUI();
gFxAccounts.updateUI();
});
var tests = [
@ -35,7 +35,7 @@ var tests = [
yield setSignedInUser();
let userData = yield fxAccounts.getSignedInUser();
isnot(userData, null, "Logged in now");
gSync.updateUI(); // Causes a leak (see bug 1332985)
gFxAccounts.updateUI(); // Causes a leak (see bug 1332985)
yield showMenuPromise("appMenu");
yield showHighlightPromise("accountStatus");
let highlight = document.getElementById("UITourHighlightContainer");

View File

@ -575,8 +575,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
display: none;
}
#PanelUI-footer-fxa[fxastatus="login-failed"] > #PanelUI-fxa-status::after,
#PanelUI-footer-fxa[fxastatus="unverified"] > #PanelUI-fxa-status::after {
#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status::after {
content: url(chrome://browser/skin/warning.svg);
filter: drop-shadow(0 1px 0 hsla(206,50%,10%,.15));
width: 47px;
@ -955,19 +954,16 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
outline: none;
}
#PanelUI-footer-fxa[fxastatus="login-failed"],
#PanelUI-footer-fxa[fxastatus="unverified"] {
#PanelUI-footer-fxa[fxastatus="error"] {
background-color: hsl(42,94%,88%);
border-top: 1px solid hsl(42,94%,70%);
}
#PanelUI-footer-fxa[fxastatus="login-failed"] > #PanelUI-fxa-status:hover,
#PanelUI-footer-fxa[fxastatus="unverified"] > #PanelUI-fxa-status:hover {
#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover {
background-color: hsl(42,94%,85%);
}
#PanelUI-footer-fxa[fxastatus="login-failed"] > #PanelUI-fxa-status:hover:active,
#PanelUI-footer-fxa[fxastatus="unverified"] > #PanelUI-fxa-status:hover:active {
#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover:active {
background-color: hsl(42,94%,82%);
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
}

View File

@ -85,9 +85,7 @@ this.FxAccountsWebChannel = function(options) {
this._webChannelId = options.channel_id;
// options.helpers is only specified by tests.
XPCOMUtils.defineLazyGetter(this, "_helpers", () => {
return options.helpers || new FxAccountsWebChannelHelpers(options);
});
this._helpers = options.helpers || new FxAccountsWebChannelHelpers(options);
this._setupChannel();
};

View File

@ -213,14 +213,6 @@ let SyncedTabsInternal = {
}
},
get loginFailed() {
if (!weaveXPCService.ready) {
log.debug("Sync isn't yet ready; assuming the login didn't fail");
return false;
}
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
// Returns true if Sync is configured to Sync tabs, false otherwise
get isConfiguredToSyncTabs() {
if (!weaveXPCService.ready) {

View File

@ -1,263 +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/. */
"use strict";
/**
* @typedef {Object} UIState
* @property {string} status The Sync/FxA status, see STATUS_* constants.
* @property {string} [email] The FxA email configured to log-in with Sync.
* @property {string} [displayName] The user's FxA display name.
* @property {string} [avatarURL] The user's FxA avatar URL.
* @property {Date} [lastSync] The last sync time.
* @property {boolean} [syncing] Whether or not we are currently syncing.
*/
this.EXPORTED_SYMBOLS = ["UIState"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
"resource://services-sync/main.js");
const TOPICS = [
"weave:service:login:change",
"weave:service:login:error",
"weave:service:ready",
"weave:service:sync:start",
"weave:service:sync:finish",
"weave:service:sync:error",
"fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive.
"fxaccounts:onlogout",
"fxaccounts:profilechange",
];
const ON_UPDATE = "sync-ui-state:update"
const STATUS_NOT_CONFIGURED = "not_configured";
const STATUS_LOGIN_FAILED = "login_failed";
const STATUS_NOT_VERIFIED = "not_verified";
const STATUS_SIGNED_IN = "signed_in";
const DEFAULT_STATE = {
status: STATUS_NOT_CONFIGURED
};
const UIStateInternal = {
_initialized: false,
_state: null,
// We keep _syncing out of the state object because we can only track it
// using sync events and we can't determine it at any point in time.
_syncing: false,
get state() {
if (!this._state) {
return DEFAULT_STATE;
}
return Object.assign({}, this._state, { syncing: this._syncing });
},
isReady() {
if (!this._initialized) {
this.init();
return false;
}
return true;
},
init() {
this._initialized = true;
// Refresh the state in the background.
this.refreshState().catch(e => {
Cu.reportError(e);
});
},
// Used for testing.
reset() {
this._state = null;
this._syncing = false;
this._initialized = false;
},
observe(subject, topic, data) {
switch (topic) {
case "weave:service:sync:start":
this.toggleSyncActivity(true);
break;
case "weave:service:sync:finish":
case "weave:service:sync:error":
this.toggleSyncActivity(false);
break;
default:
this.refreshState().catch(e => {
Cu.reportError(e);
});
break;
}
},
// Builds a new state from scratch.
async refreshState() {
this._state = {};
await this._refreshFxAState();
this._setLastSyncTime(this._state); // We want this in case we change accounts.
this.notifyStateUpdated();
return this.state;
},
// Update the current state with the last sync time/currently syncing status.
toggleSyncActivity(syncing) {
this._syncing = syncing;
this._setLastSyncTime(this._state);
this.notifyStateUpdated();
},
notifyStateUpdated() {
Services.obs.notifyObservers(null, ON_UPDATE);
},
async _refreshFxAState() {
let userData = await this._getUserData();
this._populateWithUserData(this._state, userData);
if (this.state.status != STATUS_SIGNED_IN) {
return;
}
let profile = await this._getProfile();
if (!profile) {
return;
}
this._populateWithProfile(this._state, profile);
},
_populateWithUserData(state, userData) {
let status;
if (!userData) {
status = STATUS_NOT_CONFIGURED;
} else {
if (this._loginFailed()) {
status = STATUS_LOGIN_FAILED;
} else if (!userData.verified) {
status = STATUS_NOT_VERIFIED;
} else {
status = STATUS_SIGNED_IN;
}
state.email = userData.email;
}
state.status = status;
},
_populateWithProfile(state, profile) {
state.displayName = profile.displayName;
state.avatarURL = profile.avatar;
},
async _getUserData() {
try {
return await this.fxAccounts.getSignedInUser();
} catch (e) {
// This is most likely in tests, where we quickly log users in and out.
// The most likely scenario is a user logged out, so reflect that.
// Bug 995134 calls for better errors so we could retry if we were
// sure this was the failure reason.
Cu.reportError("Error updating FxA account info: " + e);
return null;
}
},
async _getProfile() {
try {
return await this.fxAccounts.getSignedInUserProfile();
} catch (e) {
// Not fetching the profile is sad but the FxA logs will already have noise.
return null;
}
},
_setLastSyncTime(state) {
if (state.status == UIState.STATUS_SIGNED_IN) {
try {
state.lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync", null));
} catch (_) {
state.lastSync = null;
}
}
},
_loginFailed() {
// Referencing Weave.Service will implicitly initialize sync, and we don't
// want to force that - so first check if it is ready.
let service = Cc["@mozilla.org/weave/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
if (!service.ready) {
return false;
}
// LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
// All other login failures are assumed to be transient and should go
// away by themselves, so aren't reflected here.
return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
},
set fxAccounts(mockFxAccounts) {
delete this.fxAccounts;
this.fxAccounts = mockFxAccounts;
}
};
XPCOMUtils.defineLazyModuleGetter(UIStateInternal, "fxAccounts",
"resource://gre/modules/FxAccounts.jsm");
for (let topic of TOPICS) {
Services.obs.addObserver(UIStateInternal, topic);
}
this.UIState = {
_internal: UIStateInternal,
ON_UPDATE,
STATUS_NOT_CONFIGURED,
STATUS_LOGIN_FAILED,
STATUS_NOT_VERIFIED,
STATUS_SIGNED_IN,
/**
* Returns true if the module has been initialized and the state set.
* If not, return false and trigger an init in the background.
*/
isReady() {
return this._internal.isReady();
},
/**
* @returns {UIState} The current Sync/FxA UI State.
*/
get() {
return this._internal.state;
},
/**
* Refresh the state. Used for testing, don't call this directly since
* UIState already listens to Sync/FxA notifications to determine if the state
* needs to be refreshed. ON_UPDATE will be fired once the state is refreshed.
*
* @returns {Promise<UIState>} Resolved once the state is refreshed.
*/
refresh() {
return this._internal.refreshState();
},
/**
* Reset the state of the whole module. Used for testing.
*/
reset() {
this._internal.reset();
}
};

View File

@ -36,7 +36,6 @@ EXTRA_JS_MODULES['services-sync'] += [
'modules/status.js',
'modules/SyncedTabs.jsm',
'modules/telemetry.js',
'modules/UIState.jsm',
'modules/util.js',
]

View File

@ -1,329 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// ================================================
// Load mocking/stubbing library, sinon
// docs: http://sinonjs.org/docs/
/* global sinon */
Cu.import("resource://gre/modules/Timer.jsm");
let window = {
document: {},
location: {},
setTimeout,
setInterval,
clearTimeout,
clearInterval,
};
let self = window;
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
// ================================================
Cu.import("resource://services-sync/UIState.jsm");
const UIStateInternal = UIState._internal;
add_task(async function test_isReady() {
UIState.reset();
let refreshState = sinon.spy(UIStateInternal, "refreshState");
// On the first call, returns false and triggers a refresh of the state
ok(!UIState.isReady());
ok(refreshState.calledOnce);
refreshState.reset();
// On subsequent calls, only return true
ok(UIState.isReady());
ok(!refreshState.called);
refreshState.restore();
});
add_task(async function test_refreshState_signedin() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
const now = new Date().toString();
Services.prefs.setCharPref("services.sync.lastSync", now);
UIStateInternal.syncing = false;
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: () => Promise.resolve({ displayName: "Foo Bar", avatar: "https://foo/bar" })
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_SIGNED_IN);
equal(state.email, "foo@bar.com");
equal(state.displayName, "Foo Bar");
equal(state.avatarURL, "https://foo/bar");
equal(state.lastSync, now);
equal(state.syncing, false);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_signedin_profile_cached() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
const now = new Date().toString();
Services.prefs.setCharPref("services.sync.lastSync", now);
UIStateInternal.syncing = false;
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: () => Promise.resolve({ displayName: "Foo Bar", avatar: "https://foo/bar" })
}
await UIState.refresh();
// The profile should be cached at this point.
let getProfileSpy = sinon.spy();
UIStateInternal.fxAccounts.getSignedInUserProfile = getProfileSpy;
await UIState.refresh();
ok(!getProfileSpy.called);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_signedin_profile_unavailable() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
const now = new Date().toString();
Services.prefs.setCharPref("services.sync.lastSync", now);
UIStateInternal.syncing = false;
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: () => Promise.reject(new Error("Profile unavailable"))
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_SIGNED_IN);
equal(state.email, "foo@bar.com");
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, now);
equal(state.syncing, false);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_unconfigured() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
let getSignedInUserProfile = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve(null),
getSignedInUserProfile: getSignedInUserProfile
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_NOT_CONFIGURED);
equal(state.email, undefined);
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, undefined);
ok(!getSignedInUserProfile.called);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_unverified() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
let getSignedInUserProfile = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: false, email: "foo@bar.com" }),
getSignedInUserProfile: getSignedInUserProfile
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_NOT_VERIFIED);
equal(state.email, "foo@bar.com");
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, undefined);
ok(!getSignedInUserProfile.called);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_refreshState_loginFailed() {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
let loginFailed = sinon.stub(UIStateInternal, "_loginFailed");
loginFailed.returns(true);
let getSignedInUserProfile = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: getSignedInUserProfile
}
let state = await UIState.refresh();
equal(state.status, UIState.STATUS_LOGIN_FAILED);
equal(state.email, "foo@bar.com");
equal(state.displayName, undefined);
equal(state.avatarURL, undefined);
equal(state.lastSync, undefined);
ok(!getSignedInUserProfile.called);
loginFailed.restore();
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_observer_refreshState() {
let refreshState = sinon.spy(UIStateInternal, "refreshState");
let shouldRefresh = ["weave:service:login:change", "weave:service:login:error",
"weave:service:ready", "fxaccounts:onlogin",
"fxaccounts:onlogout", "fxaccounts:profilechange"];
for (let topic of shouldRefresh) {
let uiUpdateObserved = observeUIUpdate();
Services.obs.notifyObservers(null, topic);
await uiUpdateObserved;
ok(refreshState.calledOnce);
refreshState.reset();
}
refreshState.restore();
});
// Drive the UIState in a configured state.
async function configureUIState(syncing, lastSync = new Date()) {
UIState.reset();
const fxAccountsOrig = UIStateInternal.fxAccounts;
UIStateInternal._syncing = syncing;
Services.prefs.setCharPref("services.sync.lastSync", lastSync.toString());
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: () => Promise.resolve({ displayName: "Foo Bar", avatar: "https://foo/bar" })
}
await UIState.refresh();
UIStateInternal.fxAccounts = fxAccountsOrig;
}
add_task(async function test_syncStarted() {
await configureUIState(false);
const oldState = Object.assign({}, UIState.get());
ok(!oldState.syncing);
let uiUpdateObserved = observeUIUpdate();
Services.obs.notifyObservers(null, "weave:service:sync:start");
await uiUpdateObserved;
const newState = Object.assign({}, UIState.get());
ok(newState.syncing);
});
add_task(async function test_syncFinished() {
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
await configureUIState(true, yesterday);
const oldState = Object.assign({}, UIState.get());
ok(oldState.syncing);
let uiUpdateObserved = observeUIUpdate();
Services.prefs.setCharPref("services.sync.lastSync", new Date().toString());
Services.obs.notifyObservers(null, "weave:service:sync:finish");
await uiUpdateObserved;
const newState = Object.assign({}, UIState.get());
ok(!newState.syncing);
ok(new Date(newState.lastSync) > new Date(oldState.lastSync));
});
add_task(async function test_syncError() {
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
await configureUIState(true, yesterday);
const oldState = Object.assign({}, UIState.get());
ok(oldState.syncing);
let uiUpdateObserved = observeUIUpdate();
Services.obs.notifyObservers(null, "weave:service:sync:error");
await uiUpdateObserved;
const newState = Object.assign({}, UIState.get());
ok(!newState.syncing);
equal(newState.lastSync, oldState.lastSync);
});
add_task(async function test_profile_change_notification_clears_profile_cache() {
await configureUIState(true);
const fxAccountsOrig = UIStateInternal.fxAccounts;
let getProfileSpy = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: getProfileSpy
}
// Sanity check first.
await UIState.refresh();
ok(!getProfileSpy.called);
Services.obs.notifyObservers(null, "fxaccounts:profilechange");
await UIState.refresh();
ok(getProfileSpy.called);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
add_task(async function test_fxa_logout_notification_clears_profile_cache() {
await configureUIState(true);
const fxAccountsOrig = UIStateInternal.fxAccounts;
let getProfileSpy = sinon.spy();
UIStateInternal.fxAccounts = {
getSignedInUser: () => Promise.resolve({ verified: true, email: "foo@bar.com" }),
getSignedInUserProfile: getProfileSpy
}
// Sanity check first.
await UIState.refresh();
ok(!getProfileSpy.called);
Services.obs.notifyObservers(null, "fxaccounts:onlogout");
await UIState.refresh();
ok(getProfileSpy.called);
UIStateInternal.fxAccounts = fxAccountsOrig;
});
function observeUIUpdate() {
return new Promise(resolve => {
let obs = (aSubject, aTopic, aData) => {
Services.obs.removeObserver(obs, aTopic);
const state = UIState.get();
resolve(state);
}
Services.obs.addObserver(obs, UIState.ON_UPDATE);
});
}

View File

@ -184,5 +184,3 @@ support-files = prefs_test_prefs_store.js
[test_telemetry.js]
requesttimeoutfactor = 4
[test_uistate.js]