1/*
2 * Copyright (C) 2008, 2009 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#if ENABLE(JAVASCRIPT_DEBUGGER) && USE(JSC)
33
34#include "DOMWindow.h"
35#include "EventLoop.h"
36#include "Frame.h"
37#include "FrameTree.h"
38#include "FrameView.h"
39#include "JSDOMWindowCustom.h"
40#include "JavaScriptCallFrame.h"
41#include "JavaScriptDebugListener.h"
42#include "Page.h"
43#include "PageGroup.h"
44#include "PluginView.h"
45#include "ScrollView.h"
46#include "Widget.h"
47#include "ScriptController.h"
48#include <debugger/DebuggerCallFrame.h>
49#include <runtime/JSLock.h>
50#include <wtf/MainThread.h>
51#include <wtf/StdLibExtras.h>
52#include <wtf/UnusedParam.h>
53
54using namespace JSC;
55
56namespace WebCore {
57
58typedef JavaScriptDebugServer::ListenerSet ListenerSet;
59
60inline const UString& JavaScriptDebugServer::BreakpointInfo::condition() const
61{
62    return m_condition;
63}
64
65void JavaScriptDebugServer::BreakpointInfo::setCondition(const UString& condition)
66{
67    m_condition = condition;
68}
69
70JavaScriptDebugServer& JavaScriptDebugServer::shared()
71{
72    DEFINE_STATIC_LOCAL(JavaScriptDebugServer, server, ());
73    return server;
74}
75
76JavaScriptDebugServer::JavaScriptDebugServer()
77    : m_callingListeners(false)
78    , m_pauseOnExceptionsState(DontPauseOnExceptions)
79    , m_pauseOnNextStatement(false)
80    , m_paused(false)
81    , m_doneProcessingDebuggerEvents(true)
82    , m_pauseOnCallFrame(0)
83    , m_recompileTimer(this, &JavaScriptDebugServer::recompileAllJSFunctions)
84{
85}
86
87JavaScriptDebugServer::~JavaScriptDebugServer()
88{
89    deleteAllValues(m_pageListenersMap);
90    deleteAllValues(m_breakpoints);
91}
92
93void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener)
94{
95    ASSERT_ARG(listener, listener);
96
97    m_listeners.add(listener);
98
99    didAddListener(0);
100}
101
102void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener)
103{
104    ASSERT_ARG(listener, listener);
105
106    m_listeners.remove(listener);
107
108    didRemoveListener(0);
109    if (!hasListeners())
110        didRemoveLastListener();
111}
112
113void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener, Page* page)
114{
115    ASSERT_ARG(listener, listener);
116    ASSERT_ARG(page, page);
117
118    pair<PageListenersMap::iterator, bool> result = m_pageListenersMap.add(page, 0);
119    if (result.second)
120        result.first->second = new ListenerSet;
121
122    ListenerSet* listeners = result.first->second;
123    listeners->add(listener);
124
125    didAddListener(page);
126}
127
128void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener, Page* page)
129{
130    ASSERT_ARG(listener, listener);
131    ASSERT_ARG(page, page);
132
133    PageListenersMap::iterator it = m_pageListenersMap.find(page);
134    if (it == m_pageListenersMap.end())
135        return;
136
137    ListenerSet* listeners = it->second;
138    listeners->remove(listener);
139    if (listeners->isEmpty()) {
140        m_pageListenersMap.remove(it);
141        delete listeners;
142    }
143
144    didRemoveListener(page);
145    if (!hasListeners())
146        didRemoveLastListener();
147}
148
149void JavaScriptDebugServer::pageCreated(Page* page)
150{
151    ASSERT_ARG(page, page);
152
153    if (!hasListenersInterestedInPage(page))
154        return;
155    page->setDebugger(this);
156}
157
158bool JavaScriptDebugServer::hasListenersInterestedInPage(Page* page)
159{
160    ASSERT_ARG(page, page);
161
162    if (hasGlobalListeners())
163        return true;
164
165    return m_pageListenersMap.contains(page);
166}
167
168void JavaScriptDebugServer::addBreakpoint(intptr_t sourceID, unsigned lineNumber, const UString& condition)
169{
170    LineToBreakpointInfoMap* sourceBreakpoints = m_breakpoints.get(sourceID);
171    if (!sourceBreakpoints) {
172        sourceBreakpoints = new LineToBreakpointInfoMap;
173        m_breakpoints.set(sourceID, sourceBreakpoints);
174    }
175    BreakpointInfo* info = sourceBreakpoints->get(lineNumber);
176    if (!info)
177        sourceBreakpoints->set(lineNumber, new BreakpointInfo(condition));
178    else
179        updateBreakpointInfo(info, condition);
180}
181
182JavaScriptDebugServer::BreakpointInfo* JavaScriptDebugServer::breakpointInfo(intptr_t sourceID, unsigned lineNumber) const
183{
184    LineToBreakpointInfoMap* sourceBreakpoints = m_breakpoints.get(sourceID);
185    if (!sourceBreakpoints)
186        return 0;
187    return sourceBreakpoints->get(lineNumber);
188}
189
190void JavaScriptDebugServer::updateBreakpoint(intptr_t sourceID, unsigned lineNumber, const UString& condition)
191{
192    BreakpointInfo* info = breakpointInfo(sourceID, lineNumber);
193    if (!info)
194        return;
195    updateBreakpointInfo(info, condition);
196}
197
198void JavaScriptDebugServer::updateBreakpointInfo(BreakpointInfo* info, const UString& condition)
199{
200    info->setCondition(condition);
201}
202
203void JavaScriptDebugServer::removeBreakpoint(intptr_t sourceID, unsigned lineNumber)
204{
205    LineToBreakpointInfoMap* sourceBreakpoints = m_breakpoints.get(sourceID);
206    if (!sourceBreakpoints)
207        return;
208
209    BreakpointInfo* info = sourceBreakpoints->get(lineNumber);
210    if (!info)
211        return;
212
213    sourceBreakpoints->remove(lineNumber);
214    delete info;
215
216    if (sourceBreakpoints->isEmpty()) {
217        m_breakpoints.remove(sourceID);
218        delete sourceBreakpoints;
219    }
220}
221
222bool JavaScriptDebugServer::hasBreakpoint(intptr_t sourceID, unsigned lineNumber) const
223{
224    BreakpointInfo* info = breakpointInfo(sourceID, lineNumber);
225    if (!info)
226        return false;
227
228    // An empty condition counts as no condition which is equivalent to "true".
229    if (info->condition().isEmpty())
230        return true;
231
232    JSValue exception;
233    JSValue result = m_currentCallFrame->evaluate(info->condition(), exception);
234    if (exception) {
235        // An erroneous condition counts as "false".
236        return false;
237    }
238    return result.toBoolean(m_currentCallFrame->scopeChain()->globalObject->globalExec());
239}
240
241void JavaScriptDebugServer::clearBreakpoints()
242{
243    BreakpointsMap::iterator end = m_breakpoints.end();
244    for (BreakpointsMap::iterator it = m_breakpoints.begin(); it != end; ++it) {
245        deleteAllValues(*(it->second));
246        it->second->clear();
247    }
248    deleteAllValues(m_breakpoints);
249    m_breakpoints.clear();
250}
251
252void JavaScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause)
253{
254    m_pauseOnExceptionsState = pause;
255}
256
257void JavaScriptDebugServer::pauseProgram()
258{
259    m_pauseOnNextStatement = true;
260}
261
262void JavaScriptDebugServer::continueProgram()
263{
264    if (!m_paused)
265        return;
266
267    m_pauseOnNextStatement = false;
268    m_doneProcessingDebuggerEvents = true;
269}
270
271void JavaScriptDebugServer::stepIntoStatement()
272{
273    if (!m_paused)
274        return;
275
276    m_pauseOnNextStatement = true;
277    m_doneProcessingDebuggerEvents = true;
278}
279
280void JavaScriptDebugServer::stepOverStatement()
281{
282    if (!m_paused)
283        return;
284
285    m_pauseOnCallFrame = m_currentCallFrame.get();
286    m_doneProcessingDebuggerEvents = true;
287}
288
289void JavaScriptDebugServer::stepOutOfFunction()
290{
291    if (!m_paused)
292        return;
293
294    m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0;
295    m_doneProcessingDebuggerEvents = true;
296}
297
298JavaScriptCallFrame* JavaScriptDebugServer::currentCallFrame()
299{
300    if (!m_paused)
301        return 0;
302    return m_currentCallFrame.get();
303}
304
305static void dispatchDidParseSource(const ListenerSet& listeners, ExecState* exec, const JSC::SourceCode& source)
306{
307    Vector<JavaScriptDebugListener*> copy;
308    copyToVector(listeners, copy);
309    for (size_t i = 0; i < copy.size(); ++i)
310        copy[i]->didParseSource(exec, source);
311}
312
313static void dispatchFailedToParseSource(const ListenerSet& listeners, ExecState* exec, const SourceCode& source, int errorLine, const String& errorMessage)
314{
315    Vector<JavaScriptDebugListener*> copy;
316    copyToVector(listeners, copy);
317    for (size_t i = 0; i < copy.size(); ++i)
318        copy[i]->failedToParseSource(exec, source, errorLine, errorMessage);
319}
320
321static Page* toPage(JSGlobalObject* globalObject)
322{
323    ASSERT_ARG(globalObject, globalObject);
324
325    JSDOMWindow* window = asJSDOMWindow(globalObject);
326    Frame* frame = window->impl()->frame();
327    return frame ? frame->page() : 0;
328}
329
330void JavaScriptDebugServer::detach(JSGlobalObject* globalObject)
331{
332    // If we're detaching from the currently executing global object, manually tear down our
333    // stack, since we won't get further debugger callbacks to do so. Also, resume execution,
334    // since there's no point in staying paused once a window closes.
335    if (m_currentCallFrame && m_currentCallFrame->dynamicGlobalObject() == globalObject) {
336        m_currentCallFrame = 0;
337        m_pauseOnCallFrame = 0;
338        continueProgram();
339    }
340    Debugger::detach(globalObject);
341}
342
343void JavaScriptDebugServer::sourceParsed(ExecState* exec, const SourceCode& source, int errorLine, const UString& errorMessage)
344{
345    if (m_callingListeners)
346        return;
347
348    Page* page = toPage(exec->dynamicGlobalObject());
349    if (!page)
350        return;
351
352    m_callingListeners = true;
353
354    bool isError = errorLine != -1;
355
356    if (hasGlobalListeners()) {
357        if (isError)
358            dispatchFailedToParseSource(m_listeners, exec, source, errorLine, errorMessage);
359        else
360            dispatchDidParseSource(m_listeners, exec, source);
361    }
362
363    if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
364        ASSERT(!pageListeners->isEmpty());
365        if (isError)
366            dispatchFailedToParseSource(*pageListeners, exec, source, errorLine, errorMessage);
367        else
368            dispatchDidParseSource(*pageListeners, exec, source);
369    }
370
371    m_callingListeners = false;
372}
373
374static void dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptDebugServer::JavaScriptExecutionCallback callback)
375{
376    Vector<JavaScriptDebugListener*> copy;
377    copyToVector(listeners, copy);
378    for (size_t i = 0; i < copy.size(); ++i)
379        (copy[i]->*callback)();
380}
381
382void JavaScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, Page* page)
383{
384    if (m_callingListeners)
385        return;
386
387    m_callingListeners = true;
388
389    ASSERT(hasListeners());
390
391    WebCore::dispatchFunctionToListeners(m_listeners, callback);
392
393    if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) {
394        ASSERT(!pageListeners->isEmpty());
395        WebCore::dispatchFunctionToListeners(*pageListeners, callback);
396    }
397
398    m_callingListeners = false;
399}
400
401void JavaScriptDebugServer::setJavaScriptPaused(const PageGroup& pageGroup, bool paused)
402{
403    setMainThreadCallbacksPaused(paused);
404
405    const HashSet<Page*>& pages = pageGroup.pages();
406
407    HashSet<Page*>::const_iterator end = pages.end();
408    for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it)
409        setJavaScriptPaused(*it, paused);
410}
411
412void JavaScriptDebugServer::setJavaScriptPaused(Page* page, bool paused)
413{
414    ASSERT_ARG(page, page);
415
416    page->setDefersLoading(paused);
417
418    for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext())
419        setJavaScriptPaused(frame, paused);
420}
421
422void JavaScriptDebugServer::setJavaScriptPaused(Frame* frame, bool paused)
423{
424    ASSERT_ARG(frame, frame);
425
426    if (!frame->script()->canExecuteScripts())
427        return;
428
429    frame->script()->setPaused(paused);
430
431    Document* document = frame->document();
432    if (paused)
433        document->suspendActiveDOMObjects();
434    else
435        document->resumeActiveDOMObjects();
436
437    setJavaScriptPaused(frame->view(), paused);
438}
439
440#if PLATFORM(MAC)
441
442void JavaScriptDebugServer::setJavaScriptPaused(FrameView*, bool)
443{
444}
445
446#else
447
448void JavaScriptDebugServer::setJavaScriptPaused(FrameView* view, bool paused)
449{
450    if (!view)
451        return;
452
453    const HashSet<RefPtr<Widget> >* children = view->children();
454    ASSERT(children);
455
456    HashSet<RefPtr<Widget> >::const_iterator end = children->end();
457    for (HashSet<RefPtr<Widget> >::const_iterator it = children->begin(); it != end; ++it) {
458        Widget* widget = (*it).get();
459        if (!widget->isPluginView())
460            continue;
461        static_cast<PluginView*>(widget)->setJavaScriptPaused(paused);
462    }
463}
464
465#endif
466
467void JavaScriptDebugServer::pauseIfNeeded(Page* page)
468{
469    if (m_paused)
470        return;
471
472    if (!page || !hasListenersInterestedInPage(page))
473        return;
474
475    bool pauseNow = m_pauseOnNextStatement;
476    pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
477    pauseNow |= (m_currentCallFrame->line() > 0 && hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->line()));
478    if (!pauseNow)
479        return;
480
481    m_pauseOnCallFrame = 0;
482    m_pauseOnNextStatement = false;
483    m_paused = true;
484
485    dispatchFunctionToListeners(&JavaScriptDebugListener::didPause, page);
486
487    setJavaScriptPaused(page->group(), true);
488
489    TimerBase::fireTimersInNestedEventLoop();
490
491    EventLoop loop;
492    m_doneProcessingDebuggerEvents = false;
493    while (!m_doneProcessingDebuggerEvents && !loop.ended())
494        loop.cycle();
495
496    setJavaScriptPaused(page->group(), false);
497
498    m_paused = false;
499
500    dispatchFunctionToListeners(&JavaScriptDebugListener::didContinue, page);
501}
502
503void JavaScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
504{
505    if (m_paused)
506        return;
507
508    m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, lineNumber);
509    pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
510}
511
512void JavaScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
513{
514    if (m_paused)
515        return;
516
517    ASSERT(m_currentCallFrame);
518    if (!m_currentCallFrame)
519        return;
520
521    m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
522    pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
523}
524
525void JavaScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
526{
527    if (m_paused)
528        return;
529
530    ASSERT(m_currentCallFrame);
531    if (!m_currentCallFrame)
532        return;
533
534    m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
535    pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
536
537    // Treat stepping over a return statement like stepping out.
538    if (m_currentCallFrame == m_pauseOnCallFrame)
539        m_pauseOnCallFrame = m_currentCallFrame->caller();
540    m_currentCallFrame = m_currentCallFrame->caller();
541}
542
543void JavaScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler)
544{
545    if (m_paused)
546        return;
547
548    ASSERT(m_currentCallFrame);
549    if (!m_currentCallFrame)
550        return;
551
552    if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler))
553        m_pauseOnNextStatement = true;
554
555    m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
556    pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
557}
558
559void JavaScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
560{
561    if (m_paused)
562        return;
563
564    m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, lineNumber);
565    pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
566}
567
568void JavaScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
569{
570    if (m_paused)
571        return;
572
573    ASSERT(m_currentCallFrame);
574    if (!m_currentCallFrame)
575        return;
576
577    m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
578    pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
579
580    // Treat stepping over the end of a program like stepping out.
581    if (m_currentCallFrame == m_pauseOnCallFrame)
582        m_pauseOnCallFrame = m_currentCallFrame->caller();
583    m_currentCallFrame = m_currentCallFrame->caller();
584}
585
586void JavaScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
587{
588    if (m_paused)
589        return;
590
591    ASSERT(m_currentCallFrame);
592    if (!m_currentCallFrame)
593        return;
594
595    m_pauseOnNextStatement = true;
596    m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber);
597    pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject()));
598}
599
600void JavaScriptDebugServer::recompileAllJSFunctionsSoon()
601{
602    m_recompileTimer.startOneShot(0);
603}
604
605void JavaScriptDebugServer::recompileAllJSFunctions(Timer<JavaScriptDebugServer>*)
606{
607    JSLock lock(SilenceAssertionsOnly);
608    Debugger::recompileAllJSFunctions(JSDOMWindow::commonJSGlobalData());
609}
610
611void JavaScriptDebugServer::didAddListener(Page* page)
612{
613    recompileAllJSFunctionsSoon();
614
615    if (page)
616        page->setDebugger(this);
617    else
618        Page::setDebuggerForAllPages(this);
619}
620
621void JavaScriptDebugServer::didRemoveListener(Page* page)
622{
623    if (hasGlobalListeners() || (page && hasListenersInterestedInPage(page)))
624        return;
625
626    recompileAllJSFunctionsSoon();
627
628    if (page)
629        page->setDebugger(0);
630    else
631        Page::setDebuggerForAllPages(0);
632}
633
634void JavaScriptDebugServer::didRemoveLastListener()
635{
636    m_doneProcessingDebuggerEvents = true;
637}
638
639} // namespace WebCore
640
641#endif // ENABLE(JAVASCRIPT_DEBUGGER)
642