Web Inspector: Add Context Menus to Object Tree properties
[WebKit-https.git] / Source / JavaScriptCore / inspector / JSInjectedScriptHost.cpp
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "JSInjectedScriptHost.h"
28
29 #include "DateInstance.h"
30 #include "Error.h"
31 #include "InjectedScriptHost.h"
32 #include "JSArray.h"
33 #include "JSBoundFunction.h"
34 #include "JSCInlines.h"
35 #include "JSFunction.h"
36 #include "JSInjectedScriptHostPrototype.h"
37 #include "JSMap.h"
38 #include "JSSet.h"
39 #include "JSTypedArrays.h"
40 #include "JSWeakMap.h"
41 #include "ObjectConstructor.h"
42 #include "RegExpObject.h"
43 #include "SourceCode.h"
44 #include "TypedArrayInlines.h"
45 #include "WeakMapData.h"
46
47 #if ENABLE(PROMISES)
48 #include "JSPromise.h"
49 #endif
50
51 using namespace JSC;
52
53 namespace Inspector {
54
55 const ClassInfo JSInjectedScriptHost::s_info = { "InjectedScriptHost", &Base::s_info, 0, CREATE_METHOD_TABLE(JSInjectedScriptHost) };
56
57 JSInjectedScriptHost::JSInjectedScriptHost(VM& vm, Structure* structure, PassRefPtr<InjectedScriptHost> impl)
58     : JSDestructibleObject(vm, structure)
59     , m_impl(impl.leakRef())
60 {
61 }
62
63 void JSInjectedScriptHost::finishCreation(VM& vm)
64 {
65     Base::finishCreation(vm);
66     ASSERT(inherits(info()));
67 }
68
69 JSObject* JSInjectedScriptHost::createPrototype(VM& vm, JSGlobalObject* globalObject)
70 {
71     return JSInjectedScriptHostPrototype::create(vm, globalObject, JSInjectedScriptHostPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
72 }
73
74 void JSInjectedScriptHost::destroy(JSC::JSCell* cell)
75 {
76     JSInjectedScriptHost* thisObject = static_cast<JSInjectedScriptHost*>(cell);
77     thisObject->JSInjectedScriptHost::~JSInjectedScriptHost();
78 }
79
80 void JSInjectedScriptHost::releaseImpl()
81 {
82     if (m_impl) {
83         m_impl->deref();
84         m_impl = nullptr;
85     }
86 }
87
88 JSInjectedScriptHost::~JSInjectedScriptHost()
89 {
90     releaseImpl();
91 }
92
93 JSValue JSInjectedScriptHost::evaluate(ExecState* exec) const
94 {
95     JSGlobalObject* globalObject = exec->lexicalGlobalObject();
96     return globalObject->evalFunction();
97 }
98
99 JSValue JSInjectedScriptHost::internalConstructorName(ExecState* exec)
100 {
101     if (exec->argumentCount() < 1)
102         return jsUndefined();
103
104     JSObject* object = jsCast<JSObject*>(exec->uncheckedArgument(0).toThis(exec, NotStrictMode));
105     return jsString(exec, JSObject::calculatedClassName(object));
106 }
107
108 JSValue JSInjectedScriptHost::isHTMLAllCollection(ExecState* exec)
109 {
110     if (exec->argumentCount() < 1)
111         return jsUndefined();
112
113     JSValue value = exec->uncheckedArgument(0);
114     return jsBoolean(impl().isHTMLAllCollection(value));
115 }
116
117 JSValue JSInjectedScriptHost::subtype(ExecState* exec)
118 {
119     if (exec->argumentCount() < 1)
120         return jsUndefined();
121
122     JSValue value = exec->uncheckedArgument(0);
123     if (value.isString())
124         return exec->vm().smallStrings.stringString();
125     if (value.isBoolean())
126         return exec->vm().smallStrings.booleanString();
127     if (value.isNumber())
128         return exec->vm().smallStrings.numberString();
129     if (value.isSymbol())
130         return exec->vm().smallStrings.symbolString();
131
132     JSObject* object = asObject(value);
133     if (object && object->isErrorInstance())
134         return jsNontrivialString(exec, ASCIILiteral("error"));
135
136     if (value.inherits(JSArray::info()))
137         return jsNontrivialString(exec, ASCIILiteral("array"));
138     if (value.inherits(DateInstance::info()))
139         return jsNontrivialString(exec, ASCIILiteral("date"));
140     if (value.inherits(RegExpObject::info()))
141         return jsNontrivialString(exec, ASCIILiteral("regexp"));
142
143     if (value.inherits(JSMap::info()))
144         return jsNontrivialString(exec, ASCIILiteral("map"));
145     if (value.inherits(JSSet::info()))
146         return jsNontrivialString(exec, ASCIILiteral("set"));
147     if (value.inherits(JSWeakMap::info()))
148         return jsNontrivialString(exec, ASCIILiteral("weakmap"));
149
150     if (value.inherits(JSInt8Array::info()) || value.inherits(JSInt16Array::info()) || value.inherits(JSInt32Array::info()))
151         return jsNontrivialString(exec, ASCIILiteral("array"));
152     if (value.inherits(JSUint8Array::info()) || value.inherits(JSUint16Array::info()) || value.inherits(JSUint32Array::info()))
153         return jsNontrivialString(exec, ASCIILiteral("array"));
154     if (value.inherits(JSFloat32Array::info()) || value.inherits(JSFloat64Array::info()))
155         return jsNontrivialString(exec, ASCIILiteral("array"));
156
157     return impl().subtype(exec, value);
158 }
159
160 JSValue JSInjectedScriptHost::functionDetails(ExecState* exec)
161 {
162     if (exec->argumentCount() < 1)
163         return jsUndefined();
164
165     JSValue value = exec->uncheckedArgument(0);
166     if (!value.asCell()->inherits(JSFunction::info()))
167         return jsUndefined();
168
169     // FIXME: This should provide better details for JSBoundFunctions.
170
171     JSFunction* function = jsCast<JSFunction*>(value);
172     const SourceCode* sourceCode = function->sourceCode();
173     if (!sourceCode)
174         return jsUndefined();
175
176     // In the inspector protocol all positions are 0-based while in SourceCode they are 1-based
177     int lineNumber = sourceCode->firstLine();
178     if (lineNumber)
179         lineNumber -= 1;
180     int columnNumber = sourceCode->startColumn();
181     if (columnNumber)
182         columnNumber -= 1;
183
184     String scriptID = String::number(sourceCode->provider()->asID());
185     JSObject* location = constructEmptyObject(exec);
186     location->putDirect(exec->vm(), Identifier(exec, "scriptId"), jsString(exec, scriptID));
187     location->putDirect(exec->vm(), Identifier(exec, "lineNumber"), jsNumber(lineNumber));
188     location->putDirect(exec->vm(), Identifier(exec, "columnNumber"), jsNumber(columnNumber));
189
190     JSObject* result = constructEmptyObject(exec);
191     result->putDirect(exec->vm(), Identifier(exec, "location"), location);
192
193     String name = function->name(exec);
194     if (!name.isEmpty())
195         result->putDirect(exec->vm(), Identifier(exec, "name"), jsString(exec, name));
196
197     String displayName = function->displayName(exec);
198     if (!displayName.isEmpty())
199         result->putDirect(exec->vm(), Identifier(exec, "displayName"), jsString(exec, displayName));
200
201     // FIXME: provide function scope data in "scopesRaw" property when JSC supports it.
202     // <https://webkit.org/b/87192> [JSC] expose function (closure) inner context to debugger
203
204     return result;
205 }
206
207 static JSObject* constructInternalProperty(ExecState* exec, const String& name, JSValue value)
208 {
209     JSObject* result = constructEmptyObject(exec);
210     result->putDirect(exec->vm(), Identifier(exec, "name"), jsString(exec, name));
211     result->putDirect(exec->vm(), Identifier(exec, "value"), value);
212     return result;
213 }
214
215 JSValue JSInjectedScriptHost::getInternalProperties(ExecState* exec)
216 {
217     if (exec->argumentCount() < 1)
218         return jsUndefined();
219
220     JSValue value = exec->uncheckedArgument(0);
221
222 #if ENABLE(PROMISES)
223     if (JSPromise* promise = jsDynamicCast<JSPromise*>(value)) {
224         unsigned index = 0;
225         JSArray* array = constructEmptyArray(exec, nullptr);
226         switch (promise->status()) {
227         case JSPromise::Status::Unresolved:
228             array->putDirectIndex(exec, index++, constructInternalProperty(exec, ASCIILiteral("status"), jsNontrivialString(exec, ASCIILiteral("pending"))));
229             break;
230         case JSPromise::Status::HasResolution:
231             array->putDirectIndex(exec, index++, constructInternalProperty(exec, ASCIILiteral("status"), jsNontrivialString(exec, ASCIILiteral("resolved"))));
232             array->putDirectIndex(exec, index++, constructInternalProperty(exec, ASCIILiteral("result"), promise->result()));
233             break;
234         case JSPromise::Status::HasRejection:
235             array->putDirectIndex(exec, index++, constructInternalProperty(exec, ASCIILiteral("status"), jsNontrivialString(exec, ASCIILiteral("rejected"))));
236             array->putDirectIndex(exec, index++, constructInternalProperty(exec, ASCIILiteral("result"), promise->result()));
237             break;
238         }
239         // FIXME: <https://webkit.org/b/141664> Web Inspector: ES6: Improved Support for Promises - Promise Reactions
240         return array;
241     }
242 #endif
243
244     if (JSBoundFunction* boundFunction = jsDynamicCast<JSBoundFunction*>(value)) {
245         unsigned index = 0;
246         JSArray* array = constructEmptyArray(exec, nullptr, 3);
247         array->putDirectIndex(exec, index++, constructInternalProperty(exec, "targetFunction", boundFunction->targetFunction()));
248         array->putDirectIndex(exec, index++, constructInternalProperty(exec, "boundThis", boundFunction->boundThis()));
249         array->putDirectIndex(exec, index++, constructInternalProperty(exec, "boundArgs", boundFunction->boundArgs()));
250         return array;
251     }
252
253     return jsUndefined();
254 }
255
256 JSValue JSInjectedScriptHost::weakMapEntries(ExecState* exec)
257 {
258     if (exec->argumentCount() < 1)
259         return jsUndefined();
260
261     JSValue value = exec->uncheckedArgument(0);
262     JSWeakMap* weakMap = jsDynamicCast<JSWeakMap*>(value);
263     if (!weakMap)
264         return jsUndefined();
265
266     unsigned fetched = 0;
267     unsigned numberToFetch = 100;
268
269     JSValue numberToFetchArg = exec->argument(1);
270     double fetchDouble = numberToFetchArg.toInteger(exec);
271     if (fetchDouble >= 0)
272         numberToFetch = static_cast<unsigned>(fetchDouble);
273
274     JSArray* array = constructEmptyArray(exec, nullptr);
275     for (auto it = weakMap->weakMapData()->begin(); it != weakMap->weakMapData()->end(); ++it) {
276         JSObject* entry = constructEmptyObject(exec);
277         entry->putDirect(exec->vm(), Identifier(exec, "key"), it->key);
278         entry->putDirect(exec->vm(), Identifier(exec, "value"), it->value.get());
279         array->putDirectIndex(exec, fetched++, entry);
280         if (numberToFetch && fetched >= numberToFetch)
281             break;
282     }
283
284     return array;
285 }
286
287 JSValue toJS(ExecState* exec, JSGlobalObject* globalObject, InjectedScriptHost* impl)
288 {
289     if (!impl)
290         return jsNull();
291
292     JSObject* prototype = JSInjectedScriptHost::createPrototype(exec->vm(), globalObject);
293     Structure* structure = JSInjectedScriptHost::createStructure(exec->vm(), globalObject, prototype);
294     JSInjectedScriptHost* injectedScriptHost = JSInjectedScriptHost::create(exec->vm(), structure, impl);
295
296     return injectedScriptHost;
297 }
298
299 JSInjectedScriptHost* toJSInjectedScriptHost(JSValue value)
300 {
301     return value.inherits(JSInjectedScriptHost::info()) ? jsCast<JSInjectedScriptHost*>(value) : nullptr;
302 }
303
304 } // namespace Inspector