--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -856,12 +856,67 @@ extensions.registerSchemaAPI("tabs", nul
newTab.addEventListener("SSTabRestored", function listener() {
// Once it has been restored, select it and return the promise.
newTab.removeEventListener("SSTabRestored", listener);
gBrowser.selectedTab = newTab;
return resolve(TabManager.convert(extension, newTab));
});
});
},
+
+ getZoom(tabId) {
+ let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
+
+ let {ZoomManager} = tab.ownerDocument.defaultView;
+ let zoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+
+ return Promise.resolve(zoom);
+ },
+
+ setZoom(tabId, zoom) {
+ let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
+
+ let {FullZoom, ZoomManager} = tab.ownerDocument.defaultView;
+
+ if (zoom === 0) {
+ // A value of zero means use the default zoom factor.
+ return FullZoom.reset(tab.linkedBrowser);
+ } else if (zoom >= ZoomManager.MIN && zoom <= ZoomManager.MAX) {
+ FullZoom.setZoom(zoom, tab.linkedBrowser);
+ } else {
+ return Promise.reject({
+ message: `Zoom value ${zoom} out of range (must be between ${ZoomManager.MIN} and ${ZoomManager.MAX})`,
+ });
+ }
+
+ return Promise.resolve();
+ },
+
+ _getZoomSettings(tabId) {
+ let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
+
+ let {FullZoom} = tab.ownerDocument.defaultView;
+
+ return {
+ mode: "automatic",
+ scope: FullZoom.siteSpecific ? "per-origin" : "per-tab",
+ defaultZoomFactor: 1,
+ };
+ },
+
+ getZoomSettings(tabId) {
+ return Promise.resolve(this._getZoomSettings(tabId));
+ },
+
+ setZoomSettings(tabId, settings) {
+ let tab = tabId ? TabManager.getTab(tabId) : TabManager.activeTab;
+
+ 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();
+ },
},
};
return self;
});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
@@ -0,0 +1,182 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const SITE_SPECIFIC_PREF = "browser.zoom.siteSpecific";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+
+ gBrowser.selectedTab = tab1;
+
+ function background() {
+ function promiseUpdated(tabId, attr) {
+ return new Promise(resolve => {
+ let onUpdated = (tabId_, changeInfo, tab) => {
+ if (tabId == tabId_ && attr in changeInfo) {
+ browser.tabs.onUpdated.removeListener(onUpdated);
+
+ resolve({changeInfo, tab});
+ }
+ };
+ browser.tabs.onUpdated.addListener(onUpdated);
+ });
+ }
+
+ let deferred = {};
+ browser.test.onMessage.addListener((message, msg, result) => {
+ if (message == "msg-done" && deferred[msg]) {
+ deferred[msg].resolve(result);
+ }
+ });
+
+ let _id = 0;
+ 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) => {
+ return Promise.all([
+ browser.tabs.getZoom(tabId),
+ msg("get-zoom", tabId),
+ ]).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`);
+ });
+ };
+
+ 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);
+ }).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`);
+
+ browser.test.log(`Switch to tab 2`);
+ return browser.tabs.update(tabIds[1], {active: true});
+ }).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);
+ }).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);
+ }).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);
+ }).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);
+ }).then(() => {
+ browser.test.log("Change zoom externally, expect changes reflected");
+ return msg("enlarge");
+ }).then(() => {
+ return checkZoom(tabIds[0], 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),
+ ]);
+ }).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 => {
+ browser.test.assertTrue(error.message.includes("Zoom value 42 out of range"),
+ "Got expected error");
+ });
+ }).then(() => {
+ browser.test.log("Disable site-specific zoom, expect correct scope");
+ return msg("site-specific", false);
+ }).then(() => {
+ return browser.tabs.getZoomSettings(tabIds[0]);
+ }).then(zoomSettings => {
+ browser.test.assertEq("per-tab", zoomSettings.scope, `Scope should be "per-tab"`);
+ }).then(() => {
+ return msg("site-specific", null);
+ }).then(() => {
+ browser.test.notifyPass("tab-zoom");
+ }).catch(e => {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("tab-zoom");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ extension.onMessage("msg", (id, msg, ...args) => {
+ let {TabManager} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let resp;
+ if (msg == "get-zoom") {
+ let tab = TabManager.getTab(args[0]);
+ resp = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+ } else if (msg == "set-zoom") {
+ let tab = TabManager.getTab(args[0]);
+ ZoomManager.setZoomForBrowser(tab.linkedBrowser);
+ } else if (msg == "enlarge") {
+ FullZoom.enlarge();
+ } else if (msg == "site-specific") {
+ if (args[0] == null) {
+ SpecialPowers.clearUserPref(SITE_SPECIFIC_PREF);
+ } else {
+ SpecialPowers.setBoolPref(SITE_SPECIFIC_PREF, args[0]);
+ }
+ }
+
+ extension.sendMessage("msg-done", id, resp);
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("tab-zoom");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});