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