Bug 1496468 - Add a console pause indicator. r=loganfsmyth

Differential Revision: https://phabricator.services.mozilla.com/D7741

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jason Laster 2018-10-04 20:14:53 +00:00
parent 9f095c8011
commit 46254f84e4
14 changed files with 102 additions and 7 deletions

View File

@ -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();

View File

@ -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;

View File

@ -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,
};

View File

@ -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),

View File

@ -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");
}

View File

@ -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)
}));
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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 = {

View File

@ -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 = [];

View File

@ -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
};

View File

@ -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

View File

@ -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,

View File

@ -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;
}