2009-03-26 Darin Adler <darin@apple.com>
[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 "Event.h"
25 #include "EventNames.h"
26 #include "Frame.h"
27 #include "GCController.h"
28 #include "HTMLPlugInElement.h"
29 #include "JSDocument.h"
30 #include "JSLazyEventListener.h"
31 #include "NP_jsobject.h"
32 #include "Page.h"
33 #include "PageGroup.h"
34 #include "ScriptSourceCode.h"
35 #include "ScriptValue.h"
36 #include "Settings.h"
37 #include "npruntime_impl.h"
38 #include "runtime_root.h"
39 #include <debugger/Debugger.h>
40 #include <runtime/JSLock.h>
41
42 using namespace JSC;
43
44 namespace WebCore {
45
46 ScriptController::ScriptController(Frame* frame)
47     : m_frame(frame)
48     , m_handlerLineno(0)
49     , m_sourceURL(0)
50     , m_processingTimerCallback(false)
51     , m_paused(false)
52 #if ENABLE(NETSCAPE_PLUGIN_API)
53     , m_windowScriptNPObject(0)
54 #endif
55 #if PLATFORM(MAC)
56     , m_windowScriptObject(0)
57 #endif
58 {
59 #if PLATFORM(MAC) && ENABLE(MAC_JAVA_BRIDGE)
60     static bool initializedJavaJSBindings;
61     if (!initializedJavaJSBindings) {
62         initializedJavaJSBindings = true;
63         initJavaJSBindings();
64     }
65 #endif
66 }
67
68 ScriptController::~ScriptController()
69 {
70     if (m_windowShell) {
71         m_windowShell = 0;
72     
73         // It's likely that releasing the global object has created a lot of garbage.
74         gcController().garbageCollectSoon();
75     }
76
77     disconnectPlatformScriptObjects();
78 }
79
80 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) 
81 {
82     // evaluate code. Returns the JS return value or 0
83     // if there was none, an error occured or the type couldn't be converted.
84     
85     const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
86
87     initScriptIfNeeded();
88     // inlineCode is true for <a href="javascript:doSomething()">
89     // and false for <script>doSomething()</script>. Check if it has the
90     // expected value in all cases.
91     // See smart window.open policy for where this is used.
92     ExecState* exec = m_windowShell->window()->globalExec();
93     const String* savedSourceURL = m_sourceURL;
94     String sourceURL = jsSourceCode.provider()->url();
95     m_sourceURL = &sourceURL;
96
97     JSLock lock(false);
98
99     // Evaluating the JavaScript could cause the frame to be deallocated
100     // so we start the keep alive timer here.
101     m_frame->keepAlive();
102
103     m_windowShell->window()->globalData()->timeoutChecker.start();
104     Completion comp = JSC::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, m_windowShell);
105     m_windowShell->window()->globalData()->timeoutChecker.stop();
106
107     if (comp.complType() == Normal || comp.complType() == ReturnValue) {
108         m_sourceURL = savedSourceURL;
109         return comp.value();
110     }
111
112     if (comp.complType() == Throw || comp.complType() == Interrupted)
113         reportException(exec, comp.value());
114
115     m_sourceURL = savedSourceURL;
116     return noValue();
117 }
118
119 void ScriptController::clearWindowShell()
120 {
121     if (!m_windowShell)
122         return;
123
124     JSLock lock(false);
125     m_windowShell->window()->clear();
126     m_liveFormerWindows.add(m_windowShell->window());
127     m_windowShell->setWindow(m_frame->domWindow());
128     if (Page* page = m_frame->page()) {
129         attachDebugger(page->debugger());
130         m_windowShell->window()->setProfileGroup(page->group().identifier());
131     }
132
133     // There is likely to be a lot of garbage now.
134     gcController().garbageCollectSoon();
135 }
136
137 PassRefPtr<EventListener> ScriptController::createInlineEventListener(const String& functionName, const String& code, Node* node)
138 {
139     initScriptIfNeeded();
140     JSLock lock(false);
141     return JSLazyEventListener::create(JSLazyEventListener::HTMLLazyEventListener, functionName, code, m_windowShell->window(), node, m_handlerLineno);
142 }
143
144 #if ENABLE(SVG)
145
146 PassRefPtr<EventListener> ScriptController::createSVGEventHandler(const String& functionName, const String& code, Node* node)
147 {
148     initScriptIfNeeded();
149     JSLock lock(false);
150     return JSLazyEventListener::create(JSLazyEventListener::SVGLazyEventListener, functionName, code, m_windowShell->window(), node, m_handlerLineno);
151 }
152
153 #endif
154
155 void ScriptController::initScript()
156 {
157     if (m_windowShell)
158         return;
159
160     JSLock lock(false);
161
162     m_windowShell = new JSDOMWindowShell(m_frame->domWindow());
163     updateDocument();
164
165     if (Page* page = m_frame->page()) {
166         attachDebugger(page->debugger());
167         m_windowShell->window()->setProfileGroup(page->group().identifier());
168     }
169
170     m_frame->loader()->dispatchWindowObjectAvailable();
171 }
172
173 bool ScriptController::processingUserGesture() const
174 {
175     return processingUserGestureEvent() || isJavaScriptAnchorNavigation();
176 }
177
178 bool ScriptController::processingUserGestureEvent() const
179 {
180     if (!m_windowShell)
181         return false;
182
183     if (Event* event = m_windowShell->window()->currentEvent()) {
184         const AtomicString& type = event->type();
185         if ( // mouse events
186             type == eventNames().clickEvent || type == eventNames().mousedownEvent ||
187             type == eventNames().mouseupEvent || type == eventNames().dblclickEvent ||
188             // keyboard events
189             type == eventNames().keydownEvent || type == eventNames().keypressEvent ||
190             type == eventNames().keyupEvent ||
191             // other accepted events
192             type == eventNames().selectEvent || type == eventNames().changeEvent ||
193             type == eventNames().focusEvent || type == eventNames().blurEvent ||
194             type == eventNames().submitEvent)
195             return true;
196     }
197     
198     return false;
199 }
200
201 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor.
202 bool ScriptController::isJavaScriptAnchorNavigation() const
203 {
204     // This is the <a href="javascript:window.open('...')> case -> we let it through
205     if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
206         return true;
207
208     // This is the <script>window.open(...)</script> case or a timer callback -> block it
209     return false;
210 }
211
212 bool ScriptController::anyPageIsProcessingUserGesture() const
213 {
214     Page* page = m_frame->page();
215     if (!page)
216         return false;
217
218     const HashSet<Page*>& pages = page->group().pages();
219     HashSet<Page*>::const_iterator end = pages.end();
220     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
221         for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
222             if (frame->script()->processingUserGesture())
223                 return true;
224         }
225     }
226
227     return false;
228 }
229
230 bool ScriptController::isEnabled()
231 {
232     Settings* settings = m_frame->settings();
233     return (settings && settings->isJavaScriptEnabled());
234 }
235
236 void ScriptController::attachDebugger(JSC::Debugger* debugger)
237 {
238     if (!m_windowShell)
239         return;
240
241     if (debugger)
242         debugger->attach(m_windowShell->window());
243     else if (JSC::Debugger* currentDebugger = m_windowShell->window()->debugger())
244         currentDebugger->detach(m_windowShell->window());
245 }
246
247 void ScriptController::updateDocument()
248 {
249     if (!m_frame->document())
250         return;
251
252     JSLock lock(false);
253     if (m_windowShell)
254         m_windowShell->window()->updateDocument();
255     HashSet<JSDOMWindow*>::iterator end = m_liveFormerWindows.end();
256     for (HashSet<JSDOMWindow*>::iterator it = m_liveFormerWindows.begin(); it != end; ++it)
257         (*it)->updateDocument();
258 }
259
260 void ScriptController::updateSecurityOrigin()
261 {
262     // Our bindings do not do anything in this case.
263 }
264
265 Bindings::RootObject* ScriptController::bindingRootObject()
266 {
267     if (!isEnabled())
268         return 0;
269
270     if (!m_bindingRootObject) {
271         JSLock lock(false);
272         m_bindingRootObject = Bindings::RootObject::create(0, globalObject());
273     }
274     return m_bindingRootObject.get();
275 }
276
277 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
278 {
279     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
280     if (it != m_rootObjects.end())
281         return it->second;
282
283     RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject());
284
285     m_rootObjects.set(nativeHandle, rootObject);
286     return rootObject.release();
287 }
288
289 #if ENABLE(NETSCAPE_PLUGIN_API)
290
291 NPObject* ScriptController::windowScriptNPObject()
292 {
293     if (!m_windowScriptNPObject) {
294         if (isEnabled()) {
295             // JavaScript is enabled, so there is a JavaScript window object.
296             // Return an NPObject bound to the window object.
297             JSC::JSLock lock(false);
298             JSObject* win = windowShell()->window();
299             ASSERT(win);
300             Bindings::RootObject* root = bindingRootObject();
301             m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
302         } else {
303             // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
304             // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
305             m_windowScriptNPObject = _NPN_CreateNoScriptObject();
306         }
307     }
308
309     return m_windowScriptNPObject;
310 }
311
312 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
313 {
314     JSObject* object = jsObjectForPluginElement(plugin);
315     if (!object)
316         return _NPN_CreateNoScriptObject();
317
318     // Wrap the JSObject in an NPObject
319     return _NPN_CreateScriptObject(0, object, bindingRootObject());
320 }
321
322 #endif
323
324 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
325 {
326     // Can't create JSObjects when JavaScript is disabled
327     if (!isEnabled())
328         return 0;
329
330     // Create a JSObject bound to this element
331     JSLock lock(false);
332     ExecState* exec = globalObject()->globalExec();
333     JSValuePtr jsElementValue = toJS(exec, plugin);
334     if (!jsElementValue || !jsElementValue.isObject())
335         return 0;
336     
337     return jsElementValue.getObject();
338 }
339
340 #if !PLATFORM(MAC)
341
342 void ScriptController::updatePlatformScriptObjects()
343 {
344 }
345
346 void ScriptController::disconnectPlatformScriptObjects()
347 {
348 }
349
350 #endif
351
352 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
353 {
354     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
355
356     if (it == m_rootObjects.end())
357         return;
358
359     it->second->invalidate();
360     m_rootObjects.remove(it);
361 }
362
363 void ScriptController::clearScriptObjects()
364 {
365     JSLock lock(false);
366
367     RootObjectMap::const_iterator end = m_rootObjects.end();
368     for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
369         it->second->invalidate();
370
371     m_rootObjects.clear();
372
373     if (m_bindingRootObject) {
374         m_bindingRootObject->invalidate();
375         m_bindingRootObject = 0;
376     }
377
378 #if ENABLE(NETSCAPE_PLUGIN_API)
379     if (m_windowScriptNPObject) {
380         // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
381         // script object properly.
382         // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
383         _NPN_DeallocateObject(m_windowScriptNPObject);
384         m_windowScriptNPObject = 0;
385     }
386 #endif
387 }
388
389 } // namespace WebCore