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