Home | History | Annotate | Download | only in js
      1 /*
      2  * Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
      3  * Copyright (C) 2010-2011 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 "EventLoop.h"
     36 #include "Frame.h"
     37 #include "JavaScriptCallFrame.h"
     38 #include "ScriptBreakpoint.h"
     39 #include "ScriptController.h"
     40 #include "ScriptDebugListener.h"
     41 #include <debugger/DebuggerCallFrame.h>
     42 #include <parser/SourceProvider.h>
     43 #include <runtime/JSLock.h>
     44 #include <wtf/text/StringConcatenate.h>
     45 #include <wtf/MainThread.h>
     46 
     47 using namespace JSC;
     48 
     49 namespace WebCore {
     50 
     51 ScriptDebugServer::ScriptDebugServer()
     52     : m_callingListeners(false)
     53     , m_pauseOnExceptionsState(DontPauseOnExceptions)
     54     , m_pauseOnNextStatement(false)
     55     , m_paused(false)
     56     , m_doneProcessingDebuggerEvents(true)
     57     , m_breakpointsActivated(true)
     58     , m_pauseOnCallFrame(0)
     59     , m_recompileTimer(this, &ScriptDebugServer::recompileAllJSFunctions)
     60 {
     61 }
     62 
     63 ScriptDebugServer::~ScriptDebugServer()
     64 {
     65     deleteAllValues(m_pageListenersMap);
     66 }
     67 
     68 String ScriptDebugServer::setBreakpoint(const String& sourceID, const ScriptBreakpoint& scriptBreakpoint, int* actualLineNumber, int* actualColumnNumber)
     69 {
     70     intptr_t sourceIDValue = sourceID.toIntPtr();
     71     if (!sourceIDValue)
     72         return "";
     73     SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
     74     if (it == m_sourceIdToBreakpoints.end())
     75         it = m_sourceIdToBreakpoints.set(sourceIDValue, LineToBreakpointMap()).first;
     76     if (it->second.contains(scriptBreakpoint.lineNumber + 1))
     77         return "";
     78     it->second.set(scriptBreakpoint.lineNumber + 1, scriptBreakpoint);
     79     *actualLineNumber = scriptBreakpoint.lineNumber;
     80     // FIXME(WK53003): implement setting breakpoints by line:column.
     81     *actualColumnNumber = 0;
     82     return makeString(sourceID, ":", String::number(scriptBreakpoint.lineNumber));
     83 }
     84 
     85 void ScriptDebugServer::removeBreakpoint(const String& breakpointId)
     86 {
     87     Vector<String> tokens;
     88     breakpointId.split(":", tokens);
     89     if (tokens.size() != 2)
     90         return;
     91     bool success;
     92     intptr_t sourceIDValue = tokens[0].toIntPtr(&success);
     93     if (!success)
     94         return;
     95     unsigned lineNumber = tokens[1].toUInt(&success);
     96     if (!success)
     97         return;
     98     SourceIdToBreakpointsMap::iterator it = m_sourceIdToBreakpoints.find(sourceIDValue);
     99     if (it != m_sourceIdToBreakpoints.end())
    100         it->second.remove(lineNumber + 1);
    101 }
    102 
    103 bool ScriptDebugServer::hasBreakpoint(intptr_t sourceID, const TextPosition0& position) const
    104 {
    105     if (!m_breakpointsActivated)
    106         return false;
    107 
    108     SourceIdToBreakpointsMap::const_iterator it = m_sourceIdToBreakpoints.find(sourceID);
    109     if (it == m_sourceIdToBreakpoints.end())
    110         return false;
    111     int lineNumber = position.m_line.convertAsOneBasedInt();
    112     if (lineNumber <= 0)
    113         return false;
    114     LineToBreakpointMap::const_iterator breakIt = it->second.find(lineNumber);
    115     if (breakIt == it->second.end())
    116         return false;
    117 
    118     // An empty condition counts as no condition which is equivalent to "true".
    119     if (breakIt->second.condition.isEmpty())
    120         return true;
    121 
    122     JSValue exception;
    123     JSValue result = m_currentCallFrame->evaluate(stringToUString(breakIt->second.condition), exception);
    124     if (exception) {
    125         // An erroneous condition counts as "false".
    126         return false;
    127     }
    128     return result.toBoolean(m_currentCallFrame->scopeChain()->globalObject->globalExec());
    129 }
    130 
    131 void ScriptDebugServer::clearBreakpoints()
    132 {
    133     m_sourceIdToBreakpoints.clear();
    134 }
    135 
    136 void ScriptDebugServer::setBreakpointsActivated(bool activated)
    137 {
    138     m_breakpointsActivated = activated;
    139 }
    140 
    141 void ScriptDebugServer::setPauseOnExceptionsState(PauseOnExceptionsState pause)
    142 {
    143     m_pauseOnExceptionsState = pause;
    144 }
    145 
    146 void ScriptDebugServer::setPauseOnNextStatement(bool pause)
    147 {
    148     m_pauseOnNextStatement = pause;
    149 }
    150 
    151 void ScriptDebugServer::breakProgram()
    152 {
    153     // FIXME(WK43332): implement this.
    154 }
    155 
    156 void ScriptDebugServer::continueProgram()
    157 {
    158     if (!m_paused)
    159         return;
    160 
    161     m_pauseOnNextStatement = false;
    162     m_doneProcessingDebuggerEvents = true;
    163 }
    164 
    165 void ScriptDebugServer::stepIntoStatement()
    166 {
    167     if (!m_paused)
    168         return;
    169 
    170     m_pauseOnNextStatement = true;
    171     m_doneProcessingDebuggerEvents = true;
    172 }
    173 
    174 void ScriptDebugServer::stepOverStatement()
    175 {
    176     if (!m_paused)
    177         return;
    178 
    179     m_pauseOnCallFrame = m_currentCallFrame.get();
    180     m_doneProcessingDebuggerEvents = true;
    181 }
    182 
    183 void ScriptDebugServer::stepOutOfFunction()
    184 {
    185     if (!m_paused)
    186         return;
    187 
    188     m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0;
    189     m_doneProcessingDebuggerEvents = true;
    190 }
    191 
    192 bool ScriptDebugServer::editScriptSource(const String&, const String&, String*)
    193 {
    194     // FIXME(40300): implement this.
    195     return false;
    196 }
    197 
    198 JavaScriptCallFrame* ScriptDebugServer::currentCallFrame()
    199 {
    200     if (!m_paused)
    201         return 0;
    202     return m_currentCallFrame.get();
    203 }
    204 
    205 void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
    206 {
    207     ASSERT(m_paused);
    208     ScriptState* state = m_currentCallFrame->scopeChain()->globalObject->globalExec();
    209     listener->didPause(state);
    210 }
    211 
    212 void ScriptDebugServer::dispatchDidContinue(ScriptDebugListener* listener)
    213 {
    214     listener->didContinue();
    215 }
    216 
    217 void ScriptDebugServer::dispatchDidParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, bool isContentScript)
    218 {
    219     String sourceID = ustringToString(JSC::UString::number(sourceProvider->asID()));
    220     String url = ustringToString(sourceProvider->url());
    221     String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length()));
    222     int lineOffset = sourceProvider->startPosition().m_line.convertAsZeroBasedInt();
    223     int columnOffset = sourceProvider->startPosition().m_column.convertAsZeroBasedInt();
    224 
    225     Vector<ScriptDebugListener*> copy;
    226     copyToVector(listeners, copy);
    227     for (size_t i = 0; i < copy.size(); ++i)
    228         copy[i]->didParseSource(sourceID, url, data, lineOffset, columnOffset, isContentScript);
    229 }
    230 
    231 void ScriptDebugServer::dispatchFailedToParseSource(const ListenerSet& listeners, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
    232 {
    233     String url = ustringToString(sourceProvider->url());
    234     String data = ustringToString(JSC::UString(sourceProvider->data(), sourceProvider->length()));
    235     int firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
    236 
    237     Vector<ScriptDebugListener*> copy;
    238     copyToVector(listeners, copy);
    239     for (size_t i = 0; i < copy.size(); ++i)
    240         copy[i]->failedToParseSource(url, data, firstLine, errorLine, errorMessage);
    241 }
    242 
    243 static bool isContentScript(ExecState* exec)
    244 {
    245     return currentWorld(exec) != mainThreadNormalWorld();
    246 }
    247 
    248 void ScriptDebugServer::detach(JSGlobalObject* globalObject)
    249 {
    250     // If we're detaching from the currently executing global object, manually tear down our
    251     // stack, since we won't get further debugger callbacks to do so. Also, resume execution,
    252     // since there's no point in staying paused once a window closes.
    253     if (m_currentCallFrame && m_currentCallFrame->dynamicGlobalObject() == globalObject) {
    254         m_currentCallFrame = 0;
    255         m_pauseOnCallFrame = 0;
    256         continueProgram();
    257     }
    258     Debugger::detach(globalObject);
    259 }
    260 
    261 void ScriptDebugServer::sourceParsed(ExecState* exec, SourceProvider* sourceProvider, int errorLine, const UString& errorMessage)
    262 {
    263     if (m_callingListeners)
    264         return;
    265 
    266     ListenerSet* listeners = getListenersForGlobalObject(exec->lexicalGlobalObject());
    267     if (!listeners)
    268         return;
    269     ASSERT(!listeners->isEmpty());
    270 
    271     m_callingListeners = true;
    272 
    273     bool isError = errorLine != -1;
    274     if (isError)
    275         dispatchFailedToParseSource(*listeners, sourceProvider, errorLine, ustringToString(errorMessage));
    276     else
    277         dispatchDidParseSource(*listeners, sourceProvider, isContentScript(exec));
    278 
    279     m_callingListeners = false;
    280 }
    281 
    282 void ScriptDebugServer::dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptExecutionCallback callback)
    283 {
    284     Vector<ScriptDebugListener*> copy;
    285     copyToVector(listeners, copy);
    286     for (size_t i = 0; i < copy.size(); ++i)
    287         (this->*callback)(copy[i]);
    288 }
    289 
    290 void ScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, JSGlobalObject* globalObject)
    291 {
    292     if (m_callingListeners)
    293         return;
    294 
    295     m_callingListeners = true;
    296 
    297     if (ListenerSet* listeners = getListenersForGlobalObject(globalObject)) {
    298         ASSERT(!listeners->isEmpty());
    299         dispatchFunctionToListeners(*listeners, callback);
    300     }
    301 
    302     m_callingListeners = false;
    303 }
    304 
    305 void ScriptDebugServer::createCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    306 {
    307     TextPosition0 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber).convertToZeroBased(), WTF::ZeroBasedNumber::base());
    308     m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, textPosition);
    309     pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
    310 }
    311 
    312 void ScriptDebugServer::updateCallFrameAndPauseIfNeeded(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    313 {
    314     ASSERT(m_currentCallFrame);
    315     if (!m_currentCallFrame)
    316         return;
    317 
    318     TextPosition0 textPosition(WTF::OneBasedNumber::fromOneBasedInt(lineNumber).convertToZeroBased(), WTF::ZeroBasedNumber::base());
    319     m_currentCallFrame->update(debuggerCallFrame, sourceID, textPosition);
    320     pauseIfNeeded(debuggerCallFrame.dynamicGlobalObject());
    321 }
    322 
    323 void ScriptDebugServer::pauseIfNeeded(JSGlobalObject* dynamicGlobalObject)
    324 {
    325     if (m_paused)
    326         return;
    327 
    328     if (!getListenersForGlobalObject(dynamicGlobalObject))
    329         return;
    330 
    331     bool pauseNow = m_pauseOnNextStatement;
    332     pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
    333     pauseNow |= hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->position());
    334     if (!pauseNow)
    335         return;
    336 
    337     m_pauseOnCallFrame = 0;
    338     m_pauseOnNextStatement = false;
    339     m_paused = true;
    340 
    341     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidPause, dynamicGlobalObject);
    342     didPause(dynamicGlobalObject);
    343 
    344     TimerBase::fireTimersInNestedEventLoop();
    345 
    346     EventLoop loop;
    347     m_doneProcessingDebuggerEvents = false;
    348     while (!m_doneProcessingDebuggerEvents && !loop.ended())
    349         loop.cycle();
    350 
    351     didContinue(dynamicGlobalObject);
    352     dispatchFunctionToListeners(&ScriptDebugServer::dispatchDidContinue, dynamicGlobalObject);
    353 
    354     m_paused = false;
    355 }
    356 
    357 void ScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    358 {
    359     if (!m_paused)
    360         createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
    361 }
    362 
    363 void ScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    364 {
    365     if (!m_paused)
    366         updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
    367 }
    368 
    369 void ScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    370 {
    371     if (m_paused)
    372         return;
    373 
    374     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
    375 
    376     // detach may have been called during pauseIfNeeded
    377     if (!m_currentCallFrame)
    378         return;
    379 
    380     // Treat stepping over a return statement like stepping out.
    381     if (m_currentCallFrame == m_pauseOnCallFrame)
    382         m_pauseOnCallFrame = m_currentCallFrame->caller();
    383     m_currentCallFrame = m_currentCallFrame->caller();
    384 }
    385 
    386 void ScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber, bool hasHandler)
    387 {
    388     if (m_paused)
    389         return;
    390 
    391     if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler))
    392         m_pauseOnNextStatement = true;
    393 
    394     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
    395 }
    396 
    397 void ScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    398 {
    399     if (!m_paused)
    400         createCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
    401 }
    402 
    403 void ScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    404 {
    405     if (m_paused)
    406         return;
    407 
    408     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
    409 
    410     // Treat stepping over the end of a program like stepping out.
    411     if (m_currentCallFrame == m_pauseOnCallFrame)
    412         m_pauseOnCallFrame = m_currentCallFrame->caller();
    413     m_currentCallFrame = m_currentCallFrame->caller();
    414 }
    415 
    416 void ScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber)
    417 {
    418     if (m_paused)
    419         return;
    420 
    421     m_pauseOnNextStatement = true;
    422     updateCallFrameAndPauseIfNeeded(debuggerCallFrame, sourceID, lineNumber);
    423 }
    424 
    425 void ScriptDebugServer::recompileAllJSFunctionsSoon()
    426 {
    427     m_recompileTimer.startOneShot(0);
    428 }
    429 
    430 } // namespace WebCore
    431 
    432 #endif // ENABLE(JAVASCRIPT_DEBUGGER)
    433