55fcebebaff469c368b3bd8f9bc6e5d075ce6622
[WebKit-https.git] / Source / JavaScriptCore / inspector / agents / InspectorConsoleAgent.cpp
1 /*
2  * Copyright (C) 2014-2019 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 "InspectorHeapAgent.h"
32 #include "ScriptCallStackFactory.h"
33 #include <wtf/text/StringConcatenateNumbers.h>
34
35 namespace Inspector {
36
37 static constexpr unsigned maximumConsoleMessages = 100;
38 static constexpr int expireConsoleMessagesStep = 10;
39
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))
45 {
46 }
47
48 InspectorConsoleAgent::~InspectorConsoleAgent() = default;
49
50 void InspectorConsoleAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
51 {
52 }
53
54 void InspectorConsoleAgent::willDestroyFrontendAndBackend(DisconnectReason)
55 {
56     disable();
57 }
58
59 void InspectorConsoleAgent::discardValues()
60 {
61     m_consoleMessages.clear();
62     m_expiredConsoleMessageCount = 0;
63 }
64
65 Protocol::ErrorStringOr<void> InspectorConsoleAgent::enable()
66 {
67     if (m_enabled)
68         return { };
69
70     m_enabled = true;
71
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);
75     }
76
77     Vector<std::unique_ptr<ConsoleMessage>> messages;
78     m_consoleMessages.swap(messages);
79
80     for (size_t i = 0; i < messages.size(); ++i)
81         messages[i]->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false);
82
83     return { };
84 }
85
86 Protocol::ErrorStringOr<void> InspectorConsoleAgent::disable()
87 {
88     if (!m_enabled)
89         return { };
90
91     m_enabled = false;
92
93     return { };
94 }
95
96 Protocol::ErrorStringOr<void> InspectorConsoleAgent::clearMessages()
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     return { };
107 }
108
109 bool InspectorConsoleAgent::developerExtrasEnabled() const
110 {
111     return m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled();
112 }
113
114 void InspectorConsoleAgent::reset()
115 {
116     clearMessages();
117
118     m_times.clear();
119     m_counts.clear();
120 }
121
122 void InspectorConsoleAgent::addMessageToConsole(std::unique_ptr<ConsoleMessage> message)
123 {
124     if (message->type() == MessageType::Clear)
125         clearMessages();
126
127     addConsoleMessage(WTFMove(message));
128 }
129
130 void InspectorConsoleAgent::startTiming(JSC::JSGlobalObject* globalObject, const String& label)
131 {
132     ASSERT(!label.isNull());
133     if (label.isNull())
134         return;
135
136     auto result = m_times.add(label, MonotonicTime::now());
137
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)));
142     }
143 }
144
145 void InspectorConsoleAgent::logTiming(JSC::JSGlobalObject* globalObject, const String& label, Ref<ScriptArguments>&& arguments)
146 {
147     ASSERT(!label.isNull());
148     if (label.isNull())
149         return;
150
151     auto callStack = createScriptCallStackForConsole(globalObject, 1);
152
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)));
158         return;
159     }
160
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)));
165 }
166
167 void InspectorConsoleAgent::stopTiming(JSC::JSGlobalObject* globalObject, const String& label)
168 {
169     ASSERT(!label.isNull());
170     if (label.isNull())
171         return;
172
173     auto callStack = createScriptCallStackForConsole(globalObject, 1);
174
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)));
180         return;
181     }
182
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)));
187
188     m_times.remove(it);
189 }
190
191 void InspectorConsoleAgent::takeHeapSnapshot(const String& title)
192 {
193     if (!m_heapAgent)
194         return;
195
196     auto result = m_heapAgent->snapshot();
197     if (!result)
198         return;
199
200     auto [timestamp, snapshotData] = WTFMove(result.value());
201     m_frontendDispatcher->heapSnapshot(timestamp, snapshotData, title);
202 }
203
204 void InspectorConsoleAgent::count(JSC::JSGlobalObject* globalObject, const String& label)
205 {
206     auto result = m_counts.add(label, 1);
207     if (!result.isNewEntry)
208         result.iterator->value += 1;
209
210     // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value.
211
212     String message = makeString(ScriptArguments::truncateStringForConsoleMessage(label), ": ", result.iterator->value);
213     addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Debug, message, createScriptCallStackForConsole(globalObject, 1)));
214 }
215
216 void InspectorConsoleAgent::countReset(JSC::JSGlobalObject* globalObject, const String& label)
217 {
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)));
223         return;
224     }
225
226     it->value = 0;
227
228     // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value.
229 }
230
231 static bool isGroupMessage(MessageType type)
232 {
233     return type == MessageType::StartGroup
234         || type == MessageType::StartGroupCollapsed
235         || type == MessageType::EndGroup;
236 }
237
238 void InspectorConsoleAgent::addConsoleMessage(std::unique_ptr<ConsoleMessage> consoleMessage)
239 {
240     ASSERT_ARG(consoleMessage, consoleMessage);
241
242     ConsoleMessage* previousMessage = m_consoleMessages.isEmpty() ? nullptr : m_consoleMessages.last().get();
243
244     if (previousMessage && !isGroupMessage(previousMessage->type()) && previousMessage->isEqual(consoleMessage.get())) {
245         previousMessage->incrementCount();
246         if (m_enabled)
247             previousMessage->updateRepeatCountInConsole(*m_frontendDispatcher);
248     } else {
249         ConsoleMessage* newMessage = consoleMessage.get();
250         m_consoleMessages.append(WTFMove(consoleMessage));
251         if (m_enabled)
252             newMessage->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, true);
253
254         if (m_consoleMessages.size() >= maximumConsoleMessages) {
255             m_expiredConsoleMessageCount += expireConsoleMessagesStep;
256             m_consoleMessages.remove(0, expireConsoleMessagesStep);
257         }
258     }
259 }
260
261 Protocol::ErrorStringOr<Ref<JSON::ArrayOf<Protocol::Console::Channel>>> InspectorConsoleAgent::getLoggingChannels()
262 {
263     // Default implementation has no logging channels.
264     return JSON::ArrayOf<Protocol::Console::Channel>::create();
265 }
266
267 Protocol::ErrorStringOr<void> InspectorConsoleAgent::setLoggingChannelLevel(Protocol::Console::ChannelSource, Protocol::Console::ChannelLevel)
268 {
269     return makeUnexpected("Not supported"_s);
270 }
271
272 } // namespace Inspector