Bug 1399242 - Prevent console react updates while console isn't visible. r=nchevobbe
MozReview-Commit-ID: A6NGLbiuyTE
--- 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();
+});