Add pause on exception support to the Inspector debugger
[WebKit-https.git] / WebCore / page / JavaScriptDebugServer.cpp
1 /*
2  * Copyright (C) 2008 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "JavaScriptDebugServer.h"
31
32 #include "DOMWindow.h"
33 #include "EventLoop.h"
34 #include "Frame.h"
35 #include "FrameTree.h"
36 #include "FrameView.h"
37 #include "JSDOMWindow.h"
38 #include "JavaScriptCallFrame.h"
39 #include "JavaScriptDebugListener.h"
40 #include "kjs_proxy.h"
41 #include "Page.h"
42 #include "PageGroup.h"
43 #include "PluginView.h"
44 #include "ScrollView.h"
45 #include "Widget.h"
46 #include <wtf/MainThread.h>
47
48 using namespace KJS;
49
50 namespace WebCore {
51
52 typedef JavaScriptDebugServer::ListenerSet ListenerSet;
53
54 JavaScriptDebugServer& JavaScriptDebugServer::shared()
55 {
56     static JavaScriptDebugServer server;
57     return server;
58 }
59
60 JavaScriptDebugServer::JavaScriptDebugServer()
61     : m_callingListeners(false)
62     , m_pauseOnExceptions(false)
63     , m_pauseOnNextStatement(false)
64     , m_paused(false)
65     , m_pauseOnExecState(0)
66 {
67 }
68
69 JavaScriptDebugServer::~JavaScriptDebugServer()
70 {
71     deleteAllValues(m_pageListenersMap);
72     deleteAllValues(m_breakpoints);
73 }
74
75 void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener)
76 {
77     if (!hasListeners())
78         Page::setDebuggerForAllPages(this);
79
80     m_listeners.add(listener);
81 }
82
83 void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener)
84 {
85     m_listeners.remove(listener);
86     if (!hasListeners()) {
87         Page::setDebuggerForAllPages(0);
88         resume();
89     }
90 }
91
92 void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener, Page* page)
93 {
94     ASSERT_ARG(page, page);
95
96     if (!hasListeners())
97         Page::setDebuggerForAllPages(this);
98
99     pair<PageListenersMap::iterator, bool> result = m_pageListenersMap.add(page, 0);
100     if (result.second)
101         result.first->second = new ListenerSet;
102     ListenerSet* listeners = result.first->second;
103
104     listeners->add(listener);
105 }
106
107 void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener, Page* page)
108 {
109     ASSERT_ARG(page, page);
110
111     PageListenersMap::iterator it = m_pageListenersMap.find(page);
112     if (it == m_pageListenersMap.end())
113         return;
114
115     ListenerSet* listeners = it->second;
116
117     listeners->remove(listener);
118
119     if (listeners->isEmpty()) {
120         m_pageListenersMap.remove(it);
121         delete listeners;
122     }
123
124     if (!hasListeners()) {
125         Page::setDebuggerForAllPages(0);
126         resume();
127     }
128 }
129
130 void JavaScriptDebugServer::pageCreated(Page* page)
131 {
132     if (!hasListeners())
133         return;
134
135     page->setDebugger(this);
136 }
137
138 bool JavaScriptDebugServer::hasListenersInterestedInPage(Page* page)
139 {
140     ASSERT_ARG(page, page);
141
142     if (!m_listeners.isEmpty())
143         return true;
144
145     return m_pageListenersMap.contains(page);
146 }
147
148 void JavaScriptDebugServer::addBreakpoint(int sourceID, unsigned lineNumber)
149 {
150     HashSet<unsigned>* lines = m_breakpoints.get(sourceID);
151     if (!lines) {
152         lines = new HashSet<unsigned>;
153         m_breakpoints.set(sourceID, lines);
154     }
155
156     lines->add(lineNumber);
157 }
158
159 void JavaScriptDebugServer::removeBreakpoint(int sourceID, unsigned lineNumber)
160 {
161     HashSet<unsigned>* lines = m_breakpoints.get(sourceID);
162     if (!lines)
163         return;
164
165     lines->remove(lineNumber);
166
167     if (!lines->isEmpty())
168         return;
169
170     m_breakpoints.remove(sourceID);
171     delete lines;
172 }
173
174 bool JavaScriptDebugServer::hasBreakpoint(int sourceID, unsigned lineNumber) const
175 {
176     HashSet<unsigned>* lines = m_breakpoints.get(sourceID);
177     if (!lines)
178         return false;
179     return lines->contains(lineNumber);
180 }
181
182 void JavaScriptDebugServer::clearBreakpoints()
183 {
184     deleteAllValues(m_breakpoints);
185     m_breakpoints.clear();
186 }
187
188 void JavaScriptDebugServer::setPauseOnExceptions(bool pause)
189 {
190     m_pauseOnExceptions = pause;
191 }
192
193 void JavaScriptDebugServer::pauseOnNextStatement()
194 {
195     m_pauseOnNextStatement = true;
196 }
197
198 void JavaScriptDebugServer::resume()
199 {
200     m_paused = false;
201 }
202
203 void JavaScriptDebugServer::stepIntoStatement()
204 {
205     if (!m_paused)
206         return;
207
208     resume();
209
210     m_pauseOnNextStatement = true;
211 }
212
213 void JavaScriptDebugServer::stepOverStatement()
214 {
215     if (!m_paused)
216         return;
217
218     resume();
219
220     if (m_currentCallFrame)
221         m_pauseOnExecState = m_currentCallFrame->execState();
222     else
223         m_pauseOnExecState = 0;
224 }
225
226 void JavaScriptDebugServer::stepOutOfFunction()
227 {
228     if (!m_paused)
229         return;
230
231     resume();
232
233     if (m_currentCallFrame && m_currentCallFrame->caller())
234         m_pauseOnExecState = m_currentCallFrame->caller()->execState();
235     else
236         m_pauseOnExecState = 0;
237 }
238
239 JavaScriptCallFrame* JavaScriptDebugServer::currentCallFrame()
240 {
241     if (!m_paused)
242         return 0;
243     return m_currentCallFrame.get();
244 }
245
246 static void dispatchDidParseSource(const ListenerSet& listeners, const UString& source, int startingLineNumber, const UString& sourceURL, int sourceID)
247 {
248     Vector<JavaScriptDebugListener*> copy;
249     copyToVector(listeners, copy);
250     for (size_t i = 0; i < copy.size(); ++i)
251         copy[i]->didParseSource(source, startingLineNumber, sourceURL, sourceID);
252 }
253
254 static void dispatchFailedToParseSource(const ListenerSet& listeners, const UString& source, int startingLineNumber, const UString& sourceURL, int errorLine, const UString& errorMessage)
255 {
256     Vector<JavaScriptDebugListener*> copy;
257     copyToVector(listeners, copy);
258     for (size_t i = 0; i < copy.size(); ++i)
259         copy[i]->failedToParseSource(source, startingLineNumber, sourceURL, errorLine, errorMessage);
260 }
261
262 static Page* toPage(ExecState* exec)
263 {
264     ASSERT_ARG(exec, exec);
265
266     JSDOMWindow* window = asJSDOMWindow(exec->dynamicGlobalObject());
267     ASSERT(window);
268
269     return window->impl()->frame()->page();
270 }
271
272 bool JavaScriptDebugServer::sourceParsed(ExecState* exec, int sourceID, const UString& sourceURL, const UString& source, int startingLineNumber, int errorLine, const UString& errorMessage)
273 {
274     if (m_callingListeners)
275         return true;
276
277     Page* page = toPage(exec);
278     if (!page)
279         return true;
280
281     m_callingListeners = true;
282
283     ASSERT(hasListeners());
284
285     bool isError = errorLine != -1;
286
287     if (!m_listeners.isEmpty()) {
288         if (isError)
289             dispatchFailedToParseSource(m_listeners, source, startingLineNumber, sourceURL, errorLine, errorMessage);
290         else
291             dispatchDidParseSource(m_listeners, source, startingLineNumber, sourceURL, sourceID);
292     }
293
294     if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
295         ASSERT(!pageListeners->isEmpty());
296         if (isError)
297             dispatchFailedToParseSource(*pageListeners, source, startingLineNumber, sourceURL, errorLine, errorMessage);
298         else
299             dispatchDidParseSource(*pageListeners, source, startingLineNumber, sourceURL, sourceID);
300     }
301
302     m_callingListeners = false;
303     return true;
304 }
305
306 static void dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptDebugServer::JavaScriptExecutionCallback callback)
307 {
308     Vector<JavaScriptDebugListener*> copy;
309     copyToVector(listeners, copy);
310     for (size_t i = 0; i < copy.size(); ++i)
311         (copy[i]->*callback)();
312 }
313
314 void JavaScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, ExecState* exec)
315 {
316     if (m_callingListeners)
317         return;
318
319     Page* page = toPage(exec);
320     if (!page)
321         return;
322
323     m_callingListeners = true;
324
325     ASSERT(hasListeners());
326
327     WebCore::dispatchFunctionToListeners(m_listeners, callback);
328     if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
329         ASSERT(!pageListeners->isEmpty());
330         WebCore::dispatchFunctionToListeners(*pageListeners, callback);
331     }
332
333     m_callingListeners = false;
334 }
335
336 void JavaScriptDebugServer::setJavaScriptPaused(const PageGroup& pageGroup, bool paused)
337 {
338     setMainThreadCallbacksPaused(paused);
339
340     const HashSet<Page*>& pages = pageGroup.pages();
341
342     HashSet<Page*>::const_iterator end = pages.end();
343     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it)
344         setJavaScriptPaused(*it, false);
345 }
346
347 void JavaScriptDebugServer::setJavaScriptPaused(Page* page, bool paused)
348 {
349     ASSERT_ARG(page, page);
350
351     page->setDefersLoading(paused);
352
353     for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext())
354         setJavaScriptPaused(frame, paused);
355 }
356
357 void JavaScriptDebugServer::setJavaScriptPaused(Frame* frame, bool paused)
358 {
359     ASSERT_ARG(frame, frame);
360
361     if (!frame->scriptProxy()->isEnabled())
362         return;
363
364     frame->scriptProxy()->setPaused(paused);
365
366     if (JSDOMWindow* window = toJSDOMWindow(frame)) {
367         if (paused)
368             m_pausedTimeouts.set(frame, window->pauseTimeouts());
369         else
370             window->resumeTimeouts(m_pausedTimeouts.take(frame));
371     }
372
373     setJavaScriptPaused(frame->view(), paused);
374 }
375
376 void JavaScriptDebugServer::setJavaScriptPaused(FrameView* view, bool paused)
377 {
378 #if !PLATFORM(MAC)
379     if (!view)
380         return;
381
382     HashSet<Widget*>* children = static_cast<ScrollView*>(view)->children();
383     ASSERT(children);
384
385     HashSet<Widget*>::iterator end = children->end();
386     for (HashSet<Widget*>::iterator it = children->begin(); it != end; ++it) {
387         Widget* widget = *it;
388         if (!widget->isPluginView())
389             continue;
390         static_cast<PluginView*>(widget)->setJavaScriptPaused(paused);
391     }
392 #endif
393 }
394
395 void JavaScriptDebugServer::pauseIfNeeded(ExecState* exec, int sourceID, int lineNumber)
396 {
397     if (m_paused)
398         return;
399
400     Page* page = toPage(exec);
401     if (!page || !hasListenersInterestedInPage(page))
402         return;
403
404     bool pauseNow = m_pauseOnNextStatement;
405     if (!pauseNow && m_pauseOnExecState)
406         pauseNow = (m_pauseOnExecState == exec);
407     if (!pauseNow && lineNumber > 0)
408         pauseNow = hasBreakpoint(sourceID, lineNumber);
409     if (!pauseNow)
410         return;
411
412     m_pauseOnExecState = 0;
413     m_pauseOnNextStatement = false;
414     m_paused = true;
415
416     dispatchFunctionToListeners(&JavaScriptDebugListener::didPause, exec);
417
418     setJavaScriptPaused(page->group(), true);
419
420     EventLoop loop;
421     while (m_paused && !loop.ended())
422         loop.cycle();
423
424     setJavaScriptPaused(page->group(), false);
425
426     m_paused = false;
427 }
428
429 bool JavaScriptDebugServer::callEvent(ExecState* exec, int sourceID, int lineNumber, JSObject*, const List&)
430 {
431     if (m_paused)
432         return true;
433
434     if (m_currentCallFrame && m_currentCallFrame->execState() != exec->callingExecState()) {
435         m_currentCallFrame->invalidate();
436         m_currentCallFrame = 0;
437     }
438
439     m_currentCallFrame = JavaScriptCallFrame::create(exec, m_currentCallFrame, sourceID, lineNumber);
440     pauseIfNeeded(exec, sourceID, lineNumber);
441     return true;
442 }
443
444 bool JavaScriptDebugServer::atStatement(ExecState* exec, int sourceID, int firstLine, int)
445 {
446     if (m_paused)
447         return true;
448     if (m_currentCallFrame)
449         m_currentCallFrame->setLine(firstLine);
450     else
451         m_currentCallFrame = JavaScriptCallFrame::create(exec, 0, sourceID, firstLine);
452     pauseIfNeeded(exec, sourceID, firstLine);
453     return true;
454 }
455
456 bool JavaScriptDebugServer::returnEvent(ExecState* exec, int sourceID, int lineNumber, JSObject*)
457 {
458     if (m_paused)
459         return true;
460     m_currentCallFrame->invalidate();
461     m_currentCallFrame = m_currentCallFrame->caller();
462     pauseIfNeeded(exec, sourceID, lineNumber);
463     return true;
464 }
465
466 bool JavaScriptDebugServer::exception(ExecState* exec, int sourceID, int lineNumber, JSValue*)
467 {
468     if (m_paused)
469         return true;
470     if (m_currentCallFrame)
471         m_currentCallFrame->setLine(lineNumber);
472     else
473         m_currentCallFrame = JavaScriptCallFrame::create(exec, 0, sourceID, lineNumber);
474     if (m_pauseOnExceptions)
475         m_pauseOnNextStatement = true;
476     pauseIfNeeded(exec, sourceID, lineNumber);
477     return true;
478 }
479
480 } // namespace WebCore