Bug 883294 - Add ability to take full viewport screenshots. r=mdas
authorDave Hunt <dhunt@mozilla.com>
Tue, 30 Sep 2014 14:42:00 -0400
changeset 231912 87916d61ca999868992ae86487969ff7786e8bfe
parent 231911 ca7dc8cb97bc4fe965d34ae2b5e590d864655921
child 231913 c54a5bded1ba876f347863cd76f1672e00fd457e
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmdas
bugs883294
milestone35.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 883294 - Add ability to take full viewport screenshots. r=mdas
testing/marionette/client/marionette/runner/mixins/reporting.py
testing/marionette/client/marionette/tests/unit/test_screenshot.py
testing/marionette/marionette-server.js
--- a/testing/marionette/client/marionette/runner/mixins/reporting.py
+++ b/testing/marionette/client/marionette/runner/mixins/reporting.py
@@ -241,19 +241,19 @@ class HTMLReportingTestResultMixin(objec
         test.debug = None
         if result_actual is not 'PASS':
             test.debug = self.gather_debug()
         return result_expected, result_actual, output, context
 
     def gather_debug(self):
         debug = {}
         try:
-            # TODO make screenshot consistant size by using full viewport
-            # Bug 883294 - Add ability to take full viewport screenshots
+            self.marionette.switch_context(self.marionette.CONTEXT_CHROME)
             debug['screenshot'] = self.marionette.screenshot()
+            self.marionette.switch_context(self.marionette.CONTEXT_CONTENT)
             debug['source'] = self.marionette.page_source
             self.marionette.switch_to_frame()
             debug['settings'] = json.dumps(self.marionette.execute_async_script("""
 SpecialPowers.addPermission('settings-read', true, document);
 SpecialPowers.addPermission('settings-api-read', true, document);
 var req = window.navigator.mozSettings.createLock().get('*');
 req.onsuccess = function() {
   marionetteScriptFinished(req.result);
--- a/testing/marionette/client/marionette/tests/unit/test_screenshot.py
+++ b/testing/marionette/client/marionette/tests/unit/test_screenshot.py
@@ -1,44 +1,59 @@
+import base64
+import imghdr
+
 from marionette_test import MarionetteTestCase
-import base64
+
 
 RED_ELEMENT_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAVUlEQVRoge3PsQ0AIAzAsI78fzBwBhHykD2ePev80LweAAGJB1ILpBZILZBaILVAaoHUAqkFUgukFkgtkFogtUBqgdQCqQVSC6QWSC2QWiC1QGp9A7ma+7nyXgOpzQAAAABJRU5ErkJggg=='
 GREEN_ELEMENT_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAV0lEQVRoge3PQRGAQAwAsWINvXgsNnI3+4iAzM7sDWZn9vneoxXRFNEU0RTRFNEU0RTRFNEU0RTRFNEU0RTRFNEU0RTRFNEU0RTRFNEU0RTRFNHcF7nBD/Ha5Ye4BbsYAAAAAElFTkSuQmCC'
 
 class ScreenshotTests(MarionetteTestCase):
 
+    def testWeCanTakeAScreenShotOfEntireViewport(self):
+        test_url = self.marionette.absolute_url('html5Page.html')
+        self.marionette.navigate(test_url)
+        content = self.marionette.screenshot()
+        self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+        chrome = self.marionette.screenshot()
+        # Check the base64 decoded string is a PNG file.
+        image = base64.decodestring(chrome)
+        self.assertEqual(imghdr.what('', image), 'png')
+        self.assertNotEqual(content, chrome)
+
     def testWeCanTakeAScreenShotOfAnElement(self):
         test_url = self.marionette.absolute_url('html5Page.html')
         self.marionette.navigate(test_url)
         el = self.marionette.find_element('id', 'red')
         self.assertEqual(RED_ELEMENT_BASE64,
                          self.marionette.screenshot(element=el))
 
     def testWeCanTakeAScreenShotWithHighlightOfAnElement(self):
         test_url = self.marionette.absolute_url('html5Page.html')
         self.marionette.navigate(test_url)
         el = self.marionette.find_element('id', 'green')
         self.assertEqual(GREEN_ELEMENT_BASE64,
                          self.marionette.screenshot(element=el, highlights=[el]))
 
-    def testWeCanTakeAScreenShotEntireCanvas(self):
+    def testWeCanTakeAScreenShotOfEntireCanvas(self):
         test_url = self.marionette.absolute_url('html5Page.html')
         self.marionette.navigate(test_url)
-        self.assertTrue('iVBORw0KGgo' in
-                        self.marionette.screenshot())
+        # Check the base64 decoded string is a PNG file.
+        image = base64.decodestring(self.marionette.screenshot())
+        self.assertEqual(imghdr.what('', image), 'png')
 
     def testWeCanTakeABinaryScreenShotOfAnElement(self):
         test_url = self.marionette.absolute_url('html5Page.html')
         self.marionette.navigate(test_url)
         el = self.marionette.find_element('id', 'red')
         binary_data = self.marionette.screenshot(element=el, format="binary")
         self.assertEqual(RED_ELEMENT_BASE64,
                          base64.b64encode(binary_data))
-    
+
     def testNotAllowedScreenshotFormatRaiseValueError(self):
         test_url = self.marionette.absolute_url('html5Page.html')
         self.marionette.navigate(test_url)
         el = self.marionette.find_element('id', 'red')
         self.assertRaises(ValueError,
                           self.marionette.screenshot,
                           element=el,
                           format="unknowformat")
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -2366,36 +2366,80 @@ MarionetteServerConnection.prototype = {
     catch (e) {
       this.sendError("Could not clear imported scripts", 500, e.name + ": " + e.message, command_id);
       return;
     }
     this.sendOk(command_id);
   },
 
   /**
-   * Takes a screenshot of a web element or the current frame.
+   * Takes a screenshot of a web element, current frame, or viewport.
    *
    * The screen capture is returned as a lossless PNG image encoded as
-   * a base 64 string.  If the <code>id</code> argument is not null
-   * and refers to a present and visible web element's ID, the capture
-   * area will be limited to the bounding box of that element.
-   * Otherwise, the capture area will be the bounding box of the
-   * current frame.
+   * a base 64 string.
+   *
+   * If called in the content context, the <code>id</code> argument is not null
+   * and refers to a present and visible web element's ID, the capture area
+   * will be limited to the bounding box of that element. Otherwise, the
+   * capture area will be the bounding box of the current frame.
    *
-   * @param id an optional reference to a web element
-   * @param highlights an optional list of web elements to draw a red
-   *                   box around in the returned capture
-   * @return PNG image encoded as base 64 string
-    */
+   * If called in the chrome context, the screenshot will always represent the
+   * entire viewport.
+   *
+   * @param {string} [id] Reference to a web element.
+   * @param {string} [highlights] List of web elements to highlight.
+   * @return {string} PNG image encoded as base 64 string.
+   */
   takeScreenshot: function MDA_takeScreenshot(aRequest) {
     this.command_id = this.getCommandId();
-    this.sendAsync("takeScreenshot",
+    if (this.context == "chrome") {
+      var win = this.getCurrentWindow();
+      var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+      var doc;
+      if (appName == "B2G") {
+        doc = win.document.body;
+      } else {
+        doc = win.document.getElementsByTagName('window')[0];
+      }
+      var docRect = doc.getBoundingClientRect();
+      var width = docRect.width;
+      var height = docRect.height;
+
+      // Convert width and height from CSS pixels (potentially fractional)
+      // to device pixels (integer).
+      var scale = win.devicePixelRatio;
+      canvas.setAttribute("width", Math.round(width * scale));
+      canvas.setAttribute("height", Math.round(height * scale));
+
+      var context = canvas.getContext("2d");
+      var flags;
+      if (appName == "B2G") {
+        flags =
+          context.DRAWWINDOW_DRAW_CARET |
+          context.DRAWWINDOW_DRAW_VIEW |
+          context.DRAWWINDOW_USE_WIDGET_LAYERS;
+      } else {
+        // Bug 1075168 - CanvasRenderingContext2D image is distorted
+        // when using certain flags in chrome context.
+        flags =
+          context.DRAWWINDOW_DRAW_VIEW |
+          context.DRAWWINDOW_USE_WIDGET_LAYERS;
+      }
+      context.scale(scale, scale);
+      context.drawWindow(win, 0, 0, width, height, "rgb(255,255,255)", flags);
+      var dataUrl = canvas.toDataURL("image/png", "");
+      var data = dataUrl.substring(dataUrl.indexOf(",") + 1);
+      this.sendResponse(data, this.command_id);
+    }
+    else {
+      this.sendAsync("takeScreenshot",
                    {id: aRequest.parameters.id,
                     highlights: aRequest.parameters.highlights},
                    this.command_id);
+    }
   },
 
   /**
    * Get the current browser orientation.
    *
    * Will return one of the valid primary orientation values
    * portrait-primary, landscape-primary, portrait-secondary, or
    * landscape-secondary.