Bug 1587846 - [remote] Add "quality" option to Page.captureScreenshot. r=remote-protocol-reviewers,ato,maja_zf
authorHenrik Skupin <mail@hskupin.info>
Thu, 07 Nov 2019 19:02:00 +0000
changeset 501133 3ef1ca5d2649f2d8c1541396cf09fee642913913
parent 501132 cfee639c527a2329efe78accce8e296a8f9c7d83
child 501134 7f37cd6cfcf95df4b6b1c3df4d1941f459751e6e
push id36781
push usercsabou@mozilla.com
push dateFri, 08 Nov 2019 05:21:04 +0000
treeherdermozilla-central@dff542b772e5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersremote-protocol-reviewers, ato, maja_zf
bugs1587846
milestone72.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 1587846 - [remote] Add "quality" option to Page.captureScreenshot. r=remote-protocol-reviewers,ato,maja_zf Differential Revision: https://phabricator.services.mozilla.com/D52146
remote/domains/parent/Page.jsm
remote/test/browser/page/browser_captureScreenshot.js
--- a/remote/domains/parent/Page.jsm
+++ b/remote/domains/parent/Page.jsm
@@ -44,34 +44,31 @@ class Page extends Domain {
   /**
    * Capture page screenshot.
    *
    * @param {Object} options
    * @param {Viewport=} options.clip (not supported)
    *     Capture the screenshot of a given region only.
    * @param {string=} options.format
    *     Image compression format. Defaults to "png".
-   * @param {number=} options.quality (not supported)
-   *     Compression quality from range [0..100] (jpeg only). Defaults to 100.
+   * @param {number=} options.quality
+   *     Compression quality from range [0..100] (jpeg only). Defaults to 80.
    *
    * @return {string}
    *     Base64-encoded image data.
    */
   async captureScreenshot(options = {}) {
-    const { format = "png" } = options;
+    const { format = "png", quality = 80 } = options;
 
     if (options.clip) {
       throw new UnsupportedError("clip not supported");
     }
     if (options.fromSurface) {
       throw new UnsupportedError("fromSurface not supported");
     }
-    if (options.quality) {
-      throw new UnsupportedError("quality not supported");
-    }
 
     const MAX_CANVAS_DIMENSION = 32767;
     const MAX_CANVAS_AREA = 472907776;
 
     // Retrieve the browsing context of the content browser
     const { browsingContext, window } = this.session.target;
     const scale = window.devicePixelRatio;
 
@@ -114,17 +111,17 @@ class Page extends Domain {
     const ctx = canvas.getContext("2d");
     ctx.drawImage(snapshot, 0, 0);
 
     // Bug 1574935 - Huge dimensions can trigger an OOM because multiple copies
     // of the bitmap will exist in memory. Force the removal of the snapshot
     // because it is no longer needed.
     snapshot.close();
 
-    const url = canvas.toDataURL(`image/${format}`);
+    const url = canvas.toDataURL(`image/${format}`, quality / 100);
     if (!url.startsWith(`data:image/${format}`)) {
       throw new UnsupportedError(`Unsupported MIME type: image/${format}`);
     }
 
     // only return the base64 encoded data without the data URL prefix
     const data = url.substring(url.indexOf(",") + 1);
 
     return { data };
--- a/remote/test/browser/page/browser_captureScreenshot.js
+++ b/remote/test/browser/page/browser_captureScreenshot.js
@@ -10,49 +10,62 @@ add_task(async function documentSmallerT
   const { data } = await Page.captureScreenshot();
   ok(!!data, "Screenshot data is not empty");
 
   const scale = await getDevicePixelRatio();
   const viewportRect = await getViewportRect();
   const { mimeType, width, height } = await getImageDetails(data);
 
   is(mimeType, "image/png", "Screenshot has correct MIME type");
-  is(width, (viewportRect.width - viewportRect.left) * scale);
-  is(height, (viewportRect.height - viewportRect.top) * scale);
+  is(
+    width,
+    (viewportRect.width - viewportRect.left) * scale,
+    "Image has expected width"
+  );
+  is(
+    height,
+    (viewportRect.height - viewportRect.top) * scale,
+    "Image has expected height"
+  );
 });
 
 add_task(async function documentLargerThanViewport({ Page }) {
   loadURL(toDataURL("<div style='margin: 100vh 100vw'>Hello world"));
 
   info("Check that captureScreenshot() captures the viewport by default");
   const { data } = await Page.captureScreenshot();
   ok(!!data, "Screenshot data is not empty");
 
   const scale = await getDevicePixelRatio();
   const viewportRect = await getViewportRect();
   const { mimeType, width, height } = await getImageDetails(data);
 
   is(mimeType, "image/png", "Screenshot has correct MIME type");
-  is(width, (viewportRect.width - viewportRect.left) * scale);
-  is(height, (viewportRect.height - viewportRect.top) * scale);
+  is(
+    width,
+    (viewportRect.width - viewportRect.left) * scale,
+    "Image has expected width"
+  );
+  is(
+    height,
+    (viewportRect.height - viewportRect.top) * scale,
+    "Image has expected height"
+  );
 });
 
 add_task(async function invalidFormat({ Page }) {
   loadURL(toDataURL("<div>Hello world"));
 
-  let exceptionThrown = false;
+  let errorThrown = false;
   try {
     await Page.captureScreenshot({ format: "foo" });
   } catch (e) {
-    exceptionThrown = true;
+    errorThrown = true;
   }
-  ok(
-    exceptionThrown,
-    "captureScreenshot raised error for invalid image format"
-  );
+  ok(errorThrown, "captureScreenshot raised error for invalid image format");
 });
 
 add_task(async function asJPEGFormat({ Page }) {
   loadURL(toDataURL("<div>Hello world"));
 
   info("Check that captureScreenshot() captures as JPEG format");
   const { data } = await Page.captureScreenshot({ format: "jpeg" });
   ok(!!data, "Screenshot data is not empty");
@@ -61,16 +74,80 @@ add_task(async function asJPEGFormat({ P
   const viewportRect = await getViewportRect();
   const { mimeType, height, width } = await getImageDetails(data);
 
   is(mimeType, "image/jpeg", "Screenshot has correct MIME type");
   is(width, (viewportRect.width - viewportRect.left) * scale);
   is(height, (viewportRect.height - viewportRect.top) * scale);
 });
 
+add_task(async function asJPEGFormatAndQuality({ Page }) {
+  loadURL(toDataURL("<div>Hello world"));
+
+  info("Check that captureScreenshot() captures as JPEG format");
+  const imageDefault = await Page.captureScreenshot({ format: "jpeg" });
+  ok(!!imageDefault, "Screenshot data with default quality is not empty");
+
+  const image100 = await Page.captureScreenshot({
+    format: "jpeg",
+    quality: 100,
+  });
+  ok(!!image100, "Screenshot data with quality 100 is not empty");
+
+  const image10 = await Page.captureScreenshot({
+    format: "jpeg",
+    quality: 10,
+  });
+  ok(!!image10, "Screenshot data with quality 10 is not empty");
+
+  const infoDefault = await getImageDetails(imageDefault.data);
+  const info100 = await getImageDetails(image100.data);
+  const info10 = await getImageDetails(image10.data);
+
+  // All screenshots are of mimeType JPEG
+  is(
+    infoDefault.mimeType,
+    "image/jpeg",
+    "Screenshot with default quality has correct MIME type"
+  );
+  is(
+    info100.mimeType,
+    "image/jpeg",
+    "Screenshot with quality 100 has correct MIME type"
+  );
+  is(
+    info10.mimeType,
+    "image/jpeg",
+    "Screenshot with quality 10 has correct MIME type"
+  );
+
+  const scale = await getDevicePixelRatio();
+  const viewportRect = await getViewportRect();
+
+  // Images are all of the same dimension
+  is(infoDefault.width, (viewportRect.width - viewportRect.left) * scale);
+  is(infoDefault.height, (viewportRect.height - viewportRect.top) * scale);
+
+  is(info100.width, (viewportRect.width - viewportRect.left) * scale);
+  is(info100.height, (viewportRect.height - viewportRect.top) * scale);
+
+  is(info10.width, (viewportRect.width - viewportRect.left) * scale);
+  is(info10.height, (viewportRect.height - viewportRect.top) * scale);
+
+  // Images of different quality result in different content sizes
+  ok(
+    info100.length > infoDefault.length,
+    "Size of quality 100 is larger than default"
+  );
+  ok(
+    info10.length < infoDefault.length,
+    "Size of quality 10 is smaller than default"
+  );
+});
+
 async function getDevicePixelRatio() {
   return ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
     return content.devicePixelRatio;
   });
 }
 
 async function getImageDetails(image) {
   const mimeType = getMimeType(image);
@@ -83,16 +160,17 @@ async function getImageDetails(image) {
         const img = new content.Image();
         img.addEventListener(
           "load",
           () => {
             resolve({
               mimeType,
               width: img.width,
               height: img.height,
+              length: image.length,
             });
           },
           { once: true }
         );
 
         img.src = `data:${mimeType};base64,${image}`;
       });
     }