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 208697 87916d61ca999868992ae86487969ff7786e8bfe
parent 208696 ca7dc8cb97bc4fe965d34ae2b5e590d864655921
child 208698 c54a5bded1ba876f347863cd76f1672e00fd457e
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmdas
bugs883294
milestone35.0a1
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.