2011-04-27 Darin Adler <darin@apple.com>
[WebKit-https.git] / Source / WebCore / inspector / InspectorConsoleAgent.cpp
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
49 namespace WebCore {
50
51 static const unsigned maximumConsoleMessages = 1000;
52 static const int expireConsoleMessagesStep = 100;
53
54 namespace ConsoleAgentState {
55 static const char monitoringXHR[] = "monitoringXHR";
56 static const char consoleMessagesEnabled[] = "consoleMessagesEnabled";
57 }
58
59 InspectorConsoleAgent::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
72 InspectorConsoleAgent::~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
82 void 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
93 void InspectorConsoleAgent::disable(ErrorString*)
94 {
95     m_inspectorState->setBoolean(ConsoleAgentState::consoleMessagesEnabled, false);
96 }
97
98 void 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
109 void InspectorConsoleAgent::reset()
110 {
111     ErrorString error;
112     clearConsoleMessages(&error);
113     m_times.clear();
114     m_counts.clear();
115 }
116
117 void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend)
118 {
119     m_frontend = frontend->console();
120 }
121
122 void InspectorConsoleAgent::clearFrontend()
123 {
124     m_frontend = 0;
125 }
126
127 void 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(adoptPtr(new ConsoleMessage(source, type, level, message, arguments, callStack)));
132 }
133
134 void 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(adoptPtr(new ConsoleMessage(source, type, level, message, lineNumber, sourceID)));
139 }
140
141 void 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
151 void 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
171 void 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
195 void 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
203 void 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(adoptPtr(new ConsoleMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, response.url().string(), identifier)));
211     }
212 }
213
214 void 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(adoptPtr(new ConsoleMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, error.failingURL(), identifier)));
224 }
225
226 void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled)
227 {
228     m_inspectorState->setBoolean(ConsoleAgentState::monitoringXHR, enabled);
229 }
230
231 void 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
239 void 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)