1/* 2 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) 3 * Copyright (C) 2001 Peter Kelly (pmk@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 "BridgeJSC.h" 25#include "ContentSecurityPolicy.h" 26#include "Event.h" 27#include "EventNames.h" 28#include "Frame.h" 29#include "FrameLoaderClient.h" 30#include "GCController.h" 31#include "HTMLPlugInElement.h" 32#include "InspectorInstrumentation.h" 33#include "JSDOMWindow.h" 34#include "JSDocument.h" 35#include "JSMainThreadExecState.h" 36#include "NP_jsobject.h" 37#include "Page.h" 38#include "PageGroup.h" 39#include "PluginView.h" 40#include "ScriptCallStack.h" 41#include "ScriptSourceCode.h" 42#include "ScriptValue.h" 43#include "ScriptableDocumentParser.h" 44#include "Settings.h" 45#include "StorageNamespace.h" 46#include "UserGestureIndicator.h" 47#include "WebCoreJSClientData.h" 48#include "npruntime_impl.h" 49#include "runtime_root.h" 50#include <debugger/Debugger.h> 51#include <heap/StrongInlines.h> 52#include <runtime/InitializeThreading.h> 53#include <runtime/JSLock.h> 54#include <wtf/text/TextPosition.h> 55#include <wtf/Threading.h> 56 57using namespace JSC; 58using namespace std; 59 60namespace WebCore { 61 62void ScriptController::initializeThreading() 63{ 64 JSC::initializeThreading(); 65 WTF::initializeMainThread(); 66} 67 68ScriptController::ScriptController(Frame* frame) 69 : m_frame(frame) 70 , m_sourceURL(0) 71 , m_paused(false) 72#if ENABLE(NETSCAPE_PLUGIN_API) 73 , m_windowScriptNPObject(0) 74#endif 75#if PLATFORM(MAC) 76 , m_windowScriptObject(0) 77#endif 78{ 79} 80 81ScriptController::~ScriptController() 82{ 83 disconnectPlatformScriptObjects(); 84 85 if (m_cacheableBindingRootObject) { 86 JSLockHolder lock(JSDOMWindowBase::commonVM()); 87 m_cacheableBindingRootObject->invalidate(); 88 m_cacheableBindingRootObject = 0; 89 } 90 91 // It's likely that destroying m_windowShells will create a lot of garbage. 92 if (!m_windowShells.isEmpty()) { 93 while (!m_windowShells.isEmpty()) 94 destroyWindowShell(m_windowShells.begin()->key.get()); 95 gcController().garbageCollectSoon(); 96 } 97} 98 99void ScriptController::destroyWindowShell(DOMWrapperWorld* world) 100{ 101 ASSERT(m_windowShells.contains(world)); 102 m_windowShells.remove(world); 103 world->didDestroyWindowShell(this); 104} 105 106JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld* world) 107{ 108 ASSERT(!m_windowShells.contains(world)); 109 Structure* structure = JSDOMWindowShell::createStructure(*world->vm(), jsNull()); 110 Strong<JSDOMWindowShell> windowShell(*world->vm(), JSDOMWindowShell::create(m_frame->document()->domWindow(), structure, world)); 111 Strong<JSDOMWindowShell> windowShell2(windowShell); 112 m_windowShells.add(world, windowShell); 113 world->didCreateWindowShell(this); 114 return windowShell.get(); 115} 116 117ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world) 118{ 119 const SourceCode& jsSourceCode = sourceCode.jsSourceCode(); 120 String sourceURL = jsSourceCode.provider()->url(); 121 122 // evaluate code. Returns the JS return value or 0 123 // if there was none, an error occurred or the type couldn't be converted. 124 125 // inlineCode is true for <a href="javascript:doSomething()"> 126 // and false for <script>doSomething()</script>. Check if it has the 127 // expected value in all cases. 128 // See smart window.open policy for where this is used. 129 JSDOMWindowShell* shell = windowShell(world); 130 ExecState* exec = shell->window()->globalExec(); 131 const String* savedSourceURL = m_sourceURL; 132 m_sourceURL = &sourceURL; 133 134 JSLockHolder lock(exec); 135 136 RefPtr<Frame> protect = m_frame; 137 138 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine()); 139 140 JSValue evaluationException; 141 142 JSValue returnValue = JSMainThreadExecState::evaluate(exec, jsSourceCode, shell, &evaluationException); 143 144 InspectorInstrumentation::didEvaluateScript(cookie); 145 146 if (evaluationException) { 147 reportException(exec, evaluationException, sourceCode.cachedScript()); 148 m_sourceURL = savedSourceURL; 149 return ScriptValue(); 150 } 151 152 m_sourceURL = savedSourceURL; 153 return ScriptValue(exec->vm(), returnValue); 154} 155 156ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) 157{ 158 return evaluateInWorld(sourceCode, mainThreadNormalWorld()); 159} 160 161PassRefPtr<DOMWrapperWorld> ScriptController::createWorld() 162{ 163 return DOMWrapperWorld::create(JSDOMWindow::commonVM()); 164} 165 166void ScriptController::getAllWorlds(Vector<RefPtr<DOMWrapperWorld> >& worlds) 167{ 168 static_cast<WebCoreJSClientData*>(JSDOMWindow::commonVM()->clientData)->getAllWorlds(worlds); 169} 170 171void ScriptController::clearWindowShell(DOMWindow* newDOMWindow, bool goingIntoPageCache) 172{ 173 if (m_windowShells.isEmpty()) 174 return; 175 176 JSLockHolder lock(JSDOMWindowBase::commonVM()); 177 178 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) { 179 JSDOMWindowShell* windowShell = iter->value.get(); 180 181 if (windowShell->window()->impl() == newDOMWindow) 182 continue; 183 184 // Clear the debugger from the current window before setting the new window. 185 attachDebugger(windowShell, 0); 186 187 windowShell->window()->willRemoveFromWindowShell(); 188 windowShell->setWindow(newDOMWindow); 189 190 // An m_cacheableBindingRootObject persists between page navigations 191 // so needs to know about the new JSDOMWindow. 192 if (m_cacheableBindingRootObject) 193 m_cacheableBindingRootObject->updateGlobalObject(windowShell->window()); 194 195 if (Page* page = m_frame->page()) { 196 attachDebugger(windowShell, page->debugger()); 197 windowShell->window()->setProfileGroup(page->group().identifier()); 198 } 199 } 200 201 // It's likely that resetting our windows created a lot of garbage, unless 202 // it went in a back/forward cache. 203 if (!goingIntoPageCache) 204 gcController().garbageCollectSoon(); 205} 206 207JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world) 208{ 209 ASSERT(!m_windowShells.contains(world)); 210 211 JSLockHolder lock(world->vm()); 212 213 JSDOMWindowShell* windowShell = createWindowShell(world); 214 215 windowShell->window()->updateDocument(); 216 217 if (m_frame->document()) 218 windowShell->window()->setEvalEnabled(m_frame->document()->contentSecurityPolicy()->allowEval(0, ContentSecurityPolicy::SuppressReport), m_frame->document()->contentSecurityPolicy()->evalDisabledErrorMessage()); 219 220 if (Page* page = m_frame->page()) { 221 attachDebugger(windowShell, page->debugger()); 222 windowShell->window()->setProfileGroup(page->group().identifier()); 223 } 224 225 m_frame->loader()->dispatchDidClearWindowObjectInWorld(world); 226 227 return windowShell; 228} 229 230TextPosition ScriptController::eventHandlerPosition() const 231{ 232 ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser(); 233 if (parser) 234 return parser->textPosition(); 235 return TextPosition::minimumPosition(); 236} 237 238void ScriptController::enableEval() 239{ 240 JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld()); 241 if (!windowShell) 242 return; 243 windowShell->window()->setEvalEnabled(true); 244} 245 246void ScriptController::disableEval(const String& errorMessage) 247{ 248 JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld()); 249 if (!windowShell) 250 return; 251 windowShell->window()->setEvalEnabled(false, errorMessage); 252} 253 254bool ScriptController::processingUserGesture() 255{ 256 return UserGestureIndicator::processingUserGesture(); 257} 258 259bool ScriptController::canAccessFromCurrentOrigin(Frame *frame) 260{ 261 ExecState* exec = JSMainThreadExecState::currentState(); 262 if (exec) 263 return shouldAllowAccessToFrame(exec, frame); 264 // If the current state is 0 we're in a call path where the DOM security 265 // check doesn't apply (eg. parser). 266 return true; 267} 268 269void ScriptController::attachDebugger(JSC::Debugger* debugger) 270{ 271 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) 272 attachDebugger(iter->value.get(), debugger); 273} 274 275void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger) 276{ 277 if (!shell) 278 return; 279 280 JSDOMWindow* globalObject = shell->window(); 281 if (debugger) 282 debugger->attach(globalObject); 283 else if (JSC::Debugger* currentDebugger = globalObject->debugger()) 284 currentDebugger->detach(globalObject); 285} 286 287void ScriptController::updateDocument() 288{ 289 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) { 290 JSLockHolder lock(iter->key->vm()); 291 iter->value->window()->updateDocument(); 292 } 293} 294 295Bindings::RootObject* ScriptController::cacheableBindingRootObject() 296{ 297 if (!canExecuteScripts(NotAboutToExecuteScript)) 298 return 0; 299 300 if (!m_cacheableBindingRootObject) { 301 JSLockHolder lock(JSDOMWindowBase::commonVM()); 302 m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld())); 303 } 304 return m_cacheableBindingRootObject.get(); 305} 306 307Bindings::RootObject* ScriptController::bindingRootObject() 308{ 309 if (!canExecuteScripts(NotAboutToExecuteScript)) 310 return 0; 311 312 if (!m_bindingRootObject) { 313 JSLockHolder lock(JSDOMWindowBase::commonVM()); 314 m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld())); 315 } 316 return m_bindingRootObject.get(); 317} 318 319PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle) 320{ 321 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 322 if (it != m_rootObjects.end()) 323 return it->value; 324 325 RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld())); 326 327 m_rootObjects.set(nativeHandle, rootObject); 328 return rootObject.release(); 329} 330 331#if ENABLE(INSPECTOR) 332void ScriptController::setCaptureCallStackForUncaughtExceptions(bool) 333{ 334} 335 336void ScriptController::collectIsolatedContexts(Vector<std::pair<JSC::ExecState*, SecurityOrigin*> >& result) 337{ 338 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) { 339 JSC::ExecState* exec = iter->value->window()->globalExec(); 340 SecurityOrigin* origin = iter->value->window()->impl()->document()->securityOrigin(); 341 result.append(std::pair<ScriptState*, SecurityOrigin*>(exec, origin)); 342 } 343} 344 345#endif 346 347#if ENABLE(NETSCAPE_PLUGIN_API) 348 349NPObject* ScriptController::windowScriptNPObject() 350{ 351 if (!m_windowScriptNPObject) { 352 if (canExecuteScripts(NotAboutToExecuteScript)) { 353 // JavaScript is enabled, so there is a JavaScript window object. 354 // Return an NPObject bound to the window object. 355 JSDOMWindow* win = windowShell(pluginWorld())->window(); 356 ASSERT(win); 357 JSC::JSLockHolder lock(win->globalExec()); 358 Bindings::RootObject* root = bindingRootObject(); 359 m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root); 360 } else { 361 // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object. 362 // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object. 363 m_windowScriptNPObject = _NPN_CreateNoScriptObject(); 364 } 365 } 366 367 return m_windowScriptNPObject; 368} 369 370NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin) 371{ 372 JSObject* object = jsObjectForPluginElement(plugin); 373 if (!object) 374 return _NPN_CreateNoScriptObject(); 375 376 // Wrap the JSObject in an NPObject 377 return _NPN_CreateScriptObject(0, object, bindingRootObject()); 378} 379 380#endif 381 382#if !PLATFORM(MAC) && !PLATFORM(QT) 383PassRefPtr<JSC::Bindings::Instance> ScriptController::createScriptInstanceForWidget(Widget* widget) 384{ 385 if (!widget->isPluginView()) 386 return 0; 387 388 return toPluginView(widget)->bindingInstance(); 389} 390#endif 391 392JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin) 393{ 394 // Can't create JSObjects when JavaScript is disabled 395 if (!canExecuteScripts(NotAboutToExecuteScript)) 396 return 0; 397 398 // Create a JSObject bound to this element 399 JSDOMWindow* globalObj = globalObject(pluginWorld()); 400 JSLockHolder lock(globalObj->globalExec()); 401 // FIXME: is normal okay? - used for NP plugins? 402 JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin); 403 if (!jsElementValue || !jsElementValue.isObject()) 404 return 0; 405 406 return jsElementValue.getObject(); 407} 408 409#if !PLATFORM(MAC) 410 411void ScriptController::updatePlatformScriptObjects() 412{ 413} 414 415void ScriptController::disconnectPlatformScriptObjects() 416{ 417} 418 419#endif 420 421void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle) 422{ 423 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 424 425 if (it == m_rootObjects.end()) 426 return; 427 428 it->value->invalidate(); 429 m_rootObjects.remove(it); 430} 431 432void ScriptController::clearScriptObjects() 433{ 434 JSLockHolder lock(JSDOMWindowBase::commonVM()); 435 436 RootObjectMap::const_iterator end = m_rootObjects.end(); 437 for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it) 438 it->value->invalidate(); 439 440 m_rootObjects.clear(); 441 442 if (m_bindingRootObject) { 443 m_bindingRootObject->invalidate(); 444 m_bindingRootObject = 0; 445 } 446 447#if ENABLE(NETSCAPE_PLUGIN_API) 448 if (m_windowScriptNPObject) { 449 // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window 450 // script object properly. 451 // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. 452 _NPN_DeallocateObject(m_windowScriptNPObject); 453 m_windowScriptNPObject = 0; 454 } 455#endif 456} 457 458ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture) 459{ 460 UserGestureIndicator gestureIndicator(forceUserGesture ? DefinitelyProcessingNewUserGesture : PossiblyProcessingUserGesture); 461 ScriptSourceCode sourceCode(script, m_frame->document()->url()); 462 463 if (!canExecuteScripts(AboutToExecuteScript) || isPaused()) 464 return ScriptValue(); 465 466 return evaluateInWorld(sourceCode, world); 467} 468 469bool ScriptController::shouldBypassMainWorldContentSecurityPolicy() 470{ 471 CallFrame* callFrame = JSDOMWindow::commonVM()->topCallFrame; 472 if (!callFrame || callFrame == CallFrame::noCaller()) 473 return false; 474 DOMWrapperWorld* domWrapperWorld = currentWorld(callFrame); 475 if (domWrapperWorld->isNormal()) 476 return false; 477 return true; 478} 479 480} // namespace WebCore 481