--- a/devtools/shared/webconsole/js-property-provider.js
+++ b/devtools/shared/webconsole/js-property-provider.js
@@ -388,16 +388,19 @@ function getMatchedProps(obj, match) {
function getMatchedPropsImpl(obj, match, {chainIterator, getProperties}) {
let matches = new Set();
let numProps = 0;
// We need to go up the prototype chain.
let iter = chainIterator(obj);
for (obj of iter) {
let props = getProperties(obj);
+ if (!props) {
+ continue;
+ }
numProps += props.length;
// If there are too many properties to event attempt autocompletion,
// or if we have already added the max number, then stop looping
// and return the partial set that has already been discovered.
if (numProps >= MAX_AUTOCOMPLETE_ATTEMPTS ||
matches.size >= MAX_AUTOCOMPLETIONS) {
break;
@@ -454,40 +457,60 @@ function getExactMatchImpl(obj, name, {c
}
return undefined;
}
var JSObjectSupport = {
chainIterator: function* (obj) {
while (obj) {
yield obj;
- obj = Object.getPrototypeOf(obj);
+ try {
+ obj = Object.getPrototypeOf(obj);
+ } catch (error) {
+ // The above can throw e.g. for some proxy objects.
+ return;
+ }
}
},
getProperties: function(obj) {
- return Object.getOwnPropertyNames(obj);
+ try {
+ return Object.getOwnPropertyNames(obj);
+ } catch (error) {
+ // The above can throw e.g. for some proxy objects.
+ return null;
+ }
},
getProperty: function() {
// getProperty is unsafe with raw JS objects.
throw new Error("Unimplemented!");
},
};
var DebuggerObjectSupport = {
chainIterator: function* (obj) {
while (obj) {
yield obj;
- obj = obj.proto;
+ try {
+ obj = obj.proto;
+ } catch (error) {
+ // The above can throw e.g. for some proxy objects.
+ return;
+ }
}
},
getProperties: function(obj) {
- return obj.getOwnPropertyNames();
+ try {
+ return obj.getOwnPropertyNames();
+ } catch (error) {
+ // The above can throw e.g. for some proxy objects.
+ return null;
+ }
},
getProperty: function(obj, name, rootObj) {
// This is left unimplemented in favor to DevToolsUtils.getProperty().
throw new Error("Unimplemented!");
},
};
--- a/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
+++ b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
@@ -18,34 +18,34 @@ let gState;
let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider");
function evaluateJS(input, options = {}) {
return new Promise((resolve, reject) => {
gState.client.evaluateJSAsync(input, resolve, options);
});
}
-function autocompletePromise(str, cursor, frameActor) {
+function autocompletePromise(str, cursor = str.length, frameActor) {
return new Promise(resolve => {
gState.client.autocomplete(str, cursor, resolve, frameActor);
});
}
// This test runs all of its assertions twice - once with
// the tab as a target and once with a worker
let runningInTab = true;
function startTest({worker}) {
if (worker) {
- attachConsoleToWorker(["PageError"], onAttach);
+ attachConsoleToWorker(["PageError"], onAttach.bind(null, true));
} else {
- attachConsoleToTab(["PageError"], onAttach);
+ attachConsoleToTab(["PageError"], onAttach.bind(null, false));
}
};
-let onAttach = async function (aState, response) {
+let onAttach = async function (isWorker, aState, response) {
gState = aState;
let longStrLength = DebuggerServer.LONG_STRING_LENGTH;
// Set up the global variables needed to test autocompletion
// in the target.
let script = `
// This is for workers so autocomplete acts the same
@@ -66,42 +66,54 @@ let onAttach = async function (aState, r
for (let i = 0; i < ${MAX_AUTOCOMPLETE_ATTEMPTS + 1}; i++) {
window.largeObject1['a' + i] = i;
}
window.largeObject2 = Object.create(null);
for (let i = 0; i < ${MAX_AUTOCOMPLETIONS * 2}; i++) {
window.largeObject2['a' + i] = i;
}
+
+ window.proxy1 = new Proxy({foo: 1}, {
+ getPrototypeOf() { throw new Error() }
+ });
+ window.proxy2 = new Proxy(Object.create(Object.create(null, {foo:{}})), {
+ ownKeys() { throw new Error() }
+ });
`;
await evaluateJS(script);
let tests = [doAutocomplete1, doAutocomplete2, doAutocomplete3,
doAutocomplete4, doAutocompleteLarge1,
- doAutocompleteLarge2];
+ doAutocompleteLarge2, doAutocompleteProxyThrowsPrototype,
+ doAutocompleteProxyThrowsOwnKeys];
+ if (!isWorker) {
+ // `Cu` is not defined in workers, then we can't test `Cu.Sandbox`
+ tests.push(doAutocompleteSandbox);
+ }
runTests(tests, testEnd);
};
async function doAutocomplete1() {
info("test autocomplete for 'window.foo'");
- let response = await autocompletePromise("window.foo", 10);
+ let response = await autocompletePromise("window.foo");
let matches = response.matches;
is(response.matchProp, "foo", "matchProp");
is(matches.length, 1, "matches.length");
is(matches[0], "foobarObject", "matches[0]");
nextTest();
}
async function doAutocomplete2() {
info("test autocomplete for 'window.foobarObject.'");
- let response = await autocompletePromise("window.foobarObject.", 20);
+ let response = await autocompletePromise("window.foobarObject.");
let matches = response.matches;
ok(!response.matchProp, "matchProp");
is(matches.length, 7, "matches.length");
checkObject(matches,
["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
nextTest();
@@ -119,46 +131,80 @@ async function doAutocomplete3() {
["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
nextTest();
}
async function doAutocomplete4() {
// Check that completion requests can have no suggestions.
info("test autocomplete for 'dump(window.foobarObject.)'");
- let response = await autocompletePromise("dump(window.foobarObject.)", 26);
+ let response = await autocompletePromise("dump(window.foobarObject.)");
ok(!response.matchProp, "matchProp");
is(response.matches.length, 0, "matches.length");
nextTest();
}
async function doAutocompleteLarge1() {
// Check that completion requests with too large objects will
// have no suggestions.
info("test autocomplete for 'window.largeObject1.'");
- let response = await autocompletePromise("window.largeObject1.", 20);
+ let response = await autocompletePromise("window.largeObject1.");
ok(!response.matchProp, "matchProp");
info (response.matches.join("|"));
is(response.matches.length, 0, "Bailed out with too many properties");
nextTest();
}
async function doAutocompleteLarge2() {
// Check that completion requests with pretty large objects will
// have MAX_AUTOCOMPLETIONS suggestions
info("test autocomplete for 'window.largeObject2.'");
- let response = await autocompletePromise("window.largeObject2.", 20);
+ let response = await autocompletePromise("window.largeObject2.");
ok(!response.matchProp, "matchProp");
is(response.matches.length, MAX_AUTOCOMPLETIONS, "matches.length is MAX_AUTOCOMPLETIONS");
nextTest();
}
+async function doAutocompleteProxyThrowsPrototype() {
+ // Check that completion provides own properties even if [[GetPrototypeOf]] throws.
+ info("test autocomplete for 'window.proxy1.'");
+ let response = await autocompletePromise("window.proxy1.");
+ ok(!response.matchProp, "matchProp");
+ is(response.matches.length, 1, "matches.length");
+ checkObject(response.matches, ["foo"]);
+
+ nextTest();
+}
+
+async function doAutocompleteProxyThrowsOwnKeys() {
+ // Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
+ info("test autocomplete for 'window.proxy2.'");
+ let response = await autocompletePromise("window.proxy2.");
+ ok(!response.matchProp, "matchProp");
+ is(response.matches.length, 1, "matches.length");
+ checkObject(response.matches, ["foo"]);
+
+ nextTest();
+}
+
+async function doAutocompleteSandbox() {
+ // Check that completion provides inherited properties even if [[OwnPropertyKeys]] throws.
+ info("test autocomplete for 'Cu.Sandbox.'");
+ let response = await autocompletePromise("Cu.Sandbox.");
+ ok(!response.matchProp, "matchProp");
+ let keys = Object.getOwnPropertyNames(Object.prototype).sort();
+ is(response.matches.length, keys.length, "matches.length");
+ checkObject(response.matches, keys);
+
+ nextTest();
+}
+
function testEnd()
{
// If this is the first run, reload the page and do it again
// in a worker. Otherwise, end the test.
closeDebugger(gState, function() {
gState = null;
if (runningInTab) {
runningInTab = false;