Bug 1399242 - Prevent console react updates while console isn't visible. r=nchevobbe draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 09 Oct 2017 20:14:34 +0200
changeset 700622 9206292300557b1ed1b07d26bd57572a67ec25a2
parent 700621 d4cf1e8376870d90d9b0ed0fc02038bd2e796a03
child 740947 9c76e4cd063934b5769fa67aaef517ed1f3ffd52
push id89914
push userbmo:poirot.alex@gmail.com
push dateMon, 20 Nov 2017 16:31:08 +0000
reviewersnchevobbe
bugs1399242
milestone59.0a1
Bug 1399242 - Prevent console react updates while console isn't visible. r=nchevobbe MozReview-Commit-ID: A6NGLbiuyTE
devtools/client/framework/toolbox.js
devtools/client/shared/components/VisibilityHandler.js
devtools/client/shared/components/moz.build
devtools/client/webconsole/new-console-output/components/ConsoleOutput.js
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_timestamps.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_visibility_messages.js
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1758,16 +1758,20 @@ Toolbox.prototype = {
       // The webconsole panel is in a special location due to split console
       if (!node.id) {
         node = this.webconsolePanel;
       }
 
       let iframe = node.querySelector(".toolbox-panel-iframe");
       if (iframe) {
         let visible = node.id == id;
+        // Prevents hiding the split-console if it is currently enabled
+        if (node == this.webconsolePanel && this.splitConsole) {
+          visible = true;
+        }
         this.setIframeVisible(iframe, visible);
       }
     });
   },
 
   /**
    * Make a privileged iframe visible/hidden.
    *
@@ -1902,16 +1906,22 @@ Toolbox.prototype = {
    * @returns {Promise} a promise that resolves once the tool has been
    *          loaded and focused.
    */
   openSplitConsole: function () {
     this._splitConsole = true;
     Services.prefs.setBoolPref(SPLITCONSOLE_ENABLED_PREF, true);
     this._refreshConsoleDisplay();
 
+    // Ensure split console is visible if console was already loaded in background
+    let iframe = this.webconsolePanel.querySelector(".toolbox-panel-iframe");
+    if (iframe) {
+      this.setIframeVisible(iframe, true);
+    }
+
     return this.loadTool("webconsole").then(() => {
       this.emit("split-console");
       this.focusConsoleInput();
     });
   },
 
   /**
    * Closes the split console.
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/VisibilityHandler.js
@@ -0,0 +1,47 @@
+/* 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";
+
+/**
+ * Helper class to disable panel rendering when it is in background.
+ *
+ * Toolbox code hides the iframes when switching to another panel
+ * and triggers `visibilitychange` events.
+ *
+ * See devtools/client/framework/toolbox.js:setIframeVisible().
+ */
+
+const {
+  createClass,
+} = require("devtools/client/shared/vendor/react");
+
+const VisibilityHandler = createClass({
+
+  displayName: "VisiblityHandler",
+
+  shouldComponentUpdate() {
+    return document.visibilityState == "visible";
+  },
+
+  onVisibilityChange() {
+    if (document.visibilityState == "visible") {
+      this.forceUpdate();
+    }
+  },
+
+  componentDidMount() {
+    window.addEventListener("visibilitychange", this.onVisibilityChange);
+  },
+
+  componentWillUnmount() {
+    window.removeEventListener("visibilitychange", this.onVisibilityChange);
+  },
+
+  render() {
+    return this.props.children;
+  }
+});
+
+module.exports = VisibilityHandler;
--- a/devtools/client/shared/components/moz.build
+++ b/devtools/client/shared/components/moz.build
@@ -17,12 +17,13 @@ DevToolsModules(
     'HSplitBox.js',
     'NotificationBox.css',
     'NotificationBox.js',
     'SearchBox.js',
     'SidebarToggle.css',
     'SidebarToggle.js',
     'StackTrace.js',
     'Tree.js',
+    'VisibilityHandler.js',
 )
 
 MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
--- a/devtools/client/webconsole/new-console-output/components/ConsoleOutput.js
+++ b/devtools/client/webconsole/new-console-output/components/ConsoleOutput.js
@@ -1,28 +1,29 @@
 /* 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 { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const { Component, createElement, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const {initialize} = require("devtools/client/webconsole/new-console-output/actions/ui");
 
 const {
   getAllMessagesById,
   getAllMessagesUiById,
   getAllMessagesTableDataById,
   getAllNetworkMessagesUpdateById,
   getVisibleMessages,
   getAllRepeatById,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/MessageContainer").MessageContainer);
+const VisibilityHandler = createFactory(require("devtools/client/shared/components/VisibilityHandler"));
 const {
   MESSAGE_TYPE,
 } = require("devtools/client/webconsole/new-console-output/constants");
 const {
   getInitialMessageCountForViewport
 } = require("devtools/client/webconsole/new-console-output/utils/messages.js");
 
 class ConsoleOutput extends Component {
@@ -185,9 +186,14 @@ function mapStateToProps(state, props) {
     messagesTableData: getAllMessagesTableDataById(state),
     messagesRepeat: getAllRepeatById(state),
     networkMessagesUpdate: getAllNetworkMessagesUpdateById(state),
     timestampsVisible: state.ui.timestampsVisible,
     networkMessageActiveTabId: state.ui.networkMessageActiveTabId,
   };
 }
 
-module.exports = connect(mapStateToProps)(ConsoleOutput);
+module.exports = connect(mapStateToProps)(props =>
+  VisibilityHandler(
+    null,
+    createElement(ConsoleOutput, props)
+  )
+);
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -204,17 +204,18 @@ NewConsoleOutputWrapper.prototype = {
   },
 
   dispatchMessageAdd: function (message, waitForResponse) {
     // Wait for the message to render to resolve with the DOM node.
     // This is just for backwards compatibility with old tests, and should
     // be removed once it's not needed anymore.
     // Can only wait for response if the action contains a valid message.
     let promise;
-    if (waitForResponse) {
+    // Also, do not expect any update while the panel is in background.
+    if (waitForResponse && document.visibilityState === "visible") {
       promise = new Promise(resolve => {
         let jsterm = this.jsterm;
         jsterm.hud.on("new-messages", function onThisMessage(e, messages) {
           for (let m of messages) {
             if (m.timeStamp === message.timestamp) {
               resolve(m.node);
               jsterm.hud.off("new-messages", onThisMessage);
               return;
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -436,11 +436,12 @@ skip-if = true #	Bug 1403196
 [browser_webconsole_variables_view_while_debugging.js]
 skip-if = true #	Bug 1403200
 [browser_webconsole_variables_view_while_debugging_and_inspecting.js]
 skip-if = true #	Bug 1403205
 [browser_webconsole_view_source.js]
 [browser_webconsole_violation.js]
 skip-if = true #	Bug 1405245
 # old console skip-if = e10s && (os == 'win') # Bug 1264955
+[browser_webconsole_visibility_messages.js]
 [browser_webconsole_warn_about_replaced_api.js]
 [browser_webconsole_websocket.js]
-skip-if = true # Bug 1408950
\ No newline at end of file
+skip-if = true # Bug 1408950
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_timestamps.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_timestamps.js
@@ -30,16 +30,19 @@ add_task(async function() {
   await testPrefDefaults(hud);
 
   let observer = new PrefObserver("");
   let toolbox = gDevTools.getToolbox(hud.target);
   let optionsPanel = await toolbox.selectTool("options");
   await togglePref(optionsPanel, observer);
   observer.destroy();
 
+  // Switch back to the console as it won't update when it is in background
+  await toolbox.selectTool("webconsole");
+
   await testChangedPref(hud);
 
   Services.prefs.clearUserPref(PREF_MESSAGE_TIMESTAMP);
 });
 
 async function testPrefDefaults(hud) {
   let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
   ok(!prefValue, "Messages should have no timestamp by default (pref check)");
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_visibility_messages.js
@@ -0,0 +1,116 @@
+/* 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";
+
+// Check messages logged when console not visible are displayed when
+// the user show the console again.
+
+const HTML = `
+  <!DOCTYPE html>
+  <html>
+    <body>
+      <h1>Test console visibility update</h1>
+      <script>
+        function log(str) {
+          console.log(str);
+        }
+      </script>
+    </body>
+  </html>
+`;
+const TEST_URI = "data:text/html;charset=utf-8," + encodeURI(HTML);
+const MESSAGES_COUNT = 10;
+
+add_task(async function () {
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const toolbox = gDevTools.getToolbox(hud.target);
+
+  info("Log one message in the console");
+  ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.log("in-console log");
+  });
+  await waitFor(() => findMessage(hud, "in-console log"));
+
+  info("select the inspector");
+  await toolbox.selectTool("inspector");
+
+  info("Wait for console to be hidden");
+  const { document } = hud.iframeWindow;
+  await waitFor(() => (document.visibilityState == "hidden"));
+
+  const onAllMessagesInStore = new Promise(done => {
+    const store = hud.ui.newConsoleOutput.getStore();
+    store.subscribe(() => {
+      const messages = store.getState().messages.messagesById.size;
+      // Also consider the "in-console log" message
+      if (messages == MESSAGES_COUNT+1) {
+        done();
+      }
+    });
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, [MESSAGES_COUNT], (count) => {
+    for (let i = 1; i <= count; i++) {
+      content.wrappedJSObject.log("in-inspector log " + i);
+    }
+  });
+
+  info("Waiting for all messages to be logged into the store");
+  await onAllMessagesInStore;
+
+  const count = await findMessages(hud, "in-inspector");
+  is(count, 0, "No messages from the inspector actually appear in the console");
+
+  info("select back the console");
+  await toolbox.selectTool("webconsole");
+
+  info("And wait for all messages to be visible");
+  let waitForMessagePromises = [];
+  for (let j = 1; j <= MESSAGES_COUNT; j++) {
+    waitForMessagePromises.push(waitFor(() => findMessage(hud, "in-inspector log " + j)));
+  }
+
+  await Promise.all(waitForMessagePromises);
+  ok(true, "All the messages logged when the console was hidden were displayed.");
+});
+
+// Similar scenario, but with the split console on the inspector panel.
+// Here, the messages should still be logged.
+add_task(async function () {
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const toolbox = gDevTools.getToolbox(hud.target);
+
+  info("Log one message in the console");
+  ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.wrappedJSObject.log("in-console log");
+  });
+  await waitFor(() => findMessage(hud, "in-console log"));
+
+  info("select the inspector");
+  await toolbox.selectTool("inspector");
+
+  info("Wait for console to be hidden");
+  const { document } = hud.iframeWindow;
+  await waitFor(() => (document.visibilityState == "hidden"));
+
+  await toolbox.openSplitConsole();
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, [MESSAGES_COUNT], (count) => {
+    for (let i = 1; i <= count; i++) {
+      content.wrappedJSObject.log("in-inspector log " + i);
+    }
+  });
+
+  info("Wait for all messages to be visible in the split console");
+  let waitForMessagePromises = [];
+  for (let j = 1; j <= MESSAGES_COUNT; j++) {
+    waitForMessagePromises.push(waitFor(() => findMessage(hud, "in-inspector log " + j)));
+  }
+
+  await Promise.all(waitForMessagePromises);
+  ok(true, "All the messages logged when we are using the split console");
+
+  await toolbox.closeSplitConsole();
+});