2 * Copyright (C) 2014-2019 Apple Inc. All rights reserved.
3 * Copyright (C) 2011 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "InspectorConsoleAgent.h"
29 #include "ConsoleMessage.h"
30 #include "InjectedScriptManager.h"
31 #include "InspectorHeapAgent.h"
32 #include "ScriptCallStackFactory.h"
33 #include <wtf/text/StringConcatenateNumbers.h>
37 static constexpr unsigned maximumConsoleMessages = 100;
38 static constexpr int expireConsoleMessagesStep = 10;
40 InspectorConsoleAgent::InspectorConsoleAgent(AgentContext& context)
41 : InspectorAgentBase("Console"_s)
42 , m_injectedScriptManager(context.injectedScriptManager)
43 , m_frontendDispatcher(makeUnique<ConsoleFrontendDispatcher>(context.frontendRouter))
44 , m_backendDispatcher(ConsoleBackendDispatcher::create(context.backendDispatcher, this))
48 InspectorConsoleAgent::~InspectorConsoleAgent() = default;
50 void InspectorConsoleAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
54 void InspectorConsoleAgent::willDestroyFrontendAndBackend(DisconnectReason)
59 void InspectorConsoleAgent::discardValues()
61 m_consoleMessages.clear();
62 m_expiredConsoleMessageCount = 0;
65 Protocol::ErrorStringOr<void> InspectorConsoleAgent::enable()
72 if (m_expiredConsoleMessageCount) {
73 ConsoleMessage expiredMessage(MessageSource::Other, MessageType::Log, MessageLevel::Warning, makeString(m_expiredConsoleMessageCount, " console messages are not shown."));
74 expiredMessage.addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false);
77 Vector<std::unique_ptr<ConsoleMessage>> messages;
78 m_consoleMessages.swap(messages);
80 for (size_t i = 0; i < messages.size(); ++i)
81 messages[i]->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false);
86 Protocol::ErrorStringOr<void> InspectorConsoleAgent::disable()
96 Protocol::ErrorStringOr<void> InspectorConsoleAgent::clearMessages()
98 m_consoleMessages.clear();
99 m_expiredConsoleMessageCount = 0;
101 m_injectedScriptManager.releaseObjectGroup("console"_s);
104 m_frontendDispatcher->messagesCleared();
109 bool InspectorConsoleAgent::developerExtrasEnabled() const
111 return m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled();
114 void InspectorConsoleAgent::reset()
122 void InspectorConsoleAgent::addMessageToConsole(std::unique_ptr<ConsoleMessage> message)
124 if (message->type() == MessageType::Clear)
127 addConsoleMessage(WTFMove(message));
130 void InspectorConsoleAgent::startTiming(JSC::JSGlobalObject* globalObject, const String& label)
132 ASSERT(!label.isNull());
136 auto result = m_times.add(label, MonotonicTime::now());
138 if (!result.isNewEntry) {
139 // FIXME: Send an enum to the frontend for localization?
140 String warning = makeString("Timer \"", ScriptArguments::truncateStringForConsoleMessage(label), "\" already exists");
141 addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, createScriptCallStackForConsole(globalObject, 1)));
145 void InspectorConsoleAgent::logTiming(JSC::JSGlobalObject* globalObject, const String& label, Ref<ScriptArguments>&& arguments)
147 ASSERT(!label.isNull());
151 auto callStack = createScriptCallStackForConsole(globalObject, 1);
153 auto it = m_times.find(label);
154 if (it == m_times.end()) {
155 // FIXME: Send an enum to the frontend for localization?
156 String warning = makeString("Timer \"", ScriptArguments::truncateStringForConsoleMessage(label), "\" does not exist");
157 addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, WTFMove(callStack)));
161 MonotonicTime startTime = it->value;
162 Seconds elapsed = MonotonicTime::now() - startTime;
163 String message = makeString(ScriptArguments::truncateStringForConsoleMessage(label), ": ", FormattedNumber::fixedWidth(elapsed.milliseconds(), 3), "ms");
164 addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, WTFMove(arguments), WTFMove(callStack)));
167 void InspectorConsoleAgent::stopTiming(JSC::JSGlobalObject* globalObject, const String& label)
169 ASSERT(!label.isNull());
173 auto callStack = createScriptCallStackForConsole(globalObject, 1);
175 auto it = m_times.find(label);
176 if (it == m_times.end()) {
177 // FIXME: Send an enum to the frontend for localization?
178 String warning = makeString("Timer \"", ScriptArguments::truncateStringForConsoleMessage(label), "\" does not exist");
179 addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, WTFMove(callStack)));
183 MonotonicTime startTime = it->value;
184 Seconds elapsed = MonotonicTime::now() - startTime;
185 String message = makeString(ScriptArguments::truncateStringForConsoleMessage(label), ": ", FormattedNumber::fixedWidth(elapsed.milliseconds(), 3), "ms");
186 addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, WTFMove(callStack)));
191 void InspectorConsoleAgent::takeHeapSnapshot(const String& title)
196 auto result = m_heapAgent->snapshot();
200 auto [timestamp, snapshotData] = WTFMove(result.value());
201 m_frontendDispatcher->heapSnapshot(timestamp, snapshotData, title);
204 void InspectorConsoleAgent::count(JSC::JSGlobalObject* globalObject, const String& label)
206 auto result = m_counts.add(label, 1);
207 if (!result.isNewEntry)
208 result.iterator->value += 1;
210 // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value.
212 String message = makeString(ScriptArguments::truncateStringForConsoleMessage(label), ": ", result.iterator->value);
213 addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Debug, message, createScriptCallStackForConsole(globalObject, 1)));
216 void InspectorConsoleAgent::countReset(JSC::JSGlobalObject* globalObject, const String& label)
218 auto it = m_counts.find(label);
219 if (it == m_counts.end()) {
220 // FIXME: Send an enum to the frontend for localization?
221 String warning = makeString("Counter \"", ScriptArguments::truncateStringForConsoleMessage(label), "\" does not exist");
222 addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Warning, warning, createScriptCallStackForConsole(globalObject, 1)));
228 // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value.
231 static bool isGroupMessage(MessageType type)
233 return type == MessageType::StartGroup
234 || type == MessageType::StartGroupCollapsed
235 || type == MessageType::EndGroup;
238 void InspectorConsoleAgent::addConsoleMessage(std::unique_ptr<ConsoleMessage> consoleMessage)
240 ASSERT_ARG(consoleMessage, consoleMessage);
242 ConsoleMessage* previousMessage = m_consoleMessages.isEmpty() ? nullptr : m_consoleMessages.last().get();
244 if (previousMessage && !isGroupMessage(previousMessage->type()) && previousMessage->isEqual(consoleMessage.get())) {
245 previousMessage->incrementCount();
247 previousMessage->updateRepeatCountInConsole(*m_frontendDispatcher);
249 ConsoleMessage* newMessage = consoleMessage.get();
250 m_consoleMessages.append(WTFMove(consoleMessage));
252 newMessage->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, true);
254 if (m_consoleMessages.size() >= maximumConsoleMessages) {
255 m_expiredConsoleMessageCount += expireConsoleMessagesStep;
256 m_consoleMessages.remove(0, expireConsoleMessagesStep);
261 Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::Console::Channel>>> InspectorConsoleAgent::getLoggingChannels()
263 // Default implementation has no logging channels.
264 return JSON::ArrayOf<Protocol::Console::Channel>::create();
267 Protocol::ErrorStringOr<void> InspectorConsoleAgent::setLoggingChannelLevel(Protocol::Console::ChannelSource, Protocol::Console::ChannelLevel)
269 return makeUnexpected("Not supported"_s);
272 } // namespace Inspector