Web Inspector: We should have a way to capture heap snapshots programatically.
[WebKit-https.git] / Source / JavaScriptCore / inspector / agents / InspectorConsoleAgent.cpp
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/CurrentTime.h>
39 #include <wtf/text/StringBuilder.h>
40 #include <wtf/text/WTFString.h>
41
42 namespace Inspector {
43
44 static const unsigned maximumConsoleMessages = 100;
45 static const int expireConsoleMessagesStep = 10;
46
47 InspectorConsoleAgent::InspectorConsoleAgent(AgentContext& context, InspectorHeapAgent* heapAgent)
48     : InspectorAgentBase(ASCIILiteral("Console"))
49     , m_injectedScriptManager(context.injectedScriptManager)
50     , m_frontendDispatcher(std::make_unique<ConsoleFrontendDispatcher>(context.frontendRouter))
51     , m_backendDispatcher(ConsoleBackendDispatcher::create(context.backendDispatcher, this))
52     , m_heapAgent(heapAgent)
53 {
54 }
55
56 InspectorConsoleAgent::~InspectorConsoleAgent()
57 {
58 }
59
60 void InspectorConsoleAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
61 {
62 }
63
64 void InspectorConsoleAgent::willDestroyFrontendAndBackend(DisconnectReason)
65 {
66     String errorString;
67     disable(errorString);
68 }
69
70 void InspectorConsoleAgent::enable(ErrorString&)
71 {
72     if (m_enabled)
73         return;
74
75     m_enabled = true;
76
77     if (m_expiredConsoleMessageCount) {
78         ConsoleMessage expiredMessage(MessageSource::Other, MessageType::Log, MessageLevel::Warning, String::format("%d console messages are not shown.", m_expiredConsoleMessageCount));
79         expiredMessage.addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false);
80     }
81
82     size_t messageCount = m_consoleMessages.size();
83     for (size_t i = 0; i < messageCount; ++i)
84         m_consoleMessages[i]->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false);
85 }
86
87 void InspectorConsoleAgent::disable(ErrorString&)
88 {
89     if (!m_enabled)
90         return;
91
92     m_enabled = false;
93 }
94
95 void InspectorConsoleAgent::clearMessages(ErrorString&)
96 {
97     m_consoleMessages.clear();
98     m_expiredConsoleMessageCount = 0;
99     m_previousMessage = nullptr;
100
101     m_injectedScriptManager.releaseObjectGroup(ASCIILiteral("console"));
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     // Follow Firebug's behavior of requiring a title that is not null or
132     // undefined for timing functions
133     if (title.isNull())
134         return;
135
136     m_times.add(title, monotonicallyIncreasingTime());
137 }
138
139 void InspectorConsoleAgent::stopTiming(const String& title, PassRefPtr<ScriptCallStack> callStack)
140 {
141     // Follow Firebug's behavior of requiring a title that is not null or
142     // undefined for timing functions
143     if (title.isNull())
144         return;
145
146     HashMap<String, double>::iterator it = m_times.find(title);
147     if (it == m_times.end())
148         return;
149
150     double startTime = it->value;
151     m_times.remove(it);
152
153     double elapsed = monotonicallyIncreasingTime() - startTime;
154     String message = title + String::format(": %.3fms", elapsed * 1000);
155     addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, callStack));
156 }
157
158 void InspectorConsoleAgent::takeHeapSnapshot(const String& title)
159 {
160     if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled())
161         return;
162
163     ErrorString ignored;
164     double timestamp;
165     String snapshotData;
166     m_heapAgent->snapshot(ignored, &timestamp, &snapshotData);
167
168     m_frontendDispatcher->heapSnapshot(timestamp, snapshotData, title.isEmpty() ? nullptr : &title);
169 }
170
171 void InspectorConsoleAgent::count(JSC::ExecState* state, PassRefPtr<ScriptArguments> arguments)
172 {
173     RefPtr<ScriptCallStack> callStack(createScriptCallStackForConsole(state, ScriptCallStack::maxCallStackSizeToCapture));
174     const ScriptCallFrame& lastCaller = callStack->at(0);
175     // Follow Firebug's behavior of counting with null and undefined title in
176     // the same bucket as no argument
177     String title;
178     arguments->getFirstArgumentAsString(title);
179     String identifier = title + '@' + lastCaller.sourceURL() + ':' + String::number(lastCaller.lineNumber());
180
181     HashMap<String, unsigned>::iterator it = m_counts.find(identifier);
182     int count;
183     if (it == m_counts.end())
184         count = 1;
185     else {
186         count = it->value + 1;
187         m_counts.remove(it);
188     }
189
190     m_counts.add(identifier, count);
191
192     String message;
193     if (title.isEmpty())
194         message = "<no label>: " + String::number(count);
195     else
196         message = title + ": " + String::number(count);
197
198     addMessageToConsole(std::make_unique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Debug, message, callStack));
199 }
200
201 static bool isGroupMessage(MessageType type)
202 {
203     return type == MessageType::StartGroup
204         || type == MessageType::StartGroupCollapsed
205         || type == MessageType::EndGroup;
206 }
207
208 void InspectorConsoleAgent::addConsoleMessage(std::unique_ptr<ConsoleMessage> consoleMessage)
209 {
210     ASSERT(m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled());
211     ASSERT_ARG(consoleMessage, consoleMessage);
212
213     if (m_previousMessage && !isGroupMessage(m_previousMessage->type()) && m_previousMessage->isEqual(consoleMessage.get())) {
214         m_previousMessage->incrementCount();
215         if (m_enabled)
216             m_previousMessage->updateRepeatCountInConsole(*m_frontendDispatcher);
217     } else {
218         m_previousMessage = consoleMessage.get();
219         m_consoleMessages.append(WTFMove(consoleMessage));
220         if (m_enabled)
221             m_previousMessage->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, true);
222     }
223
224     if (m_consoleMessages.size() >= maximumConsoleMessages) {
225         m_expiredConsoleMessageCount += expireConsoleMessagesStep;
226         m_consoleMessages.remove(0, expireConsoleMessagesStep);
227     }
228 }
229
230 } // namespace Inspector