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