From 46254f84e49d156d18457140291b754f253fffb5 Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Thu, 4 Oct 2018 20:14:53 +0000 Subject: [PATCH] Bug 1496468 - Add a console pause indicator. r=loganfsmyth Differential Revision: https://phabricator.services.mozilla.com/D7741 --HG-- extra : moz-landing-system : lando --- .../browser_dbg_rr_console_warp-01.js | 17 +++++++++++++++++ devtools/client/themes/webconsole.css | 19 ++++++++++++++++++- .../client/webconsole/actions/messages.js | 9 +++++++++ .../webconsole/components/ConsoleOutput.js | 5 +++++ .../client/webconsole/components/Message.js | 6 ++++-- .../webconsole/components/MessageContainer.js | 15 +++++++++++++-- .../message-types/ConsoleApiCall.js | 2 ++ .../components/message-types/PageError.js | 2 ++ devtools/client/webconsole/constants.js | 1 + .../client/webconsole/reducers/messages.js | 4 ++++ .../client/webconsole/selectors/messages.js | 5 +++++ .../webconsole/webconsole-output-wrapper.js | 13 +++++++++++++ devtools/server/actors/replay/debugger.js | 4 ++-- devtools/server/actors/thread.js | 7 +++++++ 14 files changed, 102 insertions(+), 7 deletions(-) diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-01.js b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-01.js index e70ae5b4fb52..e39c2dae9b39 100644 --- a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-01.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-01.js @@ -18,6 +18,19 @@ function findMessages(hud, text, selector = ".message") { return elements; } +function waitForThreadEvents(console, eventName) { + info(`Waiting for thread event '${eventName}' to fire.`); + const thread = console.threadClient; + + return new Promise(function(resolve, reject) { + thread.addListener(eventName, function onEvent(eventName, ...args) { + info(`Thread event '${eventName}' fired.`); + thread.removeListener(eventName, onEvent); + resolve.apply(resolve, args); + }); + }); +} + async function openContextMenu(hud, element) { const onConsoleMenuOpened = hud.ui.consoleOutput.once("menu-open"); synthesizeContextMenuEvent(element); @@ -61,6 +74,10 @@ async function test() { await once(Services.ppmm, "TimeWarpFinished"); + await waitForThreadEvents(console, 'paused') + messages = findMessages(hud, "", ".paused"); + ok(messages.length == 1, "Found one paused message"); + let toolbox = await attachDebugger(tab), client = toolbox.threadClient; await client.interrupt(); diff --git a/devtools/client/themes/webconsole.css b/devtools/client/themes/webconsole.css index 92468122da57..548d7c7261fe 100644 --- a/devtools/client/themes/webconsole.css +++ b/devtools/client/themes/webconsole.css @@ -77,6 +77,7 @@ a { border-inline-start: solid 3px transparent; font-size: var(--console-output-font-size); line-height: var(--console-output-line-height); + position: relative; } /* @@ -106,6 +107,22 @@ a { background-color: var(--warning-background-color); } +.message.paused::before { + background: #d8461f; + opacity: 0.6; + width: 100vw; + height: 1px; + bottom: 0px; + left: -3px; + display: block; + content: ""; + position: absolute; +} + +.message.paused ~ .message { + opacity: 0.5; +} + .message.startGroup, .message.startGroupCollapsed { --console-output-indent-border-color: transparent; @@ -190,7 +207,7 @@ a { } -span.icon[title="Jump"] { +.message > span.icon[title="Jump"] { background-image:var(--theme-console-jump-image); background-size: 14px 14px; cursor: pointer; diff --git a/devtools/client/webconsole/actions/messages.js b/devtools/client/webconsole/actions/messages.js index ebd3d964f310..31ee490e9c20 100644 --- a/devtools/client/webconsole/actions/messages.js +++ b/devtools/client/webconsole/actions/messages.js @@ -21,6 +21,7 @@ const { MESSAGE_CLOSE, MESSAGE_TYPE, MESSAGE_TABLE_RECEIVE, + PAUSED_EXCECUTION_POINT, PRIVATE_MESSAGES_CLEAR, } = require("../constants"); @@ -57,6 +58,13 @@ function messagesClear() { }; } +function setPauseExecutionPoint(executionPoint) { + return { + type: PAUSED_EXCECUTION_POINT, + executionPoint + }; +} + function privateMessagesClear() { return { type: PRIVATE_MESSAGES_CLEAR @@ -139,4 +147,5 @@ module.exports = { privateMessagesClear, // for test purpose only. messageTableDataReceive, + setPauseExecutionPoint, }; diff --git a/devtools/client/webconsole/components/ConsoleOutput.js b/devtools/client/webconsole/components/ConsoleOutput.js index fb2d75c13c4a..704bec8af9f1 100644 --- a/devtools/client/webconsole/components/ConsoleOutput.js +++ b/devtools/client/webconsole/components/ConsoleOutput.js @@ -15,6 +15,7 @@ const { getAllMessagesTableDataById, getAllNetworkMessagesUpdateById, getVisibleMessages, + getPausedExecutionPoint, getAllRepeatById, } = require("devtools/client/webconsole/selectors/messages"); const MessageContainer = createFactory(require("devtools/client/webconsole/components/MessageContainer").MessageContainer); @@ -44,6 +45,7 @@ class ConsoleOutput extends Component { visibleMessages: PropTypes.array.isRequired, networkMessageActiveTabId: PropTypes.string.isRequired, onFirstMeaningfulPaint: PropTypes.func.isRequired, + pausedExecutionPoint: PropTypes.any }; } @@ -132,6 +134,7 @@ class ConsoleOutput extends Component { serviceContainer, timestampsVisible, initialized, + pausedExecutionPoint } = this.props; if (!initialized) { @@ -153,6 +156,7 @@ class ConsoleOutput extends Component { repeat: messagesRepeat[messageId], networkMessageUpdate: networkMessagesUpdate[messageId], networkMessageActiveTabId, + pausedExecutionPoint, getMessage: () => messages.get(messageId) })); @@ -183,6 +187,7 @@ function isScrolledToBottom(outputNode, scrollNode) { function mapStateToProps(state, props) { return { initialized: state.ui.initialized, + pausedExecutionPoint: getPausedExecutionPoint(state), messages: getAllMessagesById(state), visibleMessages: getVisibleMessages(state), messagesUi: getAllMessagesUiById(state), diff --git a/devtools/client/webconsole/components/Message.js b/devtools/client/webconsole/components/Message.js index b477cd96fa49..9eec14e5e94a 100644 --- a/devtools/client/webconsole/components/Message.js +++ b/devtools/client/webconsole/components/Message.js @@ -63,7 +63,8 @@ class Message extends Component { notes: PropTypes.arrayOf(PropTypes.shape({ messageBody: PropTypes.string.isRequired, frame: PropTypes.any, - })) + })), + isPaused: PropTypes.bool }; } @@ -143,6 +144,7 @@ class Message extends Component { collapseTitle, source, type, + isPaused, level, indent, topLevelClasses, @@ -156,7 +158,7 @@ class Message extends Component { notes } = this.props; - topLevelClasses.push("message", source, type, level); + topLevelClasses.push("message", source, type, level, isPaused ? "paused" : ""); if (open) { topLevelClasses.push("open"); } diff --git a/devtools/client/webconsole/components/MessageContainer.js b/devtools/client/webconsole/components/MessageContainer.js index e3d26a49e0ff..440f39ad07c4 100644 --- a/devtools/client/webconsole/components/MessageContainer.js +++ b/devtools/client/webconsole/components/MessageContainer.js @@ -24,6 +24,13 @@ const componentMap = new Map([ ["PageError", require("./message-types/PageError")] ]); +function isPaused({ getMessage, pausedExecutionPoint }) { + const message = getMessage(); + return pausedExecutionPoint + && message.executionPoint + && pausedExecutionPoint.checkpoint === message.executionPoint.checkpoint; +} + class MessageContainer extends Component { static get propTypes() { return { @@ -52,19 +59,23 @@ class MessageContainer extends Component { this.props.timestampsVisible !== nextProps.timestampsVisible; const networkMessageUpdateChanged = this.props.networkMessageUpdate !== nextProps.networkMessageUpdate; + const pausedChanged = isPaused(this.props) !== isPaused(nextProps); return repeatChanged || openChanged || tableDataChanged || timestampVisibleChanged - || networkMessageUpdateChanged; + || networkMessageUpdateChanged + || pausedChanged; } render() { const message = this.props.getMessage(); const MessageComponent = getMessageComponent(message); - return MessageComponent(Object.assign({message}, this.props)); + return MessageComponent(Object.assign({message}, this.props, { + isPaused: isPaused(this.props) + })); } } diff --git a/devtools/client/webconsole/components/message-types/ConsoleApiCall.js b/devtools/client/webconsole/components/message-types/ConsoleApiCall.js index 70cc57bb4c5a..2201ee9d0fe1 100644 --- a/devtools/client/webconsole/components/message-types/ConsoleApiCall.js +++ b/devtools/client/webconsole/components/message-types/ConsoleApiCall.js @@ -39,6 +39,7 @@ function ConsoleApiCall(props) { serviceContainer, timestampsVisible, repeat, + isPaused } = props; const { id: messageId, @@ -115,6 +116,7 @@ function ConsoleApiCall(props) { return Message({ messageId, executionPoint, + isPaused, open, collapsible, collapseTitle, diff --git a/devtools/client/webconsole/components/message-types/PageError.js b/devtools/client/webconsole/components/message-types/PageError.js index c8921a7d2ba4..bf7954616998 100644 --- a/devtools/client/webconsole/components/message-types/PageError.js +++ b/devtools/client/webconsole/components/message-types/PageError.js @@ -32,6 +32,7 @@ function PageError(props) { repeat, serviceContainer, timestampsVisible, + isPaused } = props; const { id: messageId, @@ -57,6 +58,7 @@ function PageError(props) { return Message({ dispatch, messageId, + isPaused, open, collapsible: Array.isArray(stacktrace), source, diff --git a/devtools/client/webconsole/constants.js b/devtools/client/webconsole/constants.js index 909fde07aa49..2d7c4dae3f4d 100644 --- a/devtools/client/webconsole/constants.js +++ b/devtools/client/webconsole/constants.js @@ -38,6 +38,7 @@ const actionTypes = { REVERSE_SEARCH_INPUT_CHANGE: "REVERSE_SEARCH_INPUT_CHANGE", REVERSE_SEARCH_NEXT: "REVERSE_SEARCH_NEXT", REVERSE_SEARCH_BACK: "REVERSE_SEARCH_BACK", + PAUSED_EXCECUTION_POINT: "PAUSED_EXCECUTION_POINT" }; const prefs = { diff --git a/devtools/client/webconsole/reducers/messages.js b/devtools/client/webconsole/reducers/messages.js index d35cc9fea11b..6e61f909a407 100644 --- a/devtools/client/webconsole/reducers/messages.js +++ b/devtools/client/webconsole/reducers/messages.js @@ -55,6 +55,7 @@ const MessageState = overrides => Object.freeze(Object.assign({ // Map of the form {messageId : networkInformation} // `networkInformation` holds request, response, totalTime, ... networkMessagesUpdateById: {}, + pausedExecutionPoint: null }, overrides)); function cloneState(state) { @@ -69,6 +70,7 @@ function cloneState(state) { removedActors: [...state.removedActors], repeatById: {...state.repeatById}, networkMessagesUpdateById: {...state.networkMessagesUpdateById}, + pausedExecutionPoint: state.pausedExecutionPoint }; } @@ -159,6 +161,8 @@ function messages(state = MessageState(), action, filtersState, prefsState) { let newState; switch (action.type) { + case constants.PAUSED_EXCECUTION_POINT: + return { ...state, pausedExecutionPoint: action.executionPoint }; case constants.MESSAGES_ADD: // Preemptively remove messages that will never be rendered const list = []; diff --git a/devtools/client/webconsole/selectors/messages.js b/devtools/client/webconsole/selectors/messages.js index bd25122c9255..f2bd8dda2084 100644 --- a/devtools/client/webconsole/selectors/messages.js +++ b/devtools/client/webconsole/selectors/messages.js @@ -49,6 +49,10 @@ function getGroupsById(state) { return state.messages.groupsById; } +function getPausedExecutionPoint(state) { + return state.messages.pausedExecutionPoint; +} + module.exports = { getAllGroupsById, getAllMessagesById, @@ -61,4 +65,5 @@ module.exports = { getGroupsById, getMessage, getVisibleMessages, + getPausedExecutionPoint }; diff --git a/devtools/client/webconsole/webconsole-output-wrapper.js b/devtools/client/webconsole/webconsole-output-wrapper.js index 48afb2d50a3d..e4b6084b2ab1 100644 --- a/devtools/client/webconsole/webconsole-output-wrapper.js +++ b/devtools/client/webconsole/webconsole-output-wrapper.js @@ -173,6 +173,9 @@ WebConsoleOutputWrapper.prototype = { }; if (this.toolbox) { + this.toolbox.threadClient.addListener("paused", this.dispatchPaused.bind(this)); + this.toolbox.threadClient.addListener("resumed", this.dispatchResumed.bind(this)); + Object.assign(serviceContainer, { onViewSourceInDebugger: frame => { this.toolbox.viewSourceInDebugger(frame.url, frame.line).then(() => { @@ -355,6 +358,16 @@ WebConsoleOutputWrapper.prototype = { store.dispatch(actions.timestampsToggle(enabled)); }, + dispatchPaused: function(_, packet) { + if (packet.executionPoint) { + store.dispatch(actions.setPauseExecutionPoint(packet.executionPoint)); + } + }, + + dispatchResumed: function(_, packet) { + store.dispatch(actions.setPauseExecutionPoint(null)); + }, + dispatchMessageUpdate: function(message, res) { // network-message-updated will emit when all the update message arrives. // Since we can't ensure the order of the network update, we check diff --git a/devtools/server/actors/replay/debugger.js b/devtools/server/actors/replay/debugger.js index 6bd23a9d297e..1c7e5e75fb1d 100644 --- a/devtools/server/actors/replay/debugger.js +++ b/devtools/server/actors/replay/debugger.js @@ -343,7 +343,7 @@ ReplayDebugger.prototype = { () => handler.call(this, this.getNewestFrame())); }, - _getNewConsoleMessage() { + getNewConsoleMessage() { const message = this._sendRequest({ type: "getNewConsoleMessage" }); return this._convertConsoleMessage(message); }, @@ -353,7 +353,7 @@ ReplayDebugger.prototype = { }, set onConsoleMessage(handler) { this._breakpointKindSetter("ConsoleMessage", handler, - () => handler.call(this, this._getNewConsoleMessage())); + () => handler.call(this, this.getNewConsoleMessage())); }, clearAllBreakpoints: NYI, diff --git a/devtools/server/actors/thread.js b/devtools/server/actors/thread.js index 7a5cfbac84bd..ec20c34f5b03 100644 --- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -1479,6 +1479,13 @@ const ThreadActor = ActorClassWithSpec(threadSpec, { packet.frame = this._createFrameActor(frame).form(); } + if (this.dbg.replaying) { + const message = this.dbg.getNewConsoleMessage(); + if (message) { + packet.executionPoint = message.executionPoint; + } + } + if (poppedFrames) { packet.poppedFrames = poppedFrames; }