| /* |
| * Copyright (C) 2014-2019 Apple Inc. All rights reserved. |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "InspectorConsoleAgent.h" |
| |
| #include "ConsoleMessage.h" |
| #include "InjectedScriptManager.h" |
| #include "InspectorFrontendRouter.h" |
| #include "InspectorHeapAgent.h" |
| #include "ScriptArguments.h" |
| #include "ScriptCallFrame.h" |
| #include "ScriptCallStack.h" |
| #include "ScriptCallStackFactory.h" |
| #include "ScriptObject.h" |
| #include <wtf/text/StringConcatenateNumbers.h> |
| |
| namespace Inspector { |
| |
| static constexpr unsigned maximumConsoleMessages = 100; |
| static constexpr int expireConsoleMessagesStep = 10; |
| |
| InspectorConsoleAgent::InspectorConsoleAgent(AgentContext& context) |
| : InspectorAgentBase("Console"_s) |
| , m_injectedScriptManager(context.injectedScriptManager) |
| , m_frontendDispatcher(makeUnique<ConsoleFrontendDispatcher>(context.frontendRouter)) |
| , m_backendDispatcher(ConsoleBackendDispatcher::create(context.backendDispatcher, this)) |
| { |
| } |
| |
| InspectorConsoleAgent::~InspectorConsoleAgent() = default; |
| |
| void InspectorConsoleAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*) |
| { |
| } |
| |
| void InspectorConsoleAgent::willDestroyFrontendAndBackend(DisconnectReason) |
| { |
| String errorString; |
| disable(errorString); |
| } |
| |
| void InspectorConsoleAgent::discardValues() |
| { |
| m_consoleMessages.clear(); |
| m_expiredConsoleMessageCount = 0; |
| } |
| |
| void InspectorConsoleAgent::enable(ErrorString&) |
| { |
| if (m_enabled) |
| return; |
| |
| m_enabled = true; |
| |
| if (m_expiredConsoleMessageCount) { |
| ConsoleMessage expiredMessage(MessageSource::Other, MessageType::Log, MessageLevel::Warning, makeString(m_expiredConsoleMessageCount, " console messages are not shown.")); |
| expiredMessage.addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false); |
| } |
| |
| Vector<std::unique_ptr<ConsoleMessage>> messages; |
| m_consoleMessages.swap(messages); |
| |
| for (size_t i = 0; i < messages.size(); ++i) |
| messages[i]->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, false); |
| } |
| |
| void InspectorConsoleAgent::disable(ErrorString&) |
| { |
| if (!m_enabled) |
| return; |
| |
| m_enabled = false; |
| } |
| |
| void InspectorConsoleAgent::clearMessages(ErrorString&) |
| { |
| m_consoleMessages.clear(); |
| m_expiredConsoleMessageCount = 0; |
| |
| m_injectedScriptManager.releaseObjectGroup("console"_s); |
| |
| if (m_enabled) |
| m_frontendDispatcher->messagesCleared(); |
| } |
| |
| void InspectorConsoleAgent::reset() |
| { |
| ErrorString ignored; |
| clearMessages(ignored); |
| |
| m_times.clear(); |
| m_counts.clear(); |
| } |
| |
| void InspectorConsoleAgent::addMessageToConsole(std::unique_ptr<ConsoleMessage> message) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| if (message->type() == MessageType::Clear) { |
| ErrorString ignored; |
| clearMessages(ignored); |
| } |
| |
| addConsoleMessage(WTFMove(message)); |
| } |
| |
| void InspectorConsoleAgent::startTiming(JSC::JSGlobalObject* globalObject, const String& label) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| ASSERT(!label.isNull()); |
| if (label.isNull()) |
| return; |
| |
| auto result = m_times.add(label, MonotonicTime::now()); |
| |
| if (!result.isNewEntry) { |
| // FIXME: Send an enum to the frontend for localization? |
| String warning = makeString("Timer \"", label, "\" already exists"); |
| addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, createScriptCallStackForConsole(globalObject, 1))); |
| } |
| } |
| |
| void InspectorConsoleAgent::logTiming(JSC::JSGlobalObject* globalObject, const String& label, Ref<ScriptArguments>&& arguments) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| ASSERT(!label.isNull()); |
| if (label.isNull()) |
| return; |
| |
| auto callStack = createScriptCallStackForConsole(globalObject, 1); |
| |
| auto it = m_times.find(label); |
| if (it == m_times.end()) { |
| // FIXME: Send an enum to the frontend for localization? |
| String warning = makeString("Timer \"", label, "\" does not exist"); |
| addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, WTFMove(callStack))); |
| return; |
| } |
| |
| MonotonicTime startTime = it->value; |
| Seconds elapsed = MonotonicTime::now() - startTime; |
| String message = makeString(label, ": ", FormattedNumber::fixedWidth(elapsed.milliseconds(), 3), "ms"); |
| addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, WTFMove(arguments), WTFMove(callStack))); |
| } |
| |
| void InspectorConsoleAgent::stopTiming(JSC::JSGlobalObject* globalObject, const String& label) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| ASSERT(!label.isNull()); |
| if (label.isNull()) |
| return; |
| |
| auto callStack = createScriptCallStackForConsole(globalObject, 1); |
| |
| auto it = m_times.find(label); |
| if (it == m_times.end()) { |
| // FIXME: Send an enum to the frontend for localization? |
| String warning = makeString("Timer \"", label, "\" does not exist"); |
| addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Warning, warning, WTFMove(callStack))); |
| return; |
| } |
| |
| MonotonicTime startTime = it->value; |
| Seconds elapsed = MonotonicTime::now() - startTime; |
| String message = makeString(label, ": ", FormattedNumber::fixedWidth(elapsed.milliseconds(), 3), "ms"); |
| addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Timing, MessageLevel::Debug, message, WTFMove(callStack))); |
| |
| m_times.remove(it); |
| } |
| |
| void InspectorConsoleAgent::takeHeapSnapshot(const String& title) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| if (!m_heapAgent) |
| return; |
| |
| ErrorString ignored; |
| double timestamp; |
| String snapshotData; |
| m_heapAgent->snapshot(ignored, ×tamp, &snapshotData); |
| |
| m_frontendDispatcher->heapSnapshot(timestamp, snapshotData, title.isEmpty() ? nullptr : &title); |
| } |
| |
| void InspectorConsoleAgent::count(JSC::JSGlobalObject* globalObject, const String& label) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| auto result = m_counts.add(label, 1); |
| if (!result.isNewEntry) |
| result.iterator->value += 1; |
| |
| // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value. |
| |
| String message = makeString(label, ": ", result.iterator->value); |
| addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Debug, message, createScriptCallStackForConsole(globalObject, 1))); |
| } |
| |
| void InspectorConsoleAgent::countReset(JSC::JSGlobalObject* globalObject, const String& label) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| auto it = m_counts.find(label); |
| if (it == m_counts.end()) { |
| // FIXME: Send an enum to the frontend for localization? |
| String warning = makeString("Counter \"", label, "\" does not exist"); |
| addMessageToConsole(makeUnique<ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Warning, warning, createScriptCallStackForConsole(globalObject, 1))); |
| return; |
| } |
| |
| it->value = 0; |
| |
| // FIXME: Web Inspector should have a better UI for counters, but for now we just log an updated counter value. |
| } |
| |
| static bool isGroupMessage(MessageType type) |
| { |
| return type == MessageType::StartGroup |
| || type == MessageType::StartGroupCollapsed |
| || type == MessageType::EndGroup; |
| } |
| |
| void InspectorConsoleAgent::addConsoleMessage(std::unique_ptr<ConsoleMessage> consoleMessage) |
| { |
| if (!m_injectedScriptManager.inspectorEnvironment().developerExtrasEnabled()) |
| return; |
| |
| ASSERT_ARG(consoleMessage, consoleMessage); |
| |
| ConsoleMessage* previousMessage = m_consoleMessages.isEmpty() ? nullptr : m_consoleMessages.last().get(); |
| |
| if (previousMessage && !isGroupMessage(previousMessage->type()) && previousMessage->isEqual(consoleMessage.get())) { |
| previousMessage->incrementCount(); |
| if (m_enabled) |
| previousMessage->updateRepeatCountInConsole(*m_frontendDispatcher); |
| } else { |
| ConsoleMessage* newMessage = consoleMessage.get(); |
| m_consoleMessages.append(WTFMove(consoleMessage)); |
| if (m_enabled) |
| newMessage->addToFrontend(*m_frontendDispatcher, m_injectedScriptManager, true); |
| |
| if (m_consoleMessages.size() >= maximumConsoleMessages) { |
| m_expiredConsoleMessageCount += expireConsoleMessagesStep; |
| m_consoleMessages.remove(0, expireConsoleMessagesStep); |
| } |
| } |
| } |
| |
| void InspectorConsoleAgent::getLoggingChannels(ErrorString&, RefPtr<JSON::ArrayOf<Protocol::Console::Channel>>& channels) |
| { |
| // Default implementation has no logging channels. |
| channels = JSON::ArrayOf<Protocol::Console::Channel>::create(); |
| } |
| |
| void InspectorConsoleAgent::setLoggingChannelLevel(ErrorString& errorString, const String&, const String&) |
| { |
| errorString = "Not supported"_s; |
| } |
| |
| } // namespace Inspector |