From e82c4f789ef920499b1353171c7c18a828de9192 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Mon, 17 Dec 2018 06:09:34 -1000 Subject: [PATCH 1/3] Add windowless workers UI, gated on the devtools.debugger.features.windowless-workers pref. --- src/actions/expressions.js | 14 +- src/actions/navigation.js | 4 +- src/actions/pause/breakOnNext.js | 8 +- src/actions/pause/commands.js | 22 +- src/actions/pause/extra.js | 18 +- src/actions/pause/fetchScopes.js | 7 +- src/actions/pause/index.js | 1 + src/actions/pause/mapFrames.js | 3 + src/actions/pause/mapScopes.js | 3 +- src/actions/pause/pauseOnExceptions.js | 6 +- src/actions/pause/paused.js | 3 +- src/actions/pause/resumed.js | 5 +- src/actions/pause/selectFrame.js | 2 + src/actions/pause/setPopupObjectProperties.js | 4 +- src/actions/pause/skipPausing.js | 7 +- src/actions/pause/tests/pause.spec.js | 1 + src/actions/preview.js | 8 +- src/actions/sources/newSources.js | 1 + src/actions/sources/prettyPrint.js | 1 + src/actions/sources/select.js | 1 + .../__snapshots__/prettyPrint.spec.js.snap | 1 + src/actions/sources/tests/select.spec.js | 1 + src/actions/tests/ast.spec.js | 1 + src/actions/tests/expressions.spec.js | 5 +- src/actions/tests/preview.spec.js | 1 + src/actions/types/PauseAction.js | 15 ++ src/actions/types/index.js | 19 +- src/client/firefox.js | 3 +- src/client/firefox/commands.js | 218 +++++++++++------ src/client/firefox/create.js | 19 +- src/client/firefox/events.js | 37 ++- src/client/firefox/types.js | 5 +- src/client/firefox/workers.js | 101 ++++++++ src/components/SecondaryPanes/Workers.css | 4 + src/components/SecondaryPanes/Workers.js | 53 +++- src/reducers/pause.js | 229 ++++++++++++------ src/types.js | 2 + src/utils/prefs.js | 1 + src/utils/source-queue.js | 1 - src/utils/sources-tree/addToTree.js | 3 +- 40 files changed, 618 insertions(+), 220 deletions(-) create mode 100644 src/client/firefox/workers.js diff --git a/src/actions/expressions.js b/src/actions/expressions.js index 3dc03e544b..197cb0018f 100644 --- a/src/actions/expressions.js +++ b/src/actions/expressions.js @@ -5,6 +5,7 @@ // @flow import { + getCurrentThread, getExpression, getExpressions, getSelectedFrame, @@ -117,7 +118,11 @@ export function evaluateExpressions() { const expressions = getExpressions(getState()).toJS(); const inputs = expressions.map(({ input }) => input); const frameId = getSelectedFrameId(getState()); - const results = await client.evaluateExpressions(inputs, frameId); + const thread = getCurrentThread(getState()); + const results = await client.evaluateExpressions(inputs, { + frameId, + thread + }); dispatch({ type: "EVALUATE_EXPRESSIONS", inputs, results }); }; } @@ -147,11 +152,16 @@ function evaluateExpression(expression: Expression) { } const frameId = getSelectedFrameId(getState()); + const thread = getCurrentThread(getState()); return dispatch({ type: "EVALUATE_EXPRESSION", + thread, input: expression.input, - [PROMISE]: client.evaluateInFrame(wrapExpression(input), frameId) + [PROMISE]: client.evaluateInFrame(wrapExpression(input), { + frameId, + thread + }) }); }; } diff --git a/src/actions/navigation.js b/src/actions/navigation.js index 49412307a2..4eda9dfdc7 100644 --- a/src/actions/navigation.js +++ b/src/actions/navigation.js @@ -54,10 +54,10 @@ export function navigate(url: string): Action { }; } -export function connect(url: string, canRewind: boolean) { +export function connect(url: string, thread: string, canRewind: boolean) { return async function({ dispatch }: ThunkArgs) { await dispatch(updateWorkers()); - dispatch(({ type: "CONNECT", url, canRewind }: Action)); + dispatch(({ type: "CONNECT", url, thread, canRewind }: Action)); }; } diff --git a/src/actions/pause/breakOnNext.js b/src/actions/pause/breakOnNext.js index fca0dee214..70b4a7c328 100644 --- a/src/actions/pause/breakOnNext.js +++ b/src/actions/pause/breakOnNext.js @@ -5,6 +5,7 @@ // @flow import type { ThunkArgs } from "../types"; +import { getCurrentThread } from "../../selectors"; /** * Debugger breakOnNext command. @@ -15,8 +16,9 @@ import type { ThunkArgs } from "../types"; * @static */ export function breakOnNext() { - return async ({ dispatch, client }: ThunkArgs) => { - await client.breakOnNext(); - return dispatch({ type: "BREAK_ON_NEXT" }); + return async ({ dispatch, getState, client }: ThunkArgs) => { + const thread = getCurrentThread(getState()); + await client.breakOnNext(thread); + return dispatch({ type: "BREAK_ON_NEXT", thread }); }; } diff --git a/src/actions/pause/commands.js b/src/actions/pause/commands.js index 7f2ca93e94..f77102f89e 100644 --- a/src/actions/pause/commands.js +++ b/src/actions/pause/commands.js @@ -5,7 +5,12 @@ // @flow -import { isPaused, getSource, getTopFrame } from "../../selectors"; +import { + isPaused, + getCurrentThread, + getSource, + getTopFrame +} from "../../selectors"; import { PROMISE } from "../utils/middleware/promise"; import { getNextStep } from "../../workers/parser"; import { addHiddenBreakpoint } from "../breakpoints"; @@ -16,6 +21,15 @@ import type { Source } from "../../types"; import type { ThunkArgs } from "../types"; import type { Command } from "../../reducers/types"; +export function selectThread(thread: string) { + return async ({ dispatch, client }: ThunkArgs) => { + return dispatch({ + type: "SELECT_THREAD", + thread + }); + }; +} + /** * Debugger commands like stepOver, stepIn, stepUp * @@ -24,11 +38,13 @@ import type { Command } from "../../reducers/types"; * @static */ export function command(type: Command) { - return async ({ dispatch, client }: ThunkArgs) => { + return async ({ dispatch, getState, client }: ThunkArgs) => { + const thread = getCurrentThread(getState()); return dispatch({ type: "COMMAND", command: type, - [PROMISE]: client[type]() + thread, + [PROMISE]: client[type](thread) }); }; } diff --git a/src/actions/pause/extra.js b/src/actions/pause/extra.js index fbd1421312..5bc5c43333 100644 --- a/src/actions/pause/extra.js +++ b/src/actions/pause/extra.js @@ -4,7 +4,12 @@ // @flow -import { inComponent, getSelectedFrame } from "../../selectors"; +import { + getCurrentThread, + getSource, + inComponent, + getSelectedFrame +} from "../../selectors"; import { isImmutablePreview } from "../../utils/preview"; import type { ThunkArgs } from "../types"; @@ -77,6 +82,7 @@ export function fetchExtra() { const extra = await dispatch(getExtra("this;", frame.this)); dispatch({ type: "ADD_EXTRA", + thread: getCurrentThread(getState()), extra: extra }); }; @@ -89,8 +95,16 @@ export function getExtra(expression: string, result: Object) { return {}; } + const source = getSource(getState(), selectedFrame.location.sourceId); + if (!source) { + return {}; + } + const extra = await getExtraProps(getState, expression, result, expr => - client.evaluateInFrame(expr, selectedFrame.id) + client.evaluateInFrame(expr, { + frameId: selectedFrame.id, + thread: source.thread + }) ); return extra; diff --git a/src/actions/pause/fetchScopes.js b/src/actions/pause/fetchScopes.js index 8ce61ce331..bb9d6850a5 100644 --- a/src/actions/pause/fetchScopes.js +++ b/src/actions/pause/fetchScopes.js @@ -4,7 +4,11 @@ // @flow -import { getSelectedFrame, getGeneratedFrameScope } from "../../selectors"; +import { + getCurrentThread, + getSelectedFrame, + getGeneratedFrameScope +} from "../../selectors"; import { mapScopes } from "./mapScopes"; import { PROMISE } from "../utils/middleware/promise"; import { fetchExtra } from "./extra"; @@ -19,6 +23,7 @@ export function fetchScopes() { const scopes = dispatch({ type: "ADD_SCOPES", + thread: getCurrentThread(getState()), frame, [PROMISE]: client.getFrameScopes(frame) }); diff --git a/src/actions/pause/index.js b/src/actions/pause/index.js index 72b99cc188..84a23659db 100644 --- a/src/actions/pause/index.js +++ b/src/actions/pause/index.js @@ -10,6 +10,7 @@ */ export { + selectThread, stepIn, stepOver, stepOut, diff --git a/src/actions/pause/mapFrames.js b/src/actions/pause/mapFrames.js index da0db5ac90..c187d44c03 100644 --- a/src/actions/pause/mapFrames.js +++ b/src/actions/pause/mapFrames.js @@ -5,6 +5,7 @@ // @flow import { + getCurrentThread, getFrames, getSymbols, getSource, @@ -170,9 +171,11 @@ export function mapFrames() { mappedFrames = await expandFrames(mappedFrames, sourceMaps, getState); mappedFrames = mapDisplayNames(mappedFrames, getState); + const thread = getCurrentThread(getState()); const selectedFrameId = getSelectedFrameId(getState(), mappedFrames); dispatch({ type: "MAP_FRAMES", + thread, frames: mappedFrames, selectedFrameId }); diff --git a/src/actions/pause/mapScopes.js b/src/actions/pause/mapScopes.js index 3f62797c09..703979a615 100644 --- a/src/actions/pause/mapScopes.js +++ b/src/actions/pause/mapScopes.js @@ -4,7 +4,7 @@ // @flow -import { getSource } from "../../selectors"; +import { getCurrentThread, getSource } from "../../selectors"; import { loadSourceText } from "../sources/loadSourceText"; import { PROMISE } from "../utils/middleware/promise"; @@ -28,6 +28,7 @@ export function mapScopes(scopes: Promise, frame: Frame) { await dispatch({ type: "MAP_SCOPES", + thread: getCurrentThread(getState()), frame, [PROMISE]: (async function() { if ( diff --git a/src/actions/pause/pauseOnExceptions.js b/src/actions/pause/pauseOnExceptions.js index fec61a4556..63728a13e3 100644 --- a/src/actions/pause/pauseOnExceptions.js +++ b/src/actions/pause/pauseOnExceptions.js @@ -7,6 +7,7 @@ import { PROMISE } from "../utils/middleware/promise"; import { recordEvent } from "../../utils/telemetry"; import type { ThunkArgs } from "../types"; +import { getCurrentThread } from "../../selectors"; /** * @@ -17,7 +18,7 @@ export function pauseOnExceptions( shouldPauseOnExceptions: boolean, shouldPauseOnCaughtExceptions: boolean ) { - return ({ dispatch, client }: ThunkArgs) => { + return ({ dispatch, getState, client }: ThunkArgs) => { /* eslint-disable camelcase */ recordEvent("pause_on_exceptions", { exceptions: shouldPauseOnExceptions, @@ -26,11 +27,14 @@ export function pauseOnExceptions( }); /* eslint-enable camelcase */ + const thread = getCurrentThread(getState()); return dispatch({ type: "PAUSE_ON_EXCEPTIONS", + thread, shouldPauseOnExceptions, shouldPauseOnCaughtExceptions, [PROMISE]: client.pauseOnExceptions( + thread, shouldPauseOnExceptions, shouldPauseOnCaughtExceptions ) diff --git a/src/actions/pause/paused.js b/src/actions/pause/paused.js index 5f91f68c13..436d01b33d 100644 --- a/src/actions/pause/paused.js +++ b/src/actions/pause/paused.js @@ -38,7 +38,7 @@ async function getOriginalSourceForFrame(state, frame: Frame) { */ export function paused(pauseInfo: Pause) { return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) { - const { frames, why, loadedObjects } = pauseInfo; + const { thread, frames, why, loadedObjects } = pauseInfo; const topFrame = frames.length > 0 ? frames[0] : null; // NOTE: do not step when leaving a frame or paused at a debugger statement @@ -57,6 +57,7 @@ export function paused(pauseInfo: Pause) { dispatch({ type: "PAUSED", + thread, why, frames, selectedFrameId: topFrame ? topFrame.id : undefined, diff --git a/src/actions/pause/resumed.js b/src/actions/pause/resumed.js index fa53acce42..1125420aa8 100644 --- a/src/actions/pause/resumed.js +++ b/src/actions/pause/resumed.js @@ -4,7 +4,7 @@ // @flow -import { isStepping, getPauseReason } from "../../selectors"; +import { getCurrentThread, isStepping, getPauseReason } from "../../selectors"; import { evaluateExpressions } from "../expressions"; import { inDebuggerEval } from "../../utils/pause"; @@ -21,8 +21,9 @@ export function resumed() { const why = getPauseReason(getState()); const wasPausedInEval = inDebuggerEval(why); const wasStepping = isStepping(getState()); + const thread = getCurrentThread(getState()); - dispatch({ type: "RESUME" }); + dispatch({ type: "RESUME", thread }); if (!wasStepping && !wasPausedInEval) { await dispatch(evaluateExpressions()); diff --git a/src/actions/pause/selectFrame.js b/src/actions/pause/selectFrame.js index b58d053018..0483c6cdb6 100644 --- a/src/actions/pause/selectFrame.js +++ b/src/actions/pause/selectFrame.js @@ -7,6 +7,7 @@ import { selectLocation } from "../sources"; import { evaluateExpressions } from "../expressions"; import { fetchScopes } from "./fetchScopes"; +import { getCurrentThread } from "../../selectors"; import type { Frame } from "../../types"; import type { ThunkArgs } from "../types"; @@ -19,6 +20,7 @@ export function selectFrame(frame: Frame) { return async ({ dispatch, client, getState, sourceMaps }: ThunkArgs) => { dispatch({ type: "SELECT_FRAME", + thread: getCurrentThread(getState()), frame }); diff --git a/src/actions/pause/setPopupObjectProperties.js b/src/actions/pause/setPopupObjectProperties.js index d4357c14e5..9cffee228b 100644 --- a/src/actions/pause/setPopupObjectProperties.js +++ b/src/actions/pause/setPopupObjectProperties.js @@ -4,7 +4,7 @@ // @flow -import { getPopupObjectProperties } from "../../selectors"; +import { getCurrentThread, getPopupObjectProperties } from "../../selectors"; import type { ThunkArgs } from "../types"; /** @@ -19,8 +19,10 @@ export function setPopupObjectProperties(object: any, properties: Object) { return; } + const thread = getCurrentThread(getState()); dispatch({ type: "SET_POPUP_OBJECT_PROPERTIES", + thread, objectId, properties }); diff --git a/src/actions/pause/skipPausing.js b/src/actions/pause/skipPausing.js index bccedd573c..b66989920b 100644 --- a/src/actions/pause/skipPausing.js +++ b/src/actions/pause/skipPausing.js @@ -5,7 +5,7 @@ // @flow import type { ThunkArgs } from "../types"; -import { getSkipPausing } from "../../selectors"; +import { getSkipPausing, getCurrentThread } from "../../selectors"; /** * @memberof actions/pause @@ -13,8 +13,9 @@ import { getSkipPausing } from "../../selectors"; */ export function toggleSkipPausing() { return async ({ dispatch, client, getState, sourceMaps }: ThunkArgs) => { + const thread = getCurrentThread(getState()); const skipPausing = !getSkipPausing(getState()); - await client.setSkipPausing(skipPausing); - dispatch({ type: "TOGGLE_SKIP_PAUSING", skipPausing }); + await client.setSkipPausing(thread, skipPausing); + dispatch({ type: "TOGGLE_SKIP_PAUSING", thread, skipPausing }); }; } diff --git a/src/actions/pause/tests/pause.spec.js b/src/actions/pause/tests/pause.spec.js index df0d345ac9..9fea83cb9a 100644 --- a/src/actions/pause/tests/pause.spec.js +++ b/src/actions/pause/tests/pause.spec.js @@ -76,6 +76,7 @@ function createPauseInfo( frameOpts = {} ) { return { + thread: "FakeThread", frames: [ makeFrame( { id: mockFrameId, sourceId: frameLocation.sourceId }, diff --git a/src/actions/preview.js b/src/actions/preview.js index 5be214b8ca..d4f3af883e 100644 --- a/src/actions/preview.js +++ b/src/actions/preview.js @@ -100,10 +100,10 @@ export function setPreview( return; } - const { result } = await client.evaluateInFrame( - expression, - selectedFrame.id - ); + const { result } = await client.evaluateInFrame(expression, { + frameId: selectedFrame.id, + thread: source.thread + }); if (result === undefined) { return; diff --git a/src/actions/sources/newSources.js b/src/actions/sources/newSources.js index 8bb2d2996b..7b038bb9e8 100644 --- a/src/actions/sources/newSources.js +++ b/src/actions/sources/newSources.js @@ -40,6 +40,7 @@ function createOriginalSource( url: originalUrl, relativeUrl: originalUrl, id: generatedToOriginalId(generatedSource.id, originalUrl), + thread: "", isPrettyPrinted: false, isWasm: false, isBlackBoxed: false, diff --git a/src/actions/sources/prettyPrint.js b/src/actions/sources/prettyPrint.js index e0bfbb5c0b..229fcbdf0d 100644 --- a/src/actions/sources/prettyPrint.js +++ b/src/actions/sources/prettyPrint.js @@ -37,6 +37,7 @@ export function createPrettySource(sourceId: string) { url, relativeUrl: url, id, + thread: "", isBlackBoxed: false, isPrettyPrinted: true, isWasm: false, diff --git a/src/actions/sources/select.js b/src/actions/sources/select.js index 7b909ac993..1e867ca967 100644 --- a/src/actions/sources/select.js +++ b/src/actions/sources/select.js @@ -42,6 +42,7 @@ export const setSelectedLocation = ( location: SourceLocation ) => ({ type: "SET_SELECTED_LOCATION", + thread: source.thread, source, location }); diff --git a/src/actions/sources/tests/__snapshots__/prettyPrint.spec.js.snap b/src/actions/sources/tests/__snapshots__/prettyPrint.spec.js.snap index c19ed24d38..13a95571d9 100644 --- a/src/actions/sources/tests/__snapshots__/prettyPrint.spec.js.snap +++ b/src/actions/sources/tests/__snapshots__/prettyPrint.spec.js.snap @@ -13,6 +13,7 @@ Object { "sourceMapURL": undefined, "text": "undefined ", + "thread": "", "url": "http://localhost:8000/examples/base.js:formatted", } `; diff --git a/src/actions/sources/tests/select.spec.js b/src/actions/sources/tests/select.spec.js index a1c393a3fc..f066b5c24d 100644 --- a/src/actions/sources/tests/select.spec.js +++ b/src/actions/sources/tests/select.spec.js @@ -36,6 +36,7 @@ describe("sources", () => { await dispatch(actions.newSource(makeSource("foo1"))); await dispatch( actions.paused({ + thread: "FakeThread", why: { type: "debuggerStatement" }, frames: [makeFrame({ id: "1", sourceId: "foo1" })] }) diff --git a/src/actions/tests/ast.spec.js b/src/actions/tests/ast.spec.js index d5dbfd9e3f..0a88ee0949 100644 --- a/src/actions/tests/ast.spec.js +++ b/src/actions/tests/ast.spec.js @@ -166,6 +166,7 @@ describe("ast", () => { await dispatch( actions.paused({ + thread: "FakeThread", why: { type: "debuggerStatement" }, frames: [makeFrame({ id: "1", sourceId: "scopes.js" })] }) diff --git a/src/actions/tests/expressions.spec.js b/src/actions/tests/expressions.spec.js index 8498d66e5c..7ce03ff570 100644 --- a/src/actions/tests/expressions.spec.js +++ b/src/actions/tests/expressions.spec.js @@ -5,7 +5,7 @@ import { actions, selectors, createStore } from "../../utils/test-head"; const mockThreadClient = { - evaluateInFrame: (script, frameId) => + evaluateInFrame: (script, { frameId }) => new Promise((resolve, reject) => { if (!frameId) { resolve("bla"); @@ -13,7 +13,7 @@ const mockThreadClient = { resolve("boo"); } }), - evaluateExpressions: (inputs, frameId) => + evaluateExpressions: (inputs, { frameId }) => Promise.all( inputs.map( input => @@ -151,6 +151,7 @@ async function createFrames(dispatch) { await dispatch( actions.paused({ + thread: "UnknownThread", frames: [frame], why: { type: "just because" } }) diff --git a/src/actions/tests/preview.spec.js b/src/actions/tests/preview.spec.js index 1004102d6e..06541b1932 100644 --- a/src/actions/tests/preview.spec.js +++ b/src/actions/tests/preview.spec.js @@ -59,6 +59,7 @@ xdescribe("setPreview", () => { await dispatch(actions.setSymbols(fileName)); await dispatch( actions.paused({ + thread: "FakeThread", why: { type: "resumeLimit" }, frames: [ { id: "frame1", location: { sourceId: fileName, line: 5, column: 1 } } diff --git a/src/actions/types/PauseAction.js b/src/actions/types/PauseAction.js index c469d75787..a69788232f 100644 --- a/src/actions/types/PauseAction.js +++ b/src/actions/types/PauseAction.js @@ -12,14 +12,17 @@ import type { PromiseAction } from "../utils/middleware/promise"; export type PauseAction = | {| +type: "BREAK_ON_NEXT", + +thread: string, +value: boolean |} | {| +type: "RESUME", + +thread: string, +value: void |} | {| +type: "PAUSED", + +thread: string, +why: Why, +scopes: Scope, +frames: Frame[], @@ -28,28 +31,34 @@ export type PauseAction = |} | {| +type: "PAUSE_ON_EXCEPTIONS", + +thread: string, +shouldPauseOnExceptions: boolean, +shouldPauseOnCaughtExceptions: boolean |} | PromiseAction<{| +type: "COMMAND", + +thread: string, +command: Command |}> | {| +type: "SELECT_FRAME", + +thread: string, +frame: Frame |} | {| +type: "SELECT_COMPONENT", + +thread: string, +componentIndex: number |} | {| +type: "SET_POPUP_OBJECT_PROPERTIES", + +thread: string, +objectId: string, +properties: Object |} | {| +type: "ADD_EXPRESSION", + +thread: string, +id: number, +input: string, +value: string, @@ -58,6 +67,7 @@ export type PauseAction = | PromiseAction< {| +type: "EVALUATE_EXPRESSION", + +thread: string, +input: string |}, Object @@ -91,6 +101,7 @@ export type PauseAction = | PromiseAction< {| +type: "MAP_SCOPES", + +thread: string, +frame: Frame |}, { @@ -102,21 +113,25 @@ export type PauseAction = > | {| +type: "MAP_FRAMES", + +thread: string, +frames: Frame[], +selectedFrameId: string |} | {| +type: "ADD_EXTRA", + +thread: string, +extra: any |} | PromiseAction< {| +type: "ADD_SCOPES", + +thread: string, +frame: Frame |}, Scope > | {| +type: "TOGGLE_SKIP_PAUSING", + +thread: string, skipPausing: boolean |}; diff --git a/src/actions/types/index.js b/src/actions/types/index.js index 61dbc75fb1..ecc5440299 100644 --- a/src/actions/types/index.js +++ b/src/actions/types/index.js @@ -82,7 +82,7 @@ type ReplayAction = |}; type NavigateAction = - | {| +type: "CONNECT", +url: string, +canRewind: boolean |} + | {| +type: "CONNECT", +thread: string, +url: string, +canRewind: boolean |} | {| +type: "NAVIGATE", +url: string |}; export type SourceTreeAction = {| @@ -130,12 +130,17 @@ export type QuickOpenAction = | {| +type: "OPEN_QUICK_OPEN", +query?: string |} | {| +type: "CLOSE_QUICK_OPEN" |}; -export type DebugeeAction = {| - +type: "SET_WORKERS", - +workers: { - workers: Object[] - } -|}; +export type DebugeeAction = + | {| + +type: "SET_WORKERS", + +workers: { + workers: Object[] + } + |} + | {| + +type: "SELECT_THREAD", + +thread: string + |}; export type { StartPromiseAction, diff --git a/src/client/firefox.js b/src/client/firefox.js index 9f88c63b2a..d496b359c1 100644 --- a/src/client/firefox.js +++ b/src/client/firefox.js @@ -58,6 +58,7 @@ export async function onConnect(connection: any, actions: Object): Object { const traits = tabTarget.activeTab ? tabTarget.activeTab.traits : null; await actions.connect( tabTarget.url, + threadClient.actor, traits && traits.canRewind ); await actions.newSources(sources); @@ -66,7 +67,7 @@ export async function onConnect(connection: any, actions: Object): Object { // paused state. const pausedPacket = threadClient.getLastPausePacket(); if (pausedPacket) { - clientEvents.paused("paused", pausedPacket); + clientEvents.paused(threadClient, "paused", pausedPacket); } return { bpClients }; diff --git a/src/client/firefox/commands.js b/src/client/firefox/commands.js index 81ef32ca9f..e577ccdf17 100644 --- a/src/client/firefox/commands.js +++ b/src/client/firefox/commands.js @@ -29,11 +29,15 @@ import type { PausePoints } from "../../workers/parser"; import { makePendingLocationId } from "../../utils/breakpoint"; -import { createSource, createBreakpointLocation } from "./create"; +import { createSource, createBreakpointLocation, createWorker } from "./create"; +import { originalToGeneratedId, isOriginalId } from "devtools-source-map"; +import { updateWorkerClients, checkServerSupportsListWorkers } from "./workers"; -import Services from "devtools-services"; +import { features } from "../../utils/prefs"; let bpClients: BPClients; +let workerClients: Object; +let sourceThreads: Object; let threadClient: ThreadClient; let tabTarget: TabTarget; let debuggerClient: DebuggerClient; @@ -52,6 +56,8 @@ function setupCommands(dependencies: Dependencies): { bpClients: BPClients } { debuggerClient = dependencies.debuggerClient; supportsWasm = dependencies.supportsWasm; bpClients = {}; + workerClients = {}; + sourceThreads = {}; return { bpClients }; } @@ -72,60 +78,78 @@ function sendPacket(packet: Object, callback?: Function = r => r) { return debuggerClient.request(packet).then(callback); } -function resume(): Promise<*> { +function lookupThreadClient(thread: string) { + if (thread == threadClient.actor) { + return threadClient; + } + if (!workerClients[thread]) { + throw new Error(`Unknown thread client: ${thread}`); + } + return workerClients[thread].thread; +} + +function lookupConsoleClient(thread: string) { + if (thread == threadClient.actor) { + return tabTarget.activeConsole; + } + return workerClients[thread].console; +} + +function resume(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.resume(resolve); + lookupThreadClient(thread).resume(resolve); }); } -function stepIn(): Promise<*> { +function stepIn(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.stepIn(resolve); + lookupThreadClient(thread).stepIn(resolve); }); } -function stepOver(): Promise<*> { +function stepOver(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.stepOver(resolve); + lookupThreadClient(thread).stepOver(resolve); }); } -function stepOut(): Promise<*> { +function stepOut(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.stepOut(resolve); + lookupThreadClient(thread).stepOut(resolve); }); } -function rewind(): Promise<*> { +function rewind(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.rewind(resolve); + lookupThreadClient(thread).rewind(resolve); }); } -function reverseStepIn(): Promise<*> { +function reverseStepIn(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.reverseStepIn(resolve); + lookupThreadClient(thread).reverseStepIn(resolve); }); } -function reverseStepOver(): Promise<*> { +function reverseStepOver(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.reverseStepOver(resolve); + lookupThreadClient(thread).reverseStepOver(resolve); }); } -function reverseStepOut(): Promise<*> { +function reverseStepOut(thread: string): Promise<*> { return new Promise(resolve => { - threadClient.reverseStepOut(resolve); + lookupThreadClient(thread).reverseStepOut(resolve); }); } -function breakOnNext(): Promise<*> { - return threadClient.breakOnNext(); +function breakOnNext(thread: string): Promise<*> { + return lookupThreadClient(thread).breakOnNext(); } function sourceContents(sourceId: SourceId): Source { - const sourceClient = threadClient.source({ actor: sourceId }); + const sourceThreadClient = sourceThreads[sourceId]; + const sourceClient = sourceThreadClient.source({ actor: sourceId }); return sourceClient.source(); } @@ -162,7 +186,8 @@ function setBreakpoint( condition: boolean, noSliding: boolean ): Promise { - const sourceClient = threadClient.source({ actor: location.sourceId }); + const sourceThreadClient = sourceThreads[location.sourceId]; + const sourceClient = sourceThreadClient.source({ actor: location.sourceId }); return sourceClient .setBreakpoint({ @@ -209,39 +234,43 @@ function setBreakpointCondition( const bpClient = bpClients[breakpointId]; delete bpClients[breakpointId]; + const sourceThreadClient = sourceThreads[bpClient.source.actor]; return bpClient - .setCondition(threadClient, condition, noSliding) + .setCondition(sourceThreadClient, condition, noSliding) .then(_bpClient => { bpClients[breakpointId] = _bpClient; return { id: breakpointId }; }); } -async function evaluateInFrame(script: Script, frameId: string) { - return evaluate(script, { frameId }); +async function evaluateInFrame(script: Script, options: EvaluateParam) { + return evaluate(script, options); } -async function evaluateExpressions(scripts: Script[], frameId?: string) { - return Promise.all(scripts.map(script => evaluate(script, { frameId }))); +async function evaluateExpressions(scripts: Script[], options: EvaluateParam) { + return Promise.all(scripts.map(script => evaluate(script, options))); } -type EvaluateParam = { frameId?: FrameId }; +type EvaluateParam = { thread?: string, frameId?: FrameId }; function evaluate( script: ?Script, - { frameId }: EvaluateParam = {} + { thread, frameId }: EvaluateParam = {} ): Promise { - const params = frameId ? { frameActor: frameId } : {}; - if (!tabTarget || !tabTarget.activeConsole || !script) { + const params = { thread, frameActor: frameId }; + if (!tabTarget || !script) { + return Promise.resolve({}); + } + + const console = thread + ? lookupConsoleClient(thread) + : tabTarget.activeConsole; + if (!console) { return Promise.resolve({}); } return new Promise(resolve => { - tabTarget.activeConsole.evaluateJSAsync( - script, - result => resolve(result), - params - ); + console.evaluateJSAsync(script, result => resolve(result), params); }); } @@ -271,8 +300,8 @@ function reload(): Promise<*> { return tabTarget.activeTab.reload(); } -function getProperties(grip: Grip): Promise<*> { - const objClient = threadClient.pauseGrip(grip); +function getProperties(thread: string, grip: Grip): Promise<*> { + const objClient = lookupThreadClient(thread).pauseGrip(grip); return objClient.getPrototypeAndProperties().then(resp => { const { ownProperties, safeGetterValues } = resp; @@ -289,14 +318,21 @@ async function getFrameScopes(frame: Frame): Promise<*> { return frame.scope; } - return threadClient.getEnvironment(frame.id); + let sourceId = frame.location.sourceId; + if (isOriginalId(sourceId)) { + sourceId = originalToGeneratedId(sourceId); + } + + const sourceThreadClient = sourceThreads[sourceId]; + return sourceThreadClient.getEnvironment(frame.id); } function pauseOnExceptions( + thread: string, shouldPauseOnExceptions: boolean, shouldPauseOnCaughtExceptions: boolean ): Promise<*> { - return threadClient.pauseOnExceptions( + return lookupThreadClient(thread).pauseOnExceptions( shouldPauseOnExceptions, // Providing opposite value because server // uses "shouldIgnoreCaughtExceptions" @@ -329,73 +365,94 @@ async function setPausePoints(sourceId: SourceId, pausePoints: PausePoints) { return sendPacket({ to: sourceId, type: "setPausePoints", pausePoints }); } -async function setSkipPausing(shouldSkip: boolean) { - return threadClient.request({ +async function setSkipPausing(thread: string, shouldSkip: boolean) { + const client = lookupThreadClient(thread); + return client.request({ skip: shouldSkip, - to: threadClient.actor, + to: client.actor, type: "skipBreakpoints" }); } -function interrupt(): Promise<*> { - return threadClient.interrupt(); +function interrupt(thread: string): Promise<*> { + return lookupThreadClient(thread).interrupt(); } function eventListeners(): Promise<*> { return threadClient.eventListeners(); } -function pauseGrip(func: Function): ObjectClient { - return threadClient.pauseGrip(func); +function pauseGrip(thread: string, func: Function): ObjectClient { + return lookupThreadClient(thread).pauseGrip(func); +} + +function registerSource(source: Source) { + if (isOriginalId(source.id)) { + throw new Error("registerSource called with original ID"); + } + sourceThreads[source.id] = lookupThreadClient(source.thread); +} + +async function createSources(client: ThreadClient) { + const { sources } = await client.getSources(); + return ( + sources && + sources.map(packet => createSource(client.actor, packet, { supportsWasm })) + ); } async function fetchSources() { - const { sources } = await threadClient.getSources(); + let sources = await createSources(threadClient); // NOTE: this happens when we fetch sources and then immediately navigate if (!sources) { return; } - return sources.map(source => createSource(source, { supportsWasm })); -} - -/** - * Temporary helper to check if the current server will support a call to - * listWorkers. On Fennec 60 or older, the call will silently crash and prevent - * the client from resuming. - * XXX: Remove when FF60 for Android is no longer used or available. - * - * See https://bugzilla.mozilla.org/show_bug.cgi?id=1443550 for more details. - */ -async function checkServerSupportsListWorkers() { - const root = await tabTarget.root; - // root is not available on all debug targets. - if (!root) { - return false; - } - - const deviceFront = await debuggerClient.mainRoot.getFront("device"); - const description = await deviceFront.getDescription(); + if (features.windowlessWorkers) { + // Also fetch sources from any workers. + workerClients = await updateWorkerClients({ + threadClient, + debuggerClient, + tabTarget, + workerClients + }); - const isFennec = description.apptype === "mobile/android"; - if (!isFennec) { - // Explicitly return true early to avoid calling Services.vs.compare. - // This would force us to extent the Services shim provided by - // devtools-modules, used when this code runs in a tab. - return true; + const workerNames = Object.getOwnPropertyNames(workerClients); + workerNames.forEach(actor => { + const workerSources = createSources(workerClients[actor].thread); + if (workerSources) { + sources = sources.concat(workerSources); + } + }); } - // We are only interested in Fennec release versions here. - // We assume that the server fix for Bug 1443550 will land in FF61. - const version = description.platformversion; - return Services.vc.compare(version, "61.0") >= 0; + return sources; } async function fetchWorkers(): Promise<{ workers: Worker[] }> { + if (features.windowlessWorkers) { + workerClients = await updateWorkerClients({ + tabTarget, + debuggerClient, + threadClient, + workerClients + }); + + const workerNames = Object.getOwnPropertyNames(workerClients); + return { + workers: workerNames.map(actor => + createWorker(actor, workerClients[actor]) + ) + }; + } + // Temporary workaround for Bug 1443550 // XXX: Remove when FF60 for Android is no longer used or available. - const supportsListWorkers = await checkServerSupportsListWorkers(); + const supportsListWorkers = await checkServerSupportsListWorkers({ + tabTarget, + debuggerClient + }); // NOTE: The Worker and Browser Content toolboxes do not have a parent // with a listWorkers function @@ -450,7 +507,8 @@ const clientCommands = { fetchWorkers, sendPacket, setPausePoints, - setSkipPausing + setSkipPausing, + registerSource }; export { setupCommands, clientCommands }; diff --git a/src/client/firefox/create.js b/src/client/firefox/create.js index 18a94ca8c4..10cead887c 100644 --- a/src/client/firefox/create.js +++ b/src/client/firefox/create.js @@ -10,9 +10,12 @@ import type { PausedPacket, FramesResponse, FramePacket, - SourcePayload + SourcePayload, + ThreadClient } from "./types"; +import { clientCommands } from "./commands"; + export function createFrame(frame: FramePacket): ?Frame { if (!frame) { return null; @@ -41,11 +44,13 @@ export function createFrame(frame: FramePacket): ?Frame { } export function createSource( + thread: string, source: SourcePayload, { supportsWasm }: { supportsWasm: boolean } ): Source { const createdSource = { id: source.actor, + thread, url: source.url, relativeUrl: source.url, isPrettyPrinted: false, @@ -54,12 +59,14 @@ export function createSource( isBlackBoxed: false, loadedState: "unloaded" }; + clientCommands.registerSource(createdSource); return Object.assign(createdSource, { isWasm: supportsWasm && source.introductionType === "wasm" }); } export function createPause( + thread: string, packet: PausedPacket, response: FramesResponse ): any { @@ -68,6 +75,7 @@ export function createPause( return { ...packet, + thread, frame: createFrame(frame), frames: response.frames.map(createFrame) }; @@ -92,3 +100,12 @@ export function createBreakpointLocation( column: actualLocation.column }; } + +export function createWorker(actor: string, threadClient: ThreadClient) { + return { + actor, + url: threadClient.url, + // Ci.nsIWorkerDebugger.TYPE_DEDICATED + type: 0 + }; +} diff --git a/src/client/firefox/events.js b/src/client/firefox/events.js index 3532a95539..f3cf2521d3 100644 --- a/src/client/firefox/events.js +++ b/src/client/firefox/events.js @@ -23,21 +23,24 @@ type Dependencies = { supportsWasm: boolean }; -let threadClient: ThreadClient; let actions: Actions; let supportsWasm: boolean; let isInterrupted: boolean; +function addThreadEventListeners(client: ThreadClient) { + Object.keys(clientEvents).forEach(eventName => { + client.addListener(eventName, clientEvents[eventName].bind(null, client)); + }); +} + function setupEvents(dependencies: Dependencies) { - threadClient = dependencies.threadClient; + const threadClient = dependencies.threadClient; actions = dependencies.actions; supportsWasm = dependencies.supportsWasm; sourceQueue.initialize(actions); if (threadClient) { - Object.keys(clientEvents).forEach(eventName => { - threadClient.addListener(eventName, clientEvents[eventName]); - }); + addThreadEventListeners(threadClient); if (threadClient._parent) { // Parent may be BrowsingContextTargetFront/WorkerTargetFront and @@ -54,7 +57,11 @@ function setupEvents(dependencies: Dependencies) { } } -async function paused(_: "paused", packet: PausedPacket) { +async function paused( + threadClient: ThreadClient, + _: "paused", + packet: PausedPacket +) { // If paused by an explicit interrupt, which are generated by the // slow script dialog and internal events such as setting // breakpoints, ignore the event. @@ -79,13 +86,17 @@ async function paused(_: "paused", packet: PausedPacket) { } if (why.type != "alreadyPaused") { - const pause = createPause(packet, response); + const pause = createPause(threadClient.actor, packet, response); await sourceQueue.flush(); actions.paused(pause); } } -function resumed(_: "resumed", packet: ResumedPacket) { +function resumed( + threadClient: ThreadClient, + _: "resumed", + packet: ResumedPacket +) { // NOTE: the client suppresses resumed events while interrupted // to prevent unintentional behavior. // see [client docs](../README.md#interrupted) for more information. @@ -97,8 +108,12 @@ function resumed(_: "resumed", packet: ResumedPacket) { actions.resumed(packet); } -function newSource(_: "newSource", { source }: SourcePacket) { - sourceQueue.queue(createSource(source, { supportsWasm })); +function newSource( + threadClient: ThreadClient, + _: "newSource", + { source }: SourcePacket +) { + sourceQueue.queue(createSource(threadClient.actor, source, { supportsWasm })); } function workerListChanged() { @@ -111,4 +126,4 @@ const clientEvents = { newSource }; -export { setupEvents, clientEvents }; +export { setupEvents, clientEvents, addThreadEventListeners }; diff --git a/src/client/firefox/types.js b/src/client/firefox/types.js index 010d2d189a..3b8f925858 100644 --- a/src/client/firefox/types.js +++ b/src/client/firefox/types.js @@ -265,6 +265,7 @@ export type DebuggerClient = { }, connect: () => Promise<*>, request: (packet: Object) => Promise<*>, + attachConsole: (actor: String, listeners: Array<*>) => Promise<*>, createObjectClient: (grip: Grip) => {}, release: (actor: String) => {} }; @@ -313,6 +314,7 @@ export type FunctionGrip = {| */ export type SourceClient = { source: () => Source, + actor: string, setBreakpoint: ({ line: number, column: ?number, @@ -365,7 +367,8 @@ export type ThreadClient = { getLastPausePacket: () => ?PausedPacket, _parent: TabClient, actor: ActorId, - request: (payload: Object) => Promise<*> + request: (payload: Object) => Promise<*>, + url: string }; /** diff --git a/src/client/firefox/workers.js b/src/client/firefox/workers.js new file mode 100644 index 0000000000..5a6a7d7d7c --- /dev/null +++ b/src/client/firefox/workers.js @@ -0,0 +1,101 @@ +/* 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 . */ + +// @flow + +import Services from "devtools-services"; +import { addThreadEventListeners } from "./events"; + +/** + * Temporary helper to check if the current server will support a call to + * listWorkers. On Fennec 60 or older, the call will silently crash and prevent + * the client from resuming. + * XXX: Remove when FF60 for Android is no longer used or available. + * + * See https://bugzilla.mozilla.org/show_bug.cgi?id=1443550 for more details. + */ +export async function checkServerSupportsListWorkers({ + tabTarget, + debuggerClient +}: Object) { + const root = await tabTarget.root; + // root is not available on all debug targets. + if (!root) { + return false; + } + + const deviceFront = await debuggerClient.mainRoot.getFront("device"); + const description = await deviceFront.getDescription(); + + const isFennec = description.apptype === "mobile/android"; + if (!isFennec) { + // Explicitly return true early to avoid calling Services.vs.compare. + // This would force us to extent the Services shim provided by + // devtools-modules, used when this code runs in a tab. + return true; + } + + // We are only interested in Fennec release versions here. + // We assume that the server fix for Bug 1443550 will land in FF61. + const version = description.platformversion; + return Services.vc.compare(version, "61.0") >= 0; +} + +export async function updateWorkerClients({ + tabTarget, + debuggerClient, + threadClient, + workerClients +}: Object) { + const newWorkerClients = {}; + + // Temporary workaround for Bug 1443550 + // XXX: Remove when FF60 for Android is no longer used or available. + const supportsListWorkers = await checkServerSupportsListWorkers({ + tabTarget, + debuggerClient + }); + + // NOTE: The Worker and Browser Content toolboxes do not have a parent + // with a listWorkers function + // TODO: there is a listWorkers property, but it is not a function on the + // parent. Investigate what it is + if ( + !threadClient._parent || + typeof threadClient._parent.listWorkers != "function" || + !supportsListWorkers + ) { + return newWorkerClients; + } + + const { workers } = await threadClient._parent.listWorkers(); + for (const workerTargetFront of workers) { + await workerTargetFront.attach(); + const [, workerThread] = await workerTargetFront.attachThread(); + + if (workerClients[workerThread.actor]) { + if (workerClients[workerThread.actor].thread != workerThread) { + throw new Error(`Multiple clients for actor ID: ${workerThread.actor}`); + } + newWorkerClients[workerThread.actor] = workerClients[workerThread.actor]; + } else { + addThreadEventListeners(workerThread); + workerThread.url = workerTargetFront.url; + workerThread.resume(); + + const [, consoleClient] = await debuggerClient.attachConsole( + workerTargetFront.targetForm.consoleActor, + [] + ); + + newWorkerClients[workerThread.actor] = { + url: workerTargetFront.url, + thread: workerThread, + console: consoleClient + }; + } + } + + return newWorkerClients; +} diff --git a/src/components/SecondaryPanes/Workers.css b/src/components/SecondaryPanes/Workers.css index 45b55f8901..be9e7d6f38 100644 --- a/src/components/SecondaryPanes/Workers.css +++ b/src/components/SecondaryPanes/Workers.css @@ -19,3 +19,7 @@ .worker-list .worker:hover { background-color: var(--search-overlays-semitransparent); } + +.worker-list .worker.selected { + background-color: var(--tab-line-selected-color); +} diff --git a/src/components/SecondaryPanes/Workers.js b/src/components/SecondaryPanes/Workers.js index 70b789393a..a3312a2628 100644 --- a/src/components/SecondaryPanes/Workers.js +++ b/src/components/SecondaryPanes/Workers.js @@ -2,25 +2,54 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ -import React, { PureComponent } from "react"; +import React, { Component } from "react"; import { connect } from "react-redux"; import type { List } from "immutable"; import "./Workers.css"; import actions from "../../actions"; -import { getWorkers } from "../../selectors"; +import { + getMainThread, + getCurrentThread, + threadIsPaused, + getWorkers +} from "../../selectors"; import { basename } from "../../utils/path"; +import { features } from "../../utils/prefs"; import type { Worker } from "../../types"; import AccessibleImage from "../shared/AccessibleImage"; +import classnames from "classnames"; -export class Workers extends PureComponent { +type Props = { + selectThread: string => void +}; + +export class Workers extends Component { props: { workers: List, - openWorkerToolbox: object => void + openWorkerToolbox: object => void, + mainThread: string, + currentThread: string }; - renderWorkers(workers) { + renderWorkers(workers, mainThread, currentThread) { + if (features.windowlessWorkers) { + return [{ actor: mainThread }, ...workers].map(worker => ( +
this.props.selectThread(worker.actor)} + > + + {(worker.url ? basename(worker.url) : "Main Thread") + + (this.props.threadIsPaused(worker.actor) ? " PAUSED" : "")} +
+ )); + } const { openWorkerToolbox } = this.props; return workers.map(worker => (
{workers && workers.size > 0 - ? this.renderWorkers(workers) + ? this.renderWorkers(workers, mainThread, currentThread) : this.renderNoWorkersPlaceholder()}
); @@ -51,10 +80,16 @@ export class Workers extends PureComponent { } const mapStateToProps = state => ({ - workers: getWorkers(state) + workers: getWorkers(state), + mainThread: getMainThread(state), + currentThread: getCurrentThread(state), + threadIsPaused: thread => threadIsPaused(state, thread) }); export default connect( mapStateToProps, - actions + { + openWorkerToolbox: actions.openWorkerToolbox, + selectThread: actions.selectThread + } )(Workers); diff --git a/src/reducers/pause.js b/src/reducers/pause.js index f3468ebf66..c0f772aa0c 100644 --- a/src/reducers/pause.js +++ b/src/reducers/pause.js @@ -32,7 +32,8 @@ export type Command = | "reverseStepOut" | "expression"; -export type PauseState = { +// Pause state associated with an individual thread. +type ThreadPauseState = { extra: ?Object, why: ?Why, isWaitingOnBreak: boolean, @@ -60,27 +61,29 @@ export type PauseState = { loadedObjects: Object, shouldPauseOnExceptions: boolean, shouldPauseOnCaughtExceptions: boolean, - canRewind: boolean, - debuggeeUrl: string, command: Command, previousLocation: ?MappedLocation, skipPausing: boolean }; +// Pause state describing all threads. +export type PauseState = { + mainThread: string, + currentThread: string, + debuggeeUrl: string, + canRewind: boolean, + threads: { [string]: ThreadPauseState } +}; + export const createPauseState = (): PauseState => ({ - ...emptyPauseState, - extra: {}, - isWaitingOnBreak: false, - shouldPauseOnExceptions: prefs.pauseOnExceptions, - shouldPauseOnCaughtExceptions: prefs.pauseOnCaughtExceptions, + mainThread: "UnknownThread", + currentThread: "UnknownThread", + threads: {}, canRewind: false, - debuggeeUrl: "", - command: null, - previousLocation: null, - skipPausing: prefs.skipPausing + debuggeeUrl: "" }); -const emptyPauseState = { +const resumedPauseState = { frames: null, frameScopes: { generated: {}, @@ -92,13 +95,57 @@ const emptyPauseState = { why: null }; +const createInitialPauseState = () => ({ + ...resumedPauseState, + extra: {}, + isWaitingOnBreak: false, + shouldPauseOnExceptions: prefs.pauseOnExceptions, + shouldPauseOnCaughtExceptions: prefs.pauseOnCaughtExceptions, + canRewind: false, + debuggeeUrl: "", + command: null, + previousLocation: null, + skipPausing: prefs.skipPausing +}); + +function getThreadPauseState(state: PauseState, thread: string) { + // Thread state is lazily initialized so that we don't have to keep track of + // the current set of worker threads. + return state.threads[thread] || createInitialPauseState(); +} + function update( state: PauseState = createPauseState(), action: Action ): PauseState { + // Actions need to specify any thread they are operating on. These helpers + // manage updating the pause state for that thread. + const threadState = () => { + if (!action.thread) { + throw new Error(`Missing thread in action ${action.type}`); + } + return getThreadPauseState(state, action.thread); + }; + + const updateThreadState = newThreadState => { + if (!action.thread) { + throw new Error(`Missing thread in action ${action.type}`); + } + return { + ...state, + threads: { + ...state.threads, + [action.thread]: { ...threadState(), ...newThreadState } + } + }; + }; + switch (action.type) { + case "SELECT_THREAD": + return { ...state, currentThread: action.thread }; + case "PAUSED": { - const { selectedFrameId, frames, loadedObjects, why } = action; + const { thread, selectedFrameId, frames, loadedObjects, why } = action; // turn this into an object keyed by object id const objectMap = {}; @@ -106,24 +153,24 @@ function update( objectMap[obj.value.objectId] = obj; }); - return { - ...state, + state = { ...state, currentThread: thread }; + return updateThreadState({ isWaitingOnBreak: false, selectedFrameId, frames, - frameScopes: { ...emptyPauseState.frameScopes }, + frameScopes: { ...resumedPauseState.frameScopes }, loadedObjects: objectMap, why - }; + }); } case "MAP_FRAMES": { const { selectedFrameId, frames } = action; - return { ...state, frames, selectedFrameId }; + return updateThreadState({ frames, selectedFrameId }); } case "ADD_EXTRA": { - return { ...state, extra: action.extra }; + return updateThreadState({ extra: action.extra }); } case "ADD_SCOPES": { @@ -131,33 +178,30 @@ function update( const selectedFrameId = frame.id; const generated = { - ...state.frameScopes.generated, + ...threadState().frameScopes.generated, [selectedFrameId]: { pending: status !== "done", scope: value } }; - return { - ...state, + + return updateThreadState({ frameScopes: { - ...state.frameScopes, + ...threadState().frameScopes, generated } - }; + }); } case "TRAVEL_TO": - return { - ...state, - ...action.data.paused - }; + return updateThreadState({ ...action.data.paused }); case "MAP_SCOPES": { const { frame, status, value } = action; const selectedFrameId = frame.id; const original = { - ...state.frameScopes.original, + ...threadState().frameScopes.original, [selectedFrameId]: { pending: status !== "done", scope: value && value.scope @@ -165,49 +209,48 @@ function update( }; const mappings = { - ...state.frameScopes.mappings, + ...threadState().frameScopes.mappings, [selectedFrameId]: value && value.mappings }; - return { - ...state, + + return updateThreadState({ frameScopes: { - ...state.frameScopes, + ...threadState().frameScopes, original, mappings } - }; + }); } case "BREAK_ON_NEXT": - return { ...state, isWaitingOnBreak: true }; + return updateThreadState({ isWaitingOnBreak: true }); case "SELECT_FRAME": - return { - ...state, - selectedFrameId: action.frame.id - }; + return updateThreadState({ selectedFrameId: action.frame.id }); - case "SET_POPUP_OBJECT_PROPERTIES": + case "SET_POPUP_OBJECT_PROPERTIES": { if (!action.properties) { - return { ...state }; + return state; } - return { - ...state, + return updateThreadState({ loadedObjects: { - ...state.loadedObjects, + ...threadState().loadedObjects, [action.objectId]: action.properties } - }; + }); + } case "CONNECT": return { ...createPauseState(), + mainThread: action.thread, + currentThread: action.thread, debuggeeUrl: action.url, canRewind: action.canRewind }; - case "PAUSE_ON_EXCEPTIONS": + case "PAUSE_ON_EXCEPTIONS": { const { shouldPauseOnExceptions, shouldPauseOnCaughtExceptions } = action; prefs.pauseOnExceptions = shouldPauseOnExceptions; @@ -216,40 +259,52 @@ function update( // Preserving for the old debugger prefs.ignoreCaughtExceptions = !shouldPauseOnCaughtExceptions; - return { - ...state, + return updateThreadState({ shouldPauseOnExceptions, shouldPauseOnCaughtExceptions - }; - - case "COMMAND": { - return action.status === "start" - ? { - ...state, - ...emptyPauseState, - command: action.command, - previousLocation: getPauseLocation(state, action) - } - : { ...state, command: null }; + }); } + case "COMMAND": + if (action.status === "start") { + return updateThreadState({ + ...resumedPauseState, + command: action.command, + previousLocation: getPauseLocation(threadState(), action) + }); + } + return updateThreadState({ command: null }); + case "RESUME": - return { ...state, ...emptyPauseState }; + // Workaround for threads resuming before the initial connection. + if (!action.thread && !state.currentThread) { + return state; + } + return updateThreadState(resumedPauseState); case "EVALUATE_EXPRESSION": - return { - ...state, + return updateThreadState({ command: action.status === "start" ? "expression" : null - }; + }); case "NAVIGATE": - return { ...state, ...emptyPauseState, debuggeeUrl: action.url }; + return { + ...state, + currentThread: state.mainThread, + threads: { + [state.mainThread]: { + ...state.threads[state.mainThread], + ...resumedPauseState + } + }, + debuggeeUrl: action.url + }; case "TOGGLE_SKIP_PAUSING": { const { skipPausing } = action; prefs.skipPausing = skipPausing; - return { ...state, skipPausing }; + return updateThreadState({ skipPausing }); } } @@ -287,25 +342,39 @@ function getPauseLocation(state, action) { // (right now) to type those wrapped functions. type OuterState = State; -const getPauseState = state => state.pause; +function getCurrentPauseState(state: OuterState): ThreadPauseState { + return getThreadPauseState(state.pause, state.pause.currentThread); +} export const getAllPopupObjectProperties = createSelector( - getPauseState, + getCurrentPauseState, pauseWrapper => pauseWrapper.loadedObjects ); export function getPauseReason(state: OuterState): ?Why { - return state.pause.why; + return getCurrentPauseState(state).why; } export function getPauseCommand(state: OuterState): Command { - return state.pause && state.pause.command; + return getCurrentPauseState(state).command; } export function isStepping(state: OuterState) { return ["stepIn", "stepOver", "stepOut"].includes(getPauseCommand(state)); } +export function getMainThread(state: OuterState) { + return state.pause.mainThread; +} + +export function getCurrentThread(state: OuterState) { + return state.pause.currentThread; +} + +export function threadIsPaused(state: OuterState, thread: string) { + return !!getThreadPauseState(state.pause, thread).frames; +} + export function isPaused(state: OuterState) { return !!getFrames(state); } @@ -315,11 +384,11 @@ export function getIsPaused(state: OuterState) { } export function getPreviousPauseFrameLocation(state: OuterState) { - return state.pause.previousLocation; + return getCurrentPauseState(state).previousLocation; } export function isEvaluatingExpression(state: OuterState) { - return state.pause.command === "expression"; + return getCurrentPauseState(state).command === "expression"; } export function getPopupObjectProperties(state: OuterState, objectId: string) { @@ -327,15 +396,15 @@ export function getPopupObjectProperties(state: OuterState, objectId: string) { } export function getIsWaitingOnBreak(state: OuterState) { - return state.pause.isWaitingOnBreak; + return getCurrentPauseState(state).isWaitingOnBreak; } export function getShouldPauseOnExceptions(state: OuterState) { - return state.pause.shouldPauseOnExceptions; + return getCurrentPauseState(state).shouldPauseOnExceptions; } export function getShouldPauseOnCaughtExceptions(state: OuterState) { - return state.pause.shouldPauseOnCaughtExceptions; + return getCurrentPauseState(state).shouldPauseOnCaughtExceptions; } export function getCanRewind(state: OuterState) { @@ -343,11 +412,11 @@ export function getCanRewind(state: OuterState) { } export function getExtra(state: OuterState) { - return state.pause.extra; + return getCurrentPauseState(state).extra; } export function getFrames(state: OuterState) { - return state.pause.frames; + return getCurrentPauseState(state).frames; } function getGeneratedFrameId(frameId: string): string { @@ -389,7 +458,7 @@ export function getOriginalFrameScope( } export function getFrameScopes(state: OuterState) { - return state.pause.frameScopes; + return getCurrentPauseState(state).frameScopes; } export function getSelectedFrameBindings(state: OuterState) { @@ -467,7 +536,7 @@ export function getSelectedScopeMappings( } export function getSelectedFrameId(state: OuterState) { - return state.pause.selectedFrameId; + return getCurrentPauseState(state).selectedFrameId; } export function getTopFrame(state: OuterState) { @@ -492,7 +561,7 @@ export function getDebuggeeUrl(state: OuterState) { } export function getSkipPausing(state: OuterState) { - return state.pause.skipPausing; + return getCurrentPauseState(state).skipPausing; } // NOTE: currently only used for chrome diff --git a/src/types.js b/src/types.js index 2afd2a65ef..3a628938c8 100644 --- a/src/types.js +++ b/src/types.js @@ -238,6 +238,7 @@ export type LoadedObject = { * @static */ export type Pause = { + thread: string, frame: Frame, frames: Frame[], why: Why, @@ -294,6 +295,7 @@ export type Grip = { type BaseSource = {| +id: string, +url: string, + +thread: string, +sourceMapURL?: string, +isBlackBoxed: boolean, +isPrettyPrinted: boolean, diff --git a/src/utils/prefs.js b/src/utils/prefs.js index 0b985a41ec..edad9b0151 100644 --- a/src/utils/prefs.js +++ b/src/utils/prefs.js @@ -106,6 +106,7 @@ export const features = new PrefsHelper("devtools.debugger.features", { mapScopes: ["Bool", "map-scopes"], removeCommandBarOptions: ["Bool", "remove-command-bar-options"], workers: ["Bool", "workers"], + windowlessWorkers: ["Bool", "windowless-workers"], outline: ["Bool", "outline"], codeFolding: ["Bool", "code-folding"], pausePoints: ["Bool", "pause-points"], diff --git a/src/utils/source-queue.js b/src/utils/source-queue.js index 64c9eeaa6e..772a8c6e33 100644 --- a/src/utils/source-queue.js +++ b/src/utils/source-queue.js @@ -14,7 +14,6 @@ let currentWork; async function dispatchNewSources() { const sources = queuedSources; queuedSources = []; - currentWork = await newSources(sources); } diff --git a/src/utils/sources-tree/addToTree.js b/src/utils/sources-tree/addToTree.js index 4692de6875..035e594770 100644 --- a/src/utils/sources-tree/addToTree.js +++ b/src/utils/sources-tree/addToTree.js @@ -82,6 +82,7 @@ function findOrCreateNode( */ function traverseTree( url: ParsedURL, + thread: string, tree: TreeDirectory, debuggeeHost: ?string ): TreeNode { @@ -166,7 +167,7 @@ export function addToTree( return; } - const finalNode = traverseTree(url, tree, debuggeeHost); + const finalNode = traverseTree(url, source.thread, tree, debuggeeHost); // $FlowIgnore finalNode.contents = addSourceToNode(finalNode, url, source); From 33fc59972e45f8b67ae755b09c7613ce98fc8472 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 18 Dec 2018 20:58:14 +0000 Subject: [PATCH 2/3] Don't ignore workers.js --- bin/copy-modules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/copy-modules.js b/bin/copy-modules.js index 71df982a62..ca63a2de66 100644 --- a/bin/copy-modules.js +++ b/bin/copy-modules.js @@ -24,7 +24,7 @@ function ignoreFile(file) { // We exclude worker files because they are bundled and we include // worker/index.js files because are required by the debugger app in order // to communicate with the worker. - if (file.match(/\/workers/) && !file.match(/index.js/)) { + if (file.match(/\/workers/) && !file.match(/workers.js/) && !file.match(/index.js/)) { return true; } From d9abb971796897a1e8347cb2036fd89106f8aa5c Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Tue, 18 Dec 2018 16:12:26 -0500 Subject: [PATCH 3/3] Add prefs --- assets/panel/prefs.js | 1 + src/utils/prefs.js | 1 + 2 files changed, 2 insertions(+) diff --git a/assets/panel/prefs.js b/assets/panel/prefs.js index 917fb3158e..00c4ffe144 100644 --- a/assets/panel/prefs.js +++ b/assets/panel/prefs.js @@ -68,3 +68,4 @@ pref("devtools.debugger.features.autocomplete-expressions", false); pref("devtools.debugger.features.map-expression-bindings", true); pref("devtools.debugger.features.xhr-breakpoints", true); pref("devtools.debugger.features.origial-blackbox", false); +pref("devtools.debugger.features.windowless-workers", false); diff --git a/src/utils/prefs.js b/src/utils/prefs.js index edad9b0151..0b78efebd3 100644 --- a/src/utils/prefs.js +++ b/src/utils/prefs.js @@ -62,6 +62,7 @@ if (isDevelopment()) { pref("devtools.debugger.features.map-await-expression", true); pref("devtools.debugger.features.xhr-breakpoints", true); pref("devtools.debugger.features.origial-blackbox", false); + pref("devtools.debugger.features.windowless-workers", false); } export const prefs = new PrefsHelper("devtools", {