1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "bindings/core/v8/PrivateScriptRunner.h"
8 #include "bindings/core/v8/DOMWrapperWorld.h"
9 #include "bindings/core/v8/ExceptionState.h"
10 #include "bindings/core/v8/V8Binding.h"
11 #include "bindings/core/v8/V8PerContextData.h"
12 #include "bindings/core/v8/V8ScriptRunner.h"
13 #include "core/PrivateScriptSources.h"
15 #include "core/PrivateScriptSourcesForTesting.h"
17 #include "core/dom/ExceptionCode.h"
21 static void dumpV8Message(v8::Handle<v8::Message> message)
23 if (message.IsEmpty())
26 // FIXME: GetScriptOrigin() and GetLineNumber() return empty handles
27 // when they are called at the first time if V8 has a pending exception.
28 // So we need to call twice to get a correct ScriptOrigin and line number.
29 // This is a bug of V8.
30 message->GetScriptOrigin();
31 message->GetLineNumber();
33 v8::Handle<v8::Value> resourceName = message->GetScriptOrigin().ResourceName();
34 String fileName = "Unknown JavaScript file";
35 if (!resourceName.IsEmpty() && resourceName->IsString())
36 fileName = toCoreString(v8::Handle<v8::String>::Cast(resourceName));
37 int lineNumber = message->GetLineNumber();
38 v8::Handle<v8::String> errorMessage = message->Get();
39 fprintf(stderr, "%s (line %d): %s\n", fileName.utf8().data(), lineNumber, toCoreString(errorMessage).utf8().data());
42 static void dumpJSError(String exceptionName, v8::Handle<v8::Message> message)
44 // FIXME: Set a ScriptOrigin of the private script and print a more informative message.
46 fprintf(stderr, "Private script throws an exception: %s\n", exceptionName.utf8().data());
47 dumpV8Message(message);
51 static v8::Handle<v8::Value> compileAndRunPrivateScript(v8::Isolate* isolate, String scriptClassName, const unsigned char* source, size_t size)
54 String sourceString(reinterpret_cast<const char*>(source), size);
55 String fileName = scriptClassName + ".js";
56 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(v8String(isolate, sourceString), fileName, TextPosition::minimumPosition(), 0, isolate, NotSharableCrossOrigin, V8CacheOptionsOff);
57 if (block.HasCaught()) {
58 fprintf(stderr, "Private script error: Compile failed. (Class name = %s)\n", scriptClassName.utf8().data());
59 dumpV8Message(block.Message());
60 RELEASE_ASSERT_NOT_REACHED();
63 v8::Handle<v8::Value> result = V8ScriptRunner::runCompiledInternalScript(script, isolate);
64 if (block.HasCaught()) {
65 fprintf(stderr, "Private script error: installClass() failed. (Class name = %s)\n", scriptClassName.utf8().data());
66 dumpV8Message(block.Message());
67 RELEASE_ASSERT_NOT_REACHED();
72 // FIXME: If we have X.js, XPartial-1.js and XPartial-2.js, currently all of the JS files
73 // are compiled when any of the JS files is requested. Ideally we should avoid compiling
74 // unrelated JS files. For example, if a method in XPartial-1.js is requested, we just
75 // need to compile X.js and XPartial-1.js, and don't need to compile XPartial-2.js.
76 static void installPrivateScript(v8::Isolate* isolate, String className)
78 int compiledScriptCount = 0;
79 // |kPrivateScriptSourcesForTesting| is defined in V8PrivateScriptSources.h, which is auto-generated
80 // by make_private_script_source.py.
82 for (size_t index = 0; index < WTF_ARRAY_LENGTH(kPrivateScriptSourcesForTesting); index++) {
83 if (className == kPrivateScriptSourcesForTesting[index].className) {
84 compileAndRunPrivateScript(isolate, kPrivateScriptSourcesForTesting[index].scriptClassName, kPrivateScriptSourcesForTesting[index].source, kPrivateScriptSourcesForTesting[index].size);
85 compiledScriptCount++;
90 // |kPrivateScriptSources| is defined in V8PrivateScriptSources.h, which is auto-generated
91 // by make_private_script_source.py.
92 for (size_t index = 0; index < WTF_ARRAY_LENGTH(kPrivateScriptSources); index++) {
93 if (className == kPrivateScriptSources[index].className) {
94 compileAndRunPrivateScript(isolate, kPrivateScriptSources[index].scriptClassName, kPrivateScriptSources[index].source, kPrivateScriptSources[index].size);
95 compiledScriptCount++;
99 if (!compiledScriptCount) {
100 fprintf(stderr, "Private script error: Target source code was not found. (Class name = %s)\n", className.utf8().data());
101 RELEASE_ASSERT_NOT_REACHED();
105 static v8::Handle<v8::Value> installPrivateScriptRunner(v8::Isolate* isolate)
107 const String className = "PrivateScriptRunner";
109 // |kPrivateScriptSources| is defined in V8PrivateScriptSources.h, which is auto-generated
110 // by make_private_script_source.py.
111 for (index = 0; index < WTF_ARRAY_LENGTH(kPrivateScriptSources); index++) {
112 if (className == kPrivateScriptSources[index].className)
115 if (index == WTF_ARRAY_LENGTH(kPrivateScriptSources)) {
116 fprintf(stderr, "Private script error: Target source code was not found. (Class name = %s)\n", className.utf8().data());
117 RELEASE_ASSERT_NOT_REACHED();
119 return compileAndRunPrivateScript(isolate, className, kPrivateScriptSources[index].source, kPrivateScriptSources[index].size);
122 static v8::Handle<v8::Object> classObjectOfPrivateScript(ScriptState* scriptState, String className)
124 ASSERT(scriptState->perContextData());
125 ASSERT(scriptState->executionContext());
126 v8::Isolate* isolate = scriptState->isolate();
127 v8::Handle<v8::Value> compiledClass = scriptState->perContextData()->compiledPrivateScript(className);
128 if (compiledClass.IsEmpty()) {
129 v8::Handle<v8::Value> installedClasses = scriptState->perContextData()->compiledPrivateScript("PrivateScriptRunner");
130 if (installedClasses.IsEmpty()) {
131 installedClasses = installPrivateScriptRunner(isolate);
132 scriptState->perContextData()->setCompiledPrivateScript("PrivateScriptRunner", installedClasses);
134 RELEASE_ASSERT(!installedClasses.IsEmpty());
135 RELEASE_ASSERT(installedClasses->IsObject());
137 installPrivateScript(isolate, className);
138 compiledClass = v8::Handle<v8::Object>::Cast(installedClasses)->Get(v8String(isolate, className));
139 RELEASE_ASSERT(!compiledClass.IsEmpty());
140 RELEASE_ASSERT(compiledClass->IsObject());
141 scriptState->perContextData()->setCompiledPrivateScript(className, compiledClass);
143 return v8::Handle<v8::Object>::Cast(compiledClass);
146 static void initializeHolderIfNeeded(ScriptState* scriptState, v8::Handle<v8::Object> classObject, v8::Handle<v8::Value> holder)
148 RELEASE_ASSERT(!holder.IsEmpty());
149 RELEASE_ASSERT(holder->IsObject());
150 v8::Handle<v8::Object> holderObject = v8::Handle<v8::Object>::Cast(holder);
151 v8::Isolate* isolate = scriptState->isolate();
152 v8::Handle<v8::Value> isInitialized = V8HiddenValue::getHiddenValue(isolate, holderObject, V8HiddenValue::privateScriptObjectIsInitialized(isolate));
153 if (isInitialized.IsEmpty()) {
155 v8::Handle<v8::Value> initializeFunction = classObject->Get(v8String(isolate, "initialize"));
156 if (!initializeFunction.IsEmpty() && initializeFunction->IsFunction()) {
158 V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(initializeFunction), scriptState->executionContext(), holder, 0, 0, isolate);
159 if (block.HasCaught()) {
160 fprintf(stderr, "Private script error: Object constructor threw an exception.\n");
161 dumpV8Message(block.Message());
162 RELEASE_ASSERT_NOT_REACHED();
166 // Inject the prototype object of the private script into the prototype chain of the holder object.
167 // This is necessary to let the holder object use properties defined on the prototype object
168 // of the private script. (e.g., if the prototype object has |foo|, the holder object should be able
169 // to use it with |this.foo|.)
170 if (classObject->GetPrototype() != holderObject->GetPrototype())
171 classObject->SetPrototype(holderObject->GetPrototype());
172 holderObject->SetPrototype(classObject);
174 isInitialized = v8Boolean(true, isolate);
175 V8HiddenValue::setHiddenValue(isolate, holderObject, V8HiddenValue::privateScriptObjectIsInitialized(isolate), isInitialized);
179 v8::Handle<v8::Value> PrivateScriptRunner::installClassIfNeeded(LocalFrame* frame, String className)
182 return v8::Handle<v8::Value>();
183 v8::HandleScope handleScope(toIsolate(frame));
184 v8::Handle<v8::Context> context = toV8Context(frame, DOMWrapperWorld::privateScriptIsolatedWorld());
185 if (context.IsEmpty())
186 return v8::Handle<v8::Value>();
187 ScriptState* scriptState = ScriptState::from(context);
188 if (!scriptState->executionContext())
189 return v8::Handle<v8::Value>();
191 ScriptState::Scope scope(scriptState);
192 return classObjectOfPrivateScript(scriptState, className);
195 v8::Handle<v8::Value> PrivateScriptRunner::runDOMAttributeGetter(ScriptState* scriptState, String className, String attributeName, v8::Handle<v8::Value> holder)
197 v8::Handle<v8::Object> classObject = classObjectOfPrivateScript(scriptState, className);
198 v8::Handle<v8::Value> descriptor = classObject->GetOwnPropertyDescriptor(v8String(scriptState->isolate(), attributeName));
199 if (descriptor.IsEmpty() || !descriptor->IsObject()) {
200 fprintf(stderr, "Private script error: Target DOM attribute getter was not found. (Class name = %s, Attribute name = %s)\n", className.utf8().data(), attributeName.utf8().data());
201 RELEASE_ASSERT_NOT_REACHED();
203 v8::Handle<v8::Value> getter = v8::Handle<v8::Object>::Cast(descriptor)->Get(v8String(scriptState->isolate(), "get"));
204 if (getter.IsEmpty() || !getter->IsFunction()) {
205 fprintf(stderr, "Private script error: Target DOM attribute getter was not found. (Class name = %s, Attribute name = %s)\n", className.utf8().data(), attributeName.utf8().data());
206 RELEASE_ASSERT_NOT_REACHED();
208 initializeHolderIfNeeded(scriptState, classObject, holder);
209 return V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(getter), scriptState->executionContext(), holder, 0, 0, scriptState->isolate());
212 void PrivateScriptRunner::runDOMAttributeSetter(ScriptState* scriptState, String className, String attributeName, v8::Handle<v8::Value> holder, v8::Handle<v8::Value> v8Value)
214 v8::Handle<v8::Object> classObject = classObjectOfPrivateScript(scriptState, className);
215 v8::Handle<v8::Value> descriptor = classObject->GetOwnPropertyDescriptor(v8String(scriptState->isolate(), attributeName));
216 if (descriptor.IsEmpty() || !descriptor->IsObject()) {
217 fprintf(stderr, "Private script error: Target DOM attribute setter was not found. (Class name = %s, Attribute name = %s)\n", className.utf8().data(), attributeName.utf8().data());
218 RELEASE_ASSERT_NOT_REACHED();
220 v8::Handle<v8::Value> setter = v8::Handle<v8::Object>::Cast(descriptor)->Get(v8String(scriptState->isolate(), "set"));
221 if (setter.IsEmpty() || !setter->IsFunction()) {
222 fprintf(stderr, "Private script error: Target DOM attribute setter was not found. (Class name = %s, Attribute name = %s)\n", className.utf8().data(), attributeName.utf8().data());
223 RELEASE_ASSERT_NOT_REACHED();
225 initializeHolderIfNeeded(scriptState, classObject, holder);
226 v8::Handle<v8::Value> argv[] = { v8Value };
227 V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(setter), scriptState->executionContext(), holder, WTF_ARRAY_LENGTH(argv), argv, scriptState->isolate());
230 v8::Handle<v8::Value> PrivateScriptRunner::runDOMMethod(ScriptState* scriptState, String className, String methodName, v8::Handle<v8::Value> holder, int argc, v8::Handle<v8::Value> argv[])
232 v8::Handle<v8::Object> classObject = classObjectOfPrivateScript(scriptState, className);
233 v8::Handle<v8::Value> method = classObject->Get(v8String(scriptState->isolate(), methodName));
234 if (method.IsEmpty() || !method->IsFunction()) {
235 fprintf(stderr, "Private script error: Target DOM method was not found. (Class name = %s, Method name = %s)\n", className.utf8().data(), methodName.utf8().data());
236 RELEASE_ASSERT_NOT_REACHED();
238 initializeHolderIfNeeded(scriptState, classObject, holder);
239 return V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(method), scriptState->executionContext(), holder, argc, argv, scriptState->isolate());
242 bool PrivateScriptRunner::rethrowExceptionInPrivateScript(v8::Isolate* isolate, ExceptionState& exceptionState, v8::TryCatch& block)
244 v8::Handle<v8::Value> exception = block.Exception();
245 if (exception.IsEmpty() || !exception->IsObject())
248 v8::Handle<v8::Object> exceptionObject = v8::Handle<v8::Object>::Cast(exception);
249 v8::Handle<v8::Value> name = exceptionObject->Get(v8String(isolate, "name"));
250 if (name.IsEmpty() || !name->IsString())
253 v8::Handle<v8::Message> tryCatchMessage = block.Message();
254 v8::Handle<v8::Value> message = exceptionObject->Get(v8String(isolate, "message"));
255 String messageString;
256 if (!message.IsEmpty() && message->IsString())
257 messageString = toCoreString(v8::Handle<v8::String>::Cast(message));
259 String exceptionName = toCoreString(v8::Handle<v8::String>::Cast(name));
260 if (exceptionName == "DOMExceptionInPrivateScript") {
261 v8::Handle<v8::Value> code = exceptionObject->Get(v8String(isolate, "code"));
262 RELEASE_ASSERT(!code.IsEmpty() && code->IsInt32());
263 exceptionState.throwDOMException(toInt32(code), messageString);
264 exceptionState.throwIfNeeded();
267 if (exceptionName == "Error") {
268 exceptionState.throwDOMException(V8GeneralError, messageString);
269 exceptionState.throwIfNeeded();
270 dumpJSError(exceptionName, tryCatchMessage);
273 if (exceptionName == "TypeError") {
274 exceptionState.throwDOMException(V8TypeError, messageString);
275 exceptionState.throwIfNeeded();
276 dumpJSError(exceptionName, tryCatchMessage);
279 if (exceptionName == "RangeError") {
280 exceptionState.throwDOMException(V8RangeError, messageString);
281 exceptionState.throwIfNeeded();
282 dumpJSError(exceptionName, tryCatchMessage);
285 if (exceptionName == "SyntaxError") {
286 exceptionState.throwDOMException(V8SyntaxError, messageString);
287 exceptionState.throwIfNeeded();
288 dumpJSError(exceptionName, tryCatchMessage);
291 if (exceptionName == "ReferenceError") {
292 exceptionState.throwDOMException(V8ReferenceError, messageString);
293 exceptionState.throwIfNeeded();
294 dumpJSError(exceptionName, tryCatchMessage);