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