Bug 1238310: Part 5 - Implement the browser.tabs.onZoomChange event. r=gabor f=aswan
authorKris Maglione <maglione.k@gmail.com>
Fri, 01 Apr 2016 11:45:01 -0700
changeset 294738 2e75a8ab94a9331d0f84a4a6edb13a7843e904e8
parent 294737 d65b48650a68a678f5d4c2aabca7e539e647e3d4
child 294739 0f07f975526f3abda2f997bbb2feb0a25f771227
push id30210
push userkwierso@gmail.com
push dateMon, 25 Apr 2016 22:25:12 +0000
treeherdermozilla-central@79de998e7307 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgabor
bugs1238310
milestone48.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
Bug 1238310: Part 5 - Implement the browser.tabs.onZoomChange event. r=gabor f=aswan MozReview-Commit-ID: JlxSM13SeYg
browser/components/extensions/ext-tabs.js
browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
toolkit/content/browser-child.js
toolkit/content/widgets/remote-browser.xml
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -953,12 +953,86 @@ extensions.registerSchemaAPI("tabs", nul
 
         let currentSettings = this._getZoomSettings(tab.id);
 
         if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
           return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
         }
         return Promise.resolve();
       },
+
+      onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
+        let getZoomLevel = browser => {
+          let {ZoomManager} = browser.ownerDocument.defaultView;
+
+          return ZoomManager.getZoomForBrowser(browser);
+        };
+
+        // Stores the last known zoom level for each tab's browser.
+        // WeakMap[<browser> -> number]
+        let zoomLevels = new WeakMap();
+
+        // Store the zoom level for all existing tabs.
+        for (let window of WindowListManager.browserWindows()) {
+          for (let tab of window.gBrowser.tabs) {
+            let browser = tab.linkedBrowser;
+            zoomLevels.set(browser, getZoomLevel(browser));
+          }
+        }
+
+        let tabCreated = (eventName, event) => {
+          let browser = event.tab.linkedBrowser;
+          zoomLevels.set(browser, getZoomLevel(browser));
+        };
+
+
+        let zoomListener = event => {
+          let browser = event.originalTarget;
+
+          // For non-remote browsers, this event is dispatched on the document
+          // rather than on the <browser>.
+          if (browser instanceof Ci.nsIDOMDocument) {
+            browser = browser.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+                             .getInterface(Ci.nsIDocShell)
+                             .chromeEventHandler;
+          }
+
+          let {gBrowser} = browser.ownerDocument.defaultView;
+          let tab = gBrowser.getTabForBrowser(browser);
+          if (!tab) {
+            // We only care about zoom events in the top-level browser of a tab.
+            return;
+          }
+
+          let oldZoomFactor = zoomLevels.get(browser);
+          let newZoomFactor = getZoomLevel(browser);
+
+          if (oldZoomFactor != newZoomFactor) {
+            zoomLevels.set(browser, newZoomFactor);
+
+            let tabId = TabManager.getId(tab);
+            fire({
+              tabId,
+              oldZoomFactor,
+              newZoomFactor,
+              zoomSettings: self.tabs._getZoomSettings(tabId),
+            });
+          }
+        };
+
+        tabListener.init();
+        tabListener.on("tab-attached", tabCreated);
+        tabListener.on("tab-created", tabCreated);
+
+        AllWindowEvents.addListener("FullZoomChange", zoomListener);
+        AllWindowEvents.addListener("TextZoomChange", zoomListener);
+        return () => {
+          tabListener.off("tab-attached", tabCreated);
+          tabListener.off("tab-created", tabCreated);
+
+          AllWindowEvents.removeListener("FullZoomChange", zoomListener);
+          AllWindowEvents.removeListener("TextZoomChange", zoomListener);
+        };
+      }).api(),
     },
   };
   return self;
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
@@ -35,38 +35,68 @@ add_task(function* () {
     function msg(...args) {
       return new Promise((resolve, reject) => {
         let id = ++_id;
         deferred[id] = {resolve, reject};
         browser.test.sendMessage("msg", id, ...args);
       });
     }
 
-    let checkZoom = (tabId, newValue) => {
+
+    let zoomEvents = [];
+    let eventPromises = [];
+    browser.tabs.onZoomChange.addListener(info => {
+      zoomEvents.push(info);
+      if (eventPromises.length) {
+        eventPromises.shift().resolve();
+      }
+    });
+
+    let checkZoom = (tabId, newValue, oldValue = null) => {
+      let awaitEvent;
+      if (oldValue != null && !zoomEvents.length) {
+        awaitEvent = new Promise(resolve => {
+          eventPromises.push({resolve});
+        });
+      }
+
       return Promise.all([
         browser.tabs.getZoom(tabId),
         msg("get-zoom", tabId),
+        awaitEvent,
       ]).then(([apiZoom, realZoom]) => {
         browser.test.assertEq(newValue, apiZoom, `Got expected zoom value from API`);
         browser.test.assertEq(newValue, realZoom, `Got expected zoom value from parent`);
+
+        if (oldValue != null) {
+          let event = zoomEvents.shift();
+          browser.test.assertEq(tabId, event.tabId, `Got expected zoom event tab ID`);
+          browser.test.assertEq(newValue, event.newZoomFactor, `Got expected zoom event zoom factor`);
+          browser.test.assertEq(oldValue, event.oldZoomFactor, `Got expected zoom event old zoom factor`);
+
+          browser.test.assertEq(3, Object.keys(event.zoomSettings).length, `Zoom settings should have 3 keys`);
+          browser.test.assertEq("automatic", event.zoomSettings.mode, `Mode should be "automatic"`);
+          browser.test.assertEq("per-origin", event.zoomSettings.scope, `Scope should be "per-origin"`);
+          browser.test.assertEq(1, event.zoomSettings.defaultZoomFactor, `Default zoom should be 1`);
+        }
       });
     };
 
     let tabIds;
 
     browser.tabs.query({lastFocusedWindow: true}).then(tabs => {
       browser.test.assertEq(tabs.length, 3, "We have three tabs");
 
       tabIds = [tabs[1].id, tabs[2].id];
 
       return checkZoom(tabIds[0], 1);
     }).then(() => {
       return browser.tabs.setZoom(tabIds[0], 2);
     }).then(() => {
-      return checkZoom(tabIds[0], 2);
+      return checkZoom(tabIds[0], 2, 1);
     }).then(() => {
       return browser.tabs.getZoomSettings(tabIds[0]);
     }).then(zoomSettings => {
       browser.test.assertEq(3, Object.keys(zoomSettings).length, `Zoom settings should have 3 keys`);
       browser.test.assertEq("automatic", zoomSettings.mode, `Mode should be "automatic"`);
       browser.test.assertEq("per-origin", zoomSettings.scope, `Scope should be "per-origin"`);
       browser.test.assertEq(1, zoomSettings.defaultZoomFactor, `Default zoom should be 1`);
 
@@ -75,48 +105,48 @@ add_task(function* () {
     }).then(() => {
       return checkZoom(tabIds[1], 1);
     }).then(() => {
       browser.test.log(`Navigate tab 2 to origin of tab 1`);
       browser.tabs.update(tabIds[1], {url: "http://example.com"});
 
       return promiseUpdated(tabIds[1], "url");
     }).then(() => {
-      return checkZoom(tabIds[1], 2);
+      return checkZoom(tabIds[1], 2, 1);
     }).then(() => {
       browser.test.log(`Update zoom in tab 2, expect changes in both tabs`);
       return browser.tabs.setZoom(tabIds[1], 1.5);
     }).then(() => {
-      return checkZoom(tabIds[1], 1.5);
+      return checkZoom(tabIds[1], 1.5, 2);
     }).then(() => {
       browser.test.log(`Switch to tab 1, expect asynchronous zoom change just after the switch`);
       return browser.tabs.update(tabIds[0], {active: true});
     }).then(() => {
       return new Promise(resolve => setTimeout(resolve, 0));
     }).then(() => {
-      return checkZoom(tabIds[0], 1.5);
+      return checkZoom(tabIds[0], 1.5, 2);
     }).then(() => {
       browser.test.log("Set zoom to 0, expect it set to 1");
       return browser.tabs.setZoom(tabIds[0], 0);
     }).then(() => {
-      return checkZoom(tabIds[0], 1);
+      return checkZoom(tabIds[0], 1, 1.5);
     }).then(() => {
       browser.test.log("Change zoom externally, expect changes reflected");
       return msg("enlarge");
     }).then(() => {
-      return checkZoom(tabIds[0], 1.1);
+      return checkZoom(tabIds[0], 1.1, 1);
     }).then(() => {
       return Promise.all([
         browser.tabs.setZoom(tabIds[0], 0),
         browser.tabs.setZoom(tabIds[1], 0),
       ]);
     }).then(() => {
       return Promise.all([
-        checkZoom(tabIds[0], 1),
-        checkZoom(tabIds[1], 1),
+        checkZoom(tabIds[0], 1, 1.1),
+        checkZoom(tabIds[1], 1, 1.5),
       ]);
     }).then(() => {
       browser.test.log("Check that invalid zoom values throw an error");
       return browser.tabs.setZoom(tabIds[0], 42).then(
         () => {
           browser.test.fail("Expected an error");
         },
         error => {
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -454,23 +454,23 @@ addMessageListener("FullZoom", function 
 });
 
 addMessageListener("TextZoom", function (aMessage) {
   ZoomManager.textZoom = aMessage.data.value;
 });
 
 addEventListener("FullZoomChange", function () {
   if (ZoomManager.refreshFullZoom()) {
-    sendAsyncMessage("FullZoomChange", { value:  ZoomManager.fullZoom});
+    sendAsyncMessage("FullZoomChange", { value: ZoomManager.fullZoom });
   }
 }, false);
 
 addEventListener("TextZoomChange", function (aEvent) {
   if (ZoomManager.refreshTextZoom()) {
-    sendAsyncMessage("TextZoomChange", { value:  ZoomManager.textZoom});
+    sendAsyncMessage("TextZoomChange", { value: ZoomManager.textZoom });
   }
 }, false);
 
 addEventListener("ZoomChangeUsingMouseWheel", function () {
   sendAsyncMessage("ZoomChangeUsingMouseWheel", {});
 }, false);
 
 addMessageListener("UpdateCharacterSet", function (aMessage) {
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -182,29 +182,43 @@
                 readonly="true"/>
 
       <field name="_fullZoom">1</field>
       <property name="fullZoom">
         <getter><![CDATA[
           return this._fullZoom;
         ]]></getter>
         <setter><![CDATA[
+          let changed = val.toFixed(2) != this._fullZoom.toFixed(2);
+
           this._fullZoom = val;
           this.messageManager.sendAsyncMessage("FullZoom", {value: val});
+
+          if (changed) {
+            let event = new Event("FullZoomChange", {bubbles: true});
+            this.dispatchEvent(event);
+          }
         ]]></setter>
       </property>
 
       <field name="_textZoom">1</field>
       <property name="textZoom">
         <getter><![CDATA[
           return this._textZoom;
         ]]></getter>
         <setter><![CDATA[
+          let changed = val.toFixed(2) != this._textZoom.toFixed(2);
+
           this._textZoom = val;
           this.messageManager.sendAsyncMessage("TextZoom", {value: val});
+
+          if (changed) {
+            let event = new Event("TextZoomChange", {bubbles: true});
+            this.dispatchEvent(event);
+          }
         ]]></setter>
       </property>
 
       <field name="_isSyntheticDocument">false</field>
       <property name="isSyntheticDocument">
         <getter><![CDATA[
           return this._isSyntheticDocument;
         ]]></getter>