author | Zachary Carter <zack.carter@gmail.com> |
Fri, 27 Mar 2015 02:37:55 -0700 | |
changeset 236036 | ae08455b713c0fa1dc2b91beb52fe2db99303e63 |
parent 236035 | 776865752a675fbb707a9c8bb880af90fa27d029 |
child 236037 | cfe959cdfb212d2e0e890cf1bdfe8d391a583fc4 |
push id | 57574 |
push user | zcarter@mozilla.com |
push date | Fri, 27 Mar 2015 09:39:22 +0000 |
treeherder | mozilla-inbound@ae08455b713c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | markh, rfeeley |
bugs | 1139677 |
milestone | 39.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/components/preferences/in-content/sync.js +++ b/browser/components/preferences/in-content/sync.js @@ -90,17 +90,19 @@ let gSyncPane = { }, _init: function () { let topics = ["weave:service:login:error", "weave:service:login:finish", "weave:service:start-over:finish", "weave:service:setup-complete", "weave:service:logout:finish", - FxAccountsCommon.ONVERIFIED_NOTIFICATION]; + FxAccountsCommon.ONVERIFIED_NOTIFICATION, + FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION, + ]; let migrateTopic = "fxa-migration:state-changed"; // Add the observers now and remove them on unload //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling // of `this`. Fix in a followup. (bug 583347) topics.forEach(function (topic) { Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this); }, this); @@ -118,16 +120,18 @@ let gSyncPane = { return Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties"); }), XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => { return Services.strings.createBundle("chrome://browser/locale/accounts.properties"); }), this.updateWeavePrefs(); + + this._initProfileImageUI(); }, _setupEventListeners: function() { function setEventListener(aId, aEventType, aCallback) { document.getElementById(aId) .addEventListener(aEventType, aCallback.bind(gSyncPane)); } @@ -219,16 +223,24 @@ let gSyncPane = { fxaMigrator.forgetFxAccount(); }); setEventListener("sync-migrate-resend", "click", function () { let win = Services.wm.getMostRecentWindow("navigator:browser"); fxaMigrator.resendVerificationMail(win); }); }, + _initProfileImageUI: function () { + try { + if (Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled")) { + document.getElementById("fxaProfileImage").hidden = false; + } + } catch (e) { } + }, + updateWeavePrefs: function () { // ask the migration module to broadcast its current state (and nothing will // happen if it's not loaded - which is good, as that means no migration // is pending/necessary) - we don't want to suck that module in just to // find there's nothing to do. Services.obs.notifyObservers(null, "fxa-migration:state-request", null); let service = Components.classes["@mozilla.org/weave/service;1"] @@ -239,20 +251,21 @@ let gSyncPane = { if (service.fxAccountsEnabled) { // unhide the reading-list engine if readinglist is enabled (note we do // it here as it must remain disabled for legacy sync users) if (Services.prefs.getBoolPref("browser.readinglist.enabled")) { document.getElementById("readinglist-engine").removeAttribute("hidden"); } // determine the fxa status... this.page = PAGE_PLEASE_WAIT; + fxAccounts.getSignedInUser().then(data => { if (!data) { this.page = FXA_PAGE_LOGGED_OUT; - return; + return false; } this.page = FXA_PAGE_LOGGED_IN; // We are logged in locally, but maybe we are in a state where the // server rejected our credentials (eg, password changed on the server) let fxaLoginStatus = document.getElementById("fxaLoginStatus"); let enginesListDisabled; // Not Verfied implies login error state, so check that first. if (!data.verified) { @@ -276,17 +289,46 @@ let gSyncPane = { document.getElementById("fxaEmailAddress1").textContent = data.email; document.getElementById("fxaEmailAddress2").textContent = data.email; document.getElementById("fxaEmailAddress3").textContent = data.email; document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName; let engines = document.getElementById("fxaSyncEngines") for (let checkbox of engines.querySelectorAll("checkbox")) { checkbox.disabled = enginesListDisabled; } + + // Clear the profile image (if any) of the previously logged in account. + document.getElementById("fxaProfileImage").style.removeProperty("background-image"); + + // If the account is verified the next promise in the chain will + // fetch profile data. + return data.verified; + }).then(shouldGetProfile => { + if (shouldGetProfile) { + return fxAccounts.getSignedInUserProfile(); + } + }).then(data => { + if (data && data.avatar) { + // Make sure the image is available before displaying it, + // as we don't want to overwrite the default profile image + // with a broken/unavailable image + let img = new Image(); + img.onload = () => { + let bgImage = "url('" + data.avatar + "')"; + document.getElementById("fxaProfileImage").style.backgroundImage = bgImage; + }; + img.src = data.avatar; + } + }, err => { + FxAccountsCommon.log.error(err); + }).catch(err => { + // If we get here something's really busted + Cu.reportError(String(err)); }); + // If fxAccountEnabled is false and we are in a "not configured" state, // then fxAccounts is probably fully disabled rather than just unconfigured, // so handle this case. This block can be removed once we remove support // for fxAccounts being disabled. } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED || Weave.Svc.Prefs.get("firstSync", "") == "notReady") { this.page = PAGE_NO_ACCOUNT; // else: sync was previously configured for the legacy provider, so we @@ -516,16 +558,25 @@ let gSyncPane = { }, reSignIn: function() { this.openContentInBrowser("about:accounts?action=reauth&entrypoint=preferences", { replaceQueryString: true }); }, + openChangeProfileImage: function() { + fxAccounts.promiseAccountsChangeProfileURI("avatar") + .then(url => { + this.openContentInBrowser(url, { + replaceQueryString: true + }); + }); + }, + manageFirefoxAccount: function() { let url = Services.prefs.getCharPref("identity.fxaccounts.settings.uri"); this.openContentInBrowser(url); }, verifyFirefoxAccount: function() { fxAccounts.resendVerificationEmail().then(() => { fxAccounts.getSignedInUser().then(data => {
--- a/browser/components/preferences/in-content/sync.xul +++ b/browser/components/preferences/in-content/sync.xul @@ -228,17 +228,21 @@ <groupbox id="fxaGroup"> <caption><label>&syncBrand.fxAccount.label;</label></caption> <deck id="fxaLoginStatus"> <!-- logged in and verified and all is good --> <hbox id="fxaLoginVerified" align="center"> - <label id="fxaEmailAddress1"/> + <hbox align="center"> + <image id="fxaProfileImage" + onclick="gSyncPane.openChangeProfileImage();" hidden="true"/> + <label id="fxaEmailAddress1"/> + </hbox> <spacer flex="1"/> <button id="verifiedManage" label="&manage.label;"/> <button id="fxaUnlinkButton" label="&disconnect.label;"/> </hbox> <!-- logged in to an unverified account -->
--- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -174,16 +174,17 @@ browser.jar: skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png) #endif * skin/classic/browser/preferences/preferences.css (preferences/preferences.css) * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) * skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css) skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico) skin/classic/browser/preferences/in-content/icons.svg (../shared/incontentprefs/icons.svg) skin/classic/browser/preferences/in-content/search.css (../shared/incontentprefs/search.css) + skin/classic/browser/preferences/in-content/default-profile-image.svg (../shared/incontentprefs/default-profile-image.svg) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/preferences/search.css (preferences/search.css) skin/classic/browser/social/services-16.png (social/services-16.png) skin/classic/browser/social/services-64.png (social/services-64.png) skin/classic/browser/social/share-button.png (social/share-button.png) skin/classic/browser/social/share-button-active.png (social/share-button-active.png) skin/classic/browser/social/chat-icons.svg (../shared/social/chat-icons.svg)
--- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -279,16 +279,17 @@ browser.jar: #endif skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png) * skin/classic/browser/preferences/preferences.css (preferences/preferences.css) * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) * skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css) skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico) skin/classic/browser/preferences/in-content/icons.svg (../shared/incontentprefs/icons.svg) skin/classic/browser/preferences/in-content/search.css (../shared/incontentprefs/search.css) + skin/classic/browser/preferences/in-content/default-profile-image.svg (../shared/incontentprefs/default-profile-image.svg) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/preferences/search.css (preferences/search.css) skin/classic/browser/preferences/checkbox.png (preferences/checkbox.png) skin/classic/browser/preferences/checkbox@2x.png (preferences/checkbox@2x.png) skin/classic/browser/yosemite/preferences/checkbox.png (preferences/checkbox-yosemite.png) skin/classic/browser/yosemite/preferences/checkbox@2x.png (preferences/checkbox-yosemite@2x.png) skin/classic/browser/social/services-16.png (social/services-16.png)
new file mode 100644 --- /dev/null +++ b/browser/themes/shared/incontentprefs/default-profile-image.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve"> +<path fill="#C3CFD8" d="M500-0.3c276.1,0,500,223.9,500,500s-223.9,500-500,500S0,775.8,0,499.7C0,223.5,223.9-0.3,500-0.3z"/> +<circle fill="#FFFFFF" cx="500" cy="317" r="139.1"/> +<path fill="#FFFFFF" d="M751.8,643.6L751.8,643.6c0.1-2.3,0.2-4.6,0.2-6.9c0-68-55.3-127-136.2-156.3L505.9,590.4h0 + c-0.4,29.8-1.4,58.8-2.8,86.6c-1,0.1-2,0.3-3.1,0.3s-2-0.2-3.1-0.3c-1.4-27.9-2.4-56.9-2.8-86.7h0L384.3,480.4 + C303.3,509.7,248,568.7,248,636.7c0,2.3,0.1,4.6,0.2,6.9l7.4,49.7c57.1,72,145.4,118.2,244.4,118.2c99,0,187.3-46.2,244.4-118.2 + L751.8,643.6z"/> +</svg>
--- a/browser/themes/shared/incontentprefs/preferences.inc.css +++ b/browser/themes/shared/incontentprefs/preferences.inc.css @@ -211,16 +211,32 @@ treecol { } /* XXX This style is for bug 740213 and should be removed once that bug has a solution. */ description > html|a { cursor: pointer; } +#fxaProfileImage { + width: 60px; + height: 60px; + border-radius: 50%; + border-width: 5px; + border-color: red; + background-image: url(chrome://browser/skin/preferences/in-content/default-profile-image.svg); + background-size: contain; + cursor: pointer; + -moz-margin-end: 15px; +} + +#fxaProfileImage:hover { + border-color: blue; +} + #noFxaAccount { /* Overriding the margins from the base preferences.css theme file. These overrides can be simplified by fixing bug 1027174 */ margin: 0; } #weavePrefsDeck > vbox > label, #weavePrefsDeck > vbox > groupbox,
--- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -202,16 +202,17 @@ browser.jar: #endif skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png) * skin/classic/browser/preferences/preferences.css (preferences/preferences.css) * skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css) * skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css) skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico) skin/classic/browser/preferences/in-content/icons.svg (../shared/incontentprefs/icons.svg) skin/classic/browser/preferences/in-content/search.css (../shared/incontentprefs/search.css) + skin/classic/browser/preferences/in-content/default-profile-image.svg (../shared/incontentprefs/default-profile-image.svg) skin/classic/browser/preferences/applications.css (preferences/applications.css) skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css) skin/classic/browser/preferences/search.css (preferences/search.css) skin/classic/browser/preferences/checkbox.png (preferences/checkbox-xp.png) skin/classic/browser/preferences/checkbox-classic.png (preferences/checkbox-classic.png) skin/classic/browser/social/services-16.png (social/services-16.png) skin/classic/browser/social/services-64.png (social/services-64.png) skin/classic/browser/social/chat-icons.svg (../shared/social/chat-icons.svg)
--- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -39,16 +39,17 @@ let publicProperties = [ "getKeys", "getSignedInUser", "getOAuthToken", "getSignedInUserProfile", "loadAndPoll", "localtimeOffsetMsec", "now", "promiseAccountsForceSigninURI", + "promiseAccountsChangeProfileURI", "resendVerificationEmail", "setSignedInUser", "signOut", "version", "whenVerified" ]; // An AccountState object holds all state related to one specific account. @@ -956,16 +957,44 @@ FxAccountsInternal.prototype = { return null; } let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&"; newQueryPortion += "email=" + encodeURIComponent(accountData.email); return url + newQueryPortion; }).then(result => currentState.resolve(result)); }, + // Returns a promise that resolves with the URL to use to change + // the current account's profile image. + // if settingToEdit is set, the profile page should hightlight that setting + // for the user to edit. + promiseAccountsChangeProfileURI: function(settingToEdit = null) { + let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri"); + + if (settingToEdit) { + url += (url.indexOf("?") == -1 ? "?" : "&") + + "setting=" + encodeURIComponent(settingToEdit); + } + + if (this._requireHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting + throw new Error("Firefox Accounts server must use HTTPS"); + } + let currentState = this.currentAccountState; + // but we need to append the email address onto a query string. + return this.getSignedInUser().then(accountData => { + if (!accountData) { + return null; + } + let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&"; + newQueryPortion += "email=" + encodeURIComponent(accountData.email); + newQueryPortion += "&uid=" + encodeURIComponent(accountData.uid); + return url + newQueryPortion; + }).then(result => currentState.resolve(result)); + }, + /** * Get an OAuth token for the user * * @param options * { * scope: (string) the oauth scope being requested * } * @@ -1073,40 +1102,23 @@ FxAccountsInternal.prototype = { * UNVERIFIED_ACCOUNT * NETWORK_ERROR * AUTH_ERROR * UNKNOWN_ERROR */ getSignedInUserProfile: function () { let accountState = this.currentAccountState; return accountState.getProfile() - .then( - (profileData) => { - let profile = JSON.parse(JSON.stringify(profileData)); - // profileData doesn't include "verified", but it must be true - // if we've gotten this far. - profile.verified = true; - return accountState.resolve(profile); - }, - (error) => { - log.error("Could not retrieve profile data", error); - - return this.getSignedInUser().then(data => { - let profile = null; - if (data) { - // If we fail to fetch the profile and have no profile cached - // we resort to using the account data for basic profile data. - profile = { - email: data.email, - uid: data.uid, - verified: data.verified - }; - } - return accountState.resolve(profile); - }); + .then((profileData) => { + let profile = JSON.parse(JSON.stringify(profileData)); + return accountState.resolve(profile); + }, + (error) => { + log.error("Could not retrieve profile data", error); + return accountState.reject(error); }) .then(null, err => this._errorToErrorClass(err)); }, }; /** * JSONStorage constructor that creates instances that may set/get * to a specified file, in a directory that will be created if it
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -918,17 +918,16 @@ add_test(function test_getSignedInUserPr let accountState = fxa.internal.currentAccountState; accountState.getProfile = function () { return Promise.resolve({ avatar: "image" }); }; fxa.getSignedInUserProfile() .then(result => { do_check_eq(result.avatar, "image"); - do_check_true(result.verified); run_next_test(); }); }); }); add_test(function test_getSignedInUserProfile_error_uses_account_data() { let fxa = new MockFxAccounts(); @@ -941,44 +940,50 @@ add_test(function test_getSignedInUserPr fxa.setSignedInUser(alice).then(() => { let accountState = fxa.internal.currentAccountState; accountState.getProfile = function () { return Promise.reject("boom"); }; fxa.getSignedInUserProfile() - .then(result => { - do_check_eq(typeof result.avatar, "undefined"); - do_check_eq(result.email, "foo@bar.com"); + .catch(error => { + do_check_eq(error.message, "UNKNOWN_ERROR"); + run_next_test(); + }); + }); + +}); + +add_test(function test_getSignedInUserProfile_unverified_account() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + + fxa.setSignedInUser(alice).then(() => { + let accountState = fxa.internal.currentAccountState; + + fxa.getSignedInUserProfile() + .catch(error => { + do_check_eq(error.message, "UNVERIFIED_ACCOUNT"); run_next_test(); }); }); }); add_test(function test_getSignedInUserProfile_no_account_data() { let fxa = new MockFxAccounts(); fxa.internal.getSignedInUser = function () { - return Promise.resolve({ email: "foo@bar.com" }); - }; - - let accountState = fxa.internal.currentAccountState; - accountState.getProfile = function () { - return Promise.reject("boom"); - }; - - fxa.internal.getSignedInUser = function () { return Promise.resolve(null); }; fxa.getSignedInUserProfile() - .then(result => { - do_check_eq(result, null); + .catch(error => { + do_check_eq(error.message, "NO_ACCOUNT"); run_next_test(); }); }); /* * End of tests. * Utility functions follow.