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