Bug 1238310: Part 5 - Implement the browser.tabs.onZoomChange event. r?gabor f?aswan
MozReview-Commit-ID: JlxSM13SeYg
--- 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>