Home | History | Annotate | Download | only in inspector
      1 /*
      2  * Copyright (C) 2011 Google 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  * 1.  Redistributions of source code must retain the above copyright
      8  *     notice, this list of conditions and the following disclaimer.
      9  * 2.  Redistributions in binary form must reproduce the above copyright
     10  *     notice, this list of conditions and the following disclaimer in the
     11  *     documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     23  */
     24 
     25 
     26 #include "config.h"
     27 #include "core/inspector/InspectorConsoleAgent.h"
     28 
     29 #include "bindings/v8/ScriptCallStackFactory.h"
     30 #include "bindings/v8/ScriptController.h"
     31 #include "bindings/v8/ScriptObject.h"
     32 #include "bindings/v8/ScriptProfiler.h"
     33 #include "core/frame/Frame.h"
     34 #include "core/inspector/ConsoleMessage.h"
     35 #include "core/inspector/InjectedScriptHost.h"
     36 #include "core/inspector/InjectedScriptManager.h"
     37 #include "core/inspector/InspectorState.h"
     38 #include "core/inspector/InspectorTimelineAgent.h"
     39 #include "core/inspector/InstrumentingAgents.h"
     40 #include "core/inspector/ScriptArguments.h"
     41 #include "core/inspector/ScriptCallFrame.h"
     42 #include "core/inspector/ScriptCallStack.h"
     43 #include "core/loader/DocumentLoader.h"
     44 #include "core/page/Page.h"
     45 #include "platform/network/ResourceError.h"
     46 #include "platform/network/ResourceResponse.h"
     47 #include "wtf/CurrentTime.h"
     48 #include "wtf/OwnPtr.h"
     49 #include "wtf/PassOwnPtr.h"
     50 #include "wtf/text/StringBuilder.h"
     51 #include "wtf/text/WTFString.h"
     52 
     53 namespace WebCore {
     54 
     55 static const unsigned maximumConsoleMessages = 1000;
     56 static const int expireConsoleMessagesStep = 100;
     57 
     58 namespace ConsoleAgentState {
     59 static const char monitoringXHR[] = "monitoringXHR";
     60 static const char consoleMessagesEnabled[] = "consoleMessagesEnabled";
     61 }
     62 
     63 int InspectorConsoleAgent::s_enabledAgentCount = 0;
     64 
     65 InspectorConsoleAgent::InspectorConsoleAgent(InstrumentingAgents* instrumentingAgents, InspectorTimelineAgent* timelineAgent, InspectorCompositeState* state, InjectedScriptManager* injectedScriptManager)
     66     : InspectorBaseAgent<InspectorConsoleAgent>("Console", instrumentingAgents, state)
     67     , m_timelineAgent(timelineAgent)
     68     , m_injectedScriptManager(injectedScriptManager)
     69     , m_frontend(0)
     70     , m_previousMessage(0)
     71     , m_expiredConsoleMessageCount(0)
     72     , m_enabled(false)
     73 {
     74     m_instrumentingAgents->setInspectorConsoleAgent(this);
     75 }
     76 
     77 InspectorConsoleAgent::~InspectorConsoleAgent()
     78 {
     79     m_instrumentingAgents->setInspectorConsoleAgent(0);
     80     m_instrumentingAgents = 0;
     81     m_state = 0;
     82     m_injectedScriptManager = 0;
     83 }
     84 
     85 void InspectorConsoleAgent::enable(ErrorString*)
     86 {
     87     if (m_enabled)
     88         return;
     89     m_enabled = true;
     90     if (!s_enabledAgentCount)
     91         ScriptController::setCaptureCallStackForUncaughtExceptions(true);
     92     ++s_enabledAgentCount;
     93 
     94     m_state->setBoolean(ConsoleAgentState::consoleMessagesEnabled, true);
     95 
     96     if (m_expiredConsoleMessageCount) {
     97         ConsoleMessage expiredMessage(!isWorkerAgent(), OtherMessageSource, LogMessageType, WarningMessageLevel, String::format("%d console messages are not shown.", m_expiredConsoleMessageCount));
     98         expiredMessage.setTimestamp(0);
     99         expiredMessage.addToFrontend(m_frontend, m_injectedScriptManager, false);
    100     }
    101 
    102     size_t messageCount = m_consoleMessages.size();
    103     for (size_t i = 0; i < messageCount; ++i)
    104         m_consoleMessages[i]->addToFrontend(m_frontend, m_injectedScriptManager, false);
    105 }
    106 
    107 void InspectorConsoleAgent::disable(ErrorString*)
    108 {
    109     if (!m_enabled)
    110         return;
    111     m_enabled = false;
    112     if (!(--s_enabledAgentCount))
    113         ScriptController::setCaptureCallStackForUncaughtExceptions(false);
    114     m_state->setBoolean(ConsoleAgentState::consoleMessagesEnabled, false);
    115 }
    116 
    117 void InspectorConsoleAgent::clearMessages(ErrorString*)
    118 {
    119     m_consoleMessages.clear();
    120     m_expiredConsoleMessageCount = 0;
    121     m_previousMessage = 0;
    122     m_injectedScriptManager->releaseObjectGroup("console");
    123     if (m_frontend && m_enabled)
    124         m_frontend->messagesCleared();
    125 }
    126 
    127 void InspectorConsoleAgent::reset()
    128 {
    129     ErrorString error;
    130     clearMessages(&error);
    131     m_times.clear();
    132     m_counts.clear();
    133 }
    134 
    135 void InspectorConsoleAgent::restore()
    136 {
    137     if (m_state->getBoolean(ConsoleAgentState::consoleMessagesEnabled)) {
    138         m_frontend->messagesCleared();
    139         ErrorString error;
    140         enable(&error);
    141     }
    142 }
    143 
    144 void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend)
    145 {
    146     m_frontend = frontend->console();
    147 }
    148 
    149 void InspectorConsoleAgent::clearFrontend()
    150 {
    151     m_frontend = 0;
    152     String errorString;
    153     disable(&errorString);
    154 }
    155 
    156 void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, PassRefPtr<ScriptCallStack> callStack, unsigned long requestIdentifier)
    157 {
    158     if (type == ClearMessageType) {
    159         ErrorString error;
    160         clearMessages(&error);
    161     }
    162 
    163     addConsoleMessage(adoptPtr(new ConsoleMessage(!isWorkerAgent(), source, type, level, message, callStack, requestIdentifier)));
    164 }
    165 
    166 void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, ScriptState* state, PassRefPtr<ScriptArguments> arguments, unsigned long requestIdentifier)
    167 {
    168     if (type == ClearMessageType) {
    169         ErrorString error;
    170         clearMessages(&error);
    171     }
    172 
    173     addConsoleMessage(adoptPtr(new ConsoleMessage(!isWorkerAgent(), source, type, level, message, arguments, state, requestIdentifier)));
    174 }
    175 
    176 void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, const String& scriptId, unsigned lineNumber, unsigned columnNumber, ScriptState* state, unsigned long requestIdentifier)
    177 {
    178     if (type == ClearMessageType) {
    179         ErrorString error;
    180         clearMessages(&error);
    181     }
    182 
    183     bool canGenerateCallStack = !isWorkerAgent() && m_frontend;
    184     addConsoleMessage(adoptPtr(new ConsoleMessage(canGenerateCallStack, source, type, level, message, scriptId, lineNumber, columnNumber, state, requestIdentifier)));
    185 }
    186 
    187 Vector<unsigned> InspectorConsoleAgent::consoleMessageArgumentCounts()
    188 {
    189     Vector<unsigned> result(m_consoleMessages.size());
    190     for (size_t i = 0; i < m_consoleMessages.size(); i++)
    191         result[i] = m_consoleMessages[i]->argumentCount();
    192     return result;
    193 }
    194 
    195 void InspectorConsoleAgent::consoleTime(ExecutionContext*, const String& title)
    196 {
    197     // Follow Firebug's behavior of requiring a title that is not null or
    198     // undefined for timing functions
    199     if (title.isNull())
    200         return;
    201 
    202     m_times.add(title, monotonicallyIncreasingTime());
    203 }
    204 
    205 void InspectorConsoleAgent::consoleTimeEnd(ExecutionContext*, const String& title, ScriptState* state)
    206 {
    207     // Follow Firebug's behavior of requiring a title that is not null or
    208     // undefined for timing functions
    209     if (title.isNull())
    210         return;
    211 
    212     HashMap<String, double>::iterator it = m_times.find(title);
    213     if (it == m_times.end())
    214         return;
    215 
    216     double startTime = it->value;
    217     m_times.remove(it);
    218 
    219     double elapsed = monotonicallyIncreasingTime() - startTime;
    220     String message = title + String::format(": %.3fms", elapsed * 1000);
    221     addMessageToConsole(ConsoleAPIMessageSource, LogMessageType, DebugMessageLevel, message, String(), 0, 0, state);
    222 }
    223 
    224 void InspectorConsoleAgent::consoleTimeline(ExecutionContext* context, const String& title, ScriptState* state)
    225 {
    226     m_timelineAgent->consoleTimeline(context, title, state);
    227 }
    228 
    229 void InspectorConsoleAgent::consoleTimelineEnd(ExecutionContext* context, const String& title, ScriptState* state)
    230 {
    231     m_timelineAgent->consoleTimelineEnd(context, title, state);
    232 }
    233 
    234 void InspectorConsoleAgent::consoleCount(ScriptState* state, PassRefPtr<ScriptArguments> arguments)
    235 {
    236     RefPtr<ScriptCallStack> callStack(createScriptCallStackForConsole());
    237     const ScriptCallFrame& lastCaller = callStack->at(0);
    238     // Follow Firebug's behavior of counting with null and undefined title in
    239     // the same bucket as no argument
    240     String title;
    241     arguments->getFirstArgumentAsString(title);
    242     String identifier = title.isEmpty() ? String(lastCaller.sourceURL() + ':' + String::number(lastCaller.lineNumber()))
    243                                         : String(title + '@');
    244 
    245     HashCountedSet<String>::AddResult result = m_counts.add(identifier);
    246     String message = title + ": " + String::number(result.iterator->value);
    247     addMessageToConsole(ConsoleAPIMessageSource, LogMessageType, DebugMessageLevel, message, callStack);
    248 }
    249 
    250 void InspectorConsoleAgent::frameWindowDiscarded(DOMWindow* window)
    251 {
    252     size_t messageCount = m_consoleMessages.size();
    253     for (size_t i = 0; i < messageCount; ++i)
    254         m_consoleMessages[i]->windowCleared(window);
    255     m_injectedScriptManager->discardInjectedScriptsFor(window);
    256 }
    257 
    258 void InspectorConsoleAgent::didCommitLoad(Frame* frame, DocumentLoader* loader)
    259 {
    260     if (loader->frame() != frame->page()->mainFrame())
    261         return;
    262     reset();
    263 }
    264 
    265 void InspectorConsoleAgent::didFinishXHRLoading(XMLHttpRequest*, ThreadableLoaderClient*, unsigned long requestIdentifier, ScriptString, const String& url, const String& sendURL, unsigned sendLineNumber)
    266 {
    267     if (m_frontend && m_state->getBoolean(ConsoleAgentState::monitoringXHR)) {
    268         String message = "XHR finished loading: \"" + url + "\".";
    269         addMessageToConsole(NetworkMessageSource, LogMessageType, DebugMessageLevel, message, sendURL, sendLineNumber, 0, 0, requestIdentifier);
    270     }
    271 }
    272 
    273 void InspectorConsoleAgent::didReceiveResourceResponse(Frame*, unsigned long requestIdentifier, DocumentLoader* loader, const ResourceResponse& response, ResourceLoader* resourceLoader)
    274 {
    275     if (!loader)
    276         return;
    277     if (response.httpStatusCode() >= 400) {
    278         String message = "Failed to load resource: the server responded with a status of " + String::number(response.httpStatusCode()) + " (" + response.httpStatusText() + ')';
    279         addMessageToConsole(NetworkMessageSource, LogMessageType, ErrorMessageLevel, message, response.url().string(), 0, 0, 0, requestIdentifier);
    280     }
    281 }
    282 
    283 void InspectorConsoleAgent::didFailLoading(unsigned long requestIdentifier, DocumentLoader*, const ResourceError& error)
    284 {
    285     if (error.isCancellation()) // Report failures only.
    286         return;
    287     StringBuilder message;
    288     message.appendLiteral("Failed to load resource");
    289     if (!error.localizedDescription().isEmpty()) {
    290         message.appendLiteral(": ");
    291         message.append(error.localizedDescription());
    292     }
    293     addMessageToConsole(NetworkMessageSource, LogMessageType, ErrorMessageLevel, message.toString(), error.failingURL(), 0, 0, 0, requestIdentifier);
    294 }
    295 
    296 void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled)
    297 {
    298     m_state->setBoolean(ConsoleAgentState::monitoringXHR, enabled);
    299 }
    300 
    301 static bool isGroupMessage(MessageType type)
    302 {
    303     return type == StartGroupMessageType
    304         || type ==  StartGroupCollapsedMessageType
    305         || type == EndGroupMessageType;
    306 }
    307 
    308 void InspectorConsoleAgent::addConsoleMessage(PassOwnPtr<ConsoleMessage> consoleMessage)
    309 {
    310     ASSERT_ARG(consoleMessage, consoleMessage);
    311 
    312     if (m_previousMessage && !isGroupMessage(m_previousMessage->type()) && m_previousMessage->isEqual(consoleMessage.get())) {
    313         m_previousMessage->incrementCount();
    314         if (m_frontend && m_enabled)
    315             m_previousMessage->updateRepeatCountInConsole(m_frontend);
    316     } else {
    317         m_previousMessage = consoleMessage.get();
    318         m_consoleMessages.append(consoleMessage);
    319         if (m_frontend && m_enabled)
    320             m_previousMessage->addToFrontend(m_frontend, m_injectedScriptManager, true);
    321     }
    322 
    323     if (!m_frontend && m_consoleMessages.size() >= maximumConsoleMessages) {
    324         m_expiredConsoleMessageCount += expireConsoleMessagesStep;
    325         m_consoleMessages.remove(0, expireConsoleMessagesStep);
    326     }
    327 }
    328 
    329 class InspectableHeapObject : public InjectedScriptHost::InspectableObject {
    330 public:
    331     explicit InspectableHeapObject(int heapObjectId) : m_heapObjectId(heapObjectId) { }
    332     virtual ScriptValue get(ScriptState*)
    333     {
    334         return ScriptProfiler::objectByHeapObjectId(m_heapObjectId);
    335     }
    336 private:
    337     int m_heapObjectId;
    338 };
    339 
    340 void InspectorConsoleAgent::addInspectedHeapObject(ErrorString*, int inspectedHeapObjectId)
    341 {
    342     m_injectedScriptManager->injectedScriptHost()->addInspectedObject(adoptPtr(new InspectableHeapObject(inspectedHeapObjectId)));
    343 }
    344 
    345 } // namespace WebCore
    346 
    347