| /* |
| * Copyright (C) 2013-2019 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. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "PageConsoleClient.h" |
| |
| #include "CachedImage.h" |
| #include "CanvasRenderingContext2D.h" |
| #include "Chrome.h" |
| #include "ChromeClient.h" |
| #include "Document.h" |
| #include "ElementChildIterator.h" |
| #include "Frame.h" |
| #include "FrameSnapshotting.h" |
| #include "HTMLCanvasElement.h" |
| #include "HTMLImageElement.h" |
| #include "HTMLPictureElement.h" |
| #include "HTMLVideoElement.h" |
| #include "Image.h" |
| #include "ImageBitmap.h" |
| #include "ImageBitmapRenderingContext.h" |
| #include "ImageBuffer.h" |
| #include "ImageData.h" |
| #include "InspectorController.h" |
| #include "InspectorInstrumentation.h" |
| #include "IntRect.h" |
| #include "JSCanvasRenderingContext2D.h" |
| #include "JSExecState.h" |
| #include "JSHTMLCanvasElement.h" |
| #include "JSImageBitmap.h" |
| #include "JSImageBitmapRenderingContext.h" |
| #include "JSImageData.h" |
| #include "JSNode.h" |
| #include "JSOffscreenCanvas.h" |
| #include "Node.h" |
| #include "OffscreenCanvas.h" |
| #include "Page.h" |
| #include "ScriptableDocumentParser.h" |
| #include "Settings.h" |
| #include <JavaScriptCore/ConsoleMessage.h> |
| #include <JavaScriptCore/JSCInlines.h> |
| #include <JavaScriptCore/ScriptArguments.h> |
| #include <JavaScriptCore/ScriptCallStack.h> |
| #include <JavaScriptCore/ScriptCallStackFactory.h> |
| #include <JavaScriptCore/StrongInlines.h> |
| #include <wtf/text/WTFString.h> |
| |
| #if ENABLE(WEBGL) |
| #include "JSWebGLRenderingContext.h" |
| #include "WebGLRenderingContext.h" |
| #endif |
| |
| #if ENABLE(WEBGL2) |
| #include "JSWebGL2RenderingContext.h" |
| #include "WebGL2RenderingContext.h" |
| #endif |
| |
| namespace WebCore { |
| using namespace Inspector; |
| |
| PageConsoleClient::PageConsoleClient(Page& page) |
| : m_page(page) |
| { |
| } |
| |
| PageConsoleClient::~PageConsoleClient() = default; |
| |
| static int muteCount = 0; |
| static bool printExceptions = false; |
| |
| bool PageConsoleClient::shouldPrintExceptions() |
| { |
| return printExceptions; |
| } |
| |
| void PageConsoleClient::setShouldPrintExceptions(bool print) |
| { |
| printExceptions = print; |
| } |
| |
| void PageConsoleClient::mute() |
| { |
| muteCount++; |
| } |
| |
| void PageConsoleClient::unmute() |
| { |
| ASSERT(muteCount > 0); |
| muteCount--; |
| } |
| |
| void PageConsoleClient::addMessage(std::unique_ptr<Inspector::ConsoleMessage>&& consoleMessage) |
| { |
| if (!m_page.usesEphemeralSession()) { |
| String message; |
| if (consoleMessage->type() == MessageType::Image) { |
| ASSERT(consoleMessage->arguments()); |
| consoleMessage->arguments()->getFirstArgumentAsString(message); |
| } else |
| message = consoleMessage->message(); |
| m_page.chrome().client().addMessageToConsole(consoleMessage->source(), consoleMessage->level(), message, consoleMessage->line(), consoleMessage->column(), consoleMessage->url()); |
| |
| if (UNLIKELY(m_page.settings().logsPageMessagesToSystemConsoleEnabled() || shouldPrintExceptions())) { |
| if (consoleMessage->type() == MessageType::Image) { |
| ASSERT(consoleMessage->arguments()); |
| ConsoleClient::printConsoleMessageWithArguments(MessageSource::ConsoleAPI, MessageType::Log, consoleMessage->level(), consoleMessage->arguments()->globalObject(), *consoleMessage->arguments()); |
| } else |
| ConsoleClient::printConsoleMessage(MessageSource::ConsoleAPI, MessageType::Log, consoleMessage->level(), consoleMessage->message(), consoleMessage->url(), consoleMessage->line(), consoleMessage->column()); |
| } |
| } |
| |
| InspectorInstrumentation::addMessageToConsole(m_page, WTFMove(consoleMessage)); |
| } |
| |
| void PageConsoleClient::addMessage(MessageSource source, MessageLevel level, const String& message, unsigned long requestIdentifier, Document* document) |
| { |
| String url; |
| unsigned line = 0; |
| unsigned column = 0; |
| if (document) |
| document->getParserLocation(url, line, column); |
| |
| addMessage(source, level, message, url, line, column, 0, JSExecState::currentState(), requestIdentifier); |
| } |
| |
| void PageConsoleClient::addMessage(MessageSource source, MessageLevel level, const String& message, Ref<ScriptCallStack>&& callStack) |
| { |
| addMessage(source, level, message, String(), 0, 0, WTFMove(callStack), 0); |
| } |
| |
| void PageConsoleClient::addMessage(MessageSource source, MessageLevel level, const String& messageText, const String& suggestedURL, unsigned suggestedLineNumber, unsigned suggestedColumnNumber, RefPtr<ScriptCallStack>&& callStack, JSC::JSGlobalObject* lexicalGlobalObject, unsigned long requestIdentifier) |
| { |
| if (muteCount && source != MessageSource::ConsoleAPI) |
| return; |
| |
| std::unique_ptr<Inspector::ConsoleMessage> message; |
| |
| if (callStack) |
| message = makeUnique<Inspector::ConsoleMessage>(source, MessageType::Log, level, messageText, callStack.releaseNonNull(), requestIdentifier); |
| else |
| message = makeUnique<Inspector::ConsoleMessage>(source, MessageType::Log, level, messageText, suggestedURL, suggestedLineNumber, suggestedColumnNumber, lexicalGlobalObject, requestIdentifier); |
| |
| addMessage(WTFMove(message)); |
| } |
| |
| |
| void PageConsoleClient::messageWithTypeAndLevel(MessageType type, MessageLevel level, JSC::JSGlobalObject* lexicalGlobalObject, Ref<Inspector::ScriptArguments>&& arguments) |
| { |
| String messageText; |
| bool gotMessage = arguments->getFirstArgumentAsString(messageText); |
| |
| auto message = makeUnique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, type, level, messageText, arguments.copyRef(), lexicalGlobalObject); |
| |
| String url = message->url(); |
| unsigned lineNumber = message->line(); |
| unsigned columnNumber = message->column(); |
| |
| InspectorInstrumentation::addMessageToConsole(m_page, WTFMove(message)); |
| |
| if (m_page.usesEphemeralSession()) |
| return; |
| |
| if (gotMessage) |
| m_page.chrome().client().addMessageToConsole(MessageSource::ConsoleAPI, level, messageText, lineNumber, columnNumber, url); |
| |
| if (m_page.settings().logsPageMessagesToSystemConsoleEnabled() || PageConsoleClient::shouldPrintExceptions()) |
| ConsoleClient::printConsoleMessageWithArguments(MessageSource::ConsoleAPI, type, level, lexicalGlobalObject, WTFMove(arguments)); |
| } |
| |
| void PageConsoleClient::count(JSC::JSGlobalObject* lexicalGlobalObject, const String& label) |
| { |
| InspectorInstrumentation::consoleCount(m_page, lexicalGlobalObject, label); |
| } |
| |
| void PageConsoleClient::countReset(JSC::JSGlobalObject* lexicalGlobalObject, const String& label) |
| { |
| InspectorInstrumentation::consoleCountReset(m_page, lexicalGlobalObject, label); |
| } |
| |
| void PageConsoleClient::profile(JSC::JSGlobalObject* lexicalGlobalObject, const String& title) |
| { |
| // FIXME: <https://webkit.org/b/153499> Web Inspector: console.profile should use the new Sampling Profiler |
| InspectorInstrumentation::startProfiling(m_page, lexicalGlobalObject, title); |
| } |
| |
| void PageConsoleClient::profileEnd(JSC::JSGlobalObject* lexicalGlobalObject, const String& title) |
| { |
| // FIXME: <https://webkit.org/b/153499> Web Inspector: console.profile should use the new Sampling Profiler |
| InspectorInstrumentation::stopProfiling(m_page, lexicalGlobalObject, title); |
| } |
| |
| void PageConsoleClient::takeHeapSnapshot(JSC::JSGlobalObject*, const String& title) |
| { |
| InspectorInstrumentation::takeHeapSnapshot(m_page.mainFrame(), title); |
| } |
| |
| void PageConsoleClient::time(JSC::JSGlobalObject* lexicalGlobalObject, const String& label) |
| { |
| InspectorInstrumentation::startConsoleTiming(m_page.mainFrame(), lexicalGlobalObject, label); |
| } |
| |
| void PageConsoleClient::timeLog(JSC::JSGlobalObject* lexicalGlobalObject, const String& label, Ref<ScriptArguments>&& arguments) |
| { |
| InspectorInstrumentation::logConsoleTiming(m_page.mainFrame(), lexicalGlobalObject, label, WTFMove(arguments)); |
| } |
| |
| void PageConsoleClient::timeEnd(JSC::JSGlobalObject* lexicalGlobalObject, const String& label) |
| { |
| InspectorInstrumentation::stopConsoleTiming(m_page.mainFrame(), lexicalGlobalObject, label); |
| } |
| |
| void PageConsoleClient::timeStamp(JSC::JSGlobalObject*, Ref<ScriptArguments>&& arguments) |
| { |
| InspectorInstrumentation::consoleTimeStamp(m_page.mainFrame(), WTFMove(arguments)); |
| } |
| |
| static JSC::JSObject* objectArgumentAt(ScriptArguments& arguments, unsigned index) |
| { |
| return arguments.argumentCount() > index ? arguments.argumentAt(index).getObject() : nullptr; |
| } |
| |
| static CanvasRenderingContext* canvasRenderingContext(JSC::VM& vm, JSC::JSValue target) |
| { |
| if (auto* canvas = JSHTMLCanvasElement::toWrapped(vm, target)) |
| return canvas->renderingContext(); |
| if (auto* canvas = JSOffscreenCanvas::toWrapped(vm, target)) |
| return canvas->renderingContext(); |
| if (auto* context = JSCanvasRenderingContext2D::toWrapped(vm, target)) |
| return context; |
| if (auto* context = JSImageBitmapRenderingContext::toWrapped(vm, target)) |
| return context; |
| #if ENABLE(WEBGL) |
| if (auto* context = JSWebGLRenderingContext::toWrapped(vm, target)) |
| return context; |
| #endif |
| #if ENABLE(WEBGL2) |
| if (auto* context = JSWebGL2RenderingContext::toWrapped(vm, target)) |
| return context; |
| #endif |
| return nullptr; |
| } |
| |
| void PageConsoleClient::record(JSC::JSGlobalObject* lexicalGlobalObject, Ref<ScriptArguments>&& arguments) |
| { |
| if (auto* target = objectArgumentAt(arguments, 0)) { |
| if (auto* context = canvasRenderingContext(lexicalGlobalObject->vm(), target)) |
| InspectorInstrumentation::consoleStartRecordingCanvas(*context, *lexicalGlobalObject, objectArgumentAt(arguments, 1)); |
| } |
| } |
| |
| void PageConsoleClient::recordEnd(JSC::JSGlobalObject* lexicalGlobalObject, Ref<ScriptArguments>&& arguments) |
| { |
| if (auto* target = objectArgumentAt(arguments, 0)) { |
| if (auto* context = canvasRenderingContext(lexicalGlobalObject->vm(), target)) |
| InspectorInstrumentation::didFinishRecordingCanvasFrame(*context, true); |
| } |
| } |
| |
| static Optional<String> snapshotCanvas(HTMLCanvasElement& canvasElement, CanvasRenderingContext& canvasRenderingContext) |
| { |
| #if ENABLE(WEBGL) |
| if (is<WebGLRenderingContextBase>(canvasRenderingContext)) |
| downcast<WebGLRenderingContextBase>(canvasRenderingContext).setPreventBufferClearForInspector(true); |
| #endif |
| |
| auto result = canvasElement.toDataURL("image/png"_s); |
| |
| #if ENABLE(WEBGL) |
| if (is<WebGLRenderingContextBase>(canvasRenderingContext)) |
| downcast<WebGLRenderingContextBase>(canvasRenderingContext).setPreventBufferClearForInspector(false); |
| #endif |
| |
| if (!result.hasException()) |
| return result.releaseReturnValue().string; |
| |
| return WTF::nullopt; |
| } |
| |
| void PageConsoleClient::screenshot(JSC::JSGlobalObject* lexicalGlobalObject, Ref<ScriptArguments>&& arguments) |
| { |
| JSC::VM& vm = lexicalGlobalObject->vm(); |
| String dataURL; |
| JSC::JSValue target; |
| |
| if (arguments->argumentCount()) { |
| auto possibleTarget = arguments->argumentAt(0); |
| |
| if (auto* node = JSNode::toWrapped(vm, possibleTarget)) { |
| target = possibleTarget; |
| if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { |
| std::unique_ptr<ImageBuffer> snapshot; |
| |
| // Only try to do something special for subclasses of Node if they're detached from the DOM tree. |
| if (!node->document().contains(node)) { |
| auto snapshotImageElement = [&snapshot] (HTMLImageElement& imageElement) { |
| if (auto* cachedImage = imageElement.cachedImage()) { |
| auto* image = cachedImage->image(); |
| if (image && image != &Image::nullImage()) { |
| snapshot = ImageBuffer::create(image->size(), RenderingMode::Unaccelerated); |
| snapshot->context().drawImage(*image, FloatPoint(0, 0)); |
| } |
| } |
| }; |
| |
| if (is<HTMLImageElement>(node)) |
| snapshotImageElement(downcast<HTMLImageElement>(*node)); |
| else if (is<HTMLPictureElement>(node)) { |
| if (auto* firstImage = childrenOfType<HTMLImageElement>(downcast<HTMLPictureElement>(*node)).first()) |
| snapshotImageElement(*firstImage); |
| } |
| #if ENABLE(VIDEO) |
| else if (is<HTMLVideoElement>(node)) { |
| auto& videoElement = downcast<HTMLVideoElement>(*node); |
| unsigned videoWidth = videoElement.videoWidth(); |
| unsigned videoHeight = videoElement.videoHeight(); |
| snapshot = ImageBuffer::create(FloatSize(videoWidth, videoHeight), RenderingMode::Unaccelerated); |
| videoElement.paintCurrentFrameInContext(snapshot->context(), FloatRect(0, 0, videoWidth, videoHeight)); |
| } |
| #endif |
| else if (is<HTMLCanvasElement>(node)) { |
| auto& canvasElement = downcast<HTMLCanvasElement>(*node); |
| if (auto* canvasRenderingContext = canvasElement.renderingContext()) { |
| if (auto result = snapshotCanvas(canvasElement, *canvasRenderingContext)) |
| dataURL = result.value(); |
| } |
| } |
| } |
| |
| if (dataURL.isEmpty()) { |
| if (!snapshot) |
| snapshot = WebCore::snapshotNode(m_page.mainFrame(), *node); |
| |
| if (snapshot) |
| dataURL = snapshot->toDataURL("image/png"_s, WTF::nullopt, PreserveResolution::Yes); |
| } |
| } |
| } else if (auto* imageData = JSImageData::toWrapped(vm, possibleTarget)) { |
| target = possibleTarget; |
| if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { |
| auto sourceSize = imageData->size(); |
| if (auto imageBuffer = ImageBuffer::create(sourceSize, RenderingMode::Unaccelerated)) { |
| IntRect sourceRect(IntPoint(), sourceSize); |
| imageBuffer->putByteArray(*imageData->data(), AlphaPremultiplication::Unpremultiplied, sourceSize, sourceRect, IntPoint()); |
| dataURL = imageBuffer->toDataURL("image/png"_s, WTF::nullopt, PreserveResolution::Yes); |
| } |
| } |
| } else if (auto* imageBitmap = JSImageBitmap::toWrapped(vm, possibleTarget)) { |
| target = possibleTarget; |
| if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { |
| if (auto* imageBuffer = imageBitmap->buffer()) |
| dataURL = imageBuffer->toDataURL("image/png"_s, WTF::nullopt, PreserveResolution::Yes); |
| } |
| } else if (auto* context = canvasRenderingContext(vm, possibleTarget)) { |
| auto& canvas = context->canvasBase(); |
| if (is<HTMLCanvasElement>(canvas)) { |
| target = possibleTarget; |
| if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { |
| if (auto result = snapshotCanvas(downcast<HTMLCanvasElement>(canvas), *context)) |
| dataURL = result.value(); |
| } |
| } |
| |
| // FIXME: <https://webkit.org/b/180833> Web Inspector: support OffscreenCanvas for Canvas related operations |
| } else { |
| String base64; |
| if (possibleTarget.getString(lexicalGlobalObject, base64) && base64.startsWithIgnoringASCIICase("data:"_s) && base64.length() > 5) { |
| target = possibleTarget; |
| dataURL = base64; |
| } |
| } |
| } |
| |
| if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { |
| if (!target) { |
| // If no target is provided, capture an image of the viewport. |
| IntRect imageRect(IntPoint::zero(), m_page.mainFrame().view()->sizeForVisibleContent()); |
| if (auto snapshot = WebCore::snapshotFrameRect(m_page.mainFrame(), imageRect, SnapshotOptionsInViewCoordinates)) |
| dataURL = snapshot->toDataURL("image/png"_s, WTF::nullopt, PreserveResolution::Yes); |
| } |
| |
| if (dataURL.isEmpty()) { |
| addMessage(makeUnique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Image, MessageLevel::Error, "Could not capture screenshot"_s, WTFMove(arguments))); |
| return; |
| } |
| } |
| |
| Vector<JSC::Strong<JSC::Unknown>> adjustedArguments; |
| adjustedArguments.append({ vm, target ? target : JSC::jsNontrivialString(vm, "Viewport"_s) }); |
| for (size_t i = (!target ? 0 : 1); i < arguments->argumentCount(); ++i) |
| adjustedArguments.append({ vm, arguments->argumentAt(i) }); |
| arguments = ScriptArguments::create(lexicalGlobalObject, WTFMove(adjustedArguments)); |
| addMessage(makeUnique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Image, MessageLevel::Log, dataURL, WTFMove(arguments))); |
| } |
| |
| } // namespace WebCore |