1/*
2 * Copyright (C) 2014 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#if ENABLE(INSPECTOR)
30
31#include "ConsoleMessage.h"
32#include "InjectedScriptManager.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
42namespace Inspector {
43
44static const unsigned maximumConsoleMessages = 1000;
45static const int expireConsoleMessagesStep = 100;
46
47InspectorConsoleAgent::InspectorConsoleAgent(InjectedScriptManager* injectedScriptManager)
48    : InspectorAgentBase(ASCIILiteral("Console"))
49    , m_injectedScriptManager(injectedScriptManager)
50    , m_previousMessage(nullptr)
51    , m_expiredConsoleMessageCount(0)
52    , m_enabled(false)
53{
54}
55
56InspectorConsoleAgent::~InspectorConsoleAgent()
57{
58}
59
60void InspectorConsoleAgent::didCreateFrontendAndBackend(Inspector::InspectorFrontendChannel* frontendChannel, InspectorBackendDispatcher* backendDispatcher)
61{
62    m_frontendDispatcher = std::make_unique<InspectorConsoleFrontendDispatcher>(frontendChannel);
63    m_backendDispatcher = InspectorConsoleBackendDispatcher::create(backendDispatcher, this);
64}
65
66void InspectorConsoleAgent::willDestroyFrontendAndBackend(InspectorDisconnectReason)
67{
68    m_frontendDispatcher = nullptr;
69    m_backendDispatcher.clear();
70
71    String errorString;
72    disable(&errorString);
73}
74
75void InspectorConsoleAgent::enable(ErrorString*)
76{
77    if (m_enabled)
78        return;
79
80    m_enabled = true;
81
82    if (m_expiredConsoleMessageCount) {
83        ConsoleMessage expiredMessage(MessageSource::Other, MessageType::Log, MessageLevel::Warning, String::format("%d console messages are not shown.", m_expiredConsoleMessageCount));
84        expiredMessage.addToFrontend(m_frontendDispatcher.get(), m_injectedScriptManager, false);
85    }
86
87    size_t messageCount = m_consoleMessages.size();
88    for (size_t i = 0; i < messageCount; ++i)
89        m_consoleMessages[i]->addToFrontend(m_frontendDispatcher.get(), m_injectedScriptManager, false);
90}
91
92void InspectorConsoleAgent::disable(ErrorString*)
93{
94    if (!m_enabled)
95        return;
96
97    m_enabled = false;
98}
99
100void InspectorConsoleAgent::clearMessages(ErrorString*)
101{
102    m_consoleMessages.clear();
103    m_expiredConsoleMessageCount = 0;
104    m_previousMessage = nullptr;
105
106    m_injectedScriptManager->releaseObjectGroup(ASCIILiteral("console"));
107
108    if (m_frontendDispatcher && m_enabled)
109        m_frontendDispatcher->messagesCleared();
110}
111
112void InspectorConsoleAgent::reset()
113{
114    ErrorString error;
115    clearMessages(&error);
116
117    m_times.clear();
118    m_counts.clear();
119}
120
121void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, PassRefPtr<ScriptCallStack> callStack, unsigned long requestIdentifier)
122{
123    if (!m_injectedScriptManager->inspectorEnvironment().developerExtrasEnabled())
124        return;
125
126    if (type == MessageType::Clear) {
127        ErrorString error;
128        clearMessages(&error);
129    }
130
131    addConsoleMessage(std::make_unique<ConsoleMessage>(source, type, level, message, callStack, requestIdentifier));
132}
133
134void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, JSC::ExecState* state, PassRefPtr<ScriptArguments> arguments, unsigned long requestIdentifier)
135{
136    if (!m_injectedScriptManager->inspectorEnvironment().developerExtrasEnabled())
137        return;
138
139    if (type == MessageType::Clear) {
140        ErrorString error;
141        clearMessages(&error);
142    }
143
144    addConsoleMessage(std::make_unique<ConsoleMessage>(source, type, level, message, arguments, state, requestIdentifier));
145}
146
147void InspectorConsoleAgent::addMessageToConsole(MessageSource source, MessageType type, MessageLevel level, const String& message, const String& scriptID, unsigned lineNumber, unsigned columnNumber, JSC::ExecState* state, unsigned long requestIdentifier)
148{
149    if (!m_injectedScriptManager->inspectorEnvironment().developerExtrasEnabled())
150        return;
151
152    if (type == MessageType::Clear) {
153        ErrorString error;
154        clearMessages(&error);
155    }
156
157    addConsoleMessage(std::make_unique<ConsoleMessage>(source, type, level, message, scriptID, lineNumber, columnNumber, state, requestIdentifier));
158}
159
160Vector<unsigned> InspectorConsoleAgent::consoleMessageArgumentCounts() const
161{
162    Vector<unsigned> result(m_consoleMessages.size());
163    for (size_t i = 0; i < m_consoleMessages.size(); i++)
164        result[i] = m_consoleMessages[i]->argumentCount();
165    return result;
166}
167
168void InspectorConsoleAgent::startTiming(const String& title)
169{
170    // Follow Firebug's behavior of requiring a title that is not null or
171    // undefined for timing functions
172    if (title.isNull())
173        return;
174
175    m_times.add(title, monotonicallyIncreasingTime());
176}
177
178void InspectorConsoleAgent::stopTiming(const String& title, PassRefPtr<ScriptCallStack> callStack)
179{
180    // Follow Firebug's behavior of requiring a title that is not null or
181    // undefined for timing functions
182    if (title.isNull())
183        return;
184
185    HashMap<String, double>::iterator it = m_times.find(title);
186    if (it == m_times.end())
187        return;
188
189    double startTime = it->value;
190    m_times.remove(it);
191
192    double elapsed = monotonicallyIncreasingTime() - startTime;
193    String message = title + String::format(": %.3fms", elapsed * 1000);
194    addMessageToConsole(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, callStack);
195}
196
197void InspectorConsoleAgent::count(JSC::ExecState* state, PassRefPtr<ScriptArguments> arguments)
198{
199    RefPtr<ScriptCallStack> callStack(createScriptCallStackForConsole(state, ScriptCallStack::maxCallStackSizeToCapture));
200    const ScriptCallFrame& lastCaller = callStack->at(0);
201    // Follow Firebug's behavior of counting with null and undefined title in
202    // the same bucket as no argument
203    String title;
204    arguments->getFirstArgumentAsString(title);
205    String identifier = title + '@' + lastCaller.sourceURL() + ':' + String::number(lastCaller.lineNumber());
206
207    HashMap<String, unsigned>::iterator it = m_counts.find(identifier);
208    int count;
209    if (it == m_counts.end())
210        count = 1;
211    else {
212        count = it->value + 1;
213        m_counts.remove(it);
214    }
215
216    m_counts.add(identifier, count);
217
218    String message = title + ": " + String::number(count);
219    addMessageToConsole(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Debug, message, callStack);
220}
221
222static bool isGroupMessage(MessageType type)
223{
224    return type == MessageType::StartGroup
225        || type == MessageType::StartGroupCollapsed
226        || type == MessageType::EndGroup;
227}
228
229void InspectorConsoleAgent::addConsoleMessage(std::unique_ptr<ConsoleMessage> consoleMessage)
230{
231    ASSERT(m_injectedScriptManager->inspectorEnvironment().developerExtrasEnabled());
232    ASSERT_ARG(consoleMessage, consoleMessage);
233
234    if (m_previousMessage && !isGroupMessage(m_previousMessage->type()) && m_previousMessage->isEqual(consoleMessage.get())) {
235        m_previousMessage->incrementCount();
236        if (m_frontendDispatcher && m_enabled)
237            m_previousMessage->updateRepeatCountInConsole(m_frontendDispatcher.get());
238    } else {
239        m_previousMessage = consoleMessage.get();
240        m_consoleMessages.append(WTF::move(consoleMessage));
241        if (m_frontendDispatcher && m_enabled)
242            m_previousMessage->addToFrontend(m_frontendDispatcher.get(), m_injectedScriptManager, true);
243    }
244
245    if (!m_frontendDispatcher && m_consoleMessages.size() >= maximumConsoleMessages) {
246        m_expiredConsoleMessageCount += expireConsoleMessagesStep;
247        m_consoleMessages.remove(0, expireConsoleMessagesStep);
248    }
249}
250
251} // namespace Inspector
252
253#endif // ENABLE(INSPECTOR)
254