260 lines
6.6 KiB
JavaScript
260 lines
6.6 KiB
JavaScript
/* 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 {
|
|
CanvasFrameAnonymousContentHelper,
|
|
} = require("resource://devtools/server/actors/highlighters/utils/markup.js");
|
|
|
|
loader.lazyGetter(this, "PausedReasonsBundle", () => {
|
|
return new Localization(
|
|
["devtools/shared/debugger-paused-reasons.ftl"],
|
|
true
|
|
);
|
|
});
|
|
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"DEBUGGER_PAUSED_REASONS_L10N_MAPPING",
|
|
"resource://devtools/shared/constants.js",
|
|
true
|
|
);
|
|
|
|
/**
|
|
* 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.
|
|
* This is used to signal to users that script execution is current paused.
|
|
* 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.
|
|
*/
|
|
class PausedDebuggerOverlay {
|
|
constructor(highlighterEnv, options = {}) {
|
|
this.env = highlighterEnv;
|
|
this.resume = options.resume;
|
|
this.stepOver = options.stepOver;
|
|
|
|
this.lastTarget = null;
|
|
|
|
this.markup = new CanvasFrameAnonymousContentHelper(
|
|
highlighterEnv,
|
|
this._buildMarkup.bind(this),
|
|
{ waitForDocumentToLoad: false }
|
|
);
|
|
this.isReady = this.markup.initialize();
|
|
}
|
|
|
|
ID_CLASS_PREFIX = "paused-dbg-";
|
|
|
|
_buildMarkup() {
|
|
const prefix = this.ID_CLASS_PREFIX;
|
|
|
|
const container = this.markup.createNode({
|
|
attributes: { class: "highlighter-container" },
|
|
});
|
|
|
|
// Wrapper element.
|
|
const wrapper = this.markup.createNode({
|
|
parent: container,
|
|
attributes: {
|
|
id: "root",
|
|
class: "root",
|
|
hidden: "true",
|
|
overlay: "true",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
const toolbar = this.markup.createNode({
|
|
parent: wrapper,
|
|
attributes: {
|
|
id: "toolbar",
|
|
class: "toolbar",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
this.markup.createNode({
|
|
nodeType: "span",
|
|
parent: toolbar,
|
|
attributes: {
|
|
id: "reason",
|
|
class: "reason",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
this.markup.createNode({
|
|
parent: toolbar,
|
|
attributes: {
|
|
id: "divider",
|
|
class: "divider",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
const stepWrapper = this.markup.createNode({
|
|
parent: toolbar,
|
|
attributes: {
|
|
id: "step-button-wrapper",
|
|
class: "step-button-wrapper",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
this.markup.createNode({
|
|
nodeType: "button",
|
|
parent: stepWrapper,
|
|
attributes: {
|
|
id: "step-button",
|
|
class: "step-button",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
const resumeWrapper = this.markup.createNode({
|
|
parent: toolbar,
|
|
attributes: {
|
|
id: "resume-button-wrapper",
|
|
class: "resume-button-wrapper",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
this.markup.createNode({
|
|
nodeType: "button",
|
|
parent: resumeWrapper,
|
|
attributes: {
|
|
id: "resume-button",
|
|
class: "resume-button",
|
|
},
|
|
prefix,
|
|
});
|
|
|
|
return container;
|
|
}
|
|
|
|
destroy() {
|
|
this.hide();
|
|
this.markup.destroy();
|
|
this.env = null;
|
|
this.lastTarget = null;
|
|
}
|
|
|
|
onClick(target) {
|
|
const { id } = target;
|
|
if (!id) {
|
|
return;
|
|
}
|
|
|
|
if (id.includes("paused-dbg-step-button")) {
|
|
this.stepOver();
|
|
} else if (id.includes("paused-dbg-resume-button")) {
|
|
this.resume();
|
|
}
|
|
}
|
|
|
|
onMouseMove(target) {
|
|
// Not an element we care about
|
|
if (!target || !target.id) {
|
|
return;
|
|
}
|
|
|
|
// If the user didn't change targets, do nothing
|
|
if (this.lastTarget && this.lastTarget.id === target.id) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
target.id.includes("step-button") ||
|
|
target.id.includes("resume-button")
|
|
) {
|
|
// The hover should be applied to the wrapper (icon's parent node)
|
|
const newTarget = target.parentNode.id.includes("wrapper")
|
|
? target.parentNode
|
|
: target;
|
|
|
|
// Remove the hover class if the user has changed buttons
|
|
if (this.lastTarget && this.lastTarget != newTarget) {
|
|
this.lastTarget.classList.remove("hover");
|
|
}
|
|
newTarget.classList.add("hover");
|
|
this.lastTarget = newTarget;
|
|
} else if (this.lastTarget) {
|
|
// Remove the hover class if the user isn't on a button
|
|
this.lastTarget.classList.remove("hover");
|
|
}
|
|
}
|
|
|
|
handleEvent(e) {
|
|
switch (e.type) {
|
|
case "mousedown":
|
|
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 "mousemove":
|
|
this.onMouseMove(e.target);
|
|
break;
|
|
}
|
|
}
|
|
|
|
getElement(id) {
|
|
return this.markup.getElement(this.ID_CLASS_PREFIX + id);
|
|
}
|
|
|
|
show(reason) {
|
|
if (this.env.isXUL || !reason) {
|
|
return false;
|
|
}
|
|
|
|
// Only track mouse movement when the the overlay is shown
|
|
// Prevents mouse tracking when the user isn't paused
|
|
const { pageListenerTarget } = this.env;
|
|
pageListenerTarget.addEventListener("mousemove", this);
|
|
|
|
// Show the highlighter's root element.
|
|
const root = this.getElement("root");
|
|
root.removeAttribute("hidden");
|
|
root.setAttribute("overlay", "true");
|
|
|
|
// Set the text to appear in the toolbar.
|
|
const toolbar = this.getElement("toolbar");
|
|
this.getElement("reason").setTextContent(
|
|
PausedReasonsBundle.formatValueSync(
|
|
DEBUGGER_PAUSED_REASONS_L10N_MAPPING[reason]
|
|
)
|
|
);
|
|
toolbar.removeAttribute("hidden");
|
|
|
|
// When the debugger pauses execution in a page, events will not be delivered
|
|
// to any handlers added to elements on that page. So here we use the
|
|
// document's setSuppressedEventListener interface to still be able to act on mouse
|
|
// events (they'll be handled by the `handleEvent` method)
|
|
this.env.window.document.setSuppressedEventListener(this);
|
|
return true;
|
|
}
|
|
|
|
hide() {
|
|
if (this.env.isXUL) {
|
|
return;
|
|
}
|
|
|
|
const { pageListenerTarget } = this.env;
|
|
pageListenerTarget.removeEventListener("mousemove", this);
|
|
|
|
// Hide the overlay.
|
|
this.getElement("root").setAttribute("hidden", "true");
|
|
// Remove the hover state
|
|
this.getElement("step-button-wrapper").classList.remove("hover");
|
|
this.getElement("resume-button-wrapper").classList.remove("hover");
|
|
}
|
|
}
|
|
exports.PausedDebuggerOverlay = PausedDebuggerOverlay;
|