--- a/browser/base/content/browser-fullZoom.js
+++ b/browser/base/content/browser-fullZoom.js
@@ -189,56 +189,56 @@ var FullZoom = {
}
// Ignore all pending async zoom accesses in the browser. Pending accesses
// that started before the location change will be prevented from applying
// to the new location.
this._ignorePendingZoomAccesses(browser);
if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
- this._notifyOnLocationChange();
+ this._notifyOnLocationChange(browser);
return;
}
// Avoid the cps roundtrip and apply the default/global pref.
if (aURI.spec == "about:blank") {
this._applyPrefToZoom(undefined, browser,
- this._notifyOnLocationChange.bind(this));
+ this._notifyOnLocationChange.bind(this, browser));
return;
}
// Media documents should always start at 1, and are not affected by prefs.
if (!aIsTabSwitch && browser.isSyntheticDocument) {
ZoomManager.setZoomForBrowser(browser, 1);
// _ignorePendingZoomAccesses already called above, so no need here.
- this._notifyOnLocationChange();
+ this._notifyOnLocationChange(browser);
return;
}
// See if the zoom pref is cached.
let ctxt = this._loadContextFromBrowser(browser);
let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
if (pref) {
this._applyPrefToZoom(pref.value, browser,
- this._notifyOnLocationChange.bind(this));
+ this._notifyOnLocationChange.bind(this, browser));
return;
}
// It's not cached, so we have to asynchronously fetch it.
let value = undefined;
let token = this._getBrowserToken(browser);
this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
handleResult: function (resultPref) { value = resultPref.value; },
handleCompletion: function () {
if (!token.isCurrent) {
- this._notifyOnLocationChange();
+ this._notifyOnLocationChange(browser);
return;
}
this._applyPrefToZoom(value, browser,
- this._notifyOnLocationChange.bind(this));
+ this._notifyOnLocationChange.bind(this, browser));
}.bind(this)
});
},
// update state of zoom type menu item
updateMenu: function FullZoom_updateMenu() {
var menuItem = document.getElementById("toggle_zoom");
@@ -286,17 +286,17 @@ var FullZoom = {
* @return A promise which resolves when the zoom reset has been applied.
*/
reset: function FullZoom_reset(browser = gBrowser.selectedBrowser) {
let token = this._getBrowserToken(browser);
let result = this._getGlobalValue(browser).then(value => {
if (token.isCurrent) {
ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
this._ignorePendingZoomAccesses(browser);
- Services.obs.notifyObservers(null, "browser-fullZoom:zoomReset", "");
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
}
});
this._removePref(browser);
return result;
},
/**
* Set the zoom level for a given browser.
@@ -354,17 +354,17 @@ var FullZoom = {
/**
* Saves the zoom level of the page in the given browser to the content
* prefs store.
*
* @param browser The zoom of this browser will be saved. Required.
*/
_applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
- Services.obs.notifyObservers(null, "browser-fullZoom:zoomChange", "");
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomChange", "");
if (!this.siteSpecific ||
gInPrintPreviewMode ||
browser.isSyntheticDocument)
return;
this._cps2.set(browser.currentURI.spec, this.name,
ZoomManager.getZoomForBrowser(browser),
this._loadContextFromBrowser(browser), {
@@ -375,17 +375,17 @@ var FullZoom = {
},
/**
* Removes from the content prefs store the zoom level of the given browser.
*
* @param browser The zoom of this browser will be removed. Required.
*/
_removePref: function FullZoom__removePref(browser) {
- Services.obs.notifyObservers(null, "browser-fullZoom:zoomReset", "");
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
if (browser.isSyntheticDocument)
return;
let ctxt = this._loadContextFromBrowser(browser);
this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
handleCompletion: function () {
this._isNextContentPrefChangeInternal = true;
}.bind(this),
});
@@ -512,19 +512,19 @@ var FullZoom = {
},
/**
* Asynchronously broadcasts "browser-fullZoom:location-change" so that
* listeners can be notified when the zoom levels on those pages change.
* The notification is always asynchronous so that observers are guaranteed a
* consistent behavior.
*/
- _notifyOnLocationChange: function FullZoom__notifyOnLocationChange() {
+ _notifyOnLocationChange: function FullZoom__notifyOnLocationChange(browser) {
this._executeSoon(function () {
- Services.obs.notifyObservers(null, "browser-fullZoom:location-change", "");
+ Services.obs.notifyObservers(browser, "browser-fullZoom:location-change", "");
});
},
_executeSoon: function FullZoom__executeSoon(callback) {
if (!callback)
return;
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
},
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -777,16 +777,20 @@
class="urlbar-icon"
hidden="true"
tooltiptext="&pageReportIcon.tooltip;"
onclick="gPopupBlockerObserver.onReportButtonClick(event);"/>
<image id="reader-mode-button"
class="urlbar-icon"
hidden="true"
onclick="ReaderParent.buttonClick(event);"/>
+ <toolbarbutton id="urlbar-zoom-button"
+ onclick="FullZoom.reset();"
+ tooltiptext="&urlbar.zoomReset.tooltip;"
+ hidden="true"/>
</hbox>
<hbox id="userContext-icons" hidden="true">
<label id="userContext-label"/>
<image id="userContext-indicator"/>
</hbox>
<toolbarbutton id="urlbar-go-button"
class="chromeclass-toolbar-additional"
onclick="gURLBar.handleCommand(event);"
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -56,16 +56,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
["SelfSupportBackend", "resource:///modules/SelfSupportBackend.jsm"],
["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
["ShellService", "resource:///modules/ShellService.jsm"],
["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
["TabGroupsMigrator", "resource:///modules/TabGroupsMigrator.jsm"],
["Task", "resource://gre/modules/Task.jsm"],
["UITour", "resource:///modules/UITour.jsm"],
+ ["URLBarZoom", "resource:///modules/URLBarZoom.jsm"],
["WebChannel", "resource://gre/modules/WebChannel.jsm"],
["WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"],
["webrtcUI", "resource:///modules/webrtcUI.jsm"],
].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
if (AppConstants.MOZ_CRASHREPORTER) {
XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
"resource:///modules/ContentCrashHandlers.jsm");
@@ -685,16 +686,17 @@ BrowserGlue.prototype = {
ContentClick.init();
RemotePrompt.init();
Feeds.init();
ContentPrefServiceParent.init();
LoginManagerParent.init();
ReaderParent.init();
+ URLBarZoom.init();
SelfSupportBackend.init();
// Ensure we keep track of places/pw-mananager undo by init'ing this early.
Cu.import("resource:///modules/AutoMigrate.jsm");
if (!AppConstants.RELEASE_BUILD) {
let themeName = gBrowserBundle.GetStringFromName("deveditionTheme.name");
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -226,16 +226,18 @@ These should match what Safari and other
<!ENTITY urlbar.pointerLockNotificationAnchor.label "Change whether the site can hide the pointer">
<!ENTITY urlbar.servicesNotificationAnchor.label "View the service install message">
<!ENTITY urlbar.translateNotificationAnchor.label "Translate this page">
<!ENTITY urlbar.translatedNotificationAnchor.label "Manage page translation">
<!ENTITY urlbar.emeNotificationAnchor.label "Manage use of DRM software">
<!ENTITY urlbar.openHistoryPopup.tooltip "Show history">
+<!ENTITY urlbar.zoomReset.tooltip "Reset zoom level">
+
<!ENTITY searchItem.title "Search">
<!-- Toolbar items -->
<!ENTITY homeButton.label "Home">
<!ENTITY bookmarksButton.label "Bookmarks">
<!ENTITY bookmarksCmd.commandkey "b">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -265,16 +265,20 @@ menuUndoCloseWindowSingleTabLabel=#1
# Unified Back-/Forward Popup
tabHistory.current=Stay on this page
tabHistory.goBack=Go back to this page
tabHistory.goForward=Go forward to this page
# URL Bar
pasteAndGo.label=Paste & Go
+# LOCALIZATION NOTE(urlbar-zoom-button.label): %S is the current zoom level,
+# %% will be displayed as a single % character (% is commonly used to define
+# format specifiers, so it needs to be escaped).
+urlbar-zoom-button.label = %S%%
# Block autorefresh
refreshBlocked.goButton=Allow
refreshBlocked.goButton.accesskey=A
refreshBlocked.refreshLabel=%S prevented this page from automatically reloading.
refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page.
# General bookmarks button
new file mode 100644
--- /dev/null
+++ b/browser/modules/URLBarZoom.jsm
@@ -0,0 +1,51 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* 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";
+
+this.EXPORTED_SYMBOLS = [ "URLBarZoom" ];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var URLBarZoom = {
+
+ init: function(aWindow) {
+ // Register ourselves with the service so we know when the zoom prefs change.
+ Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomChange", false);
+ Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomReset", false);
+ Services.obs.addObserver(updateZoomButton, "browser-fullZoom:location-change", false);
+ },
+}
+
+function updateZoomButton(aSubject, aTopic) {
+ let win = aSubject.ownerDocument.defaultView;
+ let customizableZoomControls = win.document.getElementById("zoom-controls");
+ let zoomResetButton = win.document.getElementById("urlbar-zoom-button");
+ let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
+
+ // Ensure that zoom controls haven't already been added to browser in Customize Mode
+ if (customizableZoomControls &&
+ customizableZoomControls.getAttribute("cui-areatype") == "toolbar") {
+ zoomResetButton.hidden = true;
+ return;
+ }
+ if (zoomFactor != 100) {
+ // Check if zoom button is visible and update label if it is
+ if (zoomResetButton.hidden) {
+ zoomResetButton.hidden = false;
+ }
+ // Only allow pulse animation for zoom changes, not tab switching
+ if (aTopic != "browser-fullZoom:location-change") {
+ zoomResetButton.setAttribute("animate", "true");
+ } else {
+ zoomResetButton.removeAttribute("animate");
+ }
+ zoomResetButton.setAttribute("label",
+ win.gNavigatorBundle.getFormattedString("urlbar-zoom-button.label", [zoomFactor]));
+ // Hide button if zoom is at 100%
+ } else {
+ zoomResetButton.hidden = true;
+ }
+}
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -40,16 +40,17 @@ EXTRA_JS_MODULES += [
'RecentWindow.jsm',
'RemotePrompt.jsm',
'Sanitizer.jsm',
'SelfSupportBackend.jsm',
'SitePermissions.jsm',
'Social.jsm',
'TabGroupsMigrator.jsm',
'TransientPrefs.jsm',
+ 'URLBarZoom.jsm',
'webrtcUI.jsm',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
EXTRA_JS_MODULES += [
'Windows8WindowFrameColor.jsm',
'WindowsJumpLists.jsm',
'WindowsPreviewPerTab.jsm',
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
[browser_BrowserUITelemetry_defaults.js]
[browser_BrowserUITelemetry_sidebar.js]
[browser_BrowserUITelemetry_syncedtabs.js]
[browser_CaptivePortalWatcher.js]
skip-if = os == "linux" || os == "win" || (os == "mac" && debug && e10s) # Bug 1279491, Bug 1287714 for OS X debug e10s
[browser_ProcessHangNotifications.js]
skip-if = !e10s
[browser_ContentSearch.js]
+[browser_urlBar_zoom.js]
support-files =
contentSearch.js
contentSearchBadImage.xml
contentSearchSuggestions.sjs
contentSearchSuggestions.xml
[browser_NetworkPrioritizer.js]
[browser_SelfSupportBackend.js]
support-files =
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_urlBar_zoom.js
@@ -0,0 +1,73 @@
+/* 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 initialPageZoom = ZoomManager.zoom;
+const kTimeoutInMS = 20000;
+
+add_task(function* () {
+ info("Confirm whether the browser zoom is set to the default level");
+ is(initialPageZoom, 1, "Page zoom is set to default (100%)");
+ let zoomResetButton = document.getElementById("urlbar-zoom-button");
+ is(zoomResetButton.hidden, true, "Zoom reset button is currently hidden");
+
+ info("Change zoom and confirm zoom button appears");
+ let labelUpdatePromise = BrowserTestUtils.waitForAttribute("label", zoomResetButton);
+ FullZoom.enlarge();
+ yield labelUpdatePromise;
+ info("Zoom increased to " + Math.floor(ZoomManager.zoom * 100) + "%");
+ is(zoomResetButton.hidden, false, "Zoom reset button is now visible");
+ let pageZoomLevel = Math.floor(ZoomManager.zoom * 100);
+ let expectedZoomLevel = 110;
+ let buttonZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
+ is(buttonZoomLevel, expectedZoomLevel, ("Button label updated successfully to " + Math.floor(ZoomManager.zoom * 100) + "%"));
+
+ let zoomResetPromise = promiseObserverNotification("browser-fullZoom:zoomReset");
+ zoomResetButton.click();
+ yield zoomResetPromise;
+ pageZoomLevel = Math.floor(ZoomManager.zoom * 100);
+ expectedZoomLevel = 100;
+ is(pageZoomLevel, expectedZoomLevel, "Clicking zoom button successfully resets browser zoom to 100%");
+ is(zoomResetButton.hidden, true, "Zoom reset button returns to being hidden");
+
+});
+
+add_task(function* () {
+ info("Confirm that URL bar zoom button doesn't appear when customizable zoom widget is added to toolbar");
+ CustomizableUI.addWidgetToArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
+ let zoomCustomizableWidget = document.getElementById("zoom-reset-button");
+ let zoomResetButton = document.getElementById("urlbar-zoom-button");
+ let zoomChangePromise = promiseObserverNotification("browser-fullZoom:zoomChange");
+ FullZoom.enlarge();
+ yield zoomChangePromise;
+ is(zoomResetButton.hidden, true, "URL zoom button remains hidden despite zoom increase");
+ is(parseInt(zoomCustomizableWidget.label, 10), 110, "Customizable zoom widget's label has updated to " + zoomCustomizableWidget.label);
+});
+
+add_task(function* asyncCleanup() {
+ // reset zoom level and customizable widget
+ ZoomManager.zoom = initialPageZoom;
+ is(ZoomManager.zoom, 1, "Zoom level was restored");
+ if (document.getElementById("zoom-controls")) {
+ CustomizableUI.removeWidgetFromArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
+ ok(!document.getElementById("zoom-controls"),"Customizable zoom widget removed from toolbar");
+ }
+
+});
+
+function promiseObserverNotification(aObserver) {
+ let deferred = Promise.defer();
+ function notificationCallback(e) {
+ Services.obs.removeObserver(notificationCallback, aObserver, false);
+ clearTimeout(timeoutId);
+ deferred.resolve();
+ }
+ let timeoutId = setTimeout(() => {
+ Services.obs.removeObserver(notificationCallback, aObserver, false);
+ deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
+ }, kTimeoutInMS);
+ Services.obs.addObserver(notificationCallback, aObserver, false);
+ return deferred.promise;
+}
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -942,16 +942,53 @@ menuitem:not([type]):not(.menuitem-toolt
.urlbar-icon {
padding: 0 3px;
/* 16x16 icon with border-box sizing */
width: 22px;
height: 16px;
}
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1.5);
+ }
+}
+
+#urlbar-zoom-button {
+ -moz-appearance: none;
+ margin: 0 3px;
+ font-size: .8em;
+ padding: 0 8px;
+ border-radius: 1em;
+ background-color: hsla(0,0%,0%,.05);
+ border: 1px solid ThreeDLightShadow;
+}
+
+#urlbar-zoom-button[animate="true"] {
+ animation-name: urlbar-zoom-reset-pulse;
+ animation-duration: 250ms;
+}
+
+#urlbar-zoom-button:hover:active {
+ background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+ display: none;
+}
+
#urlbar-search-footer {
border-top: 1px solid hsla(210,4%,10%,.14);
background-color: hsla(210,4%,10%,.07);
}
#urlbar-search-settings {
-moz-appearance: none;
-moz-user-focus: ignore;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1650,16 +1650,51 @@ toolbar .toolbarbutton-1 > .toolbarbutto
.urlbar-icon {
padding: 0 3px;
/* 16x16 icon with border-box sizing */
width: 22px;
height: 16px;
}
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1.5);
+ }
+}
+
+#urlbar-zoom-button {
+ margin: 0 3px;
+ font-size: .8em;
+ padding: 0 8px;
+ border-radius: 1em;
+ background-color: hsla(0,0%,0%,.05);
+ border: 1px solid hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button[animate="true"] {
+ animation-name: urlbar-zoom-reset-pulse;
+ animation-duration: 250ms;
+}
+#urlbar-zoom-button:hover:active {
+ background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+ display: none;
+}
+
#urlbar-search-footer {
border-top: 1px solid hsla(210,4%,10%,.14);
background-color: hsla(210,4%,10%,.07);
}
#urlbar-search-settings {
-moz-appearance: none;
-moz-user-focus: ignore;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1362,16 +1362,53 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
.urlbar-icon {
padding: 0 3px;
/* 16x16 icon with border-box sizing */
width: 22px;
height: 16px;
}
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1.5);
+ }
+}
+
+#urlbar-zoom-button {
+ -moz-appearance: none;
+ margin: 0 3px;
+ font-size: .8em;
+ padding: 0 8px;
+ border-radius: 1em;
+ background-color: hsla(0,0%,0%,.05);
+ border: 1px solid ThreeDLightShadow;
+}
+
+#urlbar-zoom-button[animate="true"] {
+ animation-name: urlbar-zoom-reset-pulse;
+ animation-duration: 250ms;
+}
+
+#urlbar-zoom-button:hover:active {
+ background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+ display: none;
+}
+
.search-go-container {
padding: 2px 2px;
}
#urlbar-search-footer {
border-top: 1px solid hsla(210,4%,10%,.14);
background-color: hsla(210,4%,10%,.07);
}