Merge inbound to mozilla-central. a=merge

This commit is contained in:
Csoregi Natalia 2019-01-14 19:17:06 +02:00
Родитель c00b240721 49b8434f58
Коммит dbb80cb51f
67 изменённых файлов: 2117 добавлений и 4504 удалений

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

@ -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

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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);
}
}

118
js/src/gc/GCLock.h Normal file
Просмотреть файл

@ -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);
};