зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to mozilla-central. a=merge
This commit is contained in:
Коммит
dbb80cb51f
|
@ -1,5 +1,5 @@
|
|||
This is the PDF.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 2.1.176
|
||||
Current extension version is: 2.1.189
|
||||
|
||||
Taken from upstream commit: e4d2a160
|
||||
Taken from upstream commit: 5cb00b79
|
||||
|
|
|
@ -123,8 +123,8 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
"use strict";
|
||||
|
||||
|
||||
var pdfjsVersion = '2.1.176';
|
||||
var pdfjsBuild = 'e4d2a160';
|
||||
var pdfjsVersion = '2.1.189';
|
||||
var pdfjsBuild = '5cb00b79';
|
||||
|
||||
var pdfjsSharedUtil = __w_pdfjs_require__(1);
|
||||
|
||||
|
@ -5154,7 +5154,7 @@ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
|
|||
|
||||
return worker.messageHandler.sendWithPromise('GetDocRequest', {
|
||||
docId,
|
||||
apiVersion: '2.1.176',
|
||||
apiVersion: '2.1.189',
|
||||
source: {
|
||||
data: source.data,
|
||||
url: source.url,
|
||||
|
@ -6885,9 +6885,9 @@ const InternalRenderTask = function InternalRenderTaskClosure() {
|
|||
return InternalRenderTask;
|
||||
}();
|
||||
|
||||
const version = '2.1.176';
|
||||
const version = '2.1.189';
|
||||
exports.version = version;
|
||||
const build = 'e4d2a160';
|
||||
const build = '5cb00b79';
|
||||
exports.build = build;
|
||||
|
||||
/***/ }),
|
||||
|
|
|
@ -123,8 +123,8 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
"use strict";
|
||||
|
||||
|
||||
var pdfjsVersion = '2.1.176';
|
||||
var pdfjsBuild = 'e4d2a160';
|
||||
var pdfjsVersion = '2.1.189';
|
||||
var pdfjsBuild = '5cb00b79';
|
||||
|
||||
var pdfjsCoreWorker = __w_pdfjs_require__(1);
|
||||
|
||||
|
@ -375,7 +375,7 @@ var WorkerMessageHandler = {
|
|||
var cancelXHRs = null;
|
||||
var WorkerTasks = [];
|
||||
let apiVersion = docParams.apiVersion;
|
||||
let workerVersion = '2.1.176';
|
||||
let workerVersion = '2.1.189';
|
||||
|
||||
if (apiVersion !== workerVersion) {
|
||||
throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
|
||||
|
@ -8240,7 +8240,7 @@ var XRef = function XRefClosure() {
|
|||
|
||||
var objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
|
||||
const endobjRegExp = /\bendobj[\b\s]$/;
|
||||
const nestedObjRegExp = /\s+(\d+\s+\d+\s+obj[\b\s])$/;
|
||||
const nestedObjRegExp = /\s+(\d+\s+\d+\s+obj[\b\s<])$/;
|
||||
const CHECK_CONTENT_LENGTH = 25;
|
||||
var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
|
||||
var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, 101, 102]);
|
||||
|
@ -9569,7 +9569,16 @@ var Parser = function ParserClosure() {
|
|||
}
|
||||
}
|
||||
|
||||
return stream.pos - 4 - startPos;
|
||||
let endOffset = 4;
|
||||
stream.skip(-endOffset);
|
||||
ch = stream.peekByte();
|
||||
stream.skip(endOffset);
|
||||
|
||||
if (!(0, _util.isSpace)(ch)) {
|
||||
endOffset--;
|
||||
}
|
||||
|
||||
return stream.pos - endOffset - startPos;
|
||||
},
|
||||
|
||||
findDCTDecodeInlineStreamEnd: function Parser_findDCTDecodeInlineStreamEnd(stream) {
|
||||
|
|
|
@ -56,8 +56,6 @@
|
|||
background-color: rgb(0, 100, 0);
|
||||
}
|
||||
|
||||
.textLayer ::-moz-selection { background: rgb(0,0,255); }
|
||||
|
||||
.textLayer ::selection { background: rgb(0,0,255); }
|
||||
|
||||
.textLayer .endOfContent {
|
||||
|
@ -366,11 +364,6 @@
|
|||
margin-right: auto;
|
||||
}
|
||||
|
||||
.pdfPresentationMode:-moz-full-screen .pdfViewer .page {
|
||||
margin-bottom: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.pdfPresentationMode:fullscreen .pdfViewer .page {
|
||||
margin-bottom: 100%;
|
||||
border: 0;
|
||||
|
@ -414,18 +407,6 @@ select {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
#viewerContainer.pdfPresentationMode:-moz-full-screen {
|
||||
top: 0px;
|
||||
border-top: 2px solid transparent;
|
||||
background-color: #000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
cursor: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#viewerContainer.pdfPresentationMode:fullscreen {
|
||||
top: 0px;
|
||||
border-top: 2px solid transparent;
|
||||
|
@ -438,18 +419,10 @@ select {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.pdfPresentationMode:-moz-full-screen a:not(.internalLink) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pdfPresentationMode:fullscreen a:not(.internalLink) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pdfPresentationMode:-moz-full-screen .textLayer > span {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
.pdfPresentationMode:fullscreen .textLayer > span {
|
||||
cursor: none;
|
||||
}
|
||||
|
@ -1677,7 +1650,6 @@ html[dir='rtl'] .outlineItemToggler::before {
|
|||
/* TODO: file FF bug to support ::-moz-selection:window-inactive
|
||||
so we can override the opaque grey background when the window is inactive;
|
||||
see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
|
||||
::-moz-selection { background: rgba(0,0,255,0.3); }
|
||||
::selection { background: rgba(0,0,255,0.3); }
|
||||
|
||||
#errorWrapper {
|
||||
|
|
|
@ -1106,6 +1106,8 @@ let PDFViewerApplication = {
|
|||
this.initialBookmark = initialBookmark;
|
||||
pdfViewer.currentScaleValue = pdfViewer.currentScaleValue;
|
||||
this.setInitialView(hash);
|
||||
}).catch(() => {
|
||||
this.setInitialView();
|
||||
}).then(function () {
|
||||
pdfViewer.update();
|
||||
});
|
||||
|
@ -2644,46 +2646,42 @@ function backtrackBeforeAllVisibleElements(index, views, top) {
|
|||
}
|
||||
|
||||
function getVisibleElements(scrollEl, views, sortByVisibility = false, horizontal = false) {
|
||||
let top = scrollEl.scrollTop,
|
||||
bottom = top + scrollEl.clientHeight;
|
||||
let left = scrollEl.scrollLeft,
|
||||
right = left + scrollEl.clientWidth;
|
||||
const top = scrollEl.scrollTop,
|
||||
bottom = top + scrollEl.clientHeight;
|
||||
const left = scrollEl.scrollLeft,
|
||||
right = left + scrollEl.clientWidth;
|
||||
|
||||
function isElementBottomAfterViewTop(view) {
|
||||
let element = view.div;
|
||||
let elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
|
||||
const element = view.div;
|
||||
const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
|
||||
return elementBottom > top;
|
||||
}
|
||||
|
||||
function isElementRightAfterViewLeft(view) {
|
||||
let element = view.div;
|
||||
let elementRight = element.offsetLeft + element.clientLeft + element.clientWidth;
|
||||
const element = view.div;
|
||||
const elementRight = element.offsetLeft + element.clientLeft + element.clientWidth;
|
||||
return elementRight > left;
|
||||
}
|
||||
|
||||
let visible = [],
|
||||
view,
|
||||
element;
|
||||
let currentHeight, viewHeight, viewBottom, hiddenHeight;
|
||||
let currentWidth, viewWidth, viewRight, hiddenWidth;
|
||||
let percentVisible;
|
||||
let firstVisibleElementInd = views.length === 0 ? 0 : binarySearchFirstItem(views, horizontal ? isElementRightAfterViewLeft : isElementBottomAfterViewTop);
|
||||
const visible = [],
|
||||
numViews = views.length;
|
||||
let firstVisibleElementInd = numViews === 0 ? 0 : binarySearchFirstItem(views, horizontal ? isElementRightAfterViewLeft : isElementBottomAfterViewTop);
|
||||
|
||||
if (views.length > 0 && !horizontal) {
|
||||
if (numViews > 0 && firstVisibleElementInd < numViews && !horizontal) {
|
||||
firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
|
||||
}
|
||||
|
||||
let lastEdge = horizontal ? right : -1;
|
||||
|
||||
for (let i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
|
||||
view = views[i];
|
||||
element = view.div;
|
||||
currentWidth = element.offsetLeft + element.clientLeft;
|
||||
currentHeight = element.offsetTop + element.clientTop;
|
||||
viewWidth = element.clientWidth;
|
||||
viewHeight = element.clientHeight;
|
||||
viewRight = currentWidth + viewWidth;
|
||||
viewBottom = currentHeight + viewHeight;
|
||||
for (let i = firstVisibleElementInd; i < numViews; i++) {
|
||||
const view = views[i],
|
||||
element = view.div;
|
||||
const currentWidth = element.offsetLeft + element.clientLeft;
|
||||
const currentHeight = element.offsetTop + element.clientTop;
|
||||
const viewWidth = element.clientWidth,
|
||||
viewHeight = element.clientHeight;
|
||||
const viewRight = currentWidth + viewWidth;
|
||||
const viewBottom = currentHeight + viewHeight;
|
||||
|
||||
if (lastEdge === -1) {
|
||||
if (viewBottom >= bottom) {
|
||||
|
@ -2697,20 +2695,20 @@ function getVisibleElements(scrollEl, views, sortByVisibility = false, horizonta
|
|||
continue;
|
||||
}
|
||||
|
||||
hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
|
||||
hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
|
||||
percentVisible = (viewHeight - hiddenHeight) * (viewWidth - hiddenWidth) * 100 / viewHeight / viewWidth | 0;
|
||||
const hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
|
||||
const hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
|
||||
const percent = (viewHeight - hiddenHeight) * (viewWidth - hiddenWidth) * 100 / viewHeight / viewWidth | 0;
|
||||
visible.push({
|
||||
id: view.id,
|
||||
x: currentWidth,
|
||||
y: currentHeight,
|
||||
view,
|
||||
percent: percentVisible
|
||||
percent
|
||||
});
|
||||
}
|
||||
|
||||
let first = visible[0];
|
||||
let last = visible[visible.length - 1];
|
||||
const first = visible[0],
|
||||
last = visible[visible.length - 1];
|
||||
|
||||
if (sortByVisibility) {
|
||||
visible.sort(function (a, b) {
|
||||
|
|
|
@ -20,7 +20,7 @@ origin:
|
|||
|
||||
# Human-readable identifier for this version/release
|
||||
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
||||
release: version 2.1.176
|
||||
release: version 2.1.189
|
||||
|
||||
# The package's license, where possible using the mnemonic from
|
||||
# https://spdx.org/licenses/
|
||||
|
|
|
@ -14,7 +14,7 @@ cbindgen = check_prog('CBINDGEN', ['cbindgen'], paths=toolchain_search_path,
|
|||
@checking('cbindgen version')
|
||||
@imports(_from='textwrap', _import='dedent')
|
||||
def cbindgen_version(cbindgen):
|
||||
cbindgen_min_version = Version('0.6.7')
|
||||
cbindgen_min_version = Version('0.6.8')
|
||||
|
||||
# cbindgen x.y.z
|
||||
version = Version(check_cmd_output(cbindgen, '--version').strip().split(" ")[1])
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -13,8 +13,8 @@
|
|||
// C++ Debugger (Debugger, Debugger.Object, etc.), which implement similar
|
||||
// methods and properties to those C++ objects. These replay objects are
|
||||
// created in the middleman process, and describe things that exist in the
|
||||
// recording/replaying process, inspecting them via the RecordReplayControl
|
||||
// interface.
|
||||
// recording/replaying process, inspecting them via the interface provided by
|
||||
// control.js.
|
||||
|
||||
"use strict";
|
||||
|
||||
|
@ -40,8 +40,8 @@ function ReplayDebugger() {
|
|||
return existing;
|
||||
}
|
||||
|
||||
// Whether the process is currently paused.
|
||||
this._paused = false;
|
||||
// We should have been connected to control.js by the call above.
|
||||
assert(this._control);
|
||||
|
||||
// Preferred direction of travel when not explicitly resumed.
|
||||
this._direction = Direction.NONE;
|
||||
|
@ -97,14 +97,17 @@ ReplayDebugger.prototype = {
|
|||
canRewind: RecordReplayControl.canRewind,
|
||||
|
||||
replayCurrentExecutionPoint() {
|
||||
return this._sendRequest({ type: "currentExecutionPoint" });
|
||||
assert(this._paused);
|
||||
return this._control.pausePoint();
|
||||
},
|
||||
|
||||
replayRecordingEndpoint() {
|
||||
return this._sendRequest({ type: "recordingEndpoint" });
|
||||
},
|
||||
|
||||
replayIsRecording: RecordReplayControl.childIsRecording,
|
||||
replayIsRecording() {
|
||||
return this._control.childIsRecording();
|
||||
},
|
||||
|
||||
addDebuggee() {},
|
||||
removeAllDebuggees() {},
|
||||
|
@ -117,8 +120,7 @@ ReplayDebugger.prototype = {
|
|||
// Send a request object to the child process, and synchronously wait for it
|
||||
// to respond.
|
||||
_sendRequest(request) {
|
||||
assert(this._paused);
|
||||
const data = RecordReplayControl.sendRequest(request);
|
||||
const data = this._control.sendRequest(request);
|
||||
dumpv("SendRequest: " +
|
||||
JSON.stringify(request) + " -> " + JSON.stringify(data));
|
||||
if (data.exception) {
|
||||
|
@ -132,8 +134,7 @@ ReplayDebugger.prototype = {
|
|||
// replaying process (if there is one), as recording child processes won't
|
||||
// provide useful responses to such requests.
|
||||
_sendRequestAllowDiverge(request) {
|
||||
assert(this._paused);
|
||||
RecordReplayControl.maybeSwitchToReplayingChild();
|
||||
this._control.maybeSwitchToReplayingChild();
|
||||
return this._sendRequest(request);
|
||||
},
|
||||
|
||||
|
@ -173,16 +174,19 @@ ReplayDebugger.prototype = {
|
|||
// when an event loop is running (which, because of the above point, cannot
|
||||
// be associated with a thread actor's nested pause).
|
||||
|
||||
get _paused() {
|
||||
return !!this._control.pausePoint();
|
||||
},
|
||||
|
||||
replayResumeBackward() { this._resume(/* forward = */ false); },
|
||||
replayResumeForward() { this._resume(/* forward = */ true); },
|
||||
|
||||
_resume(forward) {
|
||||
this._ensurePaused();
|
||||
this._setResume(() => {
|
||||
this._paused = false;
|
||||
this._direction = forward ? Direction.FORWARD : Direction.BACKWARD;
|
||||
dumpv("Resuming " + this._direction);
|
||||
RecordReplayControl.resume(forward);
|
||||
this._control.resume(forward);
|
||||
if (this._paused) {
|
||||
// If we resume and immediately pause, we are at an endpoint of the
|
||||
// recording. Force the thread to pause.
|
||||
|
@ -194,10 +198,9 @@ ReplayDebugger.prototype = {
|
|||
replayTimeWarp(target) {
|
||||
this._ensurePaused();
|
||||
this._setResume(() => {
|
||||
this._paused = false;
|
||||
this._direction = Direction.NONE;
|
||||
dumpv("Warping " + JSON.stringify(target));
|
||||
RecordReplayControl.timeWarp(target);
|
||||
this._control.timeWarp(target);
|
||||
|
||||
// timeWarp() doesn't return until the child has reached the target of
|
||||
// the warp, after which we force the thread to pause.
|
||||
|
@ -215,17 +218,15 @@ ReplayDebugger.prototype = {
|
|||
|
||||
_ensurePaused() {
|
||||
if (!this._paused) {
|
||||
RecordReplayControl.waitUntilPaused();
|
||||
this._control.waitUntilPaused();
|
||||
assert(this._paused);
|
||||
}
|
||||
},
|
||||
|
||||
// This hook is called whenever the child has paused, which can happen
|
||||
// within a RecordReplayControl method (resume, timeWarp, waitUntilPaused) or
|
||||
// or be delivered via the event loop.
|
||||
// within a control method (resume, timeWarp, waitUntilPaused) or be
|
||||
// delivered via the event loop.
|
||||
_onPause() {
|
||||
this._paused = true;
|
||||
|
||||
// The position change handler is always called on pause notifications.
|
||||
if (this.replayingOnPositionChange) {
|
||||
this.replayingOnPositionChange();
|
||||
|
@ -248,7 +249,7 @@ ReplayDebugger.prototype = {
|
|||
const point = this.replayCurrentExecutionPoint();
|
||||
dumpv("PerformPause " + JSON.stringify(point));
|
||||
|
||||
if (point.position.kind == "Invalid") {
|
||||
if (!point.position) {
|
||||
// We paused at a checkpoint, and there are no handlers to call.
|
||||
} else {
|
||||
// Call any handlers for this point, unless one resumes execution.
|
||||
|
@ -280,11 +281,7 @@ ReplayDebugger.prototype = {
|
|||
_onSwitchChild() {
|
||||
// The position change handler listens to changes to the current child.
|
||||
if (this.replayingOnPositionChange) {
|
||||
// Children are paused whenever we switch between them.
|
||||
const paused = this._paused;
|
||||
this._paused = true;
|
||||
this.replayingOnPositionChange();
|
||||
this._paused = paused;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -295,7 +292,7 @@ ReplayDebugger.prototype = {
|
|||
assert(!this._resumeCallback);
|
||||
if (++this._threadPauseCount == 1) {
|
||||
// Save checkpoints near the current position in case the user rewinds.
|
||||
RecordReplayControl.markExplicitPause();
|
||||
this._control.markExplicitPause();
|
||||
|
||||
// There is no preferred direction of travel after an explicit pause.
|
||||
this._direction = Direction.NONE;
|
||||
|
@ -358,7 +355,7 @@ ReplayDebugger.prototype = {
|
|||
_setBreakpoint(handler, position, data) {
|
||||
this._ensurePaused();
|
||||
dumpv("AddBreakpoint " + JSON.stringify(position));
|
||||
RecordReplayControl.addBreakpoint(position);
|
||||
this._control.addBreakpoint(position);
|
||||
this._breakpoints.push({handler, position, data});
|
||||
},
|
||||
|
||||
|
@ -367,10 +364,10 @@ ReplayDebugger.prototype = {
|
|||
const newBreakpoints = this._breakpoints.filter(bp => !callback(bp));
|
||||
if (newBreakpoints.length != this._breakpoints.length) {
|
||||
dumpv("ClearBreakpoints");
|
||||
RecordReplayControl.clearBreakpoints();
|
||||
this._control.clearBreakpoints();
|
||||
for (const { position } of newBreakpoints) {
|
||||
dumpv("AddBreakpoint " + JSON.stringify(position));
|
||||
RecordReplayControl.addBreakpoint(position);
|
||||
this._control.addBreakpoint(position);
|
||||
}
|
||||
}
|
||||
this._breakpoints = newBreakpoints;
|
||||
|
@ -445,6 +442,7 @@ ReplayDebugger.prototype = {
|
|||
},
|
||||
|
||||
findScripts(query) {
|
||||
this._ensurePaused();
|
||||
const data = this._sendRequest({
|
||||
type: "findScripts",
|
||||
query: this._convertScriptQuery(query),
|
||||
|
@ -661,6 +659,7 @@ ReplayDebuggerScript.prototype = {
|
|||
get source() { return this._dbg._getSource(this._data.sourceId); },
|
||||
get sourceStart() { return this._data.sourceStart; },
|
||||
get sourceLength() { return this._data.sourceLength; },
|
||||
get format() { return this._data.format; },
|
||||
|
||||
_forward(type, value) {
|
||||
return this._dbg._sendRequest({ type, id: this._data.id, value });
|
||||
|
@ -670,6 +669,7 @@ ReplayDebuggerScript.prototype = {
|
|||
getOffsetLocation(pc) { return this._forward("getOffsetLocation", pc); },
|
||||
getSuccessorOffsets(pc) { return this._forward("getSuccessorOffsets", pc); },
|
||||
getPredecessorOffsets(pc) { return this._forward("getPredecessorOffsets", pc); },
|
||||
getAllColumnOffsets() { return this._forward("getAllColumnOffsets"); },
|
||||
|
||||
setBreakpoint(offset, handler) {
|
||||
this._dbg._setBreakpoint(() => { handler.hit(this._dbg.getNewestFrame()); },
|
||||
|
@ -685,10 +685,8 @@ ReplayDebuggerScript.prototype = {
|
|||
|
||||
get isGeneratorFunction() { NYI(); },
|
||||
get isAsyncFunction() { NYI(); },
|
||||
get format() { NYI(); },
|
||||
getChildScripts: NYI,
|
||||
getAllOffsets: NYI,
|
||||
getAllColumnOffsets: NYI,
|
||||
getBreakpoints: NYI,
|
||||
clearAllBreakpoints: NYI,
|
||||
isInCatchScope: NYI,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'control.js',
|
||||
'debugger.js',
|
||||
'graphics.js',
|
||||
'replay.js',
|
||||
|
@ -13,6 +14,7 @@ DevToolsModules(
|
|||
XPIDL_MODULE = 'devtools_rr'
|
||||
|
||||
XPIDL_SOURCES = [
|
||||
'rrIControl.idl',
|
||||
'rrIGraphics.idl',
|
||||
'rrIReplay.idl',
|
||||
]
|
||||
|
|
|
@ -519,6 +519,7 @@ function getScriptData(id) {
|
|||
sourceLength: script.sourceLength,
|
||||
displayName: script.displayName,
|
||||
url: script.url,
|
||||
format: script.format,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -738,6 +739,7 @@ const gRequestHandlers = {
|
|||
getOffsetLocation: forwardToScript("getOffsetLocation"),
|
||||
getSuccessorOffsets: forwardToScript("getSuccessorOffsets"),
|
||||
getPredecessorOffsets: forwardToScript("getPredecessorOffsets"),
|
||||
getAllColumnOffsets: forwardToScript("getAllColumnOffsets"),
|
||||
|
||||
frameEvaluate(request) {
|
||||
if (!RecordReplayControl.maybeDivergeFromRecording()) {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
// This interface defines the methods used for calling into control.js in a
|
||||
// middleman process.
|
||||
[scriptable, uuid(c296e7c3-8a27-4fd0-94c2-b6e5126909ba)]
|
||||
interface rrIControl : nsISupports {
|
||||
void Initialize(in jsval recordingChildId);
|
||||
void ConnectDebugger(in jsval replayDebugger);
|
||||
void HitExecutionPoint(in long childId, in jsval msg);
|
||||
void BeforeSaveRecording();
|
||||
void AfterSaveRecording();
|
||||
};
|
|
@ -5,15 +5,14 @@
|
|||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
// This interface defines the methods used for calling into replay.js in a
|
||||
// recording/replaying process. See JSControl.h for the documentation of these
|
||||
// methods.
|
||||
[scriptable, uuid(8b86b71f-8471-472e-9997-c5f21f9d0598)]
|
||||
interface rrIReplay : nsISupports {
|
||||
jsval ProcessRequest(in jsval request);
|
||||
|
||||
void EnsurePositionHandler(in jsval position);
|
||||
|
||||
void ClearPositionHandlers();
|
||||
|
||||
void ClearPausedState();
|
||||
|
||||
jsval GetEntryPosition(in jsval position);
|
||||
};
|
||||
|
|
|
@ -3667,7 +3667,8 @@ nsDocShell::GetContentBlockingLog(Promise** aPromise) {
|
|||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
promise->MaybeResolve(doc->GetContentBlockingLog()->Stringify());
|
||||
promise->MaybeResolve(
|
||||
NS_ConvertUTF8toUTF16(doc->GetContentBlockingLog()->Stringify()));
|
||||
promise.forget(aPromise);
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -30,16 +30,14 @@ class ContentBlockingLog final {
|
|||
// come from the blocking types defined in nsIWebProgressListener.
|
||||
typedef nsTArray<LogEntry> OriginLog;
|
||||
typedef Tuple<bool, Maybe<bool>, OriginLog> OriginData;
|
||||
typedef nsTArray<Tuple<nsString, UniquePtr<OriginData>>> OriginDataTable;
|
||||
typedef nsTArray<Tuple<nsCString, UniquePtr<OriginData>>> OriginDataTable;
|
||||
|
||||
struct StringWriteFunc : public JSONWriteFunc {
|
||||
nsAString&
|
||||
nsACString&
|
||||
mBuffer; // The lifetime of the struct must be bound to the buffer
|
||||
explicit StringWriteFunc(nsAString& aBuffer) : mBuffer(aBuffer) {}
|
||||
explicit StringWriteFunc(nsACString& aBuffer) : mBuffer(aBuffer) {}
|
||||
|
||||
void Write(const char* aStr) override {
|
||||
mBuffer.Append(NS_ConvertUTF8toUTF16(aStr));
|
||||
}
|
||||
void Write(const char* aStr) override { mBuffer.Append(aStr); }
|
||||
};
|
||||
|
||||
struct Comparator {
|
||||
|
@ -50,7 +48,7 @@ class ContentBlockingLog final {
|
|||
}
|
||||
|
||||
bool Equals(const OriginDataTable::elem_type& aLeft,
|
||||
const nsAString& aRight) const {
|
||||
const nsACString& aRight) const {
|
||||
return Get<0>(aLeft).Equals(aRight);
|
||||
}
|
||||
};
|
||||
|
@ -59,7 +57,7 @@ class ContentBlockingLog final {
|
|||
ContentBlockingLog() = default;
|
||||
~ContentBlockingLog() = default;
|
||||
|
||||
void RecordLog(const nsAString& aOrigin, uint32_t aType, bool aBlocked) {
|
||||
void RecordLog(const nsACString& aOrigin, uint32_t aType, bool aBlocked) {
|
||||
if (aOrigin.IsVoid()) {
|
||||
return;
|
||||
}
|
||||
|
@ -107,14 +105,14 @@ class ContentBlockingLog final {
|
|||
} else {
|
||||
Get<2>(*data).AppendElement(LogEntry{aType, 1u, aBlocked});
|
||||
}
|
||||
nsAutoString origin(aOrigin);
|
||||
mLog.AppendElement(Tuple<nsString, UniquePtr<OriginData>>(
|
||||
nsAutoCString origin(aOrigin);
|
||||
mLog.AppendElement(Tuple<nsCString, UniquePtr<OriginData>>(
|
||||
std::move(origin), std::move(data)));
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoString Stringify() {
|
||||
nsAutoString buffer;
|
||||
nsAutoCString Stringify() {
|
||||
nsAutoCString buffer;
|
||||
|
||||
JSONWriter w(MakeUnique<StringWriteFunc>(buffer));
|
||||
w.Start();
|
||||
|
@ -122,14 +120,12 @@ class ContentBlockingLog final {
|
|||
const auto end = mLog.end();
|
||||
for (auto iter = mLog.begin(); iter != end; ++iter) {
|
||||
if (!Get<1>(*iter)) {
|
||||
w.StartArrayProperty(NS_ConvertUTF16toUTF8(Get<0>(*iter)).get(),
|
||||
w.SingleLineStyle);
|
||||
w.StartArrayProperty(Get<0>(*iter).get(), w.SingleLineStyle);
|
||||
w.EndArray();
|
||||
continue;
|
||||
}
|
||||
|
||||
w.StartArrayProperty(NS_ConvertUTF16toUTF8(Get<0>(*iter)).get(),
|
||||
w.SingleLineStyle);
|
||||
w.StartArrayProperty(Get<0>(*iter).get(), w.SingleLineStyle);
|
||||
auto& data = Get<1>(*iter);
|
||||
if (Get<0>(*data)) {
|
||||
w.StartArrayElement(w.SingleLineStyle);
|
||||
|
|
|
@ -1019,7 +1019,7 @@ class Document : public nsINode,
|
|||
* Set the tracking content blocked flag for this document.
|
||||
*/
|
||||
void SetHasTrackingContentBlocked(bool aHasTrackingContentBlocked,
|
||||
const nsAString& aOriginBlocked) {
|
||||
const nsACString& aOriginBlocked) {
|
||||
RecordContentBlockingLog(
|
||||
aOriginBlocked, nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT,
|
||||
aHasTrackingContentBlocked);
|
||||
|
@ -1029,7 +1029,7 @@ class Document : public nsINode,
|
|||
* Set the all cookies blocked flag for this document.
|
||||
*/
|
||||
void SetHasAllCookiesBlocked(bool aHasAllCookiesBlocked,
|
||||
const nsAString& aOriginBlocked) {
|
||||
const nsACString& aOriginBlocked) {
|
||||
RecordContentBlockingLog(aOriginBlocked,
|
||||
nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL,
|
||||
aHasAllCookiesBlocked);
|
||||
|
@ -1039,7 +1039,7 @@ class Document : public nsINode,
|
|||
* Set the tracking cookies blocked flag for this document.
|
||||
*/
|
||||
void SetHasTrackingCookiesBlocked(bool aHasTrackingCookiesBlocked,
|
||||
const nsAString& aOriginBlocked) {
|
||||
const nsACString& aOriginBlocked) {
|
||||
RecordContentBlockingLog(
|
||||
aOriginBlocked, nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
|
||||
aHasTrackingCookiesBlocked);
|
||||
|
@ -1049,7 +1049,7 @@ class Document : public nsINode,
|
|||
* Set the third-party cookies blocked flag for this document.
|
||||
*/
|
||||
void SetHasForeignCookiesBlocked(bool aHasForeignCookiesBlocked,
|
||||
const nsAString& aOriginBlocked) {
|
||||
const nsACString& aOriginBlocked) {
|
||||
RecordContentBlockingLog(
|
||||
aOriginBlocked, nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN,
|
||||
aHasForeignCookiesBlocked);
|
||||
|
@ -1059,7 +1059,7 @@ class Document : public nsINode,
|
|||
* Set the cookies blocked by site permission flag for this document.
|
||||
*/
|
||||
void SetHasCookiesBlockedByPermission(bool aHasCookiesBlockedByPermission,
|
||||
const nsAString& aOriginBlocked) {
|
||||
const nsACString& aOriginBlocked) {
|
||||
RecordContentBlockingLog(
|
||||
aOriginBlocked,
|
||||
nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
|
||||
|
@ -1070,7 +1070,7 @@ class Document : public nsINode,
|
|||
* Set the cookies loaded flag for this document.
|
||||
*/
|
||||
void SetHasCookiesLoaded(bool aHasCookiesLoaded,
|
||||
const nsAString& aOriginLoaded) {
|
||||
const nsACString& aOriginLoaded) {
|
||||
RecordContentBlockingLog(aOriginLoaded,
|
||||
nsIWebProgressListener::STATE_COOKIES_LOADED,
|
||||
aHasCookiesLoaded);
|
||||
|
@ -1096,7 +1096,7 @@ class Document : public nsINode,
|
|||
* Set the tracking content loaded flag for this document.
|
||||
*/
|
||||
void SetHasTrackingContentLoaded(bool aHasTrackingContentLoaded,
|
||||
const nsAString& aOriginBlocked) {
|
||||
const nsACString& aOriginBlocked) {
|
||||
RecordContentBlockingLog(
|
||||
aOriginBlocked, nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT,
|
||||
aHasTrackingContentLoaded);
|
||||
|
@ -3581,7 +3581,7 @@ class Document : public nsINode,
|
|||
bool aUpdateCSSLoader);
|
||||
|
||||
private:
|
||||
void RecordContentBlockingLog(const nsAString& aOrigin, uint32_t aType,
|
||||
void RecordContentBlockingLog(const nsACString& aOrigin, uint32_t aType,
|
||||
bool aBlocked) {
|
||||
mContentBlockingLog.RecordLog(aOrigin, aType, aBlocked);
|
||||
}
|
||||
|
|
|
@ -5037,8 +5037,8 @@ void nsGlobalWindowOuter::NotifyContentBlockingState(unsigned aState,
|
|||
return;
|
||||
}
|
||||
securityUI->GetState(&state);
|
||||
nsAutoString origin;
|
||||
nsContentUtils::GetUTFOrigin(aURIHint, origin);
|
||||
nsAutoCString origin;
|
||||
nsContentUtils::GetASCIIOrigin(aURIHint, origin);
|
||||
|
||||
bool blockedValue = aBlocked;
|
||||
bool unblocked = false;
|
||||
|
|
|
@ -903,7 +903,7 @@ child:
|
|||
/**
|
||||
* Requests the content blocking log, which is sent back in response.
|
||||
*/
|
||||
async GetContentBlockingLog() returns(nsString log, bool success);
|
||||
async GetContentBlockingLog() returns(nsCString log, bool success);
|
||||
|
||||
parent:
|
||||
/** Records a history visit. */
|
||||
|
|
|
@ -3082,14 +3082,14 @@ mozilla::ipc::IPCResult TabChild::RecvSetWidgetNativeData(
|
|||
mozilla::ipc::IPCResult TabChild::RecvGetContentBlockingLog(
|
||||
GetContentBlockingLogResolver&& aResolve) {
|
||||
bool success = false;
|
||||
nsAutoString result;
|
||||
nsAutoCString result;
|
||||
|
||||
if (nsCOMPtr<Document> doc = GetDocument()) {
|
||||
result = doc->GetContentBlockingLog()->Stringify();
|
||||
success = true;
|
||||
}
|
||||
|
||||
aResolve(Tuple<const nsString&, const bool&>(result, success));
|
||||
aResolve(Tuple<const nsCString&, const bool&>(result, success));
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
|
|
@ -2716,9 +2716,10 @@ TabParent::GetContentBlockingLog(Promise** aPromise) {
|
|||
|
||||
auto cblPromise = SendGetContentBlockingLog();
|
||||
cblPromise->Then(GetMainThreadSerialEventTarget(), __func__,
|
||||
[jsPromise](Tuple<nsString, bool>&& aResult) {
|
||||
[jsPromise](Tuple<nsCString, bool>&& aResult) {
|
||||
if (Get<1>(aResult)) {
|
||||
jsPromise->MaybeResolve(std::move(Get<0>(aResult)));
|
||||
NS_ConvertUTF8toUTF16 utf16(Get<0>(aResult));
|
||||
jsPromise->MaybeResolve(std::move(utf16));
|
||||
} else {
|
||||
jsPromise->MaybeRejectWithUndefined();
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ EXPORTS.mozilla.webrender += [
|
|||
'RenderTextureHostWrapper.h',
|
||||
'RenderThread.h',
|
||||
'webrender_ffi.h',
|
||||
'webrender_ffi_generated.h',
|
||||
'WebRenderAPI.h',
|
||||
'WebRenderTypes.h',
|
||||
]
|
||||
|
@ -71,6 +70,24 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk3'):
|
|||
CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
|
||||
CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']
|
||||
|
||||
if CONFIG['COMPILE_ENVIRONMENT']:
|
||||
GENERATED_FILES += [
|
||||
'webrender_ffi_generated.h',
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.webrender += [
|
||||
'!webrender_ffi_generated.h',
|
||||
]
|
||||
|
||||
ffi_generated = GENERATED_FILES['webrender_ffi_generated.h']
|
||||
ffi_generated.script = '/layout/style/RunCbindgen.py:generate'
|
||||
ffi_generated.inputs = [
|
||||
'/gfx/webrender_bindings',
|
||||
'/gfx/wr/webrender',
|
||||
'/gfx/wr/webrender_api',
|
||||
]
|
||||
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -63,6 +63,7 @@ TextRun fetch_text_run(int address) {
|
|||
|
||||
VertexInfo write_text_vertex(RectWithSize local_clip_rect,
|
||||
float z,
|
||||
bool should_snap,
|
||||
Transform transform,
|
||||
PictureTask task,
|
||||
vec2 text_offset,
|
||||
|
@ -73,13 +74,8 @@ VertexInfo write_text_vertex(RectWithSize local_clip_rect,
|
|||
vec2 snap_offset = vec2(0.0);
|
||||
mat2 local_transform;
|
||||
|
||||
#ifdef WR_FEATURE_GLYPH_TRANSFORM
|
||||
bool remove_subpx_offset = true;
|
||||
#else
|
||||
bool remove_subpx_offset = transform.is_axis_aligned;
|
||||
#endif
|
||||
// Compute the snapping offset only if the scroll node transform is axis-aligned.
|
||||
if (remove_subpx_offset) {
|
||||
if (should_snap) {
|
||||
// Transform from local space to device space.
|
||||
float device_scale = task.common_data.device_pixel_scale / transform.m[3].w;
|
||||
mat2 device_transform = mat2(transform.m) * device_scale;
|
||||
|
@ -178,6 +174,8 @@ void main(void) {
|
|||
RectWithSize glyph_rect = RectWithSize(res.offset + glyph_transform * (text.offset + glyph.offset),
|
||||
res.uv_rect.zw - res.uv_rect.xy);
|
||||
|
||||
// Since the glyph is pre-transformed, snapping is both forced and does not depend on the transform.
|
||||
bool should_snap = true;
|
||||
#else
|
||||
// Scale from glyph space to local space.
|
||||
float scale = res.scale / task.common_data.device_pixel_scale;
|
||||
|
@ -185,6 +183,9 @@ void main(void) {
|
|||
// Compute the glyph rect in local space.
|
||||
RectWithSize glyph_rect = RectWithSize(scale * res.offset + text.offset + glyph.offset,
|
||||
scale * (res.uv_rect.zw - res.uv_rect.xy));
|
||||
|
||||
// Check if the primitive is actually safe to snap.
|
||||
bool should_snap = ph.user_data.x != 0;
|
||||
#endif
|
||||
|
||||
vec2 snap_bias;
|
||||
|
@ -214,6 +215,7 @@ void main(void) {
|
|||
|
||||
VertexInfo vi = write_text_vertex(ph.local_clip_rect,
|
||||
ph.z,
|
||||
should_snap,
|
||||
transform,
|
||||
task,
|
||||
text.offset,
|
||||
|
|
|
@ -829,7 +829,7 @@ impl AlphaBatchBuilder {
|
|||
}
|
||||
};
|
||||
|
||||
let prim_header_index = prim_headers.push(&prim_header, z_id, [0; 3]);
|
||||
let prim_header_index = prim_headers.push(&prim_header, z_id, [run.should_snap as i32, 0, 0]);
|
||||
let key = BatchKey::new(kind, blend_mode, textures);
|
||||
let base_instance = GlyphInstance::new(
|
||||
prim_header_index,
|
||||
|
|
|
@ -65,6 +65,7 @@ impl AsInstanceKind<TextRunDataHandle> for TextRunKey {
|
|||
used_font: self.font.clone(),
|
||||
glyph_keys_range: storage::Range::empty(),
|
||||
shadow: self.shadow,
|
||||
should_snap: true,
|
||||
});
|
||||
|
||||
PrimitiveInstanceKind::TextRun{ data_handle, run_index }
|
||||
|
@ -227,6 +228,7 @@ pub struct TextRunPrimitive {
|
|||
pub used_font: FontInstance,
|
||||
pub glyph_keys_range: storage::Range<GlyphKey>,
|
||||
pub shadow: bool,
|
||||
pub should_snap: bool,
|
||||
}
|
||||
|
||||
impl TextRunPrimitive {
|
||||
|
@ -262,6 +264,11 @@ impl TextRunPrimitive {
|
|||
FontTransform::identity()
|
||||
};
|
||||
|
||||
// We can snap only if the transform is axis-aligned and in screen-space.
|
||||
self.should_snap =
|
||||
transform.preserves_2d_axis_alignment() &&
|
||||
raster_space == RasterSpace::Screen;
|
||||
|
||||
// If the transform or device size is different, then the caller of
|
||||
// this method needs to know to rebuild the glyphs.
|
||||
let cache_dirty =
|
||||
|
|
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 19 KiB После Ширина: | Высота: | Размер: 19 KiB |
|
@ -0,0 +1,10 @@
|
|||
root:
|
||||
items:
|
||||
- type: stacking-context
|
||||
bounds: [0, 0, 480, 80]
|
||||
raster-space: screen
|
||||
items:
|
||||
- text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz"
|
||||
origin: 20.5 50
|
||||
size: 20
|
||||
font: "FreeSans.ttf"
|
|
@ -0,0 +1,10 @@
|
|||
root:
|
||||
items:
|
||||
- type: stacking-context
|
||||
bounds: [0, 0, 480, 80]
|
||||
raster-space: local(1.0)
|
||||
items:
|
||||
- text: "a Bcd Efgh Ijklm Nopqrs Tuvwxyz"
|
||||
origin: 20.5 50
|
||||
size: 20
|
||||
font: "FreeSans.ttf"
|
|
@ -68,3 +68,4 @@ fuzzy(1,113) platform(linux) == raster-space.yaml raster-space.png
|
|||
== shadow-image.yaml shadow-solid-ref.yaml
|
||||
options(disable-aa) == snap-clip.yaml snap-clip-ref.yaml
|
||||
platform(linux) == perspective-clip.yaml perspective-clip.png
|
||||
options(disable-subpixel) != raster-space-snap.yaml raster-space-snap-ref.yaml
|
||||
|
|
Двоичные данные
gfx/wr/wrench/reftests/text/two-shadows.png
Двоичные данные
gfx/wr/wrench/reftests/text/two-shadows.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 11 KiB После Ширина: | Высота: | Размер: 11 KiB |
|
@ -7,6 +7,7 @@
|
|||
#ifndef gc_Allocator_h
|
||||
#define gc_Allocator_h
|
||||
|
||||
#include "gc/GCLock.h"
|
||||
#include "gc/Heap.h"
|
||||
#include "js/RootingAPI.h"
|
||||
|
||||
|
|
|
@ -216,6 +216,7 @@
|
|||
#include "gc/FindSCCs.h"
|
||||
#include "gc/FreeOp.h"
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCLock.h"
|
||||
#include "gc/GCTrace.h"
|
||||
#include "gc/Memory.h"
|
||||
#include "gc/Policy.h"
|
||||
|
@ -4225,8 +4226,8 @@ static void DiscardJITCodeForGC(JSRuntime* rt) {
|
|||
gcstats::AutoPhase ap(rt->gc.stats(),
|
||||
gcstats::PhaseKind::MARK_DISCARD_CODE);
|
||||
zone->discardJitCode(rt->defaultFreeOp(),
|
||||
/* discardBaselineCode = */ true,
|
||||
/* releaseTypes = */ true);
|
||||
Zone::DiscardBaselineCode,
|
||||
Zone::ReleaseTypes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||
* 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/. */
|
||||
|
||||
/*
|
||||
* GC-internal classes for acquiring and releasing the GC lock.
|
||||
*/
|
||||
|
||||
#ifndef gc_GCLock_h
|
||||
#define gc_GCLock_h
|
||||
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
class AutoUnlockGC;
|
||||
|
||||
/*
|
||||
* RAII class that takes the GC lock while it is live.
|
||||
*
|
||||
* Usually functions will pass const references of this class. However
|
||||
* non-const references can be used to either temporarily release the lock by
|
||||
* use of AutoUnlockGC or to start background allocation when the lock is
|
||||
* released.
|
||||
*/
|
||||
class MOZ_RAII AutoLockGC {
|
||||
public:
|
||||
explicit AutoLockGC(JSRuntime* rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: runtime_(rt) {
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
lock();
|
||||
}
|
||||
|
||||
~AutoLockGC() { lockGuard_.reset(); }
|
||||
|
||||
void lock() {
|
||||
MOZ_ASSERT(lockGuard_.isNothing());
|
||||
lockGuard_.emplace(runtime_->gc.lock);
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
MOZ_ASSERT(lockGuard_.isSome());
|
||||
lockGuard_.reset();
|
||||
}
|
||||
|
||||
protected:
|
||||
js::LockGuard<js::Mutex>& guard() { return lockGuard_.ref(); }
|
||||
|
||||
JSRuntime* runtime() const { return runtime_; }
|
||||
|
||||
private:
|
||||
JSRuntime* runtime_;
|
||||
mozilla::Maybe<js::LockGuard<js::Mutex>> lockGuard_;
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
AutoLockGC(const AutoLockGC&) = delete;
|
||||
AutoLockGC& operator=(const AutoLockGC&) = delete;
|
||||
};
|
||||
|
||||
/*
|
||||
* Same as AutoLockGC except it can optionally start a background chunk
|
||||
* allocation task when the lock is released.
|
||||
*/
|
||||
class MOZ_RAII AutoLockGCBgAlloc : public AutoLockGC {
|
||||
public:
|
||||
explicit AutoLockGCBgAlloc(JSRuntime* rt)
|
||||
: AutoLockGC(rt), startBgAlloc(false) {}
|
||||
|
||||
~AutoLockGCBgAlloc() {
|
||||
unlock();
|
||||
|
||||
/*
|
||||
* We have to do this after releasing the lock because it may acquire
|
||||
* the helper lock which could cause lock inversion if we still held
|
||||
* the GC lock.
|
||||
*/
|
||||
if (startBgAlloc) {
|
||||
runtime()->gc.startBackgroundAllocTaskIfIdle(); // Ignore failure.
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This can be used to start a background allocation task (if one isn't
|
||||
* already running) that allocates chunks and makes them available in the
|
||||
* free chunks list. This happens after the lock is released in order to
|
||||
* avoid lock inversion.
|
||||
*/
|
||||
void tryToStartBackgroundAllocation() { startBgAlloc = true; }
|
||||
|
||||
private:
|
||||
// true if we should start a background chunk allocation task after the
|
||||
// lock is released.
|
||||
bool startBgAlloc;
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoUnlockGC {
|
||||
public:
|
||||
explicit AutoUnlockGC(AutoLockGC& lock MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: lock(lock) {
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
~AutoUnlockGC() { lock.lock(); }
|
||||
|
||||
private:
|
||||
AutoLockGC& lock;
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
AutoUnlockGC(const AutoUnlockGC&) = delete;
|
||||
AutoUnlockGC& operator=(const AutoUnlockGC&) = delete;
|
||||
};
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* gc_GCLock_h */
|
|
@ -7,6 +7,7 @@
|
|||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCLock.h"
|
||||
#include "js/HashTable.h"
|
||||
#include "vm/Realm.h"
|
||||
#include "vm/Runtime.h"
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#endif
|
||||
|
||||
#include "gc/GCInternals.h"
|
||||
#include "gc/GCLock.h"
|
||||
#include "gc/PublicIterators.h"
|
||||
#include "gc/WeakMap.h"
|
||||
#include "gc/Zone.h"
|
||||
|
|
|
@ -195,8 +195,9 @@ void Zone::sweepWeakMaps() {
|
|||
WeakMapBase::sweepZone(this);
|
||||
}
|
||||
|
||||
void Zone::discardJitCode(FreeOp* fop, bool discardBaselineCode,
|
||||
bool releaseTypes) {
|
||||
void Zone::discardJitCode(FreeOp* fop,
|
||||
ShouldDiscardBaselineCode discardBaselineCode,
|
||||
ShouldReleaseTypes releaseTypes) {
|
||||
if (!jitZone()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -186,8 +186,19 @@ class Zone : public JS::shadow::Zone,
|
|||
|
||||
void findOutgoingEdges(js::gc::ZoneComponentFinder& finder);
|
||||
|
||||
void discardJitCode(js::FreeOp* fop, bool discardBaselineCode = true,
|
||||
bool releaseTypes = false);
|
||||
enum ShouldDiscardBaselineCode : bool {
|
||||
KeepBaselineCode = false,
|
||||
DiscardBaselineCode
|
||||
};
|
||||
|
||||
enum ShouldReleaseTypes : bool {
|
||||
KeepTypes = false,
|
||||
ReleaseTypes
|
||||
};
|
||||
|
||||
void discardJitCode(js::FreeOp* fop,
|
||||
ShouldDiscardBaselineCode discardBaselineCode = DiscardBaselineCode,
|
||||
ShouldReleaseTypes releaseTypes = KeepTypes);
|
||||
|
||||
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
|
||||
size_t* typePool, size_t* regexpZone,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
#include "gc/GCLock.h"
|
||||
#include "gc/GCRuntime.h"
|
||||
#include "gc/Heap.h"
|
||||
|
||||
|
|
|
@ -1012,104 +1012,6 @@ struct JSRuntime : public js::MallocProvider<JSRuntime> {
|
|||
|
||||
namespace js {
|
||||
|
||||
/*
|
||||
* RAII class that takes the GC lock while it is live.
|
||||
*
|
||||
* Usually functions will pass const references of this class. However
|
||||
* non-const references can be used to either temporarily release the lock by
|
||||
* use of AutoUnlockGC or to start background allocation when the lock is
|
||||
* released.
|
||||
*/
|
||||
class MOZ_RAII AutoLockGC {
|
||||
public:
|
||||
explicit AutoLockGC(JSRuntime* rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: runtime_(rt) {
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
lock();
|
||||
}
|
||||
|
||||
~AutoLockGC() { lockGuard_.reset(); }
|
||||
|
||||
void lock() {
|
||||
MOZ_ASSERT(lockGuard_.isNothing());
|
||||
lockGuard_.emplace(runtime_->gc.lock);
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
MOZ_ASSERT(lockGuard_.isSome());
|
||||
lockGuard_.reset();
|
||||
}
|
||||
|
||||
js::LockGuard<js::Mutex>& guard() { return lockGuard_.ref(); }
|
||||
|
||||
protected:
|
||||
JSRuntime* runtime() const { return runtime_; }
|
||||
|
||||
private:
|
||||
JSRuntime* runtime_;
|
||||
mozilla::Maybe<js::LockGuard<js::Mutex>> lockGuard_;
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
AutoLockGC(const AutoLockGC&) = delete;
|
||||
AutoLockGC& operator=(const AutoLockGC&) = delete;
|
||||
};
|
||||
|
||||
/*
|
||||
* Same as AutoLockGC except it can optionally start a background chunk
|
||||
* allocation task when the lock is released.
|
||||
*/
|
||||
class MOZ_RAII AutoLockGCBgAlloc : public AutoLockGC {
|
||||
public:
|
||||
explicit AutoLockGCBgAlloc(JSRuntime* rt)
|
||||
: AutoLockGC(rt), startBgAlloc(false) {}
|
||||
|
||||
~AutoLockGCBgAlloc() {
|
||||
unlock();
|
||||
|
||||
/*
|
||||
* We have to do this after releasing the lock because it may acquire
|
||||
* the helper lock which could cause lock inversion if we still held
|
||||
* the GC lock.
|
||||
*/
|
||||
if (startBgAlloc) {
|
||||
runtime()->gc.startBackgroundAllocTaskIfIdle(); // Ignore failure.
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This can be used to start a background allocation task (if one isn't
|
||||
* already running) that allocates chunks and makes them available in the
|
||||
* free chunks list. This happens after the lock is released in order to
|
||||
* avoid lock inversion.
|
||||
*/
|
||||
void tryToStartBackgroundAllocation() { startBgAlloc = true; }
|
||||
|
||||
private:
|
||||
// true if we should start a background chunk allocation task after the
|
||||
// lock is released.
|
||||
bool startBgAlloc;
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoUnlockGC {
|
||||
public:
|
||||
explicit AutoUnlockGC(AutoLockGC& lock MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: lock(lock) {
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
~AutoUnlockGC() { lock.lock(); }
|
||||
|
||||
private:
|
||||
AutoLockGC& lock;
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
|
||||
AutoUnlockGC(const AutoUnlockGC&) = delete;
|
||||
AutoUnlockGC& operator=(const AutoUnlockGC&) = delete;
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
static MOZ_ALWAYS_INLINE void MakeRangeGCSafe(Value* vec, size_t len) {
|
||||
// Don't PodZero here because JS::Value is non-trivial.
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
|
|
|
@ -4801,8 +4801,7 @@ AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM() {
|
|||
JSRuntime* rt = zone->runtimeFromMainThread();
|
||||
js::CancelOffThreadIonCompile(rt);
|
||||
zone->setPreservingCode(false);
|
||||
zone->discardJitCode(rt->defaultFreeOp(),
|
||||
/* discardBaselineCode = */ false);
|
||||
zone->discardJitCode(rt->defaultFreeOp(), Zone::KeepBaselineCode);
|
||||
zone->types.clearAllNewScriptsOnOOM();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,19 +8,19 @@ import mozpack.path as mozpath
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
STYLE = mozpath.join(buildconfig.topsrcdir, "servo", "components", "style")
|
||||
CARGO_LOCK = mozpath.join(buildconfig.topsrcdir, "Cargo.lock")
|
||||
|
||||
def generate(output, cbindgen_toml_path):
|
||||
def generate(output, cbindgen_crate_path, *in_tree_dependencies):
|
||||
env = os.environ.copy()
|
||||
env['CARGO'] = str(buildconfig.substs['CARGO'])
|
||||
|
||||
p = subprocess.Popen([
|
||||
buildconfig.substs['CBINDGEN'],
|
||||
mozpath.join(buildconfig.topsrcdir, "toolkit", "library", "rust"),
|
||||
"--lockfile",
|
||||
CARGO_LOCK,
|
||||
"--crate",
|
||||
"style"
|
||||
mozpath.basename(cbindgen_crate_path),
|
||||
], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
stdout, stderr = p.communicate()
|
||||
|
@ -31,9 +31,11 @@ def generate(output, cbindgen_toml_path):
|
|||
|
||||
deps = set()
|
||||
deps.add(CARGO_LOCK)
|
||||
for path, dirs, files in os.walk(STYLE):
|
||||
for file in files:
|
||||
if os.path.splitext(file)[1] == ".rs":
|
||||
deps.add(mozpath.join(path, file))
|
||||
deps.add(mozpath.join(cbindgen_crate_path, "cbindgen.toml"))
|
||||
for directory in in_tree_dependencies + (cbindgen_crate_path,):
|
||||
for path, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if os.path.splitext(file)[1] == ".rs":
|
||||
deps.add(mozpath.join(path, file))
|
||||
|
||||
return deps
|
|
@ -322,9 +322,9 @@ if CONFIG['COMPILE_ENVIRONMENT']:
|
|||
]
|
||||
|
||||
consts = GENERATED_FILES['ServoStyleConsts.h']
|
||||
consts.script = 'GenerateServoStyleConsts.py:generate'
|
||||
consts.script = 'RunCbindgen.py:generate'
|
||||
consts.inputs = [
|
||||
'/servo/components/style/cbindgen.toml',
|
||||
'/servo/components/style',
|
||||
]
|
||||
|
||||
CONFIGURE_SUBST_FILES += [
|
||||
|
|
|
@ -130,6 +130,7 @@ RULE_TEMPLATE = '''
|
|||
'''[1:]
|
||||
|
||||
MACRO_TEMPLATE = '''
|
||||
/// Returns a static atom by passing the literal string it represents.
|
||||
#[macro_export]
|
||||
macro_rules! atom {{
|
||||
{body}\
|
||||
|
|
|
@ -11,6 +11,8 @@ use std::borrow::Borrow;
|
|||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// In Gecko namespaces are just regular atoms, so this is a simple macro to
|
||||
/// forward one macro to the other.
|
||||
#[macro_export]
|
||||
macro_rules! ns {
|
||||
() => {
|
||||
|
|
|
@ -3821,7 +3821,14 @@ impl AliasId {
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE(emilio): Callers are responsible to deal with prefs.
|
||||
/// Call the given macro with tokens like this for each longhand and shorthand properties
|
||||
/// that is enabled in content:
|
||||
///
|
||||
/// ```
|
||||
/// [CamelCaseName, SetCamelCaseName, PropertyId::Longhand(LonghandId::CamelCaseName)],
|
||||
/// ```
|
||||
///
|
||||
/// NOTE(emilio): Callers are responsible to deal with prefs.
|
||||
#[macro_export]
|
||||
macro_rules! css_properties_accessors {
|
||||
($macro_name: ident) => {
|
||||
|
@ -3844,6 +3851,14 @@ macro_rules! css_properties_accessors {
|
|||
}
|
||||
}
|
||||
|
||||
/// Call the given macro with tokens like this for each longhand properties:
|
||||
///
|
||||
/// ```
|
||||
/// { snake_case_ident, true }
|
||||
/// ```
|
||||
///
|
||||
/// … where the boolean indicates whether the property value type
|
||||
/// is wrapped in a `Box<_>` in the corresponding `PropertyDeclaration` variant.
|
||||
#[macro_export]
|
||||
macro_rules! longhand_properties_idents {
|
||||
($macro_name: ident) => {
|
||||
|
|
|
@ -11,15 +11,19 @@
|
|||
// Animate for the computed types, instead of the generic types.
|
||||
|
||||
use super::{Animate, Procedure, ToAnimatedZero};
|
||||
use crate::values::computed::{GridTemplateComponent, TrackList, TrackSize};
|
||||
use crate::values::computed::Integer;
|
||||
use crate::values::computed::LengthPercentage;
|
||||
use crate::values::computed::{GridTemplateComponent, TrackList, TrackSize};
|
||||
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||
use crate::values::generics::grid as generics;
|
||||
|
||||
fn discrete<T: Clone>(from: &T, to: &T, procedure: Procedure) -> Result<T, ()> {
|
||||
if let Procedure::Interpolate { progress } = procedure {
|
||||
Ok(if progress < 0.5 { from.clone() } else { to.clone() })
|
||||
Ok(if progress < 0.5 {
|
||||
from.clone()
|
||||
} else {
|
||||
to.clone()
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
|
@ -28,31 +32,31 @@ fn discrete<T: Clone>(from: &T, to: &T, procedure: Procedure) -> Result<T, ()> {
|
|||
fn animate_with_discrete_fallback<T: Animate + Clone>(
|
||||
from: &T,
|
||||
to: &T,
|
||||
procedure: Procedure
|
||||
procedure: Procedure,
|
||||
) -> Result<T, ()> {
|
||||
from.animate(to, procedure).or_else(|_| discrete(from, to, procedure))
|
||||
from.animate(to, procedure)
|
||||
.or_else(|_| discrete(from, to, procedure))
|
||||
}
|
||||
|
||||
impl Animate for TrackSize {
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
match (self, other) {
|
||||
(&generics::TrackSize::Breadth(ref from),
|
||||
&generics::TrackSize::Breadth(ref to)) => {
|
||||
(&generics::TrackSize::Breadth(ref from), &generics::TrackSize::Breadth(ref to)) => {
|
||||
animate_with_discrete_fallback(from, to, procedure)
|
||||
.map(generics::TrackSize::Breadth)
|
||||
},
|
||||
(&generics::TrackSize::Minmax(ref from_min, ref from_max),
|
||||
&generics::TrackSize::Minmax(ref to_min, ref to_max)) => {
|
||||
Ok(generics::TrackSize::Minmax(
|
||||
animate_with_discrete_fallback(from_min, to_min, procedure)?,
|
||||
animate_with_discrete_fallback(from_max, to_max, procedure)?,
|
||||
))
|
||||
},
|
||||
(&generics::TrackSize::FitContent(ref from),
|
||||
&generics::TrackSize::FitContent(ref to)) => {
|
||||
animate_with_discrete_fallback(from, to, procedure)
|
||||
.map(generics::TrackSize::FitContent)
|
||||
},
|
||||
(
|
||||
&generics::TrackSize::Minmax(ref from_min, ref from_max),
|
||||
&generics::TrackSize::Minmax(ref to_min, ref to_max),
|
||||
) => Ok(generics::TrackSize::Minmax(
|
||||
animate_with_discrete_fallback(from_min, to_min, procedure)?,
|
||||
animate_with_discrete_fallback(from_max, to_max, procedure)?,
|
||||
)),
|
||||
(
|
||||
&generics::TrackSize::FitContent(ref from),
|
||||
&generics::TrackSize::FitContent(ref to),
|
||||
) => animate_with_discrete_fallback(from, to, procedure)
|
||||
.map(generics::TrackSize::FitContent),
|
||||
(_, _) => discrete(self, other, procedure),
|
||||
}
|
||||
}
|
||||
|
@ -72,8 +76,11 @@ where
|
|||
// length of track_sizes is the same.
|
||||
// https://github.com/w3c/csswg-drafts/issues/3503
|
||||
match (&self.count, &other.count) {
|
||||
(&generics::RepeatCount::Number(from),
|
||||
&generics::RepeatCount::Number(to)) if from == to => (),
|
||||
(&generics::RepeatCount::Number(from), &generics::RepeatCount::Number(to))
|
||||
if from == to =>
|
||||
{
|
||||
()
|
||||
},
|
||||
(_, _) => return Err(()),
|
||||
}
|
||||
|
||||
|
@ -83,7 +90,8 @@ where
|
|||
}
|
||||
|
||||
let count = self.count;
|
||||
let track_sizes = self.track_sizes
|
||||
let track_sizes = self
|
||||
.track_sizes
|
||||
.iter()
|
||||
.zip(other.track_sizes.iter())
|
||||
.map(|(a, b)| a.animate(b, procedure))
|
||||
|
@ -93,7 +101,11 @@ where
|
|||
// of |track_sizes|. Besides, <line-names> is always discrete.
|
||||
let line_names = discrete(&self.line_names, &other.line_names, procedure)?;
|
||||
|
||||
Ok(generics::TrackRepeat { count, line_names, track_sizes })
|
||||
Ok(generics::TrackRepeat {
|
||||
count,
|
||||
line_names,
|
||||
track_sizes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +134,8 @@ impl Animate for TrackList {
|
|||
|
||||
let list_type = self.list_type;
|
||||
let auto_repeat = self.auto_repeat.animate(&other.auto_repeat, procedure)?;
|
||||
let values = self.values
|
||||
let values = self
|
||||
.values
|
||||
.iter()
|
||||
.zip(other.values.iter())
|
||||
.map(|(a, b)| a.animate(b, procedure))
|
||||
|
@ -131,7 +144,12 @@ impl Animate for TrackList {
|
|||
// of |track_sizes|. Besides, <line-names> is always discrete.
|
||||
let line_names = discrete(&self.line_names, &other.line_names, procedure)?;
|
||||
|
||||
Ok(TrackList { list_type, values, line_names, auto_repeat })
|
||||
Ok(TrackList {
|
||||
list_type,
|
||||
values,
|
||||
line_names,
|
||||
auto_repeat,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ use crate::values::specified::box_ as specified;
|
|||
|
||||
pub use crate::values::specified::box_::{AnimationName, Appearance, BreakBetween, BreakWithin};
|
||||
pub use crate::values::specified::box_::{Clear as SpecifiedClear, Float as SpecifiedFloat};
|
||||
pub use crate::values::specified::box_::{Contain, Display, Overflow, OverflowAnchor, OverflowClipBox};
|
||||
pub use crate::values::specified::box_::{Contain, Display, Overflow};
|
||||
pub use crate::values::specified::box_::{OverflowAnchor, OverflowClipBox};
|
||||
pub use crate::values::specified::box_::{OverscrollBehavior, ScrollSnapType};
|
||||
pub use crate::values::specified::box_::{TouchAction, TransitionProperty, WillChange};
|
||||
|
||||
|
|
|
@ -174,14 +174,7 @@ pub enum TrackKeyword {
|
|||
///
|
||||
/// <https://drafts.csswg.org/css-grid/#typedef-track-breadth>
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
Debug,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
SpecifiedValueInfo,
|
||||
ToComputedValue,
|
||||
ToCss,
|
||||
Animate, Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss,
|
||||
)]
|
||||
pub enum TrackBreadth<L> {
|
||||
/// The generic type is almost always a non-negative `<length-percentage>`
|
||||
|
@ -492,14 +485,7 @@ impl<L: Clone> TrackRepeat<L, specified::Integer> {
|
|||
|
||||
/// Track list values. Can be <track-size> or <track-repeat>
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
Debug,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
SpecifiedValueInfo,
|
||||
ToComputedValue,
|
||||
ToCss
|
||||
Animate, Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss,
|
||||
)]
|
||||
pub enum TrackListValue<LengthPercentage, Integer> {
|
||||
/// A <track-size> value.
|
||||
|
@ -712,20 +698,17 @@ impl ToCss for LineNameList {
|
|||
/// Subgrid deferred to Level 2 spec due to lack of implementation.
|
||||
/// But it's implemented in gecko, so we have to as well.
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
Debug,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
SpecifiedValueInfo,
|
||||
ToComputedValue,
|
||||
ToCss
|
||||
Animate, Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss,
|
||||
)]
|
||||
pub enum GridTemplateComponent<L, I> {
|
||||
/// `none` value.
|
||||
None,
|
||||
/// The grid `<track-list>`
|
||||
TrackList(#[animation(field_bound)] #[compute(field_bound)] TrackList<L, I>),
|
||||
TrackList(
|
||||
#[animation(field_bound)]
|
||||
#[compute(field_bound)]
|
||||
TrackList<L, I>,
|
||||
),
|
||||
/// A `subgrid <line-name-list>?`
|
||||
/// TODO: Support animations for this after subgrid is addressed in [grid-2] spec.
|
||||
#[animation(error)]
|
||||
|
|
|
@ -36,7 +36,8 @@ pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
|
|||
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
|
||||
pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle};
|
||||
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
|
||||
pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float, Overflow, OverflowAnchor};
|
||||
pub use self::box_::{Appearance, BreakBetween, BreakWithin};
|
||||
pub use self::box_::{Clear, Float, Overflow, OverflowAnchor};
|
||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
|
||||
pub use self::box_::{ScrollSnapType, TouchAction, TransitionProperty, VerticalAlign, WillChange};
|
||||
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
||||
|
|
|
@ -88,7 +88,10 @@ fn derive_variant_arm(
|
|||
let field_attrs = cg::parse_field_attrs::<AnimationFieldAttrs>(&result.ast());
|
||||
if field_attrs.field_bound {
|
||||
let ty = &this.ast().ty;
|
||||
cg::add_predicate(where_clause, parse_quote!(#ty: crate::values::animated::Animate));
|
||||
cg::add_predicate(
|
||||
where_clause,
|
||||
parse_quote!(#ty: crate::values::animated::Animate),
|
||||
);
|
||||
}
|
||||
if field_attrs.constant {
|
||||
quote! {
|
||||
|
|
|
@ -158,24 +158,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serialize_function {
|
||||
($dest: expr, $name: ident($( $arg: expr, )+)) => {
|
||||
serialize_function!($dest, $name($($arg),+))
|
||||
};
|
||||
($dest: expr, $name: ident($first_arg: expr $( , $arg: expr )*)) => {
|
||||
{
|
||||
$dest.write_str(concat!(stringify!($name), "("))?;
|
||||
$first_arg.to_css($dest)?;
|
||||
$(
|
||||
$dest.write_str(", ")?;
|
||||
$arg.to_css($dest)?;
|
||||
)*
|
||||
$dest.write_char(')')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience wrapper to serialise CSS values separated by a given string.
|
||||
pub struct SequenceWriter<'a, 'b: 'a, W: 'b> {
|
||||
inner: &'a mut CssWriter<'b, W>,
|
||||
|
@ -450,7 +432,7 @@ impl_to_css_for_predefined_type!(::cssparser::RGBA);
|
|||
impl_to_css_for_predefined_type!(::cssparser::Color);
|
||||
impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
|
||||
|
||||
#[macro_export]
|
||||
/// Define an enum type with unit variants that each correspond to a CSS keyword.
|
||||
macro_rules! define_css_keyword_enum {
|
||||
(pub enum $name:ident { $($variant:ident = $css:expr,)+ }) => {
|
||||
#[allow(missing_docs)]
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
set -x -e -v
|
||||
|
||||
# If you update this, make sure to update the minimum version in
|
||||
# build/moz.configure/rust.configure as well.
|
||||
CBINDGEN_VERSION=v0.6.7
|
||||
# build/moz.configure/bindgen.configure as well.
|
||||
CBINDGEN_VERSION=v0.6.8
|
||||
TARGET="$1"
|
||||
|
||||
case "$(uname -s)" in
|
||||
|
|
|
@ -18,6 +18,7 @@ sys.path.insert(1, os.path.dirname(sys.path[0]))
|
|||
|
||||
from mozharness.base.log import FATAL
|
||||
from mozharness.base.script import BaseScript, PreScriptAction
|
||||
from mozharness.mozilla.automation import TBPL_RETRY
|
||||
from mozharness.mozilla.mozbase import MozbaseMixin
|
||||
from mozharness.mozilla.testing.android import AndroidMixin
|
||||
from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
|
||||
|
@ -402,7 +403,7 @@ class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMi
|
|||
"Not all tests were executed.<br/>")
|
||||
# Signal per-test time exceeded, to break out of suites and
|
||||
# suite categories loops also.
|
||||
return False
|
||||
return
|
||||
|
||||
final_cmd = copy.copy(cmd)
|
||||
if len(per_test_args) > 0:
|
||||
|
@ -431,6 +432,9 @@ class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMi
|
|||
if len(per_test_args) > 0:
|
||||
self.record_status(tbpl_status, level=log_level)
|
||||
self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
|
||||
if tbpl_status == TBPL_RETRY:
|
||||
self.info("Per-test run abandoned due to RETRY status")
|
||||
return
|
||||
else:
|
||||
self.record_status(tbpl_status, level=log_level)
|
||||
self.log("The %s suite: %s ran with return status: %s" %
|
||||
|
|
|
@ -18,6 +18,7 @@ sys.path.insert(1, os.path.dirname(sys.path[0]))
|
|||
|
||||
from mozharness.base.log import FATAL
|
||||
from mozharness.base.script import BaseScript, PreScriptAction
|
||||
from mozharness.mozilla.automation import TBPL_RETRY
|
||||
from mozharness.mozilla.mozbase import MozbaseMixin
|
||||
from mozharness.mozilla.testing.android import AndroidMixin
|
||||
from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
|
||||
|
@ -377,7 +378,7 @@ class AndroidHardwareTest(TestingMixin, BaseScript, MozbaseMixin,
|
|||
"Not all tests were executed.<br/>")
|
||||
# Signal per-test time exceeded, to break out of suites and
|
||||
# suite categories loops also.
|
||||
return False
|
||||
return
|
||||
|
||||
final_cmd = copy.copy(cmd)
|
||||
if len(per_test_args) > 0:
|
||||
|
@ -406,6 +407,9 @@ class AndroidHardwareTest(TestingMixin, BaseScript, MozbaseMixin,
|
|||
if len(per_test_args) > 0:
|
||||
self.record_status(tbpl_status, level=log_level)
|
||||
self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
|
||||
if tbpl_status == TBPL_RETRY:
|
||||
self.info("Per-test run abandoned due to RETRY status")
|
||||
return
|
||||
else:
|
||||
self.record_status(tbpl_status, level=log_level)
|
||||
self.log("The %s suite: %s ran with return status: %s" %
|
||||
|
|
|
@ -28,7 +28,7 @@ from mozharness.base.errors import BaseErrorList
|
|||
from mozharness.base.log import INFO
|
||||
from mozharness.base.script import PreScriptAction
|
||||
from mozharness.base.vcs.vcsbase import MercurialScript
|
||||
from mozharness.mozilla.automation import TBPL_EXCEPTION
|
||||
from mozharness.mozilla.automation import TBPL_EXCEPTION, TBPL_RETRY
|
||||
from mozharness.mozilla.mozbase import MozbaseMixin
|
||||
from mozharness.mozilla.structuredlog import StructuredOutputParser
|
||||
from mozharness.mozilla.testing.errors import HarnessErrorList
|
||||
|
@ -943,6 +943,9 @@ class DesktopUnittest(TestingMixin, MercurialScript, MozbaseMixin,
|
|||
self.record_status(tbpl_status, level=log_level)
|
||||
if len(per_test_args) > 0:
|
||||
self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
|
||||
if tbpl_status == TBPL_RETRY:
|
||||
self.info("Per-test run abandoned due to RETRY status")
|
||||
return False
|
||||
else:
|
||||
self.log("The %s suite: %s ran with return status: %s" %
|
||||
(suite_category, suite, tbpl_status), level=log_level)
|
||||
|
|
|
@ -19,6 +19,7 @@ import mozinfo
|
|||
from mozharness.base.errors import BaseErrorList
|
||||
from mozharness.base.script import PreScriptAction
|
||||
from mozharness.base.vcs.vcsbase import MercurialScript
|
||||
from mozharness.mozilla.automation import TBPL_RETRY
|
||||
from mozharness.mozilla.testing.android import AndroidMixin
|
||||
from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
|
||||
from mozharness.mozilla.testing.codecoverage import (
|
||||
|
@ -444,6 +445,9 @@ class WebPlatformTest(TestingMixin, MercurialScript, CodeCoverageMixin, AndroidM
|
|||
|
||||
if len(per_test_args) > 0:
|
||||
self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
|
||||
if tbpl_status == TBPL_RETRY:
|
||||
self.info("Per-test run abandoned due to RETRY status")
|
||||
return
|
||||
|
||||
|
||||
# main {{{1
|
||||
|
|
|
@ -135,11 +135,11 @@ Channel::Channel(size_t aId, bool aMiddlemanRecording,
|
|||
}
|
||||
|
||||
while (true) {
|
||||
Message* msg = channel->WaitForMessage();
|
||||
Message::UniquePtr msg = channel->WaitForMessage();
|
||||
if (!msg) {
|
||||
break;
|
||||
}
|
||||
channel->mHandler(msg);
|
||||
channel->mHandler(std::move(msg));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ void Channel::SendMessage(const Message& aMsg) {
|
|||
}
|
||||
}
|
||||
|
||||
Message* Channel::WaitForMessage() {
|
||||
Message::UniquePtr Channel::WaitForMessage() {
|
||||
if (!mMessageBuffer) {
|
||||
mMessageBuffer = (MessageBuffer*)AllocateMemory(sizeof(MessageBuffer),
|
||||
MemoryKind::Generic);
|
||||
|
@ -216,7 +216,7 @@ Message* Channel::WaitForMessage() {
|
|||
mMessageBytes += nbytes;
|
||||
}
|
||||
|
||||
Message* res = ((Message*)mMessageBuffer->begin())->Clone();
|
||||
Message::UniquePtr res = ((Message*)mMessageBuffer->begin())->Clone();
|
||||
|
||||
// Remove the message we just received from the incoming buffer.
|
||||
size_t remaining = mMessageBytes - messageSize;
|
||||
|
@ -237,18 +237,15 @@ void Channel::PrintMessage(const char* aPrefix, const Message& aMsg) {
|
|||
AutoEnsurePassThroughThreadEvents pt;
|
||||
nsCString data;
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::HitCheckpoint: {
|
||||
const HitCheckpointMessage& nmsg = (const HitCheckpointMessage&)aMsg;
|
||||
data.AppendPrintf("Id %d Endpoint %d Duration %.2f ms",
|
||||
(int)nmsg.mCheckpointId, nmsg.mRecordingEndpoint,
|
||||
case MessageType::HitExecutionPoint: {
|
||||
const HitExecutionPointMessage& nmsg =
|
||||
(const HitExecutionPointMessage&)aMsg;
|
||||
nmsg.mPoint.ToString(data);
|
||||
data.AppendPrintf(" Endpoint %d Duration %.2f ms",
|
||||
nmsg.mRecordingEndpoint,
|
||||
nmsg.mDurationMicroseconds / 1000.0);
|
||||
break;
|
||||
}
|
||||
case MessageType::HitBreakpoint: {
|
||||
const HitBreakpointMessage& nmsg = (const HitBreakpointMessage&)aMsg;
|
||||
data.AppendPrintf("Endpoint %d", nmsg.mRecordingEndpoint);
|
||||
break;
|
||||
}
|
||||
case MessageType::Resume: {
|
||||
const ResumeMessage& nmsg = (const ResumeMessage&)aMsg;
|
||||
data.AppendPrintf("Forward %d", nmsg.mForward);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "mozilla/gfx/Types.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include "File.h"
|
||||
#include "JSControl.h"
|
||||
|
@ -76,8 +77,8 @@ namespace recordreplay {
|
|||
\
|
||||
/* Add a breakpoint position to stop at. Because a single entry point is used for */ \
|
||||
/* calling into the ReplayDebugger after pausing, the set of breakpoints is simply */ \
|
||||
/* a set of positions at which the child process should pause and send a HitBreakpoint */ \
|
||||
/* message. */ \
|
||||
/* a set of positions at which the child process should pause and send a */ \
|
||||
/* HitExecutionPoint message. */ \
|
||||
_Macro(AddBreakpoint) \
|
||||
\
|
||||
/* Clear all installed breakpoints. */ \
|
||||
|
@ -126,10 +127,8 @@ namespace recordreplay {
|
|||
/* The child's graphics were repainted. */ \
|
||||
_Macro(Paint) \
|
||||
\
|
||||
/* Notify the middleman that a checkpoint or breakpoint was hit. */ \
|
||||
/* The child will pause after sending these messages. */ \
|
||||
_Macro(HitCheckpoint) \
|
||||
_Macro(HitBreakpoint) \
|
||||
/* Notify the middleman that the child has hit an execution point and paused. */ \
|
||||
_Macro(HitExecutionPoint) \
|
||||
\
|
||||
/* Send a response to a DebuggerRequest message. */ \
|
||||
_Macro(DebuggerResponse) \
|
||||
|
@ -162,10 +161,13 @@ struct Message {
|
|||
}
|
||||
|
||||
public:
|
||||
Message* Clone() const {
|
||||
char* res = (char*)malloc(mSize);
|
||||
struct FreePolicy { void operator()(Message* msg) { /*free(msg);*/ } };
|
||||
typedef UniquePtr<Message, FreePolicy> UniquePtr;
|
||||
|
||||
UniquePtr Clone() const {
|
||||
Message* res = static_cast<Message*>(malloc(mSize));
|
||||
memcpy(res, this, mSize);
|
||||
return (Message*)res;
|
||||
return UniquePtr(res);
|
||||
}
|
||||
|
||||
const char* TypeString() const {
|
||||
|
@ -381,30 +383,26 @@ struct PaintMessage : public Message {
|
|||
mHeight(aHeight) {}
|
||||
};
|
||||
|
||||
struct HitCheckpointMessage : public Message {
|
||||
uint32_t mCheckpointId;
|
||||
struct HitExecutionPointMessage : public Message {
|
||||
// The point the child paused at.
|
||||
js::ExecutionPoint mPoint;
|
||||
|
||||
// Whether the pause occurred due to hitting the end of the recording.
|
||||
bool mRecordingEndpoint;
|
||||
|
||||
// When recording, the amount of non-idle time taken to get to this
|
||||
// checkpoint from the previous one.
|
||||
// The amount of non-idle time taken to get to this pause from the last time
|
||||
// the child paused.
|
||||
double mDurationMicroseconds;
|
||||
|
||||
HitCheckpointMessage(uint32_t aCheckpointId, bool aRecordingEndpoint,
|
||||
double aDurationMicroseconds)
|
||||
: Message(MessageType::HitCheckpoint, sizeof(*this)),
|
||||
mCheckpointId(aCheckpointId),
|
||||
HitExecutionPointMessage(const js::ExecutionPoint& aPoint,
|
||||
bool aRecordingEndpoint,
|
||||
double aDurationMicroseconds)
|
||||
: Message(MessageType::HitExecutionPoint, sizeof(*this)),
|
||||
mPoint(aPoint),
|
||||
mRecordingEndpoint(aRecordingEndpoint),
|
||||
mDurationMicroseconds(aDurationMicroseconds) {}
|
||||
};
|
||||
|
||||
struct HitBreakpointMessage : public Message {
|
||||
bool mRecordingEndpoint;
|
||||
|
||||
explicit HitBreakpointMessage(bool aRecordingEndpoint)
|
||||
: Message(MessageType::HitBreakpoint, sizeof(*this)),
|
||||
mRecordingEndpoint(aRecordingEndpoint) {}
|
||||
};
|
||||
|
||||
typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints>
|
||||
AlwaysMarkMajorCheckpointsMessage;
|
||||
|
||||
|
@ -445,7 +443,7 @@ class Channel {
|
|||
public:
|
||||
// Note: the handler is responsible for freeing its input message. It will be
|
||||
// called on the channel's message thread.
|
||||
typedef std::function<void(Message*)> MessageHandler;
|
||||
typedef std::function<void(Message::UniquePtr)> MessageHandler;
|
||||
|
||||
private:
|
||||
// ID for this channel, unique for the middleman.
|
||||
|
@ -479,7 +477,7 @@ class Channel {
|
|||
|
||||
// Block until a complete message is received from the other side of the
|
||||
// channel.
|
||||
Message* WaitForMessage();
|
||||
Message::UniquePtr WaitForMessage();
|
||||
|
||||
// Main routine for the channel's thread.
|
||||
static void ThreadMain(void* aChannel);
|
||||
|
|
|
@ -58,26 +58,28 @@ static FileHandle gCheckpointReadFd;
|
|||
|
||||
// Copy of the introduction message we got from the middleman. This is saved on
|
||||
// receipt and then processed during InitRecordingOrReplayingProcess.
|
||||
static IntroductionMessage* gIntroductionMessage;
|
||||
static UniquePtr<IntroductionMessage, Message::FreePolicy> gIntroductionMessage;
|
||||
|
||||
// When recording, whether developer tools server code runs in the middleman.
|
||||
static bool gDebuggerRunsInMiddleman;
|
||||
|
||||
// Any response received to the last MiddlemanCallRequest message.
|
||||
static MiddlemanCallResponseMessage* gCallResponseMessage;
|
||||
static UniquePtr<MiddlemanCallResponseMessage, Message::FreePolicy>
|
||||
gCallResponseMessage;
|
||||
|
||||
// Whether some thread has sent a MiddlemanCallRequest and is waiting for
|
||||
// gCallResponseMessage to be filled in.
|
||||
static bool gWaitingForCallResponse;
|
||||
|
||||
// Processing routine for incoming channel messages.
|
||||
static void ChannelMessageHandler(Message* aMsg) {
|
||||
static void ChannelMessageHandler(Message::UniquePtr aMsg) {
|
||||
MOZ_RELEASE_ASSERT(MainThreadShouldPause() || aMsg->CanBeSentWhileUnpaused());
|
||||
|
||||
switch (aMsg->mType) {
|
||||
case MessageType::Introduction: {
|
||||
MOZ_RELEASE_ASSERT(!gIntroductionMessage);
|
||||
gIntroductionMessage = (IntroductionMessage*)aMsg->Clone();
|
||||
gIntroductionMessage.reset(
|
||||
static_cast<IntroductionMessage*>(aMsg.release()));
|
||||
break;
|
||||
}
|
||||
case MessageType::CreateCheckpoint: {
|
||||
|
@ -177,16 +179,14 @@ static void ChannelMessageHandler(Message* aMsg) {
|
|||
MonitorAutoLock lock(*gMonitor);
|
||||
MOZ_RELEASE_ASSERT(gWaitingForCallResponse);
|
||||
MOZ_RELEASE_ASSERT(!gCallResponseMessage);
|
||||
gCallResponseMessage = (MiddlemanCallResponseMessage*)aMsg;
|
||||
aMsg = nullptr; // Avoid freeing the message below.
|
||||
gCallResponseMessage.reset(
|
||||
static_cast<MiddlemanCallResponseMessage*>(aMsg.release()));
|
||||
gMonitor->NotifyAll();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
free(aMsg);
|
||||
}
|
||||
|
||||
// Main routine for a thread whose sole purpose is to listen to requests from
|
||||
|
@ -294,7 +294,7 @@ void InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv) {
|
|||
|
||||
// We are ready to receive initialization messages from the middleman, pause
|
||||
// so they can be sent.
|
||||
HitCheckpoint(CheckpointId::Invalid, /* aRecordingEndpoint = */ false);
|
||||
HitExecutionPoint(js::ExecutionPoint(), /* aRecordingEndpoint = */ false);
|
||||
|
||||
// If we failed to initialize then report it to the user.
|
||||
if (gInitializationFailureMessage) {
|
||||
|
@ -322,7 +322,6 @@ void InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv) {
|
|||
free(msg);
|
||||
}
|
||||
|
||||
free(gIntroductionMessage);
|
||||
gIntroductionMessage = nullptr;
|
||||
|
||||
// Some argument manipulation code expects a null pointer at the end.
|
||||
|
@ -634,8 +633,8 @@ bool CurrentRepaintCannotFail() {
|
|||
// Checkpoint Messages
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The time when the last HitCheckpoint message was sent.
|
||||
static double gLastCheckpointTime;
|
||||
// The time when the last HitExecutionPoint message was sent.
|
||||
static double gLastPauseTime;
|
||||
|
||||
// When recording and we are idle, the time when we became idle.
|
||||
static double gIdleTimeStart;
|
||||
|
@ -650,23 +649,24 @@ void EndIdleTime() {
|
|||
|
||||
// Erase the idle time from our measurements by advancing the last checkpoint
|
||||
// time.
|
||||
gLastCheckpointTime += CurrentTime() - gIdleTimeStart;
|
||||
gLastPauseTime += CurrentTime() - gIdleTimeStart;
|
||||
gIdleTimeStart = 0;
|
||||
}
|
||||
|
||||
void HitCheckpoint(size_t aId, bool aRecordingEndpoint) {
|
||||
void HitExecutionPoint(const js::ExecutionPoint& aPoint,
|
||||
bool aRecordingEndpoint) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
double time = CurrentTime();
|
||||
PauseMainThreadAndInvokeCallback([=]() {
|
||||
double duration = 0;
|
||||
if (aId > CheckpointId::First) {
|
||||
duration = time - gLastCheckpointTime;
|
||||
if (gLastPauseTime) {
|
||||
duration = time - gLastPauseTime;
|
||||
MOZ_RELEASE_ASSERT(duration > 0);
|
||||
}
|
||||
gChannel->SendMessage(
|
||||
HitCheckpointMessage(aId, aRecordingEndpoint, duration));
|
||||
HitExecutionPointMessage(aPoint, aRecordingEndpoint, duration));
|
||||
});
|
||||
gLastCheckpointTime = time;
|
||||
gLastPauseTime = time;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -680,13 +680,6 @@ void RespondToRequest(const js::CharBuffer& aBuffer) {
|
|||
free(msg);
|
||||
}
|
||||
|
||||
void HitBreakpoint(bool aRecordingEndpoint) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
PauseMainThreadAndInvokeCallback([=]() {
|
||||
gChannel->SendMessage(HitBreakpointMessage(aRecordingEndpoint));
|
||||
});
|
||||
}
|
||||
|
||||
void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
|
||||
InfallibleVector<char>* aOutputData) {
|
||||
AutoPassThroughThreadEvents pt;
|
||||
|
@ -709,7 +702,6 @@ void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize,
|
|||
aOutputData->append(gCallResponseMessage->BinaryData(),
|
||||
gCallResponseMessage->BinaryDataSize());
|
||||
|
||||
free(gCallResponseMessage);
|
||||
gCallResponseMessage = nullptr;
|
||||
gWaitingForCallResponse = false;
|
||||
|
||||
|
|
|
@ -87,8 +87,7 @@ namespace child {
|
|||
|
||||
// IPC activity that can be triggered by navigation.
|
||||
void RespondToRequest(const js::CharBuffer& aBuffer);
|
||||
void HitCheckpoint(size_t aId, bool aRecordingEndpoint);
|
||||
void HitBreakpoint(bool aRecordingEndpoint);
|
||||
void HitExecutionPoint(const js::ExecutionPoint& aPoint, bool aRecordingEndpoint);
|
||||
|
||||
// Optional information about a crash that occurred. If not provided to
|
||||
// ReportFatalError, the current thread will be treated as crashed.
|
||||
|
|
|
@ -15,23 +15,6 @@ namespace navigation {
|
|||
typedef js::BreakpointPosition BreakpointPosition;
|
||||
typedef js::ExecutionPoint ExecutionPoint;
|
||||
|
||||
static void BreakpointPositionToString(const BreakpointPosition& aPos,
|
||||
nsAutoCString& aStr) {
|
||||
aStr.AppendPrintf("{ Kind: %s, Script: %d, Offset: %d, Frame: %d }",
|
||||
aPos.KindString(), (int)aPos.mScript, (int)aPos.mOffset,
|
||||
(int)aPos.mFrameIndex);
|
||||
}
|
||||
|
||||
static void ExecutionPointToString(const ExecutionPoint& aPoint,
|
||||
nsAutoCString& aStr) {
|
||||
aStr.AppendPrintf("{ Checkpoint %d", (int)aPoint.mCheckpoint);
|
||||
if (aPoint.HasPosition()) {
|
||||
aStr.AppendPrintf(" Progress %llu Position ", aPoint.mProgress);
|
||||
BreakpointPositionToString(aPoint.mPosition, aStr);
|
||||
}
|
||||
aStr.AppendPrintf(" }");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Navigation State
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -232,10 +215,10 @@ class ReachBreakpointPhase final : public NavigationPhase {
|
|||
|
||||
void ToString(nsAutoCString& aStr) override {
|
||||
aStr.AppendPrintf("ReachBreakpoint: ");
|
||||
ExecutionPointToString(mPoint, aStr);
|
||||
mPoint.ToString(aStr);
|
||||
if (mTemporaryCheckpoint.isSome()) {
|
||||
aStr.AppendPrintf(" TemporaryCheckpoint: ");
|
||||
ExecutionPointToString(mTemporaryCheckpoint.ref(), aStr);
|
||||
mTemporaryCheckpoint.ref().ToString(aStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,11 +477,7 @@ void PausedPhase::Enter(const ExecutionPoint& aPoint, bool aRewind,
|
|||
Unreachable();
|
||||
}
|
||||
|
||||
if (aPoint.HasPosition()) {
|
||||
child::HitBreakpoint(aRecordingEndpoint);
|
||||
} else {
|
||||
child::HitCheckpoint(aPoint.mCheckpoint, aRecordingEndpoint);
|
||||
}
|
||||
child::HitExecutionPoint(aPoint, aRecordingEndpoint);
|
||||
}
|
||||
|
||||
void PausedPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
||||
|
@ -507,7 +486,7 @@ void PausedPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) {
|
|||
// We just rewound here, and are now where we should pause.
|
||||
MOZ_RELEASE_ASSERT(
|
||||
mPoint == gNavigation->CheckpointExecutionPoint(aCheckpoint.mNormal));
|
||||
child::HitCheckpoint(mPoint.mCheckpoint, mRecordingEndpoint);
|
||||
child::HitExecutionPoint(mPoint, mRecordingEndpoint);
|
||||
} else {
|
||||
// We just saved or restored the temporary checkpoint taken while
|
||||
// processing debugger requests here.
|
||||
|
@ -785,7 +764,7 @@ void ForwardPhase::PositionHit(const ExecutionPoint& aPoint) {
|
|||
|
||||
void ForwardPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint) {
|
||||
nsAutoCString str;
|
||||
ExecutionPointToString(aPoint, str);
|
||||
aPoint.ToString(str);
|
||||
|
||||
gNavigation->mPausedPhase.Enter(aPoint, /* aRewind = */ false,
|
||||
/* aRecordingEndpoint = */ true);
|
||||
|
|
|
@ -29,17 +29,10 @@ static bool gChildrenAreDebugging;
|
|||
}
|
||||
|
||||
ChildProcessInfo::ChildProcessInfo(
|
||||
UniquePtr<ChildRole> aRole,
|
||||
const Maybe<RecordingProcessData>& aRecordingProcessData)
|
||||
: mChannel(nullptr),
|
||||
mRecording(aRecordingProcessData.isSome()),
|
||||
mRecoveryStage(RecoveryStage::None),
|
||||
mPaused(false),
|
||||
mPausedMessage(nullptr),
|
||||
mLastCheckpoint(CheckpointId::Invalid),
|
||||
mNumRecoveredMessages(0),
|
||||
mRole(std::move(aRole)),
|
||||
mPauseNeeded(false),
|
||||
mHasBegunFatalError(false),
|
||||
mHasFatalError(false) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
@ -50,19 +43,7 @@ ChildProcessInfo::ChildProcessInfo(
|
|||
gChildrenAreDebugging = !!getenv("WAIT_AT_START");
|
||||
}
|
||||
|
||||
mRole->SetProcess(this);
|
||||
|
||||
LaunchSubprocess(aRecordingProcessData);
|
||||
|
||||
// Replaying processes always save the first checkpoint, if saving
|
||||
// checkpoints is allowed. This is currently assumed by the rewinding
|
||||
// mechanism in the replaying process, and would be nice to investigate
|
||||
// removing.
|
||||
if (!IsRecording() && CanRewind()) {
|
||||
SendMessage(SetSaveCheckpointMessage(CheckpointId::First, true));
|
||||
}
|
||||
|
||||
mRole->Initialize();
|
||||
}
|
||||
|
||||
ChildProcessInfo::~ChildProcessInfo() {
|
||||
|
@ -72,147 +53,60 @@ ChildProcessInfo::~ChildProcessInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
ChildProcessInfo::Disposition ChildProcessInfo::GetDisposition() {
|
||||
// We can determine the disposition of the child by looking at the first
|
||||
// resume message sent since the last time it reached a checkpoint.
|
||||
for (Message* msg : mMessages) {
|
||||
if (msg->mType == MessageType::Resume) {
|
||||
const ResumeMessage& nmsg = static_cast<const ResumeMessage&>(*msg);
|
||||
return nmsg.mForward ? AfterLastCheckpoint : BeforeLastCheckpoint;
|
||||
}
|
||||
if (msg->mType == MessageType::RunToPoint) {
|
||||
return AfterLastCheckpoint;
|
||||
}
|
||||
}
|
||||
return AtLastCheckpoint;
|
||||
}
|
||||
|
||||
bool ChildProcessInfo::IsPausedAtCheckpoint() {
|
||||
return IsPaused() && mPausedMessage->mType == MessageType::HitCheckpoint;
|
||||
}
|
||||
|
||||
bool ChildProcessInfo::IsPausedAtRecordingEndpoint() {
|
||||
if (!IsPaused()) {
|
||||
return false;
|
||||
}
|
||||
if (mPausedMessage->mType == MessageType::HitCheckpoint) {
|
||||
return static_cast<HitCheckpointMessage*>(mPausedMessage)
|
||||
->mRecordingEndpoint;
|
||||
}
|
||||
if (mPausedMessage->mType == MessageType::HitBreakpoint) {
|
||||
return static_cast<HitBreakpointMessage*>(mPausedMessage)
|
||||
->mRecordingEndpoint;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChildProcessInfo::GetInstalledBreakpoints(
|
||||
InfallibleVector<AddBreakpointMessage*>& aBreakpoints) {
|
||||
MOZ_RELEASE_ASSERT(aBreakpoints.empty());
|
||||
for (Message* msg : mMessages) {
|
||||
if (msg->mType == MessageType::AddBreakpoint) {
|
||||
aBreakpoints.append(static_cast<AddBreakpointMessage*>(msg));
|
||||
} else if (msg->mType == MessageType::ClearBreakpoints) {
|
||||
aBreakpoints.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChildProcessInfo::AddMajorCheckpoint(size_t aId) {
|
||||
// Major checkpoints should be listed in order.
|
||||
MOZ_RELEASE_ASSERT(mMajorCheckpoints.empty() ||
|
||||
aId > mMajorCheckpoints.back());
|
||||
mMajorCheckpoints.append(aId);
|
||||
}
|
||||
|
||||
void ChildProcessInfo::SetRole(UniquePtr<ChildRole> aRole) {
|
||||
MOZ_RELEASE_ASSERT(!IsRecovering());
|
||||
|
||||
PrintSpew("SetRole:%d %s\n", (int)GetId(),
|
||||
ChildRole::TypeString(aRole->GetType()));
|
||||
|
||||
mRole = std::move(aRole);
|
||||
mRole->SetProcess(this);
|
||||
mRole->Initialize();
|
||||
}
|
||||
|
||||
void ChildProcessInfo::OnIncomingMessage(size_t aChannelId,
|
||||
const Message& aMsg) {
|
||||
void ChildProcessInfo::OnIncomingMessage(const Message& aMsg,
|
||||
bool aForwardToControl) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Ignore messages from channels for subprocesses we terminated already.
|
||||
if (aChannelId != mChannel->GetId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always handle fatal errors in the same way.
|
||||
if (aMsg.mType == MessageType::BeginFatalError) {
|
||||
mHasBegunFatalError = true;
|
||||
return;
|
||||
} else if (aMsg.mType == MessageType::FatalError) {
|
||||
mHasFatalError = true;
|
||||
const FatalErrorMessage& nmsg = static_cast<const FatalErrorMessage&>(aMsg);
|
||||
OnCrash(nmsg.Error());
|
||||
return;
|
||||
}
|
||||
|
||||
mLastMessageTime = TimeStamp::Now();
|
||||
|
||||
if (IsRecovering()) {
|
||||
OnIncomingRecoveryMessage(aMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update paused state.
|
||||
MOZ_RELEASE_ASSERT(!IsPaused());
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::HitCheckpoint:
|
||||
case MessageType::HitBreakpoint:
|
||||
MOZ_RELEASE_ASSERT(!mPausedMessage);
|
||||
mPausedMessage = aMsg.Clone();
|
||||
MOZ_FALLTHROUGH;
|
||||
case MessageType::DebuggerResponse:
|
||||
case MessageType::RecordingFlushed:
|
||||
MOZ_RELEASE_ASSERT(mPausedMessage);
|
||||
case MessageType::BeginFatalError:
|
||||
mHasBegunFatalError = true;
|
||||
return;
|
||||
case MessageType::FatalError: {
|
||||
mHasFatalError = true;
|
||||
const FatalErrorMessage& nmsg =
|
||||
static_cast<const FatalErrorMessage&>(aMsg);
|
||||
OnCrash(nmsg.Error());
|
||||
return;
|
||||
}
|
||||
case MessageType::HitExecutionPoint: {
|
||||
const HitExecutionPointMessage& nmsg =
|
||||
static_cast<const HitExecutionPointMessage&>(aMsg);
|
||||
mPaused = true;
|
||||
if (this == GetActiveChild() && !nmsg.mPoint.HasPosition()) {
|
||||
MaybeUpdateGraphicsAtCheckpoint(nmsg.mPoint.mCheckpoint);
|
||||
}
|
||||
if (aForwardToControl) {
|
||||
js::ForwardHitExecutionPointMessage(GetId(), nmsg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageType::Paint:
|
||||
MaybeUpdateGraphicsAtPaint(static_cast<const PaintMessage&>(aMsg));
|
||||
break;
|
||||
case MessageType::DebuggerResponse:
|
||||
mPaused = true;
|
||||
js::OnDebuggerResponse(aMsg);
|
||||
break;
|
||||
case MessageType::RecordingFlushed:
|
||||
mPaused = true;
|
||||
break;
|
||||
case MessageType::MiddlemanCallRequest: {
|
||||
const MiddlemanCallRequestMessage& nmsg =
|
||||
static_cast<const MiddlemanCallRequestMessage&>(aMsg);
|
||||
Message::UniquePtr response(ProcessMiddlemanCallMessage(nmsg));
|
||||
SendMessage(*response);
|
||||
break;
|
||||
}
|
||||
case MessageType::ResetMiddlemanCalls:
|
||||
ResetMiddlemanCalls();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (aMsg.mType == MessageType::HitCheckpoint) {
|
||||
const HitCheckpointMessage& nmsg =
|
||||
static_cast<const HitCheckpointMessage&>(aMsg);
|
||||
mLastCheckpoint = nmsg.mCheckpointId;
|
||||
|
||||
// All messages sent since the last checkpoint are now obsolete, except
|
||||
// those which establish the set of installed breakpoints.
|
||||
InfallibleVector<Message*> newMessages;
|
||||
for (Message* msg : mMessages) {
|
||||
if (msg->mType == MessageType::AddBreakpoint) {
|
||||
newMessages.append(msg);
|
||||
} else {
|
||||
if (msg->mType == MessageType::ClearBreakpoints) {
|
||||
for (Message* existing : newMessages) {
|
||||
free(existing);
|
||||
}
|
||||
newMessages.clear();
|
||||
}
|
||||
free(msg);
|
||||
}
|
||||
}
|
||||
mMessages = std::move(newMessages);
|
||||
}
|
||||
|
||||
// The primordial HitCheckpoint messages is not forwarded to the role, as it
|
||||
// has not been initialized yet.
|
||||
if (aMsg.mType != MessageType::HitCheckpoint || mLastCheckpoint) {
|
||||
mRole->OnIncomingMessage(aMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void ChildProcessInfo::SendMessage(const Message& aMsg) {
|
||||
MOZ_RELEASE_ASSERT(!IsRecovering());
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Update paused state.
|
||||
|
@ -221,9 +115,6 @@ void ChildProcessInfo::SendMessage(const Message& aMsg) {
|
|||
case MessageType::Resume:
|
||||
case MessageType::RestoreCheckpoint:
|
||||
case MessageType::RunToPoint:
|
||||
free(mPausedMessage);
|
||||
mPausedMessage = nullptr;
|
||||
MOZ_FALLTHROUGH;
|
||||
case MessageType::DebuggerRequest:
|
||||
case MessageType::FlushRecording:
|
||||
mPaused = false;
|
||||
|
@ -232,164 +123,10 @@ void ChildProcessInfo::SendMessage(const Message& aMsg) {
|
|||
break;
|
||||
}
|
||||
|
||||
// Keep track of messages which affect the child's behavior.
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::Resume:
|
||||
case MessageType::RestoreCheckpoint:
|
||||
case MessageType::RunToPoint:
|
||||
case MessageType::DebuggerRequest:
|
||||
case MessageType::AddBreakpoint:
|
||||
case MessageType::ClearBreakpoints:
|
||||
mMessages.emplaceBack(aMsg.Clone());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Keep track of the checkpoints the process will save.
|
||||
if (aMsg.mType == MessageType::SetSaveCheckpoint) {
|
||||
const SetSaveCheckpointMessage& nmsg =
|
||||
static_cast<const SetSaveCheckpointMessage&>(aMsg);
|
||||
MOZ_RELEASE_ASSERT(nmsg.mCheckpoint > MostRecentCheckpoint());
|
||||
VectorAddOrRemoveEntry(mShouldSaveCheckpoints, nmsg.mCheckpoint,
|
||||
nmsg.mSave);
|
||||
}
|
||||
|
||||
SendMessageRaw(aMsg);
|
||||
}
|
||||
|
||||
void ChildProcessInfo::SendMessageRaw(const Message& aMsg) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
mLastMessageTime = TimeStamp::Now();
|
||||
mChannel->SendMessage(aMsg);
|
||||
}
|
||||
|
||||
void ChildProcessInfo::Recover(bool aPaused, Message* aPausedMessage,
|
||||
size_t aLastCheckpoint, Message** aMessages,
|
||||
size_t aNumMessages) {
|
||||
MOZ_RELEASE_ASSERT(IsPaused());
|
||||
|
||||
SendMessageRaw(SetIsActiveMessage(false));
|
||||
|
||||
size_t mostRecentCheckpoint = MostRecentCheckpoint();
|
||||
bool pausedAtCheckpoint = IsPausedAtCheckpoint();
|
||||
|
||||
// Clear out all messages that have been sent to this process.
|
||||
for (Message* msg : mMessages) {
|
||||
free(msg);
|
||||
}
|
||||
mMessages.clear();
|
||||
SendMessageRaw(ClearBreakpointsMessage());
|
||||
|
||||
mPaused = aPaused;
|
||||
mPausedMessage = aPausedMessage;
|
||||
mLastCheckpoint = aLastCheckpoint;
|
||||
for (size_t i = 0; i < aNumMessages; i++) {
|
||||
mMessages.append(aMessages[i]->Clone());
|
||||
}
|
||||
|
||||
mNumRecoveredMessages = 0;
|
||||
|
||||
if (mostRecentCheckpoint < mLastCheckpoint) {
|
||||
mRecoveryStage = RecoveryStage::ReachingCheckpoint;
|
||||
SendMessageRaw(ResumeMessage(/* aForward = */ true));
|
||||
} else if (mostRecentCheckpoint > mLastCheckpoint || !pausedAtCheckpoint) {
|
||||
mRecoveryStage = RecoveryStage::ReachingCheckpoint;
|
||||
// Rewind to the last saved checkpoint at or prior to the target.
|
||||
size_t targetCheckpoint = CheckpointId::Invalid;
|
||||
for (size_t saved : mShouldSaveCheckpoints) {
|
||||
if (saved <= mLastCheckpoint && saved > targetCheckpoint) {
|
||||
targetCheckpoint = saved;
|
||||
}
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(targetCheckpoint != CheckpointId::Invalid);
|
||||
SendMessageRaw(RestoreCheckpointMessage(targetCheckpoint));
|
||||
} else {
|
||||
mRecoveryStage = RecoveryStage::PlayingMessages;
|
||||
SendNextRecoveryMessage();
|
||||
}
|
||||
|
||||
WaitUntil([=]() { return !IsRecovering(); });
|
||||
}
|
||||
|
||||
void ChildProcessInfo::Recover(ChildProcessInfo* aTargetProcess) {
|
||||
MOZ_RELEASE_ASSERT(aTargetProcess->IsPaused());
|
||||
Recover(true, aTargetProcess->mPausedMessage->Clone(),
|
||||
aTargetProcess->mLastCheckpoint, aTargetProcess->mMessages.begin(),
|
||||
aTargetProcess->mMessages.length());
|
||||
}
|
||||
|
||||
void ChildProcessInfo::RecoverToCheckpoint(size_t aCheckpoint) {
|
||||
HitCheckpointMessage pausedMessage(aCheckpoint,
|
||||
/* aRecordingEndpoint = */ false,
|
||||
/* aDuration = */ 0);
|
||||
Recover(true, pausedMessage.Clone(), aCheckpoint, nullptr, 0);
|
||||
}
|
||||
|
||||
void ChildProcessInfo::OnIncomingRecoveryMessage(const Message& aMsg) {
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::HitCheckpoint: {
|
||||
MOZ_RELEASE_ASSERT(mRecoveryStage == RecoveryStage::ReachingCheckpoint);
|
||||
const HitCheckpointMessage& nmsg =
|
||||
static_cast<const HitCheckpointMessage&>(aMsg);
|
||||
if (nmsg.mCheckpointId < mLastCheckpoint) {
|
||||
SendMessageRaw(ResumeMessage(/* aForward = */ true));
|
||||
} else {
|
||||
MOZ_RELEASE_ASSERT(nmsg.mCheckpointId == mLastCheckpoint);
|
||||
mRecoveryStage = RecoveryStage::PlayingMessages;
|
||||
SendNextRecoveryMessage();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageType::HitBreakpoint:
|
||||
case MessageType::DebuggerResponse:
|
||||
SendNextRecoveryMessage();
|
||||
break;
|
||||
case MessageType::MiddlemanCallRequest: {
|
||||
// Middleman call messages can arrive in different orders when recovering
|
||||
// than they originally did in the original process, so handle them afresh
|
||||
// even when recovering.
|
||||
MiddlemanCallResponseMessage* response =
|
||||
ProcessMiddlemanCallMessage((MiddlemanCallRequestMessage&)aMsg);
|
||||
SendMessageRaw(*response);
|
||||
free(response);
|
||||
break;
|
||||
}
|
||||
case MessageType::ResetMiddlemanCalls:
|
||||
ResetMiddlemanCalls();
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unexpected message during recovery");
|
||||
}
|
||||
}
|
||||
|
||||
void ChildProcessInfo::SendNextRecoveryMessage() {
|
||||
MOZ_RELEASE_ASSERT(mRecoveryStage == RecoveryStage::PlayingMessages);
|
||||
|
||||
// Keep sending messages to the child as long as it stays paused.
|
||||
Message* msg;
|
||||
do {
|
||||
// Check if we have recovered to the desired paused state.
|
||||
if (mNumRecoveredMessages == mMessages.length()) {
|
||||
MOZ_RELEASE_ASSERT(IsPaused());
|
||||
mRecoveryStage = RecoveryStage::None;
|
||||
return;
|
||||
}
|
||||
msg = mMessages[mNumRecoveredMessages++];
|
||||
SendMessageRaw(*msg);
|
||||
|
||||
// Messages operating on breakpoints preserve the paused state of the
|
||||
// child, so keep sending more messages.
|
||||
} while (msg->mType == MessageType::AddBreakpoint ||
|
||||
msg->mType == MessageType::ClearBreakpoints);
|
||||
|
||||
// If we have sent all messages and are in an unpaused state, we are done
|
||||
// recovering.
|
||||
if (mNumRecoveredMessages == mMessages.length() && !IsPaused()) {
|
||||
mRecoveryStage = RecoveryStage::None;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Subprocess Management
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -426,9 +163,10 @@ void ChildProcessInfo::LaunchSubprocess(
|
|||
// deleting or tearing down the old one's state. This is pretty lame and it
|
||||
// would be nice if we could do something better here, especially because
|
||||
// with restarts we could create any number of channels over time.
|
||||
mChannel = new Channel(channelId, IsRecording(), [=](Message* aMsg) {
|
||||
ReceiveChildMessageOnMainThread(channelId, aMsg);
|
||||
});
|
||||
mChannel = new Channel(channelId, IsRecording(),
|
||||
[=](Message::UniquePtr aMsg) {
|
||||
ReceiveChildMessageOnMainThread(std::move(aMsg));
|
||||
});
|
||||
|
||||
MOZ_RELEASE_ASSERT(IsRecording() == aRecordingProcessData.isSome());
|
||||
if (IsRecording()) {
|
||||
|
@ -461,11 +199,20 @@ void ChildProcessInfo::LaunchSubprocess(
|
|||
|
||||
SendGraphicsMemoryToChild();
|
||||
|
||||
// The child should send us a HitCheckpoint with an invalid ID to pause.
|
||||
// The child should send us a HitExecutionPoint message with an invalid point
|
||||
// to pause.
|
||||
WaitUntilPaused();
|
||||
|
||||
MOZ_RELEASE_ASSERT(gIntroductionMessage);
|
||||
SendMessage(*gIntroductionMessage);
|
||||
|
||||
// Always save the first checkpoint in replaying child processes.
|
||||
if (!IsRecording()) {
|
||||
SendMessage(SetSaveCheckpointMessage(CheckpointId::First, true));
|
||||
}
|
||||
|
||||
// Always run forward to the first checkpoint after the primordial one.
|
||||
SendMessage(ResumeMessage(/* aForward = */ true));
|
||||
}
|
||||
|
||||
void ChildProcessInfo::OnCrash(const char* aWhy) {
|
||||
|
@ -502,48 +249,67 @@ void ChildProcessInfo::OnCrash(const char* aWhy) {
|
|||
// processed yet. This is protected by gMonitor.
|
||||
struct PendingMessage {
|
||||
ChildProcessInfo* mProcess;
|
||||
size_t mChannelId;
|
||||
Message* mMsg;
|
||||
Message::UniquePtr mMsg;
|
||||
|
||||
PendingMessage() : mProcess(nullptr) {}
|
||||
|
||||
PendingMessage& operator=(PendingMessage&& aOther) {
|
||||
mProcess = aOther.mProcess;
|
||||
mMsg = std::move(aOther.mMsg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PendingMessage(PendingMessage&& aOther) {
|
||||
*this = std::move(aOther);
|
||||
}
|
||||
};
|
||||
static StaticInfallibleVector<PendingMessage> gPendingMessages;
|
||||
|
||||
static Message::UniquePtr ExtractChildMessage(ChildProcessInfo** aProcess) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
for (size_t i = 0; i < gPendingMessages.length(); i++) {
|
||||
PendingMessage& pending = gPendingMessages[i];
|
||||
if (!*aProcess || pending.mProcess == *aProcess) {
|
||||
*aProcess = pending.mProcess;
|
||||
Message::UniquePtr msg = std::move(pending.mMsg);
|
||||
gPendingMessages.erase(&pending);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Whether there is a pending task on the main thread's message loop to handle
|
||||
// all pending messages.
|
||||
static bool gHasPendingMessageRunnable;
|
||||
|
||||
// Process a pending message from aProcess (or any process if aProcess is null)
|
||||
// and return whether such a message was found. This must be called on the main
|
||||
// thread with gMonitor held.
|
||||
/* static */ bool ChildProcessInfo::MaybeProcessPendingMessage(
|
||||
ChildProcessInfo* aProcess) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
for (size_t i = 0; i < gPendingMessages.length(); i++) {
|
||||
if (!aProcess || gPendingMessages[i].mProcess == aProcess) {
|
||||
PendingMessage copy = gPendingMessages[i];
|
||||
gPendingMessages.erase(&gPendingMessages[i]);
|
||||
|
||||
MonitorAutoUnlock unlock(*gMonitor);
|
||||
copy.mProcess->OnIncomingMessage(copy.mChannelId, *copy.mMsg);
|
||||
free(copy.mMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// How many seconds to wait without hearing from an unpaused child before
|
||||
// considering that child to be hung.
|
||||
static const size_t HangSeconds = 30;
|
||||
|
||||
void ChildProcessInfo::WaitUntil(const std::function<bool()>& aCallback) {
|
||||
Message::UniquePtr ChildProcessInfo::WaitUntilPaused() {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (IsPaused()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool sentTerminateMessage = false;
|
||||
while (!aCallback()) {
|
||||
while (true) {
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
if (!MaybeProcessPendingMessage(this)) {
|
||||
|
||||
// Search for the first message received from this process.
|
||||
ChildProcessInfo* process = this;
|
||||
Message::UniquePtr msg = ExtractChildMessage(&process);
|
||||
|
||||
if (msg) {
|
||||
OnIncomingMessage(*msg, /* aForwardToControl = */ false);
|
||||
if (IsPaused()) {
|
||||
return msg;
|
||||
}
|
||||
} else {
|
||||
if (gChildrenAreDebugging || IsRecording()) {
|
||||
// Don't watch for hangs when children are being debugged. Recording
|
||||
// children are never treated as hanged both because they cannot be
|
||||
|
@ -561,7 +327,7 @@ void ChildProcessInfo::WaitUntil(const std::function<bool()>& aCallback) {
|
|||
// Use SendMessageRaw to avoid problems if we are recovering.
|
||||
CrashReporter::AnnotateCrashReport(
|
||||
CrashReporter::Annotation::RecordReplayHang, true);
|
||||
SendMessageRaw(TerminateMessage());
|
||||
SendMessage(TerminateMessage());
|
||||
sentTerminateMessage = true;
|
||||
} else {
|
||||
// The child is still non-responsive after sending the terminate
|
||||
|
@ -582,26 +348,33 @@ void ChildProcessInfo::WaitUntil(const std::function<bool()>& aCallback) {
|
|||
MonitorAutoLock lock(*gMonitor);
|
||||
MOZ_RELEASE_ASSERT(gHasPendingMessageRunnable);
|
||||
gHasPendingMessageRunnable = false;
|
||||
while (MaybeProcessPendingMessage(nullptr)) {
|
||||
while (true) {
|
||||
ChildProcessInfo* process = nullptr;
|
||||
Message::UniquePtr msg = ExtractChildMessage(&process);
|
||||
|
||||
if (msg) {
|
||||
MonitorAutoUnlock unlock(*gMonitor);
|
||||
process->OnIncomingMessage(*msg, /* aForwardToControl = */ true);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute a task that processes a message received from the child. This is
|
||||
// called on a channel thread, and the function executes asynchronously on
|
||||
// the main thread.
|
||||
void ChildProcessInfo::ReceiveChildMessageOnMainThread(size_t aChannelId,
|
||||
Message* aMsg) {
|
||||
void ChildProcessInfo::ReceiveChildMessageOnMainThread(Message::UniquePtr aMsg) {
|
||||
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
|
||||
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
|
||||
PendingMessage pending;
|
||||
pending.mProcess = this;
|
||||
pending.mChannelId = aChannelId;
|
||||
pending.mMsg = aMsg;
|
||||
gPendingMessages.append(pending);
|
||||
pending.mMsg = std::move(aMsg);
|
||||
gPendingMessages.append(std::move(pending));
|
||||
|
||||
// Notify the main thread, if it is waiting in WaitUntil.
|
||||
// Notify the main thread, if it is waiting in WaitUntilPaused.
|
||||
gMonitor->NotifyAll();
|
||||
|
||||
// Make sure there is a task on the main thread's message loop that can
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "ChildInternal.h"
|
||||
#include "ParentInternal.h"
|
||||
#include "nsImportModule.h"
|
||||
#include "rrIControl.h"
|
||||
#include "rrIReplay.h"
|
||||
#include "xpcprivate.h"
|
||||
|
||||
|
@ -69,6 +70,21 @@ static bool GetNumberProperty(JSContext* aCx, HandleObject aObject,
|
|||
return true;
|
||||
}
|
||||
|
||||
static parent::ChildProcessInfo* GetChildById(JSContext* aCx,
|
||||
const Value& aValue,
|
||||
bool aAllowUnpaused = false) {
|
||||
if (!aValue.isNumber()) {
|
||||
JS_ReportErrorASCII(aCx, "Expected child ID");
|
||||
return nullptr;
|
||||
}
|
||||
parent::ChildProcessInfo* child = parent::GetChildProcess(aValue.toNumber());
|
||||
if (!child || (!aAllowUnpaused && !child->IsPaused())) {
|
||||
JS_ReportErrorASCII(aCx, "Unpaused or bad child ID");
|
||||
return nullptr;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BreakpointPosition Conversion
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -106,7 +122,7 @@ bool BreakpointPosition::Decode(JSContext* aCx, HandleObject aObject) {
|
|||
return false;
|
||||
}
|
||||
|
||||
RootedString str(aCx, ToString(aCx, v));
|
||||
RootedString str(aCx, ::ToString(aCx, v));
|
||||
for (size_t i = BreakpointPosition::Invalid + 1;
|
||||
i < BreakpointPosition::sKindCount; i++) {
|
||||
BreakpointPosition::Kind kind = (BreakpointPosition::Kind)i;
|
||||
|
@ -134,6 +150,13 @@ bool BreakpointPosition::Decode(JSContext* aCx, HandleObject aObject) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BreakpointPosition::ToString(nsCString& aStr) const
|
||||
{
|
||||
aStr.AppendPrintf("{ Kind: %s, Script: %d, Offset: %d, Frame: %d }",
|
||||
KindString(), (int) mScript, (int) mOffset, (int) mFrameIndex);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ExecutionPoint Conversion
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -146,16 +169,21 @@ static const char gPositionProperty[] = "position";
|
|||
|
||||
JSObject* ExecutionPoint::Encode(JSContext* aCx) const {
|
||||
RootedObject obj(aCx, JS_NewObject(aCx, nullptr));
|
||||
RootedObject position(aCx, mPosition.Encode(aCx));
|
||||
if (!obj || !position ||
|
||||
if (!obj ||
|
||||
!JS_DefineProperty(aCx, obj, gCheckpointProperty, (double)mCheckpoint,
|
||||
JSPROP_ENUMERATE) ||
|
||||
!JS_DefineProperty(aCx, obj, gProgressProperty, (double)mProgress,
|
||||
JSPROP_ENUMERATE) ||
|
||||
!JS_DefineProperty(aCx, obj, gPositionProperty, position,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (HasPosition()) {
|
||||
RootedObject position(aCx, mPosition.Encode(aCx));
|
||||
if (!position ||
|
||||
!JS_DefineProperty(aCx, obj, gPositionProperty, position,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -165,12 +193,120 @@ bool ExecutionPoint::Decode(JSContext* aCx, HandleObject aObject) {
|
|||
return false;
|
||||
}
|
||||
|
||||
RootedObject positionObject(aCx, NonNullObject(aCx, v));
|
||||
return positionObject && mPosition.Decode(aCx, positionObject) &&
|
||||
GetNumberProperty(aCx, aObject, gCheckpointProperty, &mCheckpoint) &&
|
||||
if (v.isUndefined()) {
|
||||
MOZ_RELEASE_ASSERT(!HasPosition());
|
||||
} else {
|
||||
RootedObject positionObject(aCx, NonNullObject(aCx, v));
|
||||
if (!positionObject || !mPosition.Decode(aCx, positionObject)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return GetNumberProperty(aCx, aObject, gCheckpointProperty, &mCheckpoint) &&
|
||||
GetNumberProperty(aCx, aObject, gProgressProperty, &mProgress);
|
||||
}
|
||||
|
||||
void
|
||||
ExecutionPoint::ToString(nsCString& aStr) const
|
||||
{
|
||||
aStr.AppendPrintf("{ Checkpoint %d", (int) mCheckpoint);
|
||||
if (HasPosition()) {
|
||||
aStr.AppendPrintf(" Progress %llu Position ", mProgress);
|
||||
mPosition.ToString(aStr);
|
||||
}
|
||||
aStr.AppendPrintf(" }");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Message Conversion
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static JSObject* EncodeChannelMessage(JSContext* aCx,
|
||||
const HitExecutionPointMessage& aMsg) {
|
||||
RootedObject obj(aCx, JS_NewObject(aCx, nullptr));
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedObject pointObject(aCx, aMsg.mPoint.Encode(aCx));
|
||||
if (!pointObject ||
|
||||
!JS_DefineProperty(aCx, obj, "point", pointObject,
|
||||
JSPROP_ENUMERATE) ||
|
||||
!JS_DefineProperty(aCx, obj, "recordingEndpoint",
|
||||
aMsg.mRecordingEndpoint, JSPROP_ENUMERATE) ||
|
||||
!JS_DefineProperty(aCx, obj, "duration",
|
||||
aMsg.mDurationMicroseconds / 1000.0,
|
||||
JSPROP_ENUMERATE)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Middleman Control
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static StaticRefPtr<rrIControl> gControl;
|
||||
|
||||
void SetupMiddlemanControl(const Maybe<size_t>& aRecordingChildId) {
|
||||
MOZ_RELEASE_ASSERT(!gControl);
|
||||
|
||||
nsCOMPtr<rrIControl> control =
|
||||
do_ImportModule("resource://devtools/server/actors/replay/control.js");
|
||||
gControl = control.forget();
|
||||
ClearOnShutdown(&gControl);
|
||||
|
||||
MOZ_RELEASE_ASSERT(gControl);
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
|
||||
|
||||
RootedValue recordingChildValue(cx);
|
||||
if (aRecordingChildId.isSome()) {
|
||||
recordingChildValue.setInt32(aRecordingChildId.ref());
|
||||
}
|
||||
if (NS_FAILED(gControl->Initialize(recordingChildValue))) {
|
||||
MOZ_CRASH("SetupMiddlemanControl");
|
||||
}
|
||||
}
|
||||
|
||||
void ForwardHitExecutionPointMessage(size_t aId, const HitExecutionPointMessage& aMsg) {
|
||||
MOZ_RELEASE_ASSERT(gControl);
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
|
||||
|
||||
JSObject* obj = EncodeChannelMessage(cx, aMsg);
|
||||
MOZ_RELEASE_ASSERT(obj);
|
||||
|
||||
RootedValue value(cx, ObjectValue(*obj));
|
||||
if (NS_FAILED(gControl->HitExecutionPoint(aId, value))) {
|
||||
MOZ_CRASH("ForwardMessageToMiddlemanControl");
|
||||
}
|
||||
}
|
||||
|
||||
void BeforeSaveRecording() {
|
||||
MOZ_RELEASE_ASSERT(gControl);
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
|
||||
|
||||
if (NS_FAILED(gControl->BeforeSaveRecording())) {
|
||||
MOZ_CRASH("BeforeSaveRecording");
|
||||
}
|
||||
}
|
||||
|
||||
void AfterSaveRecording() {
|
||||
MOZ_RELEASE_ASSERT(gControl);
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
JSAutoRealm ar(cx, xpc::PrivilegedJunkScope());
|
||||
|
||||
if (NS_FAILED(gControl->AfterSaveRecording())) {
|
||||
MOZ_CRASH("AfterSaveRecording");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Middleman Methods
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -184,7 +320,7 @@ static bool Middleman_RegisterReplayDebugger(JSContext* aCx, unsigned aArgc,
|
|||
|
||||
if (gReplayDebugger) {
|
||||
args.rval().setObject(**gReplayDebugger);
|
||||
return true;
|
||||
return JS_WrapValue(aCx, args.rval());
|
||||
}
|
||||
|
||||
RootedObject obj(aCx, NonNullObject(aCx, args.get(0)));
|
||||
|
@ -192,6 +328,20 @@ static bool Middleman_RegisterReplayDebugger(JSContext* aCx, unsigned aArgc,
|
|||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
JSAutoRealm ar(aCx, xpc::PrivilegedJunkScope());
|
||||
|
||||
RootedValue debuggerValue(aCx, ObjectValue(*obj));
|
||||
if (!JS_WrapValue(aCx, &debuggerValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NS_FAILED(gControl->ConnectDebugger(debuggerValue))) {
|
||||
JS_ReportErrorASCII(aCx, "ConnectDebugger failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
obj = ::js::CheckedUnwrap(obj);
|
||||
if (!obj) {
|
||||
::js::ReportAccessDenied(aCx);
|
||||
|
@ -205,62 +355,155 @@ static bool Middleman_RegisterReplayDebugger(JSContext* aCx, unsigned aArgc,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool CallReplayDebuggerHook(const char* aMethod) {
|
||||
if (!gReplayDebugger) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
JSAutoRealm ar(cx, *gReplayDebugger);
|
||||
RootedValue rval(cx);
|
||||
if (!JS_CallFunctionName(cx, *gReplayDebugger, aMethod,
|
||||
HandleValueArray::empty(), &rval)) {
|
||||
Print("Warning: ReplayDebugger hook %s threw an exception\n", aMethod);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebuggerOnPause() { return CallReplayDebuggerHook("_onPause"); }
|
||||
|
||||
void DebuggerOnSwitchChild() { CallReplayDebuggerHook("_onSwitchChild"); }
|
||||
|
||||
static bool Middleman_CanRewind(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
args.rval().setBoolean(parent::CanRewind());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_Resume(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
||||
static bool Middleman_SpawnReplayingChild(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
bool forward = ToBoolean(args.get(0));
|
||||
|
||||
parent::Resume(forward);
|
||||
size_t id = parent::SpawnReplayingChild();
|
||||
args.rval().setInt32(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_SetActiveChild(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parent::SetActiveChild(child);
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_TimeWarp(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
||||
static bool Middleman_SendSetSaveCheckpoint(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
RootedObject targetObject(aCx, NonNullObject(aCx, args.get(0)));
|
||||
if (!targetObject) {
|
||||
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ExecutionPoint target;
|
||||
if (!target.Decode(aCx, targetObject)) {
|
||||
double checkpoint;
|
||||
if (!ToNumber(aCx, args.get(1), &checkpoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parent::TimeWarp(target);
|
||||
bool shouldSave = ToBoolean(args.get(2));
|
||||
|
||||
child->SendMessage(SetSaveCheckpointMessage(checkpoint, shouldSave));
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_SendRequest(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
||||
static bool Middleman_SendFlushRecording(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
RootedObject requestObject(aCx, NonNullObject(aCx, args.get(0)));
|
||||
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
child->SendMessage(FlushRecordingMessage());
|
||||
|
||||
// The child will unpause until the flush finishes.
|
||||
child->WaitUntilPaused();
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_SendResume(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool forward = ToBoolean(args.get(1));
|
||||
|
||||
child->SendMessage(ResumeMessage(forward));
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_SendRestoreCheckpoint(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double checkpoint;
|
||||
if (!ToNumber(aCx, args.get(1), &checkpoint)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
child->SendMessage(RestoreCheckpointMessage(checkpoint));
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_SendRunToPoint(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject pointObject(aCx, NonNullObject(aCx, args.get(1)));
|
||||
if (!pointObject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ExecutionPoint point;
|
||||
if (!point.Decode(aCx, pointObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
child->SendMessage(RunToPointMessage(point));
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Buffer for receiving the next debugger response.
|
||||
static js::CharBuffer* gResponseBuffer;
|
||||
|
||||
void OnDebuggerResponse(const Message& aMsg) {
|
||||
const DebuggerResponseMessage& nmsg =
|
||||
static_cast<const DebuggerResponseMessage&>(aMsg);
|
||||
MOZ_RELEASE_ASSERT(gResponseBuffer && gResponseBuffer->empty());
|
||||
gResponseBuffer->append(nmsg.Buffer(), nmsg.BufferSize());
|
||||
}
|
||||
|
||||
static bool Middleman_SendDebuggerRequest(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject requestObject(aCx, NonNullObject(aCx, args.get(1)));
|
||||
if (!requestObject) {
|
||||
return false;
|
||||
}
|
||||
|
@ -272,17 +515,35 @@ static bool Middleman_SendRequest(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
|||
}
|
||||
|
||||
CharBuffer responseBuffer;
|
||||
parent::SendRequest(requestBuffer, &responseBuffer);
|
||||
|
||||
MOZ_RELEASE_ASSERT(!gResponseBuffer);
|
||||
gResponseBuffer = &responseBuffer;
|
||||
|
||||
DebuggerRequestMessage* msg =
|
||||
DebuggerRequestMessage::New(requestBuffer.begin(), requestBuffer.length());
|
||||
child->SendMessage(*msg);
|
||||
free(msg);
|
||||
|
||||
// Wait for the child to respond to the query.
|
||||
child->WaitUntilPaused();
|
||||
MOZ_RELEASE_ASSERT(gResponseBuffer == &responseBuffer);
|
||||
MOZ_RELEASE_ASSERT(gResponseBuffer->length() != 0);
|
||||
gResponseBuffer = nullptr;
|
||||
|
||||
return JS_ParseJSON(aCx, responseBuffer.begin(), responseBuffer.length(),
|
||||
args.rval());
|
||||
}
|
||||
|
||||
static bool Middleman_AddBreakpoint(JSContext* aCx, unsigned aArgc,
|
||||
Value* aVp) {
|
||||
static bool Middleman_SendAddBreakpoint(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
RootedObject positionObject(aCx, NonNullObject(aCx, args.get(0)));
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject positionObject(aCx, NonNullObject(aCx, args.get(1)));
|
||||
if (!positionObject) {
|
||||
return false;
|
||||
}
|
||||
|
@ -292,27 +553,22 @@ static bool Middleman_AddBreakpoint(JSContext* aCx, unsigned aArgc,
|
|||
return false;
|
||||
}
|
||||
|
||||
parent::AddBreakpoint(position);
|
||||
child->SendMessage(AddBreakpointMessage(position));
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool Middleman_ClearBreakpoints(JSContext* aCx, unsigned aArgc,
|
||||
Value* aVp) {
|
||||
static bool Middleman_SendClearBreakpoints(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::ClearBreakpoints();
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0));
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_MaybeSwitchToReplayingChild(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::MaybeSwitchToReplayingChild();
|
||||
child->SendMessage(ClearBreakpointsMessage());
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
|
@ -346,30 +602,53 @@ static bool Middleman_HadRepaintFailure(JSContext* aCx, unsigned aArgc,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_ChildIsRecording(JSContext* aCx, unsigned aArgc,
|
||||
Value* aVp) {
|
||||
static bool Middleman_InRepaintStressMode(JSContext* aCx,
|
||||
unsigned aArgc, Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
args.rval().setBoolean(parent::ActiveChildIsRecording());
|
||||
|
||||
args.rval().setBoolean(parent::InRepaintStressMode());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Middleman_MarkExplicitPause(JSContext* aCx, unsigned aArgc,
|
||||
Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::MarkActiveChildExplicitPause();
|
||||
|
||||
args.rval().setUndefined();
|
||||
return true;
|
||||
// Recording children can idle indefinitely while waiting for input, without
|
||||
// creating a checkpoint. If this might be a problem, this method induces the
|
||||
// child to create a new checkpoint and pause.
|
||||
static void MaybeCreateCheckpointInChild(parent::ChildProcessInfo* aChild) {
|
||||
if (aChild->IsRecording() && !aChild->IsPaused()) {
|
||||
aChild->SendMessage(CreateCheckpointMessage());
|
||||
}
|
||||
}
|
||||
|
||||
static bool Middleman_WaitUntilPaused(JSContext* aCx, unsigned aArgc,
|
||||
Value* aVp) {
|
||||
CallArgs args = CallArgsFromVp(aArgc, aVp);
|
||||
|
||||
parent::WaitUntilActiveChildIsPaused();
|
||||
parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0),
|
||||
/* aAllowUnpaused = */ true);
|
||||
if (!child) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setUndefined();
|
||||
if (ToBoolean(args.get(1))) {
|
||||
MaybeCreateCheckpointInChild(child);
|
||||
}
|
||||
|
||||
Message::UniquePtr msg = child->WaitUntilPaused();
|
||||
|
||||
if (!msg) {
|
||||
JS_ReportErrorASCII(aCx, "Child process is already paused");
|
||||
}
|
||||
|
||||
MOZ_RELEASE_ASSERT(msg->mType == MessageType::HitExecutionPoint);
|
||||
const HitExecutionPointMessage& nmsg =
|
||||
static_cast<const HitExecutionPointMessage&>(*msg);
|
||||
|
||||
JSObject* obj = EncodeChannelMessage(aCx, nmsg);
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -893,18 +1172,20 @@ static bool RecordReplay_Dump(JSContext* aCx, unsigned aArgc, Value* aVp) {
|
|||
static const JSFunctionSpec gMiddlemanMethods[] = {
|
||||
JS_FN("registerReplayDebugger", Middleman_RegisterReplayDebugger, 1, 0),
|
||||
JS_FN("canRewind", Middleman_CanRewind, 0, 0),
|
||||
JS_FN("resume", Middleman_Resume, 1, 0),
|
||||
JS_FN("timeWarp", Middleman_TimeWarp, 1, 0),
|
||||
JS_FN("sendRequest", Middleman_SendRequest, 1, 0),
|
||||
JS_FN("addBreakpoint", Middleman_AddBreakpoint, 1, 0),
|
||||
JS_FN("clearBreakpoints", Middleman_ClearBreakpoints, 0, 0),
|
||||
JS_FN("maybeSwitchToReplayingChild", Middleman_MaybeSwitchToReplayingChild,
|
||||
0, 0),
|
||||
JS_FN("spawnReplayingChild", Middleman_SpawnReplayingChild, 0, 0),
|
||||
JS_FN("setActiveChild", Middleman_SetActiveChild, 1, 0),
|
||||
JS_FN("sendSetSaveCheckpoint", Middleman_SendSetSaveCheckpoint, 3, 0),
|
||||
JS_FN("sendFlushRecording", Middleman_SendFlushRecording, 1, 0),
|
||||
JS_FN("sendResume", Middleman_SendResume, 2, 0),
|
||||
JS_FN("sendRestoreCheckpoint", Middleman_SendRestoreCheckpoint, 2, 0),
|
||||
JS_FN("sendRunToPoint", Middleman_SendRunToPoint, 2, 0),
|
||||
JS_FN("sendDebuggerRequest", Middleman_SendDebuggerRequest, 2, 0),
|
||||
JS_FN("sendAddBreakpoint", Middleman_SendAddBreakpoint, 2, 0),
|
||||
JS_FN("sendClearBreakpoints", Middleman_SendClearBreakpoints, 1, 0),
|
||||
JS_FN("hadRepaint", Middleman_HadRepaint, 2, 0),
|
||||
JS_FN("hadRepaintFailure", Middleman_HadRepaintFailure, 0, 0),
|
||||
JS_FN("childIsRecording", Middleman_ChildIsRecording, 0, 0),
|
||||
JS_FN("markExplicitPause", Middleman_MarkExplicitPause, 0, 0),
|
||||
JS_FN("waitUntilPaused", Middleman_WaitUntilPaused, 0, 0),
|
||||
JS_FN("inRepaintStressMode", Middleman_InRepaintStressMode, 0, 0),
|
||||
JS_FN("waitUntilPaused", Middleman_WaitUntilPaused, 1, 0),
|
||||
JS_FN("positionSubsumes", Middleman_PositionSubsumes, 2, 0),
|
||||
JS_FS_END};
|
||||
|
||||
|
|
|
@ -13,9 +13,14 @@
|
|||
#include "ProcessRewind.h"
|
||||
|
||||
#include "mozilla/DefineEnum.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
|
||||
struct Message;
|
||||
struct HitExecutionPointMessage;
|
||||
|
||||
namespace js {
|
||||
|
||||
// This file manages interactions between the record/replay infrastructure and
|
||||
|
@ -135,6 +140,7 @@ struct BreakpointPosition {
|
|||
|
||||
JSObject* Encode(JSContext* aCx) const;
|
||||
bool Decode(JSContext* aCx, JS::HandleObject aObject);
|
||||
void ToString(nsCString& aStr) const;
|
||||
};
|
||||
|
||||
// Identification for a point in the execution of a child process where it may
|
||||
|
@ -182,23 +188,30 @@ struct ExecutionPoint {
|
|||
|
||||
JSObject* Encode(JSContext* aCx) const;
|
||||
bool Decode(JSContext* aCx, JS::HandleObject aObject);
|
||||
void ToString(nsCString& aStr) const;
|
||||
};
|
||||
|
||||
// Buffer type used for encoding object data.
|
||||
typedef InfallibleVector<char16_t> CharBuffer;
|
||||
|
||||
// Called in the middleman when the child has hit a checkpoint or breakpoint.
|
||||
// The return value is whether there is a ReplayDebugger available which the
|
||||
// notification was sent to.
|
||||
bool DebuggerOnPause();
|
||||
|
||||
// Called in the middleman when the child has changed.
|
||||
void DebuggerOnSwitchChild();
|
||||
|
||||
// Set up the JS sandbox in the current recording/replaying process and load
|
||||
// its target script.
|
||||
void SetupDevtoolsSandbox();
|
||||
|
||||
// The following hooks are used in the middleman process to call methods defined
|
||||
// by the middleman control logic.
|
||||
|
||||
// Setup the middleman control state.
|
||||
void SetupMiddlemanControl(const Maybe<size_t>& aRecordingChildId);
|
||||
|
||||
// Handle an incoming message from a child process.
|
||||
void ForwardHitExecutionPointMessage(size_t aId,
|
||||
const HitExecutionPointMessage& aMsg);
|
||||
|
||||
// Prepare the child processes so that the recording file can be safely copied.
|
||||
void BeforeSaveRecording();
|
||||
void AfterSaveRecording();
|
||||
|
||||
// The following hooks are used in the recording/replaying process to
|
||||
// call methods defined by the JS sandbox.
|
||||
|
||||
|
@ -221,6 +234,9 @@ void ClearPausedState();
|
|||
// the entry point of that script, otherwise return nothing.
|
||||
Maybe<BreakpointPosition> GetEntryPosition(const BreakpointPosition& aPosition);
|
||||
|
||||
// Called after receiving a DebuggerResponse from a child.
|
||||
void OnDebuggerResponse(const Message& aMsg);
|
||||
|
||||
} // namespace js
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -18,6 +18,11 @@ namespace mozilla {
|
|||
namespace recordreplay {
|
||||
namespace parent {
|
||||
|
||||
static bool ActiveChildIsRecording() {
|
||||
ChildProcessInfo* child = GetActiveChild();
|
||||
return child && child->IsRecording();
|
||||
}
|
||||
|
||||
static bool HandleMessageInMiddleman(ipc::Side aSide,
|
||||
const IPC::Message& aMessage) {
|
||||
IPC::Message::msgid_t type = aMessage.type();
|
||||
|
@ -73,9 +78,10 @@ static bool HandleMessageInMiddleman(ipc::Side aSide,
|
|||
ipc::IProtocol::Result r =
|
||||
contentChild->PContentChild::OnMessageReceived(aMessage);
|
||||
MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
|
||||
if (type == dom::PContent::Msg_SetXPCOMProcessAttributes__ID) {
|
||||
// Preferences are initialized via the SetXPCOMProcessAttributes message.
|
||||
PreferencesLoaded();
|
||||
if (type == dom::PContent::Msg_RegisterChrome__ID) {
|
||||
// After the RegisterChrome message we can load chrome JS and finish
|
||||
// initialization.
|
||||
ChromeRegistered();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -250,7 +256,10 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol {
|
|||
|
||||
if (mSide == ipc::ChildSide) {
|
||||
AutoMarkMainThreadWaitingForIPDLReply blocked;
|
||||
ActiveRecordingChild()->WaitUntil([&]() { return !!aReply; });
|
||||
while (!aReply) {
|
||||
GetActiveChild()->WaitUntilPaused();
|
||||
GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true));
|
||||
}
|
||||
} else {
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
while (!aReply) {
|
||||
|
@ -291,7 +300,10 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol {
|
|||
|
||||
if (mSide == ipc::ChildSide) {
|
||||
AutoMarkMainThreadWaitingForIPDLReply blocked;
|
||||
ActiveRecordingChild()->WaitUntil([&]() { return !!aReply; });
|
||||
while (!aReply) {
|
||||
GetActiveChild()->WaitUntilPaused();
|
||||
GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true));
|
||||
}
|
||||
} else {
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
while (!aReply) {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -24,18 +24,24 @@ class ChildProcessInfo;
|
|||
// Get the message loop for the main thread.
|
||||
MessageLoop* MainThreadMessageLoop();
|
||||
|
||||
// Called after prefs are available to this process.
|
||||
void PreferencesLoaded();
|
||||
// Called when chrome JS can start running and initialization can finish.
|
||||
void ChromeRegistered();
|
||||
|
||||
// Return whether replaying processes are allowed to save checkpoints and
|
||||
// rewind. Can only be called after PreferencesLoaded().
|
||||
bool CanRewind();
|
||||
|
||||
// Whether the child currently being interacted with is recording.
|
||||
bool ActiveChildIsRecording();
|
||||
// Get the current active child process.
|
||||
ChildProcessInfo* GetActiveChild();
|
||||
|
||||
// Get the active recording child process.
|
||||
ChildProcessInfo* ActiveRecordingChild();
|
||||
// Get a child process by its ID.
|
||||
ChildProcessInfo* GetChildProcess(size_t aId);
|
||||
|
||||
// Spawn a new replaying child process, returning its ID.
|
||||
size_t SpawnReplayingChild();
|
||||
|
||||
// Specify the current active child.
|
||||
void SetActiveChild(ChildProcessInfo* aChild);
|
||||
|
||||
// Return whether the middleman's main thread is blocked waiting on a
|
||||
// synchronous IPDL reply from the recording child.
|
||||
|
@ -56,32 +62,6 @@ void Shutdown();
|
|||
// threads.
|
||||
static Monitor* gMonitor;
|
||||
|
||||
// Allow the child process to resume execution.
|
||||
void Resume(bool aForward);
|
||||
|
||||
// Direct the child process to warp to a specific point.
|
||||
void TimeWarp(const js::ExecutionPoint& target);
|
||||
|
||||
// Send a JSON request to the child process, and synchronously wait for a
|
||||
// response.
|
||||
void SendRequest(const js::CharBuffer& aBuffer, js::CharBuffer* aResponse);
|
||||
|
||||
// Set the breakpoints installed in the child process.
|
||||
void AddBreakpoint(const js::BreakpointPosition& aPosition);
|
||||
void ClearBreakpoints();
|
||||
|
||||
// If possible, make sure the active child is replaying, and that requests
|
||||
// which might trigger an unhandled divergence can be processed (recording
|
||||
// children cannot process such requests).
|
||||
void MaybeSwitchToReplayingChild();
|
||||
|
||||
// Block until the active child has paused somewhere.
|
||||
void WaitUntilActiveChildIsPaused();
|
||||
|
||||
// Notify the parent that the debugger has paused and will allow the user to
|
||||
// interact with it and potentially start rewinding.
|
||||
void MarkActiveChildExplicitPause();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Graphics
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -123,57 +103,6 @@ bool InRepaintStressMode();
|
|||
// Child Processes
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Information about the role which a child process is fulfilling, and governs
|
||||
// how the process responds to incoming messages.
|
||||
class ChildRole {
|
||||
public:
|
||||
// See ParentIPC.cpp for the meaning of these role types.
|
||||
#define ForEachRoleType(Macro) Macro(Active) Macro(Standby) Macro(Inert)
|
||||
|
||||
enum Type {
|
||||
#define DefineType(Name) Name,
|
||||
ForEachRoleType(DefineType)
|
||||
#undef DefineType
|
||||
};
|
||||
|
||||
static const char* TypeString(Type aType) {
|
||||
switch (aType) {
|
||||
#define GetTypeString(Name) \
|
||||
case Name: \
|
||||
return #Name;
|
||||
ForEachRoleType(GetTypeString)
|
||||
#undef GetTypeString
|
||||
default : MOZ_CRASH("Bad ChildRole type");
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
ChildProcessInfo* mProcess;
|
||||
Type mType;
|
||||
|
||||
explicit ChildRole(Type aType) : mProcess(nullptr), mType(aType) {}
|
||||
|
||||
public:
|
||||
void SetProcess(ChildProcessInfo* aProcess) {
|
||||
MOZ_RELEASE_ASSERT(!mProcess);
|
||||
mProcess = aProcess;
|
||||
}
|
||||
Type GetType() const { return mType; }
|
||||
|
||||
virtual ~ChildRole() {}
|
||||
|
||||
// The methods below are all called on the main thread.
|
||||
|
||||
virtual void Initialize() {}
|
||||
|
||||
// When the child is paused and potentially sitting idle, notify the role
|
||||
// that state affecting its behavior has changed and may want to become
|
||||
// active again.
|
||||
virtual void Poke() {}
|
||||
|
||||
virtual void OnIncomingMessage(const Message& aMsg) = 0;
|
||||
};
|
||||
|
||||
// Handle to the underlying recording process, if there is one. Recording
|
||||
// processes are directly spawned by the middleman at startup, since they need
|
||||
// to receive all the same IPC which the middleman receives from the UI process
|
||||
|
@ -204,171 +133,31 @@ class ChildProcessInfo {
|
|||
// Whether this process is recording.
|
||||
bool mRecording;
|
||||
|
||||
// The current recovery stage of this process.
|
||||
//
|
||||
// Recovery is used when we are shepherding a child to a particular state:
|
||||
// a particular execution position and sets of installed breakpoints and
|
||||
// saved checkpoints. Recovery is used when changing a child's role, and when
|
||||
// spawning a new process to replace a crashed child process.
|
||||
//
|
||||
// When recovering, the child process won't yet be in the exact place
|
||||
// reflected by the state below, but the main thread will wait until it has
|
||||
// finished reaching this state before it is able to send or receive
|
||||
// messages.
|
||||
enum class RecoveryStage {
|
||||
// No recovery is being performed, and the process can be interacted with.
|
||||
None,
|
||||
|
||||
// The process has not yet reached mLastCheckpoint.
|
||||
ReachingCheckpoint,
|
||||
|
||||
// The process has reached mLastCheckpoint, and additional messages are
|
||||
// being sent to change its intra-checkpoint execution position or install
|
||||
// breakpoints.
|
||||
PlayingMessages
|
||||
};
|
||||
RecoveryStage mRecoveryStage;
|
||||
|
||||
// Whether the process is currently paused.
|
||||
bool mPaused;
|
||||
|
||||
// If the process is paused, or if it is running while handling a message
|
||||
// that won't cause it to change its execution point, the last message which
|
||||
// caused it to pause.
|
||||
Message* mPausedMessage;
|
||||
|
||||
// The last checkpoint which the child process reached. The child is
|
||||
// somewhere between this and either the next or previous checkpoint,
|
||||
// depending on the messages that have been sent to it.
|
||||
size_t mLastCheckpoint;
|
||||
|
||||
// Messages sent to the process which will affect its behavior as it runs
|
||||
// forward or backward from mLastCheckpoint. This includes all messages that
|
||||
// will need to be sent to another process to recover it to the same state as
|
||||
// this process.
|
||||
InfallibleVector<Message*> mMessages;
|
||||
|
||||
// In the PlayingMessages recovery stage, how much of mMessages has been sent
|
||||
// to the process.
|
||||
size_t mNumRecoveredMessages;
|
||||
|
||||
// Current role of this process.
|
||||
UniquePtr<ChildRole> mRole;
|
||||
|
||||
// Unsorted list of the checkpoints the process has been instructed to save.
|
||||
// Those at or before the most recent checkpoint will have been saved.
|
||||
InfallibleVector<size_t> mShouldSaveCheckpoints;
|
||||
|
||||
// Sorted major checkpoints for this process. See ParentIPC.cpp.
|
||||
InfallibleVector<size_t> mMajorCheckpoints;
|
||||
|
||||
// Whether we need this child to pause while the recording is updated.
|
||||
bool mPauseNeeded;
|
||||
|
||||
// Flags for whether we have received messages from the child indicating it
|
||||
// is crashing.
|
||||
bool mHasBegunFatalError;
|
||||
bool mHasFatalError;
|
||||
|
||||
void OnIncomingMessage(size_t aChannelId, const Message& aMsg);
|
||||
void OnIncomingRecoveryMessage(const Message& aMsg);
|
||||
void SendNextRecoveryMessage();
|
||||
void SendMessageRaw(const Message& aMsg);
|
||||
void OnIncomingMessage(const Message& aMsg, bool aForwardToControl);
|
||||
|
||||
static void MaybeProcessPendingMessageRunnable();
|
||||
void ReceiveChildMessageOnMainThread(size_t aChannelId, Message* aMsg);
|
||||
|
||||
// Get the position of this process relative to its last checkpoint.
|
||||
enum Disposition {
|
||||
AtLastCheckpoint,
|
||||
BeforeLastCheckpoint,
|
||||
AfterLastCheckpoint
|
||||
};
|
||||
Disposition GetDisposition();
|
||||
|
||||
void Recover(bool aPaused, Message* aPausedMessage, size_t aLastCheckpoint,
|
||||
Message** aMessages, size_t aNumMessages);
|
||||
void ReceiveChildMessageOnMainThread(Message::UniquePtr aMsg);
|
||||
|
||||
void OnCrash(const char* aWhy);
|
||||
void LaunchSubprocess(
|
||||
const Maybe<RecordingProcessData>& aRecordingProcessData);
|
||||
|
||||
public:
|
||||
ChildProcessInfo(UniquePtr<ChildRole> aRole,
|
||||
const Maybe<RecordingProcessData>& aRecordingProcessData);
|
||||
explicit ChildProcessInfo(const Maybe<RecordingProcessData>& aRecordingProcessData);
|
||||
~ChildProcessInfo();
|
||||
|
||||
ChildRole* Role() { return mRole.get(); }
|
||||
size_t GetId() { return mChannel->GetId(); }
|
||||
bool IsRecording() { return mRecording; }
|
||||
size_t LastCheckpoint() { return mLastCheckpoint; }
|
||||
bool IsRecovering() { return mRecoveryStage != RecoveryStage::None; }
|
||||
bool PauseNeeded() { return mPauseNeeded; }
|
||||
const InfallibleVector<size_t>& MajorCheckpoints() {
|
||||
return mMajorCheckpoints;
|
||||
}
|
||||
|
||||
bool IsPaused() { return mPaused; }
|
||||
bool IsPausedAtCheckpoint();
|
||||
bool IsPausedAtRecordingEndpoint();
|
||||
|
||||
// Get all breakpoints currently installed for this process.
|
||||
void GetInstalledBreakpoints(
|
||||
InfallibleVector<AddBreakpointMessage*>& aBreakpoints);
|
||||
|
||||
typedef std::function<bool(js::BreakpointPosition::Kind)> BreakpointFilter;
|
||||
|
||||
// Get the checkpoint at or earlier to the process' position. This is either
|
||||
// the last reached checkpoint or the previous one.
|
||||
size_t MostRecentCheckpoint() {
|
||||
return (GetDisposition() == BeforeLastCheckpoint) ? mLastCheckpoint - 1
|
||||
: mLastCheckpoint;
|
||||
}
|
||||
|
||||
// Get the checkpoint which needs to be saved in order for this process
|
||||
// (or another at the same place) to rewind.
|
||||
size_t RewindTargetCheckpoint() {
|
||||
switch (GetDisposition()) {
|
||||
case BeforeLastCheckpoint:
|
||||
case AtLastCheckpoint:
|
||||
// This will return CheckpointId::Invalid if we are the beginning of the
|
||||
// recording.
|
||||
return LastCheckpoint() - 1;
|
||||
case AfterLastCheckpoint:
|
||||
return LastCheckpoint();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldSaveCheckpoint(size_t aId) {
|
||||
return VectorContains(mShouldSaveCheckpoints, aId);
|
||||
}
|
||||
|
||||
bool IsMajorCheckpoint(size_t aId) {
|
||||
return VectorContains(mMajorCheckpoints, aId);
|
||||
}
|
||||
|
||||
bool HasSavedCheckpoint(size_t aId) {
|
||||
return (aId <= MostRecentCheckpoint()) && ShouldSaveCheckpoint(aId);
|
||||
}
|
||||
|
||||
size_t MostRecentSavedCheckpoint() {
|
||||
size_t id = MostRecentCheckpoint();
|
||||
while (!ShouldSaveCheckpoint(id)) {
|
||||
id--;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void SetPauseNeeded() { mPauseNeeded = true; }
|
||||
|
||||
void ClearPauseNeeded() {
|
||||
MOZ_RELEASE_ASSERT(IsPaused());
|
||||
mPauseNeeded = false;
|
||||
mRole->Poke();
|
||||
}
|
||||
|
||||
void AddMajorCheckpoint(size_t aId);
|
||||
void SetRole(UniquePtr<ChildRole> aRole);
|
||||
void SendMessage(const Message& aMessage);
|
||||
|
||||
// Recover to the same state as another process.
|
||||
|
@ -377,15 +166,11 @@ class ChildProcessInfo {
|
|||
// Recover to be paused at a checkpoint with no breakpoints set.
|
||||
void RecoverToCheckpoint(size_t aCheckpoint);
|
||||
|
||||
// Handle incoming messages from this process (and no others) until the
|
||||
// callback succeeds.
|
||||
void WaitUntil(const std::function<bool()>& aCallback);
|
||||
|
||||
void WaitUntilPaused() {
|
||||
WaitUntil([=]() { return IsPaused(); });
|
||||
}
|
||||
|
||||
static bool MaybeProcessPendingMessage(ChildProcessInfo* aProcess);
|
||||
// Handle incoming messages from this process (and no others) until it pauses.
|
||||
// The return value is null if it is already paused, otherwise the message
|
||||
// which caused it to pause. In the latter case, OnIncomingMessage will *not*
|
||||
// be called with the message.
|
||||
Message::UniquePtr WaitUntilPaused();
|
||||
|
||||
static void SetIntroductionMessage(IntroductionMessage* aMessage);
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче