1/*
2 * Copyright (C) 2014, 2015 Apple Inc. All rights reserved.
3 * Copyright (C) 2011 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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.
13 *
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.
24 */
25
26#include "config.h"
27#include "InspectorConsoleAgent.h"
28
29#include "ConsoleMessage.h"
30#include "InjectedScriptManager.h"
31#include "InspectorFrontendRouter.h"
32#include "InspectorHeapAgent.h"
33#include "ScriptArguments.h"
34#include "ScriptCallFrame.h"
35#include "ScriptCallStack.h"
36#include "ScriptCallStackFactory.h"
37#include "ScriptObject.h"
38#include <wtf/text/StringConcatenateNumbers.h>
39
40namespace Inspector {
41
42static const unsigned maximumConsoleMessages = 100;
43static const int expireConsoleMessagesStep = 10;
44
45InspectorConsoleAgent::InspectorConsoleAgent(AgentContext& context)
46 : InspectorAgentBase("Console"_s)
47 , m_injectedScriptManager(context.injectedScriptManager)
48 , m_frontendDispatcher(std::make_unique<ConsoleFrontendDispatcher>(context.frontendRouter))
49 , m_backendDispatcher(ConsoleBackendDispatcher::create(context.backendDispatcher, this))
50{
51}
52
53void InspectorConsoleAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
54{
55}
56
57void InspectorConsoleAgent::willDestroyFrontendAndBackend(DisconnectReason)
58{
59 String errorString;
60 disable(errorString);
61}
62
63void InspectorConsoleAgent::discardValues()
64{
65 m_consoleMessages.clear();
66 m_expiredConsoleMessageCount = 0;
67}
68
69void InspectorConsoleAgent::enable(ErrorString&)
70{
71 if (m_enabled)
72 return;
73
74 m_enabled = true;
75
76 if (m_expiredConsoleMessageCount) {
77 ConsoleMessage expiredMessage(MessageSource::Other, MessageType::Log, MessageLevel::Warning, makeString(m_expiredConsoleMessageCount, " console messages are not shown."));
78 expiredMessage.addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false);
79 }
80
81 Vector<std::unique_ptr<ConsoleMessage>> messages;
82 m_consoleMessages.swap(messages);
83
84 for (size_t i = 0; i < messages.size(); ++i)
85 messages[i]->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false);
86}
87
88void InspectorConsoleAgent::disable(ErrorString&)
89{
90 if (!m_enabled)
91 return;
92
93 m_enabled = false;
94}
95
96void InspectorConsoleAgent::clearMessages(ErrorString&)
97{
98 m_consoleMessages.clear();
99 m_expiredConsoleMessageCount = 0;
100
101 m_injectedScriptManager.releaseObjectGroup("console"_s);
102
103 if (m_enabled)
104 m_frontendDispatcher->messagesCleared();
105}
106
107void InspectorConsoleAgent::reset()
108{
109 ErrorString unused;
110 clearMessages(unused);
111
112 m_times.clear();
113 m_counts.clear();
114}
115
116void InspectorConsoleAgent::addMessageToConsole(std::unique_ptr<ConsoleMessage> message)
117{
118 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
119 return;
120
121 if (message->type() == MessageType::Clear) {
122 ErrorString unused;
123 clearMessages(unused);
124 }
125
126 addConsoleMessage(WTFMove(message));
127}
128
129void InspectorConsoleAgent::startTiming(JSC::ExecState* exec, const String& label)
130{
131 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
132 return;
133
134 ASSERT(!label.isNull());
135 if (label.isNull())
136 return;
137
138 auto result = m_times.add(label, MonotonicTime::now());
139
140 if (!result.isNewEntry) {
141 // FIXME: Send an enum to the frontend for localization?
142 String warning = makeString("Timer \"", label, "\" already exists");
143 addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, createScriptCallStackForConsole(exec, 1)));
144 }
145}
146
147void InspectorConsoleAgent::logTiming(JSC::ExecState* exec, const String& label, Ref<ScriptArguments>&& arguments)
148{
149 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
150 return;
151
152 ASSERT(!label.isNull());
153 if (label.isNull())
154 return;
155
156 auto callStack = createScriptCallStackForConsole(exec, 1);
157
158 auto it = m_times.find(label);
159 if (it == m_times.end()) {
160 // FIXME: Send an enum to the frontend for localization?
161 String warning = makeString("Timer \"", label, "\" does not exist");
162 addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, WTFMove(callStack)));
163 return;
164 }
165
166 MonotonicTime startTime = it->value;
167 Seconds elapsed = MonotonicTime::now() - startTime;
168 String message = makeString(label, ": ", FormattedNumber::fixedWidth(elapsed.milliseconds(), 3), "ms");
169 addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, WTFMove(arguments), WTFMove(callStack)));
170}
171
172void InspectorConsoleAgent::stopTiming(JSC::ExecState* exec, const String& label)
173{
174 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
175 return;
176
177 ASSERT(!label.isNull());
178 if (label.isNull())
179 return;
180
181 auto callStack = createScriptCallStackForConsole(exec, 1);
182
183 auto it = m_times.find(label);
184 if (it == m_times.end()) {
185 // FIXME: Send an enum to the frontend for localization?
186 String warning = makeString("Timer \"", label, "\" does not exist");
187 addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, WTFMove(callStack)));
188 return;
189 }
190
191 MonotonicTime startTime = it->value;
192 Seconds elapsed = MonotonicTime::now() - startTime;
193 String message = makeString(label, ": ", FormattedNumber::fixedWidth(elapsed.milliseconds(), 3), "ms");
194 addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, WTFMove(callStack)));
195
196 m_times.remove(it);
197}
198
199void InspectorConsoleAgent::takeHeapSnapshot(const String& title)
200{
201 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
202 return;
203
204 if (!m_heapAgent)
205 return;
206
207 ErrorString ignored;
208 double timestamp;
209 String snapshotData;
210 m_heapAgent->snapshot(ignored, &timestamp, &snapshotData);
211
212 m_frontendDispatcher->heapSnapshot(timestamp, snapshotData, title.isEmpty() ? nullptr : &title);
213}
214
215void InspectorConsoleAgent::count(JSC::ExecState* exec, const String& label)
216{
217 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
218 return;
219
220 auto result = m_counts.add(label, 1);
221 if (!result.isNewEntry)
222 result.iterator->value += 1;
223
224 // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value.
225
226 String message = makeString(label, ": ", result.iterator->value);
227 addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Debug, message, createScriptCallStackForConsole(exec, 1)));
228}
229
230void InspectorConsoleAgent::countReset(JSC::ExecState* exec, const String& label)
231{
232 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
233 return;
234
235 auto it = m_counts.find(label);
236 if (it == m_counts.end()) {
237 // FIXME: Send an enum to the frontend for localization?
238 String warning = makeString("Counter \"", label, "\" does not exist");
239 addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Warning, warning, createScriptCallStackForConsole(exec, 1)));
240 return;
241 }
242
243 it->value = 0;
244
245 // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value.
246}
247
248static bool isGroupMessage(MessageType type)
249{
250 return type == MessageType::StartGroup
251 || type == MessageType::StartGroupCollapsed
252 || type == MessageType::EndGroup;
253}
254
255void InspectorConsoleAgent::addConsoleMessage(std::unique_ptr<ConsoleMessage> consoleMessage)
256{
257 if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
258 return;
259
260 ASSERT_ARG(consoleMessage, consoleMessage);
261
262 ConsoleMessage* previousMessage = m_consoleMessages.isEmpty() ? nullptr : m_consoleMessages.last().get();
263
264 if (previousMessage && !isGroupMessage(previousMessage->type()) && previousMessage->isEqual(consoleMessage.get())) {
265 previousMessage->incrementCount();
266 if (m_enabled)
267 previousMessage->updateRepeatCountInConsole(*m_frontendDispatcher);
268 } else {
269 ConsoleMessage* newMessage = consoleMessage.get();
270 m_consoleMessages.append(WTFMove(consoleMessage));
271 if (m_enabled)
272 newMessage->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, true);
273
274 if (m_consoleMessages.size() >= maximumConsoleMessages) {
275 m_expiredConsoleMessageCount += expireConsoleMessagesStep;
276 m_consoleMessages.remove(0, expireConsoleMessagesStep);
277 }
278 }
279}
280
281void InspectorConsoleAgent::getLoggingChannels(ErrorString&, RefPtr<JSON::ArrayOf<Protocol::Console::Channel>>& channels)
282{
283 // Default implementation has no logging channels.
284 channels = JSON::ArrayOf<Protocol::Console::Channel>::create();
285}
286
287void InspectorConsoleAgent::setLoggingChannelLevel(ErrorString& errorString, const String&, const String&)
288{
289 errorString = "No such channel to enable"_s;
290}
291
292} // namespace Inspector
293