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