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 54 using namespace JSC; 55 56 namespace WebCore { 57 58 typedef JavaScriptDebugServer::ListenerSet ListenerSet; 59 60 inline const UString& JavaScriptDebugServer::BreakpointInfo::condition() const 61 { 62 return m_condition; 63 } 64 65 void JavaScriptDebugServer::BreakpointInfo::setCondition(const UString& condition) 66 { 67 m_condition = condition; 68 } 69 70 JavaScriptDebugServer& JavaScriptDebugServer::shared() 71 { 72 DEFINE_STATIC_LOCAL(JavaScriptDebugServer, server, ()); 73 return server; 74 } 75 76 JavaScriptDebugServer::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 87 JavaScriptDebugServer::~JavaScriptDebugServer() 88 { 89 deleteAllValues(m_pageListenersMap); 90 deleteAllValues(m_breakpoints); 91 } 92 93 void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener) 94 { 95 ASSERT_ARG(listener, listener); 96 97 m_listeners.add(listener); 98 99 didAddListener(0); 100 } 101 102 void 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 113 void 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 128 void 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 149 void JavaScriptDebugServer::pageCreated(Page* page) 150 { 151 ASSERT_ARG(page, page); 152 153 if (!hasListenersInterestedInPage(page)) 154 return; 155 page->setDebugger(this); 156 } 157 158 bool 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 168 void 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 182 JavaScriptDebugServer::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 190 void 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 198 void JavaScriptDebugServer::updateBreakpointInfo(BreakpointInfo* info, const UString& condition) 199 { 200 info->setCondition(condition); 201 } 202 203 void 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 222 bool 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 241 void 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 252 void JavaScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause) 253 { 254 m_pauseOnExceptionsState = pause; 255 } 256 257 void JavaScriptDebugServer::pauseProgram() 258 { 259 m_pauseOnNextStatement = true; 260 } 261 262 void JavaScriptDebugServer::continueProgram() 263 { 264 if (!m_paused) 265 return; 266 267 m_pauseOnNextStatement = false; 268 m_doneProcessingDebuggerEvents = true; 269 } 270 271 void JavaScriptDebugServer::stepIntoStatement() 272 { 273 if (!m_paused) 274 return; 275 276 m_pauseOnNextStatement = true; 277 m_doneProcessingDebuggerEvents = true; 278 } 279 280 void JavaScriptDebugServer::stepOverStatement() 281 { 282 if (!m_paused) 283 return; 284 285 m_pauseOnCallFrame = m_currentCallFrame.get(); 286 m_doneProcessingDebuggerEvents = true; 287 } 288 289 void 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 298 JavaScriptCallFrame* JavaScriptDebugServer::currentCallFrame() 299 { 300 if (!m_paused) 301 return 0; 302 return m_currentCallFrame.get(); 303 } 304 305 static 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 313 static 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 321 static 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 330 void 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 343 void 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 374 static 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 382 void 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 401 void 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 412 void 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 422 void 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 442 void JavaScriptDebugServer::setJavaScriptPaused(FrameView*, bool) 443 { 444 } 445 446 #else 447 448 void 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 467 void 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 503 void 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 512 void 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 525 void 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 543 void 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 559 void 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 568 void 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 586 void 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 600 void JavaScriptDebugServer::recompileAllJSFunctionsSoon() 601 { 602 m_recompileTimer.startOneShot(0); 603 } 604 605 void JavaScriptDebugServer::recompileAllJSFunctions(Timer<JavaScriptDebugServer>*) 606 { 607 JSLock lock(SilenceAssertionsOnly); 608 Debugger::recompileAllJSFunctions(JSDOMWindow::commonJSGlobalData()); 609 } 610 611 void JavaScriptDebugServer::didAddListener(Page* page) 612 { 613 recompileAllJSFunctionsSoon(); 614 615 if (page) 616 page->setDebugger(this); 617 else 618 Page::setDebuggerForAllPages(this); 619 } 620 621 void 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 634 void JavaScriptDebugServer::didRemoveLastListener() 635 { 636 m_doneProcessingDebuggerEvents = true; 637 } 638 639 } // namespace WebCore 640 641 #endif // ENABLE(JAVASCRIPT_DEBUGGER) 642