Backed out 13 changesets (bug 1494796) for dt failures on browser_dbg-navigation.js . CLOSED TREE

Backed out changeset 5db908b26d50 (bug 1494796)
Backed out changeset c48f00f0df72 (bug 1494796)
Backed out changeset 591453b88e8b (bug 1494796)
Backed out changeset a14e820311bc (bug 1494796)
Backed out changeset 0e214d450b35 (bug 1494796)
Backed out changeset 1a4ab8b35a85 (bug 1494796)
Backed out changeset fe1559f5f1d4 (bug 1494796)
Backed out changeset 35d967de4223 (bug 1494796)
Backed out changeset 1d21a55cae15 (bug 1494796)
Backed out changeset 33eec873a43e (bug 1494796)
Backed out changeset 25e69c21dc2e (bug 1494796)
Backed out changeset b900d41c8ae8 (bug 1494796)
Backed out changeset c2a034e34fa6 (bug 1494796)
This commit is contained in:
Narcis Beleuzu 2019-06-14 07:20:42 +03:00
Родитель 72a734cf98
Коммит 7d018750a1
42 изменённых файлов: 608 добавлений и 892 удалений

Просмотреть файл

@ -23,7 +23,6 @@ import {
import { setBreakpointPositions } from "./breakpointPositions";
import { PROMISE } from "../utils/middleware/promise";
import { recordEvent } from "../../utils/telemetry";
import { comparePosition } from "../../utils/location";
import { getTextAtPosition } from "../../utils/source";
@ -61,21 +60,24 @@ import type {
// breakpoint will be added to the reducer, to restore the above invariant.
// See syncBreakpoint.js for more.
function clientSetBreakpoint(client, state, breakpoint: Breakpoint) {
const breakpointLocation = makeBreakpointLocation(
state,
breakpoint.generatedLocation
);
return client.setBreakpoint(breakpointLocation, breakpoint.options);
function clientSetBreakpoint(breakpoint: Breakpoint) {
return ({ getState, client }: ThunkArgs) => {
const breakpointLocation = makeBreakpointLocation(
getState(),
breakpoint.generatedLocation
);
return client.setBreakpoint(breakpointLocation, breakpoint.options);
};
}
function clientRemoveBreakpoint(
client,
state,
generatedLocation: SourceLocation
) {
const breakpointLocation = makeBreakpointLocation(state, generatedLocation);
return client.removeBreakpoint(breakpointLocation);
function clientRemoveBreakpoint(generatedLocation: SourceLocation) {
return ({ getState, client }: ThunkArgs) => {
const breakpointLocation = makeBreakpointLocation(
getState(),
generatedLocation
);
return client.removeBreakpoint(breakpointLocation);
};
}
export function enableBreakpoint(cx: Context, initialBreakpoint: Breakpoint) {
@ -85,12 +87,13 @@ export function enableBreakpoint(cx: Context, initialBreakpoint: Breakpoint) {
return;
}
return dispatch({
dispatch({
type: "SET_BREAKPOINT",
cx,
breakpoint: { ...breakpoint, disabled: false },
[PROMISE]: clientSetBreakpoint(client, getState(), breakpoint),
});
return dispatch(clientSetBreakpoint(breakpoint));
};
}
@ -158,16 +161,15 @@ export function addBreakpoint(
return;
}
return dispatch({
type: "SET_BREAKPOINT",
cx,
breakpoint,
dispatch({ type: "SET_BREAKPOINT", cx, breakpoint });
if (disabled) {
// If we just clobbered an enabled breakpoint with a disabled one, we need
// to remove any installed breakpoint in the server.
[PROMISE]: disabled
? clientRemoveBreakpoint(client, getState(), generatedLocation)
: clientSetBreakpoint(client, getState(), breakpoint),
});
return dispatch(clientRemoveBreakpoint(generatedLocation));
}
return dispatch(clientSetBreakpoint(breakpoint));
};
}
@ -186,19 +188,18 @@ export function removeBreakpoint(cx: Context, initialBreakpoint: Breakpoint) {
return;
}
return dispatch({
dispatch({
type: "REMOVE_BREAKPOINT",
cx,
location: breakpoint.location,
// If the breakpoint is disabled then it is not installed in the server.
[PROMISE]: breakpoint.disabled
? Promise.resolve()
: clientRemoveBreakpoint(
client,
getState(),
breakpoint.generatedLocation
),
});
// If the breakpoint is disabled then it is not installed in the server.
if (breakpoint.disabled) {
return;
}
return dispatch(clientRemoveBreakpoint(breakpoint.generatedLocation));
};
}
@ -214,12 +215,6 @@ export function removeBreakpointAtGeneratedLocation(
target: SourceLocation
) {
return ({ dispatch, getState, client }: ThunkArgs) => {
// remove breakpoint from the server
const onBreakpointRemoved = clientRemoveBreakpoint(
client,
getState(),
target
);
// Remove any breakpoints matching the generated location.
const breakpoints = getBreakpointsList(getState());
for (const { location, generatedLocation } of breakpoints) {
@ -231,7 +226,6 @@ export function removeBreakpointAtGeneratedLocation(
type: "REMOVE_BREAKPOINT",
cx,
location,
[PROMISE]: onBreakpointRemoved,
});
}
}
@ -250,7 +244,9 @@ export function removeBreakpointAtGeneratedLocation(
});
}
}
return onBreakpointRemoved;
// Remove the breakpoint from the client itself.
return dispatch(clientRemoveBreakpoint(target));
};
}
@ -267,16 +263,13 @@ export function disableBreakpoint(cx: Context, initialBreakpoint: Breakpoint) {
return;
}
return dispatch({
dispatch({
type: "SET_BREAKPOINT",
cx,
breakpoint: { ...breakpoint, disabled: true },
[PROMISE]: clientRemoveBreakpoint(
client,
getState(),
breakpoint.generatedLocation
),
});
return dispatch(clientRemoveBreakpoint(breakpoint.generatedLocation));
};
}
@ -305,11 +298,12 @@ export function setBreakpointOptions(
// Note: setting a breakpoint's options implicitly enables it.
breakpoint = { ...breakpoint, disabled: false, options };
return dispatch({
dispatch({
type: "SET_BREAKPOINT",
cx,
breakpoint,
[PROMISE]: clientSetBreakpoint(client, getState(), breakpoint),
});
return dispatch(clientSetBreakpoint(breakpoint));
};
}

Просмотреть файл

@ -9,7 +9,7 @@ import { evaluateExpressions } from "../expressions";
import { inDebuggerEval } from "../../utils/pause";
import type { ThunkArgs } from "../types";
import type { ActorId } from "../../types";
import type { ResumedPacket } from "../../client/firefox/types";
/**
* Debugger has just resumed
@ -17,8 +17,9 @@ import type { ActorId } from "../../types";
* @memberof actions/pause
* @static
*/
export function resumed(thread: ActorId) {
export function resumed(packet: ResumedPacket) {
return async ({ dispatch, client, getState }: ThunkArgs) => {
const thread = packet.from;
const why = getPauseReason(getState(), thread);
const wasPausedInEval = inDebuggerEval(why);
const wasStepping = isStepping(getState(), thread);

Просмотреть файл

@ -72,7 +72,6 @@ const mockThreadClient = {
},
getBreakpointPositions: async () => ({}),
getBreakableLines: async () => [],
actorID: "threadActorID",
};
const mockFrameId = "1";
@ -100,6 +99,9 @@ function createPauseInfo(
};
}
function resumedPacket() {
return { from: "FakeThread", type: "resumed" };
}
describe("pause", () => {
describe("stepping", () => {
it("should set and clear the command", async () => {
@ -381,7 +383,7 @@ describe("pause", () => {
const cx = selectors.getThreadContext(getState());
dispatch(actions.stepIn(cx));
await dispatch(actions.resumed(mockThreadClient.actorID));
await dispatch(actions.resumed(resumedPacket()));
expect(client.evaluateExpressions.mock.calls).toHaveLength(1);
});
@ -399,7 +401,7 @@ describe("pause", () => {
client.evaluateExpressions.mockReturnValue(Promise.resolve(["YAY"]));
await dispatch(actions.paused(mockPauseInfo));
await dispatch(actions.resumed(mockThreadClient.actorID));
await dispatch(actions.resumed(resumedPacket()));
const expression = selectors.getExpression(getState(), "foo");
expect(expression && expression.value).toEqual("YAY");
});

Просмотреть файл

@ -41,16 +41,16 @@ export type BreakpointAction =
+index: number,
+breakpoint: XHRBreakpoint,
|}>
| PromiseAction<{|
| {|
+type: "SET_BREAKPOINT",
+cx: Context,
+breakpoint: Breakpoint,
|}>
| PromiseAction<{|
|}
| {|
+type: "REMOVE_BREAKPOINT",
+cx: Context,
+location: SourceLocation,
|}>
|}
| {|
+type: "REMOVE_PENDING_BREAKPOINT",
+cx: Context,

Просмотреть файл

@ -6,6 +6,7 @@
import type {
SourcePacket,
ResumedPacket,
PausedPacket,
ThreadClient,
Actions,
@ -73,7 +74,7 @@ async function paused(threadClient: ThreadClient, packet: PausedPacket) {
}
}
function resumed(threadClient: ThreadClient) {
function resumed(threadClient: ThreadClient, packet: ResumedPacket) {
// NOTE: the client suppresses resumed events while interrupted
// to prevent unintentional behavior.
// see [client docs](../README.md#interrupted) for more information.
@ -82,7 +83,7 @@ function resumed(threadClient: ThreadClient) {
return;
}
actions.resumed(threadClient.actorID);
actions.resumed(packet);
}
function newSource(threadClient: ThreadClient, { source }: SourcePacket) {

Просмотреть файл

@ -138,6 +138,11 @@ export type PausedPacket = {
},
};
export type ResumedPacket = {
from: ActorId,
type: string,
};
/**
* Response from the `getFrames` function call
* @memberof firefox
@ -182,7 +187,7 @@ export type TabPayload = {
*/
export type Actions = {
paused: Pause => void,
resumed: ActorId => void,
resumed: ResumedPacket => void,
newQueuedSources: (QueuedSourceData[]) => void,
fetchEventListeners: () => void,
updateWorkers: () => void,
@ -364,7 +369,6 @@ export type ThreadClient = {
getLastPausePacket: () => ?PausedPacket,
_parent: TabClient,
actor: ActorId,
actorID: ActorId,
request: (payload: Object) => Promise<*>,
url: string,
setActiveEventBreakpoints: (string[]) => void,

Просмотреть файл

@ -50,17 +50,11 @@ function update(
): BreakpointsState {
switch (action.type) {
case "SET_BREAKPOINT": {
if (action.status === "start") {
return setBreakpoint(state, action);
}
return state;
return setBreakpoint(state, action);
}
case "REMOVE_BREAKPOINT": {
if (action.status === "start") {
return removeBreakpoint(state, action);
}
return state;
return removeBreakpoint(state, action);
}
case "NAVIGATE": {

Просмотреть файл

@ -29,11 +29,6 @@ function update(state: PendingBreakpointsState = {}, action: Action) {
return setBreakpoint(state, action);
case "REMOVE_BREAKPOINT":
if (action.status === "start") {
return removeBreakpoint(state, action);
}
return state;
case "REMOVE_PENDING_BREAKPOINT":
return removeBreakpoint(state, action);
}

Просмотреть файл

@ -53,6 +53,5 @@ add_task(async function() {
await reload(dbg, "long.js");
await waitForSelectedSource(dbg, "long.js");
await waitForRequestsToSettle(dbg);
ok(getSelectedSource().url.includes("long.js"), "Selected source is long.js");
});

Просмотреть файл

@ -38,7 +38,6 @@ add_task(async function() {
const toolbox = await openNewTabAndToolbox(EXAMPLE_URL + "doc-scripts.html", "jsdebugger");
const dbg = createDebuggerContext(toolbox);
const onBreakpoint = waitForDispatch(dbg, "SET_BREAKPOINT", 2);
// Pending breakpoints are installed asynchronously, keep invoking the entry
// function until the debugger pauses.
@ -46,11 +45,12 @@ add_task(async function() {
invokeInTab("main");
return isPaused(dbg);
});
await onBreakpoint;
ok(true, "paused at unmapped breakpoint");
await waitForState(dbg, state => dbg.selectors.getBreakpointCount(state) == 2);
ok(true, "unmapped breakpoints shown in UI");
await waitForRequestsToSettle(dbg);
await toolbox.destroy();
});
// Test that if we show a breakpoint with an old generated location, it is

Просмотреть файл

@ -25,7 +25,6 @@ add_task(async function() {
await selectSource(dbg, "sjs_code_reload");
await addBreakpoint(dbg, "sjs_code_reload", 2);
await waitForRequestsToSettle(dbg);
await reload(dbg, "sjs_code_reload.sjs");
await waitForSelectedSource(dbg, "sjs_code_reload.sjs");
@ -39,5 +38,4 @@ add_task(async function() {
is(breakpointList.length, 1);
is(breakpoint.location.line, 6);
await waitForRequestsToSettle(dbg);
});

Просмотреть файл

@ -43,9 +43,7 @@ add_task(async function() {
await dbg.toolbox._target.waitForRequestsToSettle();
invokeInTab("startWorker");
await waitForPaused(dbg, "scopes-worker.js");
const onRemoved = waitForDispatch(dbg, "REMOVE_BREAKPOINT");
await removeBreakpoint(dbg, workerSource.id, 11);
await onRemoved;
// We should be paused at the first line of simple-worker.js
assertPausedAtSourceAndLine(dbg, workerSource.id, 11);

Просмотреть файл

@ -799,13 +799,11 @@ async function addBreakpoint(dbg, source, line, column, options) {
source = findSource(dbg, source);
const sourceId = source.id;
const bpCount = dbg.selectors.getBreakpointCount();
const onBreakpoint = waitForDispatch(dbg, "SET_BREAKPOINT");
await dbg.actions.addBreakpoint(
getContext(dbg),
{ sourceId, line, column },
options
);
await onBreakpoint;
is(
dbg.selectors.getBreakpointCount(),
bpCount + 1,

Просмотреть файл

@ -33,15 +33,7 @@ add_task(async function() {
iframe.style.minWidth = "1px"; // Disable the min width set in css
is(iframe.clientWidth, panelWidth - 25, "The iframe fits within the available space");
// on shutdown, the sidebar width will be set to the clientWidth of the iframe
const expectedWidth = iframe.clientWidth;
await cleanup(toolbox);
// Wait until the toolbox-host-manager was destroyed and updated the preferences
// to avoid side effects in the next test.
await waitUntil(() => {
const savedWidth = Services.prefs.getIntPref("devtools.toolbox.sidebar.width");
return savedWidth === expectedWidth;
});
});
add_task(async function() {

Просмотреть файл

@ -20,7 +20,6 @@ var WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
add_task(async function testWhilePaused() {
const dbg = await initWorkerDebugger(TAB_URL, WORKER_URL);
const {client, tab, workerTargetFront, toolbox} = dbg;
const workerThreadClient = await workerTargetFront.getFront("context");
// Execute some basic math to make sure evaluations are working.
const jsterm = await getSplitConsole(toolbox);
@ -28,7 +27,7 @@ add_task(async function testWhilePaused() {
ok(executed.textContent.includes("10001"), "Text for message appeared correct");
await clickElement(dbg, "pause");
workerThreadClient.once("willInterrupt").then(() => {
once(dbg.client, "willInterrupt").then(() => {
info("Posting message to worker, then waiting for a pause");
postMessageToWorkerInTab(tab, WORKER_URL, "ping");
});

Просмотреть файл

@ -173,8 +173,6 @@ RootActor.prototype = {
// Supports native log points and modifying the condition/log of an existing
// breakpoints. Fx66+
nativeLogpoints: true,
// support older browsers for Fx69+
hasThreadFront: true,
},
/**

Просмотреть файл

@ -335,7 +335,7 @@ const browsingContextTargetPrototype = {
* Getter for the browsing context's current DOM window.
*/
get window() {
return this.docShell && this.docShell.domWindow;
return this.docShell.domWindow;
},
get outerWindowID() {
@ -1346,7 +1346,8 @@ const browsingContextTargetPrototype = {
// will-navigate
const threadActor = this.threadActor;
if (threadActor.state == "paused") {
threadActor.unsafeSynchronize(Promise.resolve(threadActor.doResume()));
this.conn.send(
threadActor.unsafeSynchronize(Promise.resolve(threadActor.onResume())));
threadActor.dbg.enabled = false;
}
threadActor.disableAllBreakpoints();

Просмотреть файл

@ -38,7 +38,6 @@ const WorkerTargetActor = protocol.ActorClassWithSpec(workerTargetSpec, {
const form = {
actor: this.actorID,
consoleActor: this._consoleActor,
contextActor: this._threadActor,
id: this._dbg.id,
url: this._dbg.url,
type: this._dbg.type,

Просмотреть файл

@ -217,7 +217,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
destroy: function() {
dumpn("in ThreadActor.prototype.destroy");
if (this._state == "paused") {
this.doResume();
this.onResume();
}
this._xhrBreakpoints = [];
@ -252,25 +252,20 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
},
// Request handlers
onAttach: function({options}) {
onAttach: function(request) {
if (this.state === "exited") {
return {
error: "exited",
message: "threadActor has exited",
};
return { type: "exited" };
}
if (this.state !== "detached") {
return {
error: "wrongState",
message: "Current state is " + this.state,
};
return { error: "wrongState",
message: "Current state is " + this.state };
}
this._state = "attached";
this._debuggerSourcesSeen = new WeakSet();
Object.assign(this._options, options || {});
Object.assign(this._options, request.options || {});
this.sources.setOptions(this._options);
this.sources.on("newSource", this.onNewSourceEvent);
@ -282,8 +277,10 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
thread: this,
});
if (options.breakpoints) {
this._setBreakpointsOnAttach(options.breakpoints);
if (request.options.breakpoints) {
for (const { location, options } of Object.values(request.options.breakpoints)) {
this.setBreakpoint(location, options);
}
}
this.dbg.addDebuggees();
@ -304,37 +301,24 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
// Put ourselves in the paused state.
const packet = this._paused();
if (!packet) {
return {
error: "notAttached",
message: "cannot attach, could not create pause packet",
};
return { error: "notAttached" };
}
packet.why = { type: "attached" };
// Send the response to the attach request now (rather than
// returning it), because we're going to start a nested event
// loop here.
this.conn.send({from: this.actorID});
this.conn.sendActorEvent(this.actorID, "paused", packet);
// returning it), because we're going to start a nested event loop
// here.
this.conn.send(packet);
// Start a nested event loop.
this._pushThreadPause();
// We already sent a response to this request, don't send one
// now
// now.
return null;
} catch (e) {
reportError(e);
return {
error: "notAttached",
message: e.toString(),
};
}
},
_setBreakpointsOnAttach(breakpoints) {
for (const { location, options } of Object.values(breakpoints)) {
this.setBreakpoint(location, options);
return { error: "notAttached", message: e.toString() };
}
},
@ -481,8 +465,9 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
this._debuggerSourcesSeen = null;
dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet");
this.conn.sendActorEvent(this.actorID, "detached");
return {};
return {
type: "detached",
};
},
onReconfigure: function(request) {
@ -553,7 +538,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
const pkt = onPacket(packet);
this._priorPause = pkt;
this.conn.sendActorEvent(this.actorID, "paused", pkt);
this.conn.send(pkt);
} catch (error) {
reportError(error);
this.conn.send({
@ -847,14 +832,14 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
* Handle attaching the various stepping hooks we need to attach when we
* receive a resume request with a resumeLimit property.
*
* @param Object { rewind, resumeLimit }
* The values received over the RDP.
* @param Object request
* The request packet received over the RDP.
* @returns A promise that resolves to true once the hooks are attached, or is
* rejected with an error packet.
*/
_handleResumeLimit: async function({rewind, resumeLimit}) {
let steppingType = resumeLimit.type;
const rewinding = rewind;
_handleResumeLimit: async function(request) {
let steppingType = request.resumeLimit.type;
const rewinding = request.rewind;
if (!["break", "step", "next", "finish", "warp"].includes(steppingType)) {
return Promise.reject({
error: "badParameterType",
@ -945,7 +930,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
/**
* Handle a protocol request to resume execution of the debuggee.
*/
onResume: async function({resumeLimit, rewind}) {
onResume: function(request) {
if (this._state !== "paused") {
return {
error: "wrongState",
@ -968,67 +953,53 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
};
}
if (rewind && !this.dbg.replaying) {
const rewinding = request && request.rewind;
if (rewinding && !this.dbg.replaying) {
return {
error: "cantRewind",
message: "Can't rewind a debuggee that is not replaying.",
};
}
try {
if (resumeLimit) {
await this._handleResumeLimit({resumeLimit, rewind});
} else {
this._clearSteppingHooks();
}
let resumeLimitHandled;
if (request && request.resumeLimit) {
resumeLimitHandled = this._handleResumeLimit(request);
} else {
this._clearSteppingHooks();
resumeLimitHandled = Promise.resolve(true);
}
return resumeLimitHandled.then(() => {
this.maybePauseOnExceptions();
// When replaying execution in a separate process we need to explicitly
// notify that process when to resume execution.
if (this.dbg.replaying) {
if (resumeLimit && resumeLimit.type == "warp") {
this.dbg.replayTimeWarp(resumeLimit.target);
} else if (rewind) {
if (request && request.resumeLimit && request.resumeLimit.type == "warp") {
this.dbg.replayTimeWarp(request.resumeLimit.target);
} else if (rewinding) {
this.dbg.replayResumeBackward();
} else {
this.dbg.replayResumeForward();
}
}
this.doResume();
return {};
} catch (error) {
const packet = this._resumed();
this._popThreadPause();
// 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)
if (Services.obs) {
Services.obs.notifyObservers(this, "devtools-thread-resumed");
}
return packet;
}, error => {
return error instanceof Error
? {
error: "unknownError",
? { error: "unknownError",
message: DevToolsUtils.safeErrorString(error) }
// It is a known error, and the promise was rejected with an error
// packet.
: error;
}
},
/**
* Only resume and notify necessary observers. This should be used in cases
* when we do not want to notify the front end of a resume, for example when
* we are shutting down.
*/
doResume() {
this.maybePauseOnExceptions();
this._state = "running";
// Drop the actors in the pause actor pool.
this.conn.removeActorPool(this._pausePool);
this._pausePool = null;
this._pauseActor = null;
this._popThreadPause();
// 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");
if (Services.obs) {
Services.obs.notifyObservers(this, "devtools-thread-resumed");
}
});
},
/**
@ -1165,30 +1136,30 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
/**
* Handle a protocol request to pause the debuggee.
*/
onInterrupt: function({when}) {
onInterrupt: function(request) {
if (this.state == "exited") {
return { type: "exited" };
} else if (this.state == "paused") {
// TODO: return the actual reason for the existing pause.
this.conn.sendActorEvent(this.actorID, "paused", { why: { type: "alreadyPaused" }});
return {};
return { type: "paused", why: { type: "alreadyPaused" } };
} else if (this.state != "running") {
return { error: "wrongState",
message: "Received interrupt request in " + this.state +
" state." };
}
try {
// If execution should pause just before the next JavaScript bytecode is
// executed, just set an onEnterFrame handler.
if (when == "onNext" && !this.dbg.replaying) {
if (request.when == "onNext" && !this.dbg.replaying) {
const onEnterFrame = (frame) => {
this._pauseAndRespond(frame, { type: "interrupted", onNext: true });
return this._pauseAndRespond(frame, { type: "interrupted", onNext: true });
};
this.dbg.onEnterFrame = onEnterFrame;
this.conn.sendActorEvent(this.actorID, "willInterrupt");
return {};
return { type: "willInterrupt" };
}
if (this.dbg.replaying) {
this.dbg.replayPause();
}
@ -1207,8 +1178,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
// Send the response to the interrupt request now (rather than
// returning it), because we're going to start a nested event loop
// here.
this.conn.send({from: this.actorID, type: "interrupt"});
this.conn.sendActorEvent(this.actorID, "paused", packet);
this.conn.send(packet);
// Start a nested event loop.
this._pushThreadPause();
@ -1294,6 +1264,18 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
return packet;
},
_resumed: function() {
this._state = "running";
// Drop the actors in the pause actor pool.
this.conn.removeActorPool(this._pausePool);
this._pausePool = null;
this._pauseActor = null;
return { from: this.actorID, type: "resumed" };
},
/**
* Expire frame actors for frames that have been popped.
*

Просмотреть файл

@ -392,12 +392,9 @@ async function attachTestTab(client, title) {
// thread.
async function attachTestThread(client, title, callback = () => {}) {
const targetFront = await attachTestTab(client, title);
const threadClient = await targetFront.getFront("context");
const onPaused = threadClient.once("paused");
await targetFront.attachThread({
const [response, threadClient] = await targetFront.attachThread({
autoBlackBox: true,
});
const response = await onPaused;
Assert.equal(threadClient.state, "paused", "Thread client is paused");
Assert.ok("why" in response);
Assert.equal(response.why.type, "attached");

Просмотреть файл

@ -3,7 +3,7 @@
"use strict";
const { ThreadClient } = require("devtools/shared/client/thread-client");
const ThreadClient = require("devtools/shared/client/thread-client");
const { BrowsingContextTargetFront } = require("devtools/shared/fronts/targets/browsing-context");
/**
@ -13,7 +13,7 @@ const { BrowsingContextTargetFront } = require("devtools/shared/fronts/targets/b
add_task(threadClientTest(({ threadClient, debuggee, client, targetFront }) => {
ok(true, "Thread actor was able to attach");
ok(threadClient instanceof ThreadClient, "Thread client is valid");
Assert.equal(threadClient.state, "attached", "Thread client is resumed");
Assert.equal(threadClient.state, "attached", "Thread client is paused");
Assert.equal(String(debuggee), "[object Sandbox]", "Debuggee client is valid");
ok(client instanceof DebuggerClient, "Client is valid");
ok(targetFront instanceof BrowsingContextTargetFront, "TargetFront is valid");

Просмотреть файл

@ -44,16 +44,13 @@ function test_simple_breakpoint() {
);
// Set a logpoint which should throw an error message.
await gThreadClient.setBreakpoint({
gThreadClient.setBreakpoint({
sourceUrl: source.url,
line: 3,
}, { logValue: "c" });
// Execute the rest of the code.
await gThreadClient.resume();
Assert.equal(lastMessage.level, "logPointError");
Assert.equal(lastMessage.arguments[0], "[Logpoint threw]: c is not defined");
finishClient(gClient);
gThreadClient.resume();
});
/* eslint-disable */
@ -65,4 +62,8 @@ function test_simple_breakpoint() {
"test.js",
1);
/* eslint-enable */
Assert.equal(lastMessage.level, "logPointError");
Assert.equal(lastMessage.arguments[0], "[Logpoint threw]: c is not defined");
finishClient(gClient);
}

Просмотреть файл

@ -19,9 +19,8 @@ function run_test() {
gClient, "test-nesting",
function(response, targetFront, threadClient) {
// Reach over the protocol connection and get a reference to the thread actor.
// TODO: rewrite the test so we don't do this..
gThreadActor =
gClient._transport._serverConnection.getActor(threadClient.actorID);
threadClient._transport._serverConnection.getActor(threadClient._actor);
test_nesting();
});

Просмотреть файл

@ -20,9 +20,8 @@ function run_test() {
function(response, targetFront, threadClient) {
// Reach over the protocol connection and get a reference to the thread
// actor.
// TODO: rewrite tests so we don't do this kind of reaching anymore..
gThreadActor =
gClient._transport._serverConnection.getActor(threadClient.actorID);
threadClient._transport._serverConnection.getActor(threadClient._actor);
test_nesting();
});

Просмотреть файл

@ -37,19 +37,21 @@ function start_second_connection() {
}
async function test_nesting() {
let result;
try {
result = await gThreadClient1.resume();
await gThreadClient1.resume();
} catch (e) {
Assert.ok(e.includes("wrongOrder"), "rejects with the wrong order");
Assert.equal(e.error, "wrongOrder");
}
try {
await gThreadClient2.resume();
} catch (e) {
Assert.ok(!e.error);
Assert.equal(e.from, gThreadClient2.actor);
}
Assert.ok(!result, "no response");
result = await gThreadClient2.resume();
Assert.ok(true, "resumed as expected");
gThreadClient1.resume().then(response => {
Assert.ok(true, "resumed as expected");
Assert.ok(!response.error);
Assert.equal(response.from, gThreadClient1.actor);
gClient1.close(() => finishClient(gClient2));
});

Просмотреть файл

@ -0,0 +1,94 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test the DebuggerClient.registerClient API
var EventEmitter = require("devtools/shared/event-emitter");
var gClient;
var gTestClient;
function TestActor(conn) {
this.conn = conn;
}
TestActor.prototype = {
actorPrefix: "test",
start: function() {
this.conn.sendActorEvent(this.actorID, "foo", {
hello: "world",
});
return {};
},
};
TestActor.prototype.requestTypes = {
"start": TestActor.prototype.start,
};
function TestClient(client, form) {
this.client = client;
this.actor = form.test;
this.events = ["foo"];
EventEmitter.decorate(this);
client.registerClient(this);
this.detached = false;
}
TestClient.prototype = {
start: function() {
this.client.request({
to: this.actor,
type: "start",
});
},
detach: function(onDone) {
this.detached = true;
onDone();
},
};
function run_test() {
ActorRegistry.addGlobalActor({
constructorName: "TestActor",
constructorFun: TestActor,
}, "test");
DebuggerServer.init();
DebuggerServer.registerAllActors();
add_test(init);
add_test(test_client_events);
add_test(close_client);
run_next_test();
}
function init() {
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect()
.then(() => gClient.mainRoot.rootForm)
.then(response => {
gTestClient = new TestClient(gClient, response);
run_next_test();
});
}
function test_client_events() {
// Test DebuggerClient.registerClient and DebuggerServerConnection.sendActorEvent
gTestClient.on("foo", function(data) {
Assert.equal(data.hello, "world");
run_next_test();
});
gTestClient.start();
}
function close_client() {
gClient.close().then(() => {
// Check that client.detach method is call on client destruction
Assert.ok(gTestClient.detached);
run_next_test();
});
}

Просмотреть файл

@ -129,6 +129,7 @@ async function testThrow(dbg) {
{return: {type: "undefined"}},
`completion type`
);
await resume(threadClient);
}
function run_test() {

Просмотреть файл

@ -96,8 +96,6 @@ function TestTargetActor(connection, global) {
this.conn.addActor(this.threadActor);
this._attached = false;
this._extraActors = {};
// This is a hack in order to enable threadActor to be accessed from getFront
this._extraActors.contextActor = this.threadActor;
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: () => [this._global],
shouldAddNewGlobalAsDebuggee: g => {

Просмотреть файл

@ -217,6 +217,7 @@ skip-if = true # tests for breakpoint actors are obsolete bug 1524374
reason = bug 937197
[test_layout-reflows-observer.js]
[test_protocolSpec.js]
[test_registerClient.js]
[test_client_request.js]
[test_symbols-01.js]
[test_symbols-02.js]

Просмотреть файл

@ -20,6 +20,7 @@ loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socke
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "RootFront", "devtools/shared/fronts/root", true);
loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
loader.lazyRequireGetter(this, "Front", "devtools/shared/protocol", true);
@ -32,6 +33,10 @@ function DebuggerClient(transport) {
this._transport = transport;
this._transport.hooks = this;
// Map actor ID to client instance for each actor type.
// To be removed once all clients are refactored to protocol.js
this._clients = new Map();
this._pendingRequests = new Map();
this._activeRequests = new Map();
this._eventsEnabled = true;
@ -212,11 +217,55 @@ DebuggerClient.prototype = {
this.once("closed", deferred.resolve);
cleanup();
// Call each client's `detach` method by calling
// lastly registered ones first to give a chance
// to detach child clients first.
const clients = [...this._clients.values()];
this._clients.clear();
const detachClients = () => {
const client = clients.pop();
if (!client) {
// All clients detached.
cleanup();
return;
}
if (client.detach) {
client.detach(detachClients);
return;
}
detachClients();
};
detachClients();
return deferred.promise;
},
/**
* Attach to a global-scoped thread actor for chrome debugging.
*
* @param string threadActor
* The actor ID for the thread to attach.
* @param object options
* Configuration options.
*/
attachThread: function(threadActor, options = {}) {
if (this._clients.has(threadActor)) {
const client = this._clients.get(threadActor);
return promise.resolve([{}, client]);
}
const packet = {
to: threadActor,
type: "attach",
options,
};
return this.request(packet).then(response => {
const threadClient = new ThreadClient(this, threadActor);
this.registerClient(threadClient);
return [response, threadClient];
});
},
/**
* Release an object actor.
*
@ -546,13 +595,6 @@ DebuggerClient.prototype = {
return;
}
// support older browsers for Fx69+ for using the old thread client
if (!this.traits.hasThreadFront &&
packet.from.includes("context")) {
this.sendToDeprecatedThreadClient(packet);
return;
}
// If we have a registered Front for this actor, let it handle the packet
// and skip all the rest of this unpleasantness.
const front = this.getActor(packet.from);
@ -561,12 +603,24 @@ DebuggerClient.prototype = {
return;
}
if (this._clients.has(packet.from) && packet.type) {
const client = this._clients.get(packet.from);
const type = packet.type;
if (client.events.includes(type)) {
client.emit(type, packet);
// we ignore the rest, as the client is expected to handle this packet.
return;
}
}
let activeRequest;
// See if we have a handler function waiting for a reply from this
// actor. (Don't count unsolicited notifications or pauses as
// replies.)
if (this._activeRequests.has(packet.from) &&
!(packet.type in UnsolicitedNotifications)) {
!(packet.type in UnsolicitedNotifications) &&
!(packet.type == ThreadStateTypes.paused &&
packet.why.type in UnsolicitedPauses)) {
activeRequest = this._activeRequests.get(packet.from);
this._activeRequests.delete(packet.from);
}
@ -576,6 +630,13 @@ DebuggerClient.prototype = {
// in the local transport case.
this._attemptNextRequest(packet.from);
// Packets that indicate thread state changes get special treatment.
if (packet.type in ThreadStateTypes &&
this._clients.has(packet.from) &&
typeof this._clients.get(packet.from)._onThreadState == "function") {
this._clients.get(packet.from)._onThreadState(packet);
}
// Only try to notify listeners on events, not responses to requests
// that lack a packet type.
if (packet.type) {
@ -593,54 +654,6 @@ DebuggerClient.prototype = {
}
},
// support older browsers for Fx69+
// The code duplication here is intentional until we drop support for
// these versions. Once that happens this code can be deleted.
sendToDeprecatedThreadClient(packet) {
const deprecatedThreadClient = this.getActor(packet.from);
if (deprecatedThreadClient && packet.type) {
const type = packet.type;
if (deprecatedThreadClient.events.includes(type)) {
deprecatedThreadClient.emit(type, packet);
// we ignore the rest, as the client is expected to handle this packet.
return;
}
}
let activeRequest;
// See if we have a handler function waiting for a reply from this
// actor. (Don't count unsolicited notifications or pauses as
// replies.)
if (this._activeRequests.has(packet.from) &&
!(packet.type == ThreadStateTypes.paused &&
packet.why.type in UnsolicitedPauses)) {
activeRequest = this._activeRequests.get(packet.from);
this._activeRequests.delete(packet.from);
}
// If there is a subsequent request for the same actor, hand it off to the
// transport. Delivery of packets on the other end is always async, even
// in the local transport case.
this._attemptNextRequest(packet.from);
// Packets that indicate thread state changes get special treatment.
if (packet.type in ThreadStateTypes &&
deprecatedThreadClient &&
typeof deprecatedThreadClient._onThreadState == "function") {
deprecatedThreadClient._onThreadState(packet);
}
// Only try to notify listeners on events, not responses to requests
// that lack a packet type.
if (packet.type) {
this.emit(packet.type, packet);
}
if (activeRequest) {
activeRequest.emit("json-reply", packet);
}
},
/**
* Called by the DebuggerTransport to dispatch incoming bulk packets as
* appropriate.
@ -848,6 +861,38 @@ DebuggerClient.prototype = {
});
},
registerClient: function(client) {
const actorID = client.actor;
if (!actorID) {
throw new Error("DebuggerServer.registerClient expects " +
"a client instance with an `actor` attribute.");
}
if (!Array.isArray(client.events)) {
throw new Error("DebuggerServer.registerClient expects " +
"a client instance with an `events` attribute " +
"that is an array.");
}
if (client.events.length > 0 && typeof (client.emit) != "function") {
throw new Error("DebuggerServer.registerClient expects " +
"a client instance with non-empty `events` array to" +
"have an `emit` function.");
}
if (this._clients.has(actorID)) {
throw new Error("DebuggerServer.registerClient already registered " +
"a client for this actor.");
}
this._clients.set(actorID, client);
},
unregisterClient: function(client) {
const actorID = client.actor;
if (!actorID) {
throw new Error("DebuggerServer.unregisterClient expects " +
"a Client instance with a `actor` attribute.");
}
this._clients.delete(actorID);
},
/**
* Actor lifetime management, echos the server's actor pools.
*/

Просмотреть файл

@ -1,436 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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 {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
const EventEmitter = require("devtools/shared/event-emitter");
const {ThreadStateTypes} = require("devtools/shared/client/constants");
loader.lazyRequireGetter(
this,
"ObjectClient",
"devtools/shared/client/object-client"
);
loader.lazyRequireGetter(
this,
"SourceFront",
"devtools/shared/fronts/source",
true
);
/**
* Creates a thread client for the remote debugging protocol server. This client
* is a front to the thread actor created in the server side, hiding the
* protocol details in a traditional JavaScript API.
*
* @param client DebuggerClient
* @param actor string
* The actor ID for this thread.
*/
function ThreadClient(client, actor) {
this.client = client;
this._actor = actor;
this._pauseGrips = {};
this._threadGrips = {};
this.request = this.client.request;
}
ThreadClient.prototype = {
_state: "paused",
get state() {
return this._state;
},
get paused() {
return this._state === "paused";
},
_actor: null,
get actor() {
return this._actor;
},
get _transport() {
return this.client._transport;
},
_assertPaused: function(command) {
if (!this.paused) {
throw Error(
command + " command sent while not paused. Currently " + this._state
);
}
},
/**
* Resume a paused thread. If the optional limit parameter is present, then
* the thread will also pause when that limit is reached.
*
* @param [optional] object limit
* An object with a type property set to the appropriate limit (next,
* step, or finish) per the remote debugging protocol specification.
* Use null to specify no limit.
* @param bool aRewind
* Whether execution should rewind until the limit is reached, rather
* than proceeding forwards. This parameter has no effect if the
* server does not support rewinding.
*/
_doResume: DebuggerClient.requester({
type: "resume",
resumeLimit: arg(0),
rewind: arg(1),
}, {
before: function(packet) {
this._assertPaused("resume");
// Put the client in a tentative "resuming" state so we can prevent
// further requests that should only be sent in the paused state.
this._previousState = this._state;
this._state = "resuming";
return packet;
},
after: function(response) {
if (response.error && this._state == "resuming") {
// There was an error resuming, update the state to the new one
// reported by the server, if given (only on wrongState), otherwise
// reset back to the previous state.
if (response.state) {
this._state = ThreadStateTypes[response.state];
} else {
this._state = this._previousState;
}
}
delete this._previousState;
return response;
},
}),
/**
* Reconfigure the thread actor.
*
* @param object options
* A dictionary object of the new options to use in the thread actor.
*/
reconfigure: DebuggerClient.requester({
type: "reconfigure",
options: arg(0),
}),
/**
* Resume a paused thread.
*/
resume: function() {
return this._doResume(null, false);
},
/**
* Resume then pause without stepping.
*
*/
resumeThenPause: function() {
return this._doResume({ type: "break" }, false);
},
/**
* Rewind a thread until a breakpoint is hit.
*/
rewind: function() {
return this._doResume(null, true);
},
/**
* Step over a function call.
*/
stepOver: function() {
return this._doResume({ type: "next" }, false);
},
/**
* Step into a function call.
*/
stepIn: function() {
return this._doResume({ type: "step" }, false);
},
/**
* Step out of a function call.
*/
stepOut: function() {
return this._doResume({ type: "finish" }, false);
},
/**
* Rewind step over a function call.
*/
reverseStepOver: function() {
return this._doResume({ type: "next" }, true);
},
/**
* Immediately interrupt a running thread.
*/
interrupt: function() {
return this._doInterrupt(null);
},
/**
* Pause execution right before the next JavaScript bytecode is executed.
*/
breakOnNext: function() {
return this._doInterrupt("onNext");
},
/**
* Warp through time to an execution point in the past or future.
*
* @param object aTarget
* Description of the warp destination.
*/
timeWarp: function(target) {
const warp = () => {
this._doResume({ type: "warp", target }, true);
};
if (this.paused) {
return warp();
}
return this.interrupt().then(warp);
},
/**
* Interrupt a running thread.
*/
_doInterrupt: DebuggerClient.requester({
type: "interrupt",
when: arg(0),
}),
/**
* Enable or disable pausing when an exception is thrown.
*
* @param boolean pauseOnExceptions
* Enables pausing if true, disables otherwise.
* @param boolean ignoreCaughtExceptions
* Whether to ignore caught exceptions
*/
pauseOnExceptions: DebuggerClient.requester({
type: "pauseOnExceptions",
pauseOnExceptions: arg(0),
ignoreCaughtExceptions: arg(1),
}),
/**
* Detach from the thread actor.
*/
detach: DebuggerClient.requester({
type: "detach",
}),
destroy: function() {
return this.detach();
},
/**
* attach to the thread actor.
*/
attach: DebuggerClient.requester({
type: "attach",
options: arg(0),
}),
/**
* Promote multiple pause-lifetime object actors to thread-lifetime ones.
*
* @param array actors
* An array with actor IDs to promote.
*/
threadGrips: DebuggerClient.requester({
type: "threadGrips",
actors: arg(0),
}),
/**
* Request the loaded sources for the current thread.
*/
getSources: DebuggerClient.requester({
type: "sources",
}),
/**
* Request frames from the callstack for the current thread.
*
* @param start integer
* The number of the youngest stack frame to return (the youngest
* frame is 0).
* @param count integer
* The maximum number of frames to return, or null to return all
* frames.
*/
getFrames: DebuggerClient.requester({
type: "frames",
start: arg(0),
count: arg(1),
}),
/**
* Toggle pausing via breakpoints in the server.
*
* @param skip boolean
* Whether the server should skip pausing via breakpoints
*/
skipBreakpoints: DebuggerClient.requester({
type: "skipBreakpoints",
skip: arg(0),
}),
/**
* Request the frame environment.
*
* @param frameId string
*/
getEnvironment: function(frameId) {
return this.request({ to: frameId, type: "getEnvironment" });
},
/**
* Return a ObjectClient object for the given object grip.
*
* @param grip object
* A pause-lifetime object grip returned by the protocol.
*/
pauseGrip: function(grip) {
if (grip.actor in this._pauseGrips) {
return this._pauseGrips[grip.actor];
}
const client = new ObjectClient(this.client, grip);
this._pauseGrips[grip.actor] = client;
return client;
},
/**
* Clear and invalidate all the grip clients from the given cache.
*
* @param gripCacheName
* The property name of the grip cache we want to clear.
*/
_clearObjectClients: function(gripCacheName) {
for (const id in this[gripCacheName]) {
this[gripCacheName][id].valid = false;
}
this[gripCacheName] = {};
},
/**
* Invalidate pause-lifetime grip clients and clear the list of current grip
* clients.
*/
_clearPauseGrips: function() {
this._clearObjectClients("_pauseGrips");
},
/**
* Invalidate thread-lifetime grip clients and clear the list of current grip
* clients.
*/
_clearThreadGrips: function() {
this._clearObjectClients("_threadGrips");
},
/**
* Handle thread state change by doing necessary cleanup and notifying all
* registered listeners.
*/
_onThreadState: function(packet) {
this._state = ThreadStateTypes[packet.type];
// The debugger UI may not be initialized yet so we want to keep
// the packet around so it knows what to pause state to display
// when it's initialized
this._lastPausePacket = packet.type === "resumed" ? null : packet;
this._clearPauseGrips();
packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
this.client._eventsEnabled && this.emit(packet.type, packet);
},
getLastPausePacket: function() {
return this._lastPausePacket;
},
setBreakpoint: DebuggerClient.requester({
type: "setBreakpoint",
location: arg(0),
options: arg(1),
}),
removeBreakpoint: DebuggerClient.requester({
type: "removeBreakpoint",
location: arg(0),
}),
/**
* Requests to set XHR breakpoint
* @param string path
* pause when url contains `path`
* @param string method
* pause when method of request is `method`
*/
setXHRBreakpoint: DebuggerClient.requester({
type: "setXHRBreakpoint",
path: arg(0),
method: arg(1),
}),
/**
* Request to remove XHR breakpoint
* @param string path
* @param string method
*/
removeXHRBreakpoint: DebuggerClient.requester({
type: "removeXHRBreakpoint",
path: arg(0),
method: arg(1),
}),
/**
* Request to get the set of available event breakpoints.
*/
getAvailableEventBreakpoints: DebuggerClient.requester({
type: "getAvailableEventBreakpoints",
}),
/**
* Request to get the IDs of the active event breakpoints.
*/
getActiveEventBreakpoints: DebuggerClient.requester({
type: "getActiveEventBreakpoints",
}),
/**
* Request to set the IDs of the active event breakpoints.
*/
setActiveEventBreakpoints: DebuggerClient.requester({
type: "setActiveEventBreakpoints",
ids: arg(0),
}),
/**
* Return an instance of SourceFront for the given source actor form.
*/
source: function(form) {
if (form.actor in this._threadGrips) {
return this._threadGrips[form.actor];
}
this._threadGrips[form.actor] = new SourceFront(this.client, form);
return this._threadGrips[form.actor];
},
events: ["newSource", "progress"],
};
EventEmitter.decorate(ThreadClient.prototype);
module.exports = ThreadClient;

Просмотреть файл

@ -8,7 +8,6 @@ DevToolsModules(
'connection-manager.js',
'constants.js',
'debugger-client.js',
'deprecated-thread-client.js',
'environment-client.js',
'event-source.js',
'long-string-client.js',

Просмотреть файл

@ -5,9 +5,9 @@
"use strict";
const { ThreadStateTypes } = require("devtools/shared/client/constants");
const { FrontClassWithSpec, registerFront } = require("devtools/shared/protocol");
const { threadSpec } = require("devtools/shared/specs/thread");
const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
const EventEmitter = require("devtools/shared/event-emitter");
const {ThreadStateTypes} = require("devtools/shared/client/constants");
loader.lazyRequireGetter(
this,
@ -30,42 +30,40 @@ loader.lazyRequireGetter(
* @param actor string
* The actor ID for this thread.
*/
class ThreadClient extends FrontClassWithSpec(threadSpec) {
constructor(client) {
super(client);
this.client = client;
this._pauseGrips = {};
this._threadGrips = {};
this._state = "paused";
this._beforePaused = this._beforePaused.bind(this);
this._beforeResumed = this._beforeResumed.bind(this);
this._beforeDetached = this._beforeDetached.bind(this);
this.before("paused", this._beforePaused);
this.before("resumed", this._beforeResumed);
this.before("detached", this._beforeDetached);
// Attribute name from which to retrieve the actorID out of the target actor's form
this.formAttributeName = "contextActor";
}
function ThreadClient(client, actor) {
this.client = client;
this._actor = actor;
this._pauseGrips = {};
this._threadGrips = {};
this.request = this.client.request;
}
ThreadClient.prototype = {
_state: "paused",
get state() {
return this._state;
}
},
get paused() {
return this._state === "paused";
}
},
_actor: null,
get actor() {
return this.actorID;
}
return this._actor;
},
_assertPaused(command) {
get _transport() {
return this.client._transport;
},
_assertPaused: function(command) {
if (!this.paused) {
throw Error(
command + " command sent while not paused. Currently " + this._state
);
}
}
},
/**
* Resume a paused thread. If the optional limit parameter is present, then
@ -80,94 +78,111 @@ class ThreadClient extends FrontClassWithSpec(threadSpec) {
* than proceeding forwards. This parameter has no effect if the
* server does not support rewinding.
*/
async _doResume(resumeLimit, rewind) {
this._assertPaused("resume");
_doResume: DebuggerClient.requester({
type: "resume",
resumeLimit: arg(0),
rewind: arg(1),
}, {
before: function(packet) {
this._assertPaused("resume");
// Put the client in a tentative "resuming" state so we can prevent
// further requests that should only be sent in the paused state.
this._previousState = this._state;
this._state = "resuming";
try {
await super.resume(resumeLimit, rewind);
} catch (e) {
if (this._state == "resuming") {
// Put the client in a tentative "resuming" state so we can prevent
// further requests that should only be sent in the paused state.
this._previousState = this._state;
this._state = "resuming";
return packet;
},
after: function(response) {
if (response.error && this._state == "resuming") {
// There was an error resuming, update the state to the new one
// reported by the server, if given (only on wrongState), otherwise
// reset back to the previous state.
if (e.state) {
this._state = ThreadStateTypes[e.state];
if (response.state) {
this._state = ThreadStateTypes[response.state];
} else {
this._state = this._previousState;
}
}
}
delete this._previousState;
return response;
},
}),
delete this._previousState;
}
/**
* Reconfigure the thread actor.
*
* @param object options
* A dictionary object of the new options to use in the thread actor.
*/
reconfigure: DebuggerClient.requester({
type: "reconfigure",
options: arg(0),
}),
/**
* Resume a paused thread.
*/
resume() {
resume: function() {
return this._doResume(null, false);
}
},
/**
* Resume then pause without stepping.
*
*/
resumeThenPause() {
resumeThenPause: function() {
return this._doResume({ type: "break" }, false);
}
},
/**
* Rewind a thread until a breakpoint is hit.
*/
rewind() {
this._doResume(null, true);
}
rewind: function() {
return this._doResume(null, true);
},
/**
* Step over a function call.
*/
stepOver() {
stepOver: function() {
return this._doResume({ type: "next" }, false);
}
},
/**
* Step into a function call.
*/
stepIn() {
stepIn: function() {
return this._doResume({ type: "step" }, false);
}
},
/**
* Step out of a function call.
*/
stepOut() {
stepOut: function() {
return this._doResume({ type: "finish" }, false);
}
},
/**
* Rewind step over a function call.
*/
reverseStepOver() {
reverseStepOver: function() {
return this._doResume({ type: "next" }, true);
}
},
/**
* Immediately interrupt a running thread.
*/
interrupt() {
interrupt: function() {
return this._doInterrupt(null);
}
},
/**
* Pause execution right before the next JavaScript bytecode is executed.
*/
breakOnNext() {
breakOnNext: function() {
return this._doInterrupt("onNext");
}
},
/**
* Warp through time to an execution point in the past or future.
@ -175,7 +190,7 @@ class ThreadClient extends FrontClassWithSpec(threadSpec) {
* @param object aTarget
* Description of the warp destination.
*/
timeWarp(target) {
timeWarp: function(target) {
const warp = () => {
this._doResume({ type: "warp", target }, true);
};
@ -183,21 +198,59 @@ class ThreadClient extends FrontClassWithSpec(threadSpec) {
return warp();
}
return this.interrupt().then(warp);
}
},
/**
* Interrupt a running thread.
*/
_doInterrupt(when) {
return super.interrupt(when);
}
_doInterrupt: DebuggerClient.requester({
type: "interrupt",
when: arg(0),
}),
/**
* Enable or disable pausing when an exception is thrown.
*
* @param boolean pauseOnExceptions
* Enables pausing if true, disables otherwise.
* @param boolean ignoreCaughtExceptions
* Whether to ignore caught exceptions
*/
pauseOnExceptions: DebuggerClient.requester({
type: "pauseOnExceptions",
pauseOnExceptions: arg(0),
ignoreCaughtExceptions: arg(1),
}),
/**
* Detach from the thread actor.
*/
detach: DebuggerClient.requester({
type: "detach",
}, {
after: function(response) {
this.client.unregisterClient(this);
return response;
},
}),
/**
* Promote multiple pause-lifetime object actors to thread-lifetime ones.
*
* @param array actors
* An array with actor IDs to promote.
*/
threadGrips: DebuggerClient.requester({
type: "threadGrips",
actors: arg(0),
}),
/**
* Request the loaded sources for the current thread.
*/
getSources() {
return super.sources();
}
getSources: DebuggerClient.requester({
type: "sources",
}),
/**
* Request frames from the callstack for the current thread.
@ -209,42 +262,31 @@ class ThreadClient extends FrontClassWithSpec(threadSpec) {
* The maximum number of frames to return, or null to return all
* frames.
*/
getFrames(start, count) {
return super.frames(start, count);
}
/**
* attach to the thread actor.
*/
async attach(options) {
let response;
try {
const onPaused = this.once("paused");
response = await super.attach(options);
await onPaused;
} catch (e) {
throw new Error(e);
}
return response;
}
getFrames: DebuggerClient.requester({
type: "frames",
start: arg(0),
count: arg(1),
}),
/**
* Detach from the thread actor.
* Toggle pausing via breakpoints in the server.
*
* @param skip boolean
* Whether the server should skip pausing via breakpoints
*/
async detach() {
const onDetached = this.once("detached");
await super.detach();
await onDetached;
await this.destroy();
}
skipBreakpoints: DebuggerClient.requester({
type: "skipBreakpoints",
skip: arg(0),
}),
/**
* Request the frame environment.
*
* @param frameId string
*/
getEnvironment(frameId) {
return this.client.request({ to: frameId, type: "getEnvironment" });
}
getEnvironment: function(frameId) {
return this.request({ to: frameId, type: "getEnvironment" });
},
/**
* Return a ObjectClient object for the given object grip.
@ -252,7 +294,7 @@ class ThreadClient extends FrontClassWithSpec(threadSpec) {
* @param grip object
* A pause-lifetime object grip returned by the protocol.
*/
pauseGrip(grip) {
pauseGrip: function(grip) {
if (grip.actor in this._pauseGrips) {
return this._pauseGrips[grip.actor];
}
@ -260,7 +302,7 @@ class ThreadClient extends FrontClassWithSpec(threadSpec) {
const client = new ObjectClient(this.client, grip);
this._pauseGrips[grip.actor] = client;
return client;
}
},
/**
* Clear and invalidate all the grip clients from the given cache.
@ -268,72 +310,120 @@ class ThreadClient extends FrontClassWithSpec(threadSpec) {
* @param gripCacheName
* The property name of the grip cache we want to clear.
*/
_clearObjectClients(gripCacheName) {
_clearObjectClients: function(gripCacheName) {
for (const id in this[gripCacheName]) {
this[gripCacheName][id].valid = false;
}
this[gripCacheName] = {};
}
},
/**
* Invalidate pause-lifetime grip clients and clear the list of current grip
* clients.
*/
_clearPauseGrips() {
_clearPauseGrips: function() {
this._clearObjectClients("_pauseGrips");
}
},
/**
* Invalidate thread-lifetime grip clients and clear the list of current grip
* clients.
*/
_clearThreadGrips() {
_clearThreadGrips: function() {
this._clearObjectClients("_threadGrips");
}
_beforePaused(packet) {
this._state = "paused";
this._onThreadState(packet);
}
_beforeResumed() {
this._state = "attached";
this._onThreadState(null);
}
_beforeDetached(packet) {
this._state = "detached";
this._onThreadState(packet);
this._clearThreadGrips();
}
},
/**
* Handle thread state change by doing necessary cleanup
* Handle thread state change by doing necessary cleanup and notifying all
* registered listeners.
*/
_onThreadState(packet) {
_onThreadState: function(packet) {
this._state = ThreadStateTypes[packet.type];
// The debugger UI may not be initialized yet so we want to keep
// the packet around so it knows what to pause state to display
// when it's initialized
this._lastPausePacket = packet;
this._lastPausePacket = packet.type === "resumed" ? null : packet;
this._clearPauseGrips();
}
packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
this.client._eventsEnabled && this.emit(packet.type, packet);
},
getLastPausePacket() {
getLastPausePacket: function() {
return this._lastPausePacket;
}
},
setBreakpoint: DebuggerClient.requester({
type: "setBreakpoint",
location: arg(0),
options: arg(1),
}),
removeBreakpoint: DebuggerClient.requester({
type: "removeBreakpoint",
location: arg(0),
}),
/**
* Requests to set XHR breakpoint
* @param string path
* pause when url contains `path`
* @param string method
* pause when method of request is `method`
*/
setXHRBreakpoint: DebuggerClient.requester({
type: "setXHRBreakpoint",
path: arg(0),
method: arg(1),
}),
/**
* Request to remove XHR breakpoint
* @param string path
* @param string method
*/
removeXHRBreakpoint: DebuggerClient.requester({
type: "removeXHRBreakpoint",
path: arg(0),
method: arg(1),
}),
/**
* Request to get the set of available event breakpoints.
*/
getAvailableEventBreakpoints: DebuggerClient.requester({
type: "getAvailableEventBreakpoints",
}),
/**
* Request to get the IDs of the active event breakpoints.
*/
getActiveEventBreakpoints: DebuggerClient.requester({
type: "getActiveEventBreakpoints",
}),
/**
* Request to set the IDs of the active event breakpoints.
*/
setActiveEventBreakpoints: DebuggerClient.requester({
type: "setActiveEventBreakpoints",
ids: arg(0),
}),
/**
* Return an instance of SourceFront for the given source actor form.
*/
source(form) {
source: function(form) {
if (form.actor in this._threadGrips) {
return this._threadGrips[form.actor];
}
this._threadGrips[form.actor] = new SourceFront(this.client, form);
return this._threadGrips[form.actor];
}
}
},
exports.ThreadClient = ThreadClient;
registerFront(ThreadClient);
events: ["newSource", "progress"],
};
EventEmitter.decorate(ThreadClient.prototype);
module.exports = ThreadClient;

Просмотреть файл

@ -19,17 +19,11 @@ const { ArrayBufferFront } = require("devtools/shared/fronts/array-buffer");
class SourceFront extends FrontClassWithSpec(sourceSpec) {
constructor(client, form) {
super(client);
if (form) {
this._url = form.url;
// this is here for the time being, until the source front is managed
// via protocol.js marshalling
this.actorID = form.actor;
this.manage(this);
}
}
form(json) {
this._url = json.url;
this._url = form.url;
// this is here for the time being, until the source front is managed
// via protocol.js marshalling
this.actorID = form.actor;
this.manage(this);
}
get actor() {

Просмотреть файл

@ -98,7 +98,6 @@ class BrowsingContextTargetFront extends
const response = await super.attach();
this._threadActor = response.threadActor;
this.targetForm.contextActor = this._threadActor;
this.configureOptions.javascriptEnabled = response.javascriptEnabled;
this.traits = response.traits || {};

Просмотреть файл

@ -21,7 +21,6 @@ class ContentProcessTargetFront extends
// Save the full form for Target class usage.
// Do not use `form` name to avoid colliding with protocol.js's `form` method
this.targetForm = json;
this.targetForm.contextActor = json.chromeDebugger;
this._threadActor = json.chromeDebugger;
}

Просмотреть файл

@ -10,7 +10,6 @@
// used by a subclass, specific to local tabs.
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/deprecated-thread-client");
loader.lazyRequireGetter(this, "getFront", "devtools/shared/protocol", true);
/**
@ -405,21 +404,15 @@ function TargetMixin(parentClass) {
"TargetMixin sub class should set _threadActor before calling " + "attachThread"
);
}
if (this.getTrait("hasThreadFront")) {
this.threadClient = await this.getFront("context");
} else {
// Backwards compat for Firefox 68
// mimics behavior of a front
this.threadClient = new ThreadClient(this._client, this._threadActor);
this.fronts.set("context", this.threadClient);
this.threadClient.actorID = this._threadActor;
this.manage(this.threadClient);
}
const result = await this.threadClient.attach(options);
const [response, threadClient] = await this._client.attachThread(
this._threadActor,
options
);
this.threadClient = threadClient;
this.threadClient.on("newSource", this._onNewSource);
return [result, this.threadClient];
return [response, threadClient];
}
// Listener for "newSource" event fired by the thread actor
@ -461,9 +454,7 @@ function TargetMixin(parentClass) {
*/
_teardownRemoteListeners() {
// Remove listeners set in _setupRemoteListeners
if (this.client) {
this.client.off("closed", this.destroy);
}
this.client.off("closed", this.destroy);
this.off("tabDetached", this.destroy);
// Remove listeners set in attachThread
@ -545,8 +536,6 @@ function TargetMixin(parentClass) {
this._teardownRemoteListeners();
this.threadClient = null;
if (this.isLocalTab) {
// We started with a local tab and created the client ourselves, so we
// should close it.
@ -566,6 +555,14 @@ function TargetMixin(parentClass) {
}
}
if (this.threadClient) {
try {
await this.threadClient.detach();
} catch (e) {
console.warn(`Error while detaching the thread front: ${e.message}`);
}
}
// Do that very last in order to let a chance to dispatch `detach` requests.
super.destroy();

Просмотреть файл

@ -51,9 +51,7 @@ class WorkerTargetFront extends
// that will be later used by Target.
const connectResponse = await this.connect({});
// Set the console actor ID on the form to expose it to Target.attachConsole
// Set the ThreadActor on the target form so it is accessible by getFront
this.targetForm.consoleActor = connectResponse.consoleActor;
this.targetForm.contextActor = connectResponse.threadActor;
this._threadActor = connectResponse.threadActor;
return this.attachConsole();

Просмотреть файл

@ -252,10 +252,11 @@ const Types = exports.__TypesForTests = [
spec: "devtools/shared/specs/targets/worker",
front: "devtools/shared/fronts/targets/worker",
},
/* Thread has an old fashion client and no front */
{
types: ["context"],
spec: "devtools/shared/specs/thread",
front: "devtools/shared/client/thread-client",
front: null,
},
{
types: ["console"],

Просмотреть файл

@ -18,18 +18,8 @@ const threadSpec = generateActorSpec({
typeName: "context",
events: {
paused: {
actor: Option(0, "nullable:string"),
frame: Option(0, "nullable:json"),
why: Option(0, "nullable:json"),
poppedFrames: Option(0, "nullable:json"),
error: Option(0, "nullable:json"),
},
resumed: {},
detached: {},
willInterrupt: {},
newSource: {
source: Option(0, "json"),
source: Option(0, "source"),
},
progress: {
recording: Option(0, "json"),
@ -45,7 +35,6 @@ const threadSpec = generateActorSpec({
response: RetVal("nullable:json"),
},
detach: {
request: {},
response: {},
},
reconfigure: {
@ -72,6 +61,7 @@ const threadSpec = generateActorSpec({
request: {
when: Arg(0, "json"),
},
response: RetVal("array:json"),
},
sources: {
response: RetVal("array:json"),

5
package-lock.json сгенерированный
Просмотреть файл

@ -1865,11 +1865,6 @@
"requires": {
"mkdirp": "^0.5.1"
}
},
"yarn": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.16.0.tgz",
"integrity": "sha512-cfemyGlnWKA1zopUUgebTPf8C4WkPIZ+TJmklwcEAJ4u6oWPtJeAzrsamaGGh/+b1XWe8W51yzAImC4AWbWR1g=="
}
}
}

Просмотреть файл

@ -3,9 +3,7 @@
"description": "This package file is for node modules used in mozilla-central",
"repository": {},
"license": "MPL-2.0",
"dependencies": {
"yarn": "^1.16.0"
},
"dependencies": {},
"devDependencies": {
"babel-eslint": "10.0.1",
"eslint": "5.16.0",