Bug 1585008 - Part 1: Use CSS grid to place browser resizers in RDM. r=bgrins,bradwerth
authorMicah Tigley <mtigley@mozilla.com>
Fri, 10 Jan 2020 18:01:05 +0000
changeset 509845 5062702263870d73052474f4c8e3814086bb9b10
parent 509844 06786156032a72ff3511dff0b5c69c98c4021e29
child 509846 b9b06de796d7232ea8f7fd232868a8695705eada
push id37004
push usershindli@mozilla.com
push dateSat, 11 Jan 2020 09:48:29 +0000
treeherdermozilla-central@fb64636dad2c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins, bradwerth
bugs1585008
milestone74.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 1585008 - Part 1: Use CSS grid to place browser resizers in RDM. r=bgrins,bradwerth Place resizers for RDM browser using CSS grid. This revision takes advantage of using grid-template-columns/rows to specify where browser stack elements are placed in the grid. Differential Revision: https://phabricator.services.mozilla.com/D59189
devtools/client/responsive/responsive-browser.css
devtools/client/responsive/ui.js
--- a/devtools/client/responsive/responsive-browser.css
+++ b/devtools/client/responsive/responsive-browser.css
@@ -22,43 +22,80 @@
   width: 100%;
 }
 
 .browserContainer.responsive-mode .browserStack.device-modal-opened > .rdm-toolbar {
   z-index: 1;
 }
 
 .browserContainer.responsive-mode > .browserStack {
-  position: relative;
+  --resizer-offset: 4px;
+  --browser-viewport-width: calc(var(--rdm-width) + var(--resizer-offset));
+  --browser-viewport-height: calc(var(--rdm-height) + var(--resizer-offset));
   min-height: 0;
   min-width: 0;
   overflow: auto;
-  justify-items: center;
+  grid-template-columns: 15px 1fr [center-align] var(--browser-viewport-width) 1fr;
+  grid-template-rows: 65px [margin-top-offset] var(--browser-viewport-height);
 }
 
 .browserContainer.responsive-mode > .browserStack.device-modal-opened {
   overflow: hidden;
 }
 
 .browserContainer.responsive-mode > .browserStack > * {
   box-sizing: content-box; /* This is important to ensure that the pane has the
                               precise number of pixels defined by --rdm-width
                               and --rdm-height. */
   height: var(--rdm-height);
   width: var(--rdm-width);
 }
 
 .browserContainer.responsive-mode > .browserStack > *:not(.rdm-toolbar) {
-  margin-top: 65px;
+  grid-column: center-align;
+  grid-row: margin-top-offset;
+}
+
+.browserContainer.responsive-mode.left-aligned > .browserStack > *:not(.rdm-toolbar) {
+  grid-column: left-align;
 }
 
 .browserContainer.responsive-mode.left-aligned > .browserStack {
-  justify-items: left;
+  grid-template-columns: 15px [left-align] var(--browser-viewport-width) 1fr;
 }
 
-.browserContainer.responsive-mode.left-aligned > .browserStack > *:not(.rdm-toolbar) {
-  margin-left: 15px;
+html[dir="rtl"] .browserContainer.responsive-mode.left-aligned > .browserStack {
+  grid-template-columns: 1fr [left-align] var(--browser-viewport-width) 15px
 }
 
 .browserContainer.responsive-mode > .browserStack > browser {
   border: 1px solid var(--devtools-splitter-color);
   box-shadow: var(--rdm-browser-box-shadow);
 }
+
+/* Resize handles */
+
+.browserContainer.responsive-mode > .browserStack > .viewport-resize-handle {
+  width: 16px;
+  height: 16px;
+  background-image: url("./images/grippers.svg");
+  margin-inline-end: var(--resizer-offset);
+  margin-block-end: var(--resizer-offset);
+  background-repeat: no-repeat;
+  background-origin: content-box;
+  cursor: nwse-resize;
+  align-self: end;
+  justify-self: right;
+}
+
+.browserContainer.responsive-mode > .browserStack > .viewport-horizontal-resize-handle {
+  width: 5px;
+  height: calc(var(--rdm-height) - 16px);
+  cursor: ew-resize;
+  justify-self: right;
+}
+
+.browserContainer.responsive-mode > .browserStack > .viewport-vertical-resize-handle {
+  width: calc(var(--rdm-width) - 16px);
+  height: 5px;
+  cursor: ns-resize;
+  align-self: end;
+}
--- a/devtools/client/responsive/ui.js
+++ b/devtools/client/responsive/ui.js
@@ -245,27 +245,38 @@ class ResponsiveUI {
    * Initialize the RDM iframe inside of the browser document.
    */
   initRDMFrame() {
     const { document: doc, gBrowser } = this.browserWindow;
     const rdmFrame = doc.createElement("iframe");
     rdmFrame.src = "chrome://devtools/content/responsive/toolbar.xhtml";
     rdmFrame.classList.add("rdm-toolbar");
 
+    // Create resizer handlers
+    const resizeHandle = doc.createElement("div");
+    resizeHandle.classList.add("viewport-resize-handle");
+    const resizeHandleX = doc.createElement("div");
+    resizeHandleX.classList.add("viewport-horizontal-resize-handle");
+    const resizeHandleY = doc.createElement("div");
+    resizeHandleY.classList.add("viewport-vertical-resize-handle");
+
     this.browserContainerEl = gBrowser.getBrowserContainer(
       gBrowser.getBrowserForTab(this.tab)
     );
     this.browserStackEl = this.browserContainerEl.querySelector(
       ".browserStack"
     );
 
     this.browserContainerEl.classList.add("responsive-mode");
 
     // Prepend the RDM iframe inside of the current tab's browser stack.
     this.browserStackEl.prepend(rdmFrame);
+    this.browserStackEl.append(resizeHandle);
+    this.browserStackEl.append(resizeHandleX);
+    this.browserStackEl.append(resizeHandleY);
 
     // Wait for the frame script to be loaded.
     message.wait(rdmFrame.contentWindow, "script-init").then(async () => {
       // Notify the frame window that the Resposnive UI manager has begun initializing.
       // At this point, we can render our React content inside the frame.
       message.post(rdmFrame.contentWindow, "init");
       // Wait for the tools to be rendered above the content. The frame script will
       // then dispatch the necessary actions to the Redux store to give the toolbar the
@@ -273,16 +284,19 @@ class ResponsiveUI {
       message.wait(rdmFrame.contentWindow, "init:done").then(() => {
         rdmFrame.contentWindow.addInitialViewport({
           userContextId: this.tab.userContextId,
         });
       });
     });
 
     this.rdmFrame = rdmFrame;
+    this.resizeHandle = resizeHandle;
+    this.resizeHandleX = resizeHandleX;
+    this.resizeHandleY = resizeHandleY;
   }
 
   /**
    * Close RDM and restore page content back into a regular tab.
    *
    * @param object
    *        Destroy options, which currently includes a `reason` string.
    * @return boolean
@@ -326,16 +340,21 @@ class ResponsiveUI {
     if (!this.isBrowserUIEnabled) {
       this.tab.linkedBrowser.removeEventListener("FullZoomChange", this);
       this.toolWindow.removeEventListener("message", this);
     } else {
       this.browserWindow.removeEventListener("FullZoomChange", this);
       this.rdmFrame.contentWindow.removeEventListener("message", this);
       this.rdmFrame.remove();
 
+      // Clean up resize handlers
+      this.resizeHandle.remove();
+      this.resizeHandleX.remove();
+      this.resizeHandleY.remove();
+
       this.browserContainerEl.classList.remove("responsive-mode");
       this.browserStackEl.style.removeProperty("--rdm-width");
       this.browserStackEl.style.removeProperty("--rdm-height");
     }
 
     if (!this.isBrowserUIEnabled && !isTabContentDestroying) {
       // Notify the inner browser to stop the frame script
       await message.request(this.toolWindow, "stop-frame-script");
@@ -360,16 +379,19 @@ class ResponsiveUI {
     // Destroy local state
     const swap = this.swap;
     this.browserContainerEl = null;
     this.browserStackEl = null;
     this.browserWindow = null;
     this.tab = null;
     this.inited = null;
     this.rdmFrame = null;
+    this.resizeHandle = null;
+    this.resizeHandleX = null;
+    this.resizeHandleY = null;
     this.toolWindow = null;
     this.swap = null;
 
     // Close the debugger client used to speak with responsive emulation actor.
     // The actor handles clearing any overrides itself, so it's not necessary to clear
     // anything on shutdown client side.
     const clientClosed = this.client.close();
     if (!isTabContentDestroying) {