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 | |
40 | namespace Inspector { |
41 | |
42 | static const unsigned maximumConsoleMessages = 100; |
43 | static const int expireConsoleMessagesStep = 10; |
44 | |
45 | InspectorConsoleAgent::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 | |
53 | void InspectorConsoleAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*) |
54 | { |
55 | } |
56 | |
57 | void InspectorConsoleAgent::willDestroyFrontendAndBackend(DisconnectReason) |
58 | { |
59 | String errorString; |
60 | disable(errorString); |
61 | } |
62 | |
63 | void InspectorConsoleAgent::discardValues() |
64 | { |
65 | m_consoleMessages.clear(); |
66 | m_expiredConsoleMessageCount = 0; |
67 | } |
68 | |
69 | void 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 | |
88 | void InspectorConsoleAgent::disable(ErrorString&) |
89 | { |
90 | if (!m_enabled) |
91 | return; |
92 | |
93 | m_enabled = false; |
94 | } |
95 | |
96 | void 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 | |
107 | void InspectorConsoleAgent::reset() |
108 | { |
109 | ErrorString unused; |
110 | clearMessages(unused); |
111 | |
112 | m_times.clear(); |
113 | m_counts.clear(); |
114 | } |
115 | |
116 | void 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 | |
129 | void InspectorConsoleAgent::startTiming(const String& title) |
130 | { |
131 | if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
132 | return; |
133 | |
134 | ASSERT(!title.isNull()); |
135 | if (title.isNull()) |
136 | return; |
137 | |
138 | auto result = m_times.add(title, MonotonicTime::now()); |
139 | |
140 | if (!result.isNewEntry) { |
141 | // FIXME: Send an enum to the frontend for localization? |
142 | String warning = makeString("Timer \"" , title, "\" already exists" ); |
143 | addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning)); |
144 | } |
145 | } |
146 | |
147 | void InspectorConsoleAgent::stopTiming(const String& title, Ref<ScriptCallStack>&& callStack) |
148 | { |
149 | if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
150 | return; |
151 | |
152 | ASSERT(!title.isNull()); |
153 | if (title.isNull()) |
154 | return; |
155 | |
156 | auto it = m_times.find(title); |
157 | if (it == m_times.end()) { |
158 | // FIXME: Send an enum to the frontend for localization? |
159 | String warning = makeString("Timer \"" , title, "\" does not exist" ); |
160 | addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning)); |
161 | return; |
162 | } |
163 | |
164 | MonotonicTime startTime = it->value; |
165 | m_times.remove(it); |
166 | |
167 | Seconds elapsed = MonotonicTime::now() - startTime; |
168 | String message = makeString(title, ": " , FormattedNumber::fixedWidth(elapsed.milliseconds(), 3), "ms" ); |
169 | addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, WTFMove(callStack))); |
170 | } |
171 | |
172 | void InspectorConsoleAgent::takeHeapSnapshot(const String& title) |
173 | { |
174 | if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
175 | return; |
176 | |
177 | if (!m_heapAgent) |
178 | return; |
179 | |
180 | ErrorString ignored; |
181 | double timestamp; |
182 | String snapshotData; |
183 | m_heapAgent->snapshot(ignored, ×tamp, &snapshotData); |
184 | |
185 | m_frontendDispatcher->heapSnapshot(timestamp, snapshotData, title.isEmpty() ? nullptr : &title); |
186 | } |
187 | |
188 | void InspectorConsoleAgent::count(JSC::ExecState* state, Ref<ScriptArguments>&& arguments) |
189 | { |
190 | if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
191 | return; |
192 | |
193 | Ref<ScriptCallStack> callStack = createScriptCallStackForConsole(state); |
194 | |
195 | String title; |
196 | String identifier; |
197 | if (!arguments->argumentCount()) { |
198 | // '@' prefix for engine generated labels. |
199 | title = "Global"_s ; |
200 | identifier = makeString('@', title); |
201 | } else { |
202 | // '#' prefix for user labels. |
203 | arguments->getFirstArgumentAsString(title); |
204 | identifier = makeString('#', title); |
205 | } |
206 | |
207 | auto result = m_counts.add(identifier, 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(title, ": " , result.iterator->value); |
214 | addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Debug, message, WTFMove(callStack))); |
215 | } |
216 | |
217 | static bool isGroupMessage(MessageType type) |
218 | { |
219 | return type == MessageType::StartGroup |
220 | || type == MessageType::StartGroupCollapsed |
221 | || type == MessageType::EndGroup; |
222 | } |
223 | |
224 | void InspectorConsoleAgent::addConsoleMessage(std::unique_ptr<ConsoleMessage> consoleMessage) |
225 | { |
226 | if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
227 | return; |
228 | |
229 | ASSERT_ARG(consoleMessage, consoleMessage); |
230 | |
231 | ConsoleMessage* previousMessage = m_consoleMessages.isEmpty() ? nullptr : m_consoleMessages.last().get(); |
232 | |
233 | if (previousMessage && !isGroupMessage(previousMessage->type()) && previousMessage->isEqual(consoleMessage.get())) { |
234 | previousMessage->incrementCount(); |
235 | if (m_enabled) |
236 | previousMessage->updateRepeatCountInConsole(*m_frontendDispatcher); |
237 | } else { |
238 | ConsoleMessage* newMessage = consoleMessage.get(); |
239 | m_consoleMessages.append(WTFMove(consoleMessage)); |
240 | if (m_enabled) |
241 | newMessage->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, true); |
242 | |
243 | if (m_consoleMessages.size() >= maximumConsoleMessages) { |
244 | m_expiredConsoleMessageCount += expireConsoleMessagesStep; |
245 | m_consoleMessages.remove(0, expireConsoleMessagesStep); |
246 | } |
247 | } |
248 | } |
249 | |
250 | void InspectorConsoleAgent::getLoggingChannels(ErrorString&, RefPtr<JSON::ArrayOf<Protocol::Console::Channel>>& channels) |
251 | { |
252 | // Default implementation has no logging channels. |
253 | channels = JSON::ArrayOf<Protocol::Console::Channel>::create(); |
254 | } |
255 | |
256 | void InspectorConsoleAgent::setLoggingChannelLevel(ErrorString& errorString, const String&, const String&) |
257 | { |
258 | errorString = "No such channel to enable"_s ; |
259 | } |
260 | |
261 | } // namespace Inspector |
262 | |