1/*
2 * Copyright (C) 2013 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "InjectedScriptBase.h"
34
35#include "DebuggerEvalEnabler.h"
36#include "JSCInlines.h"
37#include "JSGlobalObject.h"
38#include "JSLock.h"
39#include "JSNativeStdFunction.h"
40#include "NativeStdFunctionCell.h"
41#include "ScriptFunctionCall.h"
42#include <wtf/JSONValues.h>
43#include <wtf/text/StringConcatenateNumbers.h>
44
45namespace Inspector {
46
47InjectedScriptBase::InjectedScriptBase(const String& name)
48 : m_name(name)
49{
50}
51
52InjectedScriptBase::InjectedScriptBase(const String& name, Deprecated::ScriptObject injectedScriptObject, InspectorEnvironment* environment)
53 : m_name(name)
54 , m_injectedScriptObject(injectedScriptObject)
55 , m_environment(environment)
56{
57}
58
59InjectedScriptBase::~InjectedScriptBase()
60{
61}
62
63bool InjectedScriptBase::hasAccessToInspectedScriptState() const
64{
65 return m_environment && m_environment->canAccessInspectedScriptState(m_injectedScriptObject.globalObject());
66}
67
68const Deprecated::ScriptObject& InjectedScriptBase::injectedScriptObject() const
69{
70 return m_injectedScriptObject;
71}
72
73Expected<JSC::JSValue, NakedPtr<JSC::Exception>> InjectedScriptBase::callFunctionWithEvalEnabled(Deprecated::ScriptFunctionCall& function) const
74{
75 JSC::JSGlobalObject* globalObject = m_injectedScriptObject.globalObject();
76 JSC::DebuggerEvalEnabler evalEnabler(globalObject);
77 return function.call();
78}
79
80Ref<JSON::Value> InjectedScriptBase::makeCall(Deprecated::ScriptFunctionCall& function)
81{
82 if (hasNoValue() || !hasAccessToInspectedScriptState())
83 return JSON::Value::null();
84
85 auto result = callFunctionWithEvalEnabled(function);
86 if (!result)
87 return JSON::Value::create("Exception while making a call.");
88
89 RefPtr<JSON::Value> resultJSONValue = toInspectorValue(m_injectedScriptObject.globalObject(), result.value());
90 if (!resultJSONValue)
91 return JSON::Value::create(makeString("Object has too long reference chain (must not be longer than ", JSON::Value::maxDepth, ')'));
92
93 return resultJSONValue.releaseNonNull();
94}
95
96void InjectedScriptBase::makeEvalCall(ErrorString& errorString, Deprecated::ScriptFunctionCall& function, RefPtr<Protocol::Runtime::RemoteObject>& out_resultObject, Optional<bool>& out_wasThrown, Optional<int>& out_savedResultIndex)
97{
98 checkCallResult(errorString, makeCall(function), out_resultObject, out_wasThrown, out_savedResultIndex);
99}
100
101void InjectedScriptBase::makeAsyncCall(Deprecated::ScriptFunctionCall& function, AsyncCallCallback&& callback)
102{
103 if (hasNoValue() || !hasAccessToInspectedScriptState()) {
104 checkAsyncCallResult(JSON::Value::null(), callback);
105 return;
106 }
107
108 auto* globalObject = m_injectedScriptObject.globalObject();
109 JSC::VM& vm = globalObject->vm();
110
111 JSC::JSNativeStdFunction* jsFunction = nullptr;
112 {
113 JSC::JSLockHolder locker(vm);
114
115 jsFunction = JSC::JSNativeStdFunction::create(vm, globalObject, 1, String(), [&, callback = WTFMove(callback)] (JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) {
116 if (!callFrame)
117 checkAsyncCallResult(JSON::Value::create("Exception while making a call."), callback);
118 else if (auto resultJSONValue = toInspectorValue(globalObject, callFrame->argument(0)))
119 checkAsyncCallResult(resultJSONValue, callback);
120 else
121 checkAsyncCallResult(JSON::Value::create(makeString("Object has too long reference chain (must not be longer than ", JSON::Value::maxDepth, ')')), callback);
122 return JSC::JSValue::encode(JSC::jsUndefined());
123 });
124 }
125
126 function.appendArgument(JSC::JSValue(jsFunction));
127
128 auto result = callFunctionWithEvalEnabled(function);
129 ASSERT_UNUSED(result, result.value().isUndefined());
130
131 ASSERT(result);
132 if (!result) {
133 // Since `callback` is moved above, we can't call it if there's an exception while trying to
134 // execute the `JSNativeStdFunction` inside InjectedScriptSource.js.
135 jsFunction->nativeStdFunctionCell()->function()(globalObject, nullptr);
136 }
137}
138
139void InjectedScriptBase::checkCallResult(ErrorString& errorString, RefPtr<JSON::Value> result, RefPtr<Protocol::Runtime::RemoteObject>& out_resultObject, Optional<bool>& out_wasThrown, Optional<int>& out_savedResultIndex)
140{
141 if (!result) {
142 errorString = "Internal error: result value is empty"_s;
143 return;
144 }
145
146 if (result->type() == JSON::Value::Type::String) {
147 result->asString(errorString);
148 ASSERT(errorString.length());
149 return;
150 }
151
152 RefPtr<JSON::Object> resultTuple;
153 if (!result->asObject(resultTuple)) {
154 errorString = "Internal error: result is not an Object"_s;
155 return;
156 }
157
158 RefPtr<JSON::Object> resultObject;
159 if (!resultTuple->getObject("result"_s, resultObject)) {
160 errorString = "Internal error: result is not a pair of value and wasThrown flag"_s;
161 return;
162 }
163
164 bool wasThrown = false;
165 if (!resultTuple->getBoolean("wasThrown"_s, wasThrown)) {
166 errorString = "Internal error: result is not a pair of value and wasThrown flag"_s;
167 return;
168 }
169
170 out_resultObject = BindingTraits<Protocol::Runtime::RemoteObject>::runtimeCast(resultObject);
171
172 if (wasThrown)
173 out_wasThrown = wasThrown;
174
175 int savedResultIndex;
176 if (resultTuple->getInteger("savedResultIndex"_s, savedResultIndex))
177 out_savedResultIndex = savedResultIndex;
178}
179
180void InjectedScriptBase::checkAsyncCallResult(RefPtr<JSON::Value> result, const AsyncCallCallback& callback)
181{
182 ErrorString errorString;
183 RefPtr<Protocol::Runtime::RemoteObject> resultObject;
184 Optional<bool> wasThrown;
185 Optional<int> savedResultIndex;
186
187 checkCallResult(errorString, result, resultObject, wasThrown, savedResultIndex);
188
189 callback(errorString, WTFMove(resultObject), wasThrown, savedResultIndex);
190}
191
192} // namespace Inspector
193
194