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(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(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(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(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) 264