Web Inspector: Implement `queryObjects` Command Line API
authorwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 3 Jan 2019 03:48:19 +0000 (03:48 +0000)
committerwebkit@devinrousso.com <webkit@devinrousso.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 3 Jan 2019 03:48:19 +0000 (03:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176766
<rdar://problem/34890689>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

Introduces a new Command Line function called `queryObjects` that will return an array of
object that have the given constructor/prototype argument in their prototype chain.
 - `queryObjects(Promise)` will return an array of all Promises.
 - `queryObjects(Foo)` will return all objects created with the constructor `Foo`.

Currently, an error is thrown if the first argument is one of the following:
 - Object
 - Object.prototype
 - Function
 - Function.prototype
 - Array
 - Array.prototype
 - Map
 - Map.prototype
 - Set
 - Set.prototype
 - Proxy

The reason for this is that we don't want to expose any internal/builtin objects, as some of
them are highly sensitive and undefined behaviour can occur if they are modified.

* inspector/JSInjectedScriptHost.h:
* inspector/JSInjectedScriptHost.cpp:
(Inspector::checkForbiddenPrototype): Added.
(Inspector::JSInjectedScriptHost::queryObjects): Added.
Does a GC and then iterates over all live JSCell in the heap to find these objects.

* inspector/JSInjectedScriptHostPrototype.cpp:
(Inspector::JSInjectedScriptHostPrototype::finishCreation):
(Inspector::jsInjectedScriptHostPrototypeFunctionQueryObjects): Added.

* inspector/InjectedScriptSource.js:
(queryObjects): Added.

* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::promisePrototype): Added.

Source/WebCore:

Test: inspector/console/queryObjects.html

* inspector/CommandLineAPIModuleSource.js:
(CommandLineAPI):
(CommandLineAPIImpl.prototype.queryObjects): Added.

Source/WebInspectorUI:

* UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
(WI.JavaScriptRuntimeCompletionProvider.completionControllerCompletionsNeeded.receivedPropertyNames):
Add `queryObjects` to the list of command line functions.

LayoutTests:

* inspector/console/queryObjects-expected.html: Added.
* inspector/console/queryObjects.html: Added.

* http/tests/inspector/console/cross-domain-inspected-node-access-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@239583 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/dom/cross-domain-inspected-node-access-expected.txt
LayoutTests/inspector/console/queryObjects-expected.txt [new file with mode: 0644]
LayoutTests/inspector/console/queryObjects.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp
Source/JavaScriptCore/inspector/JSInjectedScriptHost.h
Source/JavaScriptCore/inspector/JSInjectedScriptHostPrototype.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h
Source/WebCore/ChangeLog
Source/WebCore/inspector/CommandLineAPIModuleSource.js
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js

index c2882eb..6304aa0 100644 (file)
@@ -1,3 +1,16 @@
+2019-01-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Implement `queryObjects` Command Line API
+        https://bugs.webkit.org/show_bug.cgi?id=176766
+        <rdar://problem/34890689>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/console/queryObjects-expected.html: Added.
+        * inspector/console/queryObjects.html: Added.
+
+        * http/tests/inspector/console/cross-domain-inspected-node-access-expected.txt:
+
 2019-01-02  Charles Vazac  <cvazac@gmail.com>
 
         Fix resourcetimingbufferfull bubbles attribute
index d638f6b..89c9c53 100644 (file)
@@ -1,5 +1,5 @@
-CONSOLE MESSAGE: line 42: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
-CONSOLE MESSAGE: line 42: Blocked a frame with origin "http://localhost:8000" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 43: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 43: Blocked a frame with origin "http://localhost:8000" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
 Test that code evaluated in the main frame cannot access $0 that resolves to a node in a frame from a different domain. Bug 105423.
 
 
diff --git a/LayoutTests/inspector/console/queryObjects-expected.txt b/LayoutTests/inspector/console/queryObjects-expected.txt
new file mode 100644 (file)
index 0000000..c093f18
--- /dev/null
@@ -0,0 +1,342 @@
+Tests for the `queryObjects` function in the Command Line API.
+
+
+== Running test suite: CommandLineAPI.queryObjects
+-- Running test case: CommandLineAPI.queryObjects.Instances.ClassA
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.ClassA
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 5 items.
+[ClassA, ClassB, ClassB, ClassC, ClassC]
+
+-- Running test case: CommandLineAPI.queryObjects.ClassA.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 5 items.
+[ClassA, ClassB, ClassB, ClassC, ClassC]
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.ClassB
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.ClassB
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 3 items.
+[ClassB, ClassC, ClassC]
+
+-- Running test case: CommandLineAPI.queryObjects.ClassB.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 3 items.
+[ClassB, ClassC, ClassC]
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.ClassC
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.ClassC
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 1 items.
+[ClassC]
+
+-- Running test case: CommandLineAPI.queryObjects.ClassC.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 1 items.
+[ClassC]
+
+-- Running test case: CommandLineAPI.queryObjects.HTMLDocument
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 1 items.
+[#document]
+
+-- Running test case: CommandLineAPI.queryObjects.HTMLBodyElement
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 1 items.
+[<body>]
+
+-- Running test case: CommandLineAPI.queryObjects.HTMLImageElement
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.objectWithDisallowedPrototypeGetter
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.objectWithErrorPrototypeGetter
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.undefined
+PASS: Calling "queryObjects" with "undefined" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects.null
+PASS: Calling "queryObjects" with "null" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects.Infinity
+PASS: Calling "queryObjects" with "Infinity" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects.NaN
+PASS: Calling "queryObjects" with "NaN" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects.1
+PASS: Calling "queryObjects" with "1" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects.true
+PASS: Calling "queryObjects" with "true" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects."test"
+PASS: Calling "queryObjects" with ""test"" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Symbol
+PASS: Calling "queryObjects" with "Instances.Symbol" should throw an exception.
+TypeError: queryObjects first argument must be an object.
+
+-- Running test case: CommandLineAPI.queryObjects.Symbol
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Symbol.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Proxies.constructor
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Proxies.basic
+PASS: Calling "queryObjects" with "Proxies.basic" should throw an exception.
+TypeError: queryObjects cannot be called with a Proxy.
+
+-- Running test case: CommandLineAPI.queryObjects.Proxies.object
+PASS: Calling "queryObjects" with "Proxies.object" should throw an exception.
+TypeError: queryObjects cannot be called with a Proxy.
+
+-- Running test case: CommandLineAPI.queryObjects.Proxies.tricky
+PASS: Calling "queryObjects" with "Proxies.tricky" should throw an exception.
+TypeError: queryObjects cannot be called with a Proxy.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Object
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Object
+PASS: Calling "queryObjects" with "Object" should throw an exception.
+TypeError: queryObjects cannot be called with Object.
+
+-- Running test case: CommandLineAPI.queryObjects.Object.prototype
+PASS: Calling "queryObjects" with "Object.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Object.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Function
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Function
+PASS: Calling "queryObjects" with "Function" should throw an exception.
+TypeError: queryObjects cannot be called with Function.
+
+-- Running test case: CommandLineAPI.queryObjects.Function.prototype
+PASS: Calling "queryObjects" with "Function.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Function.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Array
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Array
+PASS: Calling "queryObjects" with "Array" should throw an exception.
+TypeError: queryObjects cannot be called with Array.
+
+-- Running test case: CommandLineAPI.queryObjects.Array.prototype
+PASS: Calling "queryObjects" with "Array.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Array.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Map
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Map
+PASS: Calling "queryObjects" with "Map" should throw an exception.
+TypeError: queryObjects cannot be called with Map.
+
+-- Running test case: CommandLineAPI.queryObjects.Map.prototype
+PASS: Calling "queryObjects" with "Map.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Map.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Set
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Set
+PASS: Calling "queryObjects" with "Set" should throw an exception.
+TypeError: queryObjects cannot be called with Set.
+
+-- Running test case: CommandLineAPI.queryObjects.Set.prototype
+PASS: Calling "queryObjects" with "Set.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Set.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Promise
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Promise
+PASS: Calling "queryObjects" with "Promise" should throw an exception.
+TypeError: queryObjects cannot be called with Promise.
+
+-- Running test case: CommandLineAPI.queryObjects.Promise.prototype
+PASS: Calling "queryObjects" with "Promise.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Promise.
+
+-- Running test case: CommandLineAPI.queryObjects.FunctionPrototypeReplacement
+PASS: Calling "queryObjects" with "FunctionPrototypeReplacement" should throw an exception.
+TypeError: queryObjects cannot be called with Function.
+
+-- Running test case: CommandLineAPI.queryObjects.FunctionPrototypeReplacement.prototype
+PASS: Calling "queryObjects" with "FunctionPrototypeReplacement.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Function.
+
+-- Running test case: CommandLineAPI.queryObjects.ArrayPrototypeReplacement
+PASS: Calling "queryObjects" with "ArrayPrototypeReplacement" should throw an exception.
+TypeError: queryObjects cannot be called with Array.
+
+-- Running test case: CommandLineAPI.queryObjects.ArrayPrototypeReplacement.prototype
+PASS: Calling "queryObjects" with "ArrayPrototypeReplacement.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Array.
+
+-- Running test case: CommandLineAPI.queryObjects.MapPrototypeReplacement
+PASS: Calling "queryObjects" with "MapPrototypeReplacement" should throw an exception.
+TypeError: queryObjects cannot be called with Map.
+
+-- Running test case: CommandLineAPI.queryObjects.MapPrototypeReplacement.prototype
+PASS: Calling "queryObjects" with "MapPrototypeReplacement.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Map.
+
+-- Running test case: CommandLineAPI.queryObjects.SetPrototypeReplacement
+PASS: Calling "queryObjects" with "SetPrototypeReplacement" should throw an exception.
+TypeError: queryObjects cannot be called with Set.
+
+-- Running test case: CommandLineAPI.queryObjects.SetPrototypeReplacement.prototype
+PASS: Calling "queryObjects" with "SetPrototypeReplacement.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Set.
+
+-- Running test case: CommandLineAPI.queryObjects.PromisePrototypeReplacement
+PASS: Calling "queryObjects" with "PromisePrototypeReplacement" should throw an exception.
+TypeError: queryObjects cannot be called with Promise.
+
+-- Running test case: CommandLineAPI.queryObjects.PromisePrototypeReplacement.prototype
+PASS: Calling "queryObjects" with "PromisePrototypeReplacement.prototype" should throw an exception.
+TypeError: queryObjects cannot be called with Promise.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Boolean
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Boolean
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.Boolean.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.String
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.String
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.String.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Number
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Number
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.Number.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.Date
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.Date
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.Date.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.Instances.RegExp
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have 0 items.
+
+-- Running test case: CommandLineAPI.queryObjects.RegExp
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.RegExp.prototype
+PASS: The result should be an object.
+PASS: The result should be an array object.
+PASS: The result should have at least 1 item.
+
+-- Running test case: CommandLineAPI.queryObjects.GC
+PASS: Should be 1 ClassC instance.
+Clearing instances...
+PASS: Should now be 0 ClassC instances.
+
+-- Running test case: CommandLineAPI.queryObjects.NoParameter
+PASS: The result should be undefined.
+
diff --git a/LayoutTests/inspector/console/queryObjects.html b/LayoutTests/inspector/console/queryObjects.html
new file mode 100644 (file)
index 0000000..03ba6c2
--- /dev/null
@@ -0,0 +1,215 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+class ClassA { }
+class ClassB extends ClassA { }
+class ClassC extends ClassB { }
+
+var Instances = {
+    ClassA: new ClassA,
+    ClassB: new ClassB,
+    ClassC: new ClassC,
+    Object: new Object,
+    Function: new Function,
+    Array: new Array,
+    Boolean: new Boolean,
+    String: new String,
+    Symbol: Symbol(),
+    Number: new Number,
+    Date: new Date,
+    RegExp: new RegExp,
+    Map: new Map,
+    Set: new Set,
+    Promise: new Promise(function() {}),
+};
+
+var Proxies = {
+    constructor: Proxy,
+    basic: new Proxy({}, {}),
+    object: new Proxy({}, { get() { return Object.prototype; } }),
+    tricky: new Proxy({}, { get() { return this.count++ === 0 ? ClassA : Object.prototype; } }),
+};
+
+var objectWithDisallowedPrototypeGetter = {
+    get prototype() { return Object.prototype; },
+};
+
+var objectWithErrorPrototypeGetter = {
+    get prototype() { throw 42; },
+};
+
+class FunctionPrototypeReplacement { };
+Function.prototype.__proto__ = FunctionPrototypeReplacement.prototype;
+
+class ArrayPrototypeReplacement { };
+Array.prototype.__proto__ = ArrayPrototypeReplacement.prototype;
+
+class MapPrototypeReplacement { };
+Map.prototype.__proto__ = MapPrototypeReplacement.prototype;
+
+class SetPrototypeReplacement { };
+Set.prototype.__proto__ = SetPrototypeReplacement.prototype;
+
+class PromisePrototypeReplacement { };
+Promise.prototype.__proto__ = PromisePrototypeReplacement.prototype;
+
+function clearInstances() {
+    Instances = {};
+}
+
+function test() {
+    function queryObjects(prototypeOrConstructor, callback) {
+        WI.runtimeManager.evaluateInInspectedWindow(`queryObjects(${prototypeOrConstructor})`, {objectGroup: "test", includeCommandLineAPI: true, generatePreview: true}, callback);
+    }
+
+    let suite = InspectorTest.createAsyncSuite("CommandLineAPI.queryObjects");
+
+    let cases = [
+        {prototypeOrConstructor: `Instances.ClassA`, resultCount: 0},
+        {prototypeOrConstructor: `ClassA`, resultCount: 5},
+        {prototypeOrConstructor: `ClassA.prototype`, resultCount: 5},
+
+        {prototypeOrConstructor: `Instances.ClassB`, resultCount: 0},
+        {prototypeOrConstructor: `ClassB`, resultCount: 3},
+        {prototypeOrConstructor: `ClassB.prototype`, resultCount: 3},
+
+        {prototypeOrConstructor: `Instances.ClassC`, resultCount: 0},
+        {prototypeOrConstructor: `ClassC`, resultCount: 1},
+        {prototypeOrConstructor: `ClassC.prototype`, resultCount: 1},
+
+        {prototypeOrConstructor: `HTMLDocument`, resultCount: 1},
+        {prototypeOrConstructor: `HTMLBodyElement`, resultCount: 1},
+        {prototypeOrConstructor: `HTMLImageElement`, resultCount: 0},
+
+        {prototypeOrConstructor: `objectWithDisallowedPrototypeGetter`, resultCount: 0},
+        {prototypeOrConstructor: `objectWithErrorPrototypeGetter`, resultCount: 0},
+
+        {prototypeOrConstructor: `undefined`, shouldThrow: true},
+        {prototypeOrConstructor: `null`, shouldThrow: true},
+        {prototypeOrConstructor: `Infinity`, shouldThrow: true},
+        {prototypeOrConstructor: `NaN`, shouldThrow: true},
+        {prototypeOrConstructor: `1`, shouldThrow: true},
+        {prototypeOrConstructor: `true`, shouldThrow: true},
+        {prototypeOrConstructor: `"test"`, shouldThrow: true},
+
+        {prototypeOrConstructor: `Instances.Symbol`, shouldThrow: true},
+        {prototypeOrConstructor: `Symbol`, resultCount: 0},
+        {prototypeOrConstructor: `Symbol.prototype`, resultCount: 0},
+
+        {prototypeOrConstructor: `Proxies.constructor`, resultCount: 0},
+        {prototypeOrConstructor: `Proxies.basic`, shouldThrow: true},
+        {prototypeOrConstructor: `Proxies.object`, shouldThrow: true},
+        {prototypeOrConstructor: `Proxies.tricky`, shouldThrow: true},
+    ];
+
+    for (let type of ["Object", "Function", "Array", "Map", "Set", "Promise"]) {
+        cases.push({prototypeOrConstructor: `Instances.${type}`, resultCount: 0});
+        cases.push({prototypeOrConstructor: type, shouldThrow: true});
+        cases.push({prototypeOrConstructor: `${type}.prototype`, shouldThrow: true});
+    }
+
+    for (let type of ["FunctionPrototypeReplacement", "ArrayPrototypeReplacement", "MapPrototypeReplacement", "SetPrototypeReplacement", "PromisePrototypeReplacement"]) {
+        cases.push({prototypeOrConstructor: type, shouldThrow: true});
+        cases.push({prototypeOrConstructor: `${type}.prototype`, shouldThrow: true});
+    }
+
+    for (let type of ["Boolean", "String", "Number", "Date", "RegExp"]) {
+        cases.push({prototypeOrConstructor: `Instances.${type}`, resultCount: 0});
+        cases.push({prototypeOrConstructor: type});
+        cases.push({prototypeOrConstructor: `${type}.prototype`});
+    }
+
+    for (let {prototypeOrConstructor, resultCount, shouldThrow} of cases) {
+        suite.addTestCase({
+            name: `CommandLineAPI.queryObjects.${prototypeOrConstructor}`,
+            test(resolve, reject) {
+                queryObjects(prototypeOrConstructor, (remoteObject, wasThrown, savedResultIndex) => {
+                    if (shouldThrow) {
+                        InspectorTest.expectThat(wasThrown, `Calling "queryObjects" with "${prototypeOrConstructor}" should throw an exception.`);
+                        InspectorTest.log(remoteObject.description);
+                        if (wasThrown) {
+                            resolve();
+                            return;
+                        }
+                    } else if (wasThrown) {
+                        InspectorTest.fail("An exception was thrown.");
+                        InspectorTest.log(remoteObject.description);
+                        resolve();
+                        return;
+                    }
+
+                    InspectorTest.expectEqual(remoteObject.type, "object", "The result should be an object.");
+                    InspectorTest.expectEqual(remoteObject.subtype, "array", "The result should be an array object.");
+
+                    if (resultCount === undefined)
+                        InspectorTest.expectGreaterThan(remoteObject.size, 0, `The result should have at least 1 item.`);
+                    else {
+                        InspectorTest.expectEqual(remoteObject.size, resultCount, `The result should have ${resultCount} items.`);
+                        if (remoteObject.size) {
+                            let propertyPreviews = remoteObject.preview.propertyPreviews.map((preview) => preview.value);
+                            propertyPreviews.sort();
+                            InspectorTest.log("[" + propertyPreviews.join(", ") + "]");
+                        }
+                    }
+
+                    resolve();
+                });
+            }
+        });
+    }
+
+    suite.addTestCase({
+        name: "CommandLineAPI.queryObjects.GC",
+        test(resolve, reject) {
+            queryObjects(`ClassC`, (remoteObject, wasThrown, savedResultIndex) => {
+                InspectorTest.assert(!wasThrown);
+                if (wasThrown)
+                    InspectorTest.log(remoteObject.description);
+
+                InspectorTest.expectEqual(remoteObject.size, 1, `Should be 1 ClassC instance.`);
+
+                InspectorTest.log("Clearing instances...");
+
+                RuntimeAgent.releaseObjectGroup("test");
+                InspectorTest.evaluateInPage(`clearInstances()`);
+
+                queryObjects(`ClassC`, (remoteObject, wasThrown, savedResultIndex) => {
+                    InspectorTest.assert(!wasThrown);
+                    if (wasThrown)
+                        InspectorTest.log(remoteObject.description);
+
+                    InspectorTest.expectEqual(remoteObject.size, 0, `Should now be 0 ClassC instances.`);
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "CommandLineAPI.queryObjects.NoParameter",
+        test(resolve, reject) {
+            const prototypeOrConstructor = "";
+            queryObjects(prototypeOrConstructor, (remoteObject, wasThrown, savedResultIndex) => {
+                if (wasThrown) {
+                    InspectorTest.fail("An exception was thrown.");
+                    InspectorTest.log(remoteObject.description);
+                    reject();
+                    return;
+                }
+
+                InspectorTest.expectEqual(remoteObject.type, "undefined", "The result should be undefined.");
+                resolve();
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests for the `queryObjects` function in the Command Line API.</p>
+</body>
+</html>
index 94368dc..94d4d6f 100644 (file)
@@ -1,3 +1,48 @@
+2019-01-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Implement `queryObjects` Command Line API
+        https://bugs.webkit.org/show_bug.cgi?id=176766
+        <rdar://problem/34890689>
+
+        Reviewed by Joseph Pecoraro.
+
+        Introduces a new Command Line function called `queryObjects` that will return an array of
+        object that have the given constructor/prototype argument in their prototype chain.
+         - `queryObjects(Promise)` will return an array of all Promises.
+         - `queryObjects(Foo)` will return all objects created with the constructor `Foo`.
+
+        Currently, an error is thrown if the first argument is one of the following:
+         - Object
+         - Object.prototype
+         - Function
+         - Function.prototype
+         - Array
+         - Array.prototype
+         - Map
+         - Map.prototype
+         - Set
+         - Set.prototype
+         - Proxy
+
+        The reason for this is that we don't want to expose any internal/builtin objects, as some of
+        them are highly sensitive and undefined behaviour can occur if they are modified.
+
+        * inspector/JSInjectedScriptHost.h:
+        * inspector/JSInjectedScriptHost.cpp:
+        (Inspector::checkForbiddenPrototype): Added.
+        (Inspector::JSInjectedScriptHost::queryObjects): Added.
+        Does a GC and then iterates over all live JSCell in the heap to find these objects.
+
+        * inspector/JSInjectedScriptHostPrototype.cpp:
+        (Inspector::JSInjectedScriptHostPrototype::finishCreation):
+        (Inspector::jsInjectedScriptHostPrototypeFunctionQueryObjects): Added.
+
+        * inspector/InjectedScriptSource.js:
+        (queryObjects): Added.
+
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::promisePrototype): Added.
+
 2018-12-31  Keith Miller  <keith_miller@apple.com>
 
         SourceProviders should use an actual URL instead of a string
index b5adc6f..01c1abe 100644 (file)
@@ -1447,6 +1447,10 @@ BasicCommandLineAPI.methods = [
             result.push(object[key]);
         return result;
     },
+
+    function queryObjects() {
+        return InjectedScriptHost.queryObjects(...arguments);
+    },
 ];
 
 for (let i = 0; i < BasicCommandLineAPI.methods.length; ++i) {
index 71d7c40..241f5db 100644 (file)
 #include "JSInjectedScriptHost.h"
 
 #include "ArrayIteratorPrototype.h"
+#include "ArrayPrototype.h"
 #include "BuiltinNames.h"
 #include "Completion.h"
 #include "DateInstance.h"
 #include "DirectArguments.h"
 #include "Error.h"
+#include "FunctionPrototype.h"
+#include "HeapIterationScope.h"
 #include "InjectedScriptHost.h"
 #include "IterationKind.h"
 #include "IteratorOperations.h"
@@ -44,6 +47,7 @@
 #include "JSInjectedScriptHostPrototype.h"
 #include "JSMap.h"
 #include "JSPromise.h"
+#include "JSPromisePrototype.h"
 #include "JSSet.h"
 #include "JSStringIterator.h"
 #include "JSTypedArrays.h"
 #include "JSWeakSet.h"
 #include "JSWithScope.h"
 #include "MapIteratorPrototype.h"
+#include "MapPrototype.h"
+#include "MarkedSpaceInlines.h"
 #include "ObjectConstructor.h"
+#include "ObjectPrototype.h"
 #include "ProxyObject.h"
 #include "RegExpObject.h"
 #include "ScopedArguments.h"
 #include "SetIteratorPrototype.h"
+#include "SetPrototype.h"
 #include "SourceCode.h"
 #include "TypedArrayInlines.h"
 
@@ -621,4 +629,87 @@ JSValue JSInjectedScriptHost::iteratorEntries(ExecState* exec)
     return array;
 }
 
+static bool checkForbiddenPrototype(ExecState* exec, JSValue value, JSValue proto)
+{
+    if (value == proto)
+        return true;
+
+    // Check that the prototype chain of proto hasn't been modified to include value.
+    return JSObject::defaultHasInstance(exec, proto, value);
+}
+
+JSValue JSInjectedScriptHost::queryObjects(ExecState* exec)
+{
+    if (exec->argumentCount() < 1)
+        return jsUndefined();
+
+    VM& vm = exec->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSValue prototypeOrConstructor = exec->uncheckedArgument(0);
+    if (!prototypeOrConstructor.isObject())
+        return throwTypeError(exec, scope, "queryObjects first argument must be an object."_s);
+
+    JSObject* object = asObject(prototypeOrConstructor);
+    if (object->inherits<ProxyObject>(vm))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with a Proxy."_s);
+
+    JSValue prototype = object;
+
+    PropertySlot prototypeSlot(object, PropertySlot::InternalMethodType::VMInquiry);
+    if (object->getPropertySlot(exec, vm.propertyNames->prototype, prototypeSlot)) {
+        RETURN_IF_EXCEPTION(scope, { });
+        if (prototypeSlot.isValue()) {
+            JSValue prototypeValue = prototypeSlot.getValue(exec, vm.propertyNames->prototype);
+            if (prototypeValue.isObject()) {
+                prototype = prototypeValue;
+                object = asObject(prototype);
+            }
+        }
+    }
+
+    if (object->inherits<ProxyObject>(vm) || prototype.inherits<ProxyObject>(vm))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with a Proxy."_s);
+
+    // FIXME: implement a way of distinguishing between internal and user-created objects.
+    JSGlobalObject* lexicalGlobalObject = exec->lexicalGlobalObject();
+    if (checkForbiddenPrototype(exec, object, lexicalGlobalObject->objectPrototype()))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with Object."_s);
+    if (checkForbiddenPrototype(exec, object, lexicalGlobalObject->functionPrototype()))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with Function."_s);
+    if (checkForbiddenPrototype(exec, object, lexicalGlobalObject->arrayPrototype()))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with Array."_s);
+    if (checkForbiddenPrototype(exec, object, lexicalGlobalObject->mapPrototype()))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with Map."_s);
+    if (checkForbiddenPrototype(exec, object, lexicalGlobalObject->jsSetPrototype()))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with Set."_s);
+    if (checkForbiddenPrototype(exec, object, lexicalGlobalObject->promisePrototype()))
+        return throwTypeError(exec, scope, "queryObjects cannot be called with Promise."_s);
+
+    sanitizeStackForVM(&vm);
+    vm.heap.collectNow(Sync, CollectionScope::Full);
+
+    JSArray* array = constructEmptyArray(exec, nullptr);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    {
+        HeapIterationScope iterationScope(vm.heap);
+        vm.heap.objectSpace().forEachLiveCell(iterationScope, [&] (HeapCell* cell, HeapCell::Kind kind) {
+            if (!isJSCellKind(kind))
+                return IterationStatus::Continue;
+
+            JSValue value(static_cast<JSCell*>(cell));
+            if (value.inherits<ProxyObject>(vm))
+                return IterationStatus::Continue;
+
+            if (JSObject::defaultHasInstance(exec, value, prototype))
+                array->putDirectIndex(exec, array->length(), value);
+
+            return IterationStatus::Continue;
+        });
+    }
+
+    return array;
+}
+
 } // namespace Inspector
index 4035a69..bacc37a 100644 (file)
@@ -71,6 +71,7 @@ public:
     JSC::JSValue weakSetSize(JSC::ExecState*);
     JSC::JSValue weakSetEntries(JSC::ExecState*);
     JSC::JSValue iteratorEntries(JSC::ExecState*);
+    JSC::JSValue queryObjects(JSC::ExecState*);
 
 protected:
     void finishCreation(JSC::VM&);
index 5493329..f3960de 100644 (file)
@@ -49,6 +49,7 @@ static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionWeakMap
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionWeakSetSize(ExecState*);
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionWeakSetEntries(ExecState*);
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionIteratorEntries(ExecState*);
+static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionQueryObjects(ExecState*);
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionEvaluateWithScopeExtension(ExecState*);
 
 static EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeAttributeEvaluate(ExecState*);
@@ -72,6 +73,7 @@ void JSInjectedScriptHostPrototype::finishCreation(VM& vm, JSGlobalObject* globa
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("weakSetSize", jsInjectedScriptHostPrototypeFunctionWeakSetSize, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("weakSetEntries", jsInjectedScriptHostPrototypeFunctionWeakSetEntries, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("iteratorEntries", jsInjectedScriptHostPrototypeFunctionIteratorEntries, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
+    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("queryObjects", jsInjectedScriptHostPrototypeFunctionQueryObjects, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("evaluateWithScopeExtension", jsInjectedScriptHostPrototypeFunctionEvaluateWithScopeExtension, static_cast<unsigned>(PropertyAttribute::DontEnum), 1);
 
     JSC_NATIVE_GETTER("evaluate", jsInjectedScriptHostPrototypeAttributeEvaluate, PropertyAttribute::DontEnum | PropertyAttribute::Accessor);
@@ -194,6 +196,19 @@ EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionIteratorEntrie
     return JSValue::encode(castedThis->iteratorEntries(exec));
 }
 
+EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionQueryObjects(ExecState* exec)
+{
+    VM& vm = exec->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    JSValue thisValue = exec->thisValue();
+    JSInjectedScriptHost* castedThis = jsDynamicCast<JSInjectedScriptHost*>(vm, thisValue);
+    if (!castedThis)
+        return throwVMTypeError(exec, scope);
+
+    return JSValue::encode(castedThis->queryObjects(exec));
+}
+
 EncodedJSValue JSC_HOST_CALL jsInjectedScriptHostPrototypeFunctionEvaluateWithScopeExtension(ExecState* exec)
 {
     VM& vm = exec->vm();
index 2cf557d..357ca8b 100644 (file)
@@ -616,6 +616,7 @@ public:
     MapPrototype* mapPrototype() const { return m_mapPrototype.get(); }
     // Workaround for the name conflict between JSCell::setPrototype.
     SetPrototype* jsSetPrototype() const { return m_setPrototype.get(); }
+    JSPromisePrototype* promisePrototype() const { return m_promisePrototype.get(); }
     AsyncGeneratorPrototype* asyncGeneratorPrototype() const { return m_asyncGeneratorPrototype.get(); }
     AsyncGeneratorFunctionPrototype* asyncGeneratorFunctionPrototype() const { return m_asyncGeneratorFunctionPrototype.get(); }
 
index 510b23b..723c7ef 100644 (file)
@@ -1,3 +1,17 @@
+2019-01-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Implement `queryObjects` Command Line API
+        https://bugs.webkit.org/show_bug.cgi?id=176766
+        <rdar://problem/34890689>
+
+        Reviewed by Joseph Pecoraro.
+
+        Test: inspector/console/queryObjects.html
+
+        * inspector/CommandLineAPIModuleSource.js:
+        (CommandLineAPI):
+        (CommandLineAPIImpl.prototype.queryObjects): Added.
+
 2019-01-02  Charles Vazac  <cvazac@gmail.com>
 
         Fix resourcetimingbufferfull bubbles attribute
index 7cf4fcd..92254f5 100644 (file)
@@ -57,9 +57,10 @@ function CommandLineAPI(commandLineAPIImpl, callFrame)
         this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
 
     // Command Line API methods.
-    for (let member of CommandLineAPI.members_) {
-        this[member] = bind(commandLineAPIImpl[member], commandLineAPIImpl);
-        this[member].toString = function() { return "function " + member + "() { [Command Line API] }" };
+    for (let i = 0; i < CommandLineAPI.methods.length; ++i) {
+        let method = CommandLineAPI.methods[i];
+        this[method] = bind(commandLineAPIImpl[method], commandLineAPIImpl);
+        this[method].toString = function() { return "function " + method + "() { [Command Line API] }" };
     }
 }
 
@@ -67,9 +68,24 @@ function CommandLineAPI(commandLineAPIImpl, callFrame)
  * @type {Array.<string>}
  * @const
  */
-CommandLineAPI.members_ = [
-    "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "table",
-    "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners"
+CommandLineAPI.methods = [
+    "$",
+    "$$",
+    "$x",
+    "clear",
+    "copy",
+    "dir",
+    "dirxml",
+    "getEventListeners",
+    "inspect",
+    "keys",
+    "monitorEvents",
+    "profile",
+    "profileEnd",
+    "queryObjects",
+    "table",
+    "unmonitorEvents",
+    "values",
 ];
 
 /**
@@ -221,6 +237,11 @@ CommandLineAPIImpl.prototype = {
         return this._inspect(object);
     },
 
+    queryObjects()
+    {
+        return InjectedScriptHost.queryObjects(...arguments);
+    },
+
     copy: function(object)
     {
         var string;
index d0d8c6c..044b0d5 100644 (file)
@@ -1,3 +1,15 @@
+2019-01-02  Devin Rousso  <webkit@devinrousso.com>
+
+        Web Inspector: Implement `queryObjects` Command Line API
+        https://bugs.webkit.org/show_bug.cgi?id=176766
+        <rdar://problem/34890689>
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
+        (WI.JavaScriptRuntimeCompletionProvider.completionControllerCompletionsNeeded.receivedPropertyNames):
+        Add `queryObjects` to the list of command line functions.
+
 2018-12-21  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: Styles Redesign: remove unused CSS style icons
index aebd456..f23df46 100644 (file)
@@ -218,14 +218,14 @@ WI.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvid
             WI.runtimeManager.activeExecutionContext.target.RuntimeAgent.releaseObjectGroup("completion");
 
             if (!base) {
-                var commandLineAPI = ["$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners", "$0", "$_"];
+                let commandLineAPI = WI.JavaScriptRuntimeCompletionProvider._commandLineAPI.slice(0);
                 if (WI.debuggerManager.paused) {
                     let targetData = WI.debuggerManager.dataForTarget(WI.runtimeManager.activeExecutionContext.target);
                     if (targetData.pauseReason === WI.DebuggerManager.PauseReason.Exception)
                         commandLineAPI.push("$exception");
                 }
-                for (var i = 0; i < commandLineAPI.length; ++i)
-                    propertyNames[commandLineAPI[i]] = true;
+                for (let name of commandLineAPI)
+                    propertyNames[name] = true;
 
                 // FIXME: Due to caching, sometimes old $n values show up as completion results even though they are not available. We should clear that proactively.
                 for (var i = 1; i <= WI.ConsoleCommandResultMessage.maximumSavedResultIndex; ++i)
@@ -301,3 +301,25 @@ WI.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvid
         this._lastPropertyNames = null;
     }
 };
+
+WI.JavaScriptRuntimeCompletionProvider._commandLineAPI = [
+    "$",
+    "$$",
+    "$0",
+    "$_",
+    "$x",
+    "clear",
+    "copy",
+    "dir",
+    "dirxml",
+    "getEventListeners",
+    "inspect",
+    "keys",
+    "monitorEvents",
+    "profile",
+    "profileEnd",
+    "queryObjects",
+    "table",
+    "unmonitorEvents",
+    "values",
+];