1/* 2 * Copyright (C) 2008, 2009 Google Inc. All rights reserved. 3 * Copyright (C) 2009 Apple 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 "ScriptController.h" 34 35#include "PlatformBridge.h" 36#include "CString.h" 37#include "Document.h" 38#include "DOMWindow.h" 39#include "Event.h" 40#include "EventListener.h" 41#include "EventNames.h" 42#include "Frame.h" 43#include "FrameLoaderClient.h" 44#include "Node.h" 45#include "NotImplemented.h" 46#include "npruntime_impl.h" 47#include "npruntime_priv.h" 48#include "NPV8Object.h" 49#include "ScriptSourceCode.h" 50#include "Settings.h" 51#include "V8Binding.h" 52#include "V8BindingState.h" 53#include "V8DOMWindow.h" 54#include "V8Event.h" 55#include "V8HTMLEmbedElement.h" 56#include "V8IsolatedContext.h" 57#include "V8NPObject.h" 58#include "V8Proxy.h" 59#include "Widget.h" 60#include "XSSAuditor.h" 61#include <wtf/StdLibExtras.h> 62 63namespace WebCore { 64 65void ScriptController::initializeThreading() 66{ 67 static bool initializedThreading = false; 68 if (!initializedThreading) { 69 WTF::initializeThreading(); 70 initializedThreading = true; 71 } 72} 73 74void ScriptController::setFlags(const char* string, int length) 75{ 76 v8::V8::SetFlagsFromString(string, length); 77} 78 79Frame* ScriptController::retrieveFrameForEnteredContext() 80{ 81 return V8Proxy::retrieveFrameForEnteredContext(); 82} 83 84Frame* ScriptController::retrieveFrameForCurrentContext() 85{ 86 return V8Proxy::retrieveFrameForCurrentContext(); 87} 88 89bool ScriptController::isSafeScript(Frame* target) 90{ 91 return V8BindingSecurity::canAccessFrame(V8BindingState::Only(), target, true); 92} 93 94void ScriptController::gcProtectJSWrapper(void* domObject) 95{ 96 V8GCController::gcProtect(domObject); 97} 98 99void ScriptController::gcUnprotectJSWrapper(void* domObject) 100{ 101 V8GCController::gcUnprotect(domObject); 102} 103 104ScriptController::ScriptController(Frame* frame) 105 : m_frame(frame) 106 , m_sourceURL(0) 107 , m_inExecuteScript(false) 108 , m_processingTimerCallback(false) 109 , m_paused(false) 110 , m_proxy(new V8Proxy(frame)) 111#if ENABLE(NETSCAPE_PLUGIN_API) 112 , m_windowScriptNPObject(0) 113#endif 114 , m_XSSAuditor(new XSSAuditor(frame)) 115{ 116} 117 118ScriptController::~ScriptController() 119{ 120 m_proxy->disconnectFrame(); 121} 122 123void ScriptController::clearScriptObjects() 124{ 125 PluginObjectMap::iterator it = m_pluginObjects.begin(); 126 for (; it != m_pluginObjects.end(); ++it) { 127 _NPN_UnregisterObject(it->second); 128 _NPN_ReleaseObject(it->second); 129 } 130 m_pluginObjects.clear(); 131 132#if ENABLE(NETSCAPE_PLUGIN_API) 133 if (m_windowScriptNPObject) { 134 // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window 135 // script object properly. 136 // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. 137 _NPN_DeallocateObject(m_windowScriptNPObject); 138 m_windowScriptNPObject = 0; 139 } 140#endif 141} 142 143void ScriptController::updateSecurityOrigin() 144{ 145 m_proxy->windowShell()->updateSecurityOrigin(); 146} 147 148void ScriptController::updatePlatformScriptObjects() 149{ 150 notImplemented(); 151} 152 153bool ScriptController::processingUserGesture(DOMWrapperWorld*) const 154{ 155 Frame* activeFrame = V8Proxy::retrieveFrameForEnteredContext(); 156 // No script is running, so it must be run by users. 157 if (!activeFrame) 158 return true; 159 160 V8Proxy* activeProxy = activeFrame->script()->proxy(); 161 162 v8::HandleScope handleScope; 163 v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(activeFrame); 164 // FIXME: find all cases context can be empty: 165 // 1) JS is disabled; 166 // 2) page is NULL; 167 if (v8Context.IsEmpty()) 168 return true; 169 170 v8::Context::Scope scope(v8Context); 171 172 v8::Handle<v8::Object> global = v8Context->Global(); 173 v8::Handle<v8::Value> jsEvent = global->Get(v8::String::NewSymbol("event")); 174 Event* event = (!jsEvent.IsEmpty() && jsEvent->IsObject()) ? V8Event::toNative(v8::Handle<v8::Object>::Cast(jsEvent)) : 0; 175 176 // Based on code from kjs_bindings.cpp. 177 // Note: This is more liberal than Firefox's implementation. 178 if (event) { 179 if (event->createdByDOM()) 180 return false; 181 182 const AtomicString& type = event->type(); 183 bool eventOk = 184 // mouse events 185 type == eventNames().clickEvent || type == eventNames().mousedownEvent || type == eventNames().mouseupEvent || type == eventNames().dblclickEvent 186 // keyboard events 187 || type == eventNames().keydownEvent || type == eventNames().keypressEvent || type == eventNames().keyupEvent 188 // other accepted events 189 || type == eventNames().selectEvent || type == eventNames().changeEvent || type == eventNames().focusEvent || type == eventNames().blurEvent || type == eventNames().submitEvent; 190 191 if (eventOk) 192 return true; 193 } else if (activeProxy->inlineCode() && !activeProxy->timerCallback()) { 194 // This is the <a href="javascript:window.open('...')> case -> we let it through. 195 return true; 196 } 197 198 // This is the <script>window.open(...)</script> case or a timer callback -> block it. 199 return false; 200} 201 202bool ScriptController::anyPageIsProcessingUserGesture() const 203{ 204 // FIXME: is this right? 205 return processingUserGesture(); 206} 207 208void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources) 209{ 210 m_proxy->evaluateInIsolatedWorld(worldID, sources, 0); 211} 212 213void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources, int extensionGroup) 214{ 215 m_proxy->evaluateInIsolatedWorld(worldID, sources, extensionGroup); 216} 217 218// Evaluate a script file in the environment of this proxy. 219ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) 220{ 221 String sourceURL = sourceCode.url(); 222 223 if (!m_XSSAuditor->canEvaluate(sourceCode.source())) { 224 // This script is not safe to be evaluated. 225 return ScriptValue(); 226 } 227 228 v8::HandleScope handleScope; 229 v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_proxy->frame()); 230 if (v8Context.IsEmpty()) 231 return ScriptValue(); 232 233 v8::Context::Scope scope(v8Context); 234 235 RefPtr<Frame> protect(m_frame); 236 237 v8::Local<v8::Value> object = m_proxy->evaluate(sourceCode, 0); 238 239 // Evaluating the JavaScript could cause the frame to be deallocated 240 // so we starot the keep alive timer here. 241 m_frame->keepAlive(); 242 243 if (object.IsEmpty() || object->IsUndefined()) 244 return ScriptValue(); 245 246 return ScriptValue(object); 247} 248 249void ScriptController::setEventHandlerLineNumber(int lineNumber) 250{ 251 m_proxy->setEventHandlerLineNumber(lineNumber); 252} 253 254void ScriptController::finishedWithEvent(Event* event) 255{ 256 m_proxy->finishedWithEvent(event); 257} 258 259// Create a V8 object with an interceptor of NPObjectPropertyGetter. 260void ScriptController::bindToWindowObject(Frame* frame, const String& key, NPObject* object) 261{ 262 v8::HandleScope handleScope; 263 264 v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame); 265 if (v8Context.IsEmpty()) 266 return; 267 268 v8::Context::Scope scope(v8Context); 269 270 v8::Handle<v8::Object> value = createV8ObjectForNPObject(object, 0); 271 272 // Attach to the global object. 273 v8::Handle<v8::Object> global = v8Context->Global(); 274 global->Set(v8String(key), value); 275} 276 277void ScriptController::collectGarbage() 278{ 279 v8::HandleScope handleScope; 280 v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_proxy->frame()); 281 if (v8Context.IsEmpty()) 282 return; 283 284 v8::Context::Scope scope(v8Context); 285 286 m_proxy->evaluate(ScriptSourceCode("if (window.gc) void(gc());"), 0); 287} 288 289void ScriptController::lowMemoryNotification() 290{ 291 v8::V8::LowMemoryNotification(); 292} 293 294bool ScriptController::haveInterpreter() const 295{ 296 return m_proxy->windowShell()->isContextInitialized(); 297} 298 299PassScriptInstance ScriptController::createScriptInstanceForWidget(Widget* widget) 300{ 301 ASSERT(widget); 302 303 if (widget->isFrameView()) 304 return 0; 305 306 NPObject* npObject = PlatformBridge::pluginScriptableObject(widget); 307 308 if (!npObject) 309 return 0; 310 311 // Frame Memory Management for NPObjects 312 // ------------------------------------- 313 // NPObjects are treated differently than other objects wrapped by JS. 314 // NPObjects can be created either by the browser (e.g. the main 315 // window object) or by the plugin (the main plugin object 316 // for a HTMLEmbedElement). Further, unlike most DOM Objects, the frame 317 // is especially careful to ensure NPObjects terminate at frame teardown because 318 // if a plugin leaks a reference, it could leak its objects (or the browser's objects). 319 // 320 // The Frame maintains a list of plugin objects (m_pluginObjects) 321 // which it can use to quickly find the wrapped embed object. 322 // 323 // Inside the NPRuntime, we've added a few methods for registering 324 // wrapped NPObjects. The purpose of the registration is because 325 // javascript garbage collection is non-deterministic, yet we need to 326 // be able to tear down the plugin objects immediately. When an object 327 // is registered, javascript can use it. When the object is destroyed, 328 // or when the object's "owning" object is destroyed, the object will 329 // be un-registered, and the javascript engine must not use it. 330 // 331 // Inside the javascript engine, the engine can keep a reference to the 332 // NPObject as part of its wrapper. However, before accessing the object 333 // it must consult the _NPN_Registry. 334 335 v8::Local<v8::Object> wrapper = createV8ObjectForNPObject(npObject, 0); 336 337#ifdef ANDROID_FIX 338 // TODO: this should be up streamed. 339 // HTMLEmbedElement::getInstance() will call this function with its closest 340 // ancestor who has the objectTag. So this "widget" may be already in the 341 // HashMap. If it does, even m_pluginObjects.set() is a no-op, we do need to 342 // call _NPN_ReleaseObject on the npObject to balance the reference count. 343 PluginObjectMap::iterator it = m_pluginObjects.find(widget); 344 if (it != m_pluginObjects.end()) { 345 ASSERT(it->second == npObject); 346 _NPN_ReleaseObject(it->second); 347 } 348#endif 349 350 // Track the plugin object. We've been given a reference to the object. 351 m_pluginObjects.set(widget, npObject); 352 353 return V8ScriptInstance::create(wrapper); 354} 355 356void ScriptController::cleanupScriptObjectsForPlugin(Widget* nativeHandle) 357{ 358 PluginObjectMap::iterator it = m_pluginObjects.find(nativeHandle); 359 if (it == m_pluginObjects.end()) 360 return; 361 _NPN_UnregisterObject(it->second); 362 _NPN_ReleaseObject(it->second); 363 m_pluginObjects.remove(it); 364} 365 366void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds) 367{ 368 worlds.append(mainThreadNormalWorld()); 369} 370 371void ScriptController::evaluateInWorld(const ScriptSourceCode& source, 372 DOMWrapperWorld* world) 373{ 374 Vector<ScriptSourceCode> sources; 375 sources.append(source); 376 // FIXME: Get an ID from the world param. 377 evaluateInIsolatedWorld(0, sources); 378} 379 380static NPObject* createNoScriptObject() 381{ 382 notImplemented(); 383 return 0; 384} 385 386static NPObject* createScriptObject(Frame* frame) 387{ 388 v8::HandleScope handleScope; 389 v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame); 390 if (v8Context.IsEmpty()) 391 return createNoScriptObject(); 392 393 v8::Context::Scope scope(v8Context); 394 DOMWindow* window = frame->domWindow(); 395 v8::Handle<v8::Value> global = toV8(window); 396 ASSERT(global->IsObject()); 397 return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(global), window); 398} 399 400NPObject* ScriptController::windowScriptNPObject() 401{ 402 if (m_windowScriptNPObject) 403 return m_windowScriptNPObject; 404 405 if (canExecuteScripts()) { 406 // JavaScript is enabled, so there is a JavaScript window object. 407 // Return an NPObject bound to the window object. 408 m_windowScriptNPObject = createScriptObject(m_frame); 409 _NPN_RegisterObject(m_windowScriptNPObject, 0); 410 } else { 411 // JavaScript is not enabled, so we cannot bind the NPObject to the 412 // JavaScript window object. Instead, we create an NPObject of a 413 // different class, one which is not bound to a JavaScript object. 414 m_windowScriptNPObject = createNoScriptObject(); 415 } 416 return m_windowScriptNPObject; 417} 418 419NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin) 420{ 421 // Can't create NPObjects when JavaScript is disabled. 422 if (!canExecuteScripts()) 423 return createNoScriptObject(); 424 425 v8::HandleScope handleScope; 426 v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_frame); 427 if (v8Context.IsEmpty()) 428 return createNoScriptObject(); 429 v8::Context::Scope scope(v8Context); 430 431 DOMWindow* window = m_frame->domWindow(); 432 v8::Handle<v8::Value> v8plugin = toV8(static_cast<HTMLEmbedElement*>(plugin)); 433 if (!v8plugin->IsObject()) 434 return createNoScriptObject(); 435 436 return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(v8plugin), window); 437} 438 439 440void ScriptController::clearWindowShell() 441{ 442 // V8 binding expects ScriptController::clearWindowShell only be called 443 // when a frame is loading a new page. V8Proxy::clearForNavigation 444 // creates a new context for the new page. 445 m_proxy->clearForNavigation(); 446} 447 448void ScriptController::attachDebugger(void*) 449{ 450 notImplemented(); 451} 452 453void ScriptController::updateDocument() 454{ 455 m_proxy->windowShell()->updateDocument(); 456} 457 458} // namespace WebCore 459