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 28#if ENABLE(INSPECTOR) 29#include "InspectorConsoleAgent.h" 30 31#include "InstrumentingAgents.h" 32#include "Console.h" 33#include "ConsoleMessage.h" 34#include "DOMWindow.h" 35#include "InjectedScriptHost.h" 36#include "InjectedScriptManager.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 "ScriptCallStackFactory.h" 45#include "ScriptController.h" 46#include "ScriptObject.h" 47#include "ScriptProfiler.h" 48#include <wtf/CurrentTime.h> 49#include <wtf/OwnPtr.h> 50#include <wtf/PassOwnPtr.h> 51#include <wtf/text/StringBuilder.h> 52#include <wtf/text/WTFString.h> 53 54namespace WebCore { 55 56static const unsigned maximumConsoleMessages = 1000; 57static const int expireConsoleMessagesStep = 100; 58 59namespace ConsoleAgentState { 60static const char monitoringXHR[] = "monitoringXHR"; 61static const char consoleMessagesEnabled[] = "consoleMessagesEnabled"; 62} 63 64int InspectorConsoleAgent::s_enabledAgentCount = 0; 65 66InspectorConsoleAgent::InspectorConsoleAgent(InstrumentingAgents* instrumentingAgents, InspectorCompositeState* state, InjectedScriptManager* injectedScriptManager) 67 : InspectorBaseAgent<InspectorConsoleAgent>("Console", instrumentingAgents, state) 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 77InspectorConsoleAgent::~InspectorConsoleAgent() 78{ 79 m_instrumentingAgents->setInspectorConsoleAgent(0); 80 m_instrumentingAgents = 0; 81 m_state = 0; 82 m_injectedScriptManager = 0; 83} 84 85void 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.addToFrontend(m_frontend, m_injectedScriptManager, false); 99 } 100 101 size_t messageCount = m_consoleMessages.size(); 102 for (size_t i = 0; i < messageCount; ++i) 103 m_consoleMessages[i]->addToFrontend(m_frontend, m_injectedScriptManager, false); 104} 105 106void InspectorConsoleAgent::disable(ErrorString*) 107{ 108 if (!m_enabled) 109 return; 110 m_enabled = false; 111 if (!(--s_enabledAgentCount)) 112 ScriptController::setCaptureCallStackForUncaughtExceptions(false); 113 m_state->setBoolean(ConsoleAgentState::consoleMessagesEnabled, false); 114} 115 116void InspectorConsoleAgent::clearMessages(ErrorString*) 117{ 118 m_consoleMessages.clear(); 119 m_expiredConsoleMessageCount = 0; 120 m_previousMessage = 0; 121 m_injectedScriptManager->releaseObjectGroup("console"); 122 if (m_frontend && m_enabled) 123 m_frontend->messagesCleared(); 124} 125 126void InspectorConsoleAgent::reset() 127{ 128 ErrorString error; 129 clearMessages(&error); 130 m_times.clear(); 131 m_counts.clear(); 132} 133 134void InspectorConsoleAgent::restore() 135{ 136 if (m_state->getBoolean(ConsoleAgentState::consoleMessagesEnabled)) { 137 m_frontend->messagesCleared(); 138 ErrorString error; 139 enable(&error); 140 } 141} 142 143void InspectorConsoleAgent::setFrontend(InspectorFrontend* frontend) 144{ 145 m_frontend = frontend->console(); 146} 147 148void InspectorConsoleAgent::clearFrontend() 149{ 150 m_frontend = 0; 151 String errorString; 152 disable(&errorString); 153} 154 155void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, PassRefPtr<ScriptCallStack> callStack, unsigned long requestIdentifier) 156{ 157 if (!developerExtrasEnabled()) 158 return; 159 160 if (type == ClearMessageType) { 161 ErrorString error; 162 clearMessages(&error); 163 } 164 165 addConsoleMessage(adoptPtr(new ConsoleMessage(!isWorkerAgent(), source, type, level, message, callStack, requestIdentifier))); 166} 167 168void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, ScriptState* state, PassRefPtr<ScriptArguments> arguments, unsigned long requestIdentifier) 169{ 170 if (!developerExtrasEnabled()) 171 return; 172 173 if (type == ClearMessageType) { 174 ErrorString error; 175 clearMessages(&error); 176 } 177 178 addConsoleMessage(adoptPtr(new ConsoleMessage(!isWorkerAgent(), source, type, level, message, arguments, state, requestIdentifier))); 179} 180 181void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, const String& scriptId, unsigned lineNumber, unsigned columnNumber, ScriptState* state, unsigned long requestIdentifier) 182{ 183 if (!developerExtrasEnabled()) 184 return; 185 186 if (type == ClearMessageType) { 187 ErrorString error; 188 clearMessages(&error); 189 } 190 191 bool canGenerateCallStack = !isWorkerAgent() && m_frontend; 192 addConsoleMessage(adoptPtr(new ConsoleMessage(canGenerateCallStack, source, type, level, message, scriptId, lineNumber, columnNumber, state, requestIdentifier))); 193} 194 195Vector<unsigned> InspectorConsoleAgent::consoleMessageArgumentCounts() 196{ 197 Vector<unsigned> result(m_consoleMessages.size()); 198 for (size_t i = 0; i < m_consoleMessages.size(); i++) 199 result[i] = m_consoleMessages[i]->argumentCount(); 200 return result; 201} 202 203void InspectorConsoleAgent::startTiming(const String& title) 204{ 205 // Follow Firebug's behavior of requiring a title that is not null or 206 // undefined for timing functions 207 if (title.isNull()) 208 return; 209 210 m_times.add(title, monotonicallyIncreasingTime()); 211} 212 213void InspectorConsoleAgent::stopTiming(const String& title, PassRefPtr<ScriptCallStack> callStack) 214{ 215 // Follow Firebug's behavior of requiring a title that is not null or 216 // undefined for timing functions 217 if (title.isNull()) 218 return; 219 220 HashMap<String, double>::iterator it = m_times.find(title); 221 if (it == m_times.end()) 222 return; 223 224 double startTime = it->value; 225 m_times.remove(it); 226 227 double elapsed = monotonicallyIncreasingTime() - startTime; 228 String message = title + String::format(": %.3fms", elapsed * 1000); 229 addMessageToConsole(ConsoleAPIMessageSource, TimingMessageType, DebugMessageLevel, message, callStack); 230} 231 232void InspectorConsoleAgent::count(ScriptState* state, PassRefPtr<ScriptArguments> arguments) 233{ 234 RefPtr<ScriptCallStack> callStack(createScriptCallStackForConsole(state)); 235 const ScriptCallFrame& lastCaller = callStack->at(0); 236 // Follow Firebug's behavior of counting with null and undefined title in 237 // the same bucket as no argument 238 String title; 239 arguments->getFirstArgumentAsString(title); 240 String identifier = title + '@' + lastCaller.sourceURL() + ':' + String::number(lastCaller.lineNumber()); 241 242 HashMap<String, unsigned>::iterator it = m_counts.find(identifier); 243 int count; 244 if (it == m_counts.end()) 245 count = 1; 246 else { 247 count = it->value + 1; 248 m_counts.remove(it); 249 } 250 251 m_counts.add(identifier, count); 252 253 String message = title + ": " + String::number(count); 254 addMessageToConsole(ConsoleAPIMessageSource, LogMessageType, DebugMessageLevel, message, callStack); 255} 256 257void InspectorConsoleAgent::frameWindowDiscarded(DOMWindow* window) 258{ 259 size_t messageCount = m_consoleMessages.size(); 260 for (size_t i = 0; i < messageCount; ++i) 261 m_consoleMessages[i]->windowCleared(window); 262 m_injectedScriptManager->discardInjectedScriptsFor(window); 263} 264 265void InspectorConsoleAgent::didFinishXHRLoading(unsigned long requestIdentifier, const String& url, const String& sendURL, unsigned sendLineNumber) 266{ 267 if (!developerExtrasEnabled()) 268 return; 269 if (m_frontend && m_state->getBoolean(ConsoleAgentState::monitoringXHR)) { 270 String message = "XHR finished loading: \"" + url + "\"."; 271 // FIXME: <http://webkit.org/b/114316> InspectorConsoleAgent::didFinishXHRLoading ConsoleMessage should include a column number 272 addMessageToConsole(NetworkMessageSource, LogMessageType, DebugMessageLevel, message, sendURL, sendLineNumber, 0, 0, requestIdentifier); 273 } 274} 275 276void InspectorConsoleAgent::didReceiveResponse(unsigned long requestIdentifier, const ResourceResponse& response) 277{ 278 if (!developerExtrasEnabled()) 279 return; 280 281 if (response.httpStatusCode() >= 400) { 282 String message = "Failed to load resource: the server responded with a status of " + String::number(response.httpStatusCode()) + " (" + response.httpStatusText() + ')'; 283 addMessageToConsole(NetworkMessageSource, LogMessageType, ErrorMessageLevel, message, response.url().string(), 0, 0, 0, requestIdentifier); 284 } 285} 286 287void InspectorConsoleAgent::didFailLoading(unsigned long requestIdentifier, const ResourceError& error) 288{ 289 if (!developerExtrasEnabled()) 290 return; 291 if (error.isCancellation()) // Report failures only. 292 return; 293 StringBuilder message; 294 message.appendLiteral("Failed to load resource"); 295 if (!error.localizedDescription().isEmpty()) { 296 message.appendLiteral(": "); 297 message.append(error.localizedDescription()); 298 } 299 addMessageToConsole(NetworkMessageSource, LogMessageType, ErrorMessageLevel, message.toString(), error.failingURL(), 0, 0, 0, requestIdentifier); 300} 301 302void InspectorConsoleAgent::setMonitoringXHREnabled(ErrorString*, bool enabled) 303{ 304 m_state->setBoolean(ConsoleAgentState::monitoringXHR, enabled); 305} 306 307static bool isGroupMessage(MessageType type) 308{ 309 return type == StartGroupMessageType 310 || type == StartGroupCollapsedMessageType 311 || type == EndGroupMessageType; 312} 313 314void InspectorConsoleAgent::addConsoleMessage(PassOwnPtr<ConsoleMessage> consoleMessage) 315{ 316 ASSERT(developerExtrasEnabled()); 317 ASSERT_ARG(consoleMessage, consoleMessage); 318 319 if (m_previousMessage && !isGroupMessage(m_previousMessage->type()) && m_previousMessage->isEqual(consoleMessage.get())) { 320 m_previousMessage->incrementCount(); 321 if (m_frontend && m_enabled) 322 m_previousMessage->updateRepeatCountInConsole(m_frontend); 323 } else { 324 m_previousMessage = consoleMessage.get(); 325 m_consoleMessages.append(consoleMessage); 326 if (m_frontend && m_enabled) 327 m_previousMessage->addToFrontend(m_frontend, m_injectedScriptManager, true); 328 } 329 330 if (!m_frontend && m_consoleMessages.size() >= maximumConsoleMessages) { 331 m_expiredConsoleMessageCount += expireConsoleMessagesStep; 332 m_consoleMessages.remove(0, expireConsoleMessagesStep); 333 } 334} 335 336class InspectableHeapObject : public InjectedScriptHost::InspectableObject { 337public: 338 explicit InspectableHeapObject(int heapObjectId) : m_heapObjectId(heapObjectId) { } 339 virtual ScriptValue get(ScriptState*) 340 { 341 return ScriptProfiler::objectByHeapObjectId(m_heapObjectId); 342 } 343private: 344 int m_heapObjectId; 345}; 346 347void InspectorConsoleAgent::addInspectedHeapObject(ErrorString*, int inspectedHeapObjectId) 348{ 349 m_injectedScriptManager->injectedScriptHost()->addInspectedObject(adoptPtr(new InspectableHeapObject(inspectedHeapObjectId))); 350} 351 352} // namespace WebCore 353 354#endif // ENABLE(INSPECTOR) 355