Bug 1403682 - Add _findBoundingBox function and tests for mozscreenshots cropping; r?jaws draft
authorMike Williams <will2616@msu.edu>
Sat, 14 Oct 2017 11:13:32 -0400
changeset 686537 b892839e4f46d29a76b9d5ff9bc3d874f23f3cfc
parent 680510 32bd6d5641dbd854177f25d4487473a1c19aa760
child 687686 b4c2998efeda9d8bb279e443ae005708bd10a364
child 687711 a00521cfcdf73c2dfca4f8153e1d863a455bd9af
push id86204
push userbmo:will2616@msu.edu
push dateWed, 25 Oct 2017 22:48:47 +0000
reviewersjaws
bugs1403682
milestone58.0a1
Bug 1403682 - Add _findBoundingBox function and tests for mozscreenshots cropping; r?jaws MozReview-Commit-ID: 7kvVsF3Wq3z
browser/tools/mozscreenshots/browser.ini
browser/tools/mozscreenshots/browser_boundingbox.js
browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
toolkit/modules/Geometry.jsm
--- a/browser/tools/mozscreenshots/browser.ini
+++ b/browser/tools/mozscreenshots/browser.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 subsuite = screenshots
 support-files =
   head.js
 
 [browser_screenshots.js]
+[browser_boundingbox.js]
new file mode 100644
--- /dev/null
+++ b/browser/tools/mozscreenshots/browser_boundingbox.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(async function() {
+  const scale = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIBaseWindow)
+                      .devicePixelsPerDesktopPixel;
+  let rect = TestRunner._findBoundingBox(["#tabbrowser-tabs"]);
+  let element = document.querySelector("#tabbrowser-tabs");
+  let tabBar = element.ownerDocument.getBoxObjectFor(element);
+
+  // Calculate expected values
+  let expectedLeft = scale * (tabBar.screenX - TestRunner.croppingPadding);
+  let expectedTop = scale * (tabBar.screenY - TestRunner.croppingPadding);
+  let expectedRight = scale * (tabBar.width + TestRunner.croppingPadding * 2) + expectedLeft;
+  let expectedBottom = scale * (tabBar.height + TestRunner.croppingPadding * 2) + expectedTop;
+
+  // Calculate browser region
+  let windowLeft = window.screenX * scale;
+  let windowTop = window.screenY * scale;
+  let windowRight = window.outerWidth * scale + windowLeft;
+  let windowBottom = window.outerHeight * scale + windowTop;
+
+  // Adjust values based on browser window
+  expectedLeft = Math.max(expectedLeft, windowLeft);
+  expectedTop = Math.max(expectedTop, windowTop);
+  expectedRight = Math.min(expectedRight, windowRight);
+  expectedBottom = Math.min(expectedBottom, windowBottom);
+  // Check width calculation on simple example
+  is(rect.width, expectedRight - expectedLeft,
+     "Checking _findBoundingBox width calculation");
+  // Check height calculation on simple example
+  is(rect.height, expectedBottom - expectedTop,
+     "Checking _findBoundingBox height caclulation");
+
+  rect = TestRunner._findBoundingBox(["#forward-button", "#TabsToolbar"]);
+  element = document.querySelector("#TabsToolbar");
+  let tabToolbar = element.ownerDocument.getBoxObjectFor(element);
+  element = document.querySelector("#forward-button");
+  let fButton = element.ownerDocument.getBoxObjectFor(element);
+
+  // Calculate expected values
+  expectedLeft = scale * (Math.min(tabToolbar.screenX, fButton.screenX)
+                              - TestRunner.croppingPadding);
+  expectedTop = scale * (Math.min(tabToolbar.screenY, fButton.screenY)
+                              - TestRunner.croppingPadding);
+  expectedRight = scale * (Math.max(tabToolbar.width + tabToolbar.screenX,
+                                    fButton.width + fButton.screenX)
+                              + TestRunner.croppingPadding);
+  expectedBottom = scale * (Math.max(tabToolbar.height + tabToolbar.screenY,
+                                     fButton.height + fButton.screenY)
+                              + TestRunner.croppingPadding );
+
+  // Adjust values based on browser window
+  expectedLeft = Math.max(expectedLeft, windowLeft);
+  expectedTop = Math.max(expectedTop, windowTop);
+  expectedRight = Math.min(expectedRight, windowRight);
+  expectedBottom = Math.min(expectedBottom, windowBottom);
+
+  // Check width calculation on union
+  is(rect.width, expectedRight - expectedLeft,
+     "Checking _findBoundingBox union width calculation");
+  // Check height calculation on union
+  is(rect.height, expectedBottom - expectedTop,
+     "Checking _findBoundingBox union height calculation");
+
+  rect = TestRunner._findBoundingBox(["#does_not_exist"]);
+  // Check that 'selector not found' returns null
+  is(rect, null, "Checking that selector not found returns null");
+
+  rect = TestRunner._findBoundingBox([]);
+  // Check that no selectors returns null
+  is(rect, null, "Checking that no selectors returns null");
+});
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
@@ -11,16 +11,17 @@ const env = Cc["@mozilla.org/process/env
 const APPLY_CONFIG_TIMEOUT_MS = 60 * 1000;
 const HOME_PAGE = "chrome://mozscreenshots/content/lib/mozscreenshots.html";
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Geometry.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
                                   "resource://testing-common/BrowserTestUtils.jsm");
 
 Cu.import("chrome://mozscreenshots/content/Screenshot.jsm");
 
 // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
@@ -36,16 +37,17 @@ XPCOMUtils.defineLazyGetter(this, "log",
 });
 
 this.TestRunner = {
   combos: null,
   completedCombos: 0,
   currentComboIndex: 0,
   _lastCombo: null,
   _libDir: null,
+  croppingPadding: 10,
 
   init(extensionPath) {
     log.debug("init");
     this._extensionPath = extensionPath;
   },
 
   /**
    * Load specified sets, execute all combinations of them, and capture screenshots.
@@ -154,16 +156,83 @@ this.TestRunner = {
     gBrowser.unpinTab(gBrowser.selectedTab);
     gBrowser.selectedBrowser.loadURI("data:text/html;charset=utf-8,<h1>Done!");
     browserWindow.restore();
     Services.prefs.clearUserPref("toolkit.cosmeticAnimations.enabled");
   },
 
   // helpers
 
+  /**
+  * Calculate the bounding box based on CSS selector from config for cropping
+  *
+  * @param {String[]} selectors - array of CSS selectors for relevant DOM element
+  * @return {Geometry.jsm Rect} Rect holding relevant x, y, width, height with padding
+  **/
+  _findBoundingBox(selectors, windowType) {
+    // No selectors provided
+    if (!selectors.length) {
+      log.info("_findBoundingBox: selectors argument is empty");
+      return null;
+    }
+
+    // Set window type, default "navigator:browser"
+    windowType = windowType || "navigator:browser";
+    let browserWindow = Services.wm.getMostRecentWindow(windowType);
+    // Scale for high-density displays
+    const scale = browserWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIBaseWindow)
+                        .devicePixelsPerDesktopPixel;
+
+    let finalRect = undefined;
+    // Grab bounding boxes and find the union
+    for (let selector of selectors) {
+      let element;
+      // Check for function to find anonymous content
+      if (typeof(selector) == "function") {
+        element = selector();
+      } else {
+        element = browserWindow.document.querySelector(selector);
+      }
+
+      // Selector not found
+      if (!element) {
+        log.info("_findBoundingBox: selector not found");
+        return null;
+      }
+
+      // Calculate box region, convert to Rect
+      let box = element.ownerDocument.getBoxObjectFor(element);
+      let newRect = new Rect(box.screenX * scale, box.screenY * scale,
+                             box.width * scale, box.height * scale);
+
+      if (!finalRect) {
+        finalRect = newRect;
+      } else {
+        finalRect = finalRect.union(newRect);
+      }
+    }
+
+    // Add fixed padding
+    finalRect = finalRect.inflateFixed(this.croppingPadding * scale);
+
+    let windowLeft = browserWindow.screenX * scale;
+    let windowTop = browserWindow.screenY * scale;
+    let windowWidth = browserWindow.outerWidth * scale;
+    let windowHeight = browserWindow.outerHeight * scale;
+
+    // Clip dimensions to window only
+    finalRect.left = Math.max(finalRect.left, windowLeft);
+    finalRect.top = Math.max(finalRect.top, windowTop);
+    finalRect.right = Math.min(finalRect.right, windowLeft + windowWidth);
+    finalRect.bottom = Math.min(finalRect.bottom, windowTop + windowHeight);
+
+    return finalRect;
+  },
+
   async _performCombo(combo) {
     let paddedComboIndex = padLeft(this.currentComboIndex + 1, String(this.combos.length).length);
     log.info("Combination " + paddedComboIndex + "/" + this.combos.length + ": " +
              this._comboName(combo).substring(1));
 
     function changeConfig(config) {
       log.debug("calling " + config.name);
       let applyPromise = Promise.resolve(config.applyConfig());
--- a/toolkit/modules/Geometry.jsm
+++ b/toolkit/modules/Geometry.jsm
@@ -325,10 +325,22 @@ Rect.prototype = {
     let xAdj = (this.width * xscl - this.width) / 2;
     let s = (arguments.length > 1) ? yscl : xscl;
     let yAdj = (this.height * s - this.height) / 2;
     this.left -= xAdj;
     this.right += xAdj;
     this.top -= yAdj;
     this.bottom += yAdj;
     return this;
+  },
+
+  /**
+   * Grows or shrinks the rectangle by fixed amount while keeping the center point.
+   * Accepts single fixed amount
+   */
+  inflateFixed: function inflateFixed(fixed) {
+    this.left -= fixed;
+    this.right += fixed;
+    this.top -= fixed;
+    this.bottom += fixed;
+    return this;
   }
 };