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