From 5970b1a8fea0e6844f10e59032d74b912d703c15 Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Tue, 9 Jul 2019 22:01:34 +0000 Subject: [PATCH] Bug 1354679 - Automatically display the PausedDebuggerOverlay when the debugger is paused. r=gl Differential Revision: https://phabricator.services.mozilla.com/D35162 --HG-- extra : moz-landing-system : lando --- devtools/client/debugger/src/utils/prefs.js | 2 + devtools/client/framework/toolbox.js | 3 + devtools/client/preferences/debugger.js | 1 + devtools/server/actors/highlighters.css | 57 ++++++++++-- devtools/server/actors/highlighters.js | 7 ++ .../actors/highlighters/paused-debugger.js | 89 +++++++++++++++---- devtools/server/actors/thread.js | 54 +++++++++++ .../test_highlighter_paused_debugger.html | 22 ++--- 8 files changed, 202 insertions(+), 33 deletions(-) diff --git a/devtools/client/debugger/src/utils/prefs.js b/devtools/client/debugger/src/utils/prefs.js index 8f4634331764..d7bac79341c9 100644 --- a/devtools/client/debugger/src/utils/prefs.js +++ b/devtools/client/debugger/src/utils/prefs.js @@ -69,6 +69,7 @@ if (isDevelopment()) { pref("devtools.debugger.features.event-listeners-breakpoints", true); pref("devtools.debugger.features.log-points", true); pref("devtools.debugger.log-actions", true); + pref("devtools.debugger.features.overlay-step-buttons", false); } export const prefs = new PrefsHelper("devtools", { @@ -128,6 +129,7 @@ export const features = new PrefsHelper("devtools.debugger.features", { originalBlackbox: ["Bool", "original-blackbox"], eventListenersBreakpoints: ["Bool", "event-listeners-breakpoints"], logPoints: ["Bool", "log-points"], + showOverlayStepButtons: ["Bool", "debugger.features.overlay-step-buttons"], }); export const asyncStore = asyncStoreHelper("debugger", { diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js index 1336c7121400..8481528f8c42 100644 --- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -583,6 +583,9 @@ Toolbox.prototype = { ignoreCaughtExceptions: Services.prefs.getBoolPref( "devtools.debugger.ignore-caught-exceptions" ), + showOverlayStepButtons: Services.prefs.getBoolPref( + "devtools.debugger.features.overlay-step-buttons" + ), }; const [, threadClient] = await this._target.attachThread(threadOptions); diff --git a/devtools/client/preferences/debugger.js b/devtools/client/preferences/debugger.js index 09901724638e..007851bbdbe8 100644 --- a/devtools/client/preferences/debugger.js +++ b/devtools/client/preferences/debugger.js @@ -76,3 +76,4 @@ pref("devtools.debugger.features.original-blackbox", true); pref("devtools.debugger.features.windowless-workers", true); pref("devtools.debugger.features.event-listeners-breakpoints", true); pref("devtools.debugger.features.log-points", true); +pref("devtools.debugger.features.overlay-step-buttons", false); \ No newline at end of file diff --git a/devtools/server/actors/highlighters.css b/devtools/server/actors/highlighters.css index b21dfe489a0e..32151629365d 100644 --- a/devtools/server/actors/highlighters.css +++ b/devtools/server/actors/highlighters.css @@ -584,8 +584,12 @@ position: fixed; top: 0; left: 0; - width: 100%; - height: 100%; + zoom: 1; + right: 0; + bottom: 0; + + width: 100vw; + height: 100vh; display: flex; align-items: center; @@ -596,7 +600,7 @@ --text-color: #585959; /* --theme-body-color-alt */ --toolbar-background: #fcfcfc; /* --theme-toolbar-background */; --toolbar-border: #dde1e4; /* --theme-splitter-color */ - --toolbar-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26); /* --rdm-box-shadow */ + --toolbar-box-shadow: 0 2px 2px 0 rgba(155, 155, 155, 0.26); /* --rdm-box-shadow */ --overlay-background: #dde1e4a8; } @@ -607,21 +611,64 @@ :-moz-native-anonymous .paused-dbg-toolbar { margin-top: 15px; - padding: 4px 5px; display: inline-flex; -moz-user-select: none; - pointer-events: auto; color: var(--text-color); border-radius: 2px; box-shadow: var(--toolbar-box-shadow); background-color: var(--toolbar-background); border: 1px solid var(--toolbar-border); + border-radius: 4px; font: var(--highlighter-font-family); font-size: var(--highlighter-font-size); } +:-moz-native-anonymous .paused-dbg-toolbar button { + margin: 8px 4px 6px 6px; + width: 14px; + height: 14px; + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 14px 14px; + background-color: var(--text-color); + + border: 0px; + -moz-appearance: none; +} + +:-moz-native-anonymous .paused-dbg-toolbar button:hover { + cursor: pointer; +} + +:-moz-native-anonymous .paused-dbg-divider { + width: 1px; + height: 14px; + margin-top: 8px; + background-color: var(--toolbar-border); +} + +:-moz-native-anonymous button.paused-dbg-step-button { + margin-left: 10px; + mask-image: url(resource://devtools/client/debugger/images/stepOver.svg); +} + +:-moz-native-anonymous button.paused-dbg-resume-button { + margin-right: 10px; + mask-image: url(resource://devtools/client/debugger/images/resume.svg); +} + +:-moz-native-anonymous .paused-dbg-reason { + padding: 1px 16px; + margin: 6px 0px; + line-height: 16px; + font-size: 14px; + font: var(--highlighter-font-family); + font-size: var(--highlighter-font-size); +} + /* Shapes highlighter */ :-moz-native-anonymous .shapes-root { diff --git a/devtools/server/actors/highlighters.js b/devtools/server/actors/highlighters.js index 37de4b6167c5..b70891989816 100644 --- a/devtools/server/actors/highlighters.js +++ b/devtools/server/actors/highlighters.js @@ -257,6 +257,10 @@ exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, { _currentNode: null, pick: function() { + if (this._targetActor.threadActor) { + this._targetActor.threadActor.hideOverlay(); + } + if (this._isPicking) { return null; } @@ -484,6 +488,9 @@ exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, { }, cancelPick: function() { + if (this._targetActor.threadActor) { + this._targetActor.threadActor.showOverlay(); + } if (this._isPicking) { this._highlighter.hide(); this._stopPickerListeners(); diff --git a/devtools/server/actors/highlighters/paused-debugger.js b/devtools/server/actors/highlighters/paused-debugger.js index 1ca2208a8b31..71d59bc982df 100644 --- a/devtools/server/actors/highlighters/paused-debugger.js +++ b/devtools/server/actors/highlighters/paused-debugger.js @@ -9,6 +9,10 @@ const { createNode, } = require("./utils/markup"); +const { LocalizationHelper } = require("devtools/shared/l10n"); +const STRINGS_URI = "devtools/client/shared/locales/debugger.properties"; +const L10N = new LocalizationHelper(STRINGS_URI); + /** * The PausedDebuggerOverlay is a class that displays a semi-transparent mask on top of * the whole page and a toolbar at the top of the page. @@ -16,8 +20,12 @@ const { * The toolbar is used to display the reason for the pause in script execution as well as * buttons to resume or step through the program. */ -function PausedDebuggerOverlay(highlighterEnv) { +function PausedDebuggerOverlay(highlighterEnv, options) { this.env = highlighterEnv; + this.showOverlayStepButtons = options.showOverlayStepButtons; + this.resume = options.resume; + this.stepOver = options.stepOver; + this.markup = new CanvasFrameAnonymousContentHelper( highlighterEnv, this._buildMarkup.bind(this) @@ -68,6 +76,37 @@ PausedDebuggerOverlay.prototype = { prefix, }); + if (this.showOverlayStepButtons) { + createNode(window, { + parent: toolbar, + attributes: { + id: "divider", + class: "divider", + }, + prefix, + }); + + createNode(window, { + nodeType: "button", + parent: toolbar, + attributes: { + id: "step-button", + class: "step-button", + }, + prefix, + }); + + createNode(window, { + nodeType: "button", + parent: toolbar, + attributes: { + id: "resume-button", + class: "resume-button", + }, + prefix, + }); + } + return container; }, @@ -77,35 +116,55 @@ PausedDebuggerOverlay.prototype = { this.env = null; }, + onClick(target) { + if (target.id == "paused-dbg-step-button") { + this.stepOver(); + } else if (target.id == "paused-dbg-resume-button") { + this.resume(); + } + }, + + handleEvent(e) { + switch (e.type) { + case "click": + case "mouseup": + this.onClick(e.target); + break; + case "DOMMouseScroll": + // Prevent scrolling. That's because we only took a screenshot of the viewport, so + // scrolling out of the viewport wouldn't draw the expected things. In the future + // we can take the screenshot again on scroll, but for now it doesn't seem + // important. + e.preventDefault(); + break; + case "mouseover": + console.log(`> mouse over ${e.target.id}`); + break; + } + }, + getElement(id) { return this.markup.getElement(this.ID_CLASS_PREFIX + id); }, show(node, options = {}) { - if (this.env.isXUL) { + if (this.env.isXUL || !options.reason) { return false; } // Show the highlighter's root element. const root = this.getElement("root"); root.removeAttribute("hidden"); - - // The page overlay is only shown upon request. Sometimes we just want the toolbar. - if (options.onlyToolbar) { - root.removeAttribute("overlay"); - } else { - root.setAttribute("overlay", "true"); - } + root.setAttribute("overlay", "true"); // Set the text to appear in the toolbar. const toolbar = this.getElement("toolbar"); - if (options.reason) { - this.getElement("reason").setTextContent(options.reason); - toolbar.removeAttribute("hidden"); - } else { - toolbar.setAttribute("hidden", "true"); - } + this.getElement("reason").setTextContent( + L10N.getStr(`whyPaused.${options.reason}`) + ); + toolbar.removeAttribute("hidden"); + this.env.window.document.setSuppressedEventListener(this); return true; }, diff --git a/devtools/server/actors/thread.js b/devtools/server/actors/thread.js index 4395c5e75569..8f9d3df46966 100644 --- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -53,6 +53,19 @@ loader.lazyRequireGetter( ); loader.lazyRequireGetter(this, "throttle", "devtools/shared/throttle", true); +loader.lazyRequireGetter( + this, + "HighlighterEnvironment", + "devtools/server/actors/highlighters", + true +); +loader.lazyRequireGetter( + this, + "PausedDebuggerOverlay", + "devtools/server/actors/highlighters/paused-debugger", + true +); + /** * JSD2 actors. */ @@ -91,6 +104,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, { this._observingNetwork = false; this._activeEventBreakpoints = new Set(); this._activeEventPause = null; + this._pauseOverlay = null; this._priorPause = null; @@ -241,6 +255,10 @@ const ThreadActor = ActorClassWithSpec(threadSpec, { } }, + isPaused() { + return this._state === "paused"; + }, + /** * Remove all debuggees and clear out the thread's sources. */ @@ -396,6 +414,40 @@ const ThreadActor = ActorClassWithSpec(threadSpec, { } }, + get pauseOverlay() { + if (this._pauseOverlay) { + return this._pauseOverlay; + } + + const env = new HighlighterEnvironment(); + env.initFromTargetActor(this._parent); + const highlighter = new PausedDebuggerOverlay(env, { + showOverlayStepButtons: this._options.showOverlayStepButtons, + resume: () => this.onResume({ resumeLimit: null }), + stepOver: () => this.onResume({ resumeLimit: { type: "next" } }), + }); + this._pauseOverlay = highlighter; + return highlighter; + }, + + showOverlay() { + if ( + this._parent.on && + this.pauseOverlay && + !this._parent.window.isChromeWindow && + this.isPaused() + ) { + const reason = this._priorPause.why.type; + this.pauseOverlay.show(null, { reason }); + } + }, + + hideOverlay(msg) { + if (this._parent.on && !this._parent.window.isChromeWindow) { + this.pauseOverlay.hide(); + } + }, + /** * Tell the thread to automatically add a breakpoint on the first line of * a given file, when it is first loaded. @@ -712,6 +764,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, { this._priorPause = pkt; this.conn.sendActorEvent(this.actorID, "paused", pkt); + this.showOverlay(); } catch (error) { reportError(error); this.conn.send({ @@ -1229,6 +1282,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, { // Tell anyone who cares of the resume (as of now, that's the xpcshell harness and // devtools-startup.js when handling the --wait-for-jsdebugger flag) this.conn.sendActorEvent(this.actorID, "resumed"); + this.hideOverlay(); if (Services.obs) { Services.obs.notifyObservers(this, "devtools-thread-resumed"); diff --git a/devtools/server/tests/mochitest/test_highlighter_paused_debugger.html b/devtools/server/tests/mochitest/test_highlighter_paused_debugger.html index 133124719ce3..46d948b1a1ca 100644 --- a/devtools/server/tests/mochitest/test_highlighter_paused_debugger.html +++ b/devtools/server/tests/mochitest/test_highlighter_paused_debugger.html @@ -25,7 +25,7 @@ window.onload = function() { const env = new HighlighterEnvironment(); env.initFromWindow(window); - const highlighter = new PausedDebuggerOverlay(env); + const highlighter = new PausedDebuggerOverlay(env, {}); const anonymousContent = highlighter.markup.content; const id = elementID => `${highlighter.ID_CLASS_PREFIX}${elementID}`; @@ -53,32 +53,28 @@ window.onload = function() { ok(isHidden("root"), "The highlighter is hidden"); info("Show the highlighter with overlay and toolbar"); - let didShow = highlighter.show(null, {"reason": "Paused in debugger"}); + let didShow = highlighter.show(null, { "reason": "breakpoint" }); ok(didShow, "Calling show returned true"); ok(!isHidden("root"), "The highlighter is shown"); ok(isOverlayShown(), "The overlay is shown"); - is(getReason(), "Paused in debugger", "The reason displayed in the toolbar is correct"); + is( + getReason(), + "Paused on breakpoint", + "The reason displayed in the toolbar is correct" + ); info("Call show again with another reason"); - didShow = highlighter.show(null, {"reason": "Paused for another reason"}); + didShow = highlighter.show(null, {"reason": "debuggerStatement"}); ok(didShow, "Calling show returned true too"); ok(!isHidden("root"), "The highlighter is still shown"); - is(getReason(), "Paused for another reason", + is(getReason(), "Paused on debugger statement", "The reason displayed in the toolbar is correct again"); ok(isOverlayShown(), "The overlay is still shown too"); info("Call show again but with no reason"); highlighter.show(); - ok(isHidden("toolbar"), "The toolbar is hidden"); ok(isOverlayShown(), "The overlay is shown however"); - info("Call show again with a reason but no overlay"); - highlighter.show(null, {reason: "no overlay this time", onlyToolbar: true}); - ok(!isHidden("toolbar"), "The toolbar is shown this time"); - is(getReason(), "no overlay this time", - "The reason displayed in the toolbar is correct again"); - ok(!isOverlayShown(), "The overlay is hidden"); - info("Hide the highlighter"); highlighter.hide(); ok(isHidden("root"), "The highlighter is now hidden");