Bug 1311586 - Implement method |PPB_PDF::Print|. r=brsun
authorlochang <ghoo1125@yahoo.com.tw>
Tue, 14 Mar 2017 15:22:07 +0800
changeset 348657 61809db0f8287313e8fe036c0c0b49f4ff4212a8
parent 348656 a8ef959f05f6741a8778959f7d1db6fa38082d40
child 348658 52ed0ff336482d468f9d2ffc99621cc1ff39eb5d
push id31533
push userkwierso@gmail.com
push dateTue, 21 Mar 2017 23:08:53 +0000
treeherdermozilla-central@8744e9f8eb99 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbrsun
bugs1311586
milestone55.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 1311586 - Implement method |PPB_PDF::Print|. r=brsun From eb817ace782f0c7deb420e61c5d9d6185c665e28 Mon Sep 17 00:00:00 2001 --- .../mortar/host/common/ppapi-runtime.jsm | 205 +++++++++++++++++++++ .../mortar/host/pdf/chrome/js/toolbar.js | 4 + .../mortar/host/pdf/chrome/js/viewport.js | 6 + .../mortar/host/pdf/ppapi-content-sandbox.js | 70 +++++++ 4 files changed, 285 insertions(+)
browser/extensions/mortar/host/common/ppapi-runtime.jsm
browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
browser/extensions/mortar/host/pdf/chrome/js/viewport.js
browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
--- a/browser/extensions/mortar/host/common/ppapi-runtime.jsm
+++ b/browser/extensions/mortar/host/common/ppapi-runtime.jsm
@@ -48,16 +48,23 @@ const PP_ERROR_CONNECTION_ABORTED = -103
 const PP_ERROR_CONNECTION_FAILED = -104;
 const PP_ERROR_CONNECTION_TIMEDOUT = -105;
 const PP_ERROR_ADDRESS_INVALID = -106;
 const PP_ERROR_ADDRESS_UNREACHABLE = -107;
 const PP_ERROR_ADDRESS_IN_USE = -108;
 const PP_ERROR_MESSAGE_TOO_BIG = -109;
 const PP_ERROR_NAME_NOT_RESOLVED = -110;
 
+// Point is defined as 1/72 of an inch (25.4mm)
+const POINT_PER_INCH = 72;
+const POINT_PER_MILLIMETER = POINT_PER_INCH / 25.4;
+
+const PRINT_FILE_NAME = "print.pdf";
+const PRINT_CONTENT_TEMP_KEY =
+  (Services.appinfo.OS == "Linux") ? "TmpD" : "ContentTmpD";
 
 const PP_Bool = {
   PP_FALSE: 0,
   PP_TRUE: 1,
 };
 
 const PP_AudioFrameSize = {
   PP_AUDIOMINSAMPLEFRAMECOUNT: 64,
@@ -257,16 +264,36 @@ const PP_NetworkList_State = {
 
 const PP_NetworkList_Type = {
   PP_NETWORKLIST_TYPE_UNKNOWN: 0,
   PP_NETWORKLIST_TYPE_ETHERNET: 1,
   PP_NETWORKLIST_TYPE_WIFI: 2,
   PP_NETWORKLIST_TYPE_CELLULAR: 3
 };
 
+const PP_PrintOrientation_Dev = {
+  PP_PRINTORIENTATION_NORMAL: 0,
+  PP_PRINTORIENTATION_ROTATED_90_CW: 1,
+  PP_PRINTORIENTATION_ROTATED_180: 2,
+  PP_PRINTORIENTATION_ROTATED_90_CCW: 3
+};
+
+const PP_PrintOutputFormat_Dev = {
+  PP_PRINTOUTPUTFORMAT_RASTER: 1 << 0,
+  PP_PRINTOUTPUTFORMAT_PDF: 1 << 1,
+  PP_PRINTOUTPUTFORMAT_POSTSCRIPT: 1 << 2,
+  PP_PRINTOUTPUTFORMAT_EMF: 1 << 3
+};
+
+const PP_PrintScalingOption_Dev = {
+  PP_PRINTSCALINGOPTION_NONE: 0,
+  PP_PRINTSCALINGOPTION_FIT_TO_PRINTABLE_AREA: 1,
+  PP_PRINTSCALINGOPTION_SOURCE_SIZE: 2
+};
+
 const PP_TextInput_Type_Dev = {
   PP_TEXTINPUT_TYPE_DEV_NONE: 0,
   PP_TEXTINPUT_TYPE_DEV_TEXT: 1,
   PP_TEXTINPUT_TYPE_DEV_PASSWORD: 2,
   PP_TEXTINPUT_TYPE_DEV_SEARCH: 3,
   PP_TEXTINPUT_TYPE_DEV_EMAIL: 4,
   PP_TEXTINPUT_TYPE_DEV_NUMBER: 5,
   PP_TEXTINPUT_TYPE_DEV_TELEPHONE: 6,
@@ -325,16 +352,29 @@ const PR_RDONLY = 0x01;
 const PR_WRONLY = 0x02;
 const PR_RDWR = 0x04;
 const PR_CREATE_FILE = 0x08;
 const PR_APPEND = 0x10;
 const PR_TRUNCATE = 0x20;
 const PR_SYNC = 0x40;
 const PR_EXCL = 0x80;
 
+/* File mode bits */
+const PR_IRWXU = 0o700;  /* read, write, execute/search by owner */
+const PR_IRUSR = 0o400;  /* read permission, owner */
+const PR_IWUSR = 0o200;  /* write permission, owner */
+const PR_IXUSR = 0o100;  /* execute/search permission, owner */
+const PR_IRWXG = 0o070;  /* read, write, execute/search by group */
+const PR_IRGRP = 0o040;  /* read permission, group */
+const PR_IWGRP = 0o020;  /* write permission, group */
+const PR_IXGRP = 0o010;  /* execute/search permission, group */
+const PR_IRWXO = 0o007;  /* read, write, execute/search by others */
+const PR_IROTH = 0o004;  /* read permission, others */
+const PR_IWOTH = 0o002;  /* write permission, others */
+const PR_IXOTH = 0o001;  /* execute/search permission, others */
 
 class InterfaceMemberCall {
   constructor(interfaceName, memberName, args) {
     this.__interface = interfaceName;
     this.__member = memberName;
     Object.assign(this, args);
   }
 }
@@ -1536,16 +1576,18 @@ class PPAPIInstance {
     this.containerWindow = containerWindow;
     this.mm = mm;
     this.eventHandlers = 0;
     this.filteringEventHandlers = 0;
     this.throttled_ = false;
     this.cachedImageData = null;
     this.viewport = new PPAPIViewport(this);
     this.selectedText = "";
+    this.ppapiPrintSettings = {};
+    this.pageRangeInfo = {};
 
     this.notifyHashChange(info.url);
 
     this.mm.addMessageListener("ppapi.js:fullscreenchange", (evt) => {
       this.viewport.notify({
         type: "fullscreenChange",
         fullscreen: evt.data.fullscreen
       });
@@ -1556,16 +1598,86 @@ class PPAPIInstance {
     });
 
     this.mm.addMessageListener("ppapipdf.js:oncommand", (evt) => {
       this.viewport.notify({
         type: "command",
         name: evt.data.name
       });
     });
+
+    this.mm.addMessageListener("ppapipdf.js:printsettingschanged", (evt) => {
+      // Convert trim print settings to ppapi print settings
+      let ps = evt.data.trimPrintSettings;
+      let point = (ps.paperSizeUnit == Ci.nsIPrintSettings.kPaperSizeInches)
+        ? POINT_PER_INCH : POINT_PER_MILLIMETER;
+      // Unit of margins are in inches but paper size could be inches or mm
+      this.ppapiPrintSettings.printable_area = {
+        point: {
+          x: ps.unwriteableMarginLeft * POINT_PER_INCH,
+          y: ps.unwriteableMarginTop * POINT_PER_INCH
+        },
+        size: {
+          width: ps.paperWidth * point - (ps.unwriteableMarginLeft +
+                 ps.unwriteableMarginRight) * POINT_PER_INCH,
+          height: ps.paperHeight * point - (ps.unwriteableMarginTop +
+                  ps.unwriteableMarginBottom) * POINT_PER_INCH
+        }
+      };
+      this.ppapiPrintSettings.content_area = {
+        point: {
+          x: ps.marginLeft * POINT_PER_INCH,
+          y: ps.marginTop * POINT_PER_INCH
+        },
+        size: {
+          width: ps.paperWidth * point - (ps.marginLeft +
+                 ps.marginRight) * POINT_PER_INCH,
+          height: ps.paperHeight * point - (ps.marginTop +
+                  ps.marginBottom) * POINT_PER_INCH
+        }
+      };
+      this.ppapiPrintSettings.paper_size = {
+        width: ps.paperWidth * point,
+        height: ps.paperHeight * point
+      };
+      // XXX The print dialog does not provide a way for user to set resolution
+      //     which causes us not able to access the resolution. So here we
+      //     workaround by setting resolution to a default value 0 and pass it
+      //     to PDFium for getting PDF file. It is fine to pass a unmeaningful
+      //     value of resolution when print as PDF.
+      this.ppapiPrintSettings.dpi = 0;
+      // XXX We only support kPortraitOrientation and kLandscapeOreintation,
+      //     otherwise we return directly.
+      switch (ps.orientation) {
+        case Ci.nsIPrintSettings.kPortraitOrientation:
+          this.ppapiPrintSettings.orientation =
+            PP_PrintOrientation_Dev.PP_PRINTORIENTATION_NORMAL;
+          break;
+        case Ci.nsIPrintSettings.kLandscapeOrientation:
+          this.ppapiPrintSettings.orientation =
+            PP_PrintOrientation_Dev.PP_PRINTORIENTATION_ROTATED_90_CW;
+          break;
+        default:
+          return;
+      }
+      this.ppapiPrintSettings.print_scaling_option = ps.shrinkToFit ?
+        PP_PrintScalingOption_Dev.PP_PRINTSCALINGOPTION_FIT_TO_PRINTABLE_AREA :
+        PP_PrintScalingOption_Dev.PP_PRINTSCALINGOPTION_NONE;
+      this.ppapiPrintSettings.grayscale = !ps.printInColor;
+      this.ppapiPrintSettings.format =
+        PP_PrintOutputFormat_Dev.PP_PRINTOUTPUTFORMAT_PDF;
+
+      // Store page range information
+      this.pageRangeInfo.printRange = ps.printRange;
+      this.pageRangeInfo.startPageRange = ps.startPageRange;
+      this.pageRangeInfo.endPageRange = ps.endPageRange;
+
+      let message = {type: 'print'};
+      this.viewportActionHandler(message);
+    });
   }
 
   notifyHashChange(url) {
     let location = new URL(url);
     if (location.hash) {
       this.viewport.notify({
         type: "hashChange",
         // substring(1) for getting rid of the first '#' character
@@ -1699,23 +1811,28 @@ class PPAPIInstance {
         this.mm.sendAsyncMessage("ppapi.js:setFullscreen", message.fullscreen);
         break;
       case 'save':
         this.mm.sendAsyncMessage("ppapipdf.js:save");
         break;
       case 'setHash':
         this.mm.sendAsyncMessage("ppapipdf.js:setHash", message.hash);
         break;
+      case 'startPrint':
+        // We need permission for showing print dialog to get print settings
+        this.mm.sendAsyncMessage("ppapipdf.js:getPrintSettings");
+        break;
       case 'viewport':
       case 'rotateClockwise':
       case 'rotateCounterclockwise':
       case 'selectAll':
       case 'getSelectedText':
       case 'getNamedDestination':
       case 'getPasswordComplete':
+      case 'print':
         let data = PP_Var.fromJSValue(new Dictionary(message), this);
         this.rt.call(new InterfaceMemberCall("PPP_Messaging;1.0", "HandleMessage",
           { instance: this, var: data }));
         break;
       default:
         throw new Error(`Invalid message type "${message.type}".`);
     }
   }
@@ -4806,16 +4923,104 @@ dump(`callFromJSON: < ${JSON.stringify(c
      *                                     [in] PP_BrowserFont_Trusted_Description description,
      *                                     [in] PP_PrivateFontCharset charset);
      */
     PPB_PDF_GetFontFileWithFallback: function(json) {
       return 0;
     },
 
     /**
+     * void Print(
+     *   [in] PP_Instance instance);
+     */
+    PPB_PDF_Print: function(json) {
+      let instance = this.instances[json.instance];
+      let pageRangeInfo = instance.pageRangeInfo;
+
+      // Query supported formats
+      let supportedFormats = instance.rt.call(new InterfaceMemberCall(
+        "PPP_Printing(Dev);0.6", "QuerySupportedFormats", { instance }), true);
+      // XXX We only support PDF output format now.
+      if (!(PP_PrintOutputFormat_Dev.PP_PRINTOUTPUTFORMAT_PDF &
+          supportedFormats)) {
+        return;
+      }
+
+      // Begin of printing
+      let totalPageNumber = instance.rt.call(new InterfaceMemberCall(
+        "PPP_Printing(Dev);0.6", "Begin", { instance, print_settings:
+        instance.ppapiPrintSettings }), true);
+      if (totalPageNumber <= 0) {
+        return;
+      }
+
+      // Get PDF file
+      if (pageRangeInfo.printRange == Ci.nsIPrintSettings.kRangeAllPages) {
+        pageRangeInfo.startPageRange = 1;
+        pageRangeInfo.endPageRange = totalPageNumber;
+      } else {
+        if (pageRangeInfo.startPageRange < 1) {
+          pageRanges.startPageRange = 1;
+        }
+        if (pageRangeInfo.endPageRange > totalPageNumber) {
+          pageRangeInfo.endPageRange = totalPageNumber;
+        }
+        if (pageRangeInfo.startPageRange > pageRangeInfo.endPageRange) {
+          return;
+        }
+      }
+      // XXX We only support one page range now:
+      //     Gecko is using OS native print dialog now. And the print dialog
+      //     on Linux do support multiple page ranges. However, there is no
+      //     existing XPCOM interface for us to get the multiple page ranges
+      //     information. In addition, the print dialog on Mac and Windows only
+      //     support one page range.
+      //
+      //     The behavior of printing muliple page ranges on Linux now is we
+      //     will print out the pages from the minimum start page to the
+      //     maximum last page of the page ranges.
+      let pageRanges = [{ from: pageRangeInfo.startPageRange - 1,
+        to: pageRangeInfo.endPageRange - 1 }];
+      let pageRangeCount = pageRanges.length;
+      let bufferId = instance.rt.call(new InterfaceMemberCall(
+        "PPP_Printing(Dev);0.6", "PrintPages", {
+          instance, page_ranges: pageRanges, page_range_count: pageRangeCount
+        }), true);
+      if (!bufferId) {
+        return;
+      }
+      let buffer = PP_Resource.lookup(bufferId);
+
+      // Save PDF to file
+      let file = Services.dirsvc.get(PRINT_CONTENT_TEMP_KEY, Ci.nsIFile);
+      file.append(PRINT_FILE_NAME);
+      file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR);
+      let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+                   createInstance(Ci.nsIFileOutputStream);
+      stream.init(file, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
+                  PR_IRUSR | PR_IWUSR, 0);
+      let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
+                         createInstance(Ci.nsIBinaryOutputStream);
+      binaryStream.setOutputStream(stream);
+      let data = new Uint8ClampedArray(instance.rt.getCachedBuffer(buffer.mem));
+      binaryStream.writeByteArray(Array.from(data), buffer.size);
+
+      // End of printing
+      stream.flush();
+      stream.close();
+      buffer.unmap();
+      buffer.release();
+      instance.rt.call(new InterfaceMemberCall(
+        "PPP_Printing(Dev);0.6", "End", { instance }), true);
+      // We need permission for printing PDF file
+      instance.mm.sendAsyncMessage("ppapipdf.js:printPDF", {
+        contentTempKey: PRINT_CONTENT_TEMP_KEY, fileName: PRINT_FILE_NAME });
+    },
+
+    /**
      * void SearchString(
      *   [in] PP_Instance instance,
      *   [in] mem_t str,
      *   [in] mem_t term,
      *   [in] PP_Bool case_sensitive,
      *   [out, size_is(count)] PP_PrivateFindResult[] results,
      *   [out] int32_t count);
      */
--- a/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
@@ -192,16 +192,20 @@ class Toolbar {
         break;
       case 'zoomOut':
         this._zoomOut();
         break;
       case 'download':
       case 'secondaryDownload':
         this._viewport.save();
         break;
+      case 'print':
+      case 'secondaryPrint':
+        this._viewport.print();
+        break;
       case 'pageRotateCw':
         this._viewport.rotateClockwise();
         break;
       case 'pageRotateCcw':
         this._viewport.rotateCounterClockwise();
         break;
       case 'presentationMode':
       case 'secondaryPresentationMode':
--- a/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
@@ -654,16 +654,22 @@ class Viewport {
   }
 
   save() {
     this._doAction({
       type: 'save'
     });
   }
 
+  print() {
+    this._doAction({
+      type: 'startPrint'
+    });
+  }
+
   // A handler for delivering messages to runtime.
   registerActionHandler(handler) {
     if (typeof handler === 'function') {
       this._actionHandler = handler;
     }
   }
 
   createBookmarkHash() {
--- a/browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
+++ b/browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
@@ -7,24 +7,29 @@
 /**
  * This code runs in the sandbox in the content process where the page that
  * loaded the plugin lives. It communicates with the process where the PPAPI
  * implementation lives.
  */
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                           "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                              "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 let mm = pluginElement.frameLoader.messageManager;
 let containerWindow = pluginElement.ownerGlobal;
+// We have to cache the print settings for printing PDF file here, since in
+// general we are not able to send XPCOM object across processes.
+let printSettings = {};
+
 // Prevent the drag event's default action on the element, to avoid dragging of
 // that element while the user selects text.
 pluginElement.addEventListener("dragstart",
                                function(event) { event.preventDefault(); });
 // For synthetic documents only, prevent the select event's default action on
 // the element, to avoid selecting the element and the copying of an empty
 // string into the clipboard. Don't do this for non-synthetic documents, as
 // users still need to be able to select text outside the plugin.
@@ -105,16 +110,81 @@ mm.addMessageListener("ppapi.js:setFulls
 });
 
 mm.addMessageListener("ppapipdf.js:setHash", ({ data }) => {
   if (data) {
     containerWindow.location.hash = data;
   }
 });
 
+mm.addMessageListener("ppapipdf.js:getPrintSettings", () => {
+  let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].
+              getService(Ci.nsIPrintSettingsService);
+  printSettings = PSSVC.globalPrintSettings;
+  let webBrowserPrint =
+    containerWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+    getInterface(Ci.nsIWebBrowserPrint);
+  let PPSVC = Cc["@mozilla.org/embedcomp/printingprompt-service;1"].
+              getService(Ci.nsIPrintingPromptService);
+  PPSVC.showPrintDialog(containerWindow, webBrowserPrint, printSettings);
+
+  // XPCOM object should not in general be sent across processes. So we have to
+  // copy out only the info PDFium needed and send it back to jsplugin process
+  // for getting PDF file.
+  let trimPrintSettings = {};
+  trimPrintSettings.marginTop = printSettings.marginTop;
+  trimPrintSettings.marginLeft = printSettings.marginLeft;
+  trimPrintSettings.marginBottom = printSettings.marginBottom;
+  trimPrintSettings.marginRight = printSettings.marginRight;
+  trimPrintSettings.unwriteableMarginLeft =
+    printSettings.unwriteableMarginLeft;
+  trimPrintSettings.unwriteableMarginTop =
+    printSettings.unwriteableMarginTop;
+  trimPrintSettings.unwriteableMarginRight =
+    printSettings.unwriteableMarginRight;
+  trimPrintSettings.unwriteableMarginBottom =
+    printSettings.unwriteableMarginBottom;
+  trimPrintSettings.paperWidth = printSettings.paperWidth;
+  trimPrintSettings.paperHeight = printSettings.paperHeight;
+  trimPrintSettings.paperSizeUnit = printSettings.paperSizeUnit;
+  trimPrintSettings.orientation = printSettings.orientation;
+  trimPrintSettings.shrinkToFit = printSettings.shrinkToFit;
+  trimPrintSettings.printInColor = printSettings.printInColor;
+  trimPrintSettings.printRange = printSettings.printRange;
+  trimPrintSettings.startPageRange = printSettings.startPageRange;
+  trimPrintSettings.endPageRange = printSettings.endPageRange;
+
+  mm.sendAsyncMessage("ppapipdf.js:printsettingschanged", {
+    trimPrintSettings });
+});
+
+mm.addMessageListener("ppapipdf.js:printPDF", ({ data }) => {
+  let file = Services.dirsvc.get(data.contentTempKey, Ci.nsIFile);
+  file.append(data.fileName);
+  if (!file.exists()) {
+    return;
+  }
+
+  let webBrowserPrint =
+    containerWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+    getInterface(Ci.nsIWebBrowserPrint);
+  if (!webBrowserPrint || !webBrowserPrint.printPDF) {
+    file.remove(false);
+    return;
+  }
+
+  webBrowserPrint.printPDF(file.path, printSettings)
+  .then(() => {
+    file.remove(false);
+  })
+  .catch(() => {
+    file.remove(false);
+  });
+});
+
 mm.addMessageListener("ppapipdf.js:save", () => {
   let url = containerWindow.document.location;
   let filename = "document.pdf";
   let regex = /[^\/#\?]+\.pdf$/i;
 
   let result = regex.exec(url.hash) ||
                regex.exec(url.search) ||
                regex.exec(url.pathname);