Bug 1239437 - Add a basic viewport frame. r=pbrosset draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Fri, 22 Jan 2016 20:36:58 -0600
changeset 324476 f1a1532180a71bab4c564483fa359a6ea88c2a8e
parent 324475 3492105afd192fbfc99d4427a0cbcd1dc20d02de
child 324477 28217f2b31b827699cd2da17a06f526aa33f7cea
push id9925
push userjryans@gmail.com
push dateSat, 23 Jan 2016 02:38:47 +0000
reviewerspbrosset
bugs1239437
milestone46.0a1
Bug 1239437 - Add a basic viewport frame. r=pbrosset
devtools/client/jar.mn
devtools/client/responsive.html/index.js
devtools/client/responsive.html/index.xhtml
devtools/client/responsive.html/manager.js
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -140,16 +140,18 @@ devtools.jar:
     content/shared/widgets/filter-frame.xhtml (shared/widgets/filter-frame.xhtml)
     content/shared/widgets/filter-widget.css (shared/widgets/filter-widget.css)
     content/eyedropper/eyedropper.xul (eyedropper/eyedropper.xul)
     content/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
     content/eyedropper/nocursor.css (eyedropper/nocursor.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/aboutdebugging.js (aboutdebugging/aboutdebugging.js)
+    content/responsive.html/index.xhtml (responsive.html/index.xhtml)
+    content/responsive.html/index.js (responsive.html/index.js)
 %   skin devtools classic/1.0 %skin/
     skin/devtools-browser.css (themes/devtools-browser.css)
     skin/common.css (themes/common.css)
     skin/splitters.css (themes/splitters.css)
     skin/dark-theme.css (themes/dark-theme.css)
     skin/light-theme.css (themes/light-theme.css)
     skin/toolbars.css (themes/toolbars.css)
     skin/variables.css (themes/variables.css)
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/index.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-env browser */
+
+"use strict";
+
+window.addViewport = contentURI => {
+  try {
+    let frame = document.createElement("iframe");
+    frame.setAttribute("src", contentURI);
+    document.body.appendChild(frame);
+  } catch (e) {
+    console.error(e);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/index.xhtml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel="stylesheet" type="text/css"
+          href="chrome://devtools/skin/common.css"/>
+    <script type="application/javascript;version=1.8"
+            src="chrome://devtools/content/shared/theme-switching.js"></script>
+    <script type="application/javascript;version=1.8"
+            src="./index.js"></script>
+  </head>
+  <body class="theme-body" role="application">
+  </body>
+</html>
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -1,17 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const promise = require("promise");
+const { Task } = require("resource://gre/modules/Task.jsm");
 const EventEmitter = require("devtools/shared/event-emitter");
 
+const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
+
 /**
  * ResponsiveUIManager is the external API for the browser UI, etc. to use when
  * opening and closing the responsive UI.
  *
  * While the HTML UI is in an experimental stage, the older ResponsiveUIManager
  * from devtools/client/responsivedesign/responsivedesign.jsm delegates to this
  * object when the pref "devtools.responsive.html.enabled" is true.
  */
@@ -25,17 +28,18 @@ exports.ResponsiveUIManager = {
    *        The main browser chrome window.
    * @param tab
    *        The browser tab.
    * @return Promise
    *         Resolved when the toggling has completed.
    */
   toggle(window, tab) {
     if (this.isActiveForTab(tab)) {
-      this.activeTabs.get(tab).close();
+      this.activeTabs.get(tab).destroy();
+      this.activeTabs.delete(tab);
     } else {
       this.runIfNeeded(window, tab);
     }
     // TODO: Becomes a more interesting value in a later patch
     return promise.resolve();
   },
 
   /**
@@ -43,17 +47,17 @@ exports.ResponsiveUIManager = {
    *
    * @param window
    *        The main browser chrome window.
    * @param tab
    *        The browser tab.
    */
   runIfNeeded(window, tab) {
     if (!this.isActiveForTab(tab)) {
-      // TODO: Unimplemented
+      this.activeTabs.set(tab, new ResponsiveUI(window, tab));
     }
   },
 
   /**
    * Returns true if responsive UI is active for a given tab.
    *
    * @param tab
    *        The browser tab.
@@ -63,17 +67,18 @@ exports.ResponsiveUIManager = {
     return this.activeTabs.has(tab);
   },
 
   /**
    * Return the responsive UI controller for a tab.
    *
    * @param tab
    *        The browser tab.
-   * @return TODO: Some object!
+   * @return ResponsiveUI
+   *         The UI instance for this tab.
    */
   getResponsiveUIForTab(tab) {
     return this.activeTabs.get(tab);
   },
 
   /**
    * Handle GCLI commands.
    *
@@ -93,23 +98,94 @@ exports.ResponsiveUIManager = {
         // TODO: Probably the wrong API
         this.activeTabs.get(tab).setSize(args.width, args.height);
         break;
       case "resize on":
         this.runIfNeeded(window, tab);
         break;
       case "resize off":
         if (this.isActiveForTab(tab)) {
-          // TODO: Probably the wrong API
-          this.activeTabs.get(tab).close();
+          this.activeTabs.get(tab).destroy();
+          this.activeTabs.delete(tab);
         }
         break;
       case "resize toggle":
         this.toggle(window, tab);
         break;
       default:
     }
   }
 };
 
 // GCLI commands in ../responsivedesign/resize-commands.js listen for events
 // from this object to know when the UI for a tab has opened or closed.
 EventEmitter.decorate(exports.ResponsiveUIManager);
+
+/**
+ * ResponsiveUI manages the responsive design tool for a specific tab.  The
+ * actual tool itself lives in a separate chrome:// document that is loaded into
+ * the tab upon opening responsive design.  This object acts a helper to
+ * integrate the tool into the surrounding browser UI as needed.
+ */
+function ResponsiveUI(window, tab) {
+  this.window = window;
+  this.tab = tab;
+  this.init();
+}
+
+ResponsiveUI.prototype = {
+
+  /**
+   * The main browser chrome window (that holds many tabs).
+   */
+  window: null,
+
+  /**
+   * The specific browser tab this responsive instance is for.
+   */
+  tab: null,
+
+  /**
+   * For the moment, we open the tool by:
+   * 1. Recording the tab's URL
+   * 2. Navigating the tab to the tool
+   * 3. Passing along the URL to the tool to open in the viewport
+   *
+   * This approach is simple, but it also discards the user's state on the page.
+   * It's just like opening a fresh tab and pasting the URL.
+   *
+   * In the future, we can do better by using swapFrameLoaders to preserve the
+   * state.  Platform discussions are in progress to make this happen.  See
+   * bug 1238160 about <iframe mozbrowser> for more details.
+   */
+  init: Task.async(function*() {
+    let tabBrowser = this.tab.linkedBrowser;
+    let contentURI = tabBrowser.documentURI.spec;
+    tabBrowser.loadURI(TOOL_URL);
+    yield tabLoaded(this.tab);
+    let toolWindow = tabBrowser.contentWindow;
+    toolWindow.addViewport(contentURI);
+  }),
+
+  destroy() {
+    let tabBrowser = this.tab.linkedBrowser;
+    tabBrowser.goBack();
+    this.window = null;
+    this.tab = null;
+  },
+
+};
+
+function tabLoaded(tab) {
+  let deferred = promise.defer();
+
+  function handle(event) {
+    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+        event.target.location.href == "about:blank") {
+      return;
+    }
+    tab.linkedBrowser.removeEventListener("load", handle, true);
+    deferred.resolve(event);
+  }
+
+  tab.linkedBrowser.addEventListener("load", handle, true);
+  return deferred.promise;
+}