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 "DocumentLoader.h" 27#include "Event.h" 28#include "EventNames.h" 29#include "Frame.h" 30#include "FrameLoaderClient.h" 31#include "GCController.h" 32#include "HTMLPlugInElement.h" 33#include "InspectorInstrumentation.h" 34#include "JSDOMWindow.h" 35#include "JSDocument.h" 36#include "JSMainThreadExecState.h" 37#include "MainFrame.h" 38#include "NP_jsobject.h" 39#include "Page.h" 40#include "PageConsole.h" 41#include "PageGroup.h" 42#include "PluginView.h" 43#include "ScriptSourceCode.h" 44#include "ScriptableDocumentParser.h" 45#include "Settings.h" 46#include "StorageNamespace.h" 47#include "UserGestureIndicator.h" 48#include "WebCoreJSClientData.h" 49#include "npruntime_impl.h" 50#include "runtime_root.h" 51#include <bindings/ScriptValue.h> 52#include <debugger/Debugger.h> 53#include <heap/StrongInlines.h> 54#include <inspector/ScriptCallStack.h> 55#include <runtime/InitializeThreading.h> 56#include <runtime/JSLock.h> 57#include <wtf/Threading.h> 58#include <wtf/text/TextPosition.h> 59 60using namespace JSC; 61 62namespace WebCore { 63 64void ScriptController::initializeThreading() 65{ 66#if !PLATFORM(IOS) 67 JSC::initializeThreading(); 68 WTF::initializeMainThread(); 69#endif 70} 71 72ScriptController::ScriptController(Frame& frame) 73 : m_frame(frame) 74 , m_sourceURL(0) 75 , m_paused(false) 76#if ENABLE(NETSCAPE_PLUGIN_API) 77 , m_windowScriptNPObject(0) 78#endif 79#if PLATFORM(COCOA) 80 , m_windowScriptObject(0) 81#endif 82{ 83} 84 85ScriptController::~ScriptController() 86{ 87 disconnectPlatformScriptObjects(); 88 89 if (m_cacheableBindingRootObject) { 90 JSLockHolder lock(JSDOMWindowBase::commonVM()); 91 m_cacheableBindingRootObject->invalidate(); 92 m_cacheableBindingRootObject = 0; 93 } 94 95 // It's likely that destroying m_windowShells will create a lot of garbage. 96 if (!m_windowShells.isEmpty()) { 97 while (!m_windowShells.isEmpty()) { 98 ShellMap::iterator iter = m_windowShells.begin(); 99 iter->value->window()->setConsoleClient(nullptr); 100 destroyWindowShell(*iter->key); 101 } 102 gcController().garbageCollectSoon(); 103 } 104} 105 106void ScriptController::destroyWindowShell(DOMWrapperWorld& world) 107{ 108 ASSERT(m_windowShells.contains(&world)); 109 m_windowShells.remove(&world); 110 world.didDestroyWindowShell(this); 111} 112 113JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld& world) 114{ 115 ASSERT(!m_windowShells.contains(&world)); 116 117 VM& vm = world.vm(); 118 119 Structure* structure = JSDOMWindowShell::createStructure(vm, jsNull()); 120 Strong<JSDOMWindowShell> windowShell(vm, JSDOMWindowShell::create(vm, m_frame.document()->domWindow(), structure, world)); 121 Strong<JSDOMWindowShell> windowShell2(windowShell); 122 m_windowShells.add(&world, windowShell); 123 world.didCreateWindowShell(this); 124 return windowShell.get(); 125} 126 127Deprecated::ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld& world) 128{ 129 JSLockHolder lock(world.vm()); 130 131 const SourceCode& jsSourceCode = sourceCode.jsSourceCode(); 132 String sourceURL = jsSourceCode.provider()->url(); 133 134 // evaluate code. Returns the JS return value or 0 135 // if there was none, an error occurred or the type couldn't be converted. 136 137 // inlineCode is true for <a href="javascript:doSomething()"> 138 // and false for <script>doSomething()</script>. Check if it has the 139 // expected value in all cases. 140 // See smart window.open policy for where this is used. 141 JSDOMWindowShell* shell = windowShell(world); 142 ExecState* exec = shell->window()->globalExec(); 143 const String* savedSourceURL = m_sourceURL; 144 m_sourceURL = &sourceURL; 145 146 Ref<Frame> protect(m_frame); 147 148 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(&m_frame, sourceURL, sourceCode.startLine()); 149 150 JSValue evaluationException; 151 152 JSValue returnValue = JSMainThreadExecState::evaluate(exec, jsSourceCode, shell, &evaluationException); 153 154 InspectorInstrumentation::didEvaluateScript(cookie, &m_frame); 155 156 if (evaluationException) { 157 reportException(exec, evaluationException, sourceCode.cachedScript()); 158 m_sourceURL = savedSourceURL; 159 return Deprecated::ScriptValue(); 160 } 161 162 m_sourceURL = savedSourceURL; 163 return Deprecated::ScriptValue(exec->vm(), returnValue); 164} 165 166Deprecated::ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) 167{ 168 return evaluateInWorld(sourceCode, mainThreadNormalWorld()); 169} 170 171PassRefPtr<DOMWrapperWorld> ScriptController::createWorld() 172{ 173 return DOMWrapperWorld::create(JSDOMWindow::commonVM()); 174} 175 176Vector<JSC::Strong<JSDOMWindowShell>> ScriptController::windowShells() 177{ 178 Vector<JSC::Strong<JSDOMWindowShell>> windowShells; 179 copyValuesToVector(m_windowShells, windowShells); 180 return windowShells; 181} 182 183void ScriptController::getAllWorlds(Vector<Ref<DOMWrapperWorld>>& worlds) 184{ 185 static_cast<WebCoreJSClientData*>(JSDOMWindow::commonVM().clientData)->getAllWorlds(worlds); 186} 187 188void ScriptController::clearWindowShell(DOMWindow* newDOMWindow, bool goingIntoPageCache) 189{ 190 if (m_windowShells.isEmpty()) 191 return; 192 193 JSLockHolder lock(JSDOMWindowBase::commonVM()); 194 195 Vector<JSC::Strong<JSDOMWindowShell>> windowShells = this->windowShells(); 196 for (size_t i = 0; i < windowShells.size(); ++i) { 197 JSDOMWindowShell* windowShell = windowShells[i].get(); 198 199 if (&windowShell->window()->impl() == newDOMWindow) 200 continue; 201 202 // Clear the debugger and console from the current window before setting the new window. 203 attachDebugger(windowShell, nullptr); 204 windowShell->window()->setConsoleClient(nullptr); 205 206 // FIXME: We should clear console profiles for each frame as soon as the frame is destroyed. 207 // Instead of clearing all of them when the main frame is destroyed. 208 if (m_frame.isMainFrame()) { 209 if (Page* page = m_frame.page()) 210 page->console().clearProfiles(); 211 } 212 213 windowShell->window()->willRemoveFromWindowShell(); 214 windowShell->setWindow(newDOMWindow); 215 216 // An m_cacheableBindingRootObject persists between page navigations 217 // so needs to know about the new JSDOMWindow. 218 if (m_cacheableBindingRootObject) 219 m_cacheableBindingRootObject->updateGlobalObject(windowShell->window()); 220 221 if (Page* page = m_frame.page()) { 222 attachDebugger(windowShell, page->debugger()); 223 windowShell->window()->setProfileGroup(page->group().identifier()); 224 windowShell->window()->setConsoleClient(&page->console()); 225 } 226 } 227 228 // It's likely that resetting our windows created a lot of garbage, unless 229 // it went in a back/forward cache. 230 if (!goingIntoPageCache) 231 gcController().garbageCollectSoon(); 232} 233 234JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld& world) 235{ 236 ASSERT(!m_windowShells.contains(&world)); 237 238 JSLockHolder lock(world.vm()); 239 240 JSDOMWindowShell* windowShell = createWindowShell(world); 241 242 windowShell->window()->updateDocument(); 243 244 if (m_frame.document()) 245 windowShell->window()->setEvalEnabled(m_frame.document()->contentSecurityPolicy()->allowEval(0, ContentSecurityPolicy::SuppressReport), m_frame.document()->contentSecurityPolicy()->evalDisabledErrorMessage()); 246 247 if (Page* page = m_frame.page()) { 248 attachDebugger(windowShell, page->debugger()); 249 windowShell->window()->setProfileGroup(page->group().identifier()); 250 windowShell->window()->setConsoleClient(&page->console()); 251 } 252 253 m_frame.loader().dispatchDidClearWindowObjectInWorld(world); 254 255 return windowShell; 256} 257 258TextPosition ScriptController::eventHandlerPosition() const 259{ 260 ScriptableDocumentParser* parser = m_frame.document()->scriptableDocumentParser(); 261 if (parser) 262 return parser->textPosition(); 263 return TextPosition::minimumPosition(); 264} 265 266void ScriptController::enableEval() 267{ 268 JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld()); 269 if (!windowShell) 270 return; 271 windowShell->window()->setEvalEnabled(true); 272} 273 274void ScriptController::disableEval(const String& errorMessage) 275{ 276 JSDOMWindowShell* windowShell = existingWindowShell(mainThreadNormalWorld()); 277 if (!windowShell) 278 return; 279 windowShell->window()->setEvalEnabled(false, errorMessage); 280} 281 282bool ScriptController::processingUserGesture() 283{ 284 return UserGestureIndicator::processingUserGesture(); 285} 286 287bool ScriptController::canAccessFromCurrentOrigin(Frame *frame) 288{ 289 ExecState* exec = JSMainThreadExecState::currentState(); 290 if (exec) 291 return shouldAllowAccessToFrame(exec, frame); 292 // If the current state is 0 we're in a call path where the DOM security 293 // check doesn't apply (eg. parser). 294 return true; 295} 296 297void ScriptController::attachDebugger(JSC::Debugger* debugger) 298{ 299 Vector<JSC::Strong<JSDOMWindowShell>> windowShells = this->windowShells(); 300 for (size_t i = 0; i < windowShells.size(); ++i) 301 attachDebugger(windowShells[i].get(), debugger); 302} 303 304void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger) 305{ 306 if (!shell) 307 return; 308 309 JSDOMWindow* globalObject = shell->window(); 310 if (debugger) 311 debugger->attach(globalObject); 312 else if (JSC::Debugger* currentDebugger = globalObject->debugger()) 313 currentDebugger->detach(globalObject, JSC::Debugger::TerminatingDebuggingSession); 314} 315 316void ScriptController::updateDocument() 317{ 318 Vector<JSC::Strong<JSDOMWindowShell>> windowShells = this->windowShells(); 319 for (size_t i = 0; i < windowShells.size(); ++i) { 320 JSDOMWindowShell* windowShell = windowShells[i].get(); 321 JSLockHolder lock(windowShell->world().vm()); 322 windowShell->window()->updateDocument(); 323 } 324} 325 326Bindings::RootObject* ScriptController::cacheableBindingRootObject() 327{ 328 if (!canExecuteScripts(NotAboutToExecuteScript)) 329 return 0; 330 331 if (!m_cacheableBindingRootObject) { 332 JSLockHolder lock(JSDOMWindowBase::commonVM()); 333 m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld())); 334 } 335 return m_cacheableBindingRootObject.get(); 336} 337 338Bindings::RootObject* ScriptController::bindingRootObject() 339{ 340 if (!canExecuteScripts(NotAboutToExecuteScript)) 341 return 0; 342 343 if (!m_bindingRootObject) { 344 JSLockHolder lock(JSDOMWindowBase::commonVM()); 345 m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld())); 346 } 347 return m_bindingRootObject.get(); 348} 349 350PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle) 351{ 352 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 353 if (it != m_rootObjects.end()) 354 return it->value; 355 356 RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld())); 357 358 m_rootObjects.set(nativeHandle, rootObject); 359 return rootObject.release(); 360} 361 362#if ENABLE(INSPECTOR) 363void ScriptController::collectIsolatedContexts(Vector<std::pair<JSC::ExecState*, SecurityOrigin*>>& result) 364{ 365 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) { 366 JSC::ExecState* exec = iter->value->window()->globalExec(); 367 SecurityOrigin* origin = iter->value->window()->impl().document()->securityOrigin(); 368 result.append(std::pair<JSC::ExecState*, SecurityOrigin*>(exec, origin)); 369 } 370} 371#endif 372 373#if ENABLE(NETSCAPE_PLUGIN_API) 374 375NPObject* ScriptController::windowScriptNPObject() 376{ 377 if (!m_windowScriptNPObject) { 378 JSLockHolder lock(JSDOMWindowBase::commonVM()); 379 if (canExecuteScripts(NotAboutToExecuteScript)) { 380 // JavaScript is enabled, so there is a JavaScript window object. 381 // Return an NPObject bound to the window object. 382 JSDOMWindow* win = windowShell(pluginWorld())->window(); 383 ASSERT(win); 384 Bindings::RootObject* root = bindingRootObject(); 385 m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root); 386 } else { 387 // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object. 388 // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object. 389 m_windowScriptNPObject = _NPN_CreateNoScriptObject(); 390 } 391 } 392 393 return m_windowScriptNPObject; 394} 395 396NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin) 397{ 398 JSObject* object = jsObjectForPluginElement(plugin); 399 if (!object) 400 return _NPN_CreateNoScriptObject(); 401 402 // Wrap the JSObject in an NPObject 403 return _NPN_CreateScriptObject(0, object, bindingRootObject()); 404} 405 406#endif 407 408#if !PLATFORM(COCOA) 409PassRefPtr<JSC::Bindings::Instance> ScriptController::createScriptInstanceForWidget(Widget* widget) 410{ 411 if (!widget->isPluginView()) 412 return 0; 413 414 return toPluginView(widget)->bindingInstance(); 415} 416#endif 417 418JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin) 419{ 420 // Can't create JSObjects when JavaScript is disabled 421 if (!canExecuteScripts(NotAboutToExecuteScript)) 422 return 0; 423 424 JSLockHolder lock(JSDOMWindowBase::commonVM()); 425 426 // Create a JSObject bound to this element 427 JSDOMWindow* globalObj = globalObject(pluginWorld()); 428 // FIXME: is normal okay? - used for NP plugins? 429 JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin); 430 if (!jsElementValue || !jsElementValue.isObject()) 431 return 0; 432 433 return jsElementValue.getObject(); 434} 435 436#if !PLATFORM(COCOA) 437 438void ScriptController::updatePlatformScriptObjects() 439{ 440} 441 442void ScriptController::disconnectPlatformScriptObjects() 443{ 444} 445 446#endif 447 448void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle) 449{ 450 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 451 452 if (it == m_rootObjects.end()) 453 return; 454 455 it->value->invalidate(); 456 m_rootObjects.remove(it); 457} 458 459void ScriptController::clearScriptObjects() 460{ 461 JSLockHolder lock(JSDOMWindowBase::commonVM()); 462 463 RootObjectMap::const_iterator end = m_rootObjects.end(); 464 for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it) 465 it->value->invalidate(); 466 467 m_rootObjects.clear(); 468 469 if (m_bindingRootObject) { 470 m_bindingRootObject->invalidate(); 471 m_bindingRootObject = 0; 472 } 473 474#if ENABLE(NETSCAPE_PLUGIN_API) 475 if (m_windowScriptNPObject) { 476 // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window 477 // script object properly. 478 // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. 479 _NPN_DeallocateObject(m_windowScriptNPObject); 480 m_windowScriptNPObject = 0; 481 } 482#endif 483} 484 485Deprecated::ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld& world, const String& script, bool forceUserGesture) 486{ 487 UserGestureIndicator gestureIndicator(forceUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture); 488 ScriptSourceCode sourceCode(script, m_frame.document()->url()); 489 490 if (!canExecuteScripts(AboutToExecuteScript) || isPaused()) 491 return Deprecated::ScriptValue(); 492 493 return evaluateInWorld(sourceCode, world); 494} 495 496bool ScriptController::shouldBypassMainWorldContentSecurityPolicy() 497{ 498 CallFrame* callFrame = JSDOMWindow::commonVM().topCallFrame; 499 if (callFrame == CallFrame::noCaller()) 500 return false; 501 DOMWrapperWorld& domWrapperWorld = currentWorld(callFrame); 502 if (domWrapperWorld.isNormal()) 503 return false; 504 return true; 505} 506 507bool ScriptController::canExecuteScripts(ReasonForCallingCanExecuteScripts reason) 508{ 509 if (m_frame.document() && m_frame.document()->isSandboxed(SandboxScripts)) { 510 // FIXME: This message should be moved off the console once a solution to https://bugs.webkit.org/show_bug.cgi?id=103274 exists. 511 if (reason == AboutToExecuteScript) 512 m_frame.document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Blocked script execution in '" + m_frame.document()->url().stringCenterEllipsizedToLength() + "' because the document's frame is sandboxed and the 'allow-scripts' permission is not set."); 513 return false; 514 } 515 516 if (!m_frame.page()) 517 return false; 518 519 return m_frame.loader().client().allowScript(m_frame.settings().isScriptEnabled()); 520} 521 522Deprecated::ScriptValue ScriptController::executeScript(const String& script, bool forceUserGesture) 523{ 524 UserGestureIndicator gestureIndicator(forceUserGesture ? DefinitelyProcessingUserGesture : PossiblyProcessingUserGesture); 525 return executeScript(ScriptSourceCode(script, m_frame.document()->url())); 526} 527 528Deprecated::ScriptValue ScriptController::executeScript(const ScriptSourceCode& sourceCode) 529{ 530 if (!canExecuteScripts(AboutToExecuteScript) || isPaused()) 531 return Deprecated::ScriptValue(); 532 533 Ref<Frame> protect(m_frame); // Script execution can destroy the frame, and thus the ScriptController. 534 535 return evaluate(sourceCode); 536} 537 538bool ScriptController::executeIfJavaScriptURL(const URL& url, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL) 539{ 540 if (!protocolIsJavaScript(url)) 541 return false; 542 543 if (!m_frame.page() || !m_frame.document()->contentSecurityPolicy()->allowJavaScriptURLs(m_frame.document()->url(), eventHandlerPosition().m_line)) 544 return true; 545 546 // We need to hold onto the Frame here because executing script can 547 // destroy the frame. 548 Ref<Frame> protector(m_frame); 549 RefPtr<Document> ownerDocument(m_frame.document()); 550 551 const int javascriptSchemeLength = sizeof("javascript:") - 1; 552 553 String decodedURL = decodeURLEscapeSequences(url.string()); 554 Deprecated::ScriptValue result = executeScript(decodedURL.substring(javascriptSchemeLength)); 555 556 // If executing script caused this frame to be removed from the page, we 557 // don't want to try to replace its document! 558 if (!m_frame.page()) 559 return true; 560 561 String scriptResult; 562 JSDOMWindowShell* shell = windowShell(mainThreadNormalWorld()); 563 JSC::ExecState* exec = shell->window()->globalExec(); 564 if (!result.getString(exec, scriptResult)) 565 return true; 566 567 // FIXME: We should always replace the document, but doing so 568 // synchronously can cause crashes: 569 // http://bugs.webkit.org/show_bug.cgi?id=16782 570 if (shouldReplaceDocumentIfJavaScriptURL == ReplaceDocumentIfJavaScriptURL) { 571 // We're still in a frame, so there should be a DocumentLoader. 572 ASSERT(m_frame.document()->loader()); 573 574 // DocumentWriter::replaceDocument can cause the DocumentLoader to get deref'ed and possible destroyed, 575 // so protect it with a RefPtr. 576 if (RefPtr<DocumentLoader> loader = m_frame.document()->loader()) 577 loader->writer().replaceDocument(scriptResult, ownerDocument.get()); 578 } 579 return true; 580} 581 582} // namespace WebCore 583