diff --git a/browser/base/content/browser-fxaccounts.js b/browser/base/content/browser-fxaccounts.js index 292e2fdbca42..0c827d87d90b 100644 --- a/browser/base/content/browser-fxaccounts.js +++ b/browser/base/content/browser-fxaccounts.js @@ -36,12 +36,28 @@ let gFxAccounts = { this.FxAccountsCommon.ONVERIFIED_NOTIFICATION, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION, "weave:notification:removed", + this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION, ]; }, - get button() { - delete this.button; - return this.button = document.getElementById("PanelUI-fxa-status"); + get panelUIFooter() { + return document.getElementById("PanelUI-footer-fxa"); + }, + + get panelUIStatus() { + return document.getElementById("PanelUI-fxa-status"); + }, + + get panelUIAvatar() { + return document.getElementById("PanelUI-fxa-avatar"); + }, + + get panelUILabel() { + return document.getElementById("PanelUI-fxa-label"); + }, + + get panelUIIcon() { + return document.getElementById("PanelUI-fxa-icon"); }, get strings() { @@ -135,6 +151,9 @@ let gFxAccounts = { this.fxaMigrator.recordTelemetry(this.fxaMigrator.TELEMETRY_DECLINED); } break; + case this.FxAccountsCommon.ONPROFILE_IMAGE_CHANGE_NOTIFICATION: + this.updateUI(); + break; default: this.updateUI(); break; @@ -211,59 +230,96 @@ let gFxAccounts = { return; } + let profileInfoEnabled = false; + try { + profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled"); + } catch (e) { } + // Bail out if FxA is disabled. if (!this.weave.fxAccountsEnabled) { // When migration transitions from needs-verification to the null state, // fxAccountsEnabled is false because migration has not yet finished. In // that case, hide the button. We'll get another notification with a null // state once migration is complete. - this.button.hidden = true; - this.button.removeAttribute("fxastatus"); + this.panelUIFooter.removeAttribute("fxastatus"); return; } - // FxA is enabled, show the widget. - this.button.hidden = false; - // Make sure the button is disabled in customization mode. if (this._inCustomizationMode) { - this.button.setAttribute("disabled", "true"); + this.panelUILabel.setAttribute("disabled", "true"); + this.panelUIAvatar.setAttribute("disabled", "true"); + this.panelUIIcon.setAttribute("disabled", "true"); } else { - this.button.removeAttribute("disabled"); + this.panelUILabel.removeAttribute("disabled"); + this.panelUIAvatar.removeAttribute("disabled"); + this.panelUIIcon.removeAttribute("disabled"); } - let defaultLabel = this.button.getAttribute("defaultlabel"); - let errorLabel = this.button.getAttribute("errorlabel"); + let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel"); + let errorLabel = this.panelUIStatus.getAttribute("errorlabel"); + let signedInTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext"); // If the user is signed into their Firefox account and we are not // currently in customization mode, show their email address. - let doUpdate = userData => { + let doUpdate = (profile, userData) => { + // Reset the button to its original state. - this.button.setAttribute("label", defaultLabel); - this.button.removeAttribute("tooltiptext"); - this.button.removeAttribute("fxastatus"); + this.panelUILabel.setAttribute("label", defaultLabel); + this.panelUILabel.removeAttribute("tooltiptext"); + this.panelUIAvatar.removeAttribute("tooltiptext"); + this.panelUIFooter.removeAttribute("fxastatus"); + this.panelUIFooter.removeAttribute("fxaprofileimage"); + this.panelUIAvatar.style.removeProperty("background-image"); if (!this._inCustomizationMode) { if (this.loginFailed) { let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [userData.email], 1); - this.button.setAttribute("fxastatus", "error"); - this.button.setAttribute("label", errorLabel); - this.button.setAttribute("tooltiptext", tooltipDescription); - } else if (userData) { - this.button.setAttribute("fxastatus", "signedin"); - this.button.setAttribute("label", userData.email); - this.button.setAttribute("tooltiptext", userData.email); + this.panelUIFooter.setAttribute("fxastatus", "error"); + this.panelUILabel.setAttribute("label", errorLabel); + this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription); + this.panelUIAvatar.setAttribute("tooltiptext", tooltipDescription); + } else { + let label = profile && profile.displayName ? profile.displayName : userData.email; + this.panelUIFooter.setAttribute("fxastatus", "signedin"); + this.panelUILabel.setAttribute("label", label); + this.panelUIStatus.setAttribute("tooltiptext", signedInTooltiptext); + this.panelUIAvatar.setAttribute("tooltiptext", signedInTooltiptext); + } + if (profileInfoEnabled) { + this.panelUIFooter.setAttribute("fxaprofileimage", "enabled"); + if (profile && profile.avatar) { + let img = new Image(); + // Make sure the image is available before attempting to display it + img.onload = () => { + this.panelUIFooter.setAttribute("fxaprofileimage", "set"); + this.panelUIAvatar.style.backgroundImage = "url('" + profile.avatar + "')"; + }; + img.src = profile.avatar; + } } } } - fxAccounts.getSignedInUser().then(userData => { - doUpdate(userData); - }).then(null, error => { + let userData, profile; + // Calling getSignedInUserProfile() without a user logged in causes log + // noise that looks like an actual error... + fxAccounts.getSignedInUser().then(data => { + userData = data; + if (!userData) { + return null; + } + return fxAccounts.getSignedInUserProfile(); + }).then(data => { + profile = data; + // profile may be null here, but that's expected. + doUpdate(profile, userData); + }).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. - doUpdate(null); + this.FxAccountsCommon.log.error("Error updating FxA profile", error); + doUpdate(profile, userData); }); }, @@ -274,7 +330,7 @@ let gFxAccounts = { case this.fxaMigrator.STATE_USER_FXA: status = "migrate-signup"; label = this.strings.formatStringFromName("needUserShort", - [this.button.getAttribute("fxabrandname")], 1); + [this.panelUILabel.getAttribute("fxabrandname")], 1); break; case this.fxaMigrator.STATE_USER_FXA_VERIFIED: status = "migrate-verify"; @@ -283,9 +339,8 @@ let gFxAccounts = { 1); break; } - this.button.label = label; - this.button.hidden = false; - this.button.setAttribute("fxastatus", status); + this.panelUILabel.label = label; + this.panelUIFooter.setAttribute("fxastatus", status); }), updateMigrationNotification: Task.async(function* () { @@ -352,10 +407,9 @@ let gFxAccounts = { Weave.Notifications.replaceTitle(note); }), - onMenuPanelCommand: function (event) { - let button = event.originalTarget; + onMenuPanelCommand: function () { - switch (button.getAttribute("fxastatus")) { + switch (this.panelUIFooter.getAttribute("fxastatus")) { case "signedin": this.openPreferences(); break; diff --git a/browser/base/content/browser-syncui.js b/browser/base/content/browser-syncui.js index aa97ac41a876..062de3267e93 100644 --- a/browser/base/content/browser-syncui.js +++ b/browser/base/content/browser-syncui.js @@ -166,8 +166,15 @@ let gSyncUI = { return; let syncButton = document.getElementById("sync-button"); - if (needsSetup && syncButton) - syncButton.removeAttribute("tooltiptext"); + let statusButton = document.getElementById("PanelUI-fxa-icon"); + if (needsSetup) { + if (syncButton) { + syncButton.removeAttribute("tooltiptext"); + } + if (statusButton) { + statusButton.removeAttribute("tooltiptext"); + } + } this._updateLastSyncTime(); }, @@ -184,9 +191,9 @@ let gSyncUI = { if (button) { button.setAttribute("status", "active"); } - button = document.getElementById("PanelUI-fxa-status"); - if (button) { - button.setAttribute("syncstatus", "active"); + let container = document.getElementById("PanelUI-footer-fxa"); + if (container) { + container.setAttribute("syncstatus", "active"); } } }, @@ -210,9 +217,9 @@ let gSyncUI = { if (syncButton) { syncButton.removeAttribute("status"); } - let panelHorizontalButton = document.getElementById("PanelUI-fxa-status"); - if (panelHorizontalButton) { - panelHorizontalButton.removeAttribute("syncstatus"); + let fxaContainer = document.getElementById("PanelUI-footer-fxa"); + if (fxaContainer) { + fxaContainer.removeAttribute("syncstatus"); } }, @@ -418,8 +425,7 @@ let gSyncUI = { return; let syncButton = document.getElementById("sync-button"); - if (!syncButton) - return; + let statusButton = document.getElementById("PanelUI-fxa-icon"); let lastSync; try { @@ -435,7 +441,12 @@ let gSyncUI = { } catch (e) { }; if (!lastSync || this._needsSetup()) { - syncButton.removeAttribute("tooltiptext"); + if (syncButton) { + syncButton.removeAttribute("tooltiptext"); + } + if (statusButton) { + statusButton.removeAttribute("tooltiptext"); + } return; } @@ -444,7 +455,12 @@ let gSyncUI = { let lastSyncLabel = this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1); - syncButton.setAttribute("tooltiptext", lastSyncLabel); + if (syncButton) { + syncButton.setAttribute("tooltiptext", lastSyncLabel); + } + if (statusButton) { + statusButton.setAttribute("tooltiptext", lastSyncLabel); + } }, clearError: function SUI_clearError(errorString) { diff --git a/browser/base/content/test/general/browser_fxa_migrate.js b/browser/base/content/test/general/browser_fxa_migrate.js index 7612de469115..558b06410ed6 100644 --- a/browser/base/content/test/general/browser_fxa_migrate.js +++ b/browser/base/content/test/general/browser_fxa_migrate.js @@ -9,24 +9,24 @@ Cu.import("resource://services-sync/FxaMigrator.jsm", imports); add_task(function* test() { // Fake the state where we need an FxA user. - let buttonPromise = promiseButtonMutation(); + let fxaPanelUIPromise = promiseButtonMutation(); Services.obs.notifyObservers(null, STATE_CHANGED_TOPIC, imports.fxaMigrator.STATE_USER_FXA); - let buttonState = yield buttonPromise; - assertButtonState(buttonState, "migrate-signup", true); + let buttonState = yield fxaPanelUIPromise; + assertButtonState(buttonState, "migrate-signup"); Assert.ok(Weave.Notifications.notifications.some(n => { return n.title == NOTIFICATION_TITLE; }), "Needs-user notification should be present"); // Fake the state where we need a verified FxA user. - buttonPromise = promiseButtonMutation(); + fxaPanelUIPromise = promiseButtonMutation(); let email = Cc["@mozilla.org/supports-string;1"]. createInstance(Ci.nsISupportsString); email.data = "foo@example.com"; Services.obs.notifyObservers(email, STATE_CHANGED_TOPIC, imports.fxaMigrator.STATE_USER_FXA_VERIFIED); - buttonState = yield buttonPromise; - assertButtonState(buttonState, "migrate-verify", true, + buttonState = yield fxaPanelUIPromise; + assertButtonState(buttonState, "migrate-verify", "foo@example.com not verified"); let note = Weave.Notifications.notifications.find(n => { return n.title == NOTIFICATION_TITLE; @@ -36,23 +36,22 @@ add_task(function* test() { "Needs-verification notification should include email"); // Fake the state where no migration is needed. - buttonPromise = promiseButtonMutation(); + fxaPanelUIPromise = promiseButtonMutation(); Services.obs.notifyObservers(null, STATE_CHANGED_TOPIC, null); - buttonState = yield buttonPromise; + buttonState = yield fxaPanelUIPromise; // In this case, the front end has called fxAccounts.getSignedInUser() to // update the button label and status. But since there isn't actually a user, // the button is left with no fxastatus. - assertButtonState(buttonState, "", true); + assertButtonState(buttonState, ""); Assert.ok(!Weave.Notifications.notifications.some(n => { return n.title == NOTIFICATION_TITLE; }), "Migration notifications should no longer be present"); }); -function assertButtonState(buttonState, expectedStatus, expectedVisible, +function assertButtonState(buttonState, expectedStatus, expectedLabel=undefined) { Assert.equal(buttonState.fxastatus, expectedStatus, "Button fxstatus attribute"); - Assert.equal(!buttonState.hidden, expectedVisible, "Button visibility"); if (expectedLabel !== undefined) { Assert.equal(buttonState.label, expectedLabel, "Button label"); } @@ -66,12 +65,11 @@ function promiseButtonMutation() { if (mutations.some(m => m.attributeName == "fxastatus")) { obs.disconnect(); resolve({ - fxastatus: gFxAccounts.button.getAttribute("fxastatus"), - hidden: gFxAccounts.button.hidden, - label: gFxAccounts.button.label, + fxastatus: gFxAccounts.panelUIFooter.getAttribute("fxastatus"), + label: gFxAccounts.panelUILabel.label, }); } }); - obs.observe(gFxAccounts.button, { attributes: true }); + obs.observe(gFxAccounts.panelUIFooter, { attributes: true }); }); } diff --git a/browser/base/content/test/general/browser_syncui.js b/browser/base/content/test/general/browser_syncui.js index 4c91ff9b6e82..cc8cbdab97ca 100644 --- a/browser/base/content/test/general/browser_syncui.js +++ b/browser/base/content/test/general/browser_syncui.js @@ -255,13 +255,13 @@ add_task(function* testRLLoginErrorRemains() { function checkButtonsStatus(shouldBeActive) { let button = document.getElementById("sync-button"); - let panelbutton = document.getElementById("PanelUI-fxa-status"); + let fxaContainer = document.getElementById("PanelUI-footer-fxa"); if (shouldBeActive) { Assert.equal(button.getAttribute("status"), "active"); - Assert.equal(panelbutton.getAttribute("syncstatus"), "active"); + Assert.equal(fxaContainer.getAttribute("syncstatus"), "active"); } else { Assert.ok(!button.hasAttribute("status")); - Assert.ok(!panelbutton.hasAttribute("syncstatus")); + Assert.ok(!fxaContainer.hasAttribute("syncstatus")); } } diff --git a/browser/components/customizableui/content/panelUI.inc.xul b/browser/components/customizableui/content/panelUI.inc.xul index df5db6de13d6..b33176f04f05 100644 --- a/browser/components/customizableui/content/panelUI.inc.xul +++ b/browser/components/customizableui/content/panelUI.inc.xul @@ -20,12 +20,21 @@ oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);" wrap="true" hidden="true"/> -