Web Inspector: create protocol for recording Canvas contexts
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 26 Jul 2017 21:45:22 +0000 (21:45 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 26 Jul 2017 21:45:22 +0000 (21:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=174481

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

* inspector/protocol/Canvas.json:
 - Add `requestRecording` command to mark the provided canvas as having requested a recording.
 - Add `cancelRecording` command to clear a previously marked canvas and flush any recorded data.
 - Add `recordingFinished` event that is fired once a recording is finished.

* CMakeLists.txt:
* DerivedSources.make:
* inspector/protocol/Recording.json: Added.
 - Add `Type` enum that lists the types of recordings
 - Add `InitialState` type that contains information about the canvas context at the
   beginning of the recording.
 - Add `Frame` type that holds a list of actions that were recorded.
 - Add `Recording` type as the container object of recording data.

* inspector/scripts/codegen/generate_js_backend_commands.py:
(JSBackendCommandsGenerator.generate_domain):
Create an agent for domains with no events or commands.

* inspector/InspectorValues.h:
Make Array `get` public so that values can be retrieved if needed.

Source/WebCore:

Currently, a recording doesn't actually "start" until an action is performed on the context.
This change adds the recording logic, but it does not use it anywhere. Additonal tests will
be added in the patches that add uses:
 - <https://webkit.org/b/174482> Web Inspector: Record actions performed on CanvasRenderingContext2D
 - <https://webkit.org/b/174483> Web Inspector: Record actions performed on WebGLRenderingContext

Test: inspector/model/recording.html

* bindings/scripts/IDLAttributes.json:
* bindings/scripts/CodeGeneratorJS.pm:
(GenerateAttributeGetterBodyDefinition):
(GenerateAttributeSetterBodyDefinition):
(GenerateImplementationFunctionCall):
* WebCore.xcodeproj/project.pbxproj:
* bindings/js/CallTracer.h: Added.
* bindings/js/CallTracer.cpp: Added.
(WebCore::CallTracer::recordCanvasAction):
* bindings/js/CallTracerTypes.h: Added.
* bindings/scripts/test/TestCallTracer.idl: Added.
* bindings/scripts/test/JS/JSTestCallTracer.h: Added.
* bindings/scripts/test/JS/JSTestCallTracer.cpp: Added.

Create new IDL extended attribute called "CallTracingCallback" that will add code to call a
static function on CallTracer with the given extended attribute value as the function name,
the `impl` object as the first parameter, the name of the attribute/operation as the second,
and an optional object that accepts an initializer list of all the parameters as the third.

This function will not be called, however, unless a `callTracingActive` function on the
`impl` object returns true, and this is marked as UNLIKELY.

"CallTracingCallback" can be added to an Interface, in which case it will apply to all
attributes/operations of the generated class, or an individual Attribute/Operation.

* html/canvas/CanvasRenderingContext.h:
(WebCore::CanvasRenderingContext::callTracingActive):
(WebCore::CanvasRenderingContext::setCallTracingActive):

* inspector/InspectorCanvas.h:
* inspector/InspectorCanvas.cpp:
(WebCore::InspectorCanvas::~InspectorCanvas):
(WebCore::InspectorCanvas::resetRecordingData):
(WebCore::InspectorCanvas::hasRecordingData):
(WebCore::InspectorCanvas::recordAction):
(WebCore::InspectorCanvas::releaseInitialState):
(WebCore::InspectorCanvas::releaseFrames):
(WebCore::InspectorCanvas::releaseData):
(WebCore::InspectorCanvas::markNewFrame):
(WebCore::InspectorCanvas::markCurrentFrameIncomplete):
(WebCore::InspectorCanvas::setBufferLimit):
(WebCore::InspectorCanvas::hasBufferSpace):
(WebCore::InspectorCanvas::singleFrame):
(WebCore::InspectorCanvas::setSingleFrame):
(WebCore::InspectorCanvas::indexForData):
(WebCore::buildArrayForAffineTransform):
(WebCore::buildArrayForVector):
(WebCore::InspectorCanvas::buildInitialState):
(WebCore::InspectorCanvas::buildAction):
(WebCore::InspectorCanvas::buildArrayForCanvasGradient):
(WebCore::InspectorCanvas::buildArrayForCanvasPattern):
(WebCore::InspectorCanvas::buildArrayForImageData):

Hold the recording data on the corresponding InspectorCanvas. Recording Frames are
completed when the HTMLCanvasElement paints or a  0_s timer is fired. A recording is not
considered valid until at least one action is performed on the canvas context. Once that
condition is satisfied, canceling the recording will flush the data.

* inspector/InspectorCanvasAgent.h:
* inspector/InspectorCanvasAgent.cpp:
(WebCore::InspectorCanvasAgent::InspectorCanvasAgent):
(WebCore::InspectorCanvasAgent::disable):
(WebCore::InspectorCanvasAgent::requestRecording):
(WebCore::InspectorCanvasAgent::cancelRecording):
(WebCore::InspectorCanvasAgent::recordCanvasAction):
(WebCore::InspectorCanvasAgent::didFinishRecordingCanvasFrame):
(WebCore::InspectorCanvasAgent::canvasDestroyed):
(WebCore::InspectorCanvasAgent::canvasRecordingTimerFired):
(WebCore::InspectorCanvasAgent::clearCanvasData):

* inspector/InspectorInstrumentation.h:
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::recordCanvasAction):
(WebCore::InspectorInstrumentation::recordCanvasActionImpl):
(WebCore::InspectorInstrumentation::didFinishRecordingCanvasFrameImpl):

* html/canvas/CanvasRenderingContext2D.h:
* html/canvas/CanvasRenderingContext2D.cpp:
(WebCore::CanvasRenderingContext2D::stringForWindingRule):
(WebCore::CanvasRenderingContext2D::stringForImageSmoothingQuality):

* platform/graphics/Gradient.h:
(WebCore::Gradient::stops):

* svg/SVGPathUtilities.h:
* svg/SVGPathUtilities.cpp:
(WebCore::buildStringFromPath):

Source/WebInspectorUI:

Create model objects that effectively mirror the protocol objects sent for Canvas recordings.

* .eslintrc:
* UserInterface/Main.html:
* UserInterface/Test.html:

* UserInterface/Controllers/CanvasManager.js:
(WebInspector.CanvasManager.prototype.recordingFinished):
* UserInterface/Protocol/CanvasObserver.js:
(WebInspector.CanvasObserver.prototype.recordingFinished):

* UserInterface/Models/Recording.js: Added.
(WebInspector.Recording):
(WebInspector.Recording.fromPayload):
(WebInspector.Recording.prototype.get type):
(WebInspector.Recording.prototype.get initialState):
(WebInspector.Recording.prototype.get frames):
(WebInspector.Recording.prototype.get data):
(WebInspector.Recording.prototype.toJSON):

* UserInterface/Models/RecordingAction.js: Added.
(WebInspector.RecordingAction):
(WebInspector.RecordingAction.fromPayload):
(WebInspector.RecordingAction.prototype.get name):
(WebInspector.RecordingAction.prototype.get parameters):
(WebInspector.RecordingAction.prototype.toJSON):

* UserInterface/Models/RecordingFrame.js: Added.
(WebInspector.RecordingFrame):
(WebInspector.RecordingFrame.fromPayload):
(WebInspector.RecordingFrame.prototype.get actions):
(WebInspector.RecordingFrame.prototype.get incomplete):
(WebInspector.RecordingFrame.prototype.toJSON):

LayoutTests:

* inspector/model/recording-expected.txt: Added.
* inspector/model/recording.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@219964 268f45cc-cd09-0410-ab3c-d52691b4dbfc

41 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/model/recording-expected.txt [new file with mode: 0644]
LayoutTests/inspector/model/recording.html [new file with mode: 0644]
Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/DerivedSources.make
Source/JavaScriptCore/inspector/InspectorValues.h
Source/JavaScriptCore/inspector/protocol/Canvas.json
Source/JavaScriptCore/inspector/protocol/Recording.json [new file with mode: 0644]
Source/JavaScriptCore/inspector/scripts/codegen/generate_js_backend_commands.py
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/CallTracer.cpp [new file with mode: 0644]
Source/WebCore/bindings/js/CallTracer.h [new file with mode: 0644]
Source/WebCore/bindings/js/CallTracerTypes.h [new file with mode: 0644]
Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
Source/WebCore/bindings/scripts/IDLAttributes.json
Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.cpp [new file with mode: 0644]
Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.h [new file with mode: 0644]
Source/WebCore/bindings/scripts/test/TestCallTracer.idl [new file with mode: 0644]
Source/WebCore/html/canvas/CanvasRenderingContext.h
Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp
Source/WebCore/html/canvas/CanvasRenderingContext2D.h
Source/WebCore/inspector/InspectorCanvas.cpp
Source/WebCore/inspector/InspectorCanvas.h
Source/WebCore/inspector/InspectorCanvasAgent.cpp
Source/WebCore/inspector/InspectorCanvasAgent.h
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/platform/graphics/Gradient.h
Source/WebCore/svg/SVGPathUtilities.cpp
Source/WebCore/svg/SVGPathUtilities.h
Source/WebInspectorUI/.eslintrc
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Controllers/CanvasManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/Recording.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/RecordingAction.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/RecordingFrame.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Protocol/CanvasObserver.js
Source/WebInspectorUI/UserInterface/Test.html

index 5ced66f..9b97ecc 100644 (file)
@@ -1,3 +1,13 @@
+2017-07-26  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: create protocol for recording Canvas contexts
+        https://bugs.webkit.org/show_bug.cgi?id=174481
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/model/recording-expected.txt: Added.
+        * inspector/model/recording.html: Added.
+
 2017-07-26  Ali Juma  <ajuma@chromium.org>
 
         Implement document.elementsFromPoint
diff --git a/LayoutTests/inspector/model/recording-expected.txt b/LayoutTests/inspector/model/recording-expected.txt
new file mode 100644 (file)
index 0000000..5791504
--- /dev/null
@@ -0,0 +1,143 @@
+Testing the fault-tolerance of WebInspector.Recording.
+
+
+== Running test suite: Recording
+-- Running test case: Recording.fromPayload.nullObject
+null
+
+-- Running test case: Recording.fromPayload.nonObject
+null
+
+-- Running test case: Recording.fromPayload.emptyObject
+null
+
+-- Running test case: Recording.fromPayload.invalidTopLevelMembers
+null
+
+-- Running test case: Recording.fromPayload.invalidSubMembers
+{
+  "version": 1,
+  "type": "test",
+  "initialState": {},
+  "frames": [
+    {
+      "actions": []
+    }
+  ],
+  "data": [
+    "test"
+  ]
+}
+
+-- Running test case: Recording.fromPayload.invalidFrame
+{
+  "version": 1,
+  "type": "test",
+  "initialState": {
+    "attributes": {
+      "test": "test"
+    },
+    "parameters": [
+      "test"
+    ],
+    "content": "test"
+  },
+  "frames": [
+    {
+      "actions": []
+    }
+  ],
+  "data": [
+    "test"
+  ]
+}
+
+-- Running test case: Recording.fromPayload.invalidAction
+{
+  "version": 1,
+  "type": "test",
+  "initialState": {
+    "attributes": {
+      "test": "test"
+    },
+    "parameters": [
+      "test"
+    ],
+    "content": "test"
+  },
+  "frames": [
+    {
+      "actions": [
+        [
+          -1,
+          []
+        ]
+      ],
+      "incomplete": true
+    }
+  ],
+  "data": [
+    "test"
+  ]
+}
+
+-- Running test case: Recording.fromPayload.invalidActionMembers
+{
+  "version": 1,
+  "type": "test",
+  "initialState": {
+    "attributes": {
+      "test": "test"
+    },
+    "parameters": [
+      "test"
+    ],
+    "content": "test"
+  },
+  "frames": [
+    {
+      "actions": [
+        [
+          null,
+          []
+        ]
+      ],
+      "incomplete": true
+    }
+  ],
+  "data": [
+    "test"
+  ]
+}
+
+-- Running test case: Recording.fromPayload.valid
+{
+  "version": 1,
+  "type": "test",
+  "initialState": {
+    "attributes": {
+      "test": "test"
+    },
+    "parameters": [
+      "test"
+    ],
+    "content": "test"
+  },
+  "frames": [
+    {
+      "actions": [
+        [
+          0,
+          [
+            0
+          ]
+        ]
+      ],
+      "incomplete": true
+    }
+  ],
+  "data": [
+    "test"
+  ]
+}
+
diff --git a/LayoutTests/inspector/model/recording.html b/LayoutTests/inspector/model/recording.html
new file mode 100644 (file)
index 0000000..39abef1
--- /dev/null
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createSyncSuite("Recording");
+
+    function addTest({name, payload}) {
+        suite.addTestCase({
+            name,
+            test() {
+                let recording = WebInspector.Recording.fromPayload(payload);
+                InspectorTest.log(recording ? JSON.stringify(recording.toJSON(), null, 2) : recording);
+                return true;
+            }
+        });
+    }
+
+    let tests = [
+        {
+            name: "Recording.fromPayload.nullObject",
+            payload: null,
+        },
+        {
+            name: "Recording.fromPayload.nonObject",
+            payload: "INVALID",
+        },
+        {
+            name: "Recording.fromPayload.emptyObject",
+            payload: {},
+        },
+        {
+            name: "Recording.fromPayload.invalidTopLevelMembers",
+            payload: {
+                version: null,
+                type: null,
+                initialState: null,
+                frames: null,
+                data: null,
+            },
+        },
+        {
+            name: "Recording.fromPayload.invalidSubMembers",
+            payload: {
+                version: 1,
+                type: "test",
+                initialState: {
+                    attributes: null,
+                    parameters: null,
+                    content: null,
+                },
+                frames: [null],
+                data: ["test"],
+            },
+        },
+        {
+            name: "Recording.fromPayload.invalidFrame",
+            payload: {
+                version: 1,
+                type: "test",
+                initialState: {
+                    attributes: {
+                        test: "test",
+                    },
+                    parameters: ["test"],
+                    content: "test",
+                },
+                frames: [
+                    {
+                        actions: null,
+                        incomplete: null,
+                    },
+                ],
+                data: ["test"],
+            },
+        },
+        {
+            name: "Recording.fromPayload.invalidAction",
+            payload: {
+                version: 1,
+                type: "test",
+                initialState: {
+                    attributes: {
+                        test: "test",
+                    },
+                    parameters: ["test"],
+                    content: "test",
+                },
+                frames: [
+                    {
+                        actions: [null],
+                        incomplete: true,
+                    },
+                ],
+                data: ["test"],
+            },
+        },
+        {
+            name: "Recording.fromPayload.invalidActionMembers",
+            payload: {
+                version: 1,
+                type: "test",
+                initialState: {
+                    attributes: {
+                        test: "test",
+                    },
+                    parameters: ["test"],
+                    content: "test",
+                },
+                frames: [
+                    {
+                        actions: [
+                            [
+                                null,
+                                null,
+                            ],
+                        ],
+                        incomplete: true,
+                    },
+                ],
+                data: ["test"],
+            },
+        },
+        {
+            name: "Recording.fromPayload.valid",
+            payload: {
+                version: 1,
+                type: "test",
+                initialState: {
+                    attributes: {
+                        test: "test",
+                    },
+                    parameters: ["test"],
+                    content: "test",
+                },
+                frames: [
+                    {
+                        actions: [
+                            [
+                                0,
+                                [0],
+                            ],
+                        ],
+                        incomplete: true,
+                    },
+                ],
+                data: ["test"],
+            },
+        },
+    ];
+    tests.forEach(addTest);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing the fault-tolerance of WebInspector.Recording.</p>
+</body>
+</html>
index c5c7a2f..f009e00 100644 (file)
@@ -1363,6 +1363,7 @@ set(JavaScriptCore_INSPECTOR_DOMAINS
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Network.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/OverlayTypes.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Page.json
+    ${JAVASCRIPTCORE_DIR}/inspector/protocol/Recording.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Runtime.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/ScriptProfiler.json
     ${JAVASCRIPTCORE_DIR}/inspector/protocol/Timeline.json
index f066f27..2709753 100644 (file)
@@ -1,3 +1,31 @@
+2017-07-26  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: create protocol for recording Canvas contexts
+        https://bugs.webkit.org/show_bug.cgi?id=174481
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/protocol/Canvas.json:
+         - Add `requestRecording` command to mark the provided canvas as having requested a recording.
+         - Add `cancelRecording` command to clear a previously marked canvas and flush any recorded data.
+         - Add `recordingFinished` event that is fired once a recording is finished.
+
+        * CMakeLists.txt:
+        * DerivedSources.make:
+        * inspector/protocol/Recording.json: Added.
+         - Add `Type` enum that lists the types of recordings
+         - Add `InitialState` type that contains information about the canvas context at the
+           beginning of the recording.
+         - Add `Frame` type that holds a list of actions that were recorded.
+         - Add `Recording` type as the container object of recording data.
+
+        * inspector/scripts/codegen/generate_js_backend_commands.py:
+        (JSBackendCommandsGenerator.generate_domain):
+        Create an agent for domains with no events or commands.
+
+        * inspector/InspectorValues.h:
+        Make Array `get` public so that values can be retrieved if needed.
+
 2017-07-26  Brian Burg  <bburg@apple.com>
 
         Remove WEB_TIMING feature flag
index d69baf5..55c508c 100644 (file)
@@ -227,6 +227,7 @@ INSPECTOR_DOMAINS = \
     $(JavaScriptCore)/inspector/protocol/Network.json \
     $(JavaScriptCore)/inspector/protocol/OverlayTypes.json \
     $(JavaScriptCore)/inspector/protocol/Page.json \
+    $(JavaScriptCore)/inspector/protocol/Recording.json \
     $(JavaScriptCore)/inspector/protocol/Runtime.json \
     $(JavaScriptCore)/inspector/protocol/ScriptProfiler.json \
     $(JavaScriptCore)/inspector/protocol/Timeline.json \
index a1fc743..0b747bf 100644 (file)
@@ -274,6 +274,8 @@ public:
 
     unsigned length() const { return static_cast<unsigned>(m_map.size()); }
 
+    RefPtr<InspectorValue> get(size_t index) const;
+
     size_t memoryCost() const final;
 
 protected:
@@ -289,8 +291,6 @@ protected:
     void pushObject(RefPtr<InspectorObjectBase>&&);
     void pushArray(RefPtr<InspectorArrayBase>&&);
 
-    RefPtr<InspectorValue> get(size_t index) const;
-
     void writeJSON(StringBuilder& output) const override;
 
     iterator begin() { return m_map.begin(); }
index d70d157..85e19f4 100644 (file)
             "returns": [
                 { "name": "object", "$ref": "Runtime.RemoteObject", "description": "JavaScript object wrapper for given canvas context." }
             ]
+        },
+        {
+            "name": "requestRecording",
+            "description": "Requests that the next frame or up to the given number of bytes of data be recorded for the given canvas.",
+            "parameters": [
+                { "name": "canvasId", "$ref": "CanvasId" },
+                { "name": "singleFrame", "type": "boolean", "optional": true, "description": "Whether to record a single frame or until the memory limit is reached." },
+                { "name": "memoryLimit", "type": "integer", "optional": true, "description": "Memory limit of recorded data." }
+            ]
+        },
+        {
+            "name": "cancelRecording",
+            "description": "Cancels a requested recording for the given canvas.",
+            "parameters": [
+                { "name": "canvasId", "$ref": "CanvasId" }
+            ]
         }
     ],
     "events": [
             "parameters": [
                 { "name": "canvasId", "$ref": "CanvasId", "description": "Identifier of canvas that changed." }
             ]
+        },
+        {
+            "name": "recordingFinished",
+            "parameters": [
+                { "name": "canvasId", "$ref": "CanvasId" },
+                { "name": "recording", "$ref": "Recording.Recording" }
+            ]
         }
     ]
 }
diff --git a/Source/JavaScriptCore/inspector/protocol/Recording.json b/Source/JavaScriptCore/inspector/protocol/Recording.json
new file mode 100644 (file)
index 0000000..fc78743
--- /dev/null
@@ -0,0 +1,42 @@
+{
+    "domain": "Recording",
+    "description": "General types used for recordings of actions performed in the inspected page.",
+    "types": [
+        {
+            "id": "Type",
+            "type": "string",
+            "enum": ["canvas-2d"],
+            "description": "The type of the recording."
+        },
+        {
+            "id": "InitialState",
+            "type": "object",
+            "description": "Information about the initial state of the recorded object.",
+            "properties": [
+                { "name": "attributes", "type": "object", "optional": true, "description": "Key-value map for each attribute of the state." },
+                { "name": "parameters", "type": "array", "items": { "type": "any" }, "optional": true, "description": "Array of values that were used to construct the recorded object." },
+                { "name": "content", "type": "string", "optional": true, "description": "Current content at the start of the recording." }
+            ]
+        },
+        {
+            "id": "Frame",
+            "type": "object",
+            "description": "Container object for a single frame of the recording.",
+            "properties": [
+                { "name": "actions", "type": "array", "items": { "type": "any" }, "description": "Information about an action made to the recorded object. Follows the structure [name, parameters, trace], where name is a string, parameters is an array, and trace is an array."},
+                { "name": "incomplete", "type": "boolean", "optional": true, "description": "Flag indicating if the recording was stopped before this frame ended." }
+            ]
+        },
+        {
+            "id": "Recording",
+            "type": "object",
+            "properties": [
+                { "name": "version", "type": "integer", "description": "Used for future/backwards compatibility." },
+                { "name": "type", "$ref": "Type" },
+                { "name": "initialState", "$ref": "InitialState", "description": "JSON data of inital state of object before recording." },
+                { "name": "frames", "type": "array", "items": { "$ref": "Frame" }, "description": "JSON data of all object API calls." },
+                { "name": "data", "type": "array", "items": { "type": "any" }, "description": "Array of objects that can be referenced by index. Used to avoid duplicating objects." }
+            ]
+        }
+    ]
+}
index 555c12f..785d017 100755 (executable)
@@ -129,15 +129,14 @@ class JSBackendCommandsGenerator(Generator):
             }
             lines.append('InspectorBackend.registerCommand("%(domain)s.%(commandName)s", [%(callParams)s], [%(returnParams)s]);' % command_args)
 
-        if commands or events:
-            activate_args = {
-                'domain': domain.domain_name,
-                'availability': domain.availability,
-            }
-            if domain.availability:
-                lines.append('InspectorBackend.activateDomain("%(domain)s", "%(availability)s");' % activate_args)
-            else:
-                lines.append('InspectorBackend.activateDomain("%(domain)s");' % activate_args)
+        activate_args = {
+            'domain': domain.domain_name,
+            'availability': domain.availability,
+        }
+        if domain.availability:
+            lines.append('InspectorBackend.activateDomain("%(domain)s", "%(availability)s");' % activate_args)
+        else:
+            lines.append('InspectorBackend.activateDomain("%(domain)s");' % activate_args)
 
         if domain.workerSupported:
             lines.append('InspectorBackend.workerSupportedDomain("%s");' % domain.domain_name)
index 6384509..5878570 100644 (file)
@@ -1,3 +1,106 @@
+2017-07-26  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: create protocol for recording Canvas contexts
+        https://bugs.webkit.org/show_bug.cgi?id=174481
+
+        Reviewed by Joseph Pecoraro.
+
+        Currently, a recording doesn't actually "start" until an action is performed on the context.
+        This change adds the recording logic, but it does not use it anywhere. Additonal tests will
+        be added in the patches that add uses:
+         - <https://webkit.org/b/174482> Web Inspector: Record actions performed on CanvasRenderingContext2D
+         - <https://webkit.org/b/174483> Web Inspector: Record actions performed on WebGLRenderingContext
+
+        Test: inspector/model/recording.html
+
+        * bindings/scripts/IDLAttributes.json:
+        * bindings/scripts/CodeGeneratorJS.pm:
+        (GenerateAttributeGetterBodyDefinition):
+        (GenerateAttributeSetterBodyDefinition):
+        (GenerateImplementationFunctionCall):
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/js/CallTracer.h: Added.
+        * bindings/js/CallTracer.cpp: Added.
+        (WebCore::CallTracer::recordCanvasAction):
+        * bindings/js/CallTracerTypes.h: Added.
+        * bindings/scripts/test/TestCallTracer.idl: Added.
+        * bindings/scripts/test/JS/JSTestCallTracer.h: Added.
+        * bindings/scripts/test/JS/JSTestCallTracer.cpp: Added.
+
+        Create new IDL extended attribute called "CallTracingCallback" that will add code to call a
+        static function on CallTracer with the given extended attribute value as the function name,
+        the `impl` object as the first parameter, the name of the attribute/operation as the second,
+        and an optional object that accepts an initializer list of all the parameters as the third.
+
+        This function will not be called, however, unless a `callTracingActive` function on the
+        `impl` object returns true, and this is marked as UNLIKELY.
+
+        "CallTracingCallback" can be added to an Interface, in which case it will apply to all
+        attributes/operations of the generated class, or an individual Attribute/Operation.
+
+        * html/canvas/CanvasRenderingContext.h:
+        (WebCore::CanvasRenderingContext::callTracingActive):
+        (WebCore::CanvasRenderingContext::setCallTracingActive):
+
+        * inspector/InspectorCanvas.h:
+        * inspector/InspectorCanvas.cpp:
+        (WebCore::InspectorCanvas::~InspectorCanvas):
+        (WebCore::InspectorCanvas::resetRecordingData):
+        (WebCore::InspectorCanvas::hasRecordingData):
+        (WebCore::InspectorCanvas::recordAction):
+        (WebCore::InspectorCanvas::releaseInitialState):
+        (WebCore::InspectorCanvas::releaseFrames):
+        (WebCore::InspectorCanvas::releaseData):
+        (WebCore::InspectorCanvas::markNewFrame):
+        (WebCore::InspectorCanvas::markCurrentFrameIncomplete):
+        (WebCore::InspectorCanvas::setBufferLimit):
+        (WebCore::InspectorCanvas::hasBufferSpace):
+        (WebCore::InspectorCanvas::singleFrame):
+        (WebCore::InspectorCanvas::setSingleFrame):
+        (WebCore::InspectorCanvas::indexForData):
+        (WebCore::buildArrayForAffineTransform):
+        (WebCore::buildArrayForVector):
+        (WebCore::InspectorCanvas::buildInitialState):
+        (WebCore::InspectorCanvas::buildAction):
+        (WebCore::InspectorCanvas::buildArrayForCanvasGradient):
+        (WebCore::InspectorCanvas::buildArrayForCanvasPattern):
+        (WebCore::InspectorCanvas::buildArrayForImageData):
+
+        Hold the recording data on the corresponding InspectorCanvas. Recording Frames are
+        completed when the HTMLCanvasElement paints or a  0_s timer is fired. A recording is not
+        considered valid until at least one action is performed on the canvas context. Once that
+        condition is satisfied, canceling the recording will flush the data.
+
+        * inspector/InspectorCanvasAgent.h:
+        * inspector/InspectorCanvasAgent.cpp:
+        (WebCore::InspectorCanvasAgent::InspectorCanvasAgent):
+        (WebCore::InspectorCanvasAgent::disable):
+        (WebCore::InspectorCanvasAgent::requestRecording):
+        (WebCore::InspectorCanvasAgent::cancelRecording):
+        (WebCore::InspectorCanvasAgent::recordCanvasAction):
+        (WebCore::InspectorCanvasAgent::didFinishRecordingCanvasFrame):
+        (WebCore::InspectorCanvasAgent::canvasDestroyed):
+        (WebCore::InspectorCanvasAgent::canvasRecordingTimerFired):
+        (WebCore::InspectorCanvasAgent::clearCanvasData):
+
+        * inspector/InspectorInstrumentation.h:
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::recordCanvasAction):
+        (WebCore::InspectorInstrumentation::recordCanvasActionImpl):
+        (WebCore::InspectorInstrumentation::didFinishRecordingCanvasFrameImpl):
+
+        * html/canvas/CanvasRenderingContext2D.h:
+        * html/canvas/CanvasRenderingContext2D.cpp:
+        (WebCore::CanvasRenderingContext2D::stringForWindingRule):
+        (WebCore::CanvasRenderingContext2D::stringForImageSmoothingQuality):
+
+        * platform/graphics/Gradient.h:
+        (WebCore::Gradient::stops):
+
+        * svg/SVGPathUtilities.h:
+        * svg/SVGPathUtilities.cpp:
+        (WebCore::buildStringFromPath):
+
 2017-07-26  Ali Juma  <ajuma@chromium.org>
 
         Implement document.elementsFromPoint
index 7424594..be4600d 100644 (file)
                94E839521DFB2A12007BC6A7 /* CSSNamespaceRule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94E8394E1DFB2700007BC6A7 /* CSSNamespaceRule.cpp */; };
                94E839551DFB2BC4007BC6A7 /* JSCSSNamespaceRule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94E839531DFB2BA6007BC6A7 /* JSCSSNamespaceRule.cpp */; };
                94E839561DFB2BC4007BC6A7 /* JSCSSNamespaceRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 94E839541DFB2BA6007BC6A7 /* JSCSSNamespaceRule.h */; };
+               952076041F2675FE007D2AAB /* CallTracer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 952076001F2675F9007D2AAB /* CallTracer.cpp */; };
+               952076051F2675FE007D2AAB /* CallTracer.h in Headers */ = {isa = PBXBuildFile; fileRef = 952076011F2675F9007D2AAB /* CallTracer.h */; };
+               952076061F2675FE007D2AAB /* CallTracerTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 952076021F2675F9007D2AAB /* CallTracerTypes.h */; };
                96ABA42314BCB80E00D56204 /* GraphicsContext3DOpenGLCommon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 96ABA42214BCB80E00D56204 /* GraphicsContext3DOpenGLCommon.cpp */; };
                9703E1BF15DC4E37001F24C8 /* JSVoidCallback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 97E9EC8B15DC492F004F2E71 /* JSVoidCallback.cpp */; };
                97059977107D975200A50A7C /* PolicyCallback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 97059973107D975200A50A7C /* PolicyCallback.cpp */; };
                94E839531DFB2BA6007BC6A7 /* JSCSSNamespaceRule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSCSSNamespaceRule.cpp; sourceTree = "<group>"; };
                94E839541DFB2BA6007BC6A7 /* JSCSSNamespaceRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSCSSNamespaceRule.h; sourceTree = "<group>"; };
                950C4C02BED8936F818E2F99 /* JSSVGGraphicsElement.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = JSSVGGraphicsElement.h; sourceTree = "<group>"; };
+               952076001F2675F9007D2AAB /* CallTracer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CallTracer.cpp; sourceTree = "<group>"; };
+               952076011F2675F9007D2AAB /* CallTracer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CallTracer.h; sourceTree = "<group>"; };
+               952076021F2675F9007D2AAB /* CallTracerTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CallTracerTypes.h; sourceTree = "<group>"; };
                96ABA42214BCB80E00D56204 /* GraphicsContext3DOpenGLCommon.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GraphicsContext3DOpenGLCommon.cpp; sourceTree = "<group>"; };
                97059973107D975200A50A7C /* PolicyCallback.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PolicyCallback.cpp; sourceTree = "<group>"; };
                97059974107D975200A50A7C /* PolicyCallback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PolicyCallback.h; sourceTree = "<group>"; };
                                E30592651E27A3C600D57C98 /* CachedScriptFetcher.cpp */,
                                E30592661E27A3C600D57C98 /* CachedScriptFetcher.h */,
                                BCD533630ED6848900887468 /* CachedScriptSourceProvider.h */,
+                               952076001F2675F9007D2AAB /* CallTracer.cpp */,
+                               952076011F2675F9007D2AAB /* CallTracer.h */,
+                               952076021F2675F9007D2AAB /* CallTracerTypes.h */,
                                0F60F3291DFBB10400416D6C /* CommonVM.cpp */,
                                0F60F32A1DFBB10400416D6C /* CommonVM.h */,
                                BC53DA471143134D000D817E /* DOMWrapperWorld.cpp */,
                                E43AF8E71AC5B7EC00CA717E /* CacheValidation.h in Headers */,
                                49AE2D97134EE5F90072920A /* CalculationValue.h in Headers */,
                                7C1E8D011ED0C2DA00B1D983 /* CallbackResult.h in Headers */,
+                               952076051F2675FE007D2AAB /* CallTracer.h in Headers */,
+                               952076061F2675FE007D2AAB /* CallTracerTypes.h in Headers */,
                                415CDAF51E6B8F8B004F11EE /* CanvasCaptureMediaStreamTrack.h in Headers */,
                                49484FC2102CF23C00187DD3 /* CanvasGradient.h in Headers */,
                                4671E0661D67A59600C6B497 /* CanvasPath.h in Headers */,
                                BCB16C270979C3BD00467741 /* CachedXSLStyleSheet.cpp in Sources */,
                                E43AF8E61AC5B7E800CA717E /* CacheValidation.cpp in Sources */,
                                49AE2D96134EE5F90072920A /* CalculationValue.cpp in Sources */,
+                               952076041F2675FE007D2AAB /* CallTracer.cpp in Sources */,
                                415CDAF41E6B8F87004F11EE /* CanvasCaptureMediaStreamTrack.cpp in Sources */,
                                49484FC1102CF23C00187DD3 /* CanvasGradient.cpp in Sources */,
                                4671E0651D67A59600C6B497 /* CanvasPath.cpp in Sources */,
diff --git a/Source/WebCore/bindings/js/CallTracer.cpp b/Source/WebCore/bindings/js/CallTracer.cpp
new file mode 100644 (file)
index 0000000..9d51a66
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 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. 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 "CallTracer.h"
+
+#include "CanvasGradient.h"
+#include "CanvasPattern.h"
+#include "CanvasRenderingContext.h"
+#include "DOMPath.h"
+#include "Element.h"
+#include "HTMLCanvasElement.h"
+#include "HTMLImageElement.h"
+#include "HTMLVideoElement.h"
+#include "ImageData.h"
+#include "InspectorInstrumentation.h"
+
+namespace WebCore {
+
+void CallTracer::recordCanvasAction(CanvasRenderingContext& canvasRenderingContext, const String& name, Vector<CanvasActionParameterVariant>&& parameters)
+{
+    InspectorInstrumentation::recordCanvasAction(canvasRenderingContext, name, WTFMove(parameters));
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/bindings/js/CallTracer.h b/Source/WebCore/bindings/js/CallTracer.h
new file mode 100644 (file)
index 0000000..5edc5f9
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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. 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.
+ */
+
+#pragma once
+
+#include "CallTracerTypes.h"
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class CanvasRenderingContext;
+
+class CallTracer {
+public:
+    static void recordCanvasAction(CanvasRenderingContext&, const String&, Vector<CanvasActionParameterVariant>&& = { });
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/bindings/js/CallTracerTypes.h b/Source/WebCore/bindings/js/CallTracerTypes.h
new file mode 100644 (file)
index 0000000..bd820e3
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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. 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.
+ */
+
+#pragma once
+
+#include "CanvasRenderingContext2D.h"
+#include "DOMMatrixInit.h"
+#include <wtf/Variant.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class DOMPath;
+class Element;
+class HTMLImageElement;
+class ImageData;
+
+typedef Variant<
+    Element*,
+    HTMLImageElement*,
+    ImageData*,
+    DOMMatrixInit,
+    DOMPath*,
+    Vector<float>,
+    String,
+    double,
+    float,
+    int,
+    bool,
+    std::optional<float>,
+    CanvasImageSource,
+    CanvasRenderingContext2D::Style,
+    CanvasRenderingContext2D::WindingRule,
+    CanvasRenderingContext2D::ImageSmoothingQuality
+> CanvasActionParameterVariant;
+
+} // namespace WebCore
index 567a0b1..e95e084 100644 (file)
@@ -4676,6 +4676,15 @@ sub GenerateAttributeGetterBodyDefinition
         
         my $toJSExpression = NativeToJSValueUsingReferences($attribute, $interface, "${functionName}(" . join(", ", @arguments) . ")", "*thisObject.globalObject()");
         push(@$outputArray, "    auto& impl = thisObject.wrapped();\n") unless $attribute->isStatic or $attribute->isMapLike;
+
+        my $callTracingCallback = $attribute->extendedAttributes->{CallTracingCallback} || $interface->extendedAttributes->{CallTracingCallback};
+        if ($callTracingCallback) {
+            AddToImplIncludes("CallTracer.h");
+
+            push(@$outputArray, "    if (UNLIKELY(impl.callTracingActive()))\n");
+            push(@$outputArray, "        CallTracer::$callTracingCallback(impl, ASCIILiteral(\"" . $attribute->name . "\"));\n");
+        }
+
         push(@$outputArray, "    JSValue result = ${toJSExpression};\n");
         push(@$outputArray, "    thisObject.m_" . $attribute->name . ".set(state.vm(), &thisObject, result);\n") if $attribute->extendedAttributes->{CachedAttribute};
         push(@$outputArray, "    return result;\n");
@@ -4880,6 +4889,14 @@ sub GenerateAttributeSetterBodyDefinition
         my $functionString = "${functionName}(" . join(", ", @arguments) . ")";
         $functionString = "propagateException(state, throwScope, $functionString)" if $attribute->extendedAttributes->{SetterMayThrowException};
 
+        my $callTracingCallback = $attribute->extendedAttributes->{CallTracingCallback} || $interface->extendedAttributes->{CallTracingCallback};
+        if ($callTracingCallback) {
+            AddToImplIncludes("CallTracer.h");
+
+            push(@$outputArray, "    if (UNLIKELY(impl.callTracingActive()))\n");
+            push(@$outputArray, "        CallTracer::$callTracingCallback(impl, ASCIILiteral(\"" . $attribute->name . "\"), { nativeValue });\n");
+        }
+
         push(@$outputArray, "    ${functionString};\n");
         push(@$outputArray, "    return true;\n");
     }
@@ -6015,6 +6032,19 @@ sub GenerateImplementationFunctionCall
 {
     my ($outputArray, $operation, $interface, $functionString, $indent) = @_;
 
+    my $callTracingCallback = $operation->extendedAttributes->{CallTracingCallback} || $interface->extendedAttributes->{CallTracingCallback};
+    if ($callTracingCallback) {
+        AddToImplIncludes("CallTracer.h");
+
+        my @inspectorRecordingArguments = ();
+        foreach my $argument (@{$operation->arguments}) {
+            push(@inspectorRecordingArguments, $argument->name);
+        }
+
+        push(@$outputArray, $indent . "if (UNLIKELY(impl.callTracingActive()))\n");
+        push(@$outputArray, $indent . "    CallTracer::$callTracingCallback(impl, ASCIILiteral(\"" . $operation->name . "\"), { " . join(", ", @inspectorRecordingArguments) . " });\n");
+    }
+
     if (OperationHasForcedReturnValue($operation)) {
         push(@$outputArray, $indent . "$functionString;\n");
         push(@$outputArray, $indent . "return JSValue::encode(returnValue);\n");
index 07a3c04..3d47758 100644 (file)
         "CachedAttribute": {
             "contextsAllowed": ["attribute"]
         },
+        "CallTracingCallback": {
+            "contextsAllowed": ["interface", "attribute", "operation"],
+            "values": ["*"]
+        },
         "CallbackNeedsOperatorEqual": {
             "contextsAllowed": ["callback-function"]
         },
diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.cpp
new file mode 100644 (file)
index 0000000..e30c7ab
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+    This file is part of the WebKit open source project.
+    This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#include "JSTestCallTracer.h"
+
+#include "CallTracer.h"
+#include "JSDOMAttribute.h"
+#include "JSDOMBinding.h"
+#include "JSDOMConstructorNotConstructable.h"
+#include "JSDOMConvertBoolean.h"
+#include "JSDOMConvertInterface.h"
+#include "JSDOMConvertNullable.h"
+#include "JSDOMConvertNumbers.h"
+#include "JSDOMConvertStrings.h"
+#include "JSDOMExceptionHandling.h"
+#include "JSDOMOperation.h"
+#include "JSDOMWrapperCache.h"
+#include "JSNode.h"
+#include <runtime/FunctionPrototype.h>
+#include <runtime/JSCInlines.h>
+#include <wtf/GetPtr.h>
+
+using namespace JSC;
+
+namespace WebCore {
+
+// Functions
+
+JSC::EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationInterface(JSC::ExecState*);
+JSC::EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationSpecified(JSC::ExecState*);
+JSC::EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationWithArguments(JSC::ExecState*);
+JSC::EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationWithNullableArgument(JSC::ExecState*);
+
+// Attributes
+
+JSC::EncodedJSValue jsTestCallTracerConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+bool setJSTestCallTracerConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
+JSC::EncodedJSValue jsTestCallTracerTestAttributeInterface(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+bool setJSTestCallTracerTestAttributeInterface(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
+JSC::EncodedJSValue jsTestCallTracerTestAttributeSpecified(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+bool setJSTestCallTracerTestAttributeSpecified(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
+
+class JSTestCallTracerPrototype : public JSC::JSNonFinalObject {
+public:
+    using Base = JSC::JSNonFinalObject;
+    static JSTestCallTracerPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
+    {
+        JSTestCallTracerPrototype* ptr = new (NotNull, JSC::allocateCell<JSTestCallTracerPrototype>(vm.heap)) JSTestCallTracerPrototype(vm, globalObject, structure);
+        ptr->finishCreation(vm);
+        return ptr;
+    }
+
+    DECLARE_INFO;
+    static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+    {
+        return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+    }
+
+private:
+    JSTestCallTracerPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
+        : JSC::JSNonFinalObject(vm, structure)
+    {
+    }
+
+    void finishCreation(JSC::VM&);
+};
+
+using JSTestCallTracerConstructor = JSDOMConstructorNotConstructable<JSTestCallTracer>;
+
+template<> JSValue JSTestCallTracerConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
+{
+    UNUSED_PARAM(vm);
+    return globalObject.functionPrototype();
+}
+
+template<> void JSTestCallTracerConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
+{
+    putDirect(vm, vm.propertyNames->prototype, JSTestCallTracer::prototype(vm, globalObject), DontDelete | ReadOnly | DontEnum);
+    putDirect(vm, vm.propertyNames->name, jsNontrivialString(&vm, String(ASCIILiteral("TestCallTracer"))), ReadOnly | DontEnum);
+    putDirect(vm, vm.propertyNames->length, jsNumber(0), ReadOnly | DontEnum);
+}
+
+template<> const ClassInfo JSTestCallTracerConstructor::s_info = { "TestCallTracer", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTestCallTracerConstructor) };
+
+/* Hash table for prototype */
+
+static const HashTableValue JSTestCallTracerPrototypeTableValues[] =
+{
+    { "constructor", DontEnum, NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestCallTracerConstructor), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSTestCallTracerConstructor) } },
+    { "testAttributeInterface", CustomAccessor, NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestCallTracerTestAttributeInterface), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSTestCallTracerTestAttributeInterface) } },
+    { "testAttributeSpecified", CustomAccessor, NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestCallTracerTestAttributeSpecified), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSTestCallTracerTestAttributeSpecified) } },
+    { "testOperationInterface", JSC::Function, NoIntrinsic, { (intptr_t)static_cast<NativeFunction>(jsTestCallTracerPrototypeFunctionTestOperationInterface), (intptr_t) (0) } },
+    { "testOperationSpecified", JSC::Function, NoIntrinsic, { (intptr_t)static_cast<NativeFunction>(jsTestCallTracerPrototypeFunctionTestOperationSpecified), (intptr_t) (0) } },
+    { "testOperationWithArguments", JSC::Function, NoIntrinsic, { (intptr_t)static_cast<NativeFunction>(jsTestCallTracerPrototypeFunctionTestOperationWithArguments), (intptr_t) (3) } },
+    { "testOperationWithNullableArgument", JSC::Function, NoIntrinsic, { (intptr_t)static_cast<NativeFunction>(jsTestCallTracerPrototypeFunctionTestOperationWithNullableArgument), (intptr_t) (1) } },
+};
+
+const ClassInfo JSTestCallTracerPrototype::s_info = { "TestCallTracerPrototype", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTestCallTracerPrototype) };
+
+void JSTestCallTracerPrototype::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    reifyStaticProperties(vm, JSTestCallTracerPrototypeTableValues, *this);
+}
+
+const ClassInfo JSTestCallTracer::s_info = { "TestCallTracer", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTestCallTracer) };
+
+JSTestCallTracer::JSTestCallTracer(Structure* structure, JSDOMGlobalObject& globalObject, Ref<TestCallTracer>&& impl)
+    : JSDOMWrapper<TestCallTracer>(structure, globalObject, WTFMove(impl))
+{
+}
+
+void JSTestCallTracer::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    ASSERT(inherits(vm, info()));
+
+}
+
+JSObject* JSTestCallTracer::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+    return JSTestCallTracerPrototype::create(vm, &globalObject, JSTestCallTracerPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype()));
+}
+
+JSObject* JSTestCallTracer::prototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+    return getDOMPrototype<JSTestCallTracer>(vm, globalObject);
+}
+
+JSValue JSTestCallTracer::getConstructor(VM& vm, const JSGlobalObject* globalObject)
+{
+    return getDOMConstructor<JSTestCallTracerConstructor>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
+}
+
+void JSTestCallTracer::destroy(JSC::JSCell* cell)
+{
+    JSTestCallTracer* thisObject = static_cast<JSTestCallTracer*>(cell);
+    thisObject->JSTestCallTracer::~JSTestCallTracer();
+}
+
+template<> inline JSTestCallTracer* IDLAttribute<JSTestCallTracer>::cast(ExecState& state, EncodedJSValue thisValue)
+{
+    return jsDynamicDowncast<JSTestCallTracer*>(state.vm(), JSValue::decode(thisValue));
+}
+
+template<> inline JSTestCallTracer* IDLOperation<JSTestCallTracer>::cast(ExecState& state)
+{
+    return jsDynamicDowncast<JSTestCallTracer*>(state.vm(), state.thisValue());
+}
+
+EncodedJSValue jsTestCallTracerConstructor(ExecState* state, EncodedJSValue thisValue, PropertyName)
+{
+    VM& vm = state->vm();
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+    auto* prototype = jsDynamicDowncast<JSTestCallTracerPrototype*>(vm, JSValue::decode(thisValue));
+    if (UNLIKELY(!prototype))
+        return throwVMTypeError(state, throwScope);
+    return JSValue::encode(JSTestCallTracer::getConstructor(state->vm(), prototype->globalObject()));
+}
+
+bool setJSTestCallTracerConstructor(ExecState* state, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+{
+    VM& vm = state->vm();
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+    auto* prototype = jsDynamicDowncast<JSTestCallTracerPrototype*>(vm, JSValue::decode(thisValue));
+    if (UNLIKELY(!prototype)) {
+        throwVMTypeError(state, throwScope);
+        return false;
+    }
+    // Shadowing a built-in constructor
+    return prototype->putDirect(state->vm(), state->propertyNames().constructor, JSValue::decode(encodedValue));
+}
+
+static inline JSValue jsTestCallTracerTestAttributeInterfaceGetter(ExecState& state, JSTestCallTracer& thisObject, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(throwScope);
+    UNUSED_PARAM(state);
+    auto& impl = thisObject.wrapped();
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerInterface(impl, ASCIILiteral("testAttributeInterface"));
+    JSValue result = toJS<IDLBoolean>(impl.testAttributeInterface());
+    return result;
+}
+
+EncodedJSValue jsTestCallTracerTestAttributeInterface(ExecState* state, EncodedJSValue thisValue, PropertyName)
+{
+    return IDLAttribute<JSTestCallTracer>::get<jsTestCallTracerTestAttributeInterfaceGetter>(*state, thisValue, "testAttributeInterface");
+}
+
+static inline bool setJSTestCallTracerTestAttributeInterfaceSetter(ExecState& state, JSTestCallTracer& thisObject, JSValue value, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = thisObject.wrapped();
+    auto nativeValue = convert<IDLBoolean>(state, value);
+    RETURN_IF_EXCEPTION(throwScope, false);
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerInterface(impl, ASCIILiteral("testAttributeInterface"), { nativeValue });
+    impl.setTestAttributeInterface(WTFMove(nativeValue));
+    return true;
+}
+
+bool setJSTestCallTracerTestAttributeInterface(ExecState* state, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+{
+    return IDLAttribute<JSTestCallTracer>::set<setJSTestCallTracerTestAttributeInterfaceSetter>(*state, thisValue, encodedValue, "testAttributeInterface");
+}
+
+static inline JSValue jsTestCallTracerTestAttributeSpecifiedGetter(ExecState& state, JSTestCallTracer& thisObject, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(throwScope);
+    UNUSED_PARAM(state);
+    auto& impl = thisObject.wrapped();
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerAttribute(impl, ASCIILiteral("testAttributeSpecified"));
+    JSValue result = toJS<IDLBoolean>(impl.testAttributeSpecified());
+    return result;
+}
+
+EncodedJSValue jsTestCallTracerTestAttributeSpecified(ExecState* state, EncodedJSValue thisValue, PropertyName)
+{
+    return IDLAttribute<JSTestCallTracer>::get<jsTestCallTracerTestAttributeSpecifiedGetter>(*state, thisValue, "testAttributeSpecified");
+}
+
+static inline bool setJSTestCallTracerTestAttributeSpecifiedSetter(ExecState& state, JSTestCallTracer& thisObject, JSValue value, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = thisObject.wrapped();
+    auto nativeValue = convert<IDLBoolean>(state, value);
+    RETURN_IF_EXCEPTION(throwScope, false);
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerAttribute(impl, ASCIILiteral("testAttributeSpecified"), { nativeValue });
+    impl.setTestAttributeSpecified(WTFMove(nativeValue));
+    return true;
+}
+
+bool setJSTestCallTracerTestAttributeSpecified(ExecState* state, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+{
+    return IDLAttribute<JSTestCallTracer>::set<setJSTestCallTracerTestAttributeSpecifiedSetter>(*state, thisValue, encodedValue, "testAttributeSpecified");
+}
+
+static inline JSC::EncodedJSValue jsTestCallTracerPrototypeFunctionTestOperationInterfaceBody(JSC::ExecState* state, typename IDLOperation<JSTestCallTracer>::ClassParameter castedThis, JSC::ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = castedThis->wrapped();
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerInterface(impl, ASCIILiteral("testOperationInterface"), {  });
+    impl.testOperationInterface();
+    return JSValue::encode(jsUndefined());
+}
+
+EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationInterface(ExecState* state)
+{
+    return IDLOperation<JSTestCallTracer>::call<jsTestCallTracerPrototypeFunctionTestOperationInterfaceBody>(*state, "testOperationInterface");
+}
+
+static inline JSC::EncodedJSValue jsTestCallTracerPrototypeFunctionTestOperationSpecifiedBody(JSC::ExecState* state, typename IDLOperation<JSTestCallTracer>::ClassParameter castedThis, JSC::ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = castedThis->wrapped();
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerOperation(impl, ASCIILiteral("testOperationSpecified"), {  });
+    impl.testOperationSpecified();
+    return JSValue::encode(jsUndefined());
+}
+
+EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationSpecified(ExecState* state)
+{
+    return IDLOperation<JSTestCallTracer>::call<jsTestCallTracerPrototypeFunctionTestOperationSpecifiedBody>(*state, "testOperationSpecified");
+}
+
+static inline JSC::EncodedJSValue jsTestCallTracerPrototypeFunctionTestOperationWithArgumentsBody(JSC::ExecState* state, typename IDLOperation<JSTestCallTracer>::ClassParameter castedThis, JSC::ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = castedThis->wrapped();
+    if (UNLIKELY(state->argumentCount() < 3))
+        return throwVMError(state, throwScope, createNotEnoughArgumentsError(state));
+    auto a = convert<IDLBoolean>(*state, state->uncheckedArgument(0));
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+    auto b = convert<IDLFloat>(*state, state->uncheckedArgument(1));
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+    auto c = convert<IDLDOMString>(*state, state->uncheckedArgument(2));
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerInterface(impl, ASCIILiteral("testOperationWithArguments"), { a, b, c });
+    impl.testOperationWithArguments(WTFMove(a), WTFMove(b), WTFMove(c));
+    return JSValue::encode(jsUndefined());
+}
+
+EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationWithArguments(ExecState* state)
+{
+    return IDLOperation<JSTestCallTracer>::call<jsTestCallTracerPrototypeFunctionTestOperationWithArgumentsBody>(*state, "testOperationWithArguments");
+}
+
+static inline JSC::EncodedJSValue jsTestCallTracerPrototypeFunctionTestOperationWithNullableArgumentBody(JSC::ExecState* state, typename IDLOperation<JSTestCallTracer>::ClassParameter castedThis, JSC::ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = castedThis->wrapped();
+    if (UNLIKELY(state->argumentCount() < 1))
+        return throwVMError(state, throwScope, createNotEnoughArgumentsError(state));
+    auto nodeNullableArg = convert<IDLNullable<IDLInterface<Node>>>(*state, state->uncheckedArgument(0), [](JSC::ExecState& state, JSC::ThrowScope& scope) { throwArgumentTypeError(state, scope, 0, "nodeNullableArg", "TestCallTracer", "testOperationWithNullableArgument", "Node"); });
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+    if (UNLIKELY(impl.callTracingActive()))
+        CallTracer::testCallTracerInterface(impl, ASCIILiteral("testOperationWithNullableArgument"), { nodeNullableArg });
+    impl.testOperationWithNullableArgument(WTFMove(nodeNullableArg));
+    return JSValue::encode(jsUndefined());
+}
+
+EncodedJSValue JSC_HOST_CALL jsTestCallTracerPrototypeFunctionTestOperationWithNullableArgument(ExecState* state)
+{
+    return IDLOperation<JSTestCallTracer>::call<jsTestCallTracerPrototypeFunctionTestOperationWithNullableArgumentBody>(*state, "testOperationWithNullableArgument");
+}
+
+bool JSTestCallTracerOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, SlotVisitor& visitor)
+{
+    UNUSED_PARAM(handle);
+    UNUSED_PARAM(visitor);
+    return false;
+}
+
+void JSTestCallTracerOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
+{
+    auto* jsTestCallTracer = static_cast<JSTestCallTracer*>(handle.slot()->asCell());
+    auto& world = *static_cast<DOMWrapperWorld*>(context);
+    uncacheWrapper(world, &jsTestCallTracer->wrapped(), jsTestCallTracer);
+}
+
+#if ENABLE(BINDING_INTEGRITY)
+#if PLATFORM(WIN)
+#pragma warning(disable: 4483)
+extern "C" { extern void (*const __identifier("??_7TestCallTracer@WebCore@@6B@")[])(); }
+#else
+extern "C" { extern void* _ZTVN7WebCore14TestCallTracerE[]; }
+#endif
+#endif
+
+JSC::JSValue toJSNewlyCreated(JSC::ExecState*, JSDOMGlobalObject* globalObject, Ref<TestCallTracer>&& impl)
+{
+
+#if ENABLE(BINDING_INTEGRITY)
+    void* actualVTablePointer = *(reinterpret_cast<void**>(impl.ptr()));
+#if PLATFORM(WIN)
+    void* expectedVTablePointer = reinterpret_cast<void*>(__identifier("??_7TestCallTracer@WebCore@@6B@"));
+#else
+    void* expectedVTablePointer = &_ZTVN7WebCore14TestCallTracerE[2];
+#endif
+
+    // If this fails TestCallTracer does not have a vtable, so you need to add the
+    // ImplementationLacksVTable attribute to the interface definition
+    static_assert(std::is_polymorphic<TestCallTracer>::value, "TestCallTracer is not polymorphic");
+
+    // If you hit this assertion you either have a use after free bug, or
+    // TestCallTracer has subclasses. If TestCallTracer has subclasses that get passed
+    // to toJS() we currently require TestCallTracer you to opt out of binding hardening
+    // by adding the SkipVTableValidation attribute to the interface IDL definition
+    RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer);
+#endif
+    return createWrapper<TestCallTracer>(globalObject, WTFMove(impl));
+}
+
+JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, TestCallTracer& impl)
+{
+    return wrap(state, globalObject, impl);
+}
+
+TestCallTracer* JSTestCallTracer::toWrapped(JSC::VM& vm, JSC::JSValue value)
+{
+    if (auto* wrapper = jsDynamicDowncast<JSTestCallTracer*>(vm, value))
+        return &wrapper->wrapped();
+    return nullptr;
+}
+
+}
diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.h b/Source/WebCore/bindings/scripts/test/JS/JSTestCallTracer.h
new file mode 100644 (file)
index 0000000..dd75a17
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+    This file is part of the WebKit open source project.
+    This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#pragma once
+
+#include "JSDOMWrapper.h"
+#include "TestCallTracer.h"
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+
+class JSTestCallTracer : public JSDOMWrapper<TestCallTracer> {
+public:
+    using Base = JSDOMWrapper<TestCallTracer>;
+    static JSTestCallTracer* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<TestCallTracer>&& impl)
+    {
+        JSTestCallTracer* ptr = new (NotNull, JSC::allocateCell<JSTestCallTracer>(globalObject->vm().heap)) JSTestCallTracer(structure, *globalObject, WTFMove(impl));
+        ptr->finishCreation(globalObject->vm());
+        return ptr;
+    }
+
+    static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
+    static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
+    static TestCallTracer* toWrapped(JSC::VM&, JSC::JSValue);
+    static void destroy(JSC::JSCell*);
+
+    DECLARE_INFO;
+
+    static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+    {
+        return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+    }
+
+    static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
+protected:
+    JSTestCallTracer(JSC::Structure*, JSDOMGlobalObject&, Ref<TestCallTracer>&&);
+
+    void finishCreation(JSC::VM&);
+};
+
+class JSTestCallTracerOwner : public JSC::WeakHandleOwner {
+public:
+    virtual bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::SlotVisitor&);
+    virtual void finalize(JSC::Handle<JSC::Unknown>, void* context);
+};
+
+inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, TestCallTracer*)
+{
+    static NeverDestroyed<JSTestCallTracerOwner> owner;
+    return &owner.get();
+}
+
+inline void* wrapperKey(TestCallTracer* wrappableObject)
+{
+    return wrappableObject;
+}
+
+JSC::JSValue toJS(JSC::ExecState*, JSDOMGlobalObject*, TestCallTracer&);
+inline JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, TestCallTracer* impl) { return impl ? toJS(state, globalObject, *impl) : JSC::jsNull(); }
+JSC::JSValue toJSNewlyCreated(JSC::ExecState*, JSDOMGlobalObject*, Ref<TestCallTracer>&&);
+inline JSC::JSValue toJSNewlyCreated(JSC::ExecState* state, JSDOMGlobalObject* globalObject, RefPtr<TestCallTracer>&& impl) { return impl ? toJSNewlyCreated(state, globalObject, impl.releaseNonNull()) : JSC::jsNull(); }
+
+template<> struct JSDOMWrapperConverterTraits<TestCallTracer> {
+    using WrapperClass = JSTestCallTracer;
+    using ToWrappedReturnType = TestCallTracer*;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/bindings/scripts/test/TestCallTracer.idl b/Source/WebCore/bindings/scripts/test/TestCallTracer.idl
new file mode 100644 (file)
index 0000000..a325535
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+[
+    CallTracingCallback=testCallTracerInterface,
+] interface TestCallTracer {
+    attribute boolean testAttributeInterface;
+    [CallTracingCallback=testCallTracerAttribute] attribute boolean testAttributeSpecified;
+
+    void testOperationInterface();
+    [CallTracingCallback=testCallTracerOperation] void testOperationSpecified();
+
+    void testOperationWithArguments(boolean a, float b, DOMString c);
+    void testOperationWithNullableArgument(Node? nodeNullableArg);
+};
+
index 3255ee4..1bdd4c1 100644 (file)
@@ -62,6 +62,9 @@ public:
     virtual void paintRenderingResultsToCanvas() {}
     virtual PlatformLayer* platformLayer() const { return 0; }
 
+    bool callTracingActive() const { return m_callTracingActive; }
+    void setCallTracingActive(bool callTracingActive) { m_callTracingActive = callTracingActive; }
+
 protected:
     CanvasRenderingContext(HTMLCanvasElement&);
     bool wouldTaintOrigin(const CanvasPattern*);
@@ -77,6 +80,8 @@ protected:
     }
     void checkOrigin(const URL&);
 
+    bool m_callTracingActive { false };
+
 private:
     HTMLCanvasElement& m_canvas;
 };
index fdb30ea..c4779dd 100644 (file)
@@ -965,6 +965,19 @@ static WindRule toWindRule(CanvasRenderingContext2D::WindingRule rule)
     return rule == CanvasRenderingContext2D::WindingRule::Nonzero ? RULE_NONZERO : RULE_EVENODD;
 }
 
+String CanvasRenderingContext2D::stringForWindingRule(WindingRule windingRule)
+{
+    switch (windingRule) {
+    case WindingRule::Nonzero:
+        return ASCIILiteral("nonzero");
+    case WindingRule::Evenodd:
+        return ASCIILiteral("evenodd");
+    }
+
+    ASSERT_NOT_REACHED();
+    return String();
+}
+
 void CanvasRenderingContext2D::fill(WindingRule windingRule)
 {
     fillInternal(m_path, windingRule);
@@ -2558,6 +2571,21 @@ static inline InterpolationQuality smoothingToInterpolationQuality(CanvasRenderi
     return InterpolationLow;
 };
 
+String CanvasRenderingContext2D::stringForImageSmoothingQuality(ImageSmoothingQuality imageSmoothingQuality)
+{
+    switch (imageSmoothingQuality) {
+    case ImageSmoothingQuality::Low:
+        return ASCIILiteral("low");
+    case ImageSmoothingQuality::Medium:
+        return ASCIILiteral("medium");
+    case ImageSmoothingQuality::High:
+        return ASCIILiteral("high");
+    }
+
+    ASSERT_NOT_REACHED();
+    return String();
+}
+
 auto CanvasRenderingContext2D::imageSmoothingQuality() const -> ImageSmoothingQuality
 {
     return state().imageSmoothingQuality;
index 112403e..6801225 100644 (file)
@@ -135,6 +135,7 @@ public:
     void beginPath();
 
     enum class WindingRule { Nonzero, Evenodd };
+    static String stringForWindingRule(WindingRule);
 
     void fill(WindingRule = WindingRule::Nonzero);
     void stroke();
@@ -220,6 +221,8 @@ public:
     void setImageSmoothingEnabled(bool);
 
     enum class ImageSmoothingQuality { Low, Medium, High };
+    static String stringForImageSmoothingQuality(ImageSmoothingQuality);
+
     ImageSmoothingQuality imageSmoothingQuality() const;
     void setImageSmoothingQuality(ImageSmoothingQuality);
 
@@ -232,7 +235,6 @@ public:
     String displayListAsText(DisplayList::AsTextFlags) const;
     String replayDisplayListAsText(DisplayList::AsTextFlags) const;
 
-private:
     enum class Direction {
         Inherit,
         RTL,
@@ -296,6 +298,9 @@ private:
         FontProxy font;
     };
 
+    const State& state() const { return m_stateStack.last(); }
+
+private:
     enum CanvasDidDrawOption {
         CanvasDidDrawApplyNone = 0,
         CanvasDidDrawApplyTransform = 1,
@@ -305,7 +310,6 @@ private:
     };
 
     State& modifiableState() { ASSERT(!m_unrealizedSaveCount || m_stateStack.size() >= MaxSaveCount); return m_stateStack.last(); }
-    const State& state() const { return m_stateStack.last(); }
 
     void applyLineDash() const;
     void setShadow(const FloatSize& offset, float blur, const Color&);
index 4bc13d1..03fecbb 100644 (file)
 #include "config.h"
 #include "InspectorCanvas.h"
 
+#include "AffineTransform.h"
+#include "CachedImage.h"
+#include "CanvasGradient.h"
+#include "CanvasPattern.h"
 #include "CanvasRenderingContext2D.h"
+#include "DOMPath.h"
 #include "Document.h"
+#include "FloatPoint.h"
 #include "Frame.h"
+#include "Gradient.h"
 #include "HTMLCanvasElement.h"
+#include "HTMLImageElement.h"
+#include "HTMLVideoElement.h"
+#include "Image.h"
+#include "ImageBuffer.h"
+#include "ImageData.h"
 #include "InspectorDOMAgent.h"
 #include "InspectorPageAgent.h"
 #include "InstrumentingAgents.h"
+#include "Pattern.h"
+#include "SVGPathUtilities.h"
+#include "StringAdaptors.h"
 #if ENABLE(WEBGL)
 #include "WebGLRenderingContext.h"
 #endif
@@ -60,6 +75,83 @@ InspectorCanvas::InspectorCanvas(HTMLCanvasElement& canvas, const String& cssCan
 {
 }
 
+InspectorCanvas::~InspectorCanvas()
+{
+    resetRecordingData();
+}
+
+void InspectorCanvas::resetRecordingData()
+{
+    m_initialState = nullptr;
+    m_frames = nullptr;
+    m_currentActions = nullptr;
+    m_serializedDuplicateData = nullptr;
+    m_indexedDuplicateData.clear();
+    m_bufferLimit = 100 * 1024 * 1024;
+    m_bufferUsed = 0;
+    m_singleFrame = true;
+
+    m_canvas.renderingContext()->setCallTracingActive(false);
+}
+
+bool InspectorCanvas::hasRecordingData() const
+{
+    return m_initialState && m_frames;
+}
+
+void InspectorCanvas::recordAction(const String& name, Vector<CanvasActionParameterVariant>&& parameters)
+{
+    if (!hasRecordingData()) {
+        m_initialState = buildInitialState();
+        m_bufferUsed += m_initialState->memoryCost();
+
+        m_frames = Inspector::Protocol::Array<Inspector::Protocol::Recording::Frame>::create();
+    }
+
+    if (!m_currentActions) {
+        m_currentActions = Inspector::Protocol::Array<InspectorValue>::create();
+
+        auto frame = Inspector::Protocol::Recording::Frame::create()
+            .setActions(m_currentActions)
+            .release();
+
+        m_frames->addItem(WTFMove(frame));
+    }
+
+    auto action = buildAction(name, WTFMove(parameters));
+    m_bufferUsed += action->memoryCost();
+    m_currentActions->addItem(WTFMove(action));
+}
+
+RefPtr<Inspector::Protocol::Array<InspectorValue>>&& InspectorCanvas::releaseData()
+{
+    m_indexedDuplicateData.clear();
+    return WTFMove(m_serializedDuplicateData);
+}
+
+void InspectorCanvas::markNewFrame()
+{
+    m_currentActions = nullptr;
+}
+
+void InspectorCanvas::markCurrentFrameIncomplete()
+{
+    if (!m_currentActions)
+        return;
+
+    static_cast<Inspector::Protocol::Recording::Frame*>(m_frames->get(m_frames->length() - 1).get())->setIncomplete(true);
+}
+
+void InspectorCanvas::setBufferLimit(long memoryLimit)
+{
+    m_bufferLimit = std::min<long>(memoryLimit, std::numeric_limits<int>::max());
+}
+
+bool InspectorCanvas::hasBufferSpace() const
+{
+    return m_bufferUsed < m_bufferLimit;
+}
+
 Ref<Inspector::Protocol::Canvas::Canvas> InspectorCanvas::buildObjectForCanvas(InstrumentingAgents& instrumentingAgents)
 {
     Document& document = m_canvas.document();
@@ -130,5 +222,308 @@ Ref<Inspector::Protocol::Canvas::Canvas> InspectorCanvas::buildObjectForCanvas(I
     return canvas;
 }
 
+int InspectorCanvas::indexForData(DuplicateDataVariant data)
+{
+    size_t index = m_indexedDuplicateData.find(data);
+    if (index != notFound) {
+        ASSERT(index < std::numeric_limits<int>::max());
+        return static_cast<int>(index);
+    }
+
+    if (!m_serializedDuplicateData)
+        m_serializedDuplicateData = Inspector::Protocol::Array<InspectorValue>::create();
+
+    RefPtr<InspectorValue> item;
+    WTF::switchOn(data,
+        [&] (const HTMLImageElement* imageElement) {
+            String dataURL = ASCIILiteral("data:,");
+
+            if (CachedImage* cachedImage = imageElement->cachedImage()) {
+                Image* image = cachedImage->image();
+                if (image && image != &Image::nullImage()) {
+                    std::unique_ptr<ImageBuffer> imageBuffer = ImageBuffer::create(image->size(), RenderingMode::Unaccelerated);
+                    imageBuffer->context().drawImage(*image, FloatPoint(0, 0));
+                    dataURL = imageBuffer->toDataURL("image/png");
+                }
+            }
+
+            item = InspectorValue::create(dataURL);
+        },
+#if ENABLE(VIDEO)
+        [&] (HTMLVideoElement* videoElement) {
+            String dataURL = ASCIILiteral("data:,");
+
+            unsigned videoWidth = videoElement->videoWidth();
+            unsigned videoHeight = videoElement->videoHeight();
+            std::unique_ptr<ImageBuffer> imageBuffer = ImageBuffer::create(FloatSize(videoWidth, videoHeight), RenderingMode::Unaccelerated);
+            if (imageBuffer) {
+                videoElement->paintCurrentFrameInContext(imageBuffer->context(), FloatRect(0, 0, videoWidth, videoHeight));
+                dataURL = imageBuffer->toDataURL("image/png");
+            }
+
+            item = InspectorValue::create(dataURL);
+        },
+#endif
+        [&] (HTMLCanvasElement* canvasElement) {
+            String dataURL = ASCIILiteral("data:,");
+
+            ExceptionOr<UncachedString> result = canvasElement->toDataURL(ASCIILiteral("image/png"));
+            if (!result.hasException())
+                dataURL = result.releaseReturnValue().string;
+
+            item = InspectorValue::create(dataURL);
+        },
+        [&] (const CanvasGradient* canvasGradient) { item = buildArrayForCanvasGradient(*canvasGradient); },
+        [&] (const CanvasPattern* canvasPattern) { item = buildArrayForCanvasPattern(*canvasPattern); },
+        [&] (const ImageData* imageData) { item = buildArrayForImageData(*imageData); },
+        [&] (const String& value) { item = InspectorValue::create(value); }
+    );
+
+    m_bufferUsed += item->memoryCost();
+    m_serializedDuplicateData->addItem(WTFMove(item));
+
+    m_indexedDuplicateData.append(data);
+    index = m_indexedDuplicateData.size() - 1;
+
+    ASSERT(index < std::numeric_limits<int>::max());
+    return static_cast<int>(index);
+}
+
+static RefPtr<Inspector::Protocol::Array<double>> buildArrayForAffineTransform(const AffineTransform& affineTransform)
+{
+    RefPtr<Inspector::Protocol::Array<double>> array = Inspector::Protocol::Array<double>::create();
+    array->addItem(affineTransform.a());
+    array->addItem(affineTransform.b());
+    array->addItem(affineTransform.c());
+    array->addItem(affineTransform.d());
+    array->addItem(affineTransform.e());
+    array->addItem(affineTransform.f());
+    return array;
+}
+
+static RefPtr<Inspector::Protocol::Array<double>> buildArrayForVector(const Vector<float>& vector)
+{
+    RefPtr<Inspector::Protocol::Array<double>> array = Inspector::Protocol::Array<double>::create();
+    for (double item : vector)
+        array->addItem(item);
+    return array;
+}
+
+RefPtr<Inspector::Protocol::Recording::InitialState> InspectorCanvas::buildInitialState()
+{
+    RefPtr<Inspector::Protocol::Recording::InitialState> initialState = Inspector::Protocol::Recording::InitialState::create()
+        .release();
+
+    auto attributes = InspectorObject::create();
+    attributes->setInteger(ASCIILiteral("width"), canvas().width());
+    attributes->setInteger(ASCIILiteral("height"), canvas().height());
+
+    auto parameters = Inspector::Protocol::Array<InspectorValue>::create();
+
+    const CanvasRenderingContext* canvasRenderingContext = canvas().renderingContext();
+    if (is<CanvasRenderingContext2D>(canvasRenderingContext)) {
+        const CanvasRenderingContext2D* context2d = downcast<CanvasRenderingContext2D>(canvasRenderingContext);
+        const CanvasRenderingContext2D::State& state = context2d->state();
+
+        attributes->setArray(ASCIILiteral("setTransform"), buildArrayForAffineTransform(state.transform));
+        attributes->setDouble(ASCIILiteral("globalAlpha"), context2d->globalAlpha());
+        attributes->setString(ASCIILiteral("globalCompositeOperation"), context2d->globalCompositeOperation());
+        attributes->setDouble(ASCIILiteral("lineWidth"), context2d->lineWidth());
+        attributes->setString(ASCIILiteral("lineCap"), context2d->lineCap());
+        attributes->setString(ASCIILiteral("lineJoin"), context2d->lineJoin());
+        attributes->setDouble(ASCIILiteral("miterLimit"), context2d->miterLimit());
+        attributes->setDouble(ASCIILiteral("shadowOffsetX"), context2d->shadowOffsetX());
+        attributes->setDouble(ASCIILiteral("shadowOffsetY"), context2d->shadowOffsetY());
+        attributes->setDouble(ASCIILiteral("shadowBlur"), context2d->shadowBlur());
+        attributes->setString(ASCIILiteral("shadowColor"), context2d->shadowColor());
+
+        // The parameter to `setLineDash` is itself an array, so we need to wrap the parameters
+        // list in an array to allow spreading.
+        auto setLineDash = Inspector::Protocol::Array<InspectorValue>::create();
+        setLineDash->addItem(buildArrayForVector(state.lineDash));
+        attributes->setArray(ASCIILiteral("setLineDash"), WTFMove(setLineDash));
+
+        attributes->setDouble(ASCIILiteral("lineDashOffset"), context2d->lineDashOffset());
+        attributes->setString(ASCIILiteral("font"), context2d->font());
+        attributes->setString(ASCIILiteral("textAlign"), context2d->textAlign());
+        attributes->setString(ASCIILiteral("textBaseline"), context2d->textBaseline());
+        attributes->setString(ASCIILiteral("direction"), context2d->direction());
+
+        int strokeStyleIndex;
+        if (CanvasGradient* canvasGradient = state.strokeStyle.canvasGradient())
+            strokeStyleIndex = indexForData(canvasGradient);
+        else if (CanvasPattern* canvasPattern = state.strokeStyle.canvasPattern())
+            strokeStyleIndex = indexForData(canvasPattern);
+        else
+            strokeStyleIndex = indexForData(state.strokeStyle.color());
+        attributes->setInteger(ASCIILiteral("strokeStyle"), strokeStyleIndex);
+
+        int fillStyleIndex;
+        if (CanvasGradient* canvasGradient = state.fillStyle.canvasGradient())
+            fillStyleIndex = indexForData(canvasGradient);
+        else if (CanvasPattern* canvasPattern = state.fillStyle.canvasPattern())
+            fillStyleIndex = indexForData(canvasPattern);
+        else
+            fillStyleIndex = indexForData(state.fillStyle.color());
+        attributes->setInteger(ASCIILiteral("fillStyle"), fillStyleIndex);
+
+        attributes->setBoolean(ASCIILiteral("imageSmoothingEnabled"), context2d->imageSmoothingEnabled());
+        attributes->setString(ASCIILiteral("imageSmoothingQuality"), CanvasRenderingContext2D::stringForImageSmoothingQuality(context2d->imageSmoothingQuality()));
+    }
+
+    // <https://webkit.org/b/174483> Web Inspector: Record actions performed on WebGLRenderingContext
+
+    initialState->setAttributes(WTFMove(attributes));
+
+    if (parameters->length())
+        initialState->setParameters(WTFMove(parameters));
+
+    ExceptionOr<UncachedString> result = canvas().toDataURL(ASCIILiteral("image/png"));
+    if (!result.hasException())
+        initialState->setContent(result.releaseReturnValue().string);
+
+    return initialState;
+}
+
+RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> InspectorCanvas::buildAction(const String& name, Vector<CanvasActionParameterVariant>&& parameters)
+{
+    RefPtr<Inspector::Protocol::Array<InspectorValue>> action = Inspector::Protocol::Array<InspectorValue>::create();
+    action->addItem(static_cast<double>(indexForData(name)));
+
+    RefPtr<Inspector::Protocol::Array<InspectorValue>> parametersData = Inspector::Protocol::Array<Inspector::InspectorValue>::create();
+    for (CanvasActionParameterVariant& item : parameters) {
+        WTF::switchOn(item,
+            [&] (const Element*) {
+                // Elements are not serializable, so add a string as a placeholder since the actual
+                // element cannot be reconstructed in the frontend.
+                parametersData->addItem(indexForData(String("element")));
+            },
+            [&] (const HTMLImageElement* value) { parametersData->addItem(indexForData(value)); },
+            [&] (const ImageData* value) {
+                if (value)
+                    parametersData->addItem(indexForData(value));
+            },
+            [&] (const DOMMatrixInit& value) {
+                RefPtr<Inspector::Protocol::Array<double>> array = Inspector::Protocol::Array<double>::create();
+                array->addItem(value.a.value_or(1));
+                array->addItem(value.b.value_or(0));
+                array->addItem(value.c.value_or(0));
+                array->addItem(value.d.value_or(1));
+                array->addItem(value.e.value_or(0));
+                array->addItem(value.f.value_or(0));
+                parametersData->addItem(WTFMove(array));
+            },
+            [&] (const DOMPath* value) { parametersData->addItem(indexForData(buildStringFromPath(value->path()))); },
+            [&] (const Vector<float>& value) { parametersData->addItem(buildArrayForVector(value)); },
+            [&] (const String& value) { parametersData->addItem(indexForData(value)); },
+            [&] (double value) { parametersData->addItem(value); },
+            [&] (float value) { parametersData->addItem(value); },
+            [&] (int value) { parametersData->addItem(value); },
+            [&] (bool value) { parametersData->addItem(value); },
+            [&] (const std::optional<float>& value) {
+                if (value)
+                    parametersData->addItem(value.value());
+            },
+            [&] (CanvasImageSource& canvasImageSource) {
+                WTF::switchOn(canvasImageSource,
+                    [&] (const RefPtr<HTMLImageElement>& value) { parametersData->addItem(indexForData(value.get())); },
+#if ENABLE(VIDEO)
+                    [&] (RefPtr<HTMLVideoElement>& value) { parametersData->addItem(indexForData(value.get())); },
+#endif
+                    [&] (RefPtr<HTMLCanvasElement>& value) { parametersData->addItem(indexForData(value.get())); }
+                );
+            },
+            [&] (const CanvasRenderingContext2D::Style& style) {
+                WTF::switchOn(style,
+                    [&] (const String& value) { parametersData->addItem(indexForData(value)); },
+                    [&] (const RefPtr<CanvasGradient>& value) { parametersData->addItem(indexForData(value.get())); },
+                    [&] (const RefPtr<CanvasPattern>& value) { parametersData->addItem(indexForData(value.get())); }
+                );
+            },
+            [&] (CanvasRenderingContext2D::WindingRule value) {
+                String windingRule = CanvasRenderingContext2D::stringForWindingRule(value);
+                parametersData->addItem(indexForData(windingRule));
+            },
+            [&] (CanvasRenderingContext2D::ImageSmoothingQuality value) {
+                String imageSmoothingQuality = CanvasRenderingContext2D::stringForImageSmoothingQuality(value);
+                parametersData->addItem(indexForData(imageSmoothingQuality));
+            }
+        );
+    }
+    action->addItem(WTFMove(parametersData));
+
+    return action;
+}
+
+RefPtr<Inspector::Protocol::Array<InspectorValue>> InspectorCanvas::buildArrayForCanvasGradient(const CanvasGradient& canvasGradient)
+{
+    const Gradient& gradient = canvasGradient.gradient();
+    bool isRadial = gradient.isRadial();
+
+    String type = isRadial ? ASCIILiteral("radial-gradient") : ASCIILiteral("linear-gradient");
+
+    RefPtr<Inspector::Protocol::Array<float>> parameters = Inspector::Protocol::Array<float>::create();
+    parameters->addItem(gradient.p0().x());
+    parameters->addItem(gradient.p0().y());
+    if (isRadial)
+        parameters->addItem(gradient.startRadius());
+    parameters->addItem(gradient.p1().x());
+    parameters->addItem(gradient.p1().y());
+    if (isRadial)
+        parameters->addItem(gradient.endRadius());
+
+    RefPtr<Inspector::Protocol::Array<InspectorValue>> stops = Inspector::Protocol::Array<InspectorValue>::create();
+    for (const Gradient::ColorStop& colorStop : gradient.stops()) {
+        RefPtr<Inspector::Protocol::Array<InspectorValue>> stop = Inspector::Protocol::Array<InspectorValue>::create();
+        stop->addItem(colorStop.offset);
+        stop->addItem(indexForData(colorStop.color.cssText()));
+        stops->addItem(WTFMove(stop));
+    }
+
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> array = Inspector::Protocol::Array<Inspector::InspectorValue>::create();
+    array->addItem(indexForData(type));
+    array->addItem(WTFMove(parameters));
+    array->addItem(WTFMove(stops));
+    return array;
+}
+
+RefPtr<Inspector::Protocol::Array<InspectorValue>> InspectorCanvas::buildArrayForCanvasPattern(const CanvasPattern& canvasPattern)
+{
+    Image& tileImage = canvasPattern.pattern().tileImage();
+    std::unique_ptr<ImageBuffer> imageBuffer = ImageBuffer::create(tileImage.size(), RenderingMode::Unaccelerated);
+    imageBuffer->context().drawImage(tileImage, FloatPoint(0, 0));
+
+    String repeat;
+    bool repeatX = canvasPattern.pattern().repeatX();
+    bool repeatY = canvasPattern.pattern().repeatY();
+    if (repeatX && repeatY)
+        repeat = ASCIILiteral("repeat");
+    else if (repeatX && !repeatY)
+        repeat = ASCIILiteral("repeat-x");
+    else if (!repeatX && repeatY)
+        repeat = ASCIILiteral("repeat-y");
+    else
+        repeat = ASCIILiteral("no-repeat");
+
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> array = Inspector::Protocol::Array<Inspector::InspectorValue>::create();
+    array->addItem(indexForData("pattern"));
+    array->addItem(indexForData(imageBuffer->toDataURL("image/png")));
+    array->addItem(indexForData(repeat));
+    return array;
+}
+
+RefPtr<Inspector::Protocol::Array<InspectorValue>> InspectorCanvas::buildArrayForImageData(const ImageData& imageData)
+{
+    RefPtr<Inspector::Protocol::Array<int>> data = Inspector::Protocol::Array<int>::create();
+    for (size_t i = 0; i < imageData.data()->length(); ++i)
+        data->addItem(imageData.data()->item(i));
+
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> array = Inspector::Protocol::Array<Inspector::InspectorValue>::create();
+    array->addItem(WTFMove(data));
+    array->addItem(imageData.width());
+    array->addItem(imageData.height());
+    return array;
+}
+
 } // namespace WebCore
 
index cdfc326..11740b8 100644 (file)
 
 #pragma once
 
+#include "CallTracerTypes.h"
 #include <inspector/InspectorProtocolObjects.h>
 #include <inspector/InspectorValues.h>
 #include <wtf/HashMap.h>
+#include <wtf/Ref.h>
+#include <wtf/RefPtr.h>
+#include <wtf/Variant.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
 
 namespace WebCore {
 
+class CanvasGradient;
+class CanvasPattern;
 class HTMLCanvasElement;
+class HTMLImageElement;
+class HTMLVideoElement;
+class ImageData;
 class InstrumentingAgents;
 
 typedef String ErrorString;
 
+typedef Variant<
+    const HTMLImageElement*,
+#if ENABLE(VIDEO)
+    HTMLVideoElement*,
+#endif
+    HTMLCanvasElement*,
+    const CanvasGradient*,
+    const CanvasPattern*,
+    const ImageData*,
+    String
+> DuplicateDataVariant;
+
 class InspectorCanvas final : public RefCounted<InspectorCanvas> {
 public:
     static Ref<InspectorCanvas> create(HTMLCanvasElement&, const String& cssCanvasName);
@@ -44,16 +67,49 @@ public:
     HTMLCanvasElement& canvas() { return m_canvas; }
     const String& cssCanvasName() { return m_cssCanvasName; }
 
+    void resetRecordingData();
+    bool hasRecordingData() const;
+    void recordAction(const String&, Vector<CanvasActionParameterVariant>&& = { });
+
+    RefPtr<Inspector::Protocol::Recording::InitialState>&& releaseInitialState() { return WTFMove(m_initialState); }
+    RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Recording::Frame>>&& releaseFrames() { return WTFMove(m_frames); }
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>>&& releaseData();
+
+    void markNewFrame();
+    void markCurrentFrameIncomplete();
+
+    void setBufferLimit(long);
+    bool hasBufferSpace() const;
+
+    bool singleFrame() const { return m_singleFrame; }
+    void setSingleFrame(bool singleFrame) { m_singleFrame = singleFrame; }
+
     Ref<Inspector::Protocol::Canvas::Canvas> buildObjectForCanvas(InstrumentingAgents&);
 
-    ~InspectorCanvas() { }
+    ~InspectorCanvas();
 
 private:
     InspectorCanvas(HTMLCanvasElement&, const String& cssCanvasName);
 
+    int indexForData(DuplicateDataVariant);
+    RefPtr<Inspector::Protocol::Recording::InitialState> buildInitialState();
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> buildAction(const String&, Vector<CanvasActionParameterVariant>&& = { });
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> buildArrayForCanvasGradient(const CanvasGradient&);
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> buildArrayForCanvasPattern(const CanvasPattern&);
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> buildArrayForImageData(const ImageData&);
+
     String m_identifier;
     HTMLCanvasElement& m_canvas;
     String m_cssCanvasName;
+
+    RefPtr<Inspector::Protocol::Recording::InitialState> m_initialState;
+    RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Recording::Frame>> m_frames;
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> m_currentActions;
+    RefPtr<Inspector::Protocol::Array<Inspector::InspectorValue>> m_serializedDuplicateData;
+    Vector<DuplicateDataVariant> m_indexedDuplicateData;
+    size_t m_bufferLimit { 100 * 1024 * 1024 };
+    size_t m_bufferUsed { 0 };
+    bool m_singleFrame { true };
 };
 
 } // namespace WebCore
index c00e6db..51f2598 100644 (file)
 #include "config.h"
 #include "InspectorCanvasAgent.h"
 
+#include "CanvasGradient.h"
+#include "CanvasPattern.h"
+#include "CanvasRenderingContext.h"
 #include "CanvasRenderingContext2D.h"
+#include "DOMMatrixInit.h"
+#include "DOMPath.h"
 #include "Document.h"
 #include "Element.h"
 #include "Frame.h"
+#include "HTMLCanvasElement.h"
+#include "HTMLImageElement.h"
+#include "HTMLVideoElement.h"
+#include "ImageData.h"
 #include "InspectorDOMAgent.h"
 #include "InstrumentingAgents.h"
 #include "JSCanvasRenderingContext2D.h"
@@ -64,7 +73,8 @@ InspectorCanvasAgent::InspectorCanvasAgent(WebAgentContext& context)
     , m_frontendDispatcher(std::make_unique<Inspector::CanvasFrontendDispatcher>(context.frontendRouter))
     , m_backendDispatcher(Inspector::CanvasBackendDispatcher::create(context.backendDispatcher, this))
     , m_injectedScriptManager(context.injectedScriptManager)
-    , m_timer(*this, &InspectorCanvasAgent::canvasDestroyedTimerFired)
+    , m_canvasDestroyedTimer(*this, &InspectorCanvasAgent::canvasDestroyedTimerFired)
+    , m_canvasRecordingTimer(*this, &InspectorCanvasAgent::canvasRecordingTimerFired)
 {
 }
 
@@ -99,11 +109,17 @@ void InspectorCanvasAgent::disable(ErrorString&)
     if (!m_enabled)
         return;
 
-    if (m_timer.isActive())
-        m_timer.stop();
+    if (m_canvasDestroyedTimer.isActive())
+        m_canvasDestroyedTimer.stop();
 
     m_removedCanvasIdentifiers.clear();
 
+    if (m_canvasRecordingTimer.isActive())
+        m_canvasRecordingTimer.stop();
+
+    for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
+        inspectorCanvas->resetRecordingData();
+
     m_enabled = false;
 }
 
@@ -220,6 +236,40 @@ void InspectorCanvasAgent::resolveCanvasContext(ErrorString& errorString, const
     result = injectedScript.wrapObject(value, objectGroupName);
 }
 
+void InspectorCanvasAgent::requestRecording(ErrorString& errorString, const String& canvasId, const bool* const singleFrame, const int* const memoryLimit)
+{
+    auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
+    if (!inspectorCanvas)
+        return;
+
+    if (inspectorCanvas->canvas().renderingContext()->callTracingActive()) {
+        errorString = ASCIILiteral("Already recording canvas");
+        return;
+    }
+
+    inspectorCanvas->resetRecordingData();
+    if (singleFrame)
+        inspectorCanvas->setSingleFrame(*singleFrame);
+    if (memoryLimit)
+        inspectorCanvas->setBufferLimit(*memoryLimit);
+
+    inspectorCanvas->canvas().renderingContext()->setCallTracingActive(true);
+}
+
+void InspectorCanvasAgent::cancelRecording(ErrorString& errorString, const String& canvasId)
+{
+    auto* inspectorCanvas = assertInspectorCanvas(errorString, canvasId);
+    if (!inspectorCanvas)
+        return;
+
+    if (!inspectorCanvas->canvas().renderingContext()->callTracingActive()) {
+        errorString = ASCIILiteral("No active recording for canvas");
+        return;
+    }
+
+    didFinishRecordingCanvasFrame(inspectorCanvas->canvas(), true);
+}
+
 void InspectorCanvasAgent::frameNavigated(Frame& frame)
 {
     if (frame.isMainFrame()) {
@@ -287,6 +337,28 @@ void InspectorCanvasAgent::didChangeCanvasMemory(HTMLCanvasElement& canvasElemen
     m_frontendDispatcher->canvasMemoryChanged(inspectorCanvas->identifier(), canvasElement.memoryCost());
 }
 
+void InspectorCanvasAgent::recordCanvasAction(CanvasRenderingContext& canvasRenderingContext, const String& name, Vector<CanvasActionParameterVariant>&& parameters)
+{
+    HTMLCanvasElement& canvasElement = canvasRenderingContext.canvas();
+
+    auto* inspectorCanvas = findInspectorCanvas(canvasElement);
+    ASSERT(inspectorCanvas);
+    if (!inspectorCanvas)
+        return;
+
+    ASSERT(canvasRenderingContext.callTracingActive());
+    if (!canvasRenderingContext.callTracingActive())
+        return;
+
+    inspectorCanvas->recordAction(name, WTFMove(parameters));
+
+    if (!m_canvasRecordingTimer.isActive())
+        m_canvasRecordingTimer.startOneShot(0_s);
+
+    if (!inspectorCanvas->hasBufferSpace())
+        didFinishRecordingCanvasFrame(canvasElement, true);
+}
+
 void InspectorCanvasAgent::canvasDestroyed(HTMLCanvasElement& canvasElement)
 {
     auto* inspectorCanvas = findInspectorCanvas(canvasElement);
@@ -303,8 +375,54 @@ void InspectorCanvasAgent::canvasDestroyed(HTMLCanvasElement& canvasElement)
     // the frontend from making JS allocations while the GC is still active.
     m_removedCanvasIdentifiers.append(identifier);
 
-    if (!m_timer.isActive())
-        m_timer.startOneShot(0_s);
+    if (!m_canvasDestroyedTimer.isActive())
+        m_canvasDestroyedTimer.startOneShot(0_s);
+}
+
+void InspectorCanvasAgent::didFinishRecordingCanvasFrame(HTMLCanvasElement& canvasElement, bool forceDispatch)
+{
+    auto* inspectorCanvas = findInspectorCanvas(canvasElement);
+    ASSERT(inspectorCanvas);
+    if (!inspectorCanvas)
+        return;
+
+    CanvasRenderingContext* canvasRenderingContext = inspectorCanvas->canvas().renderingContext();
+    ASSERT(canvasRenderingContext->callTracingActive());
+    if (!canvasRenderingContext->callTracingActive())
+        return;
+
+    if (!inspectorCanvas->hasRecordingData())
+        return;
+
+    if (!forceDispatch && !inspectorCanvas->singleFrame()) {
+        inspectorCanvas->markNewFrame();
+        return;
+    }
+
+    if (forceDispatch)
+        inspectorCanvas->markCurrentFrameIncomplete();
+
+    // <https://webkit.org/b/174483> Web Inspector: Record actions performed on WebGLRenderingContext
+
+    Inspector::Protocol::Recording::Type type;
+    if (is<CanvasRenderingContext2D>(canvasRenderingContext))
+        type = Inspector::Protocol::Recording::Type::Canvas2D;
+    else {
+        ASSERT_NOT_REACHED();
+        type = Inspector::Protocol::Recording::Type::Canvas2D;
+    }
+
+    auto recording = Inspector::Protocol::Recording::Recording::create()
+        .setVersion(1)
+        .setType(type)
+        .setInitialState(inspectorCanvas->releaseInitialState())
+        .setFrames(inspectorCanvas->releaseFrames())
+        .setData(inspectorCanvas->releaseData())
+        .release();
+
+    m_frontendDispatcher->recordingFinished(inspectorCanvas->identifier(), WTFMove(recording));
+
+    inspectorCanvas->resetRecordingData();
 }
 
 void InspectorCanvasAgent::canvasDestroyedTimerFired()
@@ -320,6 +438,16 @@ void InspectorCanvasAgent::canvasDestroyedTimerFired()
     m_removedCanvasIdentifiers.clear();
 }
 
+void InspectorCanvasAgent::canvasRecordingTimerFired()
+{
+    for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
+        if (!inspectorCanvas->canvas().renderingContext()->callTracingActive())
+            continue;
+
+        didFinishRecordingCanvasFrame(inspectorCanvas->canvas());
+    }
+}
+
 void InspectorCanvasAgent::clearCanvasData()
 {
     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values())
@@ -329,8 +457,11 @@ void InspectorCanvasAgent::clearCanvasData()
     m_canvasToCSSCanvasName.clear();
     m_removedCanvasIdentifiers.clear();
 
-    if (m_timer.isActive())
-        m_timer.stop();
+    if (m_canvasRecordingTimer.isActive())
+        m_canvasRecordingTimer.stop();
+
+    if (m_canvasDestroyedTimer.isActive())
+        m_canvasDestroyedTimer.stop();
 }
 
 String InspectorCanvasAgent::unbindCanvas(InspectorCanvas& inspectorCanvas)
index 07d16e0..ebd22ef 100644 (file)
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "CallTracerTypes.h"
 #include "HTMLCanvasElement.h"
 #include "InspectorCanvas.h"
 #include "InspectorWebAgentBase.h"
@@ -32,7 +33,6 @@
 #include <inspector/InspectorBackendDispatchers.h>
 #include <inspector/InspectorFrontendDispatchers.h>
 #include <wtf/HashMap.h>
-#include <wtf/HashSet.h>
 #include <wtf/RefPtr.h>
 #include <wtf/Vector.h>
 #include <wtf/text/WTFString.h>
@@ -43,6 +43,7 @@ class InjectedScriptManager;
 
 namespace WebCore {
 
+class CanvasRenderingContext;
 class WebGLRenderingContextBase;
 
 typedef String ErrorString;
@@ -65,6 +66,8 @@ public:
     void requestContent(ErrorString&, const String& canvasId, String* content) override;
     void requestCSSCanvasClientNodes(ErrorString&, const String& canvasId, RefPtr<Inspector::Protocol::Array<int>>&) override;
     void resolveCanvasContext(ErrorString&, const String& canvasId, const String* const objectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>&) override;
+    void requestRecording(ErrorString&, const String& canvasId, const bool* const singleFrame, const int* const memoryLimit) override;
+    void cancelRecording(ErrorString&, const String& canvasId) override;
 
     // InspectorInstrumentation
     void frameNavigated(Frame&);
@@ -72,6 +75,8 @@ public:
     void didChangeCSSCanvasClientNodes(HTMLCanvasElement&);
     void didCreateCanvasRenderingContext(HTMLCanvasElement&);
     void didChangeCanvasMemory(HTMLCanvasElement&);
+    void recordCanvasAction(CanvasRenderingContext&, const String&, Vector<CanvasActionParameterVariant>&& = { });
+    void didFinishRecordingCanvasFrame(HTMLCanvasElement&, bool forceDispatch = false);
 
     // CanvasObserver
     void canvasChanged(HTMLCanvasElement&, const FloatRect&) override { }
@@ -80,6 +85,7 @@ public:
 
 private:
     void canvasDestroyedTimerFired();
+    void canvasRecordingTimerFired();
     void clearCanvasData();
     String unbindCanvas(InspectorCanvas&);
     InspectorCanvas* assertInspectorCanvas(ErrorString&, const String&);
@@ -92,7 +98,9 @@ private:
     HashMap<String, RefPtr<InspectorCanvas>> m_identifierToInspectorCanvas;
     HashMap<HTMLCanvasElement*, String> m_canvasToCSSCanvasName;
     Vector<String> m_removedCanvasIdentifiers;
-    Timer m_timer;
+    Timer m_canvasDestroyedTimer;
+    Timer m_canvasRecordingTimer;
+
     bool m_enabled { false };
 };
 
index 01374a7..d9966c0 100644 (file)
@@ -980,6 +980,18 @@ void InspectorInstrumentation::didChangeCanvasMemoryImpl(InstrumentingAgents* in
         canvasAgent->didChangeCanvasMemory(canvasElement);
 }
 
+void InspectorInstrumentation::recordCanvasActionImpl(InstrumentingAgents* instrumentingAgents, CanvasRenderingContext& canvasRenderingContext, const String& name, Vector<CanvasActionParameterVariant>&& parameters)
+{
+    if (InspectorCanvasAgent* canvasAgent = instrumentingAgents->inspectorCanvasAgent())
+        canvasAgent->recordCanvasAction(canvasRenderingContext, name, WTFMove(parameters));
+}
+
+void InspectorInstrumentation::didFinishRecordingCanvasFrameImpl(InstrumentingAgents* instrumentingAgents, HTMLCanvasElement& canvasElement, bool forceDispatch)
+{
+    if (InspectorCanvasAgent* canvasAgent = instrumentingAgents->inspectorCanvasAgent())
+        canvasAgent->didFinishRecordingCanvasFrame(canvasElement, forceDispatch);
+}
+
 #if ENABLE(RESOURCE_USAGE)
 void InspectorInstrumentation::didHandleMemoryPressureImpl(InstrumentingAgents& instrumentingAgents, Critical critical)
 {
index d54b2ea..bf259b8 100644 (file)
 #pragma once
 
 #include "CSSSelector.h"
+#include "CallTracerTypes.h"
+#include "CanvasGradient.h"
+#include "CanvasPattern.h"
+#include "CanvasRenderingContext.h"
+#include "DOMPath.h"
 #include "DocumentThreadableLoader.h"
 #include "Element.h"
 #include "FormData.h"
 #include "Frame.h"
 #include "HTMLCanvasElement.h"
+#include "HTMLImageElement.h"
+#include "HTMLVideoElement.h"
 #include "HitTestResult.h"
+#include "ImageData.h"
 #include "InspectorController.h"
 #include "InspectorInstrumentationCookie.h"
 #include "Page.h"
@@ -224,6 +232,8 @@ public:
     static void didChangeCSSCanvasClientNodes(HTMLCanvasElement&);
     static void didCreateCanvasRenderingContext(HTMLCanvasElement&);
     static void didChangeCanvasMemory(HTMLCanvasElement&);
+    static void recordCanvasAction(CanvasRenderingContext&, const String&, Vector<CanvasActionParameterVariant>&& = { });
+    static void didFinishRecordingCanvasFrame(HTMLCanvasElement&, bool forceDispatch = false);
 
     static void networkStateChanged(Page&);
     static void updateApplicationCacheStatus(Frame*);
@@ -381,6 +391,8 @@ private:
     static void didChangeCSSCanvasClientNodesImpl(InstrumentingAgents*, HTMLCanvasElement&);
     static void didCreateCanvasRenderingContextImpl(InstrumentingAgents*, HTMLCanvasElement&);
     static void didChangeCanvasMemoryImpl(InstrumentingAgents*, HTMLCanvasElement&);
+    static void recordCanvasActionImpl(InstrumentingAgents*, CanvasRenderingContext&, const String&, Vector<CanvasActionParameterVariant>&& = { });
+    static void didFinishRecordingCanvasFrameImpl(InstrumentingAgents*, HTMLCanvasElement&, bool forceDispatch = false);
 
     static void layerTreeDidChangeImpl(InstrumentingAgents&);
     static void renderLayerDestroyedImpl(InstrumentingAgents&, const RenderLayer&);
@@ -1086,6 +1098,20 @@ inline void InspectorInstrumentation::didChangeCanvasMemory(HTMLCanvasElement& c
         didChangeCanvasMemoryImpl(instrumentingAgents, canvasElement);
 }
 
+inline void InspectorInstrumentation::recordCanvasAction(CanvasRenderingContext& canvasRenderingContext, const String& name, Vector<CanvasActionParameterVariant>&& parameters)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(&canvasRenderingContext.canvas().document()))
+        recordCanvasActionImpl(instrumentingAgents, canvasRenderingContext, name, WTFMove(parameters));
+}
+
+inline void InspectorInstrumentation::didFinishRecordingCanvasFrame(HTMLCanvasElement& canvasElement, bool forceDispatch)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(&canvasElement.document()))
+        didFinishRecordingCanvasFrameImpl(instrumentingAgents, canvasElement, forceDispatch);
+}
+
 inline void InspectorInstrumentation::networkStateChanged(Page& page)
 {
     FAST_RETURN_IF_NO_FRONTENDS(void());
index 6b91bf5..c95e1ca 100644 (file)
@@ -146,6 +146,8 @@ namespace WebCore {
                 { }
         };
 
+        const Vector<ColorStop, 2>& stops() const { return m_stops; }
+
         void setStopsSorted(bool s) { m_stopsSorted = s; }
         
         void setSpreadMethod(GradientSpreadMethod);
index 3e900e4..22194af 100644 (file)
@@ -49,6 +49,59 @@ bool buildPathFromString(const String& d, Path& result)
     return SVGPathParser::parse(source, builder);
 }
 
+String buildStringFromPath(const Path& path)
+{
+    StringBuilder builder;
+
+    if (!path.isNull() && !path.isEmpty()) {
+        path.apply([&builder] (const PathElement& element) {
+            switch (element.type) {
+            case PathElementMoveToPoint:
+                builder.append('M');
+                builder.appendECMAScriptNumber(element.points[0].x());
+                builder.append(' ');
+                builder.appendECMAScriptNumber(element.points[0].y());
+                break;
+            case PathElementAddLineToPoint:
+                builder.append('L');
+                builder.appendECMAScriptNumber(element.points[0].x());
+                builder.append(' ');
+                builder.appendECMAScriptNumber(element.points[0].y());
+                break;
+            case PathElementAddQuadCurveToPoint:
+                builder.append('Q');
+                builder.appendECMAScriptNumber(element.points[0].x());
+                builder.append(' ');
+                builder.appendECMAScriptNumber(element.points[0].y());
+                builder.append(',');
+                builder.appendECMAScriptNumber(element.points[1].x());
+                builder.append(' ');
+                builder.appendECMAScriptNumber(element.points[1].y());
+                break;
+            case PathElementAddCurveToPoint:
+                builder.append('C');
+                builder.appendECMAScriptNumber(element.points[0].x());
+                builder.append(' ');
+                builder.appendECMAScriptNumber(element.points[0].y());
+                builder.append(',');
+                builder.appendECMAScriptNumber(element.points[1].x());
+                builder.append(' ');
+                builder.appendECMAScriptNumber(element.points[1].y());
+                builder.append(',');
+                builder.appendECMAScriptNumber(element.points[2].x());
+                builder.append(' ');
+                builder.appendECMAScriptNumber(element.points[2].y());
+                break;
+            case PathElementCloseSubpath:
+                builder.append('Z');
+                break;
+            }
+        });
+    }
+
+    return builder.toString();
+}
+
 bool buildSVGPathByteStreamFromSVGPathSegListValues(const SVGPathSegListValues& list, SVGPathByteStream& result, PathParsingMode parsingMode)
 {
     result.clear();
index 79e7d72..df94e27 100644 (file)
@@ -36,6 +36,9 @@ class SVGPathSegListValues;
 bool buildPathFromString(const String&, Path&);
 bool buildPathFromByteStream(const SVGPathByteStream&, Path&);
 
+// Path -> String
+String buildStringFromPath(const Path&);
+
 // SVGPathSegListValues/String -> SVGPathByteStream
 bool buildSVGPathByteStreamFromSVGPathSegListValues(const SVGPathSegListValues&, SVGPathByteStream& result, PathParsingMode);
 bool appendSVGPathByteStreamFromSVGPathSeg(RefPtr<SVGPathSeg>&&, SVGPathByteStream&, PathParsingMode);
index 9105a8c..e0d431d 100644 (file)
@@ -48,6 +48,7 @@
         "MemoryAgent": true,
         "NetworkAgent": true,
         "PageAgent": true,
+        "RecordingAgent": true,
         "RuntimeAgent": true,
         "ScriptProfilerAgent": true,
         "TimelineAgent": true,
index d2f29c1..faf33f9 100644 (file)
@@ -1,3 +1,44 @@
+2017-07-26  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: create protocol for recording Canvas contexts
+        https://bugs.webkit.org/show_bug.cgi?id=174481
+
+        Reviewed by Joseph Pecoraro.
+
+        Create model objects that effectively mirror the protocol objects sent for Canvas recordings.
+
+        * .eslintrc:
+        * UserInterface/Main.html:
+        * UserInterface/Test.html:
+
+        * UserInterface/Controllers/CanvasManager.js:
+        (WebInspector.CanvasManager.prototype.recordingFinished):
+        * UserInterface/Protocol/CanvasObserver.js:
+        (WebInspector.CanvasObserver.prototype.recordingFinished):
+
+        * UserInterface/Models/Recording.js: Added.
+        (WebInspector.Recording):
+        (WebInspector.Recording.fromPayload):
+        (WebInspector.Recording.prototype.get type):
+        (WebInspector.Recording.prototype.get initialState):
+        (WebInspector.Recording.prototype.get frames):
+        (WebInspector.Recording.prototype.get data):
+        (WebInspector.Recording.prototype.toJSON):
+
+        * UserInterface/Models/RecordingAction.js: Added.
+        (WebInspector.RecordingAction):
+        (WebInspector.RecordingAction.fromPayload):
+        (WebInspector.RecordingAction.prototype.get name):
+        (WebInspector.RecordingAction.prototype.get parameters):
+        (WebInspector.RecordingAction.prototype.toJSON):
+
+        * UserInterface/Models/RecordingFrame.js: Added.
+        (WebInspector.RecordingFrame):
+        (WebInspector.RecordingFrame.fromPayload):
+        (WebInspector.RecordingFrame.prototype.get actions):
+        (WebInspector.RecordingFrame.prototype.get incomplete):
+        (WebInspector.RecordingFrame.prototype.toJSON):
+
 2017-07-26  Brian Burg  <bburg@apple.com>
 
         Remove WEB_TIMING feature flag
index 46f322a..f4a238b 100644 (file)
@@ -96,6 +96,19 @@ WebInspector.CanvasManager = class CanvasManager extends WebInspector.Object
         canvas.cssCanvasClientNodesChanged();
     }
 
+    recordingFinished(canvasIdentifier, recordingPayload)
+    {
+        // Called from WebInspector.CanvasObserver.
+
+        let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
+        console.assert(canvas);
+        if (!canvas)
+            return;
+
+        let recording = WebInspector.Recording.fromPayload(recordingPayload);
+        this.dispatchEventToListeners(WebInspector.CanvasManager.Event.RecordingFinished, {canvas, recording});
+    }
+
     // Private
 
     _mainResourceDidChange(event)
@@ -117,4 +130,5 @@ WebInspector.CanvasManager.Event = {
     Cleared: "canvas-manager-cleared",
     CanvasWasAdded: "canvas-manager-canvas-was-added",
     CanvasWasRemoved: "canvas-manager-canvas-was-removed",
+    RecordingFinished: "canvas-managger-recording-finished",
 };
index f503e8b..c87b1d0 100644 (file)
     <script src="Models/PropertyDescriptor.js"></script>
     <script src="Models/PropertyPath.js"></script>
     <script src="Models/PropertyPreview.js"></script>
+    <script src="Models/Recording.js"></script>
+    <script src="Models/RecordingAction.js"></script>
+    <script src="Models/RecordingFrame.js"></script>
     <script src="Models/RenderingFrameTimelineRecord.js"></script>
     <script src="Models/Resource.js"></script>
     <script src="Models/ResourceCollection.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Models/Recording.js b/Source/WebInspectorUI/UserInterface/Models/Recording.js
new file mode 100644 (file)
index 0000000..e0704a0
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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. 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.
+ */
+
+WebInspector.Recording = class Recording
+{
+    constructor(version, type, initialState, frames, data)
+    {
+        this._version = version;
+        this._type = type;
+        this._initialState = initialState;
+        this._frames = frames;
+        this._data = data;
+    }
+
+    // Static
+
+    static fromPayload(payload)
+    {
+        if (typeof payload !== "object" || payload === null)
+            payload = {};
+
+        if (isNaN(payload.version) || payload.version <= 0)
+            return null;
+
+        let type = null;
+        switch (payload.type) {
+        case RecordingAgent.Type.Canvas2D:
+            type = WebInspector.Recording.Type.Canvas2D;
+            break;
+        default:
+            type = String(payload.type);
+            break;
+        }
+
+        if (typeof payload.initialState !== "object" || payload.initialState === null)
+            payload.initialState = {};
+        if (typeof payload.initialState.attributes !== "object" || payload.initialState.attributes === null)
+            payload.initialState.attributes = {};
+        if (!Array.isArray(payload.initialState.parameters))
+            payload.initialState.parameters = [];
+        if (typeof payload.initialState.content !== "string")
+            payload.initialState.content = "";
+
+        if (!Array.isArray(payload.frames))
+            payload.frames = [];
+
+        if (!Array.isArray(payload.data))
+            payload.data = [];
+
+        let frames = payload.frames.map(WebInspector.RecordingFrame.fromPayload);
+        return new WebInspector.Recording(payload.version, type, payload.initialState, frames, payload.data);
+    }
+
+    // Public
+
+    get type() { return this._type; }
+    get initialState() { return this._initialState; }
+    get frames() { return this._frames; }
+    get data() { return this._data; }
+
+    toJSON()
+    {
+        let initialState = {};
+        if (!isEmptyObject(this._initialState.attributes))
+            initialState.attributes = this._initialState.attributes;
+        if (this._initialState.parameters.length)
+            initialState.parameters = this._initialState.parameters;
+        if (this._initialState.content && this._initialState.content.length)
+            initialState.content = this._initialState.content;
+
+        return {
+            version: this._version,
+            type: this._type,
+            initialState,
+            frames: this._frames.map((frame) => frame.toJSON()),
+            data: this._data,
+        };
+    }
+};
+
+WebInspector.Recording.Type = {
+    Canvas2D: "canvas-2d",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js b/Source/WebInspectorUI/UserInterface/Models/RecordingAction.js
new file mode 100644 (file)
index 0000000..12fc946
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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. 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.
+ */
+
+WebInspector.RecordingAction = class RecordingAction
+{
+    constructor(name, parameters)
+    {
+        this._name = name;
+        this._parameters = parameters;
+    }
+
+    // Static
+
+    // Payload format: [name, parameters]
+    static fromPayload(payload)
+    {
+        if (!Array.isArray(payload))
+            payload = [];
+
+        if (isNaN(payload[0]))
+            payload[0] = -1;
+
+        if (!Array.isArray(payload[1]))
+            payload[1] = [];
+
+        return new WebInspector.RecordingAction(...payload);
+    }
+
+    // Public
+
+    get name() { return this._resolvedName; }
+    get parameters() { return this._resolvedParameters; }
+
+    toJSON()
+    {
+        return [this._name, this._parameters];
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Models/RecordingFrame.js b/Source/WebInspectorUI/UserInterface/Models/RecordingFrame.js
new file mode 100644 (file)
index 0000000..28e93a7
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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. 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.
+ */
+
+WebInspector.RecordingFrame = class RecordingFrame
+{
+    constructor(actions, {incomplete} = {})
+    {
+        this._actions = actions;
+        this._incomplete = incomplete;
+    }
+
+    // Static
+
+    static fromPayload(payload)
+    {
+        if (typeof payload !== "object" || payload === null)
+            payload = {};
+
+        if (!Array.isArray(payload.actions))
+            payload.actions = [];
+
+        let actions = payload.actions.map(WebInspector.RecordingAction.fromPayload);
+        return new WebInspector.RecordingFrame(actions, {
+            incomplete: !!payload.incomplete,
+        });
+    }
+
+    // Public
+
+    get actions() { return this._actions; }
+    get incomplete() { return this._incomplete; }
+
+    toJSON()
+    {
+        let json = {
+            actions: this._actions.map((action) => action.toJSON()),
+        };
+        if (this._incomplete)
+            json.incomplete = this._incomplete;
+        return json;
+    }
+};
index 1bd9a41..f7e1d55 100644 (file)
@@ -46,4 +46,9 @@ WebInspector.CanvasObserver = class CanvasObserver
     {
         WebInspector.canvasManager.cssCanvasClientNodesChanged(canvasId);
     }
+
+    recordingFinished(canvasId, recording)
+    {
+        WebInspector.canvasManager.recordingFinished(canvasId, recording);
+    }
 };
index 24435e4..6473ef5 100644 (file)
     <script src="Models/ProfileNodeCall.js"></script>
     <script src="Models/PropertyDescriptor.js"></script>
     <script src="Models/PropertyPreview.js"></script>
+    <script src="Models/Recording.js"></script>
+    <script src="Models/RecordingAction.js"></script>
+    <script src="Models/RecordingFrame.js"></script>
     <script src="Models/RenderingFrameTimelineRecord.js"></script>
     <script src="Models/Resource.js"></script>
     <script src="Models/ResourceCollection.js"></script>