1/*
2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
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 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "PageConsoleClient.h"
31
32#include "CanvasRenderingContext2D.h"
33#include "Chrome.h"
34#include "ChromeClient.h"
35#include "Document.h"
36#include "Frame.h"
37#include "FrameSnapshotting.h"
38#include "HTMLCanvasElement.h"
39#include "ImageBitmapRenderingContext.h"
40#include "ImageBuffer.h"
41#include "InspectorController.h"
42#include "InspectorInstrumentation.h"
43#include "IntRect.h"
44#include "JSCanvasRenderingContext2D.h"
45#include "JSExecState.h"
46#include "JSHTMLCanvasElement.h"
47#include "JSImageBitmapRenderingContext.h"
48#include "JSNode.h"
49#include "JSOffscreenCanvas.h"
50#include "Node.h"
51#include "OffscreenCanvas.h"
52#include "Page.h"
53#include "ScriptableDocumentParser.h"
54#include "Settings.h"
55#include <JavaScriptCore/ConsoleMessage.h>
56#include <JavaScriptCore/JSCInlines.h>
57#include <JavaScriptCore/ScriptArguments.h>
58#include <JavaScriptCore/ScriptCallStack.h>
59#include <JavaScriptCore/ScriptCallStackFactory.h>
60#include <wtf/text/WTFString.h>
61
62#if ENABLE(WEBGL)
63#include "JSWebGLRenderingContext.h"
64#include "WebGLRenderingContext.h"
65#endif
66
67#if ENABLE(WEBGL2)
68#include "JSWebGL2RenderingContext.h"
69#include "WebGL2RenderingContext.h"
70#endif
71
72namespace WebCore {
73using namespace Inspector;
74
75PageConsoleClient::PageConsoleClient(Page& page)
76 : m_page(page)
77{
78}
79
80PageConsoleClient::~PageConsoleClient() = default;
81
82static int muteCount = 0;
83static bool printExceptions = false;
84
85bool PageConsoleClient::shouldPrintExceptions()
86{
87 return printExceptions;
88}
89
90void PageConsoleClient::setShouldPrintExceptions(bool print)
91{
92 printExceptions = print;
93}
94
95void PageConsoleClient::mute()
96{
97 muteCount++;
98}
99
100void PageConsoleClient::unmute()
101{
102 ASSERT(muteCount > 0);
103 muteCount--;
104}
105
106void PageConsoleClient::addMessage(std::unique_ptr<Inspector::ConsoleMessage>&& consoleMessage)
107{
108 if (consoleMessage->source() != MessageSource::CSS && consoleMessage->type() != MessageType::Image && !m_page.usesEphemeralSession()) {
109 m_page.chrome().client().addMessageToConsole(consoleMessage->source(), consoleMessage->level(), consoleMessage->message(), consoleMessage->line(), consoleMessage->column(), consoleMessage->url());
110
111 if (m_page.settings().logsPageMessagesToSystemConsoleEnabled() || shouldPrintExceptions())
112 ConsoleClient::printConsoleMessage(MessageSource::ConsoleAPI, MessageType::Log, consoleMessage->level(), consoleMessage->message(), consoleMessage->url(), consoleMessage->line(), consoleMessage->column());
113 }
114
115 InspectorInstrumentation::addMessageToConsole(m_page, WTFMove(consoleMessage));
116}
117
118void PageConsoleClient::addMessage(MessageSource source, MessageLevel level, const String& message, unsigned long requestIdentifier, Document* document)
119{
120 String url;
121 unsigned line = 0;
122 unsigned column = 0;
123 if (document)
124 document->getParserLocation(url, line, column);
125
126 addMessage(source, level, message, url, line, column, 0, JSExecState::currentState(), requestIdentifier);
127}
128
129void PageConsoleClient::addMessage(MessageSource source, MessageLevel level, const String& message, Ref<ScriptCallStack>&& callStack)
130{
131 addMessage(source, level, message, String(), 0, 0, WTFMove(callStack), 0);
132}
133
134void PageConsoleClient::addMessage(MessageSource source, MessageLevel level, const String& messageText, const String& suggestedURL, unsigned suggestedLineNumber, unsigned suggestedColumnNumber, RefPtr<ScriptCallStack>&& callStack, JSC::ExecState* state, unsigned long requestIdentifier)
135{
136 if (muteCount && source != MessageSource::ConsoleAPI)
137 return;
138
139 std::unique_ptr<Inspector::ConsoleMessage> message;
140
141 if (callStack)
142 message = std::make_unique<Inspector::ConsoleMessage>(source, MessageType::Log, level, messageText, callStack.releaseNonNull(), requestIdentifier);
143 else
144 message = std::make_unique<Inspector::ConsoleMessage>(source, MessageType::Log, level, messageText, suggestedURL, suggestedLineNumber, suggestedColumnNumber, state, requestIdentifier);
145
146 addMessage(WTFMove(message));
147}
148
149
150void PageConsoleClient::messageWithTypeAndLevel(MessageType type, MessageLevel level, JSC::ExecState* exec, Ref<Inspector::ScriptArguments>&& arguments)
151{
152 String messageText;
153 bool gotMessage = arguments->getFirstArgumentAsString(messageText);
154
155 auto message = std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, type, level, messageText, arguments.copyRef(), exec);
156
157 String url = message->url();
158 unsigned lineNumber = message->line();
159 unsigned columnNumber = message->column();
160
161 InspectorInstrumentation::addMessageToConsole(m_page, WTFMove(message));
162
163 if (m_page.usesEphemeralSession())
164 return;
165
166 if (gotMessage)
167 m_page.chrome().client().addMessageToConsole(MessageSource::ConsoleAPI, level, messageText, lineNumber, columnNumber, url);
168
169 if (m_page.settings().logsPageMessagesToSystemConsoleEnabled() || PageConsoleClient::shouldPrintExceptions())
170 ConsoleClient::printConsoleMessageWithArguments(MessageSource::ConsoleAPI, type, level, exec, WTFMove(arguments));
171}
172
173void PageConsoleClient::count(JSC::ExecState* exec, Ref<ScriptArguments>&& arguments)
174{
175 InspectorInstrumentation::consoleCount(m_page, exec, WTFMove(arguments));
176}
177
178void PageConsoleClient::profile(JSC::ExecState* exec, const String& title)
179{
180 // FIXME: <https://webkit.org/b/153499> Web Inspector: console.profile should use the new Sampling Profiler
181 InspectorInstrumentation::startProfiling(m_page, exec, title);
182}
183
184void PageConsoleClient::profileEnd(JSC::ExecState* exec, const String& title)
185{
186 // FIXME: <https://webkit.org/b/153499> Web Inspector: console.profile should use the new Sampling Profiler
187 InspectorInstrumentation::stopProfiling(m_page, exec, title);
188}
189
190void PageConsoleClient::takeHeapSnapshot(JSC::ExecState*, const String& title)
191{
192 InspectorInstrumentation::takeHeapSnapshot(m_page.mainFrame(), title);
193}
194
195void PageConsoleClient::time(JSC::ExecState*, const String& title)
196{
197 InspectorInstrumentation::startConsoleTiming(m_page.mainFrame(), title);
198}
199
200void PageConsoleClient::timeEnd(JSC::ExecState* exec, const String& title)
201{
202 InspectorInstrumentation::stopConsoleTiming(m_page.mainFrame(), title, createScriptCallStackForConsole(exec, 1));
203}
204
205void PageConsoleClient::timeStamp(JSC::ExecState*, Ref<ScriptArguments>&& arguments)
206{
207 InspectorInstrumentation::consoleTimeStamp(m_page.mainFrame(), WTFMove(arguments));
208}
209
210static JSC::JSObject* objectArgumentAt(ScriptArguments& arguments, unsigned index)
211{
212 return arguments.argumentCount() > index ? arguments.argumentAt(index).getObject() : nullptr;
213}
214
215static CanvasRenderingContext* canvasRenderingContext(JSC::VM& vm, ScriptArguments& arguments)
216{
217 auto* target = objectArgumentAt(arguments, 0);
218 if (!target)
219 return nullptr;
220
221 if (auto* canvas = JSHTMLCanvasElement::toWrapped(vm, target))
222 return canvas->renderingContext();
223 if (auto* canvas = JSOffscreenCanvas::toWrapped(vm, target))
224 return canvas->renderingContext();
225 if (auto* context = JSCanvasRenderingContext2D::toWrapped(vm, target))
226 return context;
227 if (auto* context = JSImageBitmapRenderingContext::toWrapped(vm, target))
228 return context;
229#if ENABLE(WEBGL)
230 if (auto* context = JSWebGLRenderingContext::toWrapped(vm, target))
231 return context;
232#endif
233#if ENABLE(WEBGL2)
234 if (auto* context = JSWebGL2RenderingContext::toWrapped(vm, target))
235 return context;
236#endif
237 return nullptr;
238}
239
240void PageConsoleClient::record(JSC::ExecState* state, Ref<ScriptArguments>&& arguments)
241{
242 if (auto* context = canvasRenderingContext(state->vm(), arguments))
243 InspectorInstrumentation::consoleStartRecordingCanvas(*context, *state, objectArgumentAt(arguments, 1));
244}
245
246void PageConsoleClient::recordEnd(JSC::ExecState* state, Ref<ScriptArguments>&& arguments)
247{
248 if (auto* context = canvasRenderingContext(state->vm(), arguments))
249 InspectorInstrumentation::didFinishRecordingCanvasFrame(*context, true);
250}
251
252void PageConsoleClient::screenshot(JSC::ExecState* state, Ref<ScriptArguments>&& arguments)
253{
254 FAST_RETURN_IF_NO_FRONTENDS(void());
255
256 Frame& frame = m_page.mainFrame();
257
258 std::unique_ptr<ImageBuffer> snapshot;
259
260 auto* target = objectArgumentAt(arguments, 0);
261 if (target) {
262 auto* node = JSNode::toWrapped(state->vm(), target);
263 if (!node)
264 return;
265
266 snapshot = WebCore::snapshotNode(frame, *node);
267 } else {
268 // If no target is provided, capture an image of the viewport.
269 IntRect imageRect(IntPoint::zero(), frame.view()->sizeForVisibleContent());
270 snapshot = WebCore::snapshotFrameRect(frame, imageRect, SnapshotOptionsInViewCoordinates);
271 }
272
273 if (!snapshot) {
274 addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Error, "Could not capture screenshot"_s, arguments.copyRef()));
275 return;
276 }
277
278 String dataURL = snapshot->toDataURL("image/png"_s, WTF::nullopt, PreserveResolution::Yes);
279 if (dataURL.isEmpty()) {
280 addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Error, "Could not capture screenshot"_s, arguments.copyRef()));
281 return;
282 }
283
284 if (target) {
285 // Log the argument before sending the image for it.
286 String messageText;
287 arguments->getFirstArgumentAsString(messageText);
288 addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Log, messageText, arguments.copyRef()));
289 }
290
291 addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Image, MessageLevel::Log, dataURL));
292}
293
294} // namespace WebCore
295