2010-12-22 Sheriff Bot <webkit.review.bot@gmail.com>
[WebKit-https.git] / WebCore / bindings / js / ScriptDebugServer.cpp
1 /*
2  * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
3  * Copyright (C) 2010 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "ScriptDebugServer.h"
32
33 #if ENABLE(JAVASCRIPT_DEBUGGER)
34
35 #include "DOMWindow.h"
36 #include "EventLoop.h"
37 #include "Frame.h"
38 #include "FrameTree.h"
39 #include "FrameView.h"
40 #include "JSDOMWindowCustom.h"
41 #include "JavaScriptCallFrame.h"
42 #include "Page.h"
43 #include "PageGroup.h"
44 #include "PluginView.h"
45 #include "ScriptBreakpoint.h"
46 #include "ScriptController.h"
47 #include "ScriptDebugListener.h"
48 #include "ScrollView.h"
49 #include "Widget.h"
50 #include <debugger/DebuggerCallFrame.h>
51 #include <parser/SourceCode.h>
52 #include <runtime/JSLock.h>
53 #include <wtf/MainThread.h>
54 #include <wtf/StdLibExtras.h>
55 #include <wtf/UnusedParam.h>
56
57 using namespace JSC;
58
59 namespace WebCore {
60
61 ScriptDebugServer& ScriptDebugServer::shared()
62 {
63     DEFINE_STATIC_LOCAL(ScriptDebugServer, server, ());
64     return server;
65 }
66
67 ScriptDebugServer::ScriptDebugServer()
68     : m_callingListeners(false)
69     , m_pauseOnExceptionsState(DontPauseOnExceptions)
70     , m_pauseOnNextStatement(false)
71     , m_paused(false)
72     , m_pausedPage(0)
73     , m_doneProcessingDebuggerEvents(true)
74     , m_breakpointsActivated(true)
75     , m_pauseOnCallFrame(0)
76     , m_recompileTimer(this, &ScriptDebugServer::recompileAllJSFunctions)
77 {
78 }
79
80 ScriptDebugServer::~ScriptDebugServer()
81 {
82     deleteAllValues(m_pageListenersMap);
83 }
84
85 void ScriptDebugServer::addListener(ScriptDebugListener* listener, Page* page)
86 {
87     ASSERT_ARG(listener, listener);
88     ASSERT_ARG(page, page);
89
90     pair<PageListenersMap::iterator, bool> result = m_pageListenersMap.add(page, 0);
91     if (result.second)
92         result.first->second = new ListenerSet;
93
94     ListenerSet* listeners = result.first->second;
95     listeners->add(listener);
96
97     didAddListener(page);
98 }
99
100 void ScriptDebugServer::removeListener(ScriptDebugListener* listener, Page* page)
101 {
102     ASSERT_ARG(listener, listener);
103     ASSERT_ARG(page, page);
104
105     PageListenersMap::iterator it = m_pageListenersMap.find(page);
106     if (it == m_pageListenersMap.end())
107         return;
108
109     ListenerSet* listeners = it->second;
110     listeners->remove(listener);
111     if (listeners->isEmpty()) {
112         m_pageListenersMap.remove(it);
113         delete listeners;
114     }
115
116     didRemoveListener(page);
117 }
118
119 void ScriptDebugServer::pageCreated(Page* page)
120 {
121     ASSERT_ARG(page, page);
122
123     if (!hasListenersInterestedInPage(page))
124         return;
125     page->setDebugger(this);
126 }
127
128 bool ScriptDebugServer::isDebuggerAlwaysEnabled()
129 {
130     return false;
131 }
132
133 bool ScriptDebugServer::hasListenersInterestedInPage(Page* page)
134 {
135     ASSERT_ARG(page, page);
136
137     return m_pageListenersMap.contains(page);
138 }
139
140 bool ScriptDebugServer::setBreakpoint(const String& sourceID, ScriptBreakpoint breakpoint, unsigned lineNumber, unsigned* actualLineNumber)
141 {
142     intptr_t sourceIDValue = sourceID.toIntPtr();
143     if (!sourceIDValue)
144         return false;
145     BreakpointsMap::iterator it = m_breakpoints.find(sourceIDValue);
146     if (it == m_breakpoints.end())
147         it = m_breakpoints.set(sourceIDValue, SourceBreakpoints()).first;
148     it->second.set(lineNumber, breakpoint);
149     *actualLineNumber = lineNumber;
150     return true;
151 }
152
153 void ScriptDebugServer::removeBreakpoint(const String& sourceID, unsigned lineNumber)
154 {
155     intptr_t sourceIDValue = sourceID.toIntPtr();
156     if (!sourceIDValue)
157         return;
158     BreakpointsMap::iterator it = m_breakpoints.find(sourceIDValue);
159     if (it != m_breakpoints.end())
160         it->second.remove(lineNumber);
161 }
162
163 bool ScriptDebugServer::hasBreakpoint(intptr_t sourceID, unsigned lineNumber) const
164 {
165     if (!m_breakpointsActivated)
166         return false;
167
168     BreakpointsMap::const_iterator it = m_breakpoints.find(sourceID);
169     if (it == m_breakpoints.end())
170         return false;
171     SourceBreakpoints::const_iterator breakIt = it->second.find(lineNumber);
172     if (breakIt == it->second.end() || !breakIt->second.enabled)
173         return false;
174
175     // An empty condition counts as no condition which is equivalent to "true".
176     if (breakIt->second.condition.isEmpty())
177         return true;
178
179     JSValue exception;
180     JSValue result = m_currentCallFrame->evaluate(stringToUString(breakIt->second.condition), exception);
181     if (exception) {
182         // An erroneous condition counts as "false".
183         return false;
184     }
185     return result.toBoolean(m_currentCallFrame->scopeChain()->globalObject->globalExec());
186 }
187
188 void ScriptDebugServer::clearBreakpoints()
189 {
190     m_breakpoints.clear();
191 }
192
193 void ScriptDebugServer::setBreakpointsActivated(bool activated)
194 {
195     m_breakpointsActivated = activated;
196 }
197
198 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause)
199 {
200     m_pauseOnExceptionsState = pause;
201 }
202
203 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
204 {
205     m_pauseOnNextStatement = pause;
206 }
207
208 void ScriptDebugServer::breakProgram()
209 {
210     // FIXME(WK43332): implement this.
211 }
212
213 void ScriptDebugServer::continueProgram()
214 {
215     if (!m_paused)
216         return;
217
218     m_pauseOnNextStatement = false;
219     m_doneProcessingDebuggerEvents = true;
220 }
221
222 void ScriptDebugServer::stepIntoStatement()
223 {
224     if (!m_paused)
225         return;
226
227     m_pauseOnNextStatement = true;
228     m_doneProcessingDebuggerEvents = true;
229 }
230
231 void ScriptDebugServer::stepOverStatement()
232 {
233     if (!m_paused)
234         return;
235
236     m_pauseOnCallFrame = m_currentCallFrame.get();
237     m_doneProcessingDebuggerEvents = true;
238 }
239
240 void ScriptDebugServer::stepOutOfFunction()
241 {
242     if (!m_paused)
243         return;
244
245     m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0;
246     m_doneProcessingDebuggerEvents = true;
247 }
248
249 bool ScriptDebugServer::editScriptSource(const String&, const String&, String&)
250 {
251     // FIXME(40300): implement this.
252     return false;
253 }
254
255 JavaScriptCallFrame* ScriptDebugServer::currentCallFrame()
256 {
257     if (!m_paused)
258         return 0;
259     return m_currentCallFrame.get();
260 }
261
262 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
263 {
264     ASSERT(m_paused);
265     ScriptState* state = m_currentCallFrame->scopeChain()->globalObject->globalExec();
266     listener->didPause(state);
267 }
268
269 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
270 {
271     listener->didContinue();
272 }
273
274 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, const JSC::SourceCode& source, ScriptWorldType worldType)
275 {
276     String sourceID = ustringToString(JSC::UString::number(source.provider()->asID()));
277     String url = ustringToString(source.provider()->url());
278     String data = ustringToString(JSC::UString(source.data(), source.length()));
279     int firstLine = source.firstLine();
280
281     Vector<ScriptDebugListener*> copy;
282     copyToVector(listeners, copy);
283     for (size_t i = 0; i < copy.size(); ++i)
284         copy[i]->didParseSource(sourceID, url, data, firstLine, worldType);
285 }
286
287 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, const SourceCode& source, int errorLine, const String& errorMessage)
288 {
289     String url = ustringToString(source.provider()->url());
290     String data = ustringToString(JSC::UString(source.data(), source.length()));
291     int firstLine = source.firstLine();
292
293     Vector<ScriptDebugListener*> copy;
294     copyToVector(listeners, copy);
295     for (size_t i = 0; i < copy.size(); ++i)
296         copy[i]->failedToParseSource(url, data, firstLine, errorLine, errorMessage);
297 }
298
299 static Page* toPage(JSGlobalObject* globalObject)
300 {
301     ASSERT_ARG(globalObject, globalObject);
302
303     JSDOMWindow* window = asJSDOMWindow(globalObject);
304     Frame* frame = window->impl()->frame();
305     return frame ? frame->page() : 0;
306 }
307
308 static ScriptWorldType currentWorldType(ExecState* exec)
309 {
310     if (currentWorld(exec) == mainThreadNormalWorld())
311         return MAIN_WORLD;
312     return EXTENSIONS_WORLD;
313 }
314
315 void ScriptDebugServer::detach(JSGlobalObject* globalObject)
316 {
317     // If we're detaching from the currently executing global object, manually tear down our
318     // stack, since we won't get further debugger callbacks to do so. Also, resume execution,
319     // since there's no point in staying paused once a window closes.
320     if (m_currentCallFrame && m_currentCallFrame->dynamicGlobalObject() == globalObject) {
321         m_currentCallFrame = 0;
322         m_pauseOnCallFrame = 0;
323         continueProgram();
324     }
325     Debugger::detach(globalObject);
326 }
327
328 void ScriptDebugServer::sourceParsed(ExecState* exec, const SourceCode& source, int errorLine, const UString& errorMessage)
329 {
330     if (m_callingListeners)
331         return;
332
333     Page* page = toPage(exec->lexicalGlobalObject());
334     if (!page)
335         return;
336
337     ScriptWorldType worldType = currentWorldType(exec);
338
339     m_callingListeners = true;
340
341     bool isError = errorLine != -1;
342
343     if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
344         ASSERT(!pageListeners->isEmpty());
345         if (isError)
346             dispatchFailedToParseSource(*pageListeners, source, errorLine, ustringToString(errorMessage));
347         else
348             dispatchDidParseSource(*pageListeners, source, worldType);
349     }
350
351     m_callingListeners = false;
352 }
353
354 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
355 {
356     Vector<ScriptDebugListener*> copy;
357     copyToVector(listeners, copy);
358     for (size_t i = 0; i < copy.size(); ++i)
359         (this->*callback)(copy[i]);
360 }
361
362 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, Page* page)
363 {
364     if (m_callingListeners)
365         return;
366
367     m_callingListeners = true;
368
369     if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
370         ASSERT(!pageListeners->isEmpty());
371         dispatchFunctionToListeners(*pageListeners, callback);
372     }
373
374     m_callingListeners = false;
375 }
376
377 void ScriptDebugServer::setJavaScriptPaused(const PageGroup& pageGroup, bool paused)
378 {
379     setMainThreadCallbacksPaused(paused);
380
381     const HashSet<Page*>& pages = pageGroup.pages();
382
383     HashSet<Page*>::const_iterator end = pages.end();
384     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it)
385         setJavaScriptPaused(*it, paused);
386 }
387
388 void ScriptDebugServer::setJavaScriptPaused(Page* page, bool paused)
389 {
390     ASSERT_ARG(page, page);
391
392     page->setDefersLoading(paused);
393
394     for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext())
395         setJavaScriptPaused(frame, paused);
396 }
397
398 void ScriptDebugServer::setJavaScriptPaused(Frame* frame, bool paused)
399 {
400     ASSERT_ARG(frame, frame);
401
402     if (!frame->script()->canExecuteScripts(NotAboutToExecuteScript))
403         return;
404
405     frame->script()->setPaused(paused);
406
407     Document* document = frame->document();
408     if (paused)
409         document->suspendActiveDOMObjects(ActiveDOMObject::JavaScriptDebuggerPaused);
410     else
411         document->resumeActiveDOMObjects();
412
413     setJavaScriptPaused(frame->view(), paused);
414 }
415
416 void ScriptDebugServer::setJavaScriptPaused(FrameView* view, bool paused)
417 {
418     if (!view)
419         return;
420
421     const HashSet<RefPtr<Widget> >* children = view->children();
422     ASSERT(children);
423
424     HashSet<RefPtr<Widget> >::const_iterator end = children->end();
425     for (HashSet<RefPtr<Widget> >::const_iterator it = children->begin(); it != end; ++it) {
426         Widget* widget = (*it).get();
427         if (!widget->isPluginView())
428             continue;
429         static_cast<PluginView*>(widget)->setJavaScriptPaused(paused);
430     }
431 }
432
433 void ScriptDebugServer::pauseIfNeeded(Page* page)
434 {
435     if (m_paused)
436         return;
437
438     if (!page || !hasListenersInterestedInPage(page))
439         return;
440
441     bool pauseNow = m_pauseOnNextStatement;
442     pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
443     pauseNow |= (m_currentCallFrame->line() > 0 && hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->line()));
444     if (!pauseNow)
445         return;
446
447     m_pauseOnCallFrame = 0;
448     m_pauseOnNextStatement = false;
449     m_paused = true;
450     m_pausedPage = page;
451
452     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, page);
453
454     setJavaScriptPaused(page->group(), true);
455
456     TimerBase::fireTimersInNestedEventLoop();
457
458     EventLoop loop;
459     m_doneProcessingDebuggerEvents = false;
460     while (!m_doneProcessingDebuggerEvents && !loop.ended())
461         loop.cycle();
462
463     setJavaScriptPaused(page->group(), false);
464
465     m_paused = false;
466     m_pausedPage = 0;
467
468     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, page);
469 }
470
471 void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
472 {
473     if (m_paused)
474         return;
475
476     m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, lineNumber);
477     pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
478 }
479
480 void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
481 {
482     if (m_paused)
483         return;
484
485     ASSERT(m_currentCallFrame);
486     if (!m_currentCallFrame)
487         return;
488
489     m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
490     pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
491 }
492
493 void ScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
494 {
495     if (m_paused)
496         return;
497
498     ASSERT(m_currentCallFrame);
499     if (!m_currentCallFrame)
500         return;
501
502     m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
503     pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
504
505     // detach may have been called during pauseIfNeeded
506     if (!m_currentCallFrame)
507         return;
508
509     // Treat stepping over a return statement like stepping out.
510     if (m_currentCallFrame == m_pauseOnCallFrame)
511         m_pauseOnCallFrame = m_currentCallFrame->caller();
512     m_currentCallFrame = m_currentCallFrame->caller();
513 }
514
515 void ScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler)
516 {
517     if (m_paused)
518         return;
519
520     ASSERT(m_currentCallFrame);
521     if (!m_currentCallFrame)
522         return;
523
524     if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler))
525         m_pauseOnNextStatement = true;
526
527     m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
528     pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
529 }
530
531 void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
532 {
533     if (m_paused)
534         return;
535
536     m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, lineNumber);
537     pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
538 }
539
540 void ScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
541 {
542     if (m_paused)
543         return;
544
545     ASSERT(m_currentCallFrame);
546     if (!m_currentCallFrame)
547         return;
548
549     m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
550     pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
551
552     // Treat stepping over the end of a program like stepping out.
553     if (m_currentCallFrame == m_pauseOnCallFrame)
554         m_pauseOnCallFrame = m_currentCallFrame->caller();
555     m_currentCallFrame = m_currentCallFrame->caller();
556 }
557
558 void ScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
559 {
560     if (m_paused)
561         return;
562
563     ASSERT(m_currentCallFrame);
564     if (!m_currentCallFrame)
565         return;
566
567     m_pauseOnNextStatement = true;
568     m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
569     pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
570 }
571
572 void ScriptDebugServer::recompileAllJSFunctionsSoon()
573 {
574     m_recompileTimer.startOneShot(0);
575 }
576
577 void ScriptDebugServer::recompileAllJSFunctions(Timer<ScriptDebugServer>*)
578 {
579     JSLock lock(SilenceAssertionsOnly);
580     // If JavaScript stack is not empty postpone recompilation.
581     if (JSDOMWindow::commonJSGlobalData()->dynamicGlobalObject)
582         recompileAllJSFunctionsSoon();
583     else
584         Debugger::recompileAllJSFunctions(JSDOMWindow::commonJSGlobalData());
585 }
586
587 void ScriptDebugServer::didAddListener(Page* page)
588 {
589     recompileAllJSFunctionsSoon();
590     page->setDebugger(this);
591 }
592
593 void ScriptDebugServer::didRemoveListener(Page* page)
594 {
595     if (hasListenersInterestedInPage(page))
596         return;
597
598     if (m_pausedPage == page)
599         m_doneProcessingDebuggerEvents = true;
600
601     recompileAllJSFunctionsSoon();
602     page->setDebugger(0);
603 }
604
605 } // namespace WebCore
606
607 #endif // ENABLE(JAVASCRIPT_DEBUGGER)