Implements more debugger APIs on JavaScriptDebugServer and reduces
[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_pauseOnNextStatement(false)
63     , m_paused(false)
64     , m_pauseOnExecState(0)
65 {
66 }
67
68 JavaScriptDebugServer::~JavaScriptDebugServer()
69 {
70     deleteAllValues(m_pageListenersMap);
71     deleteAllValues(m_breakpoints);
72 }
73
74 void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener)
75 {
76     if (!hasListeners())
77         Page::setDebuggerForAllPages(this);
78
79     m_listeners.add(listener);
80 }
81
82 void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener)
83 {
84     m_listeners.remove(listener);
85     if (!hasListeners()) {
86         Page::setDebuggerForAllPages(0);
87         resume();
88     }
89 }
90
91 void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener, Page* page)
92 {
93     ASSERT_ARG(page, page);
94
95     if (!hasListeners())
96         Page::setDebuggerForAllPages(this);
97
98     pair<PageListenersMap::iterator, bool> result = m_pageListenersMap.add(page, 0);
99     if (result.second)
100         result.first->second = new ListenerSet;
101     ListenerSet* listeners = result.first->second;
102
103     listeners->add(listener);
104 }
105
106 void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener, Page* page)
107 {
108     ASSERT_ARG(page, page);
109
110     PageListenersMap::iterator it = m_pageListenersMap.find(page);
111     if (it == m_pageListenersMap.end())
112         return;
113
114     ListenerSet* listeners = it->second;
115
116     listeners->remove(listener);
117
118     if (listeners->isEmpty()) {
119         m_pageListenersMap.remove(it);
120         delete listeners;
121     }
122
123     if (!hasListeners()) {
124         Page::setDebuggerForAllPages(0);
125         resume();
126     }
127 }
128
129 void JavaScriptDebugServer::pageCreated(Page* page)
130 {
131     if (!hasListeners())
132         return;
133
134     page->setDebugger(this);
135 }
136
137 bool JavaScriptDebugServer::hasListenersInterestedInPage(Page* page)
138 {
139     ASSERT_ARG(page, page);
140
141     if (!m_listeners.isEmpty())
142         return true;
143
144     return m_pageListenersMap.contains(page);
145 }
146
147 void JavaScriptDebugServer::addBreakpoint(int sourceID, unsigned lineNumber)
148 {
149     HashSet<unsigned>* lines = m_breakpoints.get(sourceID);
150     if (!lines) {
151         lines = new HashSet<unsigned>;
152         m_breakpoints.set(sourceID, lines);
153     }
154
155     lines->add(lineNumber);
156 }
157
158 void JavaScriptDebugServer::removeBreakpoint(int sourceID, unsigned lineNumber)
159 {
160     HashSet<unsigned>* lines = m_breakpoints.get(sourceID);
161     if (!lines)
162         return;
163
164     lines->remove(lineNumber);
165
166     if (!lines->isEmpty())
167         return;
168
169     m_breakpoints.remove(sourceID);
170     delete lines;
171 }
172
173 bool JavaScriptDebugServer::hasBreakpoint(int sourceID, unsigned lineNumber) const
174 {
175     HashSet<unsigned>* lines = m_breakpoints.get(sourceID);
176     if (!lines)
177         return false;
178     return lines->contains(lineNumber);
179 }
180
181 void JavaScriptDebugServer::clearBreakpoints()
182 {
183     deleteAllValues(m_breakpoints);
184     m_breakpoints.clear();
185 }
186
187 void JavaScriptDebugServer::pauseOnNextStatement()
188 {
189     m_pauseOnNextStatement = true;
190 }
191
192 void JavaScriptDebugServer::resume()
193 {
194     m_paused = false;
195 }
196
197 void JavaScriptDebugServer::stepIntoStatement()
198 {
199     if (!m_paused)
200         return;
201
202     resume();
203
204     m_pauseOnNextStatement = true;
205 }
206
207 void JavaScriptDebugServer::stepOverStatement()
208 {
209     if (!m_paused)
210         return;
211
212     resume();
213
214     if (m_currentCallFrame)
215         m_pauseOnExecState = m_currentCallFrame->execState();
216     else
217         m_pauseOnExecState = 0;
218 }
219
220 void JavaScriptDebugServer::stepOutOfFunction()
221 {
222     if (!m_paused)
223         return;
224
225     resume();
226
227     if (m_currentCallFrame && m_currentCallFrame->caller())
228         m_pauseOnExecState = m_currentCallFrame->caller()->execState();
229     else
230         m_pauseOnExecState = 0;
231 }
232
233 JavaScriptCallFrame* JavaScriptDebugServer::currentCallFrame()
234 {
235     if (!m_paused)
236         return 0;
237     return m_currentCallFrame.get();
238 }
239
240 static void dispatchDidParseSource(const ListenerSet& listeners, const UString& source, int startingLineNumber, const UString& sourceURL, int sourceID)
241 {
242     Vector<JavaScriptDebugListener*> copy;
243     copyToVector(listeners, copy);
244     for (size_t i = 0; i < copy.size(); ++i)
245         copy[i]->didParseSource(source, startingLineNumber, sourceURL, sourceID);
246 }
247
248 static void dispatchFailedToParseSource(const ListenerSet& listeners, const UString& source, int startingLineNumber, const UString& sourceURL, int errorLine, const UString& errorMessage)
249 {
250     Vector<JavaScriptDebugListener*> copy;
251     copyToVector(listeners, copy);
252     for (size_t i = 0; i < copy.size(); ++i)
253         copy[i]->failedToParseSource(source, startingLineNumber, sourceURL, errorLine, errorMessage);
254 }
255
256 static Page* toPage(ExecState* exec)
257 {
258     ASSERT_ARG(exec, exec);
259
260     JSDOMWindow* window = asJSDOMWindow(exec->dynamicGlobalObject());
261     ASSERT(window);
262
263     return window->impl()->frame()->page();
264 }
265
266 bool JavaScriptDebugServer::sourceParsed(ExecState* exec, int sourceID, const UString& sourceURL, const UString& source, int startingLineNumber, int errorLine, const UString& errorMessage)
267 {
268     if (m_callingListeners)
269         return true;
270
271     Page* page = toPage(exec);
272     if (!page)
273         return true;
274
275     m_callingListeners = true;
276
277     ASSERT(hasListeners());
278
279     bool isError = errorLine != -1;
280
281     if (!m_listeners.isEmpty()) {
282         if (isError)
283             dispatchFailedToParseSource(m_listeners, source, startingLineNumber, sourceURL, errorLine, errorMessage);
284         else
285             dispatchDidParseSource(m_listeners, source, startingLineNumber, sourceURL, sourceID);
286     }
287
288     if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
289         ASSERT(!pageListeners->isEmpty());
290         if (isError)
291             dispatchFailedToParseSource(*pageListeners, source, startingLineNumber, sourceURL, errorLine, errorMessage);
292         else
293             dispatchDidParseSource(*pageListeners, source, startingLineNumber, sourceURL, sourceID);
294     }
295
296     m_callingListeners = false;
297     return true;
298 }
299
300 static void dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptDebugServer::JavaScriptExecutionCallback callback)
301 {
302     Vector<JavaScriptDebugListener*> copy;
303     copyToVector(listeners, copy);
304     for (size_t i = 0; i < copy.size(); ++i)
305         (copy[i]->*callback)();
306 }
307
308 void JavaScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, ExecState* exec)
309 {
310     if (m_callingListeners)
311         return;
312
313     Page* page = toPage(exec);
314     if (!page)
315         return;
316
317     m_callingListeners = true;
318
319     ASSERT(hasListeners());
320
321     WebCore::dispatchFunctionToListeners(m_listeners, callback);
322     if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
323         ASSERT(!pageListeners->isEmpty());
324         WebCore::dispatchFunctionToListeners(*pageListeners, callback);
325     }
326
327     m_callingListeners = false;
328 }
329
330 void JavaScriptDebugServer::setJavaScriptPaused(const PageGroup& pageGroup, bool paused)
331 {
332     setMainThreadCallbacksPaused(paused);
333
334     const HashSet<Page*>& pages = pageGroup.pages();
335
336     HashSet<Page*>::const_iterator end = pages.end();
337     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it)
338         setJavaScriptPaused(*it, false);
339 }
340
341 void JavaScriptDebugServer::setJavaScriptPaused(Page* page, bool paused)
342 {
343     ASSERT_ARG(page, page);
344
345     page->setDefersLoading(paused);
346
347     for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext())
348         setJavaScriptPaused(frame, paused);
349 }
350
351 void JavaScriptDebugServer::setJavaScriptPaused(Frame* frame, bool paused)
352 {
353     ASSERT_ARG(frame, frame);
354
355     if (!frame->scriptProxy()->isEnabled())
356         return;
357
358     frame->scriptProxy()->setPaused(paused);
359
360     if (JSDOMWindow* window = toJSDOMWindow(frame)) {
361         if (paused)
362             m_pausedTimeouts.set(frame, window->pauseTimeouts());
363         else
364             window->resumeTimeouts(m_pausedTimeouts.take(frame));
365     }
366
367     setJavaScriptPaused(frame->view(), paused);
368 }
369
370 void JavaScriptDebugServer::setJavaScriptPaused(FrameView* view, bool paused)
371 {
372 #if !PLATFORM(MAC)
373     if (!view)
374         return;
375
376     HashSet<Widget*>* children = static_cast<ScrollView*>(view)->children();
377     ASSERT(children);
378
379     HashSet<Widget*>::iterator end = children->end();
380     for (HashSet<Widget*>::iterator it = children->begin(); it != end; ++it) {
381         Widget* widget = *it;
382         if (!widget->isPluginView())
383             continue;
384         static_cast<PluginView*>(widget)->setJavaScriptPaused(paused);
385     }
386 #endif
387 }
388
389 void JavaScriptDebugServer::pauseIfNeeded(ExecState* exec, int sourceID, int lineNumber)
390 {
391     if (m_paused)
392         return;
393
394     Page* page = toPage(exec);
395     if (!page || !hasListenersInterestedInPage(page))
396         return;
397
398     bool pauseNow = m_pauseOnNextStatement;
399     if (!pauseNow && m_pauseOnExecState)
400         pauseNow = (m_pauseOnExecState == exec);
401     if (!pauseNow && lineNumber > 0)
402         pauseNow = hasBreakpoint(sourceID, lineNumber);
403     if (!pauseNow)
404         return;
405
406     m_pauseOnExecState = 0;
407     m_pauseOnNextStatement = false;
408     m_paused = true;
409
410     dispatchFunctionToListeners(&JavaScriptDebugListener::didPause, exec);
411
412     setJavaScriptPaused(page->group(), true);
413
414     EventLoop loop;
415     while (m_paused && !loop.ended())
416         loop.cycle();
417
418     setJavaScriptPaused(page->group(), false);
419
420     m_paused = false;
421 }
422
423 bool JavaScriptDebugServer::callEvent(ExecState* exec, int sourceID, int lineNumber, JSObject*, const List&)
424 {
425     if (m_paused)
426         return true;
427
428     if (m_currentCallFrame && m_currentCallFrame->execState() != exec->callingExecState()) {
429         m_currentCallFrame->invalidate();
430         m_currentCallFrame = 0;
431     }
432
433     m_currentCallFrame = JavaScriptCallFrame::create(exec, m_currentCallFrame, sourceID, lineNumber);
434     pauseIfNeeded(exec, sourceID, lineNumber);
435     return true;
436 }
437
438 bool JavaScriptDebugServer::atStatement(ExecState* exec, int sourceID, int firstLine, int)
439 {
440     if (m_paused)
441         return true;
442     if (m_currentCallFrame)
443         m_currentCallFrame->setLine(firstLine);
444     else
445         m_currentCallFrame = JavaScriptCallFrame::create(exec, 0, sourceID, firstLine);
446     pauseIfNeeded(exec, sourceID, firstLine);
447     return true;
448 }
449
450 bool JavaScriptDebugServer::returnEvent(ExecState* exec, int sourceID, int lineNumber, JSObject*)
451 {
452     if (m_paused)
453         return true;
454     m_currentCallFrame->invalidate();
455     m_currentCallFrame = m_currentCallFrame->caller();
456     pauseIfNeeded(exec, sourceID, lineNumber);
457     return true;
458 }
459
460 bool JavaScriptDebugServer::exception(ExecState* exec, int sourceID, int lineNumber, JSValue*)
461 {
462     if (m_paused)
463         return true;
464     if (m_currentCallFrame)
465         m_currentCallFrame->setLine(lineNumber);
466     else
467         m_currentCallFrame = JavaScriptCallFrame::create(exec, 0, sourceID, lineNumber);
468     // FIXME: ideally this should only pause if a "pause on exception" flag is set,
469     // not m_pauseOnNextStatement, etc.
470     pauseIfNeeded(exec, sourceID, lineNumber);
471     return true;
472 }
473
474 } // namespace WebCore