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 "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
initializeThreading()55 void ScriptController::initializeThreading()
56 {
57 JSC::initializeThreading();
58 WTF::initializeMainThread();
59 }
60
ScriptController(Frame * frame)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
~ScriptController()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
destroyWindowShell(DOMWrapperWorld * world)101 void ScriptController::destroyWindowShell(DOMWrapperWorld* world)
102 {
103 ASSERT(m_windowShells.contains(world));
104 m_windowShells.remove(world);
105 world->didDestroyWindowShell(this);
106 }
107
createWindowShell(DOMWrapperWorld * world)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
evaluateInWorld(const ScriptSourceCode & sourceCode,DOMWrapperWorld * world)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
evaluate(const ScriptSourceCode & sourceCode)163 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
164 {
165 return evaluateInWorld(sourceCode, mainThreadNormalWorld());
166 }
167
createWorld()168 PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
169 {
170 return DOMWrapperWorld::create(JSDOMWindow::commonJSGlobalData());
171 }
172
getAllWorlds(Vector<DOMWrapperWorld * > & worlds)173 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
174 {
175 static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds);
176 }
177
clearWindowShell(bool goingIntoPageCache)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
initScript(DOMWrapperWorld * world)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
eventHandlerLineNumber() const231 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
processingUserGesture()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.
isJavaScriptAnchorNavigation() const267 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
anyPageIsProcessingUserGesture() const277 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
canAccessFromCurrentOrigin(Frame * frame)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
attachDebugger(JSC::Debugger * debugger)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
attachDebugger(JSDOMWindowShell * shell,JSC::Debugger * debugger)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
updateDocument()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
updateSecurityOrigin()346 void ScriptController::updateSecurityOrigin()
347 {
348 // Our bindings do not do anything in this case.
349 }
350
cacheableBindingRootObject()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
bindingRootObject()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
createRootObject(void * nativeHandle)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)
setCaptureCallStackForUncaughtExceptions(bool)388 void ScriptController::setCaptureCallStackForUncaughtExceptions(bool)
389 {
390 }
391 #endif
392
393 #if ENABLE(NETSCAPE_PLUGIN_API)
394
windowScriptNPObject()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
createScriptObjectForPluginElement(HTMLPlugInElement * plugin)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
jsObjectForPluginElement(HTMLPlugInElement * plugin)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
updatePlatformScriptObjects()447 void ScriptController::updatePlatformScriptObjects()
448 {
449 }
450
disconnectPlatformScriptObjects()451 void ScriptController::disconnectPlatformScriptObjects()
452 {
453 }
454
455 #endif
456
cleanupScriptObjectsForPlugin(void * nativeHandle)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
clearScriptObjects()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
executeScriptInWorld(DOMWrapperWorld * world,const String & script,bool forceUserGesture)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