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 "InspectorConsoleAgent.h"
28
29#if ENABLE(INSPECTOR)
30#include "InstrumentingAgents.h"
31#include "Console.h"
32#include "ConsoleMessage.h"
33#include "InjectedScriptHost.h"
34#include "InjectedScriptManager.h"
35#include "InspectorAgent.h"
36#include "InspectorDOMAgent.h"
37#include "InspectorFrontend.h"
38#include "InspectorState.h"
39#include "ResourceError.h"
40#include "ResourceResponse.h"
41#include "ScriptArguments.h"
42#include "ScriptCallFrame.h"
43#include "ScriptCallStack.h"
44#include <wtf/CurrentTime.h>
45#include <wtf/OwnPtr.h>
46#include <wtf/PassOwnPtr.h>
47#include <wtf/text/StringConcatenate.h>
48
49namespace WebCore {
50
51static const unsigned maximumConsoleMessages = 1000;
52static const int expireConsoleMessagesStep = 100;
53
54namespace ConsoleAgentState {
55static const char monitoringXHR[] = "monitoringXHR";
56static const char consoleMessagesEnabled[] = "consoleMessagesEnabled";
57}
58
59InspectorConsoleAgent::InspectorConsoleAgent(InstrumentingAgents* instrumentingAgents, InspectorAgent* inspectorAgent, InspectorState* state, InjectedScriptManager* injectedScriptManager, InspectorDOMAgent* domAgent)
60    : m_instrumentingAgents(instrumentingAgents)
61    , m_inspectorAgent(inspectorAgent)
62    , m_inspectorState(state)
63    , m_injectedScriptManager(injectedScriptManager)
64    , m_inspectorDOMAgent(domAgent)
65    , m_frontend(0)
66    , m_previousMessage(0)
67    , m_expiredConsoleMessageCount(0)
68{
69    m_instrumentingAgents->setInspectorConsoleAgent(this);
70}
71
72InspectorConsoleAgent::~InspectorConsoleAgent()
73{
74    m_instrumentingAgents->setInspectorConsoleAgent(0);
75    m_instrumentingAgents = 0;
76    m_inspectorAgent = 0;
77    m_inspectorState = 0;
78    m_injectedScriptManager = 0;
79    m_inspectorDOMAgent = 0;
80}
81
82void InspectorConsoleAgent::enable(ErrorString*, int* consoleMessageExpireCount)
83{
84    *consoleMessageExpireCount = m_expiredConsoleMessageCount;
85
86    m_inspectorState->setBoolean(ConsoleAgentState::consoleMessagesEnabled, true);
87
88    size_t messageCount = m_consoleMessages.size();
89    for (size_t i = 0; i < messageCount; ++i)
90        m_consoleMessages[i]->addToFrontend(m_frontend, m_injectedScriptManager);
91}
92
93void InspectorConsoleAgent::disable(ErrorString*)
94{
95    m_inspectorState->setBoolean(ConsoleAgentState::consoleMessagesEnabled, false);
96}
97
98void InspectorConsoleAgent::clearConsoleMessages(ErrorString*)
99{
100    m_consoleMessages.clear();
101    m_expiredConsoleMessageCount = 0;
102    m_previousMessage = 0;
103    m_injectedScriptManager->releaseObjectGroup("console");
104    m_inspectorDOMAgent->releaseDanglingNodes();
105    if (m_frontend)
106        m_frontend->messagesCleared();
107}
108
109void InspectorConsoleAgent::reset()
110{
111    ErrorString error;
112    clearConsoleMessages(&error);
113    m_times.clear();
114    m_counts.clear();
115}
116
117void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend)
118{
119    m_frontend = frontend->console();
120}
121
122void InspectorConsoleAgent::clearFrontend()
123{
124    m_frontend = 0;
125}
126
127void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, PassRefPtr<ScriptArguments> arguments, PassRefPtr<ScriptCallStack> callStack)
128{
129    if (!m_inspectorAgent->enabled())
130        return;
131    addConsoleMessage(new ConsoleMessage(source, type, level, message, arguments, callStack));
132}
133
134void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceID)
135{
136    if (!m_inspectorAgent->enabled())
137        return;
138    addConsoleMessage(new ConsoleMessage(source, type, level, message, lineNumber, sourceID));
139}
140
141void InspectorConsoleAgent::startTiming(const String& title)
142{
143    // Follow Firebug's behavior of requiring a title that is not null or
144    // undefined for timing functions
145    if (title.isNull())
146        return;
147
148    m_times.add(title, currentTime() * 1000);
149}
150
151void InspectorConsoleAgent::stopTiming(const String& title, PassRefPtr<ScriptCallStack> callStack)
152{
153    // Follow Firebug's behavior of requiring a title that is not null or
154    // undefined for timing functions
155    if (title.isNull())
156        return;
157
158    HashMap<String, double>::iterator it = m_times.find(title);
159    if (it == m_times.end())
160        return;
161
162    double startTime = it->second;
163    m_times.remove(it);
164
165    double elapsed = currentTime() * 1000 - startTime;
166    String message = title + String::format(": %.0fms", elapsed);
167    const ScriptCallFrame& lastCaller = callStack->at(0);
168    addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lastCaller.lineNumber(), lastCaller.sourceURL());
169}
170
171void InspectorConsoleAgent::count(PassRefPtr<ScriptArguments> arguments, PassRefPtr<ScriptCallStack> callStack)
172{
173    const ScriptCallFrame& lastCaller = callStack->at(0);
174    // Follow Firebug's behavior of counting with null and undefined title in
175    // the same bucket as no argument
176    String title;
177    arguments->getFirstArgumentAsString(title);
178    String identifier = makeString(title, '@', lastCaller.sourceURL(), ':', String::number(lastCaller.lineNumber()));
179
180    HashMap<String, unsigned>::iterator it = m_counts.find(identifier);
181    int count;
182    if (it == m_counts.end())
183        count = 1;
184    else {
185        count = it->second + 1;
186        m_counts.remove(it);
187    }
188
189    m_counts.add(identifier, count);
190
191    String message = makeString(title, ": ", String::number(count));
192    addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, message, lastCaller.lineNumber(), lastCaller.sourceURL());
193}
194
195void InspectorConsoleAgent::resourceRetrievedByXMLHttpRequest(const String& url, const String& sendURL, unsigned sendLineNumber)
196{
197    if (!m_inspectorAgent->enabled())
198        return;
199    if (m_inspectorState->getBoolean(ConsoleAgentState::monitoringXHR))
200        addMessageToConsole(JSMessageSource, LogMessageType, LogMessageLevel, "XHR finished loading: \"" + url + "\".", sendLineNumber, sendURL);
201}
202
203void InspectorConsoleAgent::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
204{
205    if (!m_inspectorAgent->enabled())
206        return;
207
208    if (response.httpStatusCode() >= 400) {
209        String message = makeString("Failed to load resource: the server responded with a status of ", String::number(response.httpStatusCode()), " (", response.httpStatusText(), ')');
210        addConsoleMessage(new ConsoleMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, response.url().string(), identifier));
211    }
212}
213
214void InspectorConsoleAgent::didFailLoading(unsigned long identifier, const ResourceError& error)
215{
216    if (!m_inspectorAgent->enabled())
217        return;
218    if (error.isCancellation()) // Report failures only.
219        return;
220    String message = "Failed to load resource";
221    if (!error.localizedDescription().isEmpty())
222        message += ": " + error.localizedDescription();
223    addConsoleMessage(new ConsoleMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, error.failingURL(), identifier));
224}
225
226void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled)
227{
228    m_inspectorState->setBoolean(ConsoleAgentState::monitoringXHR, enabled);
229}
230
231void InspectorConsoleAgent::addInspectedNode(ErrorString*, int nodeId)
232{
233    Node* node = m_inspectorDOMAgent->nodeForId(nodeId);
234    if (!node)
235        return;
236    m_injectedScriptManager->injectedScriptHost()->addInspectedNode(node);
237}
238
239void InspectorConsoleAgent::addConsoleMessage(PassOwnPtr<ConsoleMessage> consoleMessage)
240{
241    ASSERT(m_inspectorAgent->enabled());
242    ASSERT_ARG(consoleMessage, consoleMessage);
243
244    if (m_previousMessage && m_previousMessage->type() != EndGroupMessageType && m_previousMessage->isEqual(consoleMessage.get())) {
245        m_previousMessage->incrementCount();
246        if (m_inspectorState->getBoolean(ConsoleAgentState::consoleMessagesEnabled) && m_frontend)
247            m_previousMessage->updateRepeatCountInConsole(m_frontend);
248    } else {
249        m_previousMessage = consoleMessage.get();
250        m_consoleMessages.append(consoleMessage);
251        if (m_inspectorState->getBoolean(ConsoleAgentState::consoleMessagesEnabled) && m_frontend)
252            m_previousMessage->addToFrontend(m_frontend, m_injectedScriptManager);
253    }
254
255    if (!m_frontend && m_consoleMessages.size() >= maximumConsoleMessages) {
256        m_expiredConsoleMessageCount += expireConsoleMessagesStep;
257        m_consoleMessages.remove(0, expireConsoleMessagesStep);
258    }
259}
260
261} // namespace WebCore
262
263#endif // ENABLE(INSPECTOR)
264