diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index faf69568af0e..abd5fadfb713 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1236,9 +1236,11 @@ pref("devtools.hud.loglimit.console", 200); // - tabsize: how many spaces to use when a Tab character is displayed. // - expandtab: expand Tab characters to spaces. // - keymap: which keymap to use (can be 'default', 'emacs' or 'vim') +// - autoclosebrackets: whether to permit automatic bracket/quote closing. pref("devtools.editor.tabsize", 4); pref("devtools.editor.expandtab", true); pref("devtools.editor.keymap", "default"); +pref("devtools.editor.autoclosebrackets", true); // Enable the Font Inspector pref("devtools.fontinspector.enabled", true); diff --git a/browser/base/content/aboutaccounts/aboutaccounts.js b/browser/base/content/aboutaccounts/aboutaccounts.js index c43c45dae283..c6e19ea0e0b3 100644 --- a/browser/base/content/aboutaccounts/aboutaccounts.js +++ b/browser/base/content/aboutaccounts/aboutaccounts.js @@ -9,7 +9,8 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/FxAccounts.jsm"); -const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUser"; +const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash"; +const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync.ui.showCustomizationDialog"; function log(msg) { //dump("FXA: " + msg + "\n"); @@ -19,7 +20,7 @@ function error(msg) { console.log("Firefox Account Error: " + msg + "\n"); }; -function getPreviousAccountName() { +function getPreviousAccountNameHash() { try { return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data; } catch (_) { @@ -27,24 +28,39 @@ function getPreviousAccountName() { } } -function setPreviousAccountName(acctName) { +function setPreviousAccountNameHash(acctName) { let string = Cc["@mozilla.org/supports-string;1"] .createInstance(Ci.nsISupportsString); - string.data = acctName; + string.data = sha256(acctName); Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string); } -function needRelinkWarning(accountData) { - let prevAcct = getPreviousAccountName(); - return prevAcct && prevAcct != accountData.email; +function needRelinkWarning(acctName) { + let prevAcctHash = getPreviousAccountNameHash(); + return prevAcctHash && prevAcctHash != sha256(acctName); } -function promptForRelink() { +// Given a string, returns the SHA265 hash in base64 +function sha256(str) { + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + // Data is an array of bytes. + let data = converter.convertToByteArray(str, {}); + let hasher = Cc["@mozilla.org/security/hash;1"] + .createInstance(Ci.nsICryptoHash); + hasher.init(hasher.SHA256); + hasher.update(data, data.length); + + return hasher.finish(true); +} + +function promptForRelink(acctName) { let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties"); let continueLabel = sb.GetStringFromName("continue.label"); let title = sb.GetStringFromName("relink.verify.title"); let description = sb.formatStringFromName("relink.verify.description", - [Services.prefs.getCharPref(PREF_LAST_FXA_USER)], 1); + [acctName], 1); let body = sb.GetStringFromName("relink.verify.heading") + "\n\n" + description; let ps = Services.prompt; @@ -104,7 +120,7 @@ let wrapper = { log("Received: 'login'. Data:" + JSON.stringify(accountData)); if (accountData.customizeSync) { - Services.prefs.setBoolPref("services.sync.needsCustomization", true); + Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, true); delete accountData.customizeSync; } @@ -113,7 +129,8 @@ let wrapper = { // (This is sync-specific, so ideally would be in sync's identity module, // but it's a little more seamless to do here, and sync is currently the // only fxa consumer, so... - if (needRelinkWarning(accountData) && !promptForRelink()) { + let newAccountEmail = accountData.email; + if (needRelinkWarning(newAccountEmail) && !promptForRelink(newAccountEmail)) { // we need to tell the page we successfully received the message, but // then bail without telling fxAccounts this.injectData("message", { status: "login" }); @@ -123,7 +140,7 @@ let wrapper = { } // Remember who it was so we can log out next time. - setPreviousAccountName(accountData.email); + setPreviousAccountNameHash(newAccountEmail); fxAccounts.setSignedInUser(accountData).then( () => { diff --git a/browser/base/content/aboutaccounts/aboutaccounts.xhtml b/browser/base/content/aboutaccounts/aboutaccounts.xhtml index d57763c62da4..a9ae5ba16f48 100644 --- a/browser/base/content/aboutaccounts/aboutaccounts.xhtml +++ b/browser/base/content/aboutaccounts/aboutaccounts.xhtml @@ -61,14 +61,14 @@
-
&aboutAccountsSetup.description;
+
&aboutAccountsConfig.description;
- &aboutAccountsSetup.startButton.label; + &aboutAccountsConfig.startButton.label;
diff --git a/browser/base/content/browser-fullZoom.js b/browser/base/content/browser-fullZoom.js index fd26ffad5f42..5137bb36b8a1 100644 --- a/browser/base/content/browser-fullZoom.js +++ b/browser/base/content/browser-fullZoom.js @@ -310,6 +310,11 @@ var FullZoom = { if (token.isCurrent) { ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value); this._ignorePendingZoomAccesses(browser); + this._executeSoon(function () { + // _getGlobalValue may be either sync or async, so notify asyncly so + // observers are guaranteed consistent behavior. + Services.obs.notifyObservers(null, "browser-fullZoom:reset", ""); + }); } }); this._removePref(browser); diff --git a/browser/base/content/browser-fxaccounts.js b/browser/base/content/browser-fxaccounts.js index 846a0a953272..c5280075ba48 100644 --- a/browser/base/content/browser-fxaccounts.js +++ b/browser/base/content/browser-fxaccounts.js @@ -6,10 +6,11 @@ XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () { return Cu.import("resource://gre/modules/FxAccountsCommon.js", {}); }); +const PREF_SYNC_START_DOORHANGER = "services.sync.ui.showSyncStartDoorhanger"; + let gFxAccounts = { _initialized: false, - _originalLabel: null, _inCustomizationMode: false, get weave() { @@ -20,29 +21,46 @@ let gFxAccounts = { }, get topics() { + // Do all this dance to lazy-load FxAccountsCommon. delete this.topics; return this.topics = [ + "weave:service:sync:start", + "weave:service:login:error", FxAccountsCommon.ONLOGIN_NOTIFICATION, FxAccountsCommon.ONVERIFIED_NOTIFICATION, FxAccountsCommon.ONLOGOUT_NOTIFICATION ]; }, + // The set of topics that only the active window should handle. + get activeWindowTopics() { + // Do all this dance to lazy-load FxAccountsCommon. + delete this.activeWindowTopics; + return this.activeWindowTopics = new Set([ + "weave:service:sync:start", + FxAccountsCommon.ONVERIFIED_NOTIFICATION + ]); + }, + get button() { delete this.button; return this.button = document.getElementById("PanelUI-fxa-status"); }, - get syncNeedsCustomization() { - try { - return Services.prefs.getBoolPref("services.sync.needsCustomization"); - } catch (e) { - return false; - } + get loginFailed() { + return Weave.Service.identity.readyToAuthenticate && + Weave.Status.login != Weave.LOGIN_SUCCEEDED; + }, + + get isActiveWindow() { + let mostRecentNonPopupWindow = + RecentWindow.getMostRecentBrowserWindow({allowPopups: false}); + return window == mostRecentNonPopupWindow; }, init: function () { - if (this._initialized) { + // Bail out if we're already initialized and for pop-up windows. + if (this._initialized || !window.toolbar.visible) { return; } @@ -53,9 +71,6 @@ let gFxAccounts = { gNavToolbox.addEventListener("customizationstarting", this); gNavToolbox.addEventListener("customizationending", this); - // Save the button's original label so that - // we can restore it if overridden later. - this._originalLabel = this.button.getAttribute("label"); this._initialized = true; this.updateUI(); @@ -74,9 +89,33 @@ let gFxAccounts = { }, observe: function (subject, topic) { - if (topic != FxAccountsCommon.ONVERIFIED_NOTIFICATION) { - this.updateUI(); - } else if (!this.syncNeedsCustomization) { + // Ignore certain topics if we're not the active window. + if (this.activeWindowTopics.has(topic) && !this.isActiveWindow) { + return; + } + + switch (topic) { + case FxAccountsCommon.ONVERIFIED_NOTIFICATION: + Services.prefs.setBoolPref(PREF_SYNC_START_DOORHANGER, true); + break; + case "weave:service:sync:start": + this.onSyncStart(); + break; + default: + this.updateUI(); + break; + } + }, + + onSyncStart: function () { + let showDoorhanger = false; + + try { + showDoorhanger = Services.prefs.getBoolPref(PREF_SYNC_START_DOORHANGER); + } catch (e) { /* The pref might not exist. */ } + + if (showDoorhanger) { + Services.prefs.clearUserPref(PREF_SYNC_START_DOORHANGER); this.showSyncStartedDoorhanger(); } }, @@ -122,17 +161,27 @@ let gFxAccounts = { this.button.removeAttribute("disabled"); } + let defaultLabel = this.button.getAttribute("defaultlabel"); + let errorLabel = this.button.getAttribute("errorlabel"); + // If the user is signed into their Firefox account and we are not // currently in customization mode, show their email address. fxAccounts.getSignedInUser().then(userData => { - if (userData && !this._inCustomizationMode) { - this.button.setAttribute("signedin", "true"); - this.button.setAttribute("label", userData.email); - this.button.setAttribute("tooltiptext", userData.email); - } else { - this.button.removeAttribute("signedin"); - this.button.setAttribute("label", this._originalLabel); - this.button.removeAttribute("tooltiptext"); + // Reset the button to its original state. + this.button.setAttribute("label", defaultLabel); + this.button.removeAttribute("tooltiptext"); + this.button.removeAttribute("signedin"); + this.button.removeAttribute("failed"); + + if (!this._inCustomizationMode) { + if (this.loginFailed) { + this.button.setAttribute("failed", "true"); + this.button.setAttribute("label", errorLabel); + } else if (userData) { + this.button.setAttribute("signedin", "true"); + this.button.setAttribute("label", userData.email); + this.button.setAttribute("tooltiptext", userData.email); + } } }); }, diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index fd8758cb8983..46f687c03423 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -15,6 +15,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", "resource://gre/modules/CharsetMenu.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils", + "resource://gre/modules/ShortcutUtils.jsm"); const nsIWebNavigation = Ci.nsIWebNavigation; @@ -3731,6 +3733,22 @@ var XULBrowserWindow = { setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0); else this.asyncUpdateUI(); + +#ifdef MOZ_CRASHREPORTER + if (aLocationURI) { + let uri = aLocationURI.clone(); + try { + // If the current URI contains a username/password, remove it. + uri.userPass = ""; + } catch (ex) { /* Ignore failures on about: URIs. */ } + + try { + gCrashReporter.annotateCrashReport("URL", uri.spec); + } catch (ex if ex.result == Components.results.NS_ERROR_NOT_INITIALIZED) { + // Don't make noise when the crash reporter is built but not enabled. + } + } +#endif }, asyncUpdateUI: function () { @@ -3990,15 +4008,6 @@ var CombinedStopReload = { var TabsProgressListener = { onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { -#ifdef MOZ_CRASHREPORTER - if (aRequest instanceof Ci.nsIChannel && - aStateFlags & Ci.nsIWebProgressListener.STATE_START && - aStateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT && - gCrashReporter.enabled) { - gCrashReporter.annotateCrashReport("URL", aRequest.URI.spec); - } -#endif - // Collect telemetry data about tab load times. if (aWebProgress.isTopLevel) { if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) { @@ -4820,6 +4829,44 @@ var gHomeButton = { }, }; +const nodeToTooltipMap = { + "bookmarks-menu-button": "bookmarksMenuButton.tooltip", +#ifdef XP_MACOSX + "print-button": "printButton.tooltip", +#endif + "new-window-button": "newWindowButton.tooltip", + "fullscreen-button": "fullscreenButton.tooltip", + "tabview-button": "tabviewButton.tooltip", +}; +const nodeToShortcutMap = { + "bookmarks-menu-button": "manBookmarkKb", +#ifdef XP_MACOSX + "print-button": "printKb", +#endif + "new-window-button": "key_newNavigator", + "fullscreen-button": "key_fullScreen", + "tabview-button": "key_tabview", +}; +const gDynamicTooltipCache = new Map(); +function UpdateDynamicShortcutTooltipText(popupTriggerNode) { + let label = document.getElementById("dynamic-shortcut-tooltip-label"); + let nodeId = popupTriggerNode.id; + if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) { + let strId = nodeToTooltipMap[nodeId]; + let args = []; + if (nodeId in nodeToShortcutMap) { + let shortcutId = nodeToShortcutMap[nodeId]; + let shortcut = document.getElementById(shortcutId); + if (shortcut) { + args.push(ShortcutUtils.prettifyShortcut(shortcut)); + } + } + gDynamicTooltipCache.set(nodeId, gNavigatorBundle.getFormattedString(strId, args)); + } + let desiredLabel = gDynamicTooltipCache.get(nodeId); + label.setAttribute("value", desiredLabel); +} + /** * Gets the selected text in the active browser. Leading and trailing * whitespace is removed, and consecutive whitespace is replaced by a single diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index 6b1034a20bed..1994baac4bc2 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -210,7 +210,11 @@ + + @@ -463,6 +467,11 @@ + + + #ifdef CAN_DRAW_IN_TITLEBAR @@ -744,7 +753,7 @@ removable="true" type="menu-button" label="&bookmarksMenuButton.label;" - tooltiptext="&bookmarksMenuButton.tooltip;" + tooltip="dynamic-shortcut-tooltip" anchor="dropmarker" ondragenter="PlacesMenuDNDHandler.onDragEnter(event);" ondragover="PlacesMenuDNDHandler.onDragOver(event);" @@ -964,17 +973,17 @@ + label="&printButton.label;"/> + tooltip="dynamic-shortcut-tooltip"/> #ifdef MOZ_SERVICES_SYNC