Bug 1946184 - Fix computing the CSD margin right after calling HideWindowChrome(...
[gecko.git] / devtools / client / debugger / panel.js
blobd0149dd75de593d95c2e7f4d0d12541b78528a00
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
5 "use strict";
7 const {
8 MultiLocalizationHelper,
9 } = require("resource://devtools/shared/l10n.js");
10 const {
11 FluentL10n,
12 } = require("resource://devtools/client/shared/fluent-l10n/fluent-l10n.js");
14 loader.lazyRequireGetter(
15 this,
16 "openContentLink",
17 "resource://devtools/client/shared/link.js",
18 true
20 loader.lazyRequireGetter(
21 this,
22 "features",
23 "resource://devtools/client/debugger/src/utils/prefs.js",
24 true
26 loader.lazyRequireGetter(
27 this,
28 "getOriginalLocation",
29 "resource://devtools/client/debugger/src/utils/source-maps.js",
30 true
32 loader.lazyRequireGetter(
33 this,
34 "createLocation",
35 "resource://devtools/client/debugger/src/utils/location.js",
36 true
38 loader.lazyRequireGetter(
39 this,
40 "registerStoreObserver",
41 "resource://devtools/client/shared/redux/subscriber.js",
42 true
44 loader.lazyRequireGetter(
45 this,
46 "getMappedExpression",
47 "resource://devtools/client/debugger/src/actions/expressions.js",
48 true
51 const DBG_STRINGS_URI = [
52 "devtools/client/locales/debugger.properties",
53 // These are used in the AppErrorBoundary component
54 "devtools/client/locales/startup.properties",
55 "devtools/client/locales/components.properties",
56 // Used by SourceMapLoader
57 "devtools/client/locales/toolbox.properties",
59 const L10N = new MultiLocalizationHelper(...DBG_STRINGS_URI);
61 async function getNodeFront(gripOrFront, toolbox) {
62 // Given a NodeFront
63 if ("actorID" in gripOrFront) {
64 return new Promise(resolve => resolve(gripOrFront));
67 const inspectorFront = await toolbox.target.getFront("inspector");
68 return inspectorFront.getNodeFrontFromNodeGrip(gripOrFront);
71 class DebuggerPanel {
72 constructor(iframeWindow, toolbox, commands) {
73 this.panelWin = iframeWindow;
74 this.panelWin.L10N = L10N;
75 this.panelWin.sourceMapURLService = toolbox.sourceMapURLService;
77 this.toolbox = toolbox;
78 this.commands = commands;
81 async open() {
82 // whypaused-* strings are in devtools/shared as they're used in the PausedDebuggerOverlay as well
83 const fluentL10n = new FluentL10n();
84 await fluentL10n.init(["devtools/shared/debugger-paused-reasons.ftl"]);
86 const { actions, store, selectors, client } =
87 await this.panelWin.Debugger.bootstrap({
88 commands: this.commands,
89 fluentBundles: fluentL10n.getBundles(),
90 resourceCommand: this.toolbox.resourceCommand,
91 workers: {
92 sourceMapLoader: this.toolbox.sourceMapLoader,
93 parserWorker: this.toolbox.parserWorker,
95 panel: this,
96 });
98 this._actions = actions;
99 this._store = store;
100 this._selectors = selectors;
101 this._client = client;
103 registerStoreObserver(this._store, this._onDebuggerStateChange.bind(this));
105 return this;
108 async _onDebuggerStateChange(state, oldState) {
109 const { getCurrentThread } = this._selectors;
110 const currentThreadActorID = getCurrentThread(state);
112 if (
113 currentThreadActorID &&
114 currentThreadActorID !== getCurrentThread(oldState)
116 const threadFront =
117 this.commands.client.getFrontByID(currentThreadActorID);
118 this.toolbox.selectTarget(threadFront?.targetFront.actorID);
121 this.toolbox.emit(
122 "show-original-variable-mapping-warnings",
123 this.shouldShowOriginalVariableMappingWarnings()
127 shouldShowOriginalVariableMappingWarnings() {
128 const { getSelectedSource, isMapScopesEnabled } = this._selectors;
129 if (!this.isPaused() || isMapScopesEnabled(this._getState())) {
130 return false;
132 const selectedSource = getSelectedSource(this._getState());
133 return selectedSource?.isOriginal && !selectedSource?.isPrettyPrinted;
136 getVarsForTests() {
137 return {
138 store: this._store,
139 selectors: this._selectors,
140 actions: this._actions,
141 client: this._client,
145 _getState() {
146 return this._store.getState();
149 getToolboxStore() {
150 return this.toolbox.store;
153 openLink(url) {
154 openContentLink(url);
157 async openConsoleAndEvaluate(input) {
158 const { hud } = await this.toolbox.selectTool("webconsole");
159 hud.ui.wrapper.dispatchEvaluateExpression(input);
162 async openInspector() {
163 this.toolbox.selectTool("inspector");
166 async openElementInInspector(gripOrFront) {
167 const onSelectInspector = this.toolbox.selectTool("inspector");
168 const onGripNodeToFront = getNodeFront(gripOrFront, this.toolbox);
170 const [front, inspector] = await Promise.all([
171 onGripNodeToFront,
172 onSelectInspector,
175 const onInspectorUpdated = inspector.once("inspector-updated");
176 const onNodeFrontSet = this.toolbox.selection.setNodeFront(front, {
177 reason: "debugger",
180 return Promise.all([onNodeFrontSet, onInspectorUpdated]);
183 highlightDomElement(gripOrFront) {
184 if (!this._highlight) {
185 const { highlight, unhighlight } = this.toolbox.getHighlighter();
186 this._highlight = highlight;
187 this._unhighlight = unhighlight;
190 return this._highlight(gripOrFront);
193 unHighlightDomElement() {
194 if (!this._unhighlight) {
195 return Promise.resolve();
198 return this._unhighlight();
202 * Return the Frame Actor ID of the currently selected frame,
203 * or null if the debugger isn't paused.
205 getSelectedFrameActorID() {
206 const selectedFrame = this._selectors.getSelectedFrame(this._getState());
207 if (selectedFrame) {
208 return selectedFrame.id;
210 return null;
213 getMappedExpression(expression) {
214 const thread = this._selectors.getCurrentThread(this._getState());
215 return getMappedExpression(expression, thread, {
216 getState: this._store.getState,
217 parserWorker: this.toolbox.parserWorker,
222 * Return the source-mapped variables for the current scope.
223 * @returns {{[String]: String} | null} A dictionary mapping original variable names to generated
224 * variable names if map scopes is enabled, otherwise null.
226 getMappedVariables() {
227 if (!this._selectors.isMapScopesEnabled(this._getState())) {
228 return null;
230 const thread = this._selectors.getCurrentThread(this._getState());
231 return this._selectors.getSelectedScopeMappings(this._getState(), thread);
234 isPaused() {
235 const thread = this._selectors.getCurrentThread(this._getState());
236 return this._selectors.getIsPaused(this._getState(), thread);
239 selectSourceURL(url, line, column) {
240 return this._actions.selectSourceURL(url, { line, column });
244 * This is called when some other panels wants to open a given source
245 * in the debugger at a precise line/column.
247 * @param {String} generatedURL
248 * @param {Number} generatedLine
249 * @param {Number} generatedColumn
250 * @param {String} sourceActorId (optional)
251 * If the callsite knows about a particular sourceActorId,
252 * or if the source doesn't have a URL, you have to pass a sourceActorId.
253 * @param {String} reason
254 * A telemetry identifier to record when opening the debugger.
255 * This help differentiate why we opened the debugger.
257 * @return {Boolean}
258 * Returns true if the location is known by the debugger
259 * and the debugger opens it.
261 async openSourceInDebugger({
262 generatedURL,
263 generatedLine,
264 generatedColumn,
265 sourceActorId,
266 reason,
267 }) {
268 const generatedSource = sourceActorId
269 ? this._selectors.getSourceByActorId(this._getState(), sourceActorId)
270 : this._selectors.getSourceByURL(this._getState(), generatedURL);
271 // We won't try opening source in the debugger when we can't find the related source actor in the reducer,
272 // or, when it doesn't have any related source actor registered.
273 if (
274 !generatedSource ||
275 // Note: We're not entirely sure when this can happen,
276 // so we may want to revisit that at some point.
277 !this._selectors.getSourceActorsForSource(
278 this._getState(),
279 generatedSource.id
280 ).length
282 return false;
285 const generatedLocation = createLocation({
286 source: generatedSource,
287 line: generatedLine,
288 column: generatedColumn,
291 // Note that getOriginalLocation can easily return generatedLocation
292 // if the location can't be mapped to any original source.
293 // So that we may open either regular source or original sources here.
294 const originalLocation = await getOriginalLocation(generatedLocation, {
295 // Reproduce a minimal thunkArgs for getOriginalLocation.
296 sourceMapLoader: this.toolbox.sourceMapLoader,
297 getState: this._store.getState,
300 // view-source module only forced the load of debugger in the background.
301 // Now that we know we want to show a source, force displaying it in foreground.
303 // Note that browser_markup_view-source.js doesn't wait for the debugger
304 // to be fully loaded with the source and requires the debugger to be loaded late.
305 // But we might try to load display it early to improve user perception.
306 await this.toolbox.selectTool("jsdebugger", reason);
308 await this._actions.selectSpecificLocation(originalLocation);
310 // XXX: should this be moved to selectSpecificLocation??
311 if (this._selectors.hasLogpoint(this._getState(), originalLocation)) {
312 this._actions.openConditionalPanel(originalLocation, true);
315 return true;
318 async selectServiceWorker(workerDescriptorFront) {
319 // The descriptor used by the application panel isn't fetching the worker target,
320 // but the debugger will fetch it via the watcher actor and TargetCommand.
321 // So try to match the descriptor with its related target.
322 const targets = this.commands.targetCommand.getAllTargets([
323 this.commands.targetCommand.TYPES.SERVICE_WORKER,
325 const workerTarget = targets.find(
326 target => target.id == workerDescriptorFront.id
329 const threadFront = await workerTarget.getFront("thread");
330 const threadActorID = threadFront?.actorID;
331 const isThreadAvailable = this._selectors
332 .getThreads(this._getState())
333 .find(x => x.actor === threadActorID);
335 if (!features.windowlessServiceWorkers) {
336 console.error(
337 "Selecting a worker needs the pref debugger.features.windowless-service-workers set to true"
339 return;
342 if (!isThreadAvailable) {
343 console.error(`Worker ${threadActorID} is not available for debugging`);
344 return;
347 // select worker's thread
348 this.selectThread(threadActorID);
350 // select worker's source
351 const source = this._selectors.getSourceByURL(
352 this._getState(),
353 workerDescriptorFront._url
355 const sourceActor = this._selectors.getFirstSourceActorForGeneratedSource(
356 this._getState(),
357 source.id,
358 threadActorID
360 await this._actions.selectSource(source, sourceActor);
363 selectThread(threadActorID) {
364 this._actions.selectThread(threadActorID);
367 showTracerSidebar() {
368 this._actions.setPrimaryPaneTab("tracer");
371 destroy() {
372 this.panelWin.Debugger.destroy();
373 this.emit("destroyed");
377 exports.DebuggerPanel = DebuggerPanel;