--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -1,15 +1,20 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
// The ext-* files are imported into the same scopes.
/* import-globals-from ext-utils.js */
+XPCOMUtils.defineLazyGetter(this, "strBundle", function() {
+ const stringSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
+ return stringSvc.createBundle("chrome://global/locale/extensions.properties");
+});
+
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
@@ -746,13 +751,149 @@ this.tabs = class extends ExtensionAPI {
return () => {
tabTracker.off("tab-attached", tabCreated);
tabTracker.off("tab-created", tabCreated);
windowTracker.removeListener("FullZoomChange", zoomListener);
windowTracker.removeListener("TextZoomChange", zoomListener);
};
}).api(),
+
+ print() {
+ let activeTab = getTabOrActive(null);
+ let {PrintUtils} = activeTab.ownerGlobal;
+
+ PrintUtils.printWindow(activeTab.linkedBrowser.outerWindowID, activeTab.linkedBrowser);
+ },
+
+ printPreview() {
+ let activeTab = getTabOrActive(null);
+ let {
+ PrintUtils,
+ PrintPreviewListener,
+ } = activeTab.ownerGlobal;
+
+ return new Promise(resolve => {
+ let ppBrowser = PrintUtils._shouldSimplify ?
+ PrintPreviewListener.getSimplifiedPrintPreviewBrowser() :
+ PrintPreviewListener.getPrintPreviewBrowser();
+
+ let mm = ppBrowser.messageManager;
+
+ let onEntered = (message) => {
+ mm.removeMessageListener("Printing:Preview:Entered", onEntered);
+ if (message.data.failed) {
+ throw new ExtensionError("Print preview failed");
+ }
+ resolve();
+ };
+
+ mm.addMessageListener("Printing:Preview:Entered", onEntered);
+
+ PrintUtils.printPreview(PrintPreviewListener);
+ });
+ },
+
+ saveAsPDF(pageSettings) {
+ let activeTab = getTabOrActive(null);
+ let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let title = strBundle.GetStringFromName("saveaspdf.saveasdialog.title");
+
+ picker.init(activeTab.ownerGlobal, title, Ci.nsIFilePicker.modeSave);
+ picker.appendFilter("PDF", "*.pdf");
+ picker.defaultExtension = "pdf";
+ picker.defaultString = activeTab.linkedBrowser.contentTitle + ".pdf";
+
+ return new Promise(resolve => {
+ picker.open(function(retval) {
+ if (retval == 0 || retval == 2) {
+ // OK clicked (retval == 0) or replace confirmed (retval == 2)
+ try {
+ let fstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ fstream.init(picker.file, 0x2A, 0x1B6, 0); // write|create|truncate, file permissions rw-rw-rw- = 0666 = 0x1B6
+ fstream.close(); // unlock file
+ } catch (e) {
+ resolve(retval == 0 ? "Not saved" : "Not replaced");
+ return;
+ }
+
+ let psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(Ci.nsIPrintSettingsService);
+ let printSettings = psService.newPrintSettings;
+
+ printSettings.printToFile = true;
+ printSettings.toFileName = picker.file.path;
+
+ printSettings.printSilent = true;
+ printSettings.showPrintProgress = false;
+
+ printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs;
+ printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
+
+ if (pageSettings.orientation !== null) {
+ printSettings.orientation = pageSettings.orientation;
+ }
+ if (pageSettings.scaling !== null) {
+ printSettings.scaling = pageSettings.scaling;
+ }
+ if (pageSettings.shrinkToFit !== null) {
+ printSettings.shrinkToFit = pageSettings.shrinkToFit;
+ }
+ if (pageSettings.showBackgroundColors !== null) {
+ printSettings.printBGColors = pageSettings.showBackgroundColors;
+ }
+ if (pageSettings.showBackgroundImages !== null) {
+ printSettings.printBGImages = pageSettings.showBackgroundImages;
+ }
+ if (pageSettings.paperSizeUnit !== null) {
+ printSettings.paperSizeUnit = pageSettings.paperSizeUnit;
+ }
+ if (pageSettings.paperWidth !== null) {
+ printSettings.paperWidth = pageSettings.paperWidth;
+ }
+ if (pageSettings.paperHeight !== null) {
+ printSettings.paperHeight = pageSettings.paperHeight;
+ }
+ if (pageSettings.headerLeft !== null) {
+ printSettings.headerStrLeft = pageSettings.headerLeft;
+ }
+ if (pageSettings.headerCenter !== null) {
+ printSettings.headerStrCenter = pageSettings.headerCenter;
+ }
+ if (pageSettings.headerRight !== null) {
+ printSettings.headerStrRight = pageSettings.headerRight;
+ }
+ if (pageSettings.footerLeft !== null) {
+ printSettings.footerStrLeft = pageSettings.footerLeft;
+ }
+ if (pageSettings.footerCenter !== null) {
+ printSettings.footerStrCenter = pageSettings.footerCenter;
+ }
+ if (pageSettings.footerRight !== null) {
+ printSettings.footerStrRight = pageSettings.footerRight;
+ }
+ if (pageSettings.marginLeft !== null) {
+ printSettings.marginLeft = pageSettings.marginLeft;
+ }
+ if (pageSettings.marginRight !== null) {
+ printSettings.marginRight = pageSettings.marginRight;
+ }
+ if (pageSettings.marginTop !== null) {
+ printSettings.marginTop = pageSettings.marginTop;
+ }
+ if (pageSettings.marginBottom !== null) {
+ printSettings.marginBottom = pageSettings.marginBottom;
+ }
+
+ activeTab.linkedBrowser.print(activeTab.linkedBrowser.outerWindowID, printSettings, null);
+
+ resolve(retval == 0 ? "Saved" : "Replaced");
+ } else {
+ // Cancel clicked (retval == 1)
+ resolve("Cancelled");
+ }
+ });
+ });
+ },
},
};
return self;
}
};
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -129,16 +129,113 @@
"defaultZoomFactor": {
"type": "number",
"optional": true,
"description": "Used to return the default zoom level for the current tab in calls to tabs.getZoomSettings."
}
}
},
{
+ "id": "PageSettings",
+ "type": "object",
+ "description": "The page settings including: orientation, scale, background, margins, headers, footers.",
+ "properties": {
+ "orientation": {
+ "type": "integer",
+ "optional": true,
+ "description": "The page content orientation: 0 = portrait, 1 = landscape. Default: 0."
+ },
+ "scaling": {
+ "type": "number",
+ "optional": true,
+ "description": "The page content scaling factor: 1.0 = 100% = normal size. Default: 1.0."
+ },
+ "shrinkToFit": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the page content should shrink to fit the page width (overrides scaling). Default: true."
+ },
+ "showBackgroundColors": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the page background colors should be shown. Default: false."
+ },
+ "showBackgroundImages": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the page background images should be shown. Default: false."
+ },
+ "paperSizeUnit": {
+ "type": "integer",
+ "optional": true,
+ "description": "The page size unit: 0 = inches, 1 = millimeters. Default: 0."
+ },
+ "paperWidth": {
+ "type": "number",
+ "optional": true,
+ "description": "The paper width in paper size units. Default: 8.5."
+ },
+ "paperHeight": {
+ "type": "number",
+ "optional": true,
+ "description": "The paper height in paper size units. Default: 11.0."
+ },
+ "headerLeft": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the page's left header. Default: '&T'."
+ },
+ "headerCenter": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the page's center header. Default: ''."
+ },
+ "headerRight": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the page's right header. Default: '&U'."
+ },
+ "footerLeft": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the page's left footer. Default: '&PT'."
+ },
+ "footerCenter": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the page's center footer. Default: ''."
+ },
+ "footerRight": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the page's right footer. Default: '&D'."
+ },
+ "marginLeft": {
+ "type": "number",
+ "optional": true,
+ "description": "The margin between the page content and the left edge of the paper (inches). Default: 0.5."
+ },
+ "marginRight": {
+ "type": "number",
+ "optional": true,
+ "description": "The margin between the page content and the right edge of the paper (inches). Default: 0.5."
+ },
+ "marginTop": {
+ "type": "number",
+ "optional": true,
+ "description": "The margin between the page content and the top edge of the paper (inches). Default: 0.5."
+ },
+ "marginBottom": {
+ "type": "number",
+ "optional": true,
+ "description": "The margin between the page content and the bottom edge of the paper (inches). Default: 0.5."
+ }
+ }
+ },
+ {
"id": "TabStatus",
"type": "string",
"enum": ["loading", "complete"],
"description": "Whether the tabs have completed loading."
},
{
"id": "WindowType",
"type": "string",
@@ -1009,16 +1106,63 @@
{
"$ref": "ZoomSettings",
"name": "zoomSettings",
"description": "The tab's current zoom settings."
}
]
}
]
+ },
+ {
+ "name": "print",
+ "type": "function",
+ "description": "Prints page in active tab.",
+ "parameters": []
+ },
+ {
+ "name": "printPreview",
+ "type": "function",
+ "description": "Shows print preview for page in active tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called after print preview entered.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "saveAsPDF",
+ "type": "function",
+ "description": "Saves page in active tab as a PDF file.",
+ "async": "callback",
+ "parameters": [
+ {
+ "$ref": "PageSettings",
+ "name": "pageSettings",
+ "description": "The page settings used to save the PDF file."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called after save as dialog closed.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "status",
+ "description": "Save status: Saved, Replaced, Cancelled, Not Saved, Not Replaced."
+ }
+ ]
+ }
+ ]
}
],
"events": [
{
"name": "onCreated",
"type": "function",
"description": "Fired when a tab is created. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.",
"parameters": [
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -125,16 +125,17 @@ skip-if = debug || asan # Bug 1354681
[browser_ext_tabs_insertCSS.js]
[browser_ext_tabs_removeCSS.js]
[browser_ext_tabs_move_array.js]
[browser_ext_tabs_move_window.js]
[browser_ext_tabs_move_window_multiple.js]
[browser_ext_tabs_move_window_pinned.js]
[browser_ext_tabs_onHighlighted.js]
[browser_ext_tabs_onUpdated.js]
+[browser_ext_tabs_printPreview.js]
[browser_ext_tabs_query.js]
[browser_ext_tabs_reload.js]
[browser_ext_tabs_reload_bypass_cache.js]
[browser_ext_tabs_sendMessage.js]
[browser_ext_tabs_cookieStoreId.js]
[browser_ext_tabs_update.js]
[browser_ext_tabs_zoom.js]
[browser_ext_tabs_update_url.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_printPreview.js
@@ -0,0 +1,43 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function testPrintPreview() {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: async function() {
+ await browser.tabs.printPreview();
+ browser.test.assertTrue(true, "print preview entered");
+ browser.test.notifyPass("tabs.printPreview");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("tabs.printPreview");
+ await extension.unload();
+
+ let ppTab = PrintUtils._shouldSimplify ?
+ PrintPreviewListener._simplifiedPrintPreviewTab :
+ PrintPreviewListener._printPreviewTab;
+
+ let ppToolbar = document.getElementById("print-preview-toolbar");
+
+ is(window.gInPrintPreviewMode, true, "window in print preview mode");
+
+ isnot(ppTab, null, "print preview tab created");
+ isnot(ppTab.linkedBrowser, null, "print preview browser created");
+ isnot(ppToolbar, null, "print preview toolbar created");
+
+ is(ppTab, gBrowser.selectedTab, "print preview tab selected");
+ is(ppTab.linkedBrowser.currentURI.spec, "about:printpreview", "print preview browser url correct");
+
+ PrintUtils.exitPrintPreview();
+ await BrowserTestUtils.waitForCondition(() => !window.gInPrintPreviewMode);
+
+ await BrowserTestUtils.removeTab(gBrowser.tabs[1]);
+});
--- a/browser/components/extensions/test/mochitest/test_ext_all_apis.html
+++ b/browser/components/extensions/test/mochitest/test_ext_all_apis.html
@@ -38,20 +38,23 @@ let expectedBackgroundApisTargetSpecific
"tabs.onCreated",
"tabs.onDetached",
"tabs.onHighlighted",
"tabs.onMoved",
"tabs.onRemoved",
"tabs.onReplaced",
"tabs.onUpdated",
"tabs.onZoomChange",
+ "tabs.print",
+ "tabs.printPreview",
"tabs.query",
"tabs.reload",
"tabs.remove",
"tabs.removeCSS",
+ "tabs.saveAsPDF",
"tabs.sendMessage",
"tabs.setZoom",
"tabs.setZoomSettings",
"tabs.update",
"windows.CreateType",
"windows.WINDOW_ID_CURRENT",
"windows.WINDOW_ID_NONE",
"windows.WindowState",
--- a/toolkit/locales/en-US/chrome/global/extensions.properties
+++ b/toolkit/locales/en-US/chrome/global/extensions.properties
@@ -22,8 +22,10 @@ csp.error.illegal-host-wildcard = %2$S: wildcard sources in ā%1$Sā directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)
#LOCALIZATION NOTE (uninstall.confirmation.title) %S is the name of the extension which is about to be uninstalled.
uninstall.confirmation.title = Uninstall %S
#LOCALIZATION NOTE (uninstall.confirmation.message) %S is the name of the extension which is about to be uninstalled.
uninstall.confirmation.message = The extension ā%Sā is requesting to be uninstalled. What would you like to do?
uninstall.confirmation.button-0.label = Uninstall
uninstall.confirmation.button-1.label = Keep Installed
+
+saveaspdf.saveasdialog.title = Save As