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