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. 4 5 #include "config.h" 6 #include "bindings/core/v8/PrivateScriptRunner.h" 7 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" 14 #ifndef NDEBUG 15 #include "core/PrivateScriptSourcesForTesting.h" 16 #endif 17 #include "core/dom/ExceptionCode.h" 18 #include "platform/PlatformResourceLoader.h" 19 20 namespace blink { 21 22 static void dumpV8Message(v8::Handle<v8::Message> message) 23 { 24 if (message.IsEmpty()) 25 return; 26 27 // FIXME: GetScriptOrigin() and GetLineNumber() return empty handles 28 // when they are called at the first time if V8 has a pending exception. 29 // So we need to call twice to get a correct ScriptOrigin and line number. 30 // This is a bug of V8. 31 message->GetScriptOrigin(); 32 message->GetLineNumber(); 33 34 v8::Handle<v8::Value> resourceName = message->GetScriptOrigin().ResourceName(); 35 String fileName = "Unknown JavaScript file"; 36 if (!resourceName.IsEmpty() && resourceName->IsString()) 37 fileName = toCoreString(v8::Handle<v8::String>::Cast(resourceName)); 38 int lineNumber = message->GetLineNumber(); 39 v8::Handle<v8::String> errorMessage = message->Get(); 40 fprintf(stderr, "%s (line %d): %s\n", fileName.utf8().data(), lineNumber, toCoreString(errorMessage).utf8().data()); 41 } 42 43 static v8::Handle<v8::Value> compileAndRunPrivateScript(v8::Isolate* isolate, String scriptClassName, const char* source, size_t size) 44 { 45 v8::TryCatch block; 46 String sourceString(source, size); 47 String fileName = scriptClassName + ".js"; 48 v8::Handle<v8::Script> script = V8ScriptRunner::compileScript(v8String(isolate, sourceString), fileName, TextPosition::minimumPosition(), 0, 0, isolate, NotSharableCrossOrigin, V8CacheOptionsOff); 49 if (block.HasCaught()) { 50 fprintf(stderr, "Private script error: Compile failed. (Class name = %s)\n", scriptClassName.utf8().data()); 51 dumpV8Message(block.Message()); 52 RELEASE_ASSERT_NOT_REACHED(); 53 } 54 55 v8::Handle<v8::Value> result = V8ScriptRunner::runCompiledInternalScript(script, isolate); 56 if (block.HasCaught()) { 57 fprintf(stderr, "Private script error: installClass() failed. (Class name = %s)\n", scriptClassName.utf8().data()); 58 dumpV8Message(block.Message()); 59 RELEASE_ASSERT_NOT_REACHED(); 60 } 61 return result; 62 } 63 64 // FIXME: If we have X.js, XPartial-1.js and XPartial-2.js, currently all of the JS files 65 // are compiled when any of the JS files is requested. Ideally we should avoid compiling 66 // unrelated JS files. For example, if a method in XPartial-1.js is requested, we just 67 // need to compile X.js and XPartial-1.js, and don't need to compile XPartial-2.js. 68 static void installPrivateScript(v8::Isolate* isolate, String className) 69 { 70 int compiledScriptCount = 0; 71 // |kPrivateScriptSourcesForTesting| is defined in V8PrivateScriptSources.h, which is auto-generated 72 // by make_private_script_source.py. 73 #ifndef NDEBUG 74 for (size_t index = 0; index < WTF_ARRAY_LENGTH(kPrivateScriptSourcesForTesting); index++) { 75 if (className == kPrivateScriptSourcesForTesting[index].className) { 76 compileAndRunPrivateScript(isolate, kPrivateScriptSourcesForTesting[index].scriptClassName, kPrivateScriptSourcesForTesting[index].source, kPrivateScriptSourcesForTesting[index].size); 77 compiledScriptCount++; 78 } 79 } 80 #endif 81 82 // |kPrivateScriptSources| is defined in V8PrivateScriptSources.h, which is auto-generated 83 // by make_private_script_source.py. 84 for (size_t index = 0; index < WTF_ARRAY_LENGTH(kPrivateScriptSources); index++) { 85 if (className == kPrivateScriptSources[index].className) { 86 String resourceData = loadResourceAsASCIIString(kPrivateScriptSources[index].resourceFile); 87 compileAndRunPrivateScript(isolate, kPrivateScriptSources[index].scriptClassName, resourceData.utf8().data(), resourceData.length()); 88 compiledScriptCount++; 89 } 90 } 91 92 if (!compiledScriptCount) { 93 fprintf(stderr, "Private script error: Target source code was not found. (Class name = %s)\n", className.utf8().data()); 94 RELEASE_ASSERT_NOT_REACHED(); 95 } 96 } 97 98 static v8::Handle<v8::Value> installPrivateScriptRunner(v8::Isolate* isolate) 99 { 100 const String className = "PrivateScriptRunner"; 101 size_t index; 102 // |kPrivateScriptSources| is defined in V8PrivateScriptSources.h, which is auto-generated 103 // by make_private_script_source.py. 104 for (index = 0; index < WTF_ARRAY_LENGTH(kPrivateScriptSources); index++) { 105 if (className == kPrivateScriptSources[index].className) 106 break; 107 } 108 if (index == WTF_ARRAY_LENGTH(kPrivateScriptSources)) { 109 fprintf(stderr, "Private script error: Target source code was not found. (Class name = %s)\n", className.utf8().data()); 110 RELEASE_ASSERT_NOT_REACHED(); 111 } 112 String resourceData = loadResourceAsASCIIString(kPrivateScriptSources[index].resourceFile); 113 return compileAndRunPrivateScript(isolate, className, resourceData.utf8().data(), resourceData.length()); 114 } 115 116 static v8::Handle<v8::Object> classObjectOfPrivateScript(ScriptState* scriptState, String className) 117 { 118 ASSERT(scriptState->perContextData()); 119 ASSERT(scriptState->executionContext()); 120 v8::Isolate* isolate = scriptState->isolate(); 121 v8::Handle<v8::Value> compiledClass = scriptState->perContextData()->compiledPrivateScript(className); 122 if (compiledClass.IsEmpty()) { 123 v8::Handle<v8::Value> installedClasses = scriptState->perContextData()->compiledPrivateScript("PrivateScriptRunner"); 124 if (installedClasses.IsEmpty()) { 125 installedClasses = installPrivateScriptRunner(isolate); 126 scriptState->perContextData()->setCompiledPrivateScript("PrivateScriptRunner", installedClasses); 127 } 128 RELEASE_ASSERT(!installedClasses.IsEmpty()); 129 RELEASE_ASSERT(installedClasses->IsObject()); 130 131 installPrivateScript(isolate, className); 132 compiledClass = v8::Handle<v8::Object>::Cast(installedClasses)->Get(v8String(isolate, className)); 133 RELEASE_ASSERT(!compiledClass.IsEmpty()); 134 RELEASE_ASSERT(compiledClass->IsObject()); 135 scriptState->perContextData()->setCompiledPrivateScript(className, compiledClass); 136 } 137 return v8::Handle<v8::Object>::Cast(compiledClass); 138 } 139 140 static void initializeHolderIfNeeded(ScriptState* scriptState, v8::Handle<v8::Object> classObject, v8::Handle<v8::Value> holder) 141 { 142 RELEASE_ASSERT(!holder.IsEmpty()); 143 RELEASE_ASSERT(holder->IsObject()); 144 v8::Handle<v8::Object> holderObject = v8::Handle<v8::Object>::Cast(holder); 145 v8::Isolate* isolate = scriptState->isolate(); 146 v8::Handle<v8::Value> isInitialized = V8HiddenValue::getHiddenValue(isolate, holderObject, V8HiddenValue::privateScriptObjectIsInitialized(isolate)); 147 if (isInitialized.IsEmpty()) { 148 v8::TryCatch block; 149 v8::Handle<v8::Value> initializeFunction = classObject->Get(v8String(isolate, "initialize")); 150 if (!initializeFunction.IsEmpty() && initializeFunction->IsFunction()) { 151 v8::TryCatch block; 152 V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(initializeFunction), scriptState->executionContext(), holder, 0, 0, isolate); 153 if (block.HasCaught()) { 154 fprintf(stderr, "Private script error: Object constructor threw an exception.\n"); 155 dumpV8Message(block.Message()); 156 RELEASE_ASSERT_NOT_REACHED(); 157 } 158 } 159 160 // Inject the prototype object of the private script into the prototype chain of the holder object. 161 // This is necessary to let the holder object use properties defined on the prototype object 162 // of the private script. (e.g., if the prototype object has |foo|, the holder object should be able 163 // to use it with |this.foo|.) 164 if (classObject->GetPrototype() != holderObject->GetPrototype()) 165 classObject->SetPrototype(holderObject->GetPrototype()); 166 holderObject->SetPrototype(classObject); 167 168 isInitialized = v8Boolean(true, isolate); 169 V8HiddenValue::setHiddenValue(isolate, holderObject, V8HiddenValue::privateScriptObjectIsInitialized(isolate), isInitialized); 170 } 171 } 172 173 v8::Handle<v8::Value> PrivateScriptRunner::installClassIfNeeded(LocalFrame* frame, String className) 174 { 175 if (!frame) 176 return v8::Handle<v8::Value>(); 177 v8::HandleScope handleScope(toIsolate(frame)); 178 v8::Handle<v8::Context> context = toV8Context(frame, DOMWrapperWorld::privateScriptIsolatedWorld()); 179 if (context.IsEmpty()) 180 return v8::Handle<v8::Value>(); 181 ScriptState* scriptState = ScriptState::from(context); 182 if (!scriptState->executionContext()) 183 return v8::Handle<v8::Value>(); 184 185 ScriptState::Scope scope(scriptState); 186 return classObjectOfPrivateScript(scriptState, className); 187 } 188 189 namespace { 190 191 void rethrowExceptionInPrivateScript(v8::Isolate* isolate, v8::TryCatch& block, ScriptState* scriptStateInUserScript, ExceptionState::Context errorContext, const char* propertyName, const char* interfaceName) 192 { 193 v8::Handle<v8::Value> exception = block.Exception(); 194 RELEASE_ASSERT(!exception.IsEmpty() && exception->IsObject()); 195 196 v8::Handle<v8::Object> exceptionObject = v8::Handle<v8::Object>::Cast(exception); 197 v8::Handle<v8::Value> name = exceptionObject->Get(v8String(isolate, "name")); 198 RELEASE_ASSERT(!name.IsEmpty() && name->IsString()); 199 200 v8::Handle<v8::Message> tryCatchMessage = block.Message(); 201 v8::Handle<v8::Value> message = exceptionObject->Get(v8String(isolate, "message")); 202 String messageString; 203 if (!message.IsEmpty() && message->IsString()) 204 messageString = toCoreString(v8::Handle<v8::String>::Cast(message)); 205 206 String exceptionName = toCoreString(v8::Handle<v8::String>::Cast(name)); 207 if (exceptionName == "PrivateScriptException") { 208 v8::Handle<v8::Value> code = exceptionObject->Get(v8String(isolate, "code")); 209 RELEASE_ASSERT(!code.IsEmpty() && code->IsInt32()); 210 ScriptState::Scope scope(scriptStateInUserScript); 211 ExceptionState exceptionState(errorContext, propertyName, interfaceName, scriptStateInUserScript->context()->Global(), scriptStateInUserScript->isolate()); 212 exceptionState.throwDOMException(toInt32(code), messageString); 213 exceptionState.throwIfNeeded(); 214 return; 215 } 216 217 // Standard JS errors thrown by a private script are treated as real errors 218 // of the private script and crash the renderer, except for a stack overflow 219 // error. A stack overflow error can happen in a valid private script 220 // if user's script can create a recursion that involves the private script. 221 if (exceptionName == "RangeError" && messageString.contains("Maximum call stack size exceeded")) { 222 ScriptState::Scope scope(scriptStateInUserScript); 223 ExceptionState exceptionState(errorContext, propertyName, interfaceName, scriptStateInUserScript->context()->Global(), scriptStateInUserScript->isolate()); 224 exceptionState.throwDOMException(V8RangeError, messageString); 225 exceptionState.throwIfNeeded(); 226 return; 227 } 228 229 fprintf(stderr, "Private script error: %s was thrown.\n", exceptionName.utf8().data()); 230 dumpV8Message(tryCatchMessage); 231 RELEASE_ASSERT_NOT_REACHED(); 232 } 233 234 } // namespace 235 236 v8::Handle<v8::Value> PrivateScriptRunner::runDOMAttributeGetter(ScriptState* scriptState, ScriptState* scriptStateInUserScript, const char* className, const char* attributeName, v8::Handle<v8::Value> holder) 237 { 238 v8::Handle<v8::Object> classObject = classObjectOfPrivateScript(scriptState, className); 239 v8::Handle<v8::Value> descriptor = classObject->GetOwnPropertyDescriptor(v8String(scriptState->isolate(), attributeName)); 240 if (descriptor.IsEmpty() || !descriptor->IsObject()) { 241 fprintf(stderr, "Private script error: Target DOM attribute getter was not found. (Class name = %s, Attribute name = %s)\n", className, attributeName); 242 RELEASE_ASSERT_NOT_REACHED(); 243 } 244 v8::Handle<v8::Value> getter = v8::Handle<v8::Object>::Cast(descriptor)->Get(v8String(scriptState->isolate(), "get")); 245 if (getter.IsEmpty() || !getter->IsFunction()) { 246 fprintf(stderr, "Private script error: Target DOM attribute getter was not found. (Class name = %s, Attribute name = %s)\n", className, attributeName); 247 RELEASE_ASSERT_NOT_REACHED(); 248 } 249 initializeHolderIfNeeded(scriptState, classObject, holder); 250 v8::TryCatch block; 251 v8::Handle<v8::Value> result = V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(getter), scriptState->executionContext(), holder, 0, 0, scriptState->isolate()); 252 if (block.HasCaught()) { 253 rethrowExceptionInPrivateScript(scriptState->isolate(), block, scriptStateInUserScript, ExceptionState::GetterContext, attributeName, className); 254 block.ReThrow(); 255 return v8::Handle<v8::Value>(); 256 } 257 return result; 258 } 259 260 bool PrivateScriptRunner::runDOMAttributeSetter(ScriptState* scriptState, ScriptState* scriptStateInUserScript, const char* className, const char* attributeName, v8::Handle<v8::Value> holder, v8::Handle<v8::Value> v8Value) 261 { 262 v8::Handle<v8::Object> classObject = classObjectOfPrivateScript(scriptState, className); 263 v8::Handle<v8::Value> descriptor = classObject->GetOwnPropertyDescriptor(v8String(scriptState->isolate(), attributeName)); 264 if (descriptor.IsEmpty() || !descriptor->IsObject()) { 265 fprintf(stderr, "Private script error: Target DOM attribute setter was not found. (Class name = %s, Attribute name = %s)\n", className, attributeName); 266 RELEASE_ASSERT_NOT_REACHED(); 267 } 268 v8::Handle<v8::Value> setter = v8::Handle<v8::Object>::Cast(descriptor)->Get(v8String(scriptState->isolate(), "set")); 269 if (setter.IsEmpty() || !setter->IsFunction()) { 270 fprintf(stderr, "Private script error: Target DOM attribute setter was not found. (Class name = %s, Attribute name = %s)\n", className, attributeName); 271 RELEASE_ASSERT_NOT_REACHED(); 272 } 273 initializeHolderIfNeeded(scriptState, classObject, holder); 274 v8::Handle<v8::Value> argv[] = { v8Value }; 275 v8::TryCatch block; 276 V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(setter), scriptState->executionContext(), holder, WTF_ARRAY_LENGTH(argv), argv, scriptState->isolate()); 277 if (block.HasCaught()) { 278 rethrowExceptionInPrivateScript(scriptState->isolate(), block, scriptStateInUserScript, ExceptionState::SetterContext, attributeName, className); 279 block.ReThrow(); 280 return false; 281 } 282 return true; 283 } 284 285 v8::Handle<v8::Value> PrivateScriptRunner::runDOMMethod(ScriptState* scriptState, ScriptState* scriptStateInUserScript, const char* className, const char* methodName, v8::Handle<v8::Value> holder, int argc, v8::Handle<v8::Value> argv[]) 286 { 287 v8::Handle<v8::Object> classObject = classObjectOfPrivateScript(scriptState, className); 288 v8::Handle<v8::Value> method = classObject->Get(v8String(scriptState->isolate(), methodName)); 289 if (method.IsEmpty() || !method->IsFunction()) { 290 fprintf(stderr, "Private script error: Target DOM method was not found. (Class name = %s, Method name = %s)\n", className, methodName); 291 RELEASE_ASSERT_NOT_REACHED(); 292 } 293 initializeHolderIfNeeded(scriptState, classObject, holder); 294 v8::TryCatch block; 295 v8::Handle<v8::Value> result = V8ScriptRunner::callFunction(v8::Handle<v8::Function>::Cast(method), scriptState->executionContext(), holder, argc, argv, scriptState->isolate()); 296 if (block.HasCaught()) { 297 rethrowExceptionInPrivateScript(scriptState->isolate(), block, scriptStateInUserScript, ExceptionState::ExecutionContext, methodName, className); 298 block.ReThrow(); 299 return v8::Handle<v8::Value>(); 300 } 301 return result; 302 } 303 304 } // namespace blink 305