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
InspectorConsoleAgent(InstrumentingAgents * instrumentingAgents,InspectorAgent * inspectorAgent,InspectorState * state,InjectedScriptManager * injectedScriptManager,InspectorDOMAgent * domAgent)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
~InspectorConsoleAgent()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
enable(ErrorString *,int * consoleMessageExpireCount)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
disable(ErrorString *)93 void InspectorConsoleAgent::disable(ErrorString*)
94 {
95 m_inspectorState->setBoolean(ConsoleAgentState::consoleMessagesEnabled, false);
96 }
97
clearConsoleMessages(ErrorString *)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
reset()109 void InspectorConsoleAgent::reset()
110 {
111 ErrorString error;
112 clearConsoleMessages(&error);
113 m_times.clear();
114 m_counts.clear();
115 }
116
setFrontend(InspectorFrontend * frontend)117 void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend)
118 {
119 m_frontend = frontend->console();
120 }
121
clearFrontend()122 void InspectorConsoleAgent::clearFrontend()
123 {
124 m_frontend = 0;
125 }
126
addMessageToConsole(MessageSource source,MessageType type,MessageLevel level,const String & message,PassRefPtr<ScriptArguments> arguments,PassRefPtr<ScriptCallStack> callStack)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(new ConsoleMessage(source, type, level, message, arguments, callStack));
132 }
133
addMessageToConsole(MessageSource source,MessageType type,MessageLevel level,const String & message,unsigned lineNumber,const String & sourceID)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(new ConsoleMessage(source, type, level, message, lineNumber, sourceID));
139 }
140
startTiming(const String & title)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
stopTiming(const String & title,PassRefPtr<ScriptCallStack> callStack)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
count(PassRefPtr<ScriptArguments> arguments,PassRefPtr<ScriptCallStack> callStack)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
resourceRetrievedByXMLHttpRequest(const String & url,const String & sendURL,unsigned sendLineNumber)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
didReceiveResponse(unsigned long identifier,const ResourceResponse & response)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(new ConsoleMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, response.url().string(), identifier));
211 }
212 }
213
didFailLoading(unsigned long identifier,const ResourceError & error)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(new ConsoleMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, error.failingURL(), identifier));
224 }
225
setMonitoringXHREnabled(ErrorString *,bool enabled)226 void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled)
227 {
228 m_inspectorState->setBoolean(ConsoleAgentState::monitoringXHR, enabled);
229 }
230
addInspectedNode(ErrorString *,int nodeId)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
addConsoleMessage(PassOwnPtr<ConsoleMessage> consoleMessage)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)
264