|  | /* | 
|  | * Copyright (C) 2013, 2014 Apple 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. ``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 | 
|  | * 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 "ConsoleClient.h" | 
|  |  | 
|  | #include "CatchScope.h" | 
|  | #include "JSCJSValueInlines.h" | 
|  | #include "JSGlobalObject.h" | 
|  | #include "ScriptArguments.h" | 
|  | #include "ScriptCallStack.h" | 
|  | #include "ScriptCallStackFactory.h" | 
|  | #include <wtf/Assertions.h> | 
|  | #include <wtf/text/CString.h> | 
|  | #include <wtf/text/StringBuilder.h> | 
|  | #include <wtf/text/WTFString.h> | 
|  |  | 
|  | using namespace Inspector; | 
|  |  | 
|  | namespace JSC { | 
|  |  | 
|  | static void appendURLAndPosition(StringBuilder& builder, const String& url, unsigned lineNumber, unsigned columnNumber) | 
|  | { | 
|  | if (url.isEmpty()) | 
|  | return; | 
|  |  | 
|  | builder.append(url); | 
|  |  | 
|  | if (lineNumber > 0) { | 
|  | builder.append(':'); | 
|  | builder.appendNumber(lineNumber); | 
|  | } | 
|  |  | 
|  | if (columnNumber > 0) { | 
|  | builder.append(':'); | 
|  | builder.appendNumber(columnNumber); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void appendMessagePrefix(StringBuilder& builder, MessageSource source, MessageType type, MessageLevel level) | 
|  | { | 
|  | String sourceString; | 
|  | switch (source) { | 
|  | case MessageSource::ConsoleAPI: | 
|  | // Default, no need to be more specific. | 
|  | break; | 
|  | case MessageSource::XML: | 
|  | sourceString = "XML"_s; | 
|  | break; | 
|  | case MessageSource::JS: | 
|  | sourceString = "JS"_s; | 
|  | break; | 
|  | case MessageSource::Network: | 
|  | sourceString = "NETWORK"_s; | 
|  | break; | 
|  | case MessageSource::Storage: | 
|  | sourceString = "STORAGE"_s; | 
|  | break; | 
|  | case MessageSource::AppCache: | 
|  | sourceString = "APPCACHE"_s; | 
|  | break; | 
|  | case MessageSource::Rendering: | 
|  | sourceString = "RENDERING"_s; | 
|  | break; | 
|  | case MessageSource::CSS: | 
|  | sourceString = "CSS"_s; | 
|  | break; | 
|  | case MessageSource::Security: | 
|  | sourceString = "SECURITY"_s; | 
|  | break; | 
|  | case MessageSource::ContentBlocker: | 
|  | sourceString = "CONTENTBLOCKER"_s; | 
|  | break; | 
|  | case MessageSource::Media: | 
|  | sourceString = "MEDIA"_s; | 
|  | break; | 
|  | case MessageSource::MediaSource: | 
|  | sourceString = "MEDIASOURCE"_s; | 
|  | break; | 
|  | case MessageSource::WebRTC: | 
|  | sourceString = "WEBRTC"_s; | 
|  | break; | 
|  | case MessageSource::ITPDebug: | 
|  | sourceString = "ITPDEBUG"_s; | 
|  | break; | 
|  | case MessageSource::PrivateClickMeasurement: | 
|  | sourceString = "PRIVATECLICKMEASUREMENT"_s; | 
|  | break; | 
|  | case MessageSource::Other: | 
|  | sourceString = "OTHER"_s; | 
|  | break; | 
|  | } | 
|  |  | 
|  | String typeString; | 
|  | switch (type) { | 
|  | case MessageType::Log: | 
|  | // Default, no need to be more specific. | 
|  | break; | 
|  | case MessageType::Clear: | 
|  | typeString = "CLEAR"_s; | 
|  | break; | 
|  | case MessageType::Dir: | 
|  | typeString = "DIR"_s; | 
|  | break; | 
|  | case MessageType::DirXML: | 
|  | typeString = "DIRXML"_s; | 
|  | break; | 
|  | case MessageType::Table: | 
|  | typeString = "TABLE"_s; | 
|  | break; | 
|  | case MessageType::Trace: | 
|  | typeString = "TRACE"_s; | 
|  | break; | 
|  | case MessageType::StartGroup: | 
|  | typeString = "STARTGROUP"_s; | 
|  | break; | 
|  | case MessageType::StartGroupCollapsed: | 
|  | typeString = "STARTGROUPCOLLAPSED"_s; | 
|  | break; | 
|  | case MessageType::EndGroup: | 
|  | typeString = "ENDGROUP"_s; | 
|  | break; | 
|  | case MessageType::Assert: | 
|  | typeString = "ASSERT"_s; | 
|  | break; | 
|  | case MessageType::Timing: | 
|  | typeString = "TIMING"_s; | 
|  | break; | 
|  | case MessageType::Profile: | 
|  | typeString = "PROFILE"_s; | 
|  | break; | 
|  | case MessageType::ProfileEnd: | 
|  | typeString = "PROFILEEND"_s; | 
|  | break; | 
|  | case MessageType::Image: | 
|  | typeString = "IMAGE"_s; | 
|  | break; | 
|  | } | 
|  |  | 
|  | String levelString; | 
|  | switch (level) { | 
|  | case MessageLevel::Log: | 
|  | // Default, no need to be more specific. | 
|  | if (type == MessageType::Log) | 
|  | levelString = "LOG"_s; | 
|  | break; | 
|  | case MessageLevel::Debug: | 
|  | levelString = "DEBUG"_s; | 
|  | break; | 
|  | case MessageLevel::Info: | 
|  | levelString = "INFO"_s; | 
|  | break; | 
|  | case MessageLevel::Warning: | 
|  | levelString = "WARN"_s; | 
|  | break; | 
|  | case MessageLevel::Error: | 
|  | levelString = "ERROR"_s; | 
|  | break; | 
|  | } | 
|  |  | 
|  | builder.append("CONSOLE"); | 
|  | if (!sourceString.isEmpty()) | 
|  | builder.append(' ', sourceString); | 
|  | if (!typeString.isEmpty()) | 
|  | builder.append(' ', typeString); | 
|  | if (!levelString.isEmpty()) | 
|  | builder.append(' ', levelString); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::printConsoleMessage(MessageSource source, MessageType type, MessageLevel level, const String& message, const String& url, unsigned lineNumber, unsigned columnNumber) | 
|  | { | 
|  | StringBuilder builder; | 
|  |  | 
|  | if (!url.isEmpty()) { | 
|  | appendURLAndPosition(builder, url, lineNumber, columnNumber); | 
|  | builder.appendLiteral(": "); | 
|  | } | 
|  |  | 
|  | appendMessagePrefix(builder, source, type, level); | 
|  | builder.append(' '); | 
|  | builder.append(message); | 
|  |  | 
|  | WTFLogAlways("%s", builder.toString().utf8().data()); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::printConsoleMessageWithArguments(MessageSource source, MessageType type, MessageLevel level, JSC::JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | bool isTraceMessage = type == MessageType::Trace; | 
|  | size_t stackSize = isTraceMessage ? ScriptCallStack::maxCallStackSizeToCapture : 1; | 
|  | Ref<ScriptCallStack> callStack = createScriptCallStackForConsole(globalObject, stackSize); | 
|  | const ScriptCallFrame& lastCaller = callStack->at(0); | 
|  |  | 
|  | StringBuilder builder; | 
|  |  | 
|  | if (!lastCaller.sourceURL().isEmpty()) { | 
|  | appendURLAndPosition(builder, lastCaller.sourceURL(), lastCaller.lineNumber(), lastCaller.columnNumber()); | 
|  | builder.appendLiteral(": "); | 
|  | } | 
|  |  | 
|  | appendMessagePrefix(builder, source, type, level); | 
|  | for (size_t i = 0; i < arguments->argumentCount(); ++i) { | 
|  | builder.append(' '); | 
|  | auto* globalObject = arguments->globalObject(); | 
|  | auto scope = DECLARE_CATCH_SCOPE(globalObject->vm()); | 
|  | builder.append(arguments->argumentAt(i).toWTFString(globalObject)); | 
|  | scope.clearException(); | 
|  | } | 
|  |  | 
|  | WTFLogAlways("%s", builder.toString().utf8().data()); | 
|  |  | 
|  | if (isTraceMessage) { | 
|  | for (size_t i = 0; i < callStack->size(); ++i) { | 
|  | const ScriptCallFrame& callFrame = callStack->at(i); | 
|  | String functionName = String(callFrame.functionName()); | 
|  | if (functionName.isEmpty()) | 
|  | functionName = "(unknown)"_s; | 
|  |  | 
|  | StringBuilder callFrameBuilder; | 
|  | callFrameBuilder.appendNumber(i); | 
|  | callFrameBuilder.appendLiteral(": "); | 
|  | callFrameBuilder.append(functionName); | 
|  | callFrameBuilder.append('('); | 
|  | appendURLAndPosition(callFrameBuilder, callFrame.sourceURL(), callFrame.lineNumber(), callFrame.columnNumber()); | 
|  | callFrameBuilder.append(')'); | 
|  |  | 
|  | WTFLogAlways("%s", callFrameBuilder.toString().utf8().data()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void ConsoleClient::internalMessageWithTypeAndLevel(MessageType type, MessageLevel level, JSC::JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments, ArgumentRequirement argumentRequirement) | 
|  | { | 
|  | if (argumentRequirement == ArgumentRequired && !arguments->argumentCount()) | 
|  | return; | 
|  |  | 
|  | messageWithTypeAndLevel(type, level, globalObject, WTFMove(arguments)); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::logWithLevel(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments, MessageLevel level) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::Log, level, globalObject, WTFMove(arguments), ArgumentRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::clear(JSGlobalObject* globalObject) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::Clear, MessageLevel::Log, globalObject, ScriptArguments::create(globalObject, { }), ArgumentNotRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::dir(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::Dir, MessageLevel::Log, globalObject, WTFMove(arguments), ArgumentRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::dirXML(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::DirXML, MessageLevel::Log, globalObject, WTFMove(arguments), ArgumentRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::table(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::Table, MessageLevel::Log, globalObject, WTFMove(arguments), ArgumentRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::trace(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::Trace, MessageLevel::Log, globalObject, WTFMove(arguments), ArgumentNotRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::assertion(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::Assert, MessageLevel::Error, globalObject, WTFMove(arguments), ArgumentNotRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::group(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::StartGroup, MessageLevel::Log, globalObject, WTFMove(arguments), ArgumentNotRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::groupCollapsed(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::StartGroupCollapsed, MessageLevel::Log, globalObject, WTFMove(arguments), ArgumentNotRequired); | 
|  | } | 
|  |  | 
|  | void ConsoleClient::groupEnd(JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) | 
|  | { | 
|  | internalMessageWithTypeAndLevel(MessageType::EndGroup, MessageLevel::Log, globalObject, WTFMove(arguments), ArgumentNotRequired); | 
|  | } | 
|  |  | 
|  | } // namespace JSC |