1 /* 2 * Copyright (C) 1999-2001 Harri Porten (porten (at) kde.org) 3 * Copyright (C) 2001 Peter Kelly (pmk (at) post.com) 4 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 */ 20 21 #include "config.h" 22 #include "ScriptController.h" 23 24 #include "ScriptableDocumentParser.h" 25 #include "Event.h" 26 #include "EventNames.h" 27 #include "Frame.h" 28 #include "FrameLoaderClient.h" 29 #include "GCController.h" 30 #include "HTMLPlugInElement.h" 31 #include "InspectorInstrumentation.h" 32 #include "JSDocument.h" 33 #include "JSMainThreadExecState.h" 34 #include "NP_jsobject.h" 35 #include "Page.h" 36 #include "PageGroup.h" 37 #include "ScriptSourceCode.h" 38 #include "ScriptValue.h" 39 #include "Settings.h" 40 #include "StorageNamespace.h" 41 #include "UserGestureIndicator.h" 42 #include "WebCoreJSClientData.h" 43 #include "npruntime_impl.h" 44 #include "runtime_root.h" 45 #include <debugger/Debugger.h> 46 #include <runtime/InitializeThreading.h> 47 #include <runtime/JSLock.h> 48 #include <wtf/Threading.h> 49 50 using namespace JSC; 51 using namespace std; 52 53 namespace WebCore { 54 55 void ScriptController::initializeThreading() 56 { 57 JSC::initializeThreading(); 58 WTF::initializeMainThread(); 59 } 60 61 ScriptController::ScriptController(Frame* frame) 62 : m_frame(frame) 63 , m_sourceURL(0) 64 , m_inExecuteScript(false) 65 , m_processingTimerCallback(false) 66 , m_paused(false) 67 , m_allowPopupsFromPlugin(false) 68 #if ENABLE(NETSCAPE_PLUGIN_API) 69 , m_windowScriptNPObject(0) 70 #endif 71 #if PLATFORM(MAC) 72 , m_windowScriptObject(0) 73 #endif 74 { 75 #if PLATFORM(MAC) && ENABLE(JAVA_BRIDGE) 76 static bool initializedJavaJSBindings; 77 if (!initializedJavaJSBindings) { 78 initializedJavaJSBindings = true; 79 initJavaJSBindings(); 80 } 81 #endif 82 } 83 84 ScriptController::~ScriptController() 85 { 86 disconnectPlatformScriptObjects(); 87 88 if (m_cacheableBindingRootObject) { 89 m_cacheableBindingRootObject->invalidate(); 90 m_cacheableBindingRootObject = 0; 91 } 92 93 // It's likely that destroying m_windowShells will create a lot of garbage. 94 if (!m_windowShells.isEmpty()) { 95 while (!m_windowShells.isEmpty()) 96 destroyWindowShell(m_windowShells.begin()->first.get()); 97 gcController().garbageCollectSoon(); 98 } 99 } 100 101 void ScriptController::destroyWindowShell(DOMWrapperWorld* world) 102 { 103 ASSERT(m_windowShells.contains(world)); 104 m_windowShells.remove(world); 105 world->didDestroyWindowShell(this); 106 } 107 108 JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld* world) 109 { 110 ASSERT(!m_windowShells.contains(world)); 111 Strong<JSDOMWindowShell> windowShell(*world->globalData(), new JSDOMWindowShell(m_frame->domWindow(), world)); 112 Strong<JSDOMWindowShell> windowShell2(windowShell); 113 m_windowShells.add(world, windowShell); 114 world->didCreateWindowShell(this); 115 return windowShell.get(); 116 } 117 118 ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world) 119 { 120 const SourceCode& jsSourceCode = sourceCode.jsSourceCode(); 121 String sourceURL = ustringToString(jsSourceCode.provider()->url()); 122 123 // evaluate code. Returns the JS return value or 0 124 // if there was none, an error occurred or the type couldn't be converted. 125 126 // inlineCode is true for <a href="javascript:doSomething()"> 127 // and false for <script>doSomething()</script>. Check if it has the 128 // expected value in all cases. 129 // See smart window.open policy for where this is used. 130 JSDOMWindowShell* shell = windowShell(world); 131 ExecState* exec = shell->window()->globalExec(); 132 const String* savedSourceURL = m_sourceURL; 133 m_sourceURL = &sourceURL; 134 135 JSLock lock(SilenceAssertionsOnly); 136 137 RefPtr<Frame> protect = m_frame; 138 139 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine()); 140 141 exec->globalData().timeoutChecker.start(); 142 Completion comp = JSMainThreadExecState::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell); 143 exec->globalData().timeoutChecker.stop(); 144 145 InspectorInstrumentation::didEvaluateScript(cookie); 146 147 // Evaluating the JavaScript could cause the frame to be deallocated 148 // so we start the keep alive timer here. 149 m_frame->keepAlive(); 150 151 if (comp.complType() == Normal || comp.complType() == ReturnValue) { 152 m_sourceURL = savedSourceURL; 153 return ScriptValue(exec->globalData(), comp.value()); 154 } 155 156 if (comp.complType() == Throw || comp.complType() == Interrupted) 157 reportException(exec, comp.value()); 158 159 m_sourceURL = savedSourceURL; 160 return ScriptValue(); 161 } 162 163 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) 164 { 165 return evaluateInWorld(sourceCode, mainThreadNormalWorld()); 166 } 167 168 PassRefPtr<DOMWrapperWorld> ScriptController::createWorld() 169 { 170 return DOMWrapperWorld::create(JSDOMWindow::commonJSGlobalData()); 171 } 172 173 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds) 174 { 175 static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds); 176 } 177 178 void ScriptController::clearWindowShell(bool goingIntoPageCache) 179 { 180 if (m_windowShells.isEmpty()) 181 return; 182 183 JSLock lock(SilenceAssertionsOnly); 184 185 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) { 186 JSDOMWindowShell* windowShell = iter->second.get(); 187 188 // Clear the debugger from the current window before setting the new window. 189 attachDebugger(windowShell, 0); 190 191 windowShell->window()->willRemoveFromWindowShell(); 192 windowShell->setWindow(m_frame->domWindow()); 193 194 // An m_cacheableBindingRootObject persists between page navigations 195 // so needs to know about the new JSDOMWindow. 196 if (m_cacheableBindingRootObject) 197 m_cacheableBindingRootObject->updateGlobalObject(windowShell->window()); 198 199 if (Page* page = m_frame->page()) { 200 attachDebugger(windowShell, page->debugger()); 201 windowShell->window()->setProfileGroup(page->group().identifier()); 202 } 203 } 204 205 // It's likely that resetting our windows created a lot of garbage, unless 206 // it went in a back/forward cache. 207 if (!goingIntoPageCache) 208 gcController().garbageCollectSoon(); 209 } 210 211 JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world) 212 { 213 ASSERT(!m_windowShells.contains(world)); 214 215 JSLock lock(SilenceAssertionsOnly); 216 217 JSDOMWindowShell* windowShell = createWindowShell(world); 218 219 windowShell->window()->updateDocument(); 220 221 if (Page* page = m_frame->page()) { 222 attachDebugger(windowShell, page->debugger()); 223 windowShell->window()->setProfileGroup(page->group().identifier()); 224 } 225 226 m_frame->loader()->dispatchDidClearWindowObjectInWorld(world); 227 228 return windowShell; 229 } 230 231 int ScriptController::eventHandlerLineNumber() const 232 { 233 // JSC expects 1-based line numbers, so we must add one here to get it right. 234 ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser(); 235 if (parser) 236 return parser->lineNumber() + 1; 237 return 0; 238 } 239 240 bool ScriptController::processingUserGesture() 241 { 242 ExecState* exec = JSMainThreadExecState::currentState(); 243 Frame* frame = exec ? toDynamicFrame(exec) : 0; 244 // No script is running, so it is user-initiated unless the gesture stack 245 // explicitly says it is not. 246 if (!frame) 247 return UserGestureIndicator::getUserGestureState() != DefinitelyNotProcessingUserGesture; 248 249 // FIXME: We check the plugin popup flag and javascript anchor navigation 250 // from the dynamic frame becuase they should only be initiated on the 251 // dynamic frame in which execution began if they do happen. 252 ScriptController* scriptController = frame->script(); 253 ASSERT(scriptController); 254 if (scriptController->allowPopupsFromPlugin() || scriptController->isJavaScriptAnchorNavigation()) 255 return true; 256 257 // If a DOM event is being processed, check that it was initiated by the user 258 // and that it is in the whitelist of event types allowed to generate pop-ups. 259 if (JSDOMWindowShell* shell = scriptController->existingWindowShell(currentWorld(exec))) 260 if (Event* event = shell->window()->currentEvent()) 261 return event->fromUserGesture(); 262 263 return UserGestureIndicator::processingUserGesture(); 264 } 265 266 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor. 267 bool ScriptController::isJavaScriptAnchorNavigation() const 268 { 269 // This is the <a href="javascript:window.open('...')> case -> we let it through 270 if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback) 271 return true; 272 273 // This is the <script>window.open(...)</script> case or a timer callback -> block it 274 return false; 275 } 276 277 bool ScriptController::anyPageIsProcessingUserGesture() const 278 { 279 Page* page = m_frame->page(); 280 if (!page) 281 return false; 282 283 const HashSet<Page*>& pages = page->group().pages(); 284 HashSet<Page*>::const_iterator end = pages.end(); 285 for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) { 286 for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) { 287 ScriptController* script = frame->script(); 288 289 if (script->m_allowPopupsFromPlugin) 290 return true; 291 292 const ShellMap::const_iterator iterEnd = m_windowShells.end(); 293 for (ShellMap::const_iterator iter = m_windowShells.begin(); iter != iterEnd; ++iter) { 294 JSDOMWindowShell* shell = iter->second.get(); 295 Event* event = shell->window()->currentEvent(); 296 if (event && event->fromUserGesture()) 297 return true; 298 } 299 300 if (isJavaScriptAnchorNavigation()) 301 return true; 302 } 303 } 304 305 return false; 306 } 307 308 bool ScriptController::canAccessFromCurrentOrigin(Frame *frame) 309 { 310 ExecState* exec = JSMainThreadExecState::currentState(); 311 if (exec) 312 return allowsAccessFromFrame(exec, frame); 313 // If the current state is 0 we're in a call path where the DOM security 314 // check doesn't apply (eg. parser). 315 return true; 316 } 317 318 void ScriptController::attachDebugger(JSC::Debugger* debugger) 319 { 320 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) 321 attachDebugger(iter->second.get(), debugger); 322 } 323 324 void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger) 325 { 326 if (!shell) 327 return; 328 329 JSDOMWindow* globalObject = shell->window(); 330 if (debugger) 331 debugger->attach(globalObject); 332 else if (JSC::Debugger* currentDebugger = globalObject->debugger()) 333 currentDebugger->detach(globalObject); 334 } 335 336 void ScriptController::updateDocument() 337 { 338 if (!m_frame->document()) 339 return; 340 341 JSLock lock(SilenceAssertionsOnly); 342 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) 343 iter->second->window()->updateDocument(); 344 } 345 346 void ScriptController::updateSecurityOrigin() 347 { 348 // Our bindings do not do anything in this case. 349 } 350 351 Bindings::RootObject* ScriptController::cacheableBindingRootObject() 352 { 353 if (!canExecuteScripts(NotAboutToExecuteScript)) 354 return 0; 355 356 if (!m_cacheableBindingRootObject) { 357 JSLock lock(SilenceAssertionsOnly); 358 m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld())); 359 } 360 return m_cacheableBindingRootObject.get(); 361 } 362 363 Bindings::RootObject* ScriptController::bindingRootObject() 364 { 365 if (!canExecuteScripts(NotAboutToExecuteScript)) 366 return 0; 367 368 if (!m_bindingRootObject) { 369 JSLock lock(SilenceAssertionsOnly); 370 m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld())); 371 } 372 return m_bindingRootObject.get(); 373 } 374 375 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle) 376 { 377 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 378 if (it != m_rootObjects.end()) 379 return it->second; 380 381 RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld())); 382 383 m_rootObjects.set(nativeHandle, rootObject); 384 return rootObject.release(); 385 } 386 387 #if ENABLE(INSPECTOR) 388 void ScriptController::setCaptureCallStackForUncaughtExceptions(bool) 389 { 390 } 391 #endif 392 393 #if ENABLE(NETSCAPE_PLUGIN_API) 394 395 NPObject* ScriptController::windowScriptNPObject() 396 { 397 if (!m_windowScriptNPObject) { 398 if (canExecuteScripts(NotAboutToExecuteScript)) { 399 // JavaScript is enabled, so there is a JavaScript window object. 400 // Return an NPObject bound to the window object. 401 JSC::JSLock lock(SilenceAssertionsOnly); 402 JSObject* win = windowShell(pluginWorld())->window(); 403 ASSERT(win); 404 Bindings::RootObject* root = bindingRootObject(); 405 m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root); 406 } else { 407 // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object. 408 // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object. 409 m_windowScriptNPObject = _NPN_CreateNoScriptObject(); 410 } 411 } 412 413 return m_windowScriptNPObject; 414 } 415 416 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin) 417 { 418 JSObject* object = jsObjectForPluginElement(plugin); 419 if (!object) 420 return _NPN_CreateNoScriptObject(); 421 422 // Wrap the JSObject in an NPObject 423 return _NPN_CreateScriptObject(0, object, bindingRootObject()); 424 } 425 426 #endif 427 428 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin) 429 { 430 // Can't create JSObjects when JavaScript is disabled 431 if (!canExecuteScripts(NotAboutToExecuteScript)) 432 return 0; 433 434 // Create a JSObject bound to this element 435 JSLock lock(SilenceAssertionsOnly); 436 JSDOMWindow* globalObj = globalObject(pluginWorld()); 437 // FIXME: is normal okay? - used for NP plugins? 438 JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin); 439 if (!jsElementValue || !jsElementValue.isObject()) 440 return 0; 441 442 return jsElementValue.getObject(); 443 } 444 445 #if !PLATFORM(MAC) 446 447 void ScriptController::updatePlatformScriptObjects() 448 { 449 } 450 451 void ScriptController::disconnectPlatformScriptObjects() 452 { 453 } 454 455 #endif 456 457 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle) 458 { 459 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 460 461 if (it == m_rootObjects.end()) 462 return; 463 464 it->second->invalidate(); 465 m_rootObjects.remove(it); 466 } 467 468 void ScriptController::clearScriptObjects() 469 { 470 JSLock lock(SilenceAssertionsOnly); 471 472 RootObjectMap::const_iterator end = m_rootObjects.end(); 473 for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it) 474 it->second->invalidate(); 475 476 m_rootObjects.clear(); 477 478 if (m_bindingRootObject) { 479 m_bindingRootObject->invalidate(); 480 m_bindingRootObject = 0; 481 } 482 483 #if ENABLE(NETSCAPE_PLUGIN_API) 484 if (m_windowScriptNPObject) { 485 // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window 486 // script object properly. 487 // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. 488 _NPN_DeallocateObject(m_windowScriptNPObject); 489 m_windowScriptNPObject = 0; 490 } 491 #endif 492 } 493 494 ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture) 495 { 496 ScriptSourceCode sourceCode(script, forceUserGesture ? KURL() : m_frame->document()->url()); 497 498 if (!canExecuteScripts(AboutToExecuteScript) || isPaused()) 499 return ScriptValue(); 500 501 bool wasInExecuteScript = m_inExecuteScript; 502 m_inExecuteScript = true; 503 504 ScriptValue result = evaluateInWorld(sourceCode, world); 505 506 if (!wasInExecuteScript) { 507 m_inExecuteScript = false; 508 Document::updateStyleForAllDocuments(); 509 } 510 511 return result; 512 } 513 514 } // namespace WebCore 515