diff --git a/devtools/client/debugger/new/src/client/firefox/commands.js b/devtools/client/debugger/new/src/client/firefox/commands.js index 0f43d2af1f63..6eb99c3ee8db 100644 --- a/devtools/client/debugger/new/src/client/firefox/commands.js +++ b/devtools/client/debugger/new/src/client/firefox/commands.js @@ -187,15 +187,6 @@ function removeXHRBreakpoint(path: string, method: string) { return threadClient.removeXHRBreakpoint(path, method); } -// Source and breakpoint clients do not yet support an options structure, so -// for now we transform options into condition strings when setting breakpoints. -function transformOptionsToCondition(options) { - if (options.logValue) { - return `console.log(${options.logValue})`; - } - return options.condition; -} - function setBreakpoint( location: SourceActorLocation, options: BreakpointOptions, @@ -210,7 +201,7 @@ function setBreakpoint( .setBreakpoint({ line: location.line, column: location.column, - condition: transformOptionsToCondition(options), + options, noSliding }) .then(([{ actualLocation }, bpClient]) => { @@ -248,14 +239,18 @@ function setBreakpointOptions( ) { const id = makeBreakpointActorId(location); const bpClient = bpClients[id]; - delete bpClients[id]; - const sourceThreadClient = bpClient.source._activeThread; - return bpClient - .setCondition(sourceThreadClient, transformOptionsToCondition(options)) - .then(_bpClient => { - bpClients[id] = _bpClient; - }); + if (debuggerClient.mainRoot.traits.nativeLogpoints) { + bpClient.setOptions(options); + } else { + // Older server breakpoints destroy themselves when changing options. + delete bpClients[id]; + bpClient + .setOptions(options) + .then(_bpClient => { + bpClients[id] = _bpClient; + }); + } } async function evaluateInFrame(script: Script, options: EvaluateParam) { diff --git a/devtools/client/debugger/new/src/client/firefox/types.js b/devtools/client/debugger/new/src/client/firefox/types.js index e68eb8295439..1dd95b6e87cd 100644 --- a/devtools/client/debugger/new/src/client/firefox/types.js +++ b/devtools/client/debugger/new/src/client/firefox/types.js @@ -398,7 +398,7 @@ export type BreakpointClient = { line: number, column: ?number }, - setCondition: (ThreadClient, ?string) => Promise, + setOptions: (BreakpointOptions) => Promise, // request: any, source: SourceClient, options: BreakpointOptions diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js index f9beb45dd572..18d07ec99446 100644 --- a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js @@ -72,8 +72,9 @@ add_task(async function() { assertEditorBreakpoint(dbg, 5, true); // Edit the conditional breakpoint set above + const bpCondition1 = waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS"); await setConditionalBreakpoint(dbg, 5, "2"); - await waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS"); + await bpCondition1; bp = findBreakpoint(dbg, "simple2", 5); is(bp.options.condition, "12", "breakpoint is created with the condition"); assertEditorBreakpoint(dbg, 5, true); @@ -87,19 +88,20 @@ add_task(async function() { // Adding a condition to a breakpoint clickElement(dbg, "gutter", 5); await waitForDispatch(dbg, "ADD_BREAKPOINT"); + const bpCondition2 = waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS"); await setConditionalBreakpoint(dbg, 5, "1"); - await waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS"); + await bpCondition2; bp = findBreakpoint(dbg, "simple2", 5); is(bp.options.condition, "1", "breakpoint is created with the condition"); assertEditorBreakpoint(dbg, 5, true); - const bpCondition = waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS"); + const bpCondition3 = waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS"); //right click breakpoint in breakpoints list rightClickElement(dbg, "breakpointItem", 3) // select "remove condition"; selectContextMenuItem(dbg, selectors.breakpointContextMenu.removeCondition); - await bpCondition; + await bpCondition3; bp = findBreakpoint(dbg, "simple2", 5); is(bp.options.condition, undefined, "breakpoint condition removed"); }); diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js index 49ca9c53d961..f2848bf404b2 100644 --- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -468,6 +468,12 @@ Toolbox.prototype = { this._threadClient = await attachThread(this); await domReady; + // The web console is immediately loaded when replaying, so that the + // timeline will always be populated with generated messages. + if (this.target.isReplayEnabled()) { + await this.loadTool("webconsole"); + } + this.isReady = true; const framesPromise = this._listFrames(); diff --git a/devtools/client/webconsole/webconsole-connection-proxy.js b/devtools/client/webconsole/webconsole-connection-proxy.js index c9ee9b255ba5..2cf3412878f4 100644 --- a/devtools/client/webconsole/webconsole-connection-proxy.js +++ b/devtools/client/webconsole/webconsole-connection-proxy.js @@ -30,6 +30,7 @@ function WebConsoleConnectionProxy(webConsoleFrame, target) { this._onPageError = this._onPageError.bind(this); this._onLogMessage = this._onLogMessage.bind(this); this._onConsoleAPICall = this._onConsoleAPICall.bind(this); + this._onVirtualConsoleLog = this._onVirtualConsoleLog.bind(this); this._onNetworkEvent = this._onNetworkEvent.bind(this); this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); this._onTabNavigated = this._onTabNavigated.bind(this); @@ -127,6 +128,8 @@ WebConsoleConnectionProxy.prototype = { client.addListener("consoleAPICall", this._onConsoleAPICall); client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited); + client.addListener("virtualConsoleLog", + this._onVirtualConsoleLog); this.target.on("will-navigate", this._onTabWillNavigate); this.target.on("navigate", this._onTabNavigated); @@ -310,6 +313,20 @@ WebConsoleConnectionProxy.prototype = { } this.dispatchMessageAdd(packet); }, + + _onVirtualConsoleLog: function(type, packet) { + if (!this.webConsoleFrame) { + return; + } + this.dispatchMessageAdd({ + type: "consoleAPICall", + message: { + executionPoint: packet.executionPoint, + "arguments": [packet.url + ":" + packet.line, packet.message], + }, + }); + }, + /** * The "networkEvent" message type handler. We redirect any message to * the UI for displaying. @@ -423,6 +440,8 @@ WebConsoleConnectionProxy.prototype = { this.client.removeListener("consoleAPICall", this._onConsoleAPICall); this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited); + this.client.removeListener("virtualConsoleLog", + this._onVirtualConsoleLog); this.webConsoleClient.off("networkEvent", this._onNetworkEvent); this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate); this.target.off("will-navigate", this._onTabWillNavigate); diff --git a/devtools/server/actors/breakpoint.js b/devtools/server/actors/breakpoint.js index c5a86e4faea7..e9a34d8b0125 100644 --- a/devtools/server/actors/breakpoint.js +++ b/devtools/server/actors/breakpoint.js @@ -22,10 +22,7 @@ const { breakpointSpec } = require("devtools/shared/specs/breakpoint"); */ function setBreakpointAtEntryPoints(actor, entryPoints) { for (const { script, offsets } of entryPoints) { - actor.addScript(script); - for (const offset of offsets) { - script.setBreakpoint(offset, actor); - } + actor.addScript(script, offsets); } } @@ -46,16 +43,25 @@ const BreakpointActor = ActorClassWithSpec(breakpointSpec, { * The generated location of the breakpoint. */ initialize: function(threadActor, generatedLocation) { - // The set of Debugger.Script instances that this breakpoint has been set - // upon. - this.scripts = new Set(); + // A map from Debugger.Script instances to the offsets which the breakpoint + // has been set for in that script. + this.scripts = new Map(); this.threadActor = threadActor; this.generatedLocation = generatedLocation; - this.condition = null; + this.options = null; this.isPending = true; }, + // Called when new breakpoint options are received from the client. + setOptions(options) { + for (const [script, offsets] of this.scripts) { + this._updateOptionsForScript(script, offsets, this.options, options); + } + + this.options = options; + }, + destroy: function() { this.removeScripts(); }, @@ -70,22 +76,58 @@ const BreakpointActor = ActorClassWithSpec(breakpointSpec, { * * @param script Debugger.Script * The new source script on which the breakpoint has been set. + * @param offsets Array + * Any offsets in the script the breakpoint is associated with. */ - addScript: function(script) { - this.scripts.add(script); + addScript: function(script, offsets) { + this.scripts.set(script, offsets.concat(this.scripts.get(offsets) || [])); + for (const offset of offsets) { + script.setBreakpoint(offset, this); + } + this.isPending = false; + this._updateOptionsForScript(script, offsets, null, this.options); }, /** * Remove the breakpoints from associated scripts and clear the script cache. */ removeScripts: function() { - for (const script of this.scripts) { + for (const [script, offsets] of this.scripts) { + this._updateOptionsForScript(script, offsets, this.options, null); script.clearBreakpoint(this); } this.scripts.clear(); }, + // Update any state affected by changing options on a script this breakpoint + // is associated with. + _updateOptionsForScript(script, offsets, oldOptions, newOptions) { + if (this.threadActor.dbg.replaying) { + // When replaying, logging breakpoints are handled using an API to get logged + // messages from throughout the recording. + const oldLogValue = oldOptions && oldOptions.logValue; + const newLogValue = newOptions && newOptions.logValue; + if (oldLogValue != newLogValue) { + for (const offset of offsets) { + const { lineNumber, columnNumber } = script.getOffsetLocation(offset); + script.replayVirtualConsoleLog(offset, newLogValue, (point, rv) => { + const packet = { + from: this.actorID, + type: "virtualConsoleLog", + url: script.url, + line: lineNumber, + column: columnNumber, + executionPoint: point, + message: "return" in rv ? "" + rv.return : "" + rv.throw, + }; + this.conn.send(packet); + }); + } + } + } + }, + /** * Check if this breakpoint has a condition that doesn't error and * evaluates to true in frame. @@ -100,8 +142,8 @@ const BreakpointActor = ActorClassWithSpec(breakpointSpec, { * - message: string * If the condition throws, this is the thrown message. */ - checkCondition: function(frame) { - const completion = frame.eval(this.condition); + checkCondition: function(frame, condition) { + const completion = frame.eval(condition); if (completion) { if (completion.throw) { // The evaluation failed and threw @@ -162,15 +204,29 @@ const BreakpointActor = ActorClassWithSpec(breakpointSpec, { } const reason = {}; + const { condition, logValue } = this.options || {}; if (this.threadActor._hiddenBreakpoints.has(this.actorID)) { reason.type = "pauseOnDOMEvents"; - } else if (!this.condition) { + } else if (!condition && !logValue) { reason.type = "breakpoint"; // TODO: add the rest of the breakpoints on that line (bug 676602). reason.actors = [ this.actorID ]; } else { - const { result, message } = this.checkCondition(frame); + // When replaying, breakpoints with log values are handled separately. + if (logValue && this.threadActor.dbg.replaying) { + return undefined; + } + + let condstr = condition; + if (logValue) { + // In the non-replaying case, log values are handled by treating them as + // conditions. console.log() never returns true so we will not pause. + condstr = condition + ? `(${condition}) && console.log(${logValue})` + : `console.log(${logValue})`; + } + const { result, message } = this.checkCondition(frame, condstr); if (result) { if (!message) { diff --git a/devtools/server/actors/replay/control.js b/devtools/server/actors/replay/control.js index 1a1313bff029..84378d663e05 100644 --- a/devtools/server/actors/replay/control.js +++ b/devtools/server/actors/replay/control.js @@ -691,6 +691,9 @@ function flushRecording() { } } + // After flushing the recording there may be more search results. + maybeResumeSearch(); + gLastRecordingCheckpoint = gActiveChild.lastCheckpoint(); // We now have a usable recording for replaying children. @@ -988,16 +991,92 @@ const gControl = { timeWarp, }; -// eslint-disable-next-line no-unused-vars -function ConnectDebugger(dbg) { - gDebugger = dbg; - dbg._control = gControl; +//////////////////////////////////////////////////////////////////////////////// +// Search Operations +//////////////////////////////////////////////////////////////////////////////// + +let gSearchChild; +let gSearchRestartNeeded; + +function maybeRestartSearch() { + if (gSearchRestartNeeded && gSearchChild.paused) { + if (gSearchChild.lastPausePoint.checkpoint != FirstCheckpointId || + gSearchChild.lastPausePoint.position) { + gSearchChild.sendRestoreCheckpoint(FirstCheckpointId); + gSearchChild.waitUntilPaused(); + } + gSearchChild.sendClearBreakpoints(); + gDebugger._forEachSearch(pos => gSearchChild.sendAddBreakpoint(pos)); + gSearchRestartNeeded = false; + gSearchChild.sendResume({ forward: true }); + return true; + } + return false; } +function ChildRoleSearch() {} + +ChildRoleSearch.prototype = { + name: "Search", + + initialize(child, { startup }) { + this.child = child; + }, + + hitExecutionPoint({ point, recordingEndpoint }) { + if (maybeRestartSearch()) { + return; + } + + if (point.position) { + gDebugger._onSearchPause(point); + } + + if (!recordingEndpoint) { + this.poke(); + } + }, + + poke() { + if (!gSearchRestartNeeded && !this.child.pauseNeeded) { + this.child.sendResume({ forward: true }); + } + }, +}; + +function ensureHasSearchChild() { + if (!gSearchChild) { + gSearchChild = spawnReplayingChild(new ChildRoleSearch()); + } +} + +function maybeResumeSearch() { + if (gSearchChild && gSearchChild.paused) { + gSearchChild.sendResume({ forward: true }); + } +} + +const gSearchControl = { + reset() { + ensureHasSearchChild(); + gSearchRestartNeeded = true; + maybeRestartSearch(); + }, + + sendRequest(request) { return gSearchChild.sendDebuggerRequest(request); }, +}; + /////////////////////////////////////////////////////////////////////////////// // Utilities /////////////////////////////////////////////////////////////////////////////// +// eslint-disable-next-line no-unused-vars +function ConnectDebugger(dbg) { + gDebugger = dbg; + dbg._control = gControl; + dbg._searchControl = gSearchControl; +} + function dumpv(str) { //dump("[ReplayControl] " + str + "\n"); } diff --git a/devtools/server/actors/replay/debugger.js b/devtools/server/actors/replay/debugger.js index b5c10d5b5d6e..4bc3ad06052c 100644 --- a/devtools/server/actors/replay/debugger.js +++ b/devtools/server/actors/replay/debugger.js @@ -42,6 +42,7 @@ function ReplayDebugger() { // We should have been connected to control.js by the call above. assert(this._control); + assert(this._searchControl); // Preferred direction of travel when not explicitly resumed. this._direction = Direction.NONE; @@ -75,6 +76,9 @@ function ReplayDebugger() { // After we are done pausing, callback describing how to resume. this._resumeCallback = null; + // Information about all searches that exist. + this._searches = []; + // Handler called when hitting the beginning/end of the recording, or when // a time warp target has been reached. this.replayingOnForcedPause = null; @@ -348,6 +352,47 @@ ReplayDebugger.prototype = { this._objects.length = 0; }, + ///////////////////////////////////////////////////////// + // Search management + ///////////////////////////////////////////////////////// + + _forEachSearch(callback) { + for (const { position } of this._searches) { + callback(position); + } + }, + + _virtualConsoleLog(position, text, callback) { + this._searches.push({ position, text, callback, results: [] }); + this._searchControl.reset(); + }, + + _onSearchPause(point) { + for (const { position, text, callback, results } of this._searches) { + if (RecordReplayControl.positionSubsumes(position, point.position)) { + if (!results.some(existing => point.progress == existing.progress)) { + let evaluateResult; + if (text) { + const frameData = this._searchControl.sendRequest({ + type: "getFrame", + index: NewestFrameIndex, + }); + if ("index" in frameData) { + const rv = this._searchControl.sendRequest({ + type: "frameEvaluate", + index: frameData.index, + text, + }); + evaluateResult = this._convertCompletionValue(rv, { forSearch: true }); + } + } + results.push(point); + callback(point, evaluateResult); + } + } + } + }, + ///////////////////////////////////////////////////////// // Breakpoint management ///////////////////////////////////////////////////////// @@ -485,14 +530,20 @@ ReplayDebugger.prototype = { // Object methods ///////////////////////////////////////////////////////// - // Objects which |forConsole| is set are objects that were logged in console - // messages, and had their properties recorded so that they can be inspected - // without switching to a replaying child. - _getObject(id, forConsole) { + _getObject(id, options) { + if (options && options.forSearch) { + // Returning objects through searches is NYI. + return ""; + } + const forConsole = options && options.forConsole; + if (id && !this._objects[id]) { const data = this._sendRequest({ type: "getObject", id }); switch (data.kind) { case "Object": + // Objects which |forConsole| is set are objects that were logged in + // console messages, and had their properties recorded so that they can + // be inspected without switching to a replaying child. this._objects[id] = new ReplayDebuggerObject(this, data, forConsole); break; case "Environment": @@ -509,10 +560,10 @@ ReplayDebugger.prototype = { return rv; }, - _convertValue(value, forConsole) { + _convertValue(value, options) { if (isNonNullObject(value)) { if (value.object) { - return this._getObject(value.object, forConsole); + return this._getObject(value.object, options); } else if (value.special == "undefined") { return undefined; } else if (value.special == "NaN") { @@ -526,12 +577,12 @@ ReplayDebugger.prototype = { return value; }, - _convertCompletionValue(value) { + _convertCompletionValue(value, options) { if ("return" in value) { - return { return: this._convertValue(value.return) }; + return { return: this._convertValue(value.return, options) }; } if ("throw" in value) { - return { throw: this._convertValue(value.throw) }; + return { throw: this._convertValue(value.throw, options) }; } ThrowError("Unexpected completion value"); return null; // For eslint @@ -582,7 +633,7 @@ ReplayDebugger.prototype = { if (message.messageType == "ConsoleAPI" && message.arguments) { for (let i = 0; i < message.arguments.length; i++) { message.arguments[i] = this._convertValue(message.arguments[i], - /* forConsole = */ true); + { forConsole: true }); } } return message; @@ -662,6 +713,7 @@ ReplayDebuggerScript.prototype = { get format() { return this._data.format; }, _forward(type, value) { + this._dbg._ensurePaused(); return this._dbg._sendRequest({ type, id: this._data.id, value }); }, @@ -683,6 +735,11 @@ ReplayDebuggerScript.prototype = { }); }, + replayVirtualConsoleLog(offset, text, callback) { + this._dbg._virtualConsoleLog({ kind: "Break", script: this._data.id, offset }, + text, callback); + }, + get isGeneratorFunction() { NYI(); }, get isAsyncFunction() { NYI(); }, getChildScripts: NYI, diff --git a/devtools/server/actors/root.js b/devtools/server/actors/root.js index cb9b7d273266..670c6805750f 100644 --- a/devtools/server/actors/root.js +++ b/devtools/server/actors/root.js @@ -170,6 +170,9 @@ RootActor.prototype = { // `front.startProfiler`. This is an optional parameter but it will throw an error if // the profiled Firefox doesn't accept it. perfActorVersion: 1, + // Supports native log points and modifying the condition/log of an existing + // breakpoints. Fx66+ + nativeLogpoints: true, }, /** diff --git a/devtools/server/actors/source.js b/devtools/server/actors/source.js index 02fa3f38f1e7..62aece7b997e 100644 --- a/devtools/server/actors/source.js +++ b/devtools/server/actors/source.js @@ -559,8 +559,8 @@ const SourceActor = ActorClassWithSpec(sourceSpec, { * Line to break on. * @param Number column * Column to break on. - * @param String condition - * A condition which must be true for breakpoint to be hit. + * @param Object options + * Any options for the breakpoint. * @param Boolean noSliding * If true, disables breakpoint sliding. * @@ -568,11 +568,11 @@ const SourceActor = ActorClassWithSpec(sourceSpec, { * A promise that resolves to a JSON object representing the * response. */ - setBreakpoint: function(line, column, condition, noSliding) { + setBreakpoint: function(line, column, options, noSliding) { const location = new GeneratedLocation(this, line, column); const actor = this._getOrCreateBreakpointActor( location, - condition, + options, noSliding ); @@ -597,16 +597,15 @@ const SourceActor = ActorClassWithSpec(sourceSpec, { * @param GeneratedLocation generatedLocation * A GeneratedLocation representing the location of the breakpoint in * the generated source. - * @param String condition - * A string that is evaluated whenever the breakpoint is hit. If the - * string evaluates to false, the breakpoint is ignored. + * @param Object options + * Any options for the breakpoint. * @param Boolean noSliding * If true, disables breakpoint sliding. * * @returns BreakpointActor * A BreakpointActor representing the breakpoint. */ - _getOrCreateBreakpointActor: function(generatedLocation, condition, noSliding) { + _getOrCreateBreakpointActor: function(generatedLocation, options, noSliding) { let actor = this.breakpointActorMap.getActor(generatedLocation); if (!actor) { actor = new BreakpointActor(this.threadActor, generatedLocation); @@ -614,7 +613,7 @@ const SourceActor = ActorClassWithSpec(sourceSpec, { this.breakpointActorMap.setActor(generatedLocation, actor); } - actor.condition = condition; + actor.setOptions(options); return this._setBreakpoint(actor, noSliding); }, diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-01.js b/devtools/server/tests/unit/test_conditional_breakpoint-01.js index 4372f7e5cec1..b643597ac53a 100644 --- a/devtools/server/tests/unit/test_conditional_breakpoint-01.js +++ b/devtools/server/tests/unit/test_conditional_breakpoint-01.js @@ -27,6 +27,8 @@ function run_test() { } function test_simple_breakpoint() { + let hitBreakpoint = false; + gThreadClient.addOneTimeListener("paused", async function(event, packet) { const source = await getSourceById( gThreadClient, @@ -34,9 +36,12 @@ function test_simple_breakpoint() { ); source.setBreakpoint({ line: 3, - condition: "a === 1", + options: { condition: "a === 1" }, }).then(function([response, bpClient]) { gThreadClient.addOneTimeListener("paused", function(event, packet) { + Assert.equal(hitBreakpoint, false); + hitBreakpoint = true; + // Check the return value. Assert.equal(packet.why.type, "breakpoint"); Assert.equal(packet.frame.where.line, 3); @@ -62,4 +67,6 @@ function test_simple_breakpoint() { "test.js", 1); /* eslint-enable */ + + Assert.equal(hitBreakpoint, true); } diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-02.js b/devtools/server/tests/unit/test_conditional_breakpoint-02.js index 0707436de414..d9d7203226ef 100644 --- a/devtools/server/tests/unit/test_conditional_breakpoint-02.js +++ b/devtools/server/tests/unit/test_conditional_breakpoint-02.js @@ -32,13 +32,17 @@ function test_simple_breakpoint() { gThreadClient, packet.frame.where.actor ); - source.setBreakpoint({ + await source.setBreakpoint({ line: 3, - condition: "a === 2", + options: { condition: "a === 2" }, + }); + source.setBreakpoint({ + line: 4, + options: { condition: "a === 1" }, }).then(function([response, bpClient]) { gThreadClient.addOneTimeListener("paused", function(event, packet) { // Check the return value. - Assert.equal(packet.why.type, "debuggerStatement"); + Assert.equal(packet.why.type, "breakpoint"); Assert.equal(packet.frame.where.line, 4); // Remove the breakpoint. @@ -57,7 +61,8 @@ function test_simple_breakpoint() { Cu.evalInSandbox("debugger;\n" + // 1 "var a = 1;\n" + // 2 "var b = 2;\n" + // 3 - "debugger;", // 4 + "b++;" + // 4 + "debugger;", // 5 gDebuggee, "1.8", "test.js", diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-03.js b/devtools/server/tests/unit/test_conditional_breakpoint-03.js index 33d0cc33e974..4e1848c63992 100644 --- a/devtools/server/tests/unit/test_conditional_breakpoint-03.js +++ b/devtools/server/tests/unit/test_conditional_breakpoint-03.js @@ -34,7 +34,7 @@ function test_simple_breakpoint() { ); source.setBreakpoint({ line: 3, - condition: "throw new Error()", + options: { condition: "throw new Error()" }, }).then(function([response, bpClient]) { gThreadClient.addOneTimeListener("paused", function(event, packet) { // Check the return value. diff --git a/devtools/server/tests/unit/test_logpoint-01.js b/devtools/server/tests/unit/test_logpoint-01.js new file mode 100644 index 000000000000..dbf28263567f --- /dev/null +++ b/devtools/server/tests/unit/test_logpoint-01.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable no-shadow, max-nested-callbacks */ + +"use strict"; + +/** + * Check that logpoints call console.log. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-logpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function() { + attachTestTabAndResume(gClient, "test-logpoint", + function(response, targetFront, threadClient) { + gThreadClient = threadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() { + gThreadClient.addOneTimeListener("paused", async function(event, packet) { + const source = await getSourceById( + gThreadClient, + packet.frame.where.actor + ); + + // Set a logpoint which should invoke console.log. + await source.setBreakpoint({ + line: 4, + options: { logValue: "a" }, + }); + + // Execute the rest of the code. + gThreadClient.resume(); + }); + + // Sandboxes don't have a console available so we add our own. + /* eslint-disable */ + Cu.evalInSandbox("var console = { log: v => { this.logValue = v } };\n" + // 1 + "debugger;\n" + // 2 + "var a = 'three';\n" + // 3 + "var b = 2;\n", // 4 + gDebuggee, + "1.8", + "test.js", + 1); + /* eslint-enable */ + + Assert.equal(gDebuggee.logValue, "three"); + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/test_logpoint-02.js b/devtools/server/tests/unit/test_logpoint-02.js new file mode 100644 index 000000000000..53e277766359 --- /dev/null +++ b/devtools/server/tests/unit/test_logpoint-02.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable no-shadow, max-nested-callbacks */ + +"use strict"; + +/** + * Check that conditions are respected when specified in a logpoint. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() { + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-logpoint"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect().then(function() { + attachTestTabAndResume(gClient, "test-logpoint", + function(response, targetFront, threadClient) { + gThreadClient = threadClient; + test_simple_breakpoint(); + }); + }); + do_test_pending(); +} + +function test_simple_breakpoint() { + gThreadClient.addOneTimeListener("paused", async function(event, packet) { + const source = await getSourceById( + gThreadClient, + packet.frame.where.actor + ); + + // Set a logpoint which should invoke console.log. + await source.setBreakpoint({ + line: 5, + options: { logValue: "a", condition: "a === 5" }, + }); + + // Execute the rest of the code. + gThreadClient.resume(); + }); + + // Sandboxes don't have a console available so we add our own. + /* eslint-disable */ + Cu.evalInSandbox("var console = { log: v => { this.logValue = v } };\n" + // 1 + "debugger;\n" + // 2 + "var a = 1;\n" + // 3 + "while (a < 10) {\n" + // 4 + " a++;\n" + // 5 + "}", + gDebuggee, + "1.8", + "test.js", + 1); + /* eslint-enable */ + + Assert.equal(gDebuggee.logValue, 5); + finishClient(gClient); +} diff --git a/devtools/server/tests/unit/xpcshell.ini b/devtools/server/tests/unit/xpcshell.ini index d4a3c19f0574..494e7bafaa8f 100644 --- a/devtools/server/tests/unit/xpcshell.ini +++ b/devtools/server/tests/unit/xpcshell.ini @@ -134,6 +134,8 @@ reason = bug 1104838 [test_conditional_breakpoint-01.js] [test_conditional_breakpoint-02.js] [test_conditional_breakpoint-03.js] +[test_logpoint-01.js] +[test_logpoint-02.js] [test_listsources-01.js] [test_listsources-02.js] [test_listsources-03.js] diff --git a/devtools/shared/client/breakpoint-client.js b/devtools/shared/client/breakpoint-client.js index 16dcc2c09dad..c97ce321904f 100644 --- a/devtools/shared/client/breakpoint-client.js +++ b/devtools/shared/client/breakpoint-client.js @@ -7,7 +7,7 @@ const promise = require("devtools/shared/deprecated-sync-thenables"); const eventSource = require("devtools/shared/client/event-source"); -const {DebuggerClient} = require("devtools/shared/client/debugger-client"); +const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client"); /** * Breakpoint clients are used to remove breakpoints that are no longer used. @@ -21,10 +21,10 @@ const {DebuggerClient} = require("devtools/shared/client/debugger-client"); * @param location object * The location of the breakpoint. This is an object with two properties: * url and line. - * @param condition string - * The conditional expression of the breakpoint + * @param options object + * Any options associated with the breakpoint */ -function BreakpointClient(client, sourceClient, actor, location, condition) { +function BreakpointClient(client, sourceClient, actor, location, options) { this._client = client; this._actor = actor; this.location = location; @@ -32,11 +32,7 @@ function BreakpointClient(client, sourceClient, actor, location, condition) { this.location.url = sourceClient.url; this.source = sourceClient; this.request = this._client.request; - - // The condition property should only exist if it's a truthy value - if (condition) { - this.condition = condition; - } + this.options = options; } BreakpointClient.prototype = { @@ -56,32 +52,46 @@ BreakpointClient.prototype = { type: "delete", }), + // Send a setOptions request to newer servers. + setOptionsRequester: DebuggerClient.requester({ + type: "setOptions", + options: arg(0), + }, { + before(packet) { + this.options = packet.options; + return packet; + }, + }), + /** - * Set the condition of this breakpoint + * Set any options for this breakpoint. */ - setCondition: function(gThreadClient, condition) { - const deferred = promise.defer(); + setOptions: function(options) { + if (this._client.mainRoot.traits.nativeLogpoints) { + this.setOptionsRequester(options); + } else { + // Older servers need to reinstall breakpoints when the condition changes. + const deferred = promise.defer(); - const info = { - line: this.location.line, - column: this.location.column, - condition: condition, - }; + const info = { + line: this.location.line, + column: this.location.column, + options, + }; - // Remove the current breakpoint and add a new one with the - // condition. - this.remove(response => { - if (response && response.error) { - deferred.reject(response); - return; - } + // Remove the current breakpoint and add a new one with the specified + // information. + this.remove(response => { + if (response && response.error) { + deferred.reject(response); + return; + } - deferred.resolve(this.source.setBreakpoint(info).then(([, newBreakpoint]) => { - return newBreakpoint; - })); - }); - - return deferred.promise; + deferred.resolve(this.source.setBreakpoint(info).then(([, newBreakpoint]) => { + return newBreakpoint; + })); + }); + } }, }; diff --git a/devtools/shared/client/constants.js b/devtools/shared/client/constants.js index 131c2fa0bb2b..f584a7f6e94f 100644 --- a/devtools/shared/client/constants.js +++ b/devtools/shared/client/constants.js @@ -37,6 +37,7 @@ const UnsolicitedNotifications = { "evaluationResult": "evaluationResult", "updatedSource": "updatedSource", "inspectObject": "inspectObject", + "virtualConsoleLog": "virtualConsoleLog", // newSource is still emitted on the ThreadActor, in addition to the // BrowsingContextActor we have to keep it here until ThreadClient is converted to diff --git a/devtools/shared/client/source-client.js b/devtools/shared/client/source-client.js index 469b7b817030..89d84460b66e 100644 --- a/devtools/shared/client/source-client.js +++ b/devtools/shared/client/source-client.js @@ -175,10 +175,10 @@ SourceClient.prototype = { * Request to set a breakpoint in the specified location. * * @param object location - * The location and condition of the breakpoint in - * the form of { line[, column, condition] }. + * The location and options of the breakpoint in + * the form of { line[, column, options] }. */ - setBreakpoint: function({ line, column, condition, noSliding }) { + setBreakpoint: function({ line, column, options, noSliding }) { // A helper function that sets the breakpoint. const doSetBreakpoint = callback => { const location = { @@ -190,10 +190,23 @@ SourceClient.prototype = { to: this.actor, type: "setBreakpoint", location, - condition, + options, noSliding, }; + // Older servers only support conditions, not a more general options + // object. Transform the packet to support the older format. + if (options && !this._client.mainRoot.traits.nativeLogpoints) { + delete packet.options; + if (options.logValue) { + // Emulate log points by setting a condition with a call to console.log, + // which always returns false so the server will never pause. + packet.condition = `console.log(${options.logValue})`; + } else { + packet.condition = options.condition; + } + } + return this._client.request(packet).then(response => { // Ignoring errors, since the user may be setting a breakpoint in a // dead script that will reappear on a page reload. @@ -204,7 +217,7 @@ SourceClient.prototype = { this, response.actor, location, - condition + options ); } if (callback) { diff --git a/devtools/shared/specs/breakpoint.js b/devtools/shared/specs/breakpoint.js index f1b641a5f763..b8e52262839f 100644 --- a/devtools/shared/specs/breakpoint.js +++ b/devtools/shared/specs/breakpoint.js @@ -3,13 +3,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -const {generateActorSpec} = require("devtools/shared/protocol"); +const {Arg, generateActorSpec} = require("devtools/shared/protocol"); const breakpointSpec = generateActorSpec({ typeName: "breakpoint", methods: { delete: {}, + + setOptions: { + request: { + options: Arg(0, "nullable:json"), + }, + }, }, }); diff --git a/devtools/shared/specs/source.js b/devtools/shared/specs/source.js index a10966780e85..fdbcb2ffcdf0 100644 --- a/devtools/shared/specs/source.js +++ b/devtools/shared/specs/source.js @@ -61,7 +61,7 @@ const sourceSpec = generateActorSpec({ line: Arg(0, "number"), column: Arg(1, "nullable:number"), }, - condition: Arg(2, "nullable:string"), + options: Arg(2, "nullable:json"), noSliding: Arg(3, "nullable:boolean"), }, response: RetVal("json"), diff --git a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic index 18748919b190..7398b079d93a 100644 --- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic +++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic @@ -1,4 +1,4 @@ -52987 +53000 0/nm 0th/pt 1/n1 @@ -15892,8 +15892,10 @@ backslider/M backspace/DSMG backspin/M backsplash/S +backstab/S backstabber/MS backstabbing +backstabby backstage/M backstair/S backstop/SM @@ -17311,6 +17313,7 @@ bomber/M bombproof bombshell/SM bombsite/S +bon/S bona bonanza/MS bonbon/MS @@ -20154,6 +20157,8 @@ colloquy/M collude/DSG collusion/M collusive +colocate/DGS +colocation/S cologne/SM colon/SM colonel/SM @@ -21241,7 +21246,7 @@ cosmonaut/SM cosmopolitan/MS cosmopolitanism/M cosmos/MS -cosplay +cosplay/DGSRZ cosponsor/GSMD cosset/SGD cossetted @@ -21846,6 +21851,7 @@ cullender/MS culminate/XDSGN culmination/M culotte/SM +culpa/S culpability/M culpable/I culpably @@ -25747,6 +25753,7 @@ extolled extolling extort/SGD extortion/MRZ +extortionary extortionate/Y extortioner/M extortionist/MS @@ -27980,7 +27987,7 @@ geometry/SM geophysical geophysicist/SM geophysics/M -geopolitical +geopolitical/Y geopolitics/M geostationary geosynchronous @@ -34613,6 +34620,7 @@ mazurka/MS maņana/M mdse me/DSH +mea mead/M meadow/MS meadowlark/MS @@ -39822,6 +39830,7 @@ pivot/MDGS pivotal pix/M pixel/MS +pixelate/DS pixie/MS pizazz/M pizza/MS @@ -43857,7 +43866,7 @@ scalar/S scalawag/MS scald/MDSG scale's -scale/CGDS +scale/CGDSB scaleless scalene scaliness/M @@ -47076,6 +47085,7 @@ stony/TRP stood stooge/MS stool/SM +stoolie/SM stoop/GSMD stop's stop/US @@ -47765,6 +47775,7 @@ supertanker/MS superuser/S supervene/GDS supervention/M +supervillain/SM supervise/XGNDS supervised/U supervision/M @@ -51476,6 +51487,7 @@ vivace vivacious/PY vivaciousness/M vivacity/M +vivant/S vivaria vivarium/SM vivid/RYTP @@ -52389,6 +52401,7 @@ wishbone/SM wisher/M wishful/Y wishlist's +wishy-washy wisp/MS wispy/RT wist diff --git a/gfx/layers/wr/ClipManager.cpp b/gfx/layers/wr/ClipManager.cpp index 807214aff879..a136189f15aa 100644 --- a/gfx/layers/wr/ClipManager.cpp +++ b/gfx/layers/wr/ClipManager.cpp @@ -221,13 +221,6 @@ wr::WrSpaceAndClipChain ClipManager::SwitchItem( // for it. clips.mClipChainId = DefineClipChain(clip, auPerDevPixel, aStackingContext); - // If we didn't define a clip chain, inherit one from the stack. Eventually - // we should ensure stacking contexts always have a valid clip chain, and - // that should eliminate the need for this. - if (clips.mClipChainId.isNothing() && !mItemClipStack.empty()) { - clips.mClipChainId = mItemClipStack.top().mClipChainId; - } - Maybe spaceAndClip = GetScrollLayer(asr); MOZ_ASSERT(spaceAndClip.isSome()); clips.mScrollId = SpatialIdAfterOverride(spaceAndClip->space); @@ -363,11 +356,22 @@ Maybe ClipManager::DefineClipChain( CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id); } - if (clipIds.Length() == 0) { - return Nothing(); + // Now find the parent display item's clipchain id + Maybe parentChainId; + if (!mItemClipStack.empty()) { + parentChainId = mItemClipStack.top().mClipChainId; } - return Some(mBuilder->DefineClipChain(clipIds)); + // And define the current display item's clipchain using the clips and the + // parent. If the current item has no clips of its own, just use the parent + // item's clipchain. + Maybe chainId; + if (clipIds.Length() > 0) { + chainId = Some(mBuilder->DefineClipChain(parentChainId, clipIds)); + } else { + chainId = parentChainId; + } + return chainId; } ClipManager::~ClipManager() { diff --git a/gfx/tests/reftest/1523776-ref.html b/gfx/tests/reftest/1523776-ref.html deleted file mode 100644 index 3f57f8df244a..000000000000 --- a/gfx/tests/reftest/1523776-ref.html +++ /dev/null @@ -1,13 +0,0 @@ - -
-
-
-
-
- -
-
diff --git a/gfx/tests/reftest/1523776.html b/gfx/tests/reftest/1523776.html deleted file mode 100644 index 76a666763df2..000000000000 --- a/gfx/tests/reftest/1523776.html +++ /dev/null @@ -1,12 +0,0 @@ - -
-
-
-
-
-
-
diff --git a/gfx/tests/reftest/1524261-ref.html b/gfx/tests/reftest/1524261-ref.html new file mode 100644 index 000000000000..45d798cec81d --- /dev/null +++ b/gfx/tests/reftest/1524261-ref.html @@ -0,0 +1,4 @@ + + +
+
diff --git a/gfx/tests/reftest/1524261.html b/gfx/tests/reftest/1524261.html new file mode 100644 index 000000000000..0db59f46309a --- /dev/null +++ b/gfx/tests/reftest/1524261.html @@ -0,0 +1,10 @@ + + +
+
+
+
+
diff --git a/gfx/tests/reftest/1524353-ref.html b/gfx/tests/reftest/1524353-ref.html new file mode 100644 index 000000000000..e1c5e7831437 --- /dev/null +++ b/gfx/tests/reftest/1524353-ref.html @@ -0,0 +1,8 @@ + +
+
+ + + +
+
diff --git a/gfx/tests/reftest/1524353.html b/gfx/tests/reftest/1524353.html new file mode 100644 index 000000000000..72a0f7d686ba --- /dev/null +++ b/gfx/tests/reftest/1524353.html @@ -0,0 +1,8 @@ + +
+
+ + + +
+
diff --git a/gfx/tests/reftest/reftest.list b/gfx/tests/reftest/reftest.list index 464d52bcc602..fd5c2ce0fa5c 100644 --- a/gfx/tests/reftest/reftest.list +++ b/gfx/tests/reftest/reftest.list @@ -18,4 +18,5 @@ fuzzy(5-32,21908-26621) fuzzy-if(webrender,0-9,0-100) == 1463802.html 1463802-re fuzzy(0-11,0-4) == 1474722.html 1474722-ref.html == 1501195.html 1501195-ref.html == 1519754.html 1519754-ref.html -fuzzy-if(webrender,6-7,34741-36908) == 1523776.html 1523776-ref.html +skip-if(!asyncPan) == 1524261.html 1524261-ref.html +fuzzy-if(webrender,14-14,44-44) == 1524353.html 1524353-ref.html diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp index e3628d28d2aa..a90796bae698 100644 --- a/gfx/webrender_bindings/WebRenderAPI.cpp +++ b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -701,10 +701,13 @@ void DisplayListBuilder::PopStackingContext(bool aIsReferenceFrame) { } wr::WrClipChainId DisplayListBuilder::DefineClipChain( + const Maybe& aParent, const nsTArray& aClips) { - uint64_t clipchainId = wr_dp_define_clipchain( - mWrState, nullptr, aClips.Elements(), aClips.Length()); - WRDL_LOG("DefineClipChain id=%" PRIu64 " clips=%zu\n", mWrState, clipchainId, + uint64_t clipchainId = + wr_dp_define_clipchain(mWrState, aParent ? &(aParent->id) : nullptr, + aClips.Elements(), aClips.Length()); + WRDL_LOG("DefineClipChain id=%" PRIu64 " p=%s clips=%zu\n", mWrState, + clipchainId, aParent ? Stringify(aParent->id).c_str() : "(nil)", aClips.Length()); return wr::WrClipChainId{clipchainId}; } diff --git a/gfx/webrender_bindings/WebRenderAPI.h b/gfx/webrender_bindings/WebRenderAPI.h index 6a616459d8ef..88606557705d 100644 --- a/gfx/webrender_bindings/WebRenderAPI.h +++ b/gfx/webrender_bindings/WebRenderAPI.h @@ -362,7 +362,8 @@ class DisplayListBuilder { const wr::RasterSpace& aRasterSpace); void PopStackingContext(bool aIsReferenceFrame); - wr::WrClipChainId DefineClipChain(const nsTArray& aClips); + wr::WrClipChainId DefineClipChain(const Maybe& aParent, + const nsTArray& aClips); wr::WrClipId DefineClip( const Maybe& aParent, const wr::LayoutRect& aClipRect, diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index 4c25c1fa9471..4a052e5b8aee 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -1541,10 +1541,23 @@ void OutlineTypedObject::setOwnerAndData(JSObject* owner, uint8_t* data) { owner_ = owner; data_ = data; - // Trigger a post barrier when attaching an object outside the nursery to - // one that is inside it. - if (owner && !IsInsideNursery(this) && IsInsideNursery(owner)) { - owner->storeBuffer()->putWholeCell(this); + if (owner) { + if (!IsInsideNursery(this) && IsInsideNursery(owner)) { + // Trigger a post barrier when attaching an object outside the nursery to + // one that is inside it. + owner->storeBuffer()->putWholeCell(this); + } else if (IsInsideNursery(this) && !IsInsideNursery(owner)) { + // ...and also when attaching an object inside the nursery to one that is + // outside it, for a subtle reason -- the outline object now points to + // the memory owned by 'owner', and can modify object/string references + // stored in that memory, potentially storing nursery pointers in it. If + // the outline object is in the nursery, then the post barrier will do + // nothing; you will be writing a nursery pointer "into" a nursery + // object. But that will result in the tenured owner's data containing a + // nursery pointer, and thus we need a store buffer edge. Since we can't + // catch the actual write, register the owner preemptively now. + storeBuffer()->putWholeCell(owner); + } } } diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index c4251c9147ee..3749c21bd7dc 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -726,14 +726,23 @@ void UnmapPages(void* region, size_t length) { } bool MarkPagesUnused(void* region, size_t length) { - MOZ_RELEASE_ASSERT(region && OffsetFromAligned(region, pageSize) == 0); - MOZ_RELEASE_ASSERT(length > 0 && length % pageSize == 0); + MOZ_RELEASE_ASSERT(region); + MOZ_RELEASE_ASSERT(length > 0); + + // pageSize == ArenaSize doesn't necessarily hold, but this function is + // used by the GC to decommit unused Arenas, so we don't want to assert + // if pageSize > ArenaSize. + MOZ_ASSERT(OffsetFromAligned(region, ArenaSize) == 0); + MOZ_ASSERT(length % ArenaSize == 0); MOZ_MAKE_MEM_NOACCESS(region, length); if (!DecommitEnabled()) { return true; } + // We can't decommit part of a page. + MOZ_RELEASE_ASSERT(OffsetFromAligned(region, pageSize) == 0); + MOZ_RELEASE_ASSERT(length % pageSize == 0); #if defined(XP_WIN) return VirtualAlloc(region, length, MEM_RESET, @@ -746,10 +755,23 @@ bool MarkPagesUnused(void* region, size_t length) { } void MarkPagesInUse(void* region, size_t length) { - MOZ_RELEASE_ASSERT(region && OffsetFromAligned(region, pageSize) == 0); - MOZ_RELEASE_ASSERT(length > 0 && length % pageSize == 0); + MOZ_RELEASE_ASSERT(region); + MOZ_RELEASE_ASSERT(length > 0); + + // pageSize == ArenaSize doesn't necessarily hold, but this function is + // used by the GC to recommit Arenas that were previously decommitted, + // so we don't want to assert if pageSize > ArenaSize. + MOZ_ASSERT(OffsetFromAligned(region, ArenaSize) == 0); + MOZ_ASSERT(length % ArenaSize == 0); MOZ_MAKE_MEM_UNDEFINED(region, length); + + if (!DecommitEnabled()) { + return; + } + // We can't commit part of a page. + MOZ_RELEASE_ASSERT(OffsetFromAligned(region, pageSize) == 0); + MOZ_RELEASE_ASSERT(length % pageSize == 0); } size_t GetPageFaultCount() { diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp index 3bbb8f1a6932..cbea1a458dcc 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -7006,11 +7006,8 @@ bool nsDisplayStickyPosition::CreateWebRenderCommands( } { - wr::StackingContextParams params; - params.clip = - wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, - aBuilder, params); + aBuilder); nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, aDisplayListBuilder); } diff --git a/layout/reftests/position-sticky/reftest.list b/layout/reftests/position-sticky/reftest.list index fc13681de9c6..f4a1765a2cad 100644 --- a/layout/reftests/position-sticky/reftest.list +++ b/layout/reftests/position-sticky/reftest.list @@ -45,8 +45,8 @@ fails == column-contain-1a.html column-contain-1-ref.html == column-contain-1b.html column-contain-1-ref.html == column-contain-2.html column-contain-2-ref.html == block-in-inline-1.html block-in-inline-1-ref.html -fuzzy-if(skiaContent,0-1,0-22) fuzzy-if((winWidget&&(webrender||!layersGPUAccelerated))||(OSX&&webrender),0-92,0-1369) fuzzy-if(Android,0-8,0-1533) == block-in-inline-2.html block-in-inline-2-ref.html -fuzzy-if(Android,0-8,0-630) fuzzy-if(OSX,0-1,0-11) fuzzy-if(skiaContent,0-1,0-220) fuzzy-if((winWidget&&(webrender||!layersGPUAccelerated))||(OSX&&webrender),0-92,0-1343) == block-in-inline-3.html block-in-inline-3-ref.html +fuzzy-if(skiaContent,0-1,0-22) fuzzy-if(winWidget&&!layersGPUAccelerated,0-116,0-1320) fuzzy-if(Android,0-8,0-1533) == block-in-inline-2.html block-in-inline-2-ref.html +fuzzy-if(Android,0-8,0-630) fuzzy-if(OSX,0-1,0-11) fuzzy-if(skiaContent,0-1,0-220) fuzzy-if(winWidget&&!layersGPUAccelerated,0-116,0-1320) == block-in-inline-3.html block-in-inline-3-ref.html == block-in-inline-continuations.html block-in-inline-continuations-ref.html == iframe-1.html iframe-1-ref.html == transformed-1.html transformed-1-ref.html diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java index 0134ef1aaf27..e9a5833c92cc 100644 --- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java +++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java @@ -1549,8 +1549,6 @@ public class BrowserApp extends GeckoApp NotificationHelper.destroy(); GeckoNetworkManager.destroy(); - EventDispatcher.getInstance().dispatch("Browser:ZombifyTabs", null); - MmaDelegate.flushResources(this); super.onDestroy(); diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 0a3727b4ca25..f2da49fbfc21 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -371,7 +371,6 @@ var BrowserApp = { GlobalEventDispatcher.registerListener(this, [ "Browser:LoadManifest", "Browser:Quit", - "Browser:ZombifyTabs", "Fonts:Reload", "FormHistory:Init", "FullScreen:Exit", @@ -1731,13 +1730,6 @@ var BrowserApp = { this.quit(data); break; - case "Browser:ZombifyTabs": - let tabs = this._tabs; - for (let i = 0; i < tabs.length; i++) { - tabs[i].zombify(); - } - break; - case "Fonts:Reload": FontEnumerator.updateFontList(); break; diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp index 16a23a912f67..783d0144672c 100644 --- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -4170,6 +4170,7 @@ int AddPreCompleteActions(ActionList *list) { rv = action->Parse(line); if (rv) { + delete action; free(buf); return rv; } diff --git a/toolkit/recordreplay/MiddlemanCall.cpp b/toolkit/recordreplay/MiddlemanCall.cpp index da29c301cf29..5a51a099d9bc 100644 --- a/toolkit/recordreplay/MiddlemanCall.cpp +++ b/toolkit/recordreplay/MiddlemanCall.cpp @@ -11,26 +11,42 @@ namespace mozilla { namespace recordreplay { -// In a replaying or middleman process, all middleman calls that have been -// encountered, indexed by their ID. -static StaticInfallibleVector gMiddlemanCalls; - -// In a replaying or middleman process, association between values produced by -// a middleman call and the call itself. typedef std::unordered_map MiddlemanCallMap; -static MiddlemanCallMap* gMiddlemanCallMap; -// In a middleman process, any buffers allocated for performed calls. -static StaticInfallibleVector gAllocatedBuffers; +// State used for keeping track of middleman calls in either a replaying +// process or middleman process. +struct MiddlemanCallState { + // In a replaying or middleman process, all middleman calls that have been + // encountered, indexed by their ID. + InfallibleVector mCalls; -// Lock protecting middleman call state. + // In a replaying or middleman process, association between values produced by + // a middleman call and the call itself. + MiddlemanCallMap mCallMap; + + // In a middleman process, any buffers allocated for performed calls. + InfallibleVector mAllocatedBuffers; +}; + +// In a replaying process, all middleman call state. In a middleman process, +// state for the child currently being processed. +static MiddlemanCallState* gState; + +// In a middleman process, middleman call state for each child process, indexed +// by the child ID. +static StaticInfallibleVector gStatePerChild; + +// In a replaying process, lock protecting middleman call state. In the +// middleman, all accesses occur on the main thread. static Monitor* gMonitor; void InitializeMiddlemanCalls() { MOZ_RELEASE_ASSERT(IsRecordingOrReplaying() || IsMiddleman()); - gMiddlemanCallMap = new MiddlemanCallMap(); - gMonitor = new Monitor(); + if (IsReplaying()) { + gState = new MiddlemanCallState(); + gMonitor = new Monitor(); + } } // Apply the ReplayInput phase to aCall and any calls it depends on that have @@ -79,9 +95,9 @@ bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, MonitorAutoLock lock(*gMonitor); // Allocate and fill in a new MiddlemanCall. - size_t id = gMiddlemanCalls.length(); + size_t id = gState->mCalls.length(); MiddlemanCall* newCall = new MiddlemanCall(); - gMiddlemanCalls.emplaceBack(newCall); + gState->mCalls.emplaceBack(newCall); newCall->mId = id; newCall->mCallId = aCallId; newCall->mArguments.CopyFrom(aArguments); @@ -93,7 +109,7 @@ bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, redirection.mMiddlemanCall(cx); if (cx.mFailed) { delete newCall; - gMiddlemanCalls.popBack(); + gState->mCalls.popBack(); if (child::CurrentRepaintCannotFail()) { child::ReportFatalError(Nothing(), "Middleman call preface failed: %s\n", @@ -154,10 +170,18 @@ bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, return true; } -void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize, +void ProcessMiddlemanCall(size_t aChildId, const char* aInputData, size_t aInputSize, InfallibleVector* aOutputData) { MOZ_RELEASE_ASSERT(IsMiddleman()); + while (aChildId >= gStatePerChild.length()) { + gStatePerChild.append(nullptr); + } + if (!gStatePerChild[aChildId]) { + gStatePerChild[aChildId] = new MiddlemanCallState(); + } + gState = gStatePerChild[aChildId]; + BufferStream inputStream(aInputData, aInputSize); BufferStream outputStream(aOutputData); @@ -192,12 +216,14 @@ void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize, call->mArguments.CopyFrom(&arguments); call->EncodeOutput(outputStream); - while (call->mId >= gMiddlemanCalls.length()) { - gMiddlemanCalls.emplaceBack(nullptr); + while (call->mId >= gState->mCalls.length()) { + gState->mCalls.emplaceBack(nullptr); } - MOZ_RELEASE_ASSERT(!gMiddlemanCalls[call->mId]); - gMiddlemanCalls[call->mId] = call; + MOZ_RELEASE_ASSERT(!gState->mCalls[call->mId]); + gState->mCalls[call->mId] = call; } + + gState = nullptr; } void* MiddlemanCallContext::AllocateBytes(size_t aSize) { @@ -209,16 +235,25 @@ void* MiddlemanCallContext::AllocateBytes(size_t aSize) { // of the MiddlemanCall itself) or will be recovered when we rewind after we // are done with our divergence from the recording (any other phase). if (IsMiddleman()) { - gAllocatedBuffers.append(rv); + gState->mAllocatedBuffers.append(rv); } return rv; } -void ResetMiddlemanCalls() { +void ResetMiddlemanCalls(size_t aChildId) { MOZ_RELEASE_ASSERT(IsMiddleman()); - for (MiddlemanCall* call : gMiddlemanCalls) { + if (aChildId >= gStatePerChild.length()) { + return; + } + + gState = gStatePerChild[aChildId]; + if (!gState) { + return; + } + + for (MiddlemanCall* call : gState->mCalls) { if (call) { CallArguments arguments; call->mArguments.CopyTo(&arguments); @@ -231,16 +266,18 @@ void ResetMiddlemanCalls() { // Delete the calls in a second pass. The MiddlemanRelease phase depends on // previous middleman calls still existing. - for (MiddlemanCall* call : gMiddlemanCalls) { + for (MiddlemanCall* call : gState->mCalls) { delete call; } - gMiddlemanCalls.clear(); - for (auto buffer : gAllocatedBuffers) { + gState->mCalls.clear(); + for (auto buffer : gState->mAllocatedBuffers) { free(buffer); } - gAllocatedBuffers.clear(); - gMiddlemanCallMap->clear(); + gState->mAllocatedBuffers.clear(); + gState->mCallMap.clear(); + + gState = nullptr; } /////////////////////////////////////////////////////////////////////////////// @@ -248,13 +285,13 @@ void ResetMiddlemanCalls() { /////////////////////////////////////////////////////////////////////////////// static void AddMiddlemanCallValue(const void* aThing, MiddlemanCall* aCall) { - gMiddlemanCallMap->erase(aThing); - gMiddlemanCallMap->insert(MiddlemanCallMap::value_type(aThing, aCall)); + gState->mCallMap.erase(aThing); + gState->mCallMap.insert(MiddlemanCallMap::value_type(aThing, aCall)); } static MiddlemanCall* LookupMiddlemanCall(const void* aThing) { - MiddlemanCallMap::const_iterator iter = gMiddlemanCallMap->find(aThing); - if (iter != gMiddlemanCallMap->end()) { + MiddlemanCallMap::const_iterator iter = gState->mCallMap.find(aThing); + if (iter != gState->mCallMap.end()) { return iter->second; } return nullptr; @@ -262,9 +299,9 @@ static MiddlemanCall* LookupMiddlemanCall(const void* aThing) { static const void* GetMiddlemanCallValue(size_t aId) { MOZ_RELEASE_ASSERT(IsMiddleman()); - MOZ_RELEASE_ASSERT(aId < gMiddlemanCalls.length() && gMiddlemanCalls[aId] && - gMiddlemanCalls[aId]->mMiddlemanValue.isSome()); - return gMiddlemanCalls[aId]->mMiddlemanValue.ref(); + MOZ_RELEASE_ASSERT(aId < gState->mCalls.length() && gState->mCalls[aId] && + gState->mCalls[aId]->mMiddlemanValue.isSome()); + return gState->mCalls[aId]->mMiddlemanValue.ref(); } bool MM_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr) { @@ -293,7 +330,7 @@ bool MM_SystemInput(MiddlemanCallContext& aCx, const void** aThingPtr) { case MiddlemanCallPhase::ReplayInput: if (callId.isSome()) { aCx.WriteInputScalar(callId.ref()); - aCx.mDependentCalls->append(gMiddlemanCalls[callId.ref()]); + aCx.mDependentCalls->append(gState->mCalls[callId.ref()]); return true; } return false; diff --git a/toolkit/recordreplay/MiddlemanCall.h b/toolkit/recordreplay/MiddlemanCall.h index 9ea800e8cfdc..2cc09883b55b 100644 --- a/toolkit/recordreplay/MiddlemanCall.h +++ b/toolkit/recordreplay/MiddlemanCall.h @@ -343,12 +343,14 @@ bool SendCallToMiddleman(size_t aCallId, CallArguments* aArguments, bool aDiverged); // In the middleman process, perform one or more calls encoded in aInputData -// and encode their outputs to aOutputData. -void ProcessMiddlemanCall(const char* aInputData, size_t aInputSize, +// and encode their outputs to aOutputData. The calls are associated with the +// specified child process ID. +void ProcessMiddlemanCall(size_t aChildId, + const char* aInputData, size_t aInputSize, InfallibleVector* aOutputData); -// In the middleman process, reset all call state. -void ResetMiddlemanCalls(); +// In the middleman process, reset all call state for a child process ID. +void ResetMiddlemanCalls(size_t aChildId); /////////////////////////////////////////////////////////////////////////////// // Middleman Call Helpers diff --git a/toolkit/recordreplay/ipc/Channel.h b/toolkit/recordreplay/ipc/Channel.h index 8b1c207bd52e..3ef4a3a345a4 100644 --- a/toolkit/recordreplay/ipc/Channel.h +++ b/toolkit/recordreplay/ipc/Channel.h @@ -434,14 +434,6 @@ typedef BinaryMessage typedef EmptyMessage ResetMiddlemanCallsMessage; -static inline MiddlemanCallResponseMessage* ProcessMiddlemanCallMessage( - const MiddlemanCallRequestMessage& aMsg) { - InfallibleVector outputData; - ProcessMiddlemanCall(aMsg.BinaryData(), aMsg.BinaryDataSize(), &outputData); - return MiddlemanCallResponseMessage::New(outputData.begin(), - outputData.length()); -} - class Channel { public: // Note: the handler is responsible for freeing its input message. It will be diff --git a/toolkit/recordreplay/ipc/ChildProcess.cpp b/toolkit/recordreplay/ipc/ChildProcess.cpp index c37578e90555..7dc245b6d31f 100644 --- a/toolkit/recordreplay/ipc/ChildProcess.cpp +++ b/toolkit/recordreplay/ipc/ChildProcess.cpp @@ -94,12 +94,17 @@ void ChildProcessInfo::OnIncomingMessage(const Message& aMsg, case MessageType::MiddlemanCallRequest: { const MiddlemanCallRequestMessage& nmsg = static_cast(aMsg); - Message::UniquePtr response(ProcessMiddlemanCallMessage(nmsg)); + InfallibleVector outputData; + ProcessMiddlemanCall(GetId(), nmsg.BinaryData(), nmsg.BinaryDataSize(), + &outputData); + Message::UniquePtr response( + MiddlemanCallResponseMessage::New(outputData.begin(), + outputData.length())); SendMessage(*response); break; } case MessageType::ResetMiddlemanCalls: - ResetMiddlemanCalls(); + ResetMiddlemanCalls(GetId()); break; default: break; diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp index f809aaecc0b7..8b23f000292d 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -636,13 +636,8 @@ static void ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *appDir, // execv on Windows. if (restart) { exit(execv(updaterPath.get(), argv)); - } - *outpid = fork(); - if (*outpid == -1) { - delete[] argv; - return; - } else if (*outpid == 0) { - exit(execv(updaterPath.get(), argv)); + } else { + *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr); } delete[] argv; #elif defined(XP_WIN) @@ -707,25 +702,6 @@ static bool ProcessHasTerminated(ProcessType pt) { #elif defined(XP_MACOSX) // We're waiting for the process to terminate in LaunchChildMac. return true; -#elif defined(XP_UNIX) - int exitStatus; - pid_t exited = waitpid(pt, &exitStatus, WNOHANG); - if (exited == 0) { - // Process is still running. - sleep(1); - return false; - } - if (exited == -1) { - LOG(("Error while checking if the updater process is finished")); - // This shouldn't happen, but if it does, the updater process is lost to us, - // so the best we can do is pretend that it's exited. - return true; - } - // If we get here, the process has exited; make sure it exited normally. - if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) { - LOG(("Error while running the updater process, check update.log")); - } - return true; #else // No way to have a non-blocking implementation on these platforms, // because we're using NSPR and it only provides a blocking wait. diff --git a/toolkit/xre/nsUpdateDriver.h b/toolkit/xre/nsUpdateDriver.h index dfd094538a88..2d2f189342b8 100644 --- a/toolkit/xre/nsUpdateDriver.h +++ b/toolkit/xre/nsUpdateDriver.h @@ -19,7 +19,7 @@ class nsIFile; #if defined(XP_WIN) # include typedef HANDLE ProcessType; -#elif defined(XP_UNIX) +#elif defined(XP_MACOSX) typedef pid_t ProcessType; #else # include "prproces.h" diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 01abda3131d6..0d5bcd7bf8ff 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -866,7 +866,8 @@ nsThread::Shutdown() { } nsThreadShutdownContext* maybeContext = ShutdownInternal(/* aSync = */ true); - NS_ENSURE_TRUE(maybeContext, NS_ERROR_UNEXPECTED); + if (!maybeContext) return NS_ERROR_UNEXPECTED; + NotNull context = WrapNotNull(maybeContext); // Process events on the current thread until we receive a shutdown ACK.