1. Virgil Dupras
  2. mozilla-central

Commits

Justin Lebar  committed ab6c700 Draft

Bug 800170 - Modify mozbrowser's getScreenshot() so it takes max-width, max-height parameters. r=smaug

  • Participants
  • Parent commits d53bd74
  • Branches default

Comments (0)

Files changed (11)

File dom/browser-element/BrowserElementChild.js

View file
  • Ignore whitespace
 
   _recvGetScreenshot: function(data) {
     debug("Received getScreenshot message: (" + data.json.id + ")");
+
+    // You can think of the screenshotting algorithm as carrying out the
+    // following steps:
+    //
+    // - Let max-width be data.json.args.width, and let max-height be
+    //   data.json.args.height.
+    //
+    // - Let scale-width be the factor by which we'd need to downscale the
+    //   viewport so it would fit within max-width.  (If the viewport's width
+    //   is less than max-width, let scale-width be 1.) Compute scale-height
+    //   the same way.
+    //
+    // - Scale the viewport by max(scale-width, scale-height).  Now either the
+    //   viewport's width is no larger than max-width, the viewport's height is
+    //   no larger than max-height, or both.
+    //
+    // - Crop the viewport so its width is no larger than max-width and its
+    //   height is no larger than max-height.
+    //
+    // - Return a screenshot of the page's viewport scaled and cropped per
+    //   above.
+
+    let maxWidth = data.json.args.width;
+    let maxHeight = data.json.args.height;
+
+    let scaleWidth = Math.min(1, maxWidth / content.innerWidth);
+    let scaleHeight = Math.min(1, maxHeight / content.innerHeight);
+
+    let scale = Math.max(scaleWidth, scaleHeight);
+
+    let canvasWidth = Math.min(maxWidth, Math.round(content.innerWidth * scale));
+    let canvasHeight = Math.min(maxHeight, Math.round(content.innerHeight * scale));
+
     var canvas = content.document
       .createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+    canvas.mozOpaque = true;
+    canvas.width = canvasWidth;
+    canvas.height = canvasHeight;
+
     var ctx = canvas.getContext("2d");
-    canvas.mozOpaque = true;
-    canvas.height = content.innerHeight;
-    canvas.width = content.innerWidth;
-    ctx.drawWindow(content, 0, 0, content.innerWidth,
-                   content.innerHeight, "rgb(255,255,255)");
+    ctx.scale(scale, scale);
+    ctx.drawWindow(content, 0, 0, content.innerWidth, content.innerHeight,
+                   "rgb(255,255,255)");
+
     sendAsyncMsg('got-screenshot', {
       id: data.json.id,
       // Hack around the fact that we can't specify opaque PNG, this requires
       // us to unpremultiply the alpha channel which is expensive on ARM
       // processors because they lack a hardware integer division instruction.
-      rv: canvas.toDataURL("image/jpeg")
+      successRv: canvas.toDataURL("image/jpeg")
     });
   },
 
     var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     sendAsyncMsg('got-can-go-back', {
       id: data.json.id,
-      rv: webNav.canGoBack
+      successRv: webNav.canGoBack
     });
   },
 
     var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     sendAsyncMsg('got-can-go-forward', {
       id: data.json.id,
-      rv: webNav.canGoForward
+      successRv: webNav.canGoForward
     });
   },
 

File dom/browser-element/BrowserElementParent.js

View file
  • Ignore whitespace
   defineMethod('goForward', this._goForward);
   defineMethod('reload', this._reload);
   defineMethod('stop', this._stop);
-  defineDOMRequestMethod('getScreenshot', 'get-screenshot');
+  defineMethod('getScreenshot', this._getScreenshot);
   defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
   defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
 
    * Kick off a DOMRequest in the child process.
    *
    * We'll fire an event called |msgName| on the child process, passing along
-   * an object with a single field, id, containing the ID of this request.
+   * an object with two fields:
+   *
+   *  - id:  the ID of this request.
+   *  - arg: arguments to pass to the child along with this request.
    *
    * We expect the child to pass the ID back to us upon completion of the
-   * request; see _gotDOMRequestResult.
+   * request.  See _gotDOMRequestResult.
    */
-  _sendDOMRequest: function(msgName) {
+  _sendDOMRequest: function(msgName, args) {
     let id = 'req_' + this._domRequestCounter++;
     let req = Services.DOMRequest.createRequest(this._window);
-    if (this._sendAsyncMsg(msgName, {id: id})) {
+    if (this._sendAsyncMsg(msgName, {id: id, args: args})) {
       this._pendingDOMRequests[id] = req;
     } else {
       Services.DOMRequest.fireErrorAsync(req, "fail");
   },
 
   /**
-   * Called when the child process finishes handling a DOMRequest.  We expect
-   * data.json to have two fields:
+   * Called when the child process finishes handling a DOMRequest.  data.json
+   * must have the fields [id, successRv], if the DOMRequest was successful, or
+   * [id, errorMsg], if the request was not successful.
    *
-   *  - id: the ID of the DOM request (see _sendDOMRequest), and
-   *  - rv: the request's return value.
+   * The fields have the following meanings:
+   *
+   *  - id:        the ID of the DOM request (see _sendDOMRequest)
+   *  - successRv: the request's return value, if the request succeeded
+   *  - errorMsg:  the message to pass to DOMRequest.fireError(), if the request
+   *               failed.
    *
    */
   _gotDOMRequestResult: function(data) {
     let req = this._pendingDOMRequests[data.json.id];
     delete this._pendingDOMRequests[data.json.id];
-    Services.DOMRequest.fireSuccess(req, data.json.rv);
+
+    if ('successRv' in data.json) {
+      debug("Successful gotDOMRequestResult.");
+      Services.DOMRequest.fireSuccess(req, data.json.successRv);
+    }
+    else {
+      debug("Got error in gotDOMRequestResult.");
+      Services.DOMRequest.fireErrorAsync(req, data.json.errorMsg);
+    }
   },
 
   _setVisible: function(visible) {
     this._sendAsyncMsg('stop');
   },
 
+  _getScreenshot: function(_width, _height) {
+    let width = parseInt(_width);
+    let height = parseInt(_height);
+    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
+      throw Components.Exception("Invalid argument",
+                                 Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    return this._sendDOMRequest('get-screenshot',
+                                {width: width, height: height});
+  },
+
   _fireKeyEvent: function(data) {
     let evt = this._window.document.createEvent("KeyboardEvent");
     evt.initKeyEvent(data.json.type, true, true, this._window,

File dom/browser-element/mochitest/Makefile.in

View file
  • Ignore whitespace
 		test_browserElement_inproc_Iconchange.html \
 		browserElement_GetScreenshot.js \
 		test_browserElement_inproc_GetScreenshot.html \
+		browserElement_BadScreenshot.js \
+		test_browserElement_inproc_BadScreenshot.html \
 		browserElement_SetVisible.js \
 		test_browserElement_inproc_SetVisible.html \
 		browserElement_SetVisibleFrames.js \
 		test_browserElement_oop_TopBarrier.html \
 		test_browserElement_oop_Iconchange.html \
 		test_browserElement_oop_GetScreenshot.html \
+		test_browserElement_oop_BadScreenshot.html \
 		test_browserElement_oop_SetVisible.html \
 		test_browserElement_oop_SetVisibleFrames.html \
 		test_browserElement_oop_SetVisibleFrames2.html \

File dom/browser-element/mochitest/browserElement_BadScreenshot.js

View file
  • Ignore whitespace
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Bug 800170 - Test that we get errors when we pass bad arguments to
+// mozbrowser's getScreenshot.
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+var iframe;
+var numPendingTests = 0;
+
+// Call iframe.getScreenshot with the given args.  If expectSuccess is true, we
+// expect the screenshot's onsuccess handler to fire.  Otherwise, we expect
+// getScreenshot() to throw an exception.
+function checkScreenshotResult(expectSuccess, args) {
+  var req;
+  try {
+    req = iframe.getScreenshot.apply(iframe, args);
+  }
+  catch(e) {
+    ok(!expectSuccess, "getScreenshot(" + JSON.stringify(args) + ") threw an exception.");
+    return;
+  }
+
+  numPendingTests++;
+  req.onsuccess = function() {
+    ok(expectSuccess, "getScreenshot(" + JSON.stringify(args) + ") succeeded.");
+    numPendingTests--;
+    if (numPendingTests == 0) {
+      SimpleTest.finish();
+    }
+  };
+
+  // We never expect to see onerror.
+  req.onerror = function() {
+    ok(false, "getScreenshot(" + JSON.stringify(args) + ") ran onerror.");
+    numPendingTests--;
+    if (numPendingTests == 0) {
+      SimpleTest.finish();
+    }
+  };
+}
+
+function runTest() {
+  dump("XXX runTest\n");
+  browserElementTestHelpers.setEnabledPref(true);
+  browserElementTestHelpers.addPermission();
+
+  iframe = document.createElement('iframe');
+  iframe.mozbrowser = true;
+  document.body.appendChild(iframe);
+  iframe.src = 'data:text/html,<html>' +
+    '<body style="background:green">hello</body></html>';
+
+  iframe.addEventListener('mozbrowserfirstpaint', function() {
+    // This one should succeed.
+    checkScreenshotResult(true, [100, 100]);
+
+    // These should fail.
+    checkScreenshotResult(false, []);
+    checkScreenshotResult(false, [100]);
+    checkScreenshotResult(false, ['a', 100]);
+    checkScreenshotResult(false, [100, 'a']);
+    checkScreenshotResult(false, [-1, 100]);
+    checkScreenshotResult(false, [100, -1]);
+
+    if (numPendingTests == 0) {
+      SimpleTest.finish();
+    }
+  });
+}
+
+runTest();

File dom/browser-element/mochitest/browserElement_DOMRequestError.js

View file
  • Ignore whitespace
         SimpleTest.executeSoon(nextTest);
       }
 
-      var domRequest = iframe1.getScreenshot();
+      var domRequest = iframe1.getScreenshot(1000, 1000);
       domRequest.onsuccess = function(e) {
         testEnd();
       }

File dom/browser-element/mochitest/browserElement_GetScreenshot.js

View file
  • Ignore whitespace
   }
 
   // We continually take screenshots until we get one that we are
-  // happy with
+  // happy with.
   function waitForScreenshot(filter) {
 
     function screenshotLoaded(e) {
         SimpleTest.finish();
       } else {
         content.document.defaultView.setTimeout(function() {
-          iframe1.getScreenshot().onsuccess = screenshotLoaded;
+          iframe1.getScreenshot(1000, 1000).onsuccess = screenshotLoaded;
         }, 200);
       }
     }
 
     var attempts = 10;
-    iframe1.getScreenshot().onsuccess = screenshotLoaded;
+    iframe1.getScreenshot(1000, 1000).onsuccess = screenshotLoaded;
   }
 
   function iframeLoadedHandler() {
 }
 
 addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
-
-

File dom/browser-element/mochitest/browserElement_OpenMixedProcess.js

View file
  • Ignore whitespace
     else if (e.detail.message == 'finish') {
       // We assume here that iframe is completely blank, and spin until popup's
       // screenshot is not the same as iframe.
-      iframe.getScreenshot().onsuccess = function(e) {
+      iframe.getScreenshot(1000, 1000).onsuccess = function(e) {
         test2(popup, e.target.result, popup);
       };
     }
 function test2(popup, blankScreenshot) {
   // Take screenshots of popup until it doesn't equal blankScreenshot (or we
   // time out).
-  popup.getScreenshot().onsuccess = function(e) {
+  popup.getScreenshot(1000, 1000).onsuccess = function(e) {
     var screenshot = e.target.result;
     if (screenshot != blankScreenshot) {
       SimpleTest.finish();

File dom/browser-element/mochitest/browserElement_XFrameOptionsAllowFrom.js

View file
  • Ignore whitespace
       // taking the screenshot).
       e.preventDefault();
 
-      iframe.getScreenshot().onsuccess = function(sshot) {
+      iframe.getScreenshot(1000, 1000).onsuccess = function(sshot) {
         if (initialScreenshot == null)
           initialScreenshot = sshot.target.result;
         e.detail.unblock();
     case 'finish':
       // The page has now attempted to load the X-Frame-Options page; take
       // another screenshot.
-      iframe.getScreenshot().onsuccess = function(sshot) {
+      iframe.getScreenshot(1000, 1000).onsuccess = function(sshot) {
         is(sshot.target.result, initialScreenshot, "Screenshots should be identical");
         SimpleTest.finish();
       };

File dom/browser-element/mochitest/browserElement_XFrameOptionsDeny.js

View file
  • Ignore whitespace
       // taking the screenshot).
       e.preventDefault();
 
-      iframe.getScreenshot().onsuccess = function(sshot) {
+      iframe.getScreenshot(1000, 1000).onsuccess = function(sshot) {
         initialScreenshot = sshot.target.result;
         e.detail.unblock();
       };
     case 'step 2':
       // The page has now attempted to load the X-Frame-Options page; take
       // another screenshot.
-      iframe.getScreenshot().onsuccess = function(sshot) {
+      iframe.getScreenshot(1000, 1000).onsuccess = function(sshot) {
         is(sshot.target.result, initialScreenshot, "Screenshots should be identical");
         SimpleTest.finish();
       };

File dom/browser-element/mochitest/test_browserElement_inproc_BadScreenshot.html

View file
  • Ignore whitespace
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 800170</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7" src="browserElement_BadScreenshot.js">
+</script>
+</body>
+</html>

File dom/browser-element/mochitest/test_browserElement_oop_BadScreenshot.html

View file
  • Ignore whitespace
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 800170</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7" src="browserElement_BadScreenshot.js">
+</script>
+</body>
+</html>