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/>. */
8 MultiLocalizationHelper
,
9 } = require("resource://devtools/shared/l10n.js");
12 } = require("resource://devtools/client/shared/fluent-l10n/fluent-l10n.js");
14 loader
.lazyRequireGetter(
17 "resource://devtools/client/shared/link.js",
20 loader
.lazyRequireGetter(
23 "resource://devtools/client/debugger/src/utils/prefs.js",
26 loader
.lazyRequireGetter(
28 "getOriginalLocation",
29 "resource://devtools/client/debugger/src/utils/source-maps.js",
32 loader
.lazyRequireGetter(
35 "resource://devtools/client/debugger/src/utils/location.js",
38 loader
.lazyRequireGetter(
40 "registerStoreObserver",
41 "resource://devtools/client/shared/redux/subscriber.js",
44 loader
.lazyRequireGetter(
46 "getMappedExpression",
47 "resource://devtools/client/debugger/src/actions/expressions.js",
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
) {
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
);
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
;
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
,
92 sourceMapLoader
: this.toolbox
.sourceMapLoader
,
93 parserWorker
: this.toolbox
.parserWorker
,
98 this._actions
= actions
;
100 this._selectors
= selectors
;
101 this._client
= client
;
103 registerStoreObserver(this._store
, this._onDebuggerStateChange
.bind(this));
108 async
_onDebuggerStateChange(state
, oldState
) {
109 const { getCurrentThread
} = this._selectors
;
110 const currentThreadActorID
= getCurrentThread(state
);
113 currentThreadActorID
&&
114 currentThreadActorID
!== getCurrentThread(oldState
)
117 this.commands
.client
.getFrontByID(currentThreadActorID
);
118 this.toolbox
.selectTarget(threadFront
?.targetFront
.actorID
);
122 "show-original-variable-mapping-warnings",
123 this.shouldShowOriginalVariableMappingWarnings()
127 shouldShowOriginalVariableMappingWarnings() {
128 const { getSelectedSource
, isMapScopesEnabled
} = this._selectors
;
129 if (!this.isPaused() || isMapScopesEnabled(this._getState())) {
132 const selectedSource
= getSelectedSource(this._getState());
133 return selectedSource
?.isOriginal
&& !selectedSource
?.isPrettyPrinted
;
139 selectors
: this._selectors
,
140 actions
: this._actions
,
141 client
: this._client
,
146 return this._store
.getState();
150 return this.toolbox
.store
;
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([
175 const onInspectorUpdated
= inspector
.once("inspector-updated");
176 const onNodeFrontSet
= this.toolbox
.selection
.setNodeFront(front
, {
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());
208 return selectedFrame
.id
;
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())) {
230 const thread
= this._selectors
.getCurrentThread(this._getState());
231 return this._selectors
.getSelectedScopeMappings(this._getState(), thread
);
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.
258 * Returns true if the location is known by the debugger
259 * and the debugger opens it.
261 async
openSourceInDebugger({
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.
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(
285 const generatedLocation
= createLocation({
286 source
: generatedSource
,
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);
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
) {
337 "Selecting a worker needs the pref debugger.features.windowless-service-workers set to true"
342 if (!isThreadAvailable
) {
343 console
.error(`Worker ${threadActorID} is not available for debugging`);
347 // select worker's thread
348 this.selectThread(threadActorID
);
350 // select worker's source
351 const source
= this._selectors
.getSourceByURL(
353 workerDescriptorFront
._url
355 const sourceActor
= this._selectors
.getFirstSourceActorForGeneratedSource(
360 await
this._actions
.selectSource(source
, sourceActor
);
363 selectThread(threadActorID
) {
364 this._actions
.selectThread(threadActorID
);
367 showTracerSidebar() {
368 this._actions
.setPrimaryPaneTab("tracer");
372 this.panelWin
.Debugger
.destroy();
373 this.emit("destroyed");
377 exports
.DebuggerPanel
= DebuggerPanel
;