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;
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