⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 242992 in webkit


Ignore:
Timestamp:
Mar 15, 2019, 1:12:21 AM (7 years ago)
Author:
Devin Rousso
Message:

Web Inspector: provide a way to capture a screenshot of a node from within the page
https://bugs.webkit.org/show_bug.cgi?id=194279
<rdar://problem/10731573>

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

Add console.screenshot functionality, which displays a screenshot of a given object (if
able) within Web Inspector's Console tab. From there, it can be viewed and saved.

Currently, console.screenshot will

  • capture an image of a Node (if provided)
  • capture an image of the viewport if nothing is provided
  • inspector/protocol/Console.json:

Add Image enum value to ConsoleMessage type.

  • runtime/ConsoleTypes.h:
  • inspector/ConsoleMessage.h:
  • inspector/ConsoleMessage.cpp:

(Inspector::messageTypeValue):

  • runtime/ConsoleClient.h:
  • runtime/ConsoleObject.cpp:

(JSC::ConsoleObject::finishCreation):
(JSC::consoleProtoFuncScreenshot): Added.

  • inspector/JSGlobalObjectConsoleClient.h:
  • inspector/JSGlobalObjectConsoleClient.cpp:

(Inspector::JSGlobalObjectConsoleClient::screenshot): Added.

Source/WebCore:

Test: inspector/console/console-screenshot.html

Add console.screenshot functionality, which displays a screenshot of a given object (if
able) within Web Inspector's Console tab. From there, it can be viewed and saved.

Currently, console.screenshot will

  • capture an image of a Node (if provided)
  • capture an image of the viewport if nothing is provided
  • page/PageConsoleClient.h:
  • page/PageConsoleClient.cpp:

(WebCore::PageConsoleClient::addMessage):
(WebCore::PageConsoleClient::screenshot): Added.

  • workers/WorkerConsoleClient.h:
  • workers/WorkerConsoleClient.cpp:

(WebCore::WorkerConsoleClient::screenshot): Added.

  • worklets/WorkletConsoleClient.h:
  • worklets/WorkletConsoleClient.cpp:

(WebCore::WorkletConsoleClient::screenshot): Added.

  • inspector/CommandLineAPIModuleSource.js:

(CommandLineAPIImpl.prototype.screenshot): Added.

  • inspector/InspectorInstrumentation.h:

Source/WebInspectorUI:

Add console.screenshot functionality, which displays a screenshot of a given object (if
able) within Web Inspector's Console tab. From there, it can be viewed and saved.

Currently, console.screenshot will

  • capture an image of a Node (if provided)
  • capture an image of the viewport if nothing is provided
  • UserInterface/Models/ConsoleMessage.js:

(WI.ConsoleMessage):

  • UserInterface/Views/ConsoleCommandView.js:

(WI.ConsoleCommandView.prototype.render):

  • UserInterface/Views/ConsoleMessageView.js:

(WI.ConsoleMessageView.prototype.render):
(WI.ConsoleMessageView.prototype.toClipboardString):
(WI.ConsoleMessageView.prototype._appendMessageTextAndArguments):
(WI.ConsoleMessageView.prototype._appendSavedResultIndex):
(WI.ConsoleMessageView.prototype._appendStackTrace):
(WI.ConsoleMessageView.prototype._makeExpandable):
(WI.ConsoleMessageView.prototype._handleContextMenu): Added.

  • UserInterface/Views/ConsoleMessageView.css:

(.console-user-command.special-user-log > .console-message-body): Added.
(.console-message-body): Added.
(.console-message-body > span): Added.
(.console-message-body > span > :matches(.console-message-enclosed, .console-message-preview, .console-message-preview-divider)): Added.
(.console-message-body > .console-image): Added.
(.console-message-body > .show-grid): Added.
(.console-error-level .console-message-body): Added.
(.console-warning-level .console-message-body): Added.
(.console-log-level.console-image-container::before): Added.
(.console-user-command > .console-message-body): Added.
(.console-warning-level .console-message-body): Added.
(.console-error-level .console-message-body): Added.
(.console-user-command > .console-message-body): Added.
(.console-user-command.special-user-log > .console-message-text): Deleted.
(.console-message-text): Deleted.
(.console-message-text > span): Deleted.
(.console-message-text > span > :matches(.console-message-enclosed, .console-message-preview, .console-message-preview-divider)): Deleted.
(.console-error-level .console-message-text): Deleted.
(.console-warning-level .console-message-text): Deleted.
(.console-user-command > .console-message-text): Deleted.
(.console-warning-level .console-message-text): Deleted.
(.console-error-level .console-message-text): Deleted.
(.console-user-command > .console-message-text): Deleted.

  • UserInterface/Views/LogContentView.css:

(.search-in-progress .console-item:not(.filtered-out-by-search).special-user-log .console-message-text .highlighted): Added.
(.search-in-progress .console-item:not(.filtered-out-by-search).special-user-log .console-message-body .highlighted): Deleted.
Renamed variables/classes to be more semantically correct when the content is an image.

  • _messageTextElement to _messageBodyElement (JS)
  • .console-message-text to .console-message-body (CSS)
  • UserInterface/Controllers/JavaScriptLogViewController.js:

(WI.JavaScriptLogViewController.prototype.renderPendingMessages):

  • UserInterface/Views/Main.css:

(:matches(img, canvas).show-grid):
(@media (prefers-color-scheme: dark) :matches(img, canvas).show-grid):

  • UserInterface/Base/FileUtilities.js:

(WI.FileUtilities.screenshotString): Added.

  • UserInterface/Models/NativeFunctionParameters.js:
  • UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
  • UserInterface/Images/ConsoleImage.svg: Copied from UserInterface/Images/Canvas.svg.
  • Localizations/en.lproj/localizedStrings.js:

LayoutTests:

  • js/console.html:
  • js/console-expected.txt:
  • inspector/console/console-screenshot.html: Added.
  • inspector/console/console-screenshot-expected.txt: Added.
  • http/tests/inspector/dom/cross-domain-inspected-node-access-expected.txt:
Location:
trunk
Files:
3 added
35 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r242986 r242992  
     12019-03-15  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: provide a way to capture a screenshot of a node from within the page
     4        https://bugs.webkit.org/show_bug.cgi?id=194279
     5        <rdar://problem/10731573>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        * js/console.html:
     10        * js/console-expected.txt:
     11        * inspector/console/console-screenshot.html: Added.
     12        * inspector/console/console-screenshot-expected.txt: Added.
     13        * http/tests/inspector/dom/cross-domain-inspected-node-access-expected.txt:
     14
    1152019-03-14  Sihui Liu  <sihui_liu@apple.com>
    216
  • trunk/LayoutTests/http/tests/inspector/dom/cross-domain-inspected-node-access-expected.txt

    r239583 r242992  
    1 CONSOLE MESSAGE: line 43: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
    2 CONSOLE MESSAGE: line 43: Blocked a frame with origin "http://localhost:8000" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
     1CONSOLE MESSAGE: line 44: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
     2CONSOLE MESSAGE: line 44: Blocked a frame with origin "http://localhost:8000" from accessing a frame with origin "http://127.0.0.1:8000". Protocols, domains, and ports must match.
    33Test that code evaluated in the main frame cannot access $0 that resolves to a node in a frame from a different domain. Bug 105423.
    44
  • trunk/LayoutTests/js/console-expected.txt

    r225488 r242992  
    178178PASS descriptor.enumerable is true
    179179
     180console.screenshot
     181PASS typeof console.screenshot is "function"
     182PASS console.screenshot.length is 0
     183PASS descriptor.configurable is true
     184PASS descriptor.writable is true
     185PASS descriptor.enumerable is true
     186
    180187PASS Object.getOwnPropertyNames(console).length is enumerablePropertyCount
    181188
     
    219226PASS console.record(window.canvas) did not throw exception.
    220227PASS console.record(window.canvas) did not throw exception.
     228
     229fuzzing of target for console.screenshot
     230PASS console.screenshot() did not throw exception.
     231PASS console.screenshot(undefined) did not throw exception.
     232PASS console.screenshot(null) did not throw exception.
     233PASS console.screenshot(1) did not throw exception.
     234PASS console.screenshot("test") did not throw exception.
     235PASS console.screenshot([]) did not throw exception.
     236PASS console.screenshot({}) did not throw exception.
     237PASS console.screenshot(window) did not throw exception.
     238PASS console.screenshot(console) did not throw exception.
     239
    221240PASS successfullyParsed is true
    222241
  • trunk/LayoutTests/js/console.html

    r225488 r242992  
    4848shouldBe("Object.getOwnPropertyNames(console).length", "enumerablePropertyCount");
    4949
    50 const consoleRecordArguments = [
     50const fuzzingArguments = [
    5151        `undefined`,
    5252        `null`,
     
    6262debug("fuzzing of target for console.record");
    6363shouldNotThrow(`console.record()`);
    64 for (let argument of consoleRecordArguments)
     64for (let argument of fuzzingArguments)
    6565        shouldNotThrow(`console.record(${argument})`);
    6666
    6767debug("");
    6868debug("fuzzing of options for console.record");
    69 for (let argument of consoleRecordArguments)
     69for (let argument of fuzzingArguments)
    7070        shouldNotThrow(`console.record({}, ${argument})`);
    7171
     
    7373debug("fuzzing of target for console.recordEnd");
    7474shouldNotThrow(`console.recordEnd()`);
    75 for (let argument of consoleRecordArguments)
     75for (let argument of fuzzingArguments)
    7676        shouldNotThrow(`console.recordEnd(${argument})`);
    7777
     
    8585shouldNotThrow(`console.record(window.canvas)`);
    8686
     87debug("");
     88debug("fuzzing of target for console.screenshot");
     89shouldNotThrow(`console.screenshot()`);
     90for (let argument of fuzzingArguments)
     91    shouldNotThrow(`console.screenshot(${argument})`);
     92
     93debug("");
     94
    8795</script>
    8896<script src="../resources/js-test-post.js"></script>
  • trunk/Source/JavaScriptCore/ChangeLog

    r242991 r242992  
     12019-03-15  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: provide a way to capture a screenshot of a node from within the page
     4        https://bugs.webkit.org/show_bug.cgi?id=194279
     5        <rdar://problem/10731573>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        Add `console.screenshot` functionality, which displays a screenshot of a given object (if
     10        able) within Web Inspector's Console tab. From there, it can be viewed and saved.
     11
     12        Currently, `console.screenshot` will
     13         - capture an image of a `Node` (if provided)
     14         - capture an image of the viewport if nothing is provided
     15
     16        * inspector/protocol/Console.json:
     17        Add `Image` enum value to `ConsoleMessage` type.
     18        * runtime/ConsoleTypes.h:
     19        * inspector/ConsoleMessage.h:
     20        * inspector/ConsoleMessage.cpp:
     21        (Inspector::messageTypeValue):
     22
     23        * runtime/ConsoleClient.h:
     24        * runtime/ConsoleObject.cpp:
     25        (JSC::ConsoleObject::finishCreation):
     26        (JSC::consoleProtoFuncScreenshot): Added.
     27
     28        * inspector/JSGlobalObjectConsoleClient.h:
     29        * inspector/JSGlobalObjectConsoleClient.cpp:
     30        (Inspector::JSGlobalObjectConsoleClient::screenshot): Added.
     31
    1322019-03-14  Yusuke Suzuki  <ysuzuki@apple.com>
    233
  • trunk/Source/JavaScriptCore/inspector/ConsoleMessage.cpp

    r241729 r242992  
    192192    case MessageType::Profile: return Protocol::Console::ConsoleMessage::Type::Profile;
    193193    case MessageType::ProfileEnd: return Protocol::Console::ConsoleMessage::Type::ProfileEnd;
     194    case MessageType::Image: return Protocol::Console::ConsoleMessage::Type::Image;
    194195    }
    195196    return Protocol::Console::ConsoleMessage::Type::Log;
  • trunk/Source/JavaScriptCore/inspector/ConsoleMessage.h

    r225764 r242992  
    5656    ConsoleMessage(MessageSource, MessageType, MessageLevel, const String& message, const String& url, unsigned line, unsigned column, JSC::ExecState* = nullptr, unsigned long requestIdentifier = 0);
    5757    ConsoleMessage(MessageSource, MessageType, MessageLevel, const String& message, Ref<ScriptCallStack>&&, unsigned long requestIdentifier = 0);
    58     ConsoleMessage(MessageSource, MessageType, MessageLevel, const String& message, Ref<ScriptArguments>&&, JSC::ExecState*, unsigned long requestIdentifier = 0);
     58    ConsoleMessage(MessageSource, MessageType, MessageLevel, const String& message, Ref<ScriptArguments>&&, JSC::ExecState* = nullptr, unsigned long requestIdentifier = 0);
    5959    ConsoleMessage(MessageSource, MessageType, MessageLevel, Vector<JSONLogValue>&&, JSC::ExecState*, unsigned long requestIdentifier = 0);
    6060    ~ConsoleMessage();
  • trunk/Source/JavaScriptCore/inspector/JSGlobalObjectConsoleClient.cpp

    r233122 r242992  
    169169void JSGlobalObjectConsoleClient::recordEnd(ExecState*, Ref<ScriptArguments>&&) { }
    170170
     171void JSGlobalObjectConsoleClient::screenshot(ExecState*, Ref<ScriptArguments>&&)
     172{
     173    warnUnimplemented("console.screenshot"_s);
     174}
     175
    171176void JSGlobalObjectConsoleClient::warnUnimplemented(const String& method)
    172177{
  • trunk/Source/JavaScriptCore/inspector/JSGlobalObjectConsoleClient.h

    r225488 r242992  
    5656    void record(JSC::ExecState*, Ref<ScriptArguments>&&) override;
    5757    void recordEnd(JSC::ExecState*, Ref<ScriptArguments>&&) override;
     58    void screenshot(JSC::ExecState*, Ref<ScriptArguments>&&) override;
    5859
    5960private:
  • trunk/Source/JavaScriptCore/inspector/protocol/Console.json

    r241729 r242992  
    3232                { "name": "level", "type": "string", "enum": ["log", "info", "warning", "error", "debug"], "description": "Message severity." },
    3333                { "name": "text", "type": "string", "description": "Message text." },
    34                 { "name": "type", "type": "string", "optional": true, "enum": ["log", "dir", "dirxml", "table", "trace", "clear", "startGroup", "startGroupCollapsed", "endGroup", "assert", "timing", "profile", "profileEnd"], "description": "Console message type." },
     34                { "name": "type", "type": "string", "optional": true, "enum": ["log", "dir", "dirxml", "table", "trace", "clear", "startGroup", "startGroupCollapsed", "endGroup", "assert", "timing", "profile", "profileEnd", "image"], "description": "Console message type." },
    3535                { "name": "url", "type": "string", "optional": true, "description": "URL of the message origin." },
    3636                { "name": "line", "type": "integer", "optional": true, "description": "Line number in the resource that generated this message." },
  • trunk/Source/JavaScriptCore/runtime/ConsoleClient.h

    r225488 r242992  
    6565    virtual void record(ExecState*, Ref<Inspector::ScriptArguments>&&) = 0;
    6666    virtual void recordEnd(ExecState*, Ref<Inspector::ScriptArguments>&&) = 0;
     67    virtual void screenshot(ExecState*, Ref<Inspector::ScriptArguments>&&) = 0;
    6768
    6869private:
  • trunk/Source/JavaScriptCore/runtime/ConsoleObject.cpp

    r233122 r242992  
    6060static EncodedJSValue JSC_HOST_CALL consoleProtoFuncRecord(ExecState*);
    6161static EncodedJSValue JSC_HOST_CALL consoleProtoFuncRecordEnd(ExecState*);
     62static EncodedJSValue JSC_HOST_CALL consoleProtoFuncScreenshot(ExecState*);
    6263
    6364const ClassInfo ConsoleObject::s_info = { "Console", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ConsoleObject) };
     
    100101    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("record", consoleProtoFuncRecord, static_cast<unsigned>(PropertyAttribute::None), 0);
    101102    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("recordEnd", consoleProtoFuncRecordEnd, static_cast<unsigned>(PropertyAttribute::None), 0);
     103    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("screenshot", consoleProtoFuncScreenshot, static_cast<unsigned>(PropertyAttribute::None), 0);
    102104}
    103105
     
    392394}
    393395
     396static EncodedJSValue JSC_HOST_CALL consoleProtoFuncScreenshot(ExecState* exec)
     397{
     398    ConsoleClient* client = exec->lexicalGlobalObject()->consoleClient();
     399    if (!client)
     400        return JSValue::encode(jsUndefined());
     401
     402    client->screenshot(exec, Inspector::createScriptArguments(exec, 0));
     403    return JSValue::encode(jsUndefined());
     404}
     405
    394406} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/ConsoleTypes.h

    r241729 r242992  
    6161    Profile,
    6262    ProfileEnd,
     63    Image,
    6364};
    6465
  • trunk/Source/WebCore/ChangeLog

    r242991 r242992  
     12019-03-15  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: provide a way to capture a screenshot of a node from within the page
     4        https://bugs.webkit.org/show_bug.cgi?id=194279
     5        <rdar://problem/10731573>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        Test: inspector/console/console-screenshot.html
     10
     11        Add `console.screenshot` functionality, which displays a screenshot of a given object (if
     12        able) within Web Inspector's Console tab. From there, it can be viewed and saved.
     13
     14        Currently, `console.screenshot` will
     15         - capture an image of a `Node` (if provided)
     16         - capture an image of the viewport if nothing is provided
     17
     18        * page/PageConsoleClient.h:
     19        * page/PageConsoleClient.cpp:
     20        (WebCore::PageConsoleClient::addMessage):
     21        (WebCore::PageConsoleClient::screenshot): Added.
     22
     23        * workers/WorkerConsoleClient.h:
     24        * workers/WorkerConsoleClient.cpp:
     25        (WebCore::WorkerConsoleClient::screenshot): Added.
     26        * worklets/WorkletConsoleClient.h:
     27        * worklets/WorkletConsoleClient.cpp:
     28        (WebCore::WorkletConsoleClient::screenshot): Added.
     29
     30        * inspector/CommandLineAPIModuleSource.js:
     31        (CommandLineAPIImpl.prototype.screenshot): Added.
     32
     33        * inspector/InspectorInstrumentation.h:
     34
    1352019-03-14  Yusuke Suzuki  <ysuzuki@apple.com>
    236
  • trunk/Source/WebCore/inspector/CommandLineAPIModuleSource.js

    r242940 r242992  
    8484    "profileEnd",
    8585    "queryObjects",
     86    "screenshot",
    8687    "table",
    8788    "unmonitorEvents",
     
    199200    {
    200201        return inspectedWindow.console.table.apply(inspectedWindow.console, arguments)
     202    },
     203
     204    screenshot: function()
     205    {
     206        return inspectedWindow.console.screenshot.apply(inspectedWindow.console, arguments)
    201207    },
    202208
  • trunk/Source/WebCore/inspector/InspectorInstrumentation.h

    r242594 r242992  
    9999struct WebSocketFrame;
    100100
    101 #define FAST_RETURN_IF_NO_FRONTENDS(value) if (LIKELY(!hasFrontends())) return value;
     101#define FAST_RETURN_IF_NO_FRONTENDS(value) if (LIKELY(!InspectorInstrumentation::hasFrontends())) return value;
    102102
    103103class InspectorInstrumentation {
  • trunk/Source/WebCore/page/PageConsoleClient.cpp

    r236008 r242992  
    3535#include "Document.h"
    3636#include "Frame.h"
     37#include "FrameSnapshotting.h"
    3738#include "HTMLCanvasElement.h"
    3839#include "ImageBitmapRenderingContext.h"
     40#include "ImageBuffer.h"
    3941#include "InspectorController.h"
    4042#include "InspectorInstrumentation.h"
     43#include "IntRect.h"
    4144#include "JSCanvasRenderingContext2D.h"
    4245#include "JSExecState.h"
    4346#include "JSHTMLCanvasElement.h"
    4447#include "JSImageBitmapRenderingContext.h"
     48#include "JSNode.h"
    4549#include "JSOffscreenCanvas.h"
     50#include "Node.h"
    4651#include "OffscreenCanvas.h"
    4752#include "Page.h"
     
    120125void PageConsoleClient::addMessage(std::unique_ptr<Inspector::ConsoleMessage>&& consoleMessage)
    121126{
    122     if (consoleMessage->source() != MessageSource::CSS && !m_page.usesEphemeralSession()) {
     127    if (consoleMessage->source() != MessageSource::CSS && consoleMessage->type() != MessageType::Image && !m_page.usesEphemeralSession()) {
    123128        m_page.chrome().client().addMessageToConsole(consoleMessage->source(), consoleMessage->level(), consoleMessage->message(), consoleMessage->line(), consoleMessage->column(), consoleMessage->url());
    124129
     
    259264}
    260265
     266void PageConsoleClient::screenshot(JSC::ExecState* state, Ref<ScriptArguments>&& arguments)
     267{
     268    FAST_RETURN_IF_NO_FRONTENDS(void());
     269
     270    Frame& frame = m_page.mainFrame();
     271
     272    std::unique_ptr<ImageBuffer> snapshot;
     273
     274    auto* target = objectArgumentAt(arguments, 0);
     275    if (target) {
     276        auto* node = JSNode::toWrapped(state->vm(), target);
     277        if (!node)
     278            return;
     279
     280        snapshot = WebCore::snapshotNode(frame, *node);
     281    } else {
     282        // If no target is provided, capture an image of the viewport.
     283        IntRect imageRect(IntPoint::zero(), frame.view()->sizeForVisibleContent());
     284        snapshot = WebCore::snapshotFrameRect(frame, imageRect, SnapshotOptionsInViewCoordinates);
     285    }
     286
     287    if (!snapshot) {
     288        addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Error, "Could not capture screenshot"_s, arguments.copyRef()));
     289        return;
     290    }
     291
     292    String dataURL = snapshot->toDataURL("image/png"_s, WTF::nullopt, PreserveResolution::Yes);
     293    if (dataURL.isEmpty()) {
     294        addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Error, "Could not capture screenshot"_s, arguments.copyRef()));
     295        return;
     296    }
     297
     298    if (target) {
     299        // Log the argument before sending the image for it.
     300        String messageText;
     301        arguments->getFirstArgumentAsString(messageText);
     302        addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Log, MessageLevel::Log, messageText, arguments.copyRef()));
     303    }
     304
     305    addMessage(std::make_unique<Inspector::ConsoleMessage>(MessageSource::ConsoleAPI, MessageType::Image, MessageLevel::Log, dataURL));
     306}
     307
    261308} // namespace WebCore
  • trunk/Source/WebCore/page/PageConsoleClient.h

    r228218 r242992  
    7777    void record(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) override;
    7878    void recordEnd(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) override;
     79    void screenshot(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) override;
    7980
    8081private:
  • trunk/Source/WebCore/workers/WorkerConsoleClient.cpp

    r228218 r242992  
    7777void WorkerConsoleClient::recordEnd(JSC::ExecState*, Ref<ScriptArguments>&&) { }
    7878
     79void WorkerConsoleClient::screenshot(JSC::ExecState*, Ref<ScriptArguments>&&) { }
     80
    7981} // namespace WebCore
  • trunk/Source/WebCore/workers/WorkerConsoleClient.h

    r228218 r242992  
    5353    void record(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) override;
    5454    void recordEnd(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) override;
     55    void screenshot(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) override;
    5556
    5657private:
  • trunk/Source/WebCore/worklets/WorkletConsoleClient.cpp

    r237766 r242992  
    6767void WorkletConsoleClient::recordEnd(JSC::ExecState*, Ref<ScriptArguments>&&) { }
    6868
     69void WorkletConsoleClient::screenshot(JSC::ExecState*, Ref<ScriptArguments>&&) { }
     70
    6971} // namespace WebCore
    7072#endif
  • trunk/Source/WebCore/worklets/WorkletConsoleClient.h

    r237766 r242992  
    5555    void record(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) final;
    5656    void recordEnd(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) final;
     57    void screenshot(JSC::ExecState*, Ref<Inspector::ScriptArguments>&&) final;
    5758
    5859    WorkletGlobalScope& m_workletGlobalScope;
  • trunk/Source/WebInspectorUI/ChangeLog

    r242973 r242992  
     12019-03-15  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: provide a way to capture a screenshot of a node from within the page
     4        https://bugs.webkit.org/show_bug.cgi?id=194279
     5        <rdar://problem/10731573>
     6
     7        Reviewed by Joseph Pecoraro.
     8
     9        Add `console.screenshot` functionality, which displays a screenshot of a given object (if
     10        able) within Web Inspector's Console tab. From there, it can be viewed and saved.
     11
     12        Currently, `console.screenshot` will
     13         - capture an image of a `Node` (if provided)
     14         - capture an image of the viewport if nothing is provided
     15
     16        * UserInterface/Models/ConsoleMessage.js:
     17        (WI.ConsoleMessage):
     18        * UserInterface/Views/ConsoleCommandView.js:
     19        (WI.ConsoleCommandView.prototype.render):
     20        * UserInterface/Views/ConsoleMessageView.js:
     21        (WI.ConsoleMessageView.prototype.render):
     22        (WI.ConsoleMessageView.prototype.toClipboardString):
     23        (WI.ConsoleMessageView.prototype._appendMessageTextAndArguments):
     24        (WI.ConsoleMessageView.prototype._appendSavedResultIndex):
     25        (WI.ConsoleMessageView.prototype._appendStackTrace):
     26        (WI.ConsoleMessageView.prototype._makeExpandable):
     27        (WI.ConsoleMessageView.prototype._handleContextMenu): Added.
     28        * UserInterface/Views/ConsoleMessageView.css:
     29        (.console-user-command.special-user-log > .console-message-body): Added.
     30        (.console-message-body): Added.
     31        (.console-message-body > span): Added.
     32        (.console-message-body > span > :matches(.console-message-enclosed, .console-message-preview, .console-message-preview-divider)): Added.
     33        (.console-message-body > .console-image): Added.
     34        (.console-message-body > .show-grid): Added.
     35        (.console-error-level .console-message-body): Added.
     36        (.console-warning-level .console-message-body): Added.
     37        (.console-log-level.console-image-container::before): Added.
     38        (.console-user-command > .console-message-body): Added.
     39        (.console-warning-level .console-message-body): Added.
     40        (.console-error-level .console-message-body): Added.
     41        (.console-user-command > .console-message-body): Added.
     42        (.console-user-command.special-user-log > .console-message-text): Deleted.
     43        (.console-message-text): Deleted.
     44        (.console-message-text > span): Deleted.
     45        (.console-message-text > span > :matches(.console-message-enclosed, .console-message-preview, .console-message-preview-divider)): Deleted.
     46        (.console-error-level .console-message-text): Deleted.
     47        (.console-warning-level .console-message-text): Deleted.
     48        (.console-user-command > .console-message-text): Deleted.
     49        (.console-warning-level .console-message-text): Deleted.
     50        (.console-error-level .console-message-text): Deleted.
     51        (.console-user-command > .console-message-text): Deleted.
     52        * UserInterface/Views/LogContentView.css:
     53        (.search-in-progress .console-item:not(.filtered-out-by-search).special-user-log .console-message-text .highlighted): Added.
     54        (.search-in-progress .console-item:not(.filtered-out-by-search).special-user-log .console-message-body .highlighted): Deleted.
     55        Renamed variables/classes to be more semantically correct when the content is an image.
     56         - `_messageTextElement` to `_messageBodyElement` (JS)
     57         - `.console-message-text` to `.console-message-body` (CSS)
     58
     59        * UserInterface/Controllers/JavaScriptLogViewController.js:
     60        (WI.JavaScriptLogViewController.prototype.renderPendingMessages):
     61
     62        * UserInterface/Views/Main.css:
     63        (:matches(img, canvas).show-grid):
     64        (@media (prefers-color-scheme: dark) :matches(img, canvas).show-grid):
     65
     66        * UserInterface/Base/FileUtilities.js:
     67        (WI.FileUtilities.screenshotString): Added.
     68
     69        * UserInterface/Models/NativeFunctionParameters.js:
     70        * UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
     71
     72        * UserInterface/Images/ConsoleImage.svg: Copied from UserInterface/Images/Canvas.svg.
     73        * Localizations/en.lproj/localizedStrings.js:
     74
    1752019-03-14  Nikita Vasilyev  <nvasilyev@apple.com>
    276
  • trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

    r242948 r242992  
    865865localizedStrings["Save %d"] = "Save %d";
    866866localizedStrings["Save File"] = "Save File";
     867localizedStrings["Save Image"] = "Save Image";
    867868localizedStrings["Save Selected"] = "Save Selected";
    868869localizedStrings["Save configuration"] = "Save configuration";
  • trunk/Source/WebInspectorUI/UserInterface/Base/FileUtilities.js

    r238198 r242992  
    2525
    2626WI.FileUtilities = class FileUtilities {
     27    static screenshotString()
     28    {
     29        let date = new Date;
     30        let values = [
     31            date.getFullYear(),
     32            Number.zeroPad(date.getMonth() + 1, 2),
     33            Number.zeroPad(date.getDate(), 2),
     34            Number.zeroPad(date.getHours(), 2),
     35            Number.zeroPad(date.getMinutes(), 2),
     36            Number.zeroPad(date.getSeconds(), 2),
     37        ];
     38        return WI.UIString("Screen Shot %s-%s-%s at %s.%s.%s").format(...values);
     39    }
     40
    2741    static save(saveData, forceSaveAs)
    2842    {
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptLogViewController.js

    r241633 r242992  
    324324        this._currentSessionOrGroup = savedCurrentConsoleGroup;
    325325
    326         if (wasScrolledToBottom || lastMessageView instanceof WI.ConsoleCommandView || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Result)
     326        if (wasScrolledToBottom || lastMessageView instanceof WI.ConsoleCommandView || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Result || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Image)
    327327            this.scrollToBottom();
    328328
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js

    r239583 r242992  
    320320    "profileEnd",
    321321    "queryObjects",
     322    "screenshot",
    322323    "table",
    323324    "unmonitorEvents",
  • trunk/Source/WebInspectorUI/UserInterface/Models/ConsoleMessage.js

    r241729 r242992  
    2828    constructor(target, source, level, message, type, url, line, column, repeatCount, parameters, callFrames, request)
    2929    {
     30        console.assert(target instanceof WI.Target);
    3031        console.assert(typeof source === "string");
    3132        console.assert(typeof level === "string");
    3233        console.assert(typeof message === "string");
    33         console.assert(target instanceof WI.Target);
     34        console.assert(!type || Object.values(WI.ConsoleMessage.MessageType).includes(type));
    3435        console.assert(!parameters || parameters.every((x) => x instanceof WI.RemoteObject));
    3536
     
    128129    Profile: "profile",
    129130    ProfileEnd: "profileEnd",
     131    Image: "image",
    130132    Result: "result", // Frontend Only.
    131133};
  • trunk/Source/WebInspectorUI/UserInterface/Models/NativeFunctionParameters.js

    r242940 r242992  
    176176        record: "object, [options]",
    177177        recordEnd: "object",
     178        screenshot: "[node]",
    178179        table: "data, [columns]",
    179180        takeHeapSnapshot: "[label]",
  • trunk/Source/WebInspectorUI/UserInterface/Views/ConsoleCommandView.js

    r220119 r242992  
    5050
    5151        this._formattedCommandElement = this._element.appendChild(document.createElement("span"));
    52         this._formattedCommandElement.classList.add("console-message-text");
     52        this._formattedCommandElement.classList.add("console-message-body");
    5353        this._formattedCommandElement.textContent = this._commandText;
    5454
  • trunk/Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.css

    r239760 r242992  
    3030}
    3131
    32 .console-user-command.special-user-log > .console-message-text {
     32.console-user-command.special-user-log > .console-message-body {
    3333    padding: 0 6px 1px;
    3434    border-radius: 3px;
     
    6060}
    6161
    62 .console-message-text {
     62.console-message-body {
    6363    white-space: pre-wrap;
    6464}
    6565
    66 .console-message-text > span {
     66.console-message-body > span {
    6767    -webkit-user-select: text;
    6868}
    6969
    70 .console-message-text > span > :matches(.console-message-enclosed, .console-message-preview, .console-message-preview-divider) {
     70.console-message-body > span > :matches(.console-message-enclosed, .console-message-preview, .console-message-preview-divider) {
    7171    -webkit-user-select: none;
     72}
     73
     74.console-message-body > .console-image {
     75    max-width: 500px;
     76    max-height: 500px;
     77    box-shadow: 1px 2px 6px hsla(0, 0%, 0%, 0.58);
     78}
     79
     80.console-message-body > .show-grid {
     81    /* Prevents the light blue highlight from being visible in the checkerboard. */
     82    --checkerboard-light-square: white;
    7283}
    7384
     
    161172}
    162173
    163 .console-error-level .console-message-text {
     174.console-error-level .console-message-body {
    164175    color: hsl(0, 75%, 45%);
    165176}
     
    170181}
    171182
    172 .console-warning-level .console-message-text {
     183.console-warning-level .console-message-body {
    173184    color: hsl(30, 90%, 35%);
    174185}
     
    183194}
    184195
     196.console-log-level.console-image-container::before {
     197    content: url(../Images/ConsoleImage.svg);
     198}
     199
    185200.console-info-level::before {
    186201    content: url(../Images/Info.svg);
     
    213228}
    214229
    215 .console-user-command > .console-message-text {
     230.console-user-command > .console-message-body {
    216231    color: hsl(209, 100%, 50%);
    217232    -webkit-user-select: text;
     
    291306    }
    292307
    293     .console-warning-level .console-message-text {
     308    .console-warning-level .console-message-body {
    294309        color: hsl(53, 80%, 55%);
    295310    }
    296311
    297     .console-error-level .console-message-text {
     312    .console-error-level .console-message-body {
    298313        color: hsl(10, 100%, 70%);
    299314    }
    300315
    301     .console-user-command > .console-message-text {
     316    .console-user-command > .console-message-body {
    302317        color: hsl(209, 100%, 70%);
    303318    }
  • trunk/Source/WebInspectorUI/UserInterface/Views/ConsoleMessageView.js

    r241643 r242992  
    8989        this._appendLocationLink();
    9090
    91         this._messageTextElement = this._element.appendChild(document.createElement("span"));
    92         this._messageTextElement.classList.add("console-top-level-message");
    93         this._messageTextElement.classList.add("console-message-text");
    94         this._appendMessageTextAndArguments(this._messageTextElement);
     91        this._messageBodyElement = this._element.appendChild(document.createElement("span"));
     92        this._messageBodyElement.classList.add("console-top-level-message", "console-message-body");
     93        this._appendMessageTextAndArguments(this._messageBodyElement);
    9594        this._appendSavedResultIndex();
    9695
     
    9998
    10099        this._renderRepeatCount();
     100
     101        if (this._message.type === WI.ConsoleMessage.MessageType.Image) {
     102            this._element.classList.add("console-image-container");
     103            this._element.addEventListener("contextmenu", this._handleContextMenu.bind(this));
     104        }
    101105    }
    102106
     
    198202    toClipboardString(isPrefixOptional)
    199203    {
    200         let clipboardString = this._messageTextElement.innerText.removeWordBreakCharacters();
     204        let clipboardString = this._messageBodyElement.innerText.removeWordBreakCharacters();
    201205        if (this._message.savedResultIndex)
    202206            clipboardString = clipboardString.replace(/\s*=\s*(\$\d+)$/, "");
     
    285289                break;
    286290
     291            case WI.ConsoleMessage.MessageType.Image: {
     292                let img = element.appendChild(document.createElement("img"));
     293                img.classList.add("console-image", "show-grid");
     294                img.src = this._message.messageText;
     295                img.setAttribute("filename", WI.FileUtilities.screenshotString() + ".png");
     296                img.addEventListener("load", (event) => {
     297                    if (img.width >= img.height)
     298                        img.width = img.width / window.devicePixelRatio;
     299                    else
     300                        img.height = img.height / window.devicePixelRatio;
     301                });
     302                break;
     303            }
     304
    287305            default:
    288306                var args = this._message.parameters || [this._message.messageText];
     
    314332            this._objectTree.appendTitleSuffix(savedVariableElement);
    315333        else
    316             this._messageTextElement.appendChild(savedVariableElement);
     334            this._messageBodyElement.appendChild(savedVariableElement);
    317335    }
    318336
     
    404422
    405423        this._stackTraceElement = this._element.appendChild(document.createElement("div"));
    406         this._stackTraceElement.classList.add("console-message-text", "console-message-stack-trace-container");
     424        this._stackTraceElement.classList.add("console-message-body", "console-message-stack-trace-container");
    407425
    408426        var callFramesElement = new WI.StackTraceView(this._message.stackTrace).element;
     
    921939
    922940        this._boundClickHandler = this.toggle.bind(this);
    923         this._messageTextElement.addEventListener("click", this._boundClickHandler);
     941        this._messageBodyElement.addEventListener("click", this._boundClickHandler);
     942    }
     943
     944    _handleContextMenu(event)
     945    {
     946        let image = event.target.closest(".console-image");
     947        if (!image)
     948            return;
     949
     950        let contextMenu = WI.ContextMenu.createFromEvent(event);
     951
     952        contextMenu.appendItem(WI.UIString("Save Image"), () => {
     953            const forceSaveAs = true;
     954            WI.FileUtilities.save({
     955                url: encodeURI("web-inspector:///" + image.getAttribute("filename")),
     956                content: parseDataURL(this._message.messageText).data,
     957                base64Encoded: true,
     958            }, forceSaveAs);
     959        });
     960
     961        contextMenu.appendSeparator();
    924962    }
    925963};
  • trunk/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js

    r242049 r242992  
    251251                }
    252252
    253                 let date = new Date;
    254                 let values = [
    255                     date.getFullYear(),
    256                     Number.zeroPad(date.getMonth() + 1, 2),
    257                     Number.zeroPad(date.getDate(), 2),
    258                     Number.zeroPad(date.getHours(), 2),
    259                     Number.zeroPad(date.getMinutes(), 2),
    260                     Number.zeroPad(date.getSeconds(), 2),
    261                 ];
    262                 let filename = WI.UIString("Screen Shot %s-%s-%s at %s.%s.%s").format(...values);
    263253                WI.FileUtilities.save({
    264                     url: encodeURI(`web-inspector:///${filename}.png`),
     254                    url: encodeURI(`web-inspector:///${WI.FileUtilities.screenshotString()}.png`),
    265255                    content: parseDataURL(dataURL).data,
    266256                    base64Encoded: true,
  • trunk/Source/WebInspectorUI/UserInterface/Views/LogContentView.css

    r242604 r242992  
    227227}
    228228
    229 .search-in-progress .console-item:not(.filtered-out-by-search).special-user-log .console-message-text .highlighted {
     229.search-in-progress .console-item:not(.filtered-out-by-search).special-user-log .console-message-body .highlighted {
    230230    color: var(--selected-foreground-color);
    231231    background-color: var(--selected-background-color-highlight);
  • trunk/Source/WebInspectorUI/UserInterface/Views/Main.css

    r242768 r242992  
    412412
    413413:matches(img, canvas).show-grid {
    414     background-image: linear-gradient(315deg, transparent 75%, hsl(0, 0%, 95%) 75%),
    415                       linear-gradient(45deg, transparent 75%, hsl(0, 0%, 95%) 75%),
    416                       linear-gradient(315deg, hsl(0, 0%, 95%) 25%, transparent 25%),
    417                       linear-gradient(45deg, hsl(0, 0%, 95%) 25%, transparent 25%);
     414    background-color: var(--checkerboard-light-square);
     415    background-image: linear-gradient(315deg, transparent 75%, var(--checkerboard-dark-square) 75%),
     416                      linear-gradient(45deg, transparent 75%, var(--checkerboard-dark-square) 75%),
     417                      linear-gradient(315deg, var(--checkerboard-dark-square) 25%, transparent 25%),
     418                      linear-gradient(45deg, var(--checkerboard-dark-square) 25%, transparent 25%);
    418419    background-size: 20px 20px;
    419420    background-position: 10px 10px, 10px 0px, 0 0, 0 10px;
     421
     422    --checkerboard-light-square: transparent;
     423    --checkerboard-dark-square: hsl(0, 0%, 95%);
    420424}
    421425
     
    480484
    481485    :matches(img, canvas).show-grid {
    482         background-color: white;
     486        --checkerboard-light-square: white;
    483487        --checkerboard-dark-square: hsl(0, 0%, 80%);
    484         background-image: linear-gradient(315deg, transparent 75%, var(--checkerboard-dark-square) 75%), linear-gradient(45deg, transparent 75%, var(--checkerboard-dark-square) 75%), linear-gradient(315deg, var(--checkerboard-dark-square) 25%, transparent 25%), linear-gradient(45deg, var(--checkerboard-dark-square) 25%, transparent 25%);
    485     }
    486 }
     488    }
     489}
Note: See TracChangeset for help on using the changeset viewer.