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/ScriptProfiler.h"
32 #include "core/frame/LocalFrame.h"
33 #include "core/inspector/ConsoleMessage.h"
34 #include "core/inspector/InjectedScriptHost.h"
35 #include "core/inspector/InjectedScriptManager.h"
36 #include "core/inspector/InspectorState.h"
37 #include "core/inspector/InspectorTimelineAgent.h"
38 #include "core/inspector/InstrumentingAgents.h"
39 #include "core/inspector/ScriptArguments.h"
40 #include "core/inspector/ScriptCallFrame.h"
41 #include "core/inspector/ScriptCallStack.h"
42 #include "core/loader/DocumentLoader.h"
43 #include "core/page/Page.h"
44 #include "platform/network/ResourceError.h"
45 #include "platform/network/ResourceResponse.h"
46 #include "wtf/CurrentTime.h"
47 #include "wtf/OwnPtr.h"
48 #include "wtf/PassOwnPtr.h"
49 #include "wtf/text/StringBuilder.h"
50 #include "wtf/text/WTFString.h"
51
52 namespace WebCore {
53
54 static const unsigned maximumConsoleMessages = 1000;
55 static const int expireConsoleMessagesStep = 100;
56
57 namespace ConsoleAgentState {
58 static const char monitoringXHR[] = "monitoringXHR";
59 static const char consoleMessagesEnabled[] = "consoleMessagesEnabled";
60 }
61
62 int InspectorConsoleAgent::s_enabledAgentCount = 0;
63
InspectorConsoleAgent(InspectorTimelineAgent * timelineAgent,InjectedScriptManager * injectedScriptManager)64 InspectorConsoleAgent::InspectorConsoleAgent(InspectorTimelineAgent* timelineAgent, InjectedScriptManager* injectedScriptManager)
65 : InspectorBaseAgent<InspectorConsoleAgent>("Console")
66 , m_timelineAgent(timelineAgent)
67 , m_injectedScriptManager(injectedScriptManager)
68 , m_frontend(0)
69 , m_expiredConsoleMessageCount(0)
70 , m_enabled(false)
71 {
72 }
73
~InspectorConsoleAgent()74 InspectorConsoleAgent::~InspectorConsoleAgent()
75 {
76 m_instrumentingAgents->setInspectorConsoleAgent(0);
77 m_instrumentingAgents = 0;
78 m_state = 0;
79 m_injectedScriptManager = 0;
80 }
81
init()82 void InspectorConsoleAgent::init()
83 {
84 m_instrumentingAgents->setInspectorConsoleAgent(this);
85 }
86
enable(ErrorString *)87 void InspectorConsoleAgent::enable(ErrorString*)
88 {
89 if (m_enabled)
90 return;
91 m_enabled = true;
92 if (!s_enabledAgentCount)
93 ScriptController::setCaptureCallStackForUncaughtExceptions(true);
94 ++s_enabledAgentCount;
95
96 m_state->setBoolean(ConsoleAgentState::consoleMessagesEnabled, true);
97
98 if (m_expiredConsoleMessageCount) {
99 ConsoleMessage expiredMessage(!isWorkerAgent(), OtherMessageSource, LogMessageType, WarningMessageLevel, String::format("%d console messages are not shown.", m_expiredConsoleMessageCount));
100 expiredMessage.setTimestamp(0);
101 expiredMessage.addToFrontend(m_frontend, m_injectedScriptManager, false);
102 }
103
104 size_t messageCount = m_consoleMessages.size();
105 for (size_t i = 0; i < messageCount; ++i)
106 m_consoleMessages[i]->addToFrontend(m_frontend, m_injectedScriptManager, false);
107 }
108
disable(ErrorString *)109 void InspectorConsoleAgent::disable(ErrorString*)
110 {
111 if (!m_enabled)
112 return;
113 m_enabled = false;
114 if (!(--s_enabledAgentCount))
115 ScriptController::setCaptureCallStackForUncaughtExceptions(false);
116 m_state->setBoolean(ConsoleAgentState::consoleMessagesEnabled, false);
117 }
118
clearMessages(ErrorString *)119 void InspectorConsoleAgent::clearMessages(ErrorString*)
120 {
121 m_consoleMessages.clear();
122 m_expiredConsoleMessageCount = 0;
123 m_injectedScriptManager->releaseObjectGroup("console");
124 if (m_frontend && m_enabled)
125 m_frontend->messagesCleared();
126 }
127
reset()128 void InspectorConsoleAgent::reset()
129 {
130 ErrorString error;
131 clearMessages(&error);
132 m_times.clear();
133 m_counts.clear();
134 }
135
restore()136 void InspectorConsoleAgent::restore()
137 {
138 if (m_state->getBoolean(ConsoleAgentState::consoleMessagesEnabled)) {
139 m_frontend->messagesCleared();
140 ErrorString error;
141 enable(&error);
142 }
143 }
144
setFrontend(InspectorFrontend * frontend)145 void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend)
146 {
147 m_frontend = frontend->console();
148 }
149
clearFrontend()150 void InspectorConsoleAgent::clearFrontend()
151 {
152 m_frontend = 0;
153 String errorString;
154 disable(&errorString);
155 }
156
addMessageToConsole(MessageSource source,MessageType type,MessageLevel level,const String & message,PassRefPtrWillBeRawPtr<ScriptCallStack> callStack,unsigned long requestIdentifier)157 void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, PassRefPtrWillBeRawPtr<ScriptCallStack> callStack, unsigned long requestIdentifier)
158 {
159 if (type == ClearMessageType) {
160 ErrorString error;
161 clearMessages(&error);
162 }
163
164 addConsoleMessage(adoptPtr(new ConsoleMessage(!isWorkerAgent(), source, type, level, message, callStack, requestIdentifier)));
165 }
166
addMessageToConsole(MessageSource source,MessageType type,MessageLevel level,const String & message,ScriptState * scriptState,PassRefPtrWillBeRawPtr<ScriptArguments> arguments,unsigned long requestIdentifier)167 void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, ScriptState* scriptState, PassRefPtrWillBeRawPtr<ScriptArguments> arguments, unsigned long requestIdentifier)
168 {
169 if (type == ClearMessageType) {
170 ErrorString error;
171 clearMessages(&error);
172 }
173
174 addConsoleMessage(adoptPtr(new ConsoleMessage(!isWorkerAgent(), source, type, level, message, arguments, scriptState, requestIdentifier)));
175 }
176
addMessageToConsole(MessageSource source,MessageType type,MessageLevel level,const String & message,const String & scriptId,unsigned lineNumber,unsigned columnNumber,ScriptState * scriptState,unsigned long requestIdentifier)177 void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, const String& scriptId, unsigned lineNumber, unsigned columnNumber, ScriptState* scriptState, unsigned long requestIdentifier)
178 {
179 if (type == ClearMessageType) {
180 ErrorString error;
181 clearMessages(&error);
182 }
183
184 bool canGenerateCallStack = !isWorkerAgent() && m_frontend;
185 addConsoleMessage(adoptPtr(new ConsoleMessage(canGenerateCallStack, source, type, level, message, scriptId, lineNumber, columnNumber, scriptState, requestIdentifier)));
186 }
187
consoleMessageArgumentCounts()188 Vector<unsigned> InspectorConsoleAgent::consoleMessageArgumentCounts()
189 {
190 Vector<unsigned> result(m_consoleMessages.size());
191 for (size_t i = 0; i < m_consoleMessages.size(); i++)
192 result[i] = m_consoleMessages[i]->argumentCount();
193 return result;
194 }
195
consoleTime(ExecutionContext *,const String & title)196 void InspectorConsoleAgent::consoleTime(ExecutionContext*, const String& title)
197 {
198 // Follow Firebug's behavior of requiring a title that is not null or
199 // undefined for timing functions
200 if (title.isNull())
201 return;
202
203 m_times.add(title, monotonicallyIncreasingTime());
204 }
205
consoleTimeEnd(ExecutionContext *,const String & title,ScriptState * scriptState)206 void InspectorConsoleAgent::consoleTimeEnd(ExecutionContext*, const String& title, ScriptState* scriptState)
207 {
208 // Follow Firebug's behavior of requiring a title that is not null or
209 // undefined for timing functions
210 if (title.isNull())
211 return;
212
213 HashMap<String, double>::iterator it = m_times.find(title);
214 if (it == m_times.end())
215 return;
216
217 double startTime = it->value;
218 m_times.remove(it);
219
220 double elapsed = monotonicallyIncreasingTime() - startTime;
221 String message = title + String::format(": %.3fms", elapsed * 1000);
222 addMessageToConsole(ConsoleAPIMessageSource, LogMessageType, DebugMessageLevel, message, String(), 0, 0, scriptState);
223 }
224
consoleTimeline(ExecutionContext * context,const String & title,ScriptState * scriptState)225 void InspectorConsoleAgent::consoleTimeline(ExecutionContext* context, const String& title, ScriptState* scriptState)
226 {
227 m_timelineAgent->consoleTimeline(context, title, scriptState);
228 }
229
consoleTimelineEnd(ExecutionContext * context,const String & title,ScriptState * scriptState)230 void InspectorConsoleAgent::consoleTimelineEnd(ExecutionContext* context, const String& title, ScriptState* scriptState)
231 {
232 m_timelineAgent->consoleTimelineEnd(context, title, scriptState);
233 }
234
consoleCount(ScriptState * scriptState,PassRefPtrWillBeRawPtr<ScriptArguments> arguments)235 void InspectorConsoleAgent::consoleCount(ScriptState* scriptState, PassRefPtrWillBeRawPtr<ScriptArguments> arguments)
236 {
237 RefPtrWillBeRawPtr<ScriptCallStack> callStack(createScriptCallStackForConsole(scriptState));
238 const ScriptCallFrame& lastCaller = callStack->at(0);
239 // Follow Firebug's behavior of counting with null and undefined title in
240 // the same bucket as no argument
241 String title;
242 arguments->getFirstArgumentAsString(title);
243 String identifier = title.isEmpty() ? String(lastCaller.sourceURL() + ':' + String::number(lastCaller.lineNumber()))
244 : String(title + '@');
245
246 HashCountedSet<String>::AddResult result = m_counts.add(identifier);
247 String message = title + ": " + String::number(result.storedValue->value);
248 addMessageToConsole(ConsoleAPIMessageSource, LogMessageType, DebugMessageLevel, message, callStack.get());
249 }
250
frameWindowDiscarded(LocalDOMWindow * window)251 void InspectorConsoleAgent::frameWindowDiscarded(LocalDOMWindow* window)
252 {
253 size_t messageCount = m_consoleMessages.size();
254 for (size_t i = 0; i < messageCount; ++i)
255 m_consoleMessages[i]->windowCleared(window);
256 m_injectedScriptManager->discardInjectedScriptsFor(window);
257 }
258
didCommitLoad(LocalFrame * frame,DocumentLoader * loader)259 void InspectorConsoleAgent::didCommitLoad(LocalFrame* frame, DocumentLoader* loader)
260 {
261 if (loader->frame() != frame->page()->mainFrame())
262 return;
263 reset();
264 }
265
didFinishXHRLoading(XMLHttpRequest *,ThreadableLoaderClient *,unsigned long requestIdentifier,ScriptString,const AtomicString & method,const String & url,const String & sendURL,unsigned sendLineNumber)266 void InspectorConsoleAgent::didFinishXHRLoading(XMLHttpRequest*, ThreadableLoaderClient*, unsigned long requestIdentifier, ScriptString, const AtomicString& method, const String& url, const String& sendURL, unsigned sendLineNumber)
267 {
268 if (m_frontend && m_state->getBoolean(ConsoleAgentState::monitoringXHR)) {
269 String message = "XHR finished loading: " + method + " \"" + url + "\".";
270 addMessageToConsole(NetworkMessageSource, LogMessageType, DebugMessageLevel, message, sendURL, sendLineNumber, 0, 0, requestIdentifier);
271 }
272 }
273
didReceiveResourceResponse(LocalFrame *,unsigned long requestIdentifier,DocumentLoader * loader,const ResourceResponse & response,ResourceLoader * resourceLoader)274 void InspectorConsoleAgent::didReceiveResourceResponse(LocalFrame*, unsigned long requestIdentifier, DocumentLoader* loader, const ResourceResponse& response, ResourceLoader* resourceLoader)
275 {
276 if (!loader)
277 return;
278 if (response.httpStatusCode() >= 400) {
279 String message = "Failed to load resource: the server responded with a status of " + String::number(response.httpStatusCode()) + " (" + response.httpStatusText() + ')';
280 addMessageToConsole(NetworkMessageSource, LogMessageType, ErrorMessageLevel, message, response.url().string(), 0, 0, 0, requestIdentifier);
281 }
282 }
283
didFailLoading(unsigned long requestIdentifier,const ResourceError & error)284 void InspectorConsoleAgent::didFailLoading(unsigned long requestIdentifier, const ResourceError& error)
285 {
286 if (error.isCancellation()) // Report failures only.
287 return;
288 StringBuilder message;
289 message.appendLiteral("Failed to load resource");
290 if (!error.localizedDescription().isEmpty()) {
291 message.appendLiteral(": ");
292 message.append(error.localizedDescription());
293 }
294 addMessageToConsole(NetworkMessageSource, LogMessageType, ErrorMessageLevel, message.toString(), error.failingURL(), 0, 0, 0, requestIdentifier);
295 }
296
setMonitoringXHREnabled(ErrorString *,bool enabled)297 void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled)
298 {
299 m_state->setBoolean(ConsoleAgentState::monitoringXHR, enabled);
300 }
301
addConsoleMessage(PassOwnPtr<ConsoleMessage> consoleMessage)302 void InspectorConsoleAgent::addConsoleMessage(PassOwnPtr<ConsoleMessage> consoleMessage)
303 {
304 ASSERT_ARG(consoleMessage, consoleMessage);
305
306 if (m_frontend && m_enabled)
307 consoleMessage->addToFrontend(m_frontend, m_injectedScriptManager, true);
308
309 m_consoleMessages.append(consoleMessage);
310
311 if (!m_frontend && m_consoleMessages.size() >= maximumConsoleMessages) {
312 m_expiredConsoleMessageCount += expireConsoleMessagesStep;
313 m_consoleMessages.remove(0, expireConsoleMessagesStep);
314 }
315 }
316
317 class InspectableHeapObject FINAL : public InjectedScriptHost::InspectableObject {
318 public:
InspectableHeapObject(int heapObjectId)319 explicit InspectableHeapObject(int heapObjectId) : m_heapObjectId(heapObjectId) { }
get(ScriptState *)320 virtual ScriptValue get(ScriptState*) OVERRIDE
321 {
322 return ScriptProfiler::objectByHeapObjectId(m_heapObjectId);
323 }
324 private:
325 int m_heapObjectId;
326 };
327
addInspectedHeapObject(ErrorString *,int inspectedHeapObjectId)328 void InspectorConsoleAgent::addInspectedHeapObject(ErrorString*, int inspectedHeapObjectId)
329 {
330 m_injectedScriptManager->injectedScriptHost()->addInspectedObject(adoptPtr(new InspectableHeapObject(inspectedHeapObjectId)));
331 }
332
333 } // namespace WebCore
334
335