mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
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:
parent
4f4ee59962
commit
18253e3fa2
@ -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;"
|
||||
|
398
browser/base/content/browser-fxaccounts.js
Normal file
398
browser/base/content/browser-fxaccounts.js
Normal 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;
|
||||
});
|
@ -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;"
|
||||
|
@ -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;
|
||||
});
|
498
browser/base/content/browser-syncui.js
Normal file
498
browser/base/content/browser-syncui.js
Normal 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;
|
||||
});
|
@ -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) {
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"):
|
||||
|
@ -601,7 +601,7 @@ nsContextMenu.prototype = {
|
||||
},
|
||||
|
||||
initSyncItems() {
|
||||
gSync.initPageContextMenu(this);
|
||||
gFxAccounts.initPageContextMenu(this);
|
||||
},
|
||||
|
||||
openPasswordManager() {
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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 = [
|
258
browser/base/content/test/general/browser_fxaccounts.js
Normal file
258
browser/base/content/test/general/browser_fxaccounts.js
Normal 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;
|
||||
});
|
205
browser/base/content/test/general/browser_syncui.js
Normal file
205
browser/base/content/test/general/browser_syncui.js
Normal 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();
|
||||
}
|
||||
});
|
@ -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.
|
||||
|
34
browser/base/content/test/general/fxa_profile_handler.sjs
Normal file
34
browser/base/content/test/general/fxa_profile_handler.sjs
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"plugin:mozilla/browser-test"
|
||||
]
|
||||
};
|
@ -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
|
@ -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);
|
||||
});
|
||||
}
|
@ -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"/>
|
||||
|
||||
|
@ -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"/>
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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");
|
||||
|
@ -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");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
@ -36,7 +36,6 @@ EXTRA_JS_MODULES['services-sync'] += [
|
||||
'modules/status.js',
|
||||
'modules/SyncedTabs.jsm',
|
||||
'modules/telemetry.js',
|
||||
'modules/UIState.jsm',
|
||||
'modules/util.js',
|
||||
]
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
@ -184,5 +184,3 @@ support-files = prefs_test_prefs_store.js
|
||||
|
||||
[test_telemetry.js]
|
||||
requesttimeoutfactor = 4
|
||||
|
||||
[test_uistate.js]
|
||||
|
Loading…
Reference in New Issue
Block a user