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 "CString.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 "InspectorTimelineAgent.h" 32 #include "JSDocument.h" 33 #include "NP_jsobject.h" 34 #include "Page.h" 35 #include "PageGroup.h" 36 #include "ScriptSourceCode.h" 37 #include "ScriptValue.h" 38 #include "Settings.h" 39 #include "StorageNamespace.h" 40 #include "XSSAuditor.h" 41 #include "npruntime_impl.h" 42 #include "runtime_root.h" 43 #include <debugger/Debugger.h> 44 #include <runtime/InitializeThreading.h> 45 #include <runtime/JSLock.h> 46 47 using namespace JSC; 48 using namespace std; 49 50 namespace WebCore { 51 52 void ScriptController::initializeThreading() 53 { 54 JSC::initializeThreading(); 55 } 56 57 ScriptController::ScriptController(Frame* frame) 58 : m_frame(frame) 59 , m_handlerLineNumber(0) 60 , m_sourceURL(0) 61 , m_inExecuteScript(false) 62 , m_processingTimerCallback(false) 63 , m_paused(false) 64 , m_allowPopupsFromPlugin(false) 65 #if ENABLE(NETSCAPE_PLUGIN_API) 66 , m_windowScriptNPObject(0) 67 #endif 68 #if PLATFORM(MAC) 69 , m_windowScriptObject(0) 70 #endif 71 , m_XSSAuditor(new XSSAuditor(frame)) 72 { 73 #if PLATFORM(MAC) && ENABLE(MAC_JAVA_BRIDGE) 74 static bool initializedJavaJSBindings; 75 if (!initializedJavaJSBindings) { 76 initializedJavaJSBindings = true; 77 initJavaJSBindings(); 78 } 79 #endif 80 } 81 82 ScriptController::~ScriptController() 83 { 84 if (!m_windowShells.isEmpty()) { 85 m_windowShells.clear(); 86 87 // It's likely that releasing the global object has created a lot of garbage. 88 gcController().garbageCollectSoon(); 89 } 90 91 disconnectPlatformScriptObjects(); 92 } 93 94 ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world) 95 { 96 const SourceCode& jsSourceCode = sourceCode.jsSourceCode(); 97 String sourceURL = jsSourceCode.provider()->url(); 98 99 if (!m_XSSAuditor->canEvaluate(sourceCode.source())) { 100 // This script is not safe to be evaluated. 101 return JSValue(); 102 } 103 104 // evaluate code. Returns the JS return value or 0 105 // if there was none, an error occurred or the type couldn't be converted. 106 107 // inlineCode is true for <a href="javascript:doSomething()"> 108 // and false for <script>doSomething()</script>. Check if it has the 109 // expected value in all cases. 110 // See smart window.open policy for where this is used. 111 JSDOMWindowShell* shell = windowShell(world); 112 ExecState* exec = shell->window()->globalExec(); 113 const String* savedSourceURL = m_sourceURL; 114 m_sourceURL = &sourceURL; 115 116 JSLock lock(SilenceAssertionsOnly); 117 118 RefPtr<Frame> protect = m_frame; 119 120 #if ENABLE(INSPECTOR) 121 if (InspectorTimelineAgent* timelineAgent = m_frame->page() ? m_frame->page()->inspectorTimelineAgent() : 0) 122 timelineAgent->willEvaluateScript(sourceURL, sourceCode.startLine()); 123 #endif 124 125 exec->globalData().timeoutChecker.start(); 126 Completion comp = JSC::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell); 127 exec->globalData().timeoutChecker.stop(); 128 129 #if ENABLE(INSPECTOR) 130 if (InspectorTimelineAgent* timelineAgent = m_frame->page() ? m_frame->page()->inspectorTimelineAgent() : 0) 131 timelineAgent->didEvaluateScript(); 132 #endif 133 134 // Evaluating the JavaScript could cause the frame to be deallocated 135 // so we start the keep alive timer here. 136 m_frame->keepAlive(); 137 138 if (comp.complType() == Normal || comp.complType() == ReturnValue) { 139 m_sourceURL = savedSourceURL; 140 return comp.value(); 141 } 142 143 if (comp.complType() == Throw || comp.complType() == Interrupted) 144 reportException(exec, comp.value()); 145 146 m_sourceURL = savedSourceURL; 147 return JSValue(); 148 } 149 150 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) 151 { 152 return evaluateInWorld(sourceCode, mainThreadNormalWorld()); 153 } 154 155 // An DOMWrapperWorld other than the thread's normal world. 156 class IsolatedWorld : public DOMWrapperWorld { 157 public: 158 static PassRefPtr<IsolatedWorld> create(JSGlobalData* globalData) { return adoptRef(new IsolatedWorld(globalData)); } 159 160 protected: 161 IsolatedWorld(JSGlobalData* globalData) 162 : DOMWrapperWorld(globalData, false) 163 { 164 JSGlobalData::ClientData* clientData = globalData->clientData; 165 ASSERT(clientData); 166 static_cast<WebCoreJSClientData*>(clientData)->rememberWorld(this); 167 } 168 }; 169 170 PassRefPtr<DOMWrapperWorld> ScriptController::createWorld() 171 { 172 return IsolatedWorld::create(JSDOMWindow::commonJSGlobalData()); 173 } 174 175 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds) 176 { 177 static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds); 178 } 179 180 void ScriptController::clearWindowShell() 181 { 182 if (m_windowShells.isEmpty()) 183 return; 184 185 JSLock lock(SilenceAssertionsOnly); 186 187 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) { 188 JSDOMWindowShell* windowShell = iter->second; 189 190 // Clear the debugger from the current window before setting the new window. 191 attachDebugger(windowShell, 0); 192 193 windowShell->window()->willRemoveFromWindowShell(); 194 windowShell->setWindow(m_frame->domWindow()); 195 196 if (Page* page = m_frame->page()) { 197 attachDebugger(windowShell, page->debugger()); 198 windowShell->window()->setProfileGroup(page->group().identifier()); 199 } 200 } 201 202 // There is likely to be a lot of garbage now. 203 gcController().garbageCollectSoon(); 204 } 205 206 JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world) 207 { 208 ASSERT(!m_windowShells.contains(world)); 209 210 JSLock lock(SilenceAssertionsOnly); 211 212 JSDOMWindowShell* windowShell = new JSDOMWindowShell(m_frame->domWindow(), world); 213 m_windowShells.add(world, windowShell); 214 windowShell->window()->updateDocument(); 215 216 if (Page* page = m_frame->page()) { 217 attachDebugger(windowShell, page->debugger()); 218 windowShell->window()->setProfileGroup(page->group().identifier()); 219 } 220 221 m_frame->loader()->dispatchDidClearWindowObjectInWorld(world); 222 223 return windowShell; 224 } 225 226 bool ScriptController::processingUserGesture(DOMWrapperWorld* world) const 227 { 228 return m_allowPopupsFromPlugin || processingUserGestureEvent(world) || isJavaScriptAnchorNavigation(); 229 } 230 231 bool ScriptController::processingUserGestureEvent(DOMWrapperWorld* world) const 232 { 233 JSDOMWindowShell* shell = existingWindowShell(world); 234 if (!shell) 235 return false; 236 237 if (Event* event = shell->window()->currentEvent()) 238 return event->fromUserGesture(); 239 240 return false; 241 } 242 243 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor. 244 bool ScriptController::isJavaScriptAnchorNavigation() const 245 { 246 // This is the <a href="javascript:window.open('...')> case -> we let it through 247 if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback) 248 return true; 249 250 // This is the <script>window.open(...)</script> case or a timer callback -> block it 251 return false; 252 } 253 254 bool ScriptController::anyPageIsProcessingUserGesture() const 255 { 256 Page* page = m_frame->page(); 257 if (!page) 258 return false; 259 260 const HashSet<Page*>& pages = page->group().pages(); 261 HashSet<Page*>::const_iterator end = pages.end(); 262 for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) { 263 for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) { 264 ScriptController* script = frame->script(); 265 266 if (script->m_allowPopupsFromPlugin) 267 return true; 268 269 const ShellMap::const_iterator iterEnd = m_windowShells.end(); 270 for (ShellMap::const_iterator iter = m_windowShells.begin(); iter != iterEnd; ++iter) { 271 JSDOMWindowShell* shell = iter->second.get(); 272 Event* event = shell->window()->currentEvent(); 273 if (event && event->fromUserGesture()) 274 return true; 275 } 276 277 if (isJavaScriptAnchorNavigation()) 278 return true; 279 } 280 } 281 282 return false; 283 } 284 285 void ScriptController::attachDebugger(JSC::Debugger* debugger) 286 { 287 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) 288 attachDebugger(iter->second, debugger); 289 } 290 291 void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger) 292 { 293 if (!shell) 294 return; 295 296 JSDOMWindow* globalObject = shell->window(); 297 if (debugger) 298 debugger->attach(globalObject); 299 else if (JSC::Debugger* currentDebugger = globalObject->debugger()) 300 currentDebugger->detach(globalObject); 301 } 302 303 void ScriptController::updateDocument() 304 { 305 if (!m_frame->document()) 306 return; 307 308 JSLock lock(SilenceAssertionsOnly); 309 for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) 310 iter->second->window()->updateDocument(); 311 } 312 313 void ScriptController::updateSecurityOrigin() 314 { 315 // Our bindings do not do anything in this case. 316 } 317 318 Bindings::RootObject* ScriptController::bindingRootObject() 319 { 320 if (!canExecuteScripts()) 321 return 0; 322 323 if (!m_bindingRootObject) { 324 JSLock lock(SilenceAssertionsOnly); 325 m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld())); 326 } 327 return m_bindingRootObject.get(); 328 } 329 330 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle) 331 { 332 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 333 if (it != m_rootObjects.end()) 334 return it->second; 335 336 RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld())); 337 338 m_rootObjects.set(nativeHandle, rootObject); 339 return rootObject.release(); 340 } 341 342 #if ENABLE(NETSCAPE_PLUGIN_API) 343 344 NPObject* ScriptController::windowScriptNPObject() 345 { 346 if (!m_windowScriptNPObject) { 347 if (canExecuteScripts()) { 348 // JavaScript is enabled, so there is a JavaScript window object. 349 // Return an NPObject bound to the window object. 350 JSC::JSLock lock(SilenceAssertionsOnly); 351 JSObject* win = windowShell(pluginWorld())->window(); 352 ASSERT(win); 353 Bindings::RootObject* root = bindingRootObject(); 354 m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root); 355 } else { 356 // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object. 357 // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object. 358 m_windowScriptNPObject = _NPN_CreateNoScriptObject(); 359 } 360 } 361 362 return m_windowScriptNPObject; 363 } 364 365 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin) 366 { 367 JSObject* object = jsObjectForPluginElement(plugin); 368 if (!object) 369 return _NPN_CreateNoScriptObject(); 370 371 // Wrap the JSObject in an NPObject 372 return _NPN_CreateScriptObject(0, object, bindingRootObject()); 373 } 374 375 #endif 376 377 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin) 378 { 379 // Can't create JSObjects when JavaScript is disabled 380 if (!canExecuteScripts()) 381 return 0; 382 383 // Create a JSObject bound to this element 384 JSLock lock(SilenceAssertionsOnly); 385 JSDOMWindow* globalObj = globalObject(pluginWorld()); 386 // FIXME: is normal okay? - used for NP plugins? 387 JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin); 388 if (!jsElementValue || !jsElementValue.isObject()) 389 return 0; 390 391 return jsElementValue.getObject(); 392 } 393 394 #if !PLATFORM(MAC) 395 396 void ScriptController::updatePlatformScriptObjects() 397 { 398 } 399 400 void ScriptController::disconnectPlatformScriptObjects() 401 { 402 } 403 404 #endif 405 406 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle) 407 { 408 RootObjectMap::iterator it = m_rootObjects.find(nativeHandle); 409 410 if (it == m_rootObjects.end()) 411 return; 412 413 it->second->invalidate(); 414 m_rootObjects.remove(it); 415 } 416 417 void ScriptController::clearScriptObjects() 418 { 419 JSLock lock(SilenceAssertionsOnly); 420 421 RootObjectMap::const_iterator end = m_rootObjects.end(); 422 for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it) 423 it->second->invalidate(); 424 425 m_rootObjects.clear(); 426 427 if (m_bindingRootObject) { 428 m_bindingRootObject->invalidate(); 429 m_bindingRootObject = 0; 430 } 431 432 #if ENABLE(NETSCAPE_PLUGIN_API) 433 if (m_windowScriptNPObject) { 434 // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window 435 // script object properly. 436 // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. 437 _NPN_DeallocateObject(m_windowScriptNPObject); 438 m_windowScriptNPObject = 0; 439 } 440 #endif 441 } 442 443 ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture) 444 { 445 ScriptSourceCode sourceCode(script, forceUserGesture ? KURL() : m_frame->loader()->url()); 446 447 if (!canExecuteScripts() || isPaused()) 448 return ScriptValue(); 449 450 bool wasInExecuteScript = m_inExecuteScript; 451 m_inExecuteScript = true; 452 453 ScriptValue result = evaluateInWorld(sourceCode, world); 454 455 if (!wasInExecuteScript) { 456 m_inExecuteScript = false; 457 Document::updateStyleForAllDocuments(); 458 } 459 460 return result; 461 } 462 463 } // namespace WebCore 464