diff --git a/devtools/client/framework/toolbox-process-window.js b/devtools/client/framework/toolbox-process-window.js
index fea73a0071f1..1e350b774b7a 100644
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -104,6 +104,7 @@ function setPrefDefaults() {
Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
+ Services.prefs.setBoolPref("devtools.console.stdout.chrome", true);
Services.prefs.setBoolPref("devtools.command-button-noautohide.enabled", true);
// Bug 1225160 - Using source maps with browser debugging can lead to a crash
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
diff --git a/devtools/docs/getting-started/development-profiles.md b/devtools/docs/getting-started/development-profiles.md
index 5d141a425b15..d1ce89231826 100644
--- a/devtools/docs/getting-started/development-profiles.md
+++ b/devtools/docs/getting-started/development-profiles.md
@@ -29,6 +29,8 @@ You can change the value of these preferences by going to `about:config`:
| Preference name | Value | Comments |
| --------------- | --------------- | -------- |
| `browser.dom.window.dump.enabled` | `true` | Adds global `dump` function to log strings to `stdout` |
+| `devtools.console.stdout.chrome` | `true` | Allows console API to write to `stdout` when used by chrome content |
+| `devtools.console.stdout.content` | `true` | Allows console API to write to `stdout` when used by content |
| `devtools.debugger.log` (*) | `true` | Dump packets sent over remote debugging protocol to `stdout`.
The [remote protocol inspector add-on](https://github.com/firebug/rdp-inspector/wiki) might be useful too. |
| `devtools.dump.emit` (*) | `true` | Log event notifications from the EventEmitter class (found at `devtools/shared/event-emitter.js`). |
diff --git a/devtools/server/actors/replay/debugger.js b/devtools/server/actors/replay/debugger.js
index 0c546e149f6d..c0f0cd0521f5 100644
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -243,12 +243,15 @@ ReplayDebugger.prototype = {
// Object methods
/////////////////////////////////////////////////////////
- _getObject(id) {
+ // Objects which |forConsole| is set are objects that were logged in console
+ // messages, and had their properties recorded so that they can be inspected
+ // without switching to a replaying child.
+ _getObject(id, forConsole) {
if (id && !this._objects[id]) {
const data = this._sendRequest({ type: "getObject", id });
switch (data.kind) {
case "Object":
- this._objects[id] = new ReplayDebuggerObject(this, data);
+ this._objects[id] = new ReplayDebuggerObject(this, data, forConsole);
break;
case "Environment":
this._objects[id] = new ReplayDebuggerEnvironment(this, data);
@@ -257,13 +260,17 @@ ReplayDebugger.prototype = {
ThrowError("Unknown object kind");
}
}
- return this._objects[id];
+ const rv = this._objects[id];
+ if (forConsole) {
+ rv._forConsole = true;
+ }
+ return rv;
},
- _convertValue(value) {
- if (value && typeof value == "object") {
+ _convertValue(value, forConsole) {
+ if (isNonNullObject(value)) {
if (value.object) {
- return this._getObject(value.object);
+ return this._getObject(value.object, forConsole);
} else if (value.special == "undefined") {
return undefined;
} else if (value.special == "NaN") {
@@ -332,7 +339,8 @@ ReplayDebugger.prototype = {
// other contents of the message can be left alone.
if (message.messageType == "ConsoleAPI" && message.arguments) {
for (let i = 0; i < message.arguments.length; i++) {
- message.arguments[i] = this._convertValue(message.arguments[i]);
+ message.arguments[i] = this._convertValue(message.arguments[i],
+ /* forConsole = */ true);
}
}
return message;
@@ -497,7 +505,7 @@ function ReplayDebuggerFrame(dbg, data) {
this._data = data;
if (this._data.arguments) {
this._data.arguments =
- this._data.arguments.map(this._dbg._convertValue.bind(this._dbg));
+ this._data.arguments.map(a => this._dbg._convertValue(a));
}
}
@@ -606,9 +614,10 @@ ReplayDebuggerFrame.prototype = {
// ReplayDebuggerObject
///////////////////////////////////////////////////////////////////////////////
-function ReplayDebuggerObject(dbg, data) {
+function ReplayDebuggerObject(dbg, data, forConsole) {
this._dbg = dbg;
this._data = data;
+ this._forConsole = forConsole;
this._properties = null;
}
@@ -623,7 +632,6 @@ ReplayDebuggerObject.prototype = {
get isArrowFunction() { return this._data.isArrowFunction; },
get isGeneratorFunction() { return this._data.isGeneratorFunction; },
get isAsyncFunction() { return this._data.isAsyncFunction; },
- get proto() { return this._dbg._getObject(this._data.proto); },
get class() { return this._data.class; },
get name() { return this._data.name; },
get displayName() { return this._data.displayName; },
@@ -641,6 +649,13 @@ ReplayDebuggerObject.prototype = {
isFrozen() { return this._data.isFrozen; },
unwrap() { return this.isProxy ? NYI() : this; },
+ get proto() {
+ // Don't allow inspection of the prototypes of objects logged to the
+ // console. This is a hack that prevents the object inspector from crawling
+ // the object's prototype chain.
+ return this._forConsole ? null : this._dbg._getObject(this._data.proto);
+ },
+
unsafeDereference() {
// Direct access to the referent is not currently available.
return null;
@@ -664,10 +679,10 @@ ReplayDebuggerObject.prototype = {
_ensureProperties() {
if (!this._properties) {
- const properties = this._dbg._sendRequestAllowDiverge({
- type: "getObjectProperties",
- id: this._data.id,
- });
+ const id = this._data.id;
+ const properties = this._forConsole
+ ? this._dbg._sendRequest({ type: "getObjectPropertiesForConsole", id })
+ : this._dbg._sendRequestAllowDiverge({ type: "getObjectProperties", id });
this._properties = {};
properties.forEach(({name, desc}) => { this._properties[name] = desc; });
}
@@ -791,4 +806,8 @@ function assert(v) {
}
}
+function isNonNullObject(obj) {
+ return obj && (typeof obj == "object" || typeof obj == "function");
+}
+
module.exports = ReplayDebugger;
diff --git a/devtools/server/actors/replay/replay.js b/devtools/server/actors/replay/replay.js
index b3f23925b6e7..469e567e0a01 100644
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -116,6 +116,10 @@ function scriptFrameForIndex(index) {
return frame;
}
+function isNonNullObject(obj) {
+ return obj && (typeof obj == "object" || typeof obj == "function");
+}
+
///////////////////////////////////////////////////////////////////////////////
// Persistent Script State
///////////////////////////////////////////////////////////////////////////////
@@ -179,6 +183,26 @@ dbg.onNewScript = function(script) {
installPendingHandlers();
};
+const gConsoleObjectProperties = new Map();
+
+function shouldSaveConsoleProperty({ desc }) {
+ // When logging an object to the console, only properties captured here will
+ // be shown. We limit this to non-object data properties, as more complex
+ // properties have two problems: A) to inspect them we will need to switch to
+ // a replaying child process, which is very slow when there are many console
+ // messages, and B) trying to access objects transitively referred to by
+ // logged console objects will fail when unpaused, and depends on the current
+ // state of the process otherwise.
+ return "value" in desc && !isNonNullObject(desc.value);
+}
+
+function saveConsoleObjectProperties(obj) {
+ if (obj instanceof Debugger.Object) {
+ const properties = getObjectProperties(obj).filter(shouldSaveConsoleProperty);
+ gConsoleObjectProperties.set(obj, properties);
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////
// Console Message State
///////////////////////////////////////////////////////////////////////////////
@@ -250,6 +274,7 @@ Services.obs.addObserver({
// Message arguments are preserved as debuggee values.
if (apiMessage.arguments) {
contents.arguments = apiMessage.arguments.map(makeDebuggeeValue);
+ contents.arguments.forEach(saveConsoleObjectProperties);
}
newConsoleMessage("ConsoleAPI", null, contents);
@@ -465,7 +490,7 @@ function convertCompletionValue(value) {
}
function makeDebuggeeValue(value) {
- if (value && typeof value == "object") {
+ if (isNonNullObject(value)) {
assert(!(value instanceof Debugger.Object));
const global = Cu.getGlobalForObject(value);
const dbgGlobal = dbg.makeGlobalObjectReference(global);
@@ -517,6 +542,24 @@ function forwardToScript(name) {
return request => gScripts.getObject(request.id)[name](request.value);
}
+function getObjectProperties(object) {
+ const names = object.getOwnPropertyNames();
+
+ return names.map(name => {
+ const desc = object.getOwnPropertyDescriptor(name);
+ if ("value" in desc) {
+ desc.value = convertValue(desc.value);
+ }
+ if ("get" in desc) {
+ desc.get = getObjectId(desc.get);
+ }
+ if ("set" in desc) {
+ desc.set = getObjectId(desc.set);
+ }
+ return { name, desc };
+ });
+}
+
///////////////////////////////////////////////////////////////////////////////
// Handlers
///////////////////////////////////////////////////////////////////////////////
@@ -626,21 +669,16 @@ const gRequestHandlers = {
}
const object = gPausedObjects.getObject(request.id);
- const names = object.getOwnPropertyNames();
+ return getObjectProperties(object);
+ },
- return names.map(name => {
- const desc = object.getOwnPropertyDescriptor(name);
- if ("value" in desc) {
- desc.value = convertValue(desc.value);
- }
- if ("get" in desc) {
- desc.get = getObjectId(desc.get);
- }
- if ("set" in desc) {
- desc.set = getObjectId(desc.set);
- }
- return { name, desc };
- });
+ getObjectPropertiesForConsole(request) {
+ const object = gPausedObjects.getObject(request.id);
+ const properties = gConsoleObjectProperties.get(object);
+ if (!properties) {
+ throw new Error("Console object properties not saved");
+ }
+ return properties;
},
getEnvironmentNames(request) {
diff --git a/devtools/server/actors/thread.js b/devtools/server/actors/thread.js
index cdaf9ee47dba..5b26a64de762 100644
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -545,10 +545,20 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
if (steppingType == "finish") {
const parentFrame = thread._getNextStepFrame(this);
if (parentFrame && parentFrame.script) {
+ // We can't use the completion value in stepping hooks if we're
+ // replaying, as we can't use its contents after resuming.
+ const ncompletion = thread.dbg.replaying ? null : completion;
const { onStep, onPop } = thread._makeSteppingHooks(
- originalLocation, "next", false, completion
+ originalLocation, "next", false, ncompletion
);
- parentFrame.onStep = onStep;
+ if (thread.dbg.replaying) {
+ const offsets =
+ thread._findReplayingStepOffsets(originalLocation, parentFrame,
+ /* rewinding = */ false);
+ parentFrame.setReplayingOnStep(onStep, offsets);
+ } else {
+ parentFrame.onStep = onStep;
+ }
// We need the onPop alongside the onStep because it is possible that
// the parent frame won't have any steppable offsets, and we want to
// make sure that we always pause in the parent _somewhere_.
diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp
index 8ef0483d2183..e9f5fbea0ba7 100644
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -484,6 +484,15 @@ WebGLContext::CreateAndInitGL(bool forceEnabled,
return false;
}
+ // WebGL can't be used when recording/replaying.
+ if (recordreplay::IsRecordingOrReplaying()) {
+ FailureReason reason;
+ reason.info = "Can't use WebGL when recording or replaying (https://bugzil.la/1506467).";
+ out_failReasons->push_back(reason);
+ GenerateWarning("%s", reason.info.BeginReading());
+ return false;
+ }
+
// WebGL2 is separately blocked:
if (IsWebGL2()) {
const nsCOMPtr gfxInfo = services::GetGfxInfo();
diff --git a/dom/console/Console.cpp b/dom/console/Console.cpp
index f90d38b84d54..e4860c4d6eea 100644
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -1093,7 +1093,9 @@ Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal,
{
// Let's enable the dumping to stdout by default for chrome.
if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) {
- mDumpToStdout = DOMPrefs::DumpEnabled();
+ mDumpToStdout = StaticPrefs::devtools_console_stdout_chrome();
+ } else {
+ mDumpToStdout = StaticPrefs::devtools_console_stdout_content();
}
mozilla::HoldJSObjects(this);
diff --git a/dom/media/tests/crashtests/1505957.html b/dom/media/tests/crashtests/1505957.html
new file mode 100644
index 000000000000..85a67f1c290c
--- /dev/null
+++ b/dom/media/tests/crashtests/1505957.html
@@ -0,0 +1,23 @@
+
+
+
+