ScriptDebugServer.cpp revision 2fc2651226baac27029e38c9d6ef883fa32084db
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/SourceProvider.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 58using namespace JSC; 59 60namespace WebCore { 61 62ScriptDebugServer& ScriptDebugServer::shared() 63{ 64 DEFINE_STATIC_LOCAL(ScriptDebugServer, server, ()); 65 return server; 66} 67 68ScriptDebugServer::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 81ScriptDebugServer::~ScriptDebugServer() 82{ 83 deleteAllValues(m_pageListenersMap); 84} 85 86void 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 101void 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 120bool ScriptDebugServer::hasListenersInterestedInPage(Page* page) 121{ 122 ASSERT_ARG(page, page); 123 124 return m_pageListenersMap.contains(page); 125} 126 127String ScriptDebugServer::setBreakpoint(const String& sourceID, const ScriptBreakpoint& scriptBreakpoint, int* actualLineNumber, int* actualColumnNumber) 128{ 129 intptr_t sourceIDValue = sourceID.toIntPtr(); 130 if (!sourceIDValue) 131 return ""; 132 SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue); 133 if (it == m_sourceIdToBreakpoints.end()) 134 it = m_sourceIdToBreakpoints.set(sourceIDValue, LineToBreakpointMap()).first; 135 if (it->second.contains(scriptBreakpoint.lineNumber + 1)) 136 return ""; 137 it->second.set(scriptBreakpoint.lineNumber + 1, scriptBreakpoint); 138 *actualLineNumber = scriptBreakpoint.lineNumber; 139 // FIXME(WK53003): implement setting breakpoints by line:column. 140 *actualColumnNumber = 0; 141 return makeString(sourceID, ":", String::number(scriptBreakpoint.lineNumber)); 142} 143 144void ScriptDebugServer::removeBreakpoint(const String& breakpointId) 145{ 146 Vector<String> tokens; 147 breakpointId.split(":", tokens); 148 if (tokens.size() != 2) 149 return; 150 bool success; 151 intptr_t sourceIDValue = tokens[0].toIntPtr(&success); 152 if (!success) 153 return; 154 unsigned lineNumber = tokens[1].toUInt(&success); 155 if (!success) 156 return; 157 SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue); 158 if (it != m_sourceIdToBreakpoints.end()) 159 it->second.remove(lineNumber + 1); 160} 161 162bool ScriptDebugServer::hasBreakpoint(intptr_t sourceID, unsigned lineNumber) const 163{ 164 if (!m_breakpointsActivated) 165 return false; 166 167 SourceIdToBreakpointsMap::const_iterator it = m_sourceIdToBreakpoints.find(sourceID); 168 if (it == m_sourceIdToBreakpoints.end()) 169 return false; 170 LineToBreakpointMap::const_iterator breakIt = it->second.find(lineNumber); 171 if (breakIt == it->second.end() || !breakIt->second.enabled) 172 return false; 173 174 // An empty condition counts as no condition which is equivalent to "true". 175 if (breakIt->second.condition.isEmpty()) 176 return true; 177 178 JSValue exception; 179 JSValue result = m_currentCallFrame->evaluate(stringToUString(breakIt->second.condition), exception); 180 if (exception) { 181 // An erroneous condition counts as "false". 182 return false; 183 } 184 return result.toBoolean(m_currentCallFrame->scopeChain()->globalObject->globalExec()); 185} 186 187void ScriptDebugServer::clearBreakpoints() 188{ 189 m_sourceIdToBreakpoints.clear(); 190} 191 192void ScriptDebugServer::setBreakpointsActivated(bool activated) 193{ 194 m_breakpointsActivated = activated; 195} 196 197void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause) 198{ 199 m_pauseOnExceptionsState = pause; 200} 201 202void ScriptDebugServer::setPauseOnNextStatement(bool pause) 203{ 204 m_pauseOnNextStatement = pause; 205} 206 207void ScriptDebugServer::breakProgram() 208{ 209 // FIXME(WK43332): implement this. 210} 211 212void ScriptDebugServer::continueProgram() 213{ 214 if (!m_paused) 215 return; 216 217 m_pauseOnNextStatement = false; 218 m_doneProcessingDebuggerEvents = true; 219} 220 221void ScriptDebugServer::stepIntoStatement() 222{ 223 if (!m_paused) 224 return; 225 226 m_pauseOnNextStatement = true; 227 m_doneProcessingDebuggerEvents = true; 228} 229 230void ScriptDebugServer::stepOverStatement() 231{ 232 if (!m_paused) 233 return; 234 235 m_pauseOnCallFrame = m_currentCallFrame.get(); 236 m_doneProcessingDebuggerEvents = true; 237} 238 239void ScriptDebugServer::stepOutOfFunction() 240{ 241 if (!m_paused) 242 return; 243 244 m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0; 245 m_doneProcessingDebuggerEvents = true; 246} 247 248bool ScriptDebugServer::editScriptSource(const String&, const String&, String&) 249{ 250 // FIXME(40300): implement this. 251 return false; 252} 253 254JavaScriptCallFrame* ScriptDebugServer::currentCallFrame() 255{ 256 if (!m_paused) 257 return 0; 258 return m_currentCallFrame.get(); 259} 260 261void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener) 262{ 263 ASSERT(m_paused); 264 ScriptState* state = m_currentCallFrame->scopeChain()->globalObject->globalExec(); 265 listener->didPause(state); 266} 267 268void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener) 269{ 270 listener->didContinue(); 271} 272 273void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, ScriptWorldType worldType) 274{ 275 String sourceID = ustringToString(JSC::UString::number(sourceProvider->asID())); 276 String url = ustringToString(sourceProvider->url()); 277 String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length())); 278 int lineOffset = sourceProvider->startPosition().m_line.convertAsZeroBasedInt(); 279 int columnOffset = sourceProvider->startPosition().m_column.convertAsZeroBasedInt(); 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, lineOffset, columnOffset, worldType); 285} 286 287void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage) 288{ 289 String url = ustringToString(sourceProvider->url()); 290 String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length())); 291 int firstLine = sourceProvider->startPosition().m_line.oneBasedInt(); 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 299static 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 308static ScriptWorldType currentWorldType(ExecState* exec) 309{ 310 if (currentWorld(exec) == mainThreadNormalWorld()) 311 return MAIN_WORLD; 312 return EXTENSIONS_WORLD; 313} 314 315void 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 328void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, 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, sourceProvider, errorLine, ustringToString(errorMessage)); 347 else 348 dispatchDidParseSource(*pageListeners, sourceProvider, worldType); 349 } 350 351 m_callingListeners = false; 352} 353 354void 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 362void 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 377void 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 388void 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 398void 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 416void 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 433void ScriptDebugServer::createCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 434{ 435 TextPosition1 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber), WTF::OneBasedNumber::base()); 436 m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, textPosition); 437 pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); 438} 439 440void ScriptDebugServer::updateCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 441{ 442 ASSERT(m_currentCallFrame); 443 if (!m_currentCallFrame) 444 return; 445 446 TextPosition1 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber), WTF::OneBasedNumber::base()); 447 m_currentCallFrame->update(debuggerCallFrame, sourceID, textPosition); 448 pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); 449} 450 451void ScriptDebugServer::pauseIfNeeded(Page* page) 452{ 453 if (m_paused) 454 return; 455 456 if (!page || !hasListenersInterestedInPage(page)) 457 return; 458 459 bool pauseNow = m_pauseOnNextStatement; 460 pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame); 461 pauseNow |= (m_currentCallFrame->line() > 0 && hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->line())); 462 if (!pauseNow) 463 return; 464 465 m_pauseOnCallFrame = 0; 466 m_pauseOnNextStatement = false; 467 m_paused = true; 468 m_pausedPage = page; 469 470 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, page); 471 472 setJavaScriptPaused(page->group(), true); 473 474 TimerBase::fireTimersInNestedEventLoop(); 475 476 EventLoop loop; 477 m_doneProcessingDebuggerEvents = false; 478 while (!m_doneProcessingDebuggerEvents && !loop.ended()) 479 loop.cycle(); 480 481 setJavaScriptPaused(page->group(), false); 482 483 m_paused = false; 484 m_pausedPage = 0; 485 486 dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, page); 487} 488 489void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 490{ 491 if (!m_paused) 492 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 493} 494 495void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 496{ 497 if (!m_paused) 498 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 499} 500 501void ScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 502{ 503 if (m_paused) 504 return; 505 506 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 507 508 // detach may have been called during pauseIfNeeded 509 if (!m_currentCallFrame) 510 return; 511 512 // Treat stepping over a return statement like stepping out. 513 if (m_currentCallFrame == m_pauseOnCallFrame) 514 m_pauseOnCallFrame = m_currentCallFrame->caller(); 515 m_currentCallFrame = m_currentCallFrame->caller(); 516} 517 518void ScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler) 519{ 520 if (m_paused) 521 return; 522 523 if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler)) 524 m_pauseOnNextStatement = true; 525 526 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 527} 528 529void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 530{ 531 if (!m_paused) 532 createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 533} 534 535void ScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 536{ 537 if (m_paused) 538 return; 539 540 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 541 542 // Treat stepping over the end of a program like stepping out. 543 if (m_currentCallFrame == m_pauseOnCallFrame) 544 m_pauseOnCallFrame = m_currentCallFrame->caller(); 545 m_currentCallFrame = m_currentCallFrame->caller(); 546} 547 548void ScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) 549{ 550 if (m_paused) 551 return; 552 553 m_pauseOnNextStatement = true; 554 updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber); 555} 556 557void ScriptDebugServer::recompileAllJSFunctionsSoon() 558{ 559 m_recompileTimer.startOneShot(0); 560} 561 562void ScriptDebugServer::recompileAllJSFunctions(Timer<ScriptDebugServer>*) 563{ 564 JSLock lock(SilenceAssertionsOnly); 565 // If JavaScript stack is not empty postpone recompilation. 566 if (JSDOMWindow::commonJSGlobalData()->dynamicGlobalObject) 567 recompileAllJSFunctionsSoon(); 568 else 569 Debugger::recompileAllJSFunctions(JSDOMWindow::commonJSGlobalData()); 570} 571 572void ScriptDebugServer::didAddListener(Page* page) 573{ 574 recompileAllJSFunctionsSoon(); 575 page->setDebugger(this); 576} 577 578void ScriptDebugServer::didRemoveListener(Page* page) 579{ 580 if (hasListenersInterestedInPage(page)) 581 return; 582 583 if (m_pausedPage == page) 584 m_doneProcessingDebuggerEvents = true; 585 586 recompileAllJSFunctionsSoon(); 587 page->setDebugger(0); 588} 589 590} // namespace WebCore 591 592#endif // ENABLE(JAVASCRIPT_DEBUGGER) 593