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