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 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 74 InspectorConsoleAgent::~InspectorConsoleAgent() 75 { 76 m_instrumentingAgents->setInspectorConsoleAgent(0); 77 m_instrumentingAgents = 0; 78 m_state = 0; 79 m_injectedScriptManager = 0; 80 } 81 82 void InspectorConsoleAgent::init() 83 { 84 m_instrumentingAgents->setInspectorConsoleAgent(this); 85 } 86 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 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 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 128 void InspectorConsoleAgent::reset() 129 { 130 ErrorString error; 131 clearMessages(&error); 132 m_times.clear(); 133 m_counts.clear(); 134 } 135 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 145 void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend) 146 { 147 m_frontend = frontend->console(); 148 } 149 150 void InspectorConsoleAgent::clearFrontend() 151 { 152 m_frontend = 0; 153 String errorString; 154 disable(&errorString); 155 } 156 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 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 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 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 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 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 225 void InspectorConsoleAgent::consoleTimeline(ExecutionContext* context, const String& title, ScriptState* scriptState) 226 { 227 m_timelineAgent->consoleTimeline(context, title, scriptState); 228 } 229 230 void InspectorConsoleAgent::consoleTimelineEnd(ExecutionContext* context, const String& title, ScriptState* scriptState) 231 { 232 m_timelineAgent->consoleTimelineEnd(context, title, scriptState); 233 } 234 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 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 259 void InspectorConsoleAgent::didCommitLoad(LocalFrame* frame, DocumentLoader* loader) 260 { 261 if (loader->frame() != frame->page()->mainFrame()) 262 return; 263 reset(); 264 } 265 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 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 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 297 void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled) 298 { 299 m_state->setBoolean(ConsoleAgentState::monitoringXHR, enabled); 300 } 301 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: 319 explicit InspectableHeapObject(int heapObjectId) : m_heapObjectId(heapObjectId) { } 320 virtual ScriptValue get(ScriptState*) OVERRIDE 321 { 322 return ScriptProfiler::objectByHeapObjectId(m_heapObjectId); 323 } 324 private: 325 int m_heapObjectId; 326 }; 327 328 void InspectorConsoleAgent::addInspectedHeapObject(ErrorString*, int inspectedHeapObjectId) 329 { 330 m_injectedScriptManager->injectedScriptHost()->addInspectedObject(adoptPtr(new InspectableHeapObject(inspectedHeapObjectId))); 331 } 332 333 } // namespace WebCore 334 335