https://bugs.webkit.org/show_bug.cgi?id=203651
<rdar://problem/
56128726>
Reviewed by Brian Burg.
Source/JavaScriptCore:
Unlike all other forms of Web Animations, CSS animations/transitions, are _not_ created by
JavaScript, and therefore can seemingly appear out of nowhere. This patch expands the Media
timeline to be the Media & Animations timeline, which tracks when CSS animations/transitions
are created, started, delayed, iterated, canceled, or finished.
* CMakeLists.txt:
* DerivedSources-input.xcfilelist:
* DerivedSources.make:
* inspector/protocol/Animation.json: Added.
* inspector/protocol/Timeline.json:
Add an Animation domain for handling the tracking of CSS Web Animations.
Source/WebCore:
Unlike all other forms of Web Animations, CSS animations/transitions, are _not_ created by
JavaScript, and therefore can seemingly appear out of nowhere. This patch expands the Media
timeline to be the Media & Animations timeline, which tracks when CSS animations/transitions
are created, started, delayed, iterated, canceled, or finished.
Test: inspector/animation/tracking.html
* animation/KeyframeEffect.cpp:
(WebCore::KeyframeEffect::apply):
* animation/WebAnimation.h:
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::~WebAnimation):
(WebCore::WebAnimation::contextDestroyed): Added.
(WebCore::WebAnimation::setEffectInternal):
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::willApplyKeyframeEffect): Added.
(WebCore::InspectorInstrumentation::didChangeWebAnimationEffect): Added.
(WebCore::InspectorInstrumentation::willDestroyWebAnimation): Added.
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::willApplyKeyframeEffectImpl): Added.
(WebCore::InspectorInstrumentation::didChangeWebAnimationEffectImpl): Added.
(WebCore::InspectorInstrumentation::willDestroyWebAnimationImpl): Added.
Add instrumentation points for CSS animations/transitions.
* inspector/agents/InspectorAnimationAgent.h: Added.
* inspector/agents/InspectorAnimationAgent.cpp: Added.
(WebCore::InspectorAnimationAgent::InspectorAnimationAgent):
(WebCore::InspectorAnimationAgent::didCreateFrontendAndBackend):
(WebCore::InspectorAnimationAgent::willDestroyFrontendAndBackend):
(WebCore::InspectorAnimationAgent::startTracking):
(WebCore::InspectorAnimationAgent::stopTracking):
(WebCore::isDelayed):
(WebCore::InspectorAnimationAgent::willApplyKeyframeEffect):
(WebCore::InspectorAnimationAgent::didChangeWebAnimationEffect):
(WebCore::InspectorAnimationAgent::willDestroyWebAnimation):
(WebCore::InspectorAnimationAgent::stopTrackingDeclarativeAnimation):
* inspector/InstrumentingAgents.h:
(WebCore::InstrumentingAgents::persistentInspectorAnimationAgent const): Added.
(WebCore::InstrumentingAgents::setPersistentInspectorAnimationAgent): Added.
(WebCore::InstrumentingAgents::trackingInspectorAnimationAgent const): Added.
(WebCore::InstrumentingAgents::setTrackingInspectorAnimationAgent): Added.
* inspector/InstrumentingAgents.cpp:
(WebCore::InstrumentingAgents::reset):
* inspector/InspectorController.cpp:
(WebCore::InspectorController::createLazyAgents):
Add an Animation domain for handling the tracking of CSS Web Animations.
* inspector/agents/InspectorTimelineAgent.h:
* inspector/agents/InspectorTimelineAgent.cpp:
(WebCore::InspectorTimelineAgent::toggleInstruments):
(WebCore::InspectorTimelineAgent::toggleAnimationInstrument): Added.
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
Source/WebInspectorUI:
Unlike all other forms of Web Animations, CSS animations/transitions, are _not_ created by
JavaScript, and therefore can seemingly appear out of nowhere. This patch expands the Media
timeline to be the Media & Animations timeline, which tracks when CSS animations/transitions
are created, started, delayed, iterated, canceled, or finished.
* UserInterface/Protocol/AnimationObserver.js: Added.
(WI.AnimationObserver.prototype.trackingStart):
(WI.AnimationObserver.prototype.trackingUpdate):
(WI.AnimationObserver.prototype.trackingComplete):
* UserInterface/Protocol/Target.js:
(WI.Target.prototype.get AnimationAgent): Added.
* UserInterface/Main.html:
* UserInterface/Base/Main.js:
(WI.loaded):
* UserInterface/Test.html:
* UserInterface/Test/Test.js:
(WI.loaded):
Add an Animation domain for handling the tracking of CSS Web Animations.
* UserInterface/Models/MediaInstrument.js:
(WI.MediaInstrument.prototype.startInstrumentation):
(WI.MediaInstrument.prototype.stopInstrumentation):
(WI.MediaInstrument):
* UserInterface/Models/MediaTimeline.js: Added.
(WI.MediaTimeline.prototype.recordForTrackingAnimationId):
(WI.MediaTimeline.prototype.recordForMediaElementEvents):
(WI.MediaTimeline.prototype.reset):
(WI.MediaTimeline.prototype.addRecord):
* UserInterface/Models/MediaTimelineRecord.js:
(WI.MediaTimelineRecord):
(WI.MediaTimelineRecord.async fromJSON):
(WI.MediaTimelineRecord.prototype.toJSON):
(WI.MediaTimelineRecord.prototype.get trackingAnimationId): Added.
(WI.MediaTimelineRecord.prototype.get timestamps): Added.
(WI.MediaTimelineRecord.prototype.get activeStartTime): Added.
(WI.MediaTimelineRecord.prototype.get updatesDynamically): Added.
(WI.MediaTimelineRecord.prototype.get usesActiveStartTime): Added.
(WI.MediaTimelineRecord.prototype.get displayName):
(WI.MediaTimelineRecord.prototype.get subtitle): Added.
(WI.MediaTimelineRecord.prototype.saveIdentityToCookie):
(WI.MediaTimelineRecord.prototype.updateProgress): Added.
(WI.MediaTimelineRecord.prototype.addDOMEvent): Added.
(WI.MediaTimelineRecord.prototype.powerEfficientPlaybackStateChanged): Added.
(WI.MediaTimelineRecord.prototype._updateTimes): Added.
(WI.MediaTimelineRecord.fromJSON): Deleted.
(WI.MediaTimelineRecord.prototype.get domEvent): Deleted.
(WI.MediaTimelineRecord.prototype.get isPowerEfficient): Deleted.
* UserInterface/Models/Timeline.js:
(WI.Timeline.create):
* UserInterface/Controllers/TimelineManager.js:
(WI.TimelineManager):
(WI.TimelineManager.prototype.async processJSON):
(WI.TimelineManager.prototype.animationTrackingStarted): Added.
(WI.TimelineManager.prototype.animationTrackingUpdated): Added.
(WI.TimelineManager.prototype.animationTrackingCompleted): Added.
(WI.TimelineManager.prototype._updateAutoCaptureInstruments):
(WI.TimelineManager.prototype._handleDOMNodeDidFireEvent):
(WI.TimelineManager.prototype._handleDOMNodePowerEfficientPlaybackStateChanged):
Start/Stop tracking animations based on whether the Media & Animations timeline is enabled.
* UserInterface/Views/MediaTimelineOverviewGraph.js:
(WI.MediaTimelineOverviewGraph):
(WI.MediaTimelineOverviewGraph.get maximumRowCount): Added.
(WI.MediaTimelineOverviewGraph.prototype.reset):
(WI.MediaTimelineOverviewGraph.prototype.layout):
(WI.MediaTimelineOverviewGraph.prototype.updateSelectedRecord):
(WI.MediaTimelineOverviewGraph.prototype._processRecord): Added.
(WI.MediaTimelineOverviewGraph.prototype._processRecord.compareByStartTime): Added.
(WI.MediaTimelineOverviewGraph.prototype._handleRecordAdded):
(WI.MediaTimelineOverviewGraph.prototype._handleTimesUpdated): Added.
(WI.MediaTimelineOverviewGraph.prototype.shown): Deleted.
(WI.MediaTimelineOverviewGraph.prototype.hidden): Deleted.
* UserInterface/Views/MediaTimelineOverviewGraph.css:
(.timeline-overview-graph.media): Added.
(.timeline-overview-graph.media > .graph-row): Added.
(.timeline-overview-graph.media > .graph-row > .timeline-record-bar): Added.
(.timeline-overview-graph.media > .graph-row > .timeline-record-bar:not(.unfinished) > .segment:not(.inactive)): Added.
(.timeline-overview-graph.media:nth-child(even) > .graph-row > .timeline-record-bar:not(.unfinished) > .segment:not(.inactive)): Added.
(.timeline-overview-graph.media > .timeline-record-bar): Deleted.
(.timeline-overview-graph.media > .timeline-record-bar > .segment): Deleted.
* UserInterface/Views/MediaTimelineView.js:
(WI.MediaTimelineView):
(WI.MediaTimelineView.prototype.shown): Added.
(WI.MediaTimelineView.prototype.hidden): Added.
(WI.MediaTimelineView.prototype.closed):
(WI.MediaTimelineView.prototype.reset):
(WI.MediaTimelineView.prototype.dataGridSortComparator):
(WI.MediaTimelineView.prototype.dataGridSortComparator.compareDOMNodes):
(WI.MediaTimelineView.prototype._processPendingRecords):
* UserInterface/Views/MediaTimelineDataGridNode.js:
(WI.MediaTimelineDataGridNode):
(WI.MediaTimelineDataGridNode.prototype.get data):
(WI.MediaTimelineDataGridNode.prototype.createCellContent):
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren): Added.
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addReadySegment): ADded.
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addDelaySegment): ADded.
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addActiveSegment): ADded.
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addFullScreenSegment): ADded.
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addPowerEfficientPlaybackSegment): ADded.
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addPausedSegment): ADded.
(WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addPlayingSegment): ADded.
(WI.MediaTimelineDataGridNode.prototype.filterableDataForColumn):
(WI.MediaTimelineDataGridNode.prototype._createNameCellDocumentFragment): Added.
(WI.MediaTimelineDataGridNode.prototype.iconClassNames): Deleted.
* UserInterface/Views/TimelineRecordBar.js:
(WI.TimelineRecordBar):
(WI.TimelineRecordBar.prototype.refresh):
(WI.TimelineRecordBar.prototype._handleClick):
* UserInterface/Views/TimelineRecordBar.css:
(.timeline-record-bar):
(.timeline-record-bar > :matches(img, .segment)):
(.timeline-record-bar > img):
(.timeline-record-bar > .segment):
(body[dir=ltr] .timeline-record-bar > .segment):
(body[dir=ltr] .timeline-record-bar > .segment:first-of-type):
(body[dir=ltr] .timeline-record-bar > .segment:last-of-type):
(body[dir=rtl] .timeline-record-bar > .segment):
(body[dir=rtl] .timeline-record-bar > .segment:first-of-type):
(body[dir=rtl] .timeline-record-bar > .segment:last-of-type):
(.timeline-record-bar > .segment:not(:last-of-type)):
(.timeline-record-bar.selected > .segment):
(.timeline-record-bar > .segment.inactive,):
(.timeline-record-bar.has-inactive-segment > .segment:not(.inactive)):
(:focus .selected .timeline-record-bar:not(.has-custom-children) > .segment):
(:focus .selected .timeline-record-bar:not(.has-custom-children) > .segment.inactive):
(:focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive)):
(.timeline-record-bar.timeline-record-type-network > .segment):
(.timeline-record-bar.timeline-record-type-network > .segment.inactive):
(.timeline-record-bar.timeline-record-type-layout > .segment):
(.timeline-record-bar.timeline-record-type-layout.paint > .segment,):
(.timeline-record-bar.timeline-record-type-script > .segment):
(.timeline-record-bar.timeline-record-type-script.garbage-collected > .segment,):
(.timeline-record-bar.timeline-record-type-media > .segment):
(.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment):
(.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment.css-animation-ready):
(.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment:matches(.css-animation-delay, .media-element-paused)):
(.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment):
(.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment.media-element-full-screen):
(.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment.media-element-power-efficient-playback):
(body[dir=ltr] .timeline-record-bar > .segment.inactive,): Deleted.
(body[dir=ltr] .timeline-record-bar.has-inactive-segment > .segment:not(.inactive),): Deleted.
(:focus .selected .timeline-record-bar > .segment): Deleted.
(:focus .selected .timeline-record-bar > .segment.inactive): Deleted.
(body[dir=ltr] :focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive)): Deleted.
(body[dir=rtl] :focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive)): Deleted.
Allow timeline record bars to be customized through a delegate callback. If provided, it
will be used instead of any default content. It is expected to return an array of objects,
each having a `startTime` number, `classNames` array, and `title` string. It can also have a
`endTime` number and an `image` string. If `endTime` is `NaN`, the record is unfinished. If
`image` is provided, an `<img>` will be used instead of a segment, allowing for markers.
* UserInterface/Views/TimelineDataGridNode.js:
(WI.TimelineDataGridNode.prototype.createCellContent):
Add a default fallback for `WI.DOMNode` values.
* UserInterface/Views/TimelineTabContentView.js:
(WI.TimelineTabContentView.displayNameForTimelineType):
(WI.TimelineTabContentView.iconClassNameForRecord):
(WI.TimelineTabContentView.displayNameForRecord):
* UserInterface/Views/TimelineRecordTreeElement.js:
* UserInterface/Views/TimelineIcons.css:
(.animation-frame-record .icon):
(.css-animation-record .icon): Added.
(.css-transition-record .icon): Added.
(.media-element-record .icon): Added.
(.animation-record .icon): Deleted.
(.dom-event-record .icon): Deleted.
(.dom-event-record.fullscreen .icon): Deleted.
(.power-efficient-playback-state-changed-record .icon): Deleted.
* UserInterface/Images/DOMEventFullscreen.svg: Removed.
* UserInterface/Images/EventCancel.svg: Added.
* UserInterface/Images/EventIteration.svg: Added.
* UserInterface/Images/EventPause.svg:
* UserInterface/Images/EventPlay.svg:
* UserInterface/Images/EventProcessing.svg:
* UserInterface/Images/EventStop.svg:
* UserInterface/Images/PowerEfficientPlaybackStateChanged.svg: Removed.
* UserInterface/Images/TimelineRecordAnimationFrame.svg: Renamed from Source/WebInspectorUI/UserInterface/Images/TimelineRecordAnimation.svg.
* UserInterface/Images/TimelineRecordCSSAnimation.svg: Added.
* UserInterface/Images/TimelineRecordCSSTransition.svg: Added.
* UserInterface/Images/TimelineRecordMediaElement.svg: Added.
Add new media icons.
* UserInterface/Models/TimelineRecording.js:
(WI.TimelineRecording.async import): Added.
(WI.TimelineRecording.import): Deleted.
* UserInterface/Models/TimelineRecord.js:
* UserInterface/Models/CPUTimelineRecord.js:
* UserInterface/Models/HeapAllocationsTimelineRecord.js:
(WI.HeapAllocationsTimelineRecord.async fromJSON): Added.
(WI.HeapAllocationsTimelineRecord.fromJSON): Deleted.
* UserInterface/Models/LayoutTimelineRecord.js:
* UserInterface/Models/MemoryTimelineRecord.js:
* UserInterface/Models/RenderingFrameTimelineRecord.js:
* UserInterface/Models/ResourceTimelineRecord.js:
* UserInterface/Models/ScriptTimelineRecord.js:
Make the importing of timeline records `async` so we can attempt to rehydrate the DOM nodes
of any media records (as well as wait for heap snapshots).
* UserInterface/Models/DOMNode.js:
(WI.DOMNode):
(WI.DOMNode.prototype.isMediaElement): Added.
(WI.DOMNode.prototype._shouldListenForEventListeners): Deleted.
* Localizations/en.lproj/localizedStrings.js:
Source/WTF:
* wtf/Markable.h:
(WTF::operator==):
(WTF::operator!=):
Add extra utility operators.
Tools:
* TestWebKitAPI/Tests/WTF/Markable.cpp:
(TestWebKitAPI::TEST):
Add tests for extra utility operators.
LayoutTests:
* inspector/animation/tracking.html: Added.
* inspector/animation/tracking-expected.txt: Added.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@251959
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2019-11-01 Devin Rousso <drousso@apple.com>
+
+ Web Inspector: Timelines: add a timeline that shows information about any recorded CSS animation/transition
+ https://bugs.webkit.org/show_bug.cgi?id=203651
+ <rdar://problem/56128726>
+
+ Reviewed by Brian Burg.
+
+ * inspector/animation/tracking.html: Added.
+ * inspector/animation/tracking-expected.txt: Added.
+
2019-11-01 Said Abou-Hallawa <sabouhallawa@apple.com>
SVG pair properties must be detached from their owner before it's deleted
--- /dev/null
+Tests that Animation.startTracking and Animation.stopTracking trigger Animation.trackingStart, Animation.trackingUpdate, and Animation.trackingComplete events with expected data.
+
+
+== Running test suite: Animation.Tracking
+-- Running test case: Animation.Tracking.StartAndStopTrackingWithEvent
+Animation.trackingStart
+PASS: Should have a timestamp when starting.
+Animation.trackingUpdate
+PASS: Should should have a timestamp number.
+PASS: Should have an event object.
+PASS: Event should have an animation tracking ID.
+PASS: Event should have an animation state.
+PASS: Event should have a node ID.
+PASS: Event should have the related CSS animation's name.
+Animation.trackingComplete
+
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<style>
+@keyframes test {
+ to { opacity: 0; }
+}
+.test {
+ animation: test 1s linear;
+}
+</style>
+<script>
+function startCSSAnimation() {
+ let element = document.body.appendChild(document.createElement("div"));
+ element.className = "test";
+}
+
+function test()
+{
+ let suite = ProtocolTest.createAsyncSuite("Animation.Tracking");
+
+ InspectorProtocol.sendCommand("DOM.getDocument");
+
+ suite.addTestCase({
+ name: "Animation.Tracking.StartAndStopTrackingWithEvent",
+ test(resolve, reject) {
+ InspectorProtocol.awaitEvent({event: "Animation.trackingStart"}).then((messageObject) => {
+ ProtocolTest.log("Animation.trackingStart");
+ ProtocolTest.expectEqual(typeof messageObject.params.timestamp, "number", "Should have a timestamp when starting.");
+
+ ProtocolTest.evaluateInPage("startCSSAnimation()");
+ });
+
+ InspectorProtocol.awaitEvent({event: "Animation.trackingUpdate"}).then((messageObject) => {
+ ProtocolTest.log("Animation.trackingUpdate");
+ ProtocolTest.expectEqual(typeof messageObject.params.timestamp, "number", "Should should have a timestamp number.");
+ ProtocolTest.expectEqual(typeof messageObject.params.event, "object", "Should have an event object.");
+ ProtocolTest.expectEqual(typeof messageObject.params.event.trackingAnimationId, "string", "Event should have an animation tracking ID.");
+ ProtocolTest.expectEqual(typeof messageObject.params.event.animationState, "string", "Event should have an animation state.");
+ ProtocolTest.expectEqual(typeof messageObject.params.event.nodeId, "number", "Event should have a node ID.");
+ ProtocolTest.expectEqual(messageObject.params.event.animationName, "test", "Event should have the related CSS animation's name.");
+
+ ProtocolTest.assert(typeof messageObject.params.event.transitionProperty === "undefined", "Event should not have a `transitionProperty` if it has an `animationName`.");
+
+ InspectorProtocol.sendCommand("Animation.stopTracking", {});
+ });
+
+ InspectorProtocol.awaitEvent({event: "Animation.trackingComplete"}).then((messageObject) => {
+ ProtocolTest.log("Animation.trackingComplete");
+ resolve();
+ });
+
+ InspectorProtocol.sendCommand("Animation.startTracking", {});
+ }
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Tests that Animation.startTracking and Animation.stopTracking trigger Animation.trackingStart, Animation.trackingUpdate, and Animation.trackingComplete events with expected data.</p>
+</body>
+</html>
)
set(JavaScriptCore_INSPECTOR_DOMAINS
+ ${JAVASCRIPTCORE_DIR}/inspector/protocol/Animation.json
${JAVASCRIPTCORE_DIR}/inspector/protocol/ApplicationCache.json
${JAVASCRIPTCORE_DIR}/inspector/protocol/Audit.json
${JAVASCRIPTCORE_DIR}/inspector/protocol/CSS.json
+2019-11-01 Devin Rousso <drousso@apple.com>
+
+ Web Inspector: Timelines: add a timeline that shows information about any recorded CSS animation/transition
+ https://bugs.webkit.org/show_bug.cgi?id=203651
+ <rdar://problem/56128726>
+
+ Reviewed by Brian Burg.
+
+ Unlike all other forms of Web Animations, CSS animations/transitions, are _not_ created by
+ JavaScript, and therefore can seemingly appear out of nowhere. This patch expands the Media
+ timeline to be the Media & Animations timeline, which tracks when CSS animations/transitions
+ are created, started, delayed, iterated, canceled, or finished.
+
+ * CMakeLists.txt:
+ * DerivedSources-input.xcfilelist:
+ * DerivedSources.make:
+ * inspector/protocol/Animation.json: Added.
+ * inspector/protocol/Timeline.json:
+ Add an Animation domain for handling the tracking of CSS Web Animations.
+
2019-11-01 Saam Barati <sbarati@apple.com>
Refactor uses of StructureStubInfo 'thisGPR' to a union for thisGPR and prototypeGPR
$(PROJECT_DIR)/generator/Wasm.rb
$(PROJECT_DIR)/generator/main.rb
$(PROJECT_DIR)/inspector/InjectedScriptSource.js
+$(PROJECT_DIR)/inspector/protocol/Animation.json
$(PROJECT_DIR)/inspector/protocol/ApplicationCache.json
$(PROJECT_DIR)/inspector/protocol/Audit.json
$(PROJECT_DIR)/inspector/protocol/CPUProfiler.json
# Inspector interfaces
INSPECTOR_DOMAINS := \
+ $(JavaScriptCore)/inspector/protocol/Animation.json \
$(JavaScriptCore)/inspector/protocol/ApplicationCache.json \
$(JavaScriptCore)/inspector/protocol/Audit.json \
$(JavaScriptCore)/inspector/protocol/CSS.json \
--- /dev/null
+{
+ "domain": "Animation",
+ "description": "Domain for tracking/modifying Web Animations, as well as CSS (declarative) animations and transitions.",
+ "debuggableTypes": ["page", "web-page"],
+ "targetTypes": ["page"],
+ "types": [
+ {
+ "id": "AnimationId",
+ "type": "string",
+ "description": "Unique Web Animation identifier."
+ },
+ {
+ "id": "AnimationState",
+ "type": "string",
+ "enum": ["ready", "delayed", "active", "canceled", "done"]
+ },
+ {
+ "id": "TrackingUpdate",
+ "type": "object",
+ "properties": [
+ { "name": "trackingAnimationId", "$ref": "AnimationId" },
+ { "name": "animationState", "$ref": "AnimationState" },
+ { "name": "nodeId", "$ref": "DOM.NodeId", "optional": true },
+ { "name": "animationName", "type": "string", "optional": true, "description": "Equal to the corresponding `animation-name` CSS property. Should not be provided if `transitionProperty` is also provided." },
+ { "name": "transitionProperty", "type": "string", "optional": true, "description": "Equal to the corresponding `transition-property` CSS property. Should not be provided if `animationName` is also provided." }
+ ]
+ }
+ ],
+ "commands": [
+ {
+ "name": "startTracking",
+ "description": "Start tracking animations. This will produce a `trackingStart` event."
+ },
+ {
+ "name": "stopTracking",
+ "description": "Stop tracking animations. This will produce a `trackingComplete` event."
+ }
+ ],
+ "events": [
+ {
+ "name": "trackingStart",
+ "description": "Dispatched after `startTracking` command.",
+ "parameters": [
+ { "name": "timestamp", "type": "number" }
+ ]
+ },
+ {
+ "name": "trackingUpdate",
+ "description": "Fired for each phase of Web Animation.",
+ "parameters": [
+ { "name": "timestamp", "type": "number" },
+ { "name": "event", "$ref": "TrackingUpdate" }
+ ]
+ },
+ {
+ "name": "trackingComplete",
+ "description": "Dispatched after `stopTracking` command.",
+ "parameters": [
+ { "name": "timestamp", "type": "number" }
+ ]
+ }
+ ]
+}
"Timeline",
"CPU",
"Memory",
- "Heap"
+ "Heap",
+ "Animation"
]
},
{
+2019-11-01 Devin Rousso <drousso@apple.com>
+
+ Web Inspector: Timelines: add a timeline that shows information about any recorded CSS animation/transition
+ https://bugs.webkit.org/show_bug.cgi?id=203651
+ <rdar://problem/56128726>
+
+ Reviewed by Brian Burg.
+
+ * wtf/Markable.h:
+ (WTF::operator==):
+ (WTF::operator!=):
+ Add extra utility operators.
+
2019-10-31 Tim Horton <timothy_horton@apple.com>
Turn on IOSurface support in the iOS Simulator
T m_value;
};
+template <typename T, typename Traits> constexpr bool operator==(const Markable<T, Traits>& x, const Markable<T, Traits>& y)
+{
+ if (bool(x) != bool(y))
+ return false;
+ if (!bool(x))
+ return true;
+ return x.value() == y.value();
+}
+template <typename T, typename Traits> constexpr bool operator==(const Markable<T, Traits>& x, const T& v) { return bool(x) && x.value() == v; }
+template <typename T, typename Traits> constexpr bool operator==(const T& v, const Markable<T, Traits>& x) { return bool(x) && v == x.value(); }
+
+template <typename T, typename Traits> constexpr bool operator!=(const Markable<T, Traits>& x, const Markable<T, Traits>& y) { return !(x == y); }
+template <typename T, typename Traits> constexpr bool operator!=(const Markable<T, Traits>& x, const T& v) { return !(x == v); }
+template <typename T, typename Traits> constexpr bool operator!=(const T& v, const Markable<T, Traits>& x) { return !(v == x); }
+
} // namespace WTF
using WTF::Markable;
+2019-11-01 Devin Rousso <drousso@apple.com>
+
+ Web Inspector: Timelines: add a timeline that shows information about any recorded CSS animation/transition
+ https://bugs.webkit.org/show_bug.cgi?id=203651
+ <rdar://problem/56128726>
+
+ Reviewed by Brian Burg.
+
+ Unlike all other forms of Web Animations, CSS animations/transitions, are _not_ created by
+ JavaScript, and therefore can seemingly appear out of nowhere. This patch expands the Media
+ timeline to be the Media & Animations timeline, which tracks when CSS animations/transitions
+ are created, started, delayed, iterated, canceled, or finished.
+
+ Test: inspector/animation/tracking.html
+
+ * animation/KeyframeEffect.cpp:
+ (WebCore::KeyframeEffect::apply):
+ * animation/WebAnimation.h:
+ * animation/WebAnimation.cpp:
+ (WebCore::WebAnimation::~WebAnimation):
+ (WebCore::WebAnimation::contextDestroyed): Added.
+ (WebCore::WebAnimation::setEffectInternal):
+ * inspector/InspectorInstrumentation.h:
+ (WebCore::InspectorInstrumentation::willApplyKeyframeEffect): Added.
+ (WebCore::InspectorInstrumentation::didChangeWebAnimationEffect): Added.
+ (WebCore::InspectorInstrumentation::willDestroyWebAnimation): Added.
+ * inspector/InspectorInstrumentation.cpp:
+ (WebCore::InspectorInstrumentation::willApplyKeyframeEffectImpl): Added.
+ (WebCore::InspectorInstrumentation::didChangeWebAnimationEffectImpl): Added.
+ (WebCore::InspectorInstrumentation::willDestroyWebAnimationImpl): Added.
+ Add instrumentation points for CSS animations/transitions.
+
+ * inspector/agents/InspectorAnimationAgent.h: Added.
+ * inspector/agents/InspectorAnimationAgent.cpp: Added.
+ (WebCore::InspectorAnimationAgent::InspectorAnimationAgent):
+ (WebCore::InspectorAnimationAgent::didCreateFrontendAndBackend):
+ (WebCore::InspectorAnimationAgent::willDestroyFrontendAndBackend):
+ (WebCore::InspectorAnimationAgent::startTracking):
+ (WebCore::InspectorAnimationAgent::stopTracking):
+ (WebCore::isDelayed):
+ (WebCore::InspectorAnimationAgent::willApplyKeyframeEffect):
+ (WebCore::InspectorAnimationAgent::didChangeWebAnimationEffect):
+ (WebCore::InspectorAnimationAgent::willDestroyWebAnimation):
+ (WebCore::InspectorAnimationAgent::stopTrackingDeclarativeAnimation):
+ * inspector/InstrumentingAgents.h:
+ (WebCore::InstrumentingAgents::persistentInspectorAnimationAgent const): Added.
+ (WebCore::InstrumentingAgents::setPersistentInspectorAnimationAgent): Added.
+ (WebCore::InstrumentingAgents::trackingInspectorAnimationAgent const): Added.
+ (WebCore::InstrumentingAgents::setTrackingInspectorAnimationAgent): Added.
+ * inspector/InstrumentingAgents.cpp:
+ (WebCore::InstrumentingAgents::reset):
+ * inspector/InspectorController.cpp:
+ (WebCore::InspectorController::createLazyAgents):
+ Add an Animation domain for handling the tracking of CSS Web Animations.
+
+ * inspector/agents/InspectorTimelineAgent.h:
+ * inspector/agents/InspectorTimelineAgent.cpp:
+ (WebCore::InspectorTimelineAgent::toggleInstruments):
+ (WebCore::InspectorTimelineAgent::toggleAnimationInstrument): Added.
+
+ * Sources.txt:
+ * WebCore.xcodeproj/project.pbxproj:
+
2019-11-01 Said Abou-Hallawa <sabouhallawa@apple.com>
SVG pair properties must be detached from their owner before it's deleted
inspector/WorkerInspectorController.cpp
inspector/WorkerScriptDebugServer.cpp
+inspector/agents/InspectorAnimationAgent.cpp
inspector/agents/InspectorApplicationCacheAgent.cpp
inspector/agents/InspectorCPUProfilerAgent.cpp
inspector/agents/InspectorCSSAgent.cpp
9109E9C8222CABCA00BB6265 /* JSInspectorAuditResourcesObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 9109E9C5222CABC800BB6265 /* JSInspectorAuditResourcesObject.h */; };
91278D5E21DEDAD600B57184 /* PageAuditAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 91278D5C21DEDAD500B57184 /* PageAuditAgent.h */; };
91278D6221DEDAF000B57184 /* WorkerAuditAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 91278D6021DEDAF000B57184 /* WorkerAuditAgent.h */; };
+ 913FE59C2362799A00F9446A /* InspectorAnimationAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 913FE59A2362799900F9446A /* InspectorAnimationAgent.h */; };
9175CE5C21E281ED00DF2C27 /* InspectorAuditDOMObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 9175CE5821E281EC00DF2C27 /* InspectorAuditDOMObject.h */; };
9175CE5C21E281ED00DF2C28 /* JSInspectorAuditDOMObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 9175CE5821E281EC00DF2C28 /* JSInspectorAuditDOMObject.h */; };
9175CE5E21E281ED00DF2C27 /* InspectorAuditAccessibilityObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 9175CE5A21E281ED00DF2C27 /* InspectorAuditAccessibilityObject.h */; };
91278D5C21DEDAD500B57184 /* PageAuditAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PageAuditAgent.h; sourceTree = "<group>"; };
91278D5F21DEDAEF00B57184 /* WorkerAuditAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WorkerAuditAgent.cpp; sourceTree = "<group>"; };
91278D6021DEDAF000B57184 /* WorkerAuditAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WorkerAuditAgent.h; sourceTree = "<group>"; };
+ 913FE5982362799900F9446A /* InspectorAnimationAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = InspectorAnimationAgent.cpp; path = inspector/agents/InspectorAnimationAgent.cpp; sourceTree = SOURCE_ROOT; };
+ 913FE59A2362799900F9446A /* InspectorAnimationAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = InspectorAnimationAgent.h; path = inspector/agents/InspectorAnimationAgent.h; sourceTree = SOURCE_ROOT; };
9175CE5721E281EB00DF2C27 /* InspectorAuditDOMObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorAuditDOMObject.cpp; sourceTree = "<group>"; };
9175CE5721E281EB00DF2C28 /* JSInspectorAuditDOMObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSInspectorAuditDOMObject.cpp; sourceTree = "<group>"; };
9175CE5821E281EC00DF2C27 /* InspectorAuditDOMObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorAuditDOMObject.h; sourceTree = "<group>"; };
CD4E4E722357B558007895C3 /* JSMediaCapabilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSMediaCapabilities.cpp; sourceTree = "<group>"; };
CD4E4E7C2357B5FD007895C3 /* JSAudioConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSAudioConfiguration.h; sourceTree = "<group>"; };
CD4E4E7D2357B5FD007895C3 /* JSMediaCapabilitiesInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSMediaCapabilitiesInfo.h; sourceTree = "<group>"; };
- CD4E4E7E2357B5FD007895C3 /* JSMediaConfiguration.dep */ = {isa = PBXFileReference; lastKnownFileType = file; path = JSMediaConfiguration.dep; sourceTree = "<group>"; };
+ CD4E4E7E2357B5FD007895C3 /* JSMediaConfiguration.dep */ = {isa = PBXFileReference; lastKnownFileType = text; path = JSMediaConfiguration.dep; sourceTree = "<group>"; };
CD4E4E7F2357B5FD007895C3 /* JSTransferFunction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSTransferFunction.h; sourceTree = "<group>"; };
CD4E4E802357B5FE007895C3 /* JSMediaEncodingConfiguration.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSMediaEncodingConfiguration.cpp; sourceTree = "<group>"; };
CD4E4E812357B5FE007895C3 /* JSHdrMetadataType.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSHdrMetadataType.cpp; sourceTree = "<group>"; };
children = (
A5B81CB91FAA44820037D1E6 /* page */,
A5B81CC61FAA44C30037D1E6 /* worker */,
+ 913FE5982362799900F9446A /* InspectorAnimationAgent.cpp */,
+ 913FE59A2362799900F9446A /* InspectorAnimationAgent.h */,
A5B81CA01FAA44270037D1E6 /* InspectorApplicationCacheAgent.cpp */,
A5B81C971FAA44260037D1E6 /* InspectorApplicationCacheAgent.h */,
A5B81C961FAA44260037D1E6 /* InspectorCanvasAgent.cpp */,
93309DEE099E64920056E581 /* InsertNodeBeforeCommand.h in Headers */,
93309DF0099E64920056E581 /* InsertParagraphSeparatorCommand.h in Headers */,
93309DF2099E64920056E581 /* InsertTextCommand.h in Headers */,
+ 913FE59C2362799A00F9446A /* InspectorAnimationAgent.h in Headers */,
A5B81CA71FAA44620037D1E6 /* InspectorApplicationCacheAgent.h in Headers */,
9175CE5E21E281ED00DF2C27 /* InspectorAuditAccessibilityObject.h in Headers */,
9175CE5C21E281ED00DF2C27 /* InspectorAuditDOMObject.h in Headers */,
BCE1C43C0D9830D3003B02F2 /* JSLocation.h in Headers */,
93A806201E03B585008A1F26 /* JSLongRange.h in Headers */,
CDAB6D2E17C814EE00C60B34 /* JSMediaControlsHost.h in Headers */,
- E4ABAC06236B016D00FA4345 /* StyleBuilder.h in Headers */,
159741DB1B7D140100201C92 /* JSMediaDeviceInfo.h in Headers */,
15739BBB1B42012D00D258C1 /* JSMediaDevices.h in Headers */,
FD23A12613F5FA5900F67001 /* JSMediaElementAudioSourceNode.h in Headers */,
414B82051D6DF0E50077EBE3 /* StructuredClone.h in Headers */,
BC5EB6A30E81DC4F00B25965 /* StyleBackgroundData.h in Headers */,
BC5EB67B0E81D3BE00B25965 /* StyleBoxData.h in Headers */,
+ E4ABAC06236B016D00FA4345 /* StyleBuilder.h in Headers */,
83B9687B19F8AB83004EF7AF /* StyleBuilderConverter.h in Headers */,
835D363719FF6193004C93AB /* StyleBuilderCustom.h in Headers */,
+ E4ABAC04236AE95900FA4345 /* StyleBuilderGenerated.h in Headers */,
E4ABABF32368B95900FA4345 /* StyleBuilderState.h in Headers */,
BCEF444D0E674628001C1287 /* StyleCachedImage.h in Headers */,
E401E0A41C3C0B8300F34D10 /* StyleChange.h in Headers */,
0854B0231255E4E600B9CDD0 /* SVGRootInlineBox.h in Headers */,
B2227AA30D00BF220071B782 /* SVGScriptElement.h in Headers */,
B2227AA60D00BF220071B782 /* SVGSetElement.h in Headers */,
- E4ABAC04236AE95900FA4345 /* StyleBuilderGenerated.h in Headers */,
E4AFD0100DAF335500F5F55C /* SVGSMILElement.h in Headers */,
B2227AA90D00BF220071B782 /* SVGStopElement.h in Headers */,
B2227AAC0D00BF220071B782 /* SVGStringList.h in Headers */,
#include "FontCascade.h"
#include "FrameView.h"
#include "GeometryUtilities.h"
+#include "InspectorInstrumentation.h"
#include "JSCompositeOperation.h"
#include "JSCompositeOperationOrAuto.h"
#include "JSDOMConvert.h"
updateAcceleratedAnimationState();
- auto progress = getComputedTiming().progress;
- if (!progress)
+ auto computedTiming = getComputedTiming();
+
+ InspectorInstrumentation::willApplyKeyframeEffect(*m_target, *this, computedTiming);
+
+ if (!computedTiming.progress)
return;
- setAnimatedPropertiesInStyle(targetStyle, progress.value());
+ setAnimatedPropertiesInStyle(targetStyle, computedTiming.progress.value());
// https://w3c.github.io/web-animations/#side-effects-section
// For every property targeted by at least one animation effect that is current or in effect, the user agent
#include "Document.h"
#include "DocumentTimeline.h"
#include "EventNames.h"
+#include "InspectorInstrumentation.h"
#include "JSWebAnimation.h"
#include "KeyframeEffect.h"
#include "Microtasks.h"
WebAnimation::~WebAnimation()
{
+ InspectorInstrumentation::willDestroyWebAnimation(*this);
+
if (m_timeline)
m_timeline->forgetAnimation(this);
}
+void WebAnimation::contextDestroyed()
+{
+ InspectorInstrumentation::willDestroyWebAnimation(*this);
+
+ ActiveDOMObject::contextDestroyed();
+}
+
void WebAnimation::remove()
{
// This object could be deleted after either clearing the effect or timeline relationship.
if (m_timeline && newTarget && previousTarget != newTarget)
m_timeline->animationWasAddedToElement(*this, *newTarget);
}
+
+ InspectorInstrumentation::didChangeWebAnimationEffect(*this);
}
void WebAnimation::setTimeline(RefPtr<AnimationTimeline>&& timeline)
bool hasPendingActivity() const final;
+ // ContextDestructionObserver.
+ ScriptExecutionContext* scriptExecutionContext() const final { return ActiveDOMObject::scriptExecutionContext(); }
+ void contextDestroyed() final;
+
using RefCounted::ref;
using RefCounted::deref;
EventTargetInterface eventTargetInterface() const final { return WebAnimationEventTargetInterfaceType; }
void refEventTarget() final { ref(); }
void derefEventTarget() final { deref(); }
- ScriptExecutionContext* scriptExecutionContext() const final { return ActiveDOMObject::scriptExecutionContext(); }
};
} // namespace WebCore
#include "DOMWrapperWorld.h"
#include "Frame.h"
#include "GraphicsContext.h"
+#include "InspectorAnimationAgent.h"
#include "InspectorApplicationCacheAgent.h"
#include "InspectorCPUProfilerAgent.h"
#include "InspectorCSSAgent.h"
m_agents.append(makeUnique<PageAuditAgent>(pageContext));
m_agents.append(makeUnique<InspectorCanvasAgent>(pageContext));
m_agents.append(makeUnique<InspectorTimelineAgent>(pageContext));
+ m_agents.append(makeUnique<InspectorAnimationAgent>(pageContext));
if (auto& commandLineAPIHost = m_injectedScriptManager->commandLineAPIHost())
commandLineAPIHost->init(m_instrumentingAgents.copyRef());
#include "InspectorInstrumentation.h"
#include "CachedResource.h"
+#include "ComputedEffectTiming.h"
#include "CustomHeaderFields.h"
#include "DOMWindow.h"
#include "DOMWrapperWorld.h"
#include "InspectorTimelineAgent.h"
#include "InspectorWorkerAgent.h"
#include "InstrumentingAgents.h"
+#include "KeyframeEffect.h"
#include "LoaderStrategy.h"
#include "PageDOMDebuggerAgent.h"
#include "PageDebuggerAgent.h"
}
#endif
+void InspectorInstrumentation::willApplyKeyframeEffectImpl(InstrumentingAgents& instrumentingAgents, Element& target, KeyframeEffect& effect, ComputedEffectTiming computedTiming)
+{
+ if (auto* animationAgent = instrumentingAgents.trackingInspectorAnimationAgent())
+ animationAgent->willApplyKeyframeEffect(target, effect, computedTiming);
+}
+
+void InspectorInstrumentation::didChangeWebAnimationEffectImpl(InstrumentingAgents& instrumentingAgents, WebAnimation& animation)
+{
+ if (auto* animationAgent = instrumentingAgents.trackingInspectorAnimationAgent())
+ animationAgent->didChangeWebAnimationEffect(animation);
+}
+
+void InspectorInstrumentation::willDestroyWebAnimationImpl(InstrumentingAgents& instrumentingAgents, WebAnimation& animation)
+{
+ if (auto* animationAgent = instrumentingAgents.trackingInspectorAnimationAgent())
+ animationAgent->willDestroyWebAnimation(animation);
+}
+
#if ENABLE(RESOURCE_USAGE)
void InspectorInstrumentation::didHandleMemoryPressureImpl(InstrumentingAgents& instrumentingAgents, Critical critical)
{
#include "CanvasBase.h"
#include "CanvasRenderingContext.h"
#include "Database.h"
+#include "DeclarativeAnimation.h"
#include "DocumentThreadableLoader.h"
#include "Element.h"
#include "EventTarget.h"
#include "InspectorInstrumentationPublic.h"
#include "Page.h"
#include "StorageArea.h"
+#include "WebAnimation.h"
#include <JavaScriptCore/ConsoleMessage.h>
#include <initializer_list>
#include <wtf/CompletionHandler.h>
class HTTPHeaderMap;
class InspectorTimelineAgent;
class InstrumentingAgents;
+class KeyframeEffect;
class NetworkLoadMetrics;
class Node;
class PseudoElement;
enum class StorageType;
+struct ComputedEffectTiming;
struct WebSocketFrame;
class InspectorInstrumentation {
static void willDestroyWebGPUPipeline(WebGPUPipeline&);
#endif
+ static void willApplyKeyframeEffect(Element&, KeyframeEffect&, ComputedEffectTiming);
+ static void didChangeWebAnimationEffect(WebAnimation&);
+ static void willDestroyWebAnimation(WebAnimation&);
+
static void networkStateChanged(Page&);
static void updateApplicationCacheStatus(Frame*);
static InstrumentingAgents* instrumentingAgentsForWebGPUDevice(WebGPUDevice&);
#endif
+ static void willApplyKeyframeEffectImpl(InstrumentingAgents&, Element&, KeyframeEffect&, ComputedEffectTiming);
+ static void didChangeWebAnimationEffectImpl(InstrumentingAgents&, WebAnimation&);
+ static void willDestroyWebAnimationImpl(InstrumentingAgents&, WebAnimation&);
+
static void layerTreeDidChangeImpl(InstrumentingAgents&);
static void renderLayerDestroyedImpl(InstrumentingAgents&, const RenderLayer&);
}
#endif
+inline void InspectorInstrumentation::willApplyKeyframeEffect(Element& target, KeyframeEffect& effect, ComputedEffectTiming computedTiming)
+{
+ FAST_RETURN_IF_NO_FRONTENDS(void());
+ if (auto* instrumentingAgents = instrumentingAgentsForDocument(target.document()))
+ willApplyKeyframeEffectImpl(*instrumentingAgents, target, effect, computedTiming);
+}
+
+inline void InspectorInstrumentation::didChangeWebAnimationEffect(WebAnimation& animation)
+{
+ FAST_RETURN_IF_NO_FRONTENDS(void());
+ if (auto* instrumentingAgents = instrumentingAgentsForContext(animation.scriptExecutionContext()))
+ didChangeWebAnimationEffectImpl(*instrumentingAgents, animation);
+}
+
+inline void InspectorInstrumentation::willDestroyWebAnimation(WebAnimation& animation)
+{
+ FAST_RETURN_IF_NO_FRONTENDS(void());
+ if (auto* instrumentingAgents = instrumentingAgentsForContext(animation.scriptExecutionContext()))
+ willDestroyWebAnimationImpl(*instrumentingAgents, animation);
+}
+
inline void InspectorInstrumentation::networkStateChanged(Page& page)
{
FAST_RETURN_IF_NO_FRONTENDS(void());
m_inspectorDOMDebuggerAgent = nullptr;
m_pageDOMDebuggerAgent = nullptr;
m_inspectorCanvasAgent = nullptr;
+ m_persistentInspectorAnimationAgent = nullptr;
+ m_trackingInspectorAnimationAgent = nullptr;
}
} // namespace WebCore
namespace WebCore {
+class InspectorAnimationAgent;
class InspectorApplicationCacheAgent;
class InspectorCPUProfilerAgent;
class InspectorCSSAgent;
InspectorWorkerAgent* inspectorWorkerAgent() const { return m_inspectorWorkerAgent; }
void setInspectorWorkerAgent(InspectorWorkerAgent* agent) { m_inspectorWorkerAgent = agent; }
+ InspectorAnimationAgent* persistentInspectorAnimationAgent() const { return m_persistentInspectorAnimationAgent; }
+ void setPersistentInspectorAnimationAgent(InspectorAnimationAgent* agent) { m_persistentInspectorAnimationAgent = agent; }
+
+ InspectorAnimationAgent* trackingInspectorAnimationAgent() const { return m_trackingInspectorAnimationAgent; }
+ void setTrackingInspectorAnimationAgent(InspectorAnimationAgent* agent) { m_trackingInspectorAnimationAgent = agent; }
+
private:
InstrumentingAgents(Inspector::InspectorEnvironment&);
InspectorDOMDebuggerAgent* m_inspectorDOMDebuggerAgent { nullptr };
PageDOMDebuggerAgent* m_pageDOMDebuggerAgent { nullptr };
InspectorCanvasAgent* m_inspectorCanvasAgent { nullptr };
+ InspectorAnimationAgent* m_persistentInspectorAnimationAgent { nullptr };
+ InspectorAnimationAgent* m_trackingInspectorAnimationAgent { nullptr };
};
} // namespace WebCore
--- /dev/null
+/*
+ * Copyright (C) 2016 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 "InspectorAnimationAgent.h"
+
+#include "AnimationEffect.h"
+#include "AnimationEffectPhase.h"
+#include "CSSAnimation.h"
+#include "CSSTransition.h"
+#include "DeclarativeAnimation.h"
+#include "Element.h"
+#include "Event.h"
+#include "InspectorDOMAgent.h"
+#include "InstrumentingAgents.h"
+#include "KeyframeEffect.h"
+#include "WebAnimation.h"
+#include <JavaScriptCore/IdentifiersFactory.h>
+#include <JavaScriptCore/InspectorEnvironment.h>
+#include <wtf/HashMap.h>
+#include <wtf/Seconds.h>
+#include <wtf/Stopwatch.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+using namespace Inspector;
+
+InspectorAnimationAgent::InspectorAnimationAgent(PageAgentContext& context)
+ : InspectorAgentBase("Animation"_s, context)
+ , m_frontendDispatcher(makeUnique<Inspector::AnimationFrontendDispatcher>(context.frontendRouter))
+ , m_backendDispatcher(Inspector::AnimationBackendDispatcher::create(context.backendDispatcher, this))
+{
+}
+
+InspectorAnimationAgent::~InspectorAnimationAgent() = default;
+
+void InspectorAnimationAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
+{
+ ASSERT(m_instrumentingAgents.persistentInspectorAnimationAgent() != this);
+ m_instrumentingAgents.setPersistentInspectorAnimationAgent(this);
+}
+
+void InspectorAnimationAgent::willDestroyFrontendAndBackend(DisconnectReason)
+{
+ ErrorString ignored;
+ stopTracking(ignored);
+
+ ASSERT(m_instrumentingAgents.persistentInspectorAnimationAgent() == this);
+ m_instrumentingAgents.setPersistentInspectorAnimationAgent(nullptr);
+}
+
+void InspectorAnimationAgent::startTracking(ErrorString& errorString)
+{
+ if (m_instrumentingAgents.trackingInspectorAnimationAgent() == this) {
+ errorString = "Animation domain already tracking"_s;
+ return;
+ }
+
+ m_instrumentingAgents.setTrackingInspectorAnimationAgent(this);
+
+ ASSERT(m_trackedDeclarativeAnimationData.isEmpty());
+
+ m_frontendDispatcher->trackingStart(m_environment.executionStopwatch()->elapsedTime().seconds());
+}
+
+void InspectorAnimationAgent::stopTracking(ErrorString&)
+{
+ if (m_instrumentingAgents.trackingInspectorAnimationAgent() != this)
+ return;
+
+ m_instrumentingAgents.setTrackingInspectorAnimationAgent(nullptr);
+
+ m_trackedDeclarativeAnimationData.clear();
+
+ m_frontendDispatcher->trackingComplete(m_environment.executionStopwatch()->elapsedTime().seconds());
+}
+
+static bool isDelayed(ComputedEffectTiming& computedTiming)
+{
+ if (!computedTiming.localTime)
+ return false;
+ return computedTiming.localTime.value() < (computedTiming.endTime - computedTiming.activeDuration);
+}
+
+void InspectorAnimationAgent::willApplyKeyframeEffect(Element& target, KeyframeEffect& keyframeEffect, ComputedEffectTiming computedTiming)
+{
+ auto* animation = keyframeEffect.animation();
+ if (!is<DeclarativeAnimation>(animation))
+ return;
+
+ auto ensureResult = m_trackedDeclarativeAnimationData.ensure(downcast<DeclarativeAnimation>(animation), [&] () -> TrackedDeclarativeAnimationData {
+ return { makeString("animation:"_s, IdentifiersFactory::createIdentifier()), computedTiming };
+ });
+ auto& trackingData = ensureResult.iterator->value;
+
+ Optional<Inspector::Protocol::Animation::AnimationState> animationAnimationState;
+
+ if ((ensureResult.isNewEntry || !isDelayed(trackingData.lastComputedTiming)) && isDelayed(computedTiming))
+ animationAnimationState = Inspector::Protocol::Animation::AnimationState::Delayed;
+ else if (ensureResult.isNewEntry || trackingData.lastComputedTiming.phase != computedTiming.phase) {
+ switch (computedTiming.phase) {
+ case AnimationEffectPhase::Before:
+ animationAnimationState = Inspector::Protocol::Animation::AnimationState::Ready;
+ break;
+
+ case AnimationEffectPhase::Active:
+ animationAnimationState = Inspector::Protocol::Animation::AnimationState::Active;
+ break;
+
+ case AnimationEffectPhase::After:
+ animationAnimationState = Inspector::Protocol::Animation::AnimationState::Done;
+ break;
+
+ case AnimationEffectPhase::Idle:
+ animationAnimationState = Inspector::Protocol::Animation::AnimationState::Canceled;
+ break;
+ }
+ } else if (trackingData.lastComputedTiming.currentIteration != computedTiming.currentIteration) {
+ // Iterations are represented by sequential "active" state events.
+ animationAnimationState = Inspector::Protocol::Animation::AnimationState::Active;
+ }
+
+ trackingData.lastComputedTiming = computedTiming;
+
+ if (!animationAnimationState)
+ return;
+
+ auto event = Inspector::Protocol::Animation::TrackingUpdate::create()
+ .setTrackingAnimationId(trackingData.trackingAnimationId)
+ .setAnimationState(animationAnimationState.value())
+ .release();
+
+ if (ensureResult.isNewEntry) {
+ if (auto* domAgent = m_instrumentingAgents.inspectorDOMAgent()) {
+ if (auto nodeId = domAgent->pushNodeToFrontend(&target))
+ event->setNodeId(nodeId);
+ }
+
+ if (is<CSSAnimation>(animation))
+ event->setAnimationName(downcast<CSSAnimation>(*animation).animationName());
+ else if (is<CSSTransition>(animation))
+ event->setTransitionProperty(downcast<CSSTransition>(*animation).transitionProperty());
+ else
+ ASSERT_NOT_REACHED();
+ }
+
+ m_frontendDispatcher->trackingUpdate(m_environment.executionStopwatch()->elapsedTime().seconds(), WTFMove(event));
+}
+
+void InspectorAnimationAgent::didChangeWebAnimationEffect(WebAnimation& animation)
+{
+ if (is<DeclarativeAnimation>(animation))
+ stopTrackingDeclarativeAnimation(downcast<DeclarativeAnimation>(animation));
+}
+
+void InspectorAnimationAgent::willDestroyWebAnimation(WebAnimation& animation)
+{
+ if (is<DeclarativeAnimation>(animation))
+ stopTrackingDeclarativeAnimation(downcast<DeclarativeAnimation>(animation));
+}
+
+void InspectorAnimationAgent::stopTrackingDeclarativeAnimation(DeclarativeAnimation& animation)
+{
+ auto it = m_trackedDeclarativeAnimationData.find(&animation);
+ if (it == m_trackedDeclarativeAnimationData.end())
+ return;
+
+ if (it->value.lastComputedTiming.phase != AnimationEffectPhase::After && it->value.lastComputedTiming.phase != AnimationEffectPhase::Idle) {
+ auto event = Inspector::Protocol::Animation::TrackingUpdate::create()
+ .setTrackingAnimationId(it->value.trackingAnimationId)
+ .setAnimationState(Inspector::Protocol::Animation::AnimationState::Canceled)
+ .release();
+ m_frontendDispatcher->trackingUpdate(m_environment.executionStopwatch()->elapsedTime().seconds(), WTFMove(event));
+ }
+
+ m_trackedDeclarativeAnimationData.remove(it);
+}
+
+} // namespace WebCore
--- /dev/null
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 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 "ComputedEffectTiming.h"
+#include "InspectorWebAgentBase.h"
+#include <JavaScriptCore/InspectorBackendDispatchers.h>
+#include <JavaScriptCore/InspectorFrontendDispatchers.h>
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class DeclarativeAnimation;
+class Element;
+class Event;
+class KeyframeEffect;
+class WebAnimation;
+
+typedef String ErrorString;
+
+class InspectorAnimationAgent final : public InspectorAgentBase, public Inspector::AnimationBackendDispatcherHandler {
+ WTF_MAKE_NONCOPYABLE(InspectorAnimationAgent);
+ WTF_MAKE_FAST_ALLOCATED;
+public:
+ InspectorAnimationAgent(PageAgentContext&);
+ ~InspectorAnimationAgent() override;
+
+ // InspectorAgentBase
+ void didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) override;
+ void willDestroyFrontendAndBackend(Inspector::DisconnectReason) override;
+
+ // AnimationBackendDispatcherHandler
+ void startTracking(ErrorString&) override;
+ void stopTracking(ErrorString&) override;
+
+ // InspectorInstrumentation
+ void willApplyKeyframeEffect(Element&, KeyframeEffect&, ComputedEffectTiming);
+ void didChangeWebAnimationEffect(WebAnimation&);
+ void willDestroyWebAnimation(WebAnimation&);
+
+private:
+ void stopTrackingDeclarativeAnimation(DeclarativeAnimation&);
+
+ std::unique_ptr<Inspector::AnimationFrontendDispatcher> m_frontendDispatcher;
+ RefPtr<Inspector::AnimationBackendDispatcher> m_backendDispatcher;
+
+ struct TrackedDeclarativeAnimationData {
+ String trackingAnimationId;
+ ComputedEffectTiming lastComputedTiming;
+ };
+ HashMap<DeclarativeAnimation*, TrackedDeclarativeAnimationData> m_trackedDeclarativeAnimationData;
+};
+
+} // namespace WebCore
#include "DOMWindow.h"
#include "Event.h"
#include "Frame.h"
+#include "InspectorAnimationAgent.h"
#include "InspectorCPUProfilerAgent.h"
#include "InspectorClient.h"
#include "InspectorController.h"
case Inspector::Protocol::Timeline::Instrument::Timeline:
toggleTimelineInstrument(state);
break;
+ case Inspector::Protocol::Timeline::Instrument::Animation:
+ toggleAnimationInstrument(state);
+ break;
}
}
}
internalStop();
}
+void InspectorTimelineAgent::toggleAnimationInstrument(InstrumentState state)
+{
+ if (auto* animationAgent = m_instrumentingAgents.persistentInspectorAnimationAgent()) {
+ ErrorString ignored;
+ if (state == InstrumentState::Start)
+ animationAgent->startTracking(ignored);
+ else
+ animationAgent->stopTracking(ignored);
+ }
+}
+
void InspectorTimelineAgent::didRequestAnimationFrame(int callbackId, Frame* frame)
{
appendRecord(TimelineRecordFactory::createAnimationFrameData(callbackId), TimelineRecordType::RequestAnimationFrame, true, frame);
void toggleCPUInstrument(InstrumentState);
void toggleMemoryInstrument(InstrumentState);
void toggleTimelineInstrument(InstrumentState);
+ void toggleAnimationInstrument(InstrumentState);
void disableBreakpoints();
void enableBreakpoints();
2019-11-01 Devin Rousso <drousso@apple.com>
+ Web Inspector: Timelines: add a timeline that shows information about any recorded CSS animation/transition
+ https://bugs.webkit.org/show_bug.cgi?id=203651
+ <rdar://problem/56128726>
+
+ Reviewed by Brian Burg.
+
+ Unlike all other forms of Web Animations, CSS animations/transitions, are _not_ created by
+ JavaScript, and therefore can seemingly appear out of nowhere. This patch expands the Media
+ timeline to be the Media & Animations timeline, which tracks when CSS animations/transitions
+ are created, started, delayed, iterated, canceled, or finished.
+
+ * UserInterface/Protocol/AnimationObserver.js: Added.
+ (WI.AnimationObserver.prototype.trackingStart):
+ (WI.AnimationObserver.prototype.trackingUpdate):
+ (WI.AnimationObserver.prototype.trackingComplete):
+ * UserInterface/Protocol/Target.js:
+ (WI.Target.prototype.get AnimationAgent): Added.
+ * UserInterface/Main.html:
+ * UserInterface/Base/Main.js:
+ (WI.loaded):
+ * UserInterface/Test.html:
+ * UserInterface/Test/Test.js:
+ (WI.loaded):
+ Add an Animation domain for handling the tracking of CSS Web Animations.
+
+ * UserInterface/Models/MediaInstrument.js:
+ (WI.MediaInstrument.prototype.startInstrumentation):
+ (WI.MediaInstrument.prototype.stopInstrumentation):
+ (WI.MediaInstrument):
+ * UserInterface/Models/MediaTimeline.js: Added.
+ (WI.MediaTimeline.prototype.recordForTrackingAnimationId):
+ (WI.MediaTimeline.prototype.recordForMediaElementEvents):
+ (WI.MediaTimeline.prototype.reset):
+ (WI.MediaTimeline.prototype.addRecord):
+ * UserInterface/Models/MediaTimelineRecord.js:
+ (WI.MediaTimelineRecord):
+ (WI.MediaTimelineRecord.async fromJSON):
+ (WI.MediaTimelineRecord.prototype.toJSON):
+ (WI.MediaTimelineRecord.prototype.get trackingAnimationId): Added.
+ (WI.MediaTimelineRecord.prototype.get timestamps): Added.
+ (WI.MediaTimelineRecord.prototype.get activeStartTime): Added.
+ (WI.MediaTimelineRecord.prototype.get updatesDynamically): Added.
+ (WI.MediaTimelineRecord.prototype.get usesActiveStartTime): Added.
+ (WI.MediaTimelineRecord.prototype.get displayName):
+ (WI.MediaTimelineRecord.prototype.get subtitle): Added.
+ (WI.MediaTimelineRecord.prototype.saveIdentityToCookie):
+ (WI.MediaTimelineRecord.prototype.updateProgress): Added.
+ (WI.MediaTimelineRecord.prototype.addDOMEvent): Added.
+ (WI.MediaTimelineRecord.prototype.powerEfficientPlaybackStateChanged): Added.
+ (WI.MediaTimelineRecord.prototype._updateTimes): Added.
+ (WI.MediaTimelineRecord.fromJSON): Deleted.
+ (WI.MediaTimelineRecord.prototype.get domEvent): Deleted.
+ (WI.MediaTimelineRecord.prototype.get isPowerEfficient): Deleted.
+ * UserInterface/Models/Timeline.js:
+ (WI.Timeline.create):
+ * UserInterface/Controllers/TimelineManager.js:
+ (WI.TimelineManager):
+ (WI.TimelineManager.prototype.async processJSON):
+ (WI.TimelineManager.prototype.animationTrackingStarted): Added.
+ (WI.TimelineManager.prototype.animationTrackingUpdated): Added.
+ (WI.TimelineManager.prototype.animationTrackingCompleted): Added.
+ (WI.TimelineManager.prototype._updateAutoCaptureInstruments):
+ (WI.TimelineManager.prototype._handleDOMNodeDidFireEvent):
+ (WI.TimelineManager.prototype._handleDOMNodePowerEfficientPlaybackStateChanged):
+ Start/Stop tracking animations based on whether the Media & Animations timeline is enabled.
+
+ * UserInterface/Views/MediaTimelineOverviewGraph.js:
+ (WI.MediaTimelineOverviewGraph):
+ (WI.MediaTimelineOverviewGraph.get maximumRowCount): Added.
+ (WI.MediaTimelineOverviewGraph.prototype.reset):
+ (WI.MediaTimelineOverviewGraph.prototype.layout):
+ (WI.MediaTimelineOverviewGraph.prototype.updateSelectedRecord):
+ (WI.MediaTimelineOverviewGraph.prototype._processRecord): Added.
+ (WI.MediaTimelineOverviewGraph.prototype._processRecord.compareByStartTime): Added.
+ (WI.MediaTimelineOverviewGraph.prototype._handleRecordAdded):
+ (WI.MediaTimelineOverviewGraph.prototype._handleTimesUpdated): Added.
+ (WI.MediaTimelineOverviewGraph.prototype.shown): Deleted.
+ (WI.MediaTimelineOverviewGraph.prototype.hidden): Deleted.
+ * UserInterface/Views/MediaTimelineOverviewGraph.css:
+ (.timeline-overview-graph.media): Added.
+ (.timeline-overview-graph.media > .graph-row): Added.
+ (.timeline-overview-graph.media > .graph-row > .timeline-record-bar): Added.
+ (.timeline-overview-graph.media > .graph-row > .timeline-record-bar:not(.unfinished) > .segment:not(.inactive)): Added.
+ (.timeline-overview-graph.media:nth-child(even) > .graph-row > .timeline-record-bar:not(.unfinished) > .segment:not(.inactive)): Added.
+ (.timeline-overview-graph.media > .timeline-record-bar): Deleted.
+ (.timeline-overview-graph.media > .timeline-record-bar > .segment): Deleted.
+ * UserInterface/Views/MediaTimelineView.js:
+ (WI.MediaTimelineView):
+ (WI.MediaTimelineView.prototype.shown): Added.
+ (WI.MediaTimelineView.prototype.hidden): Added.
+ (WI.MediaTimelineView.prototype.closed):
+ (WI.MediaTimelineView.prototype.reset):
+ (WI.MediaTimelineView.prototype.dataGridSortComparator):
+ (WI.MediaTimelineView.prototype.dataGridSortComparator.compareDOMNodes):
+ (WI.MediaTimelineView.prototype._processPendingRecords):
+ * UserInterface/Views/MediaTimelineDataGridNode.js:
+ (WI.MediaTimelineDataGridNode):
+ (WI.MediaTimelineDataGridNode.prototype.get data):
+ (WI.MediaTimelineDataGridNode.prototype.createCellContent):
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren): Added.
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addReadySegment): ADded.
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addDelaySegment): ADded.
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addActiveSegment): ADded.
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addFullScreenSegment): ADded.
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addPowerEfficientPlaybackSegment): ADded.
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addPausedSegment): ADded.
+ (WI.MediaTimelineDataGridNode.prototype.timelineRecordBarCustomChildren.addPlayingSegment): ADded.
+ (WI.MediaTimelineDataGridNode.prototype.filterableDataForColumn):
+ (WI.MediaTimelineDataGridNode.prototype._createNameCellDocumentFragment): Added.
+ (WI.MediaTimelineDataGridNode.prototype.iconClassNames): Deleted.
+
+ * UserInterface/Views/TimelineRecordBar.js:
+ (WI.TimelineRecordBar):
+ (WI.TimelineRecordBar.prototype.refresh):
+ (WI.TimelineRecordBar.prototype._handleClick):
+ * UserInterface/Views/TimelineRecordBar.css:
+ (.timeline-record-bar):
+ (.timeline-record-bar > :matches(img, .segment)):
+ (.timeline-record-bar > img):
+ (.timeline-record-bar > .segment):
+ (body[dir=ltr] .timeline-record-bar > .segment):
+ (body[dir=ltr] .timeline-record-bar > .segment:first-of-type):
+ (body[dir=ltr] .timeline-record-bar > .segment:last-of-type):
+ (body[dir=rtl] .timeline-record-bar > .segment):
+ (body[dir=rtl] .timeline-record-bar > .segment:first-of-type):
+ (body[dir=rtl] .timeline-record-bar > .segment:last-of-type):
+ (.timeline-record-bar > .segment:not(:last-of-type)):
+ (.timeline-record-bar.selected > .segment):
+ (.timeline-record-bar > .segment.inactive,):
+ (.timeline-record-bar.has-inactive-segment > .segment:not(.inactive)):
+ (:focus .selected .timeline-record-bar:not(.has-custom-children) > .segment):
+ (:focus .selected .timeline-record-bar:not(.has-custom-children) > .segment.inactive):
+ (:focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive)):
+ (.timeline-record-bar.timeline-record-type-network > .segment):
+ (.timeline-record-bar.timeline-record-type-network > .segment.inactive):
+ (.timeline-record-bar.timeline-record-type-layout > .segment):
+ (.timeline-record-bar.timeline-record-type-layout.paint > .segment,):
+ (.timeline-record-bar.timeline-record-type-script > .segment):
+ (.timeline-record-bar.timeline-record-type-script.garbage-collected > .segment,):
+ (.timeline-record-bar.timeline-record-type-media > .segment):
+ (.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment):
+ (.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment.css-animation-ready):
+ (.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment:matches(.css-animation-delay, .media-element-paused)):
+ (.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment):
+ (.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment.media-element-full-screen):
+ (.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment.media-element-power-efficient-playback):
+ (body[dir=ltr] .timeline-record-bar > .segment.inactive,): Deleted.
+ (body[dir=ltr] .timeline-record-bar.has-inactive-segment > .segment:not(.inactive),): Deleted.
+ (:focus .selected .timeline-record-bar > .segment): Deleted.
+ (:focus .selected .timeline-record-bar > .segment.inactive): Deleted.
+ (body[dir=ltr] :focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive)): Deleted.
+ (body[dir=rtl] :focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive)): Deleted.
+ Allow timeline record bars to be customized through a delegate callback. If provided, it
+ will be used instead of any default content. It is expected to return an array of objects,
+ each having a `startTime` number, `classNames` array, and `title` string. It can also have a
+ `endTime` number and an `image` string. If `endTime` is `NaN`, the record is unfinished. If
+ `image` is provided, an `<img>` will be used instead of a segment, allowing for markers.
+
+ * UserInterface/Views/TimelineDataGridNode.js:
+ (WI.TimelineDataGridNode.prototype.createCellContent):
+ Add a default fallback for `WI.DOMNode` values.
+
+ * UserInterface/Views/TimelineTabContentView.js:
+ (WI.TimelineTabContentView.displayNameForTimelineType):
+ (WI.TimelineTabContentView.iconClassNameForRecord):
+ (WI.TimelineTabContentView.displayNameForRecord):
+ * UserInterface/Views/TimelineRecordTreeElement.js:
+ * UserInterface/Views/TimelineIcons.css:
+ (.animation-frame-record .icon):
+ (.css-animation-record .icon): Added.
+ (.css-transition-record .icon): Added.
+ (.media-element-record .icon): Added.
+ (.animation-record .icon): Deleted.
+ (.dom-event-record .icon): Deleted.
+ (.dom-event-record.fullscreen .icon): Deleted.
+ (.power-efficient-playback-state-changed-record .icon): Deleted.
+ * UserInterface/Images/DOMEventFullscreen.svg: Removed.
+ * UserInterface/Images/EventCancel.svg: Added.
+ * UserInterface/Images/EventIteration.svg: Added.
+ * UserInterface/Images/EventPause.svg:
+ * UserInterface/Images/EventPlay.svg:
+ * UserInterface/Images/EventProcessing.svg:
+ * UserInterface/Images/EventStop.svg:
+ * UserInterface/Images/PowerEfficientPlaybackStateChanged.svg: Removed.
+ * UserInterface/Images/TimelineRecordAnimationFrame.svg: Renamed from Source/WebInspectorUI/UserInterface/Images/TimelineRecordAnimation.svg.
+ * UserInterface/Images/TimelineRecordCSSAnimation.svg: Added.
+ * UserInterface/Images/TimelineRecordCSSTransition.svg: Added.
+ * UserInterface/Images/TimelineRecordMediaElement.svg: Added.
+ Add new media icons.
+
+ * UserInterface/Models/TimelineRecording.js:
+ (WI.TimelineRecording.async import): Added.
+ (WI.TimelineRecording.import): Deleted.
+ * UserInterface/Models/TimelineRecord.js:
+ * UserInterface/Models/CPUTimelineRecord.js:
+ * UserInterface/Models/HeapAllocationsTimelineRecord.js:
+ (WI.HeapAllocationsTimelineRecord.async fromJSON): Added.
+ (WI.HeapAllocationsTimelineRecord.fromJSON): Deleted.
+ * UserInterface/Models/LayoutTimelineRecord.js:
+ * UserInterface/Models/MemoryTimelineRecord.js:
+ * UserInterface/Models/RenderingFrameTimelineRecord.js:
+ * UserInterface/Models/ResourceTimelineRecord.js:
+ * UserInterface/Models/ScriptTimelineRecord.js:
+ Make the importing of timeline records `async` so we can attempt to rehydrate the DOM nodes
+ of any media records (as well as wait for heap snapshots).
+
+ * UserInterface/Models/DOMNode.js:
+ (WI.DOMNode):
+ (WI.DOMNode.prototype.isMediaElement): Added.
+ (WI.DOMNode.prototype._shouldListenForEventListeners): Deleted.
+
+ * Localizations/en.lproj/localizedStrings.js:
+
+2019-11-01 Devin Rousso <drousso@apple.com>
+
Web Inspector: default to focusing the console prompt if no other content is focused after opening Web Inspector
https://bugs.webkit.org/show_bug.cgi?id=203743
localizedStrings["2D"] = "2D";
localizedStrings["Accessibility"] = "Accessibility";
localizedStrings["Action"] = "Action";
+/* Tooltip for a time range bar that represents when a CSS animation/transition is running */
+localizedStrings["Active"] = "Active";
localizedStrings["Activity Viewer"] = "Activity Viewer";
localizedStrings["Add"] = "Add";
localizedStrings["Add %s Rule"] = "Add %s Rule";
localizedStrings["CPU"] = "CPU";
localizedStrings["CPU Usage"] = "CPU Usage";
localizedStrings["CSP Hash"] = "CSP Hash";
+localizedStrings["CSS Animation"] = "CSS Animation";
localizedStrings["CSS Canvas"] = "CSS Canvas";
localizedStrings["CSS Changes:"] = "CSS Changes:";
+localizedStrings["CSS Transition"] = "CSS Transition";
localizedStrings["CSS canvas \u201C%s\u201D"] = "CSS canvas \u201C%s\u201D";
localizedStrings["Cached"] = "Cached";
localizedStrings["Call Frames Truncated"] = "Call Frames Truncated";
localizedStrings["Calls"] = "Calls";
localizedStrings["Cancel Automatic Continue"] = "Cancel Automatic Continue";
localizedStrings["Cancel comparison"] = "Cancel comparison";
+/* Tooltip for a timestamp marker that represents when a CSS animation/transition is canceled */
+localizedStrings["Canceled"] = "Canceled";
localizedStrings["Canvas"] = "Canvas";
localizedStrings["Canvas %d"] = "Canvas %d";
localizedStrings["Canvas %s"] = "Canvas %s";
localizedStrings["DNS"] = "DNS";
localizedStrings["DOM"] = "DOM";
localizedStrings["DOM Content Loaded \u2014 %s"] = "DOM Content Loaded \u2014 %s";
+localizedStrings["DOM Event \u201C%s\u201D"] = "DOM Event \u201C%s\u201D";
localizedStrings["DOM Events"] = "DOM Events";
localizedStrings["DOM Nodes:"] = "DOM Nodes:";
localizedStrings["Damping"] = "Damping";
localizedStrings["Decoded"] = "Decoded";
localizedStrings["Default"] = "Default";
localizedStrings["Deferred pause from blackboxed script"] = "Deferred pause from blackboxed script";
+/* Tooltip for a time range bar that represents when a CSS animation/transition is delayed */
+localizedStrings["Delay"] = "Delay";
localizedStrings["Delete"] = "Delete";
localizedStrings["Delete Breakpoint"] = "Delete Breakpoint";
localizedStrings["Delete Breakpoints"] = "Delete Breakpoints";
localizedStrings["Ensure that only one live region is used on the page."] = "Ensure that only one live region is used on the page.";
localizedStrings["Ensure that only one main content section is used on the page."] = "Ensure that only one main content section is used on the page.";
localizedStrings["Ensure that values for \u0022%s\u0022 are valid."] = "Ensure that values for \u0022%s\u0022 are valid.";
-localizedStrings["Entered Full-Screen Mode"] = "Entered Full-Screen Mode";
localizedStrings["Entire Recording"] = "Entire Recording";
localizedStrings["Error"] = "Error";
localizedStrings["Error: "] = "Error: ";
localizedStrings["Example: \u201C%s\u201D"] = "Example: \u201C%s\u201D";
localizedStrings["Exception with thrown value: %s"] = "Exception with thrown value: %s";
localizedStrings["Execution context for %s"] = "Execution context for %s";
-localizedStrings["Exited Full-Screen Mode"] = "Exited Full-Screen Mode";
localizedStrings["Expand All"] = "Expand All";
localizedStrings["Expand columns"] = "Expand columns";
localizedStrings["Expanded"] = "Expanded";
localizedStrings["Invisible characters"] = "Invisible characters";
localizedStrings["Invoke getter"] = "Invoke getter";
localizedStrings["It is evaluated immediately after the global object is created, before any other content has loaded."] = "It is evaluated immediately after the global object is created, before any other content has loaded.";
+/* Tooltip for a timestamp marker that represents when a CSS animation/transition iterates */
+localizedStrings["Iteration"] = "Iteration";
localizedStrings["JP2"] = "JP2";
localizedStrings["JPEG"] = "JPEG";
localizedStrings["JavaScript"] = "JavaScript";
localizedStrings["Maximum Size: %s"] = "Maximum Size: %s";
localizedStrings["Maximum maximum memory size in this recording"] = "Maximum maximum memory size in this recording";
localizedStrings["Media"] = "Media";
+localizedStrings["Media & Animations"] = "Media & Animations";
+localizedStrings["Media Element"] = "Media Element";
localizedStrings["Media Event"] = "Media Event";
localizedStrings["Media Logging:"] = "Media Logging:";
localizedStrings["MediaSource"] = "MediaSource";
localizedStrings["Pause Processing"] = "Pause Processing";
localizedStrings["Pause Reason"] = "Pause Reason";
localizedStrings["Pause script execution (%s or %s)"] = "Pause script execution (%s or %s)";
+/* Tooltip for a time range bar that represents when the playback of a audio/video element is paused */
+localizedStrings["Paused"] = "Paused";
/* The number of tests that passed expressed as a percentage, followed by a literal %. */
localizedStrings["Percentage (of audits)"] = "%s%%";
localizedStrings["Periods of high CPU utilization will rapidly drain battery. Strive to keep idle pages under %s average CPU utilization."] = "Periods of high CPU utilization will rapidly drain battery. Strive to keep idle pages under %s average CPU utilization.";
localizedStrings["Ping Frame"] = "Ping Frame";
localizedStrings["Pings"] = "Pings";
localizedStrings["Play Sound"] = "Play Sound";
+/* Tooltip for a time range bar that represents when the playback of a audio/video element is running */
+localizedStrings["Playing"] = "Playing";
localizedStrings["Polite"] = "Polite";
localizedStrings["Pong Frame"] = "Pong Frame";
localizedStrings["Port"] = "Port";
localizedStrings["Power Efficient Playback"] = "Power Efficient Playback";
-localizedStrings["Power Efficient Playback Started"] = "Power Efficient Playback Started";
-localizedStrings["Power Efficient Playback Stopped"] = "Power Efficient Playback Stopped";
localizedStrings["Prefer Shorthands"] = "Prefer Shorthands";
localizedStrings["Prefer indent using:"] = "Prefer indent using:";
localizedStrings["Preserve Log"] = "Preserve Log";
localizedStrings["Radial Gradient"] = "Radial Gradient";
localizedStrings["Range Issue"] = "Range Issue";
localizedStrings["Readonly"] = "Readonly";
+/* Tooltip for a time range bar that represents when a CSS animation/transition exists but has not started processing */
+localizedStrings["Ready"] = "Ready";
localizedStrings["Reasons for compositing"] = "Reasons for compositing";
localizedStrings["Reasons for compositing:"] = "Reasons for compositing:";
localizedStrings["Record first %s frame"] = "Record first %s frame";
WI.loaded = function()
{
// Register observers for events from the InspectorBackend.
+ if (InspectorBackend.registerAnimationDispatcher)
+ InspectorBackend.registerAnimationDispatcher(WI.AnimationObserver);
if (InspectorBackend.registerApplicationCacheDispatcher)
InspectorBackend.registerApplicationCacheDispatcher(WI.ApplicationCacheObserver);
if (InspectorBackend.registerCPUProfilerDispatcher)
let overviewData = json.overview;
let identifier = this._nextRecordingIdentifier++;
- let newRecording = WI.TimelineRecording.import(identifier, recordingData, filename);
+ let newRecording = await WI.TimelineRecording.import(identifier, recordingData, filename);
this._recordings.push(newRecording);
this.dispatchEventToListeners(WI.TimelineManager.Event.RecordingCreated, {recording: newRecording});
this.capturingStopped();
}
+ // AnimationObserver
+
+ animationTrackingStarted(timestamp)
+ {
+ this.capturingStarted(timestamp);
+ }
+
+ animationTrackingUpdated(timestamp, event)
+ {
+ if (!this._enabled)
+ return;
+
+ console.assert(this.isCapturing());
+ if (!this.isCapturing())
+ return;
+
+ let mediaTimeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Media);
+ console.assert(mediaTimeline);
+
+ let record = mediaTimeline.recordForTrackingAnimationId(event.trackingAnimationId);
+ if (!record) {
+ let details = {
+ trackingAnimationId: event.trackingAnimationId,
+ };
+
+ let eventType;
+ if (event.animationName) {
+ eventType = WI.MediaTimelineRecord.EventType.CSSAnimation;
+ details.animationName = event.animationName;
+ } else if (event.transitionProperty) {
+ eventType = WI.MediaTimelineRecord.EventType.CSSTransition;
+ details.transitionProperty = event.transitionProperty;
+ } else {
+ WI.reportInternalError(`Unknown event type for event '${JSON.stringify(event)}'`);
+ return;
+ }
+
+ let domNode = WI.domManager.nodeForId(event.nodeId) || null;
+ console.assert(domNode);
+
+ record = new WI.MediaTimelineRecord(eventType, domNode, details);
+ this._addRecord(record);
+ }
+
+ record.updateAnimationState(timestamp, event.animationState);
+ }
+
+ animationTrackingCompleted(timestamp)
+ {
+ this.capturingStopped(timestamp);
+ }
+
// Private
_updateCapturingState(state, data = {})
case WI.TimelineRecord.Type.Network:
case WI.TimelineRecord.Type.RenderingFrame:
case WI.TimelineRecord.Type.Layout:
- case WI.TimelineRecord.Type.Media:
instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Timeline);
break;
case WI.TimelineRecord.Type.CPU:
case WI.TimelineRecord.Type.Memory:
instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Memory);
break;
+ case WI.TimelineRecord.Type.Media:
+ // COMPATIBILITY (iOS 13): Animation domain did not exist yet.
+ if (InspectorBackend.hasDomain("Animation"))
+ instrumentSet.add(InspectorBackend.Enum.Timeline.Instrument.Animation);
+ break;
}
}
if (!this._enabled)
return;
+ let domNode = event.target;
+ if (!domNode.isMediaElement())
+ return;
+
let {domEvent} = event.data;
- this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.DOMEvent, domEvent.timestamp, {
- domNode: event.target,
- domEvent,
- }));
+ let mediaTimeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Media);
+ console.assert(mediaTimeline);
+
+ let record = mediaTimeline.recordForMediaElementEvents(domNode);
+ if (!record) {
+ record = new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.MediaElement, domNode);
+ this._addRecord(record);
+ }
+
+ record.addDOMEvent(domEvent.timestamp, domEvent);
}
_handleDOMNodePowerEfficientPlaybackStateChanged(event)
if (!this._enabled)
return;
+ let domNode = event.target;
+ console.assert(domNode.isMediaElement());
+
let {timestamp, isPowerEfficient} = event.data;
- this._addRecord(new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.PowerEfficientPlaybackStateChanged, timestamp, {
- domNode: event.target,
- isPowerEfficient,
- }));
+ let mediaTimeline = this._activeRecording.timelineForRecordType(WI.TimelineRecord.Type.Media);
+ console.assert(mediaTimeline);
+
+ let record = mediaTimeline.recordForMediaElementEvents(domNode);
+ if (!record) {
+ record = new WI.MediaTimelineRecord(WI.MediaTimelineRecord.EventType.MediaElement, domNode);
+ this._addRecord(record);
+ }
+
+ record.powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient);
}
};
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
-<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
- <path fill="rgb(148, 190, 164)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
- <path fill="rgb(101, 161, 134)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
- <path fill="rgb(101, 161, 134)" d="M 12 3 L 11 3 L 5 3 L 4 3 L 4 4 L 4 12 L 4 12.962999 L 4.962 12.999 L 6.962 13.075 L 8 13.115 L 8 12.076 L 8 9 L 10 9 L 11 9 L 11 8 L 11 7 L 11 6 L 10 6 L 8 6 L 11 6 L 12 6 L 12 5 L 12 4 L 12 3 Z M 11 4 L 11 5 L 7 5 L 7 7 L 10 7 L 10 8 L 7 8 L 7 12.076 L 5 12 L 5 4 L 11 4 Z"/>
- <path fill="white" d="M 5 12 L 5 4 L 11 4 L 11 5 L 7 5 L 7 7 L 10 7 L 10 8 L 7 8 L 7 12.076 L 5 12 Z"/>
-</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 14 14">
+ <circle fill="rgb(191, 222, 255)" stroke="rgb(0, 122, 255)" cx="7" cy="7" r="6.25"/>
+ <path fill="white" d="M 7 0.5 c 3.584 0 6.5 2.916 6.5 6.5 s -2.916 6.5 -6.5 6.5 S 0.5 10.584 0.5 7 3.416 0.5 7 0.5 M 7 0 a 7 7 0 1 0 0 14 A 7 7 0 0 0 7 0 z"/>
+ <circle fill="none" stroke="rgb(0, 122, 255)" cx="7" cy="7" r="4"/>
+ <line stroke="rgb(0, 122, 255)" x1="4" y1="4" x2="10" y2="10" />
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 14 14">
+ <circle fill="rgb(191, 222, 255)" stroke="rgb(0, 122, 255)" cx="7" cy="7" r="6.25"/>
+ <path fill="white" d="M 7 0.5 c 3.584 0 6.5 2.916 6.5 6.5 s -2.916 6.5 -6.5 6.5 S 0.5 10.584 0.5 7 3.416 0.5 7 0.5 M 7 0 a 7 7 0 1 0 0 14 A 7 7 0 0 0 7 0 z"/>
+ <path fill="rgb(0, 122, 255)" d="M 10.2778182,6.85756083 L 10.2778182 7.18878965 L 10.2727273 7.18878965 C 10.2727273 8.91145673 8.80727273 10.3070527 7 10.3070527 C 5.19272727 10.3070527 3.72727273 8.91145673 3.72727273 7.18878965 C 3.72727273 5.58253773 5.00654545 4.27563905 6.64509091 4.10448106 L 6.64509091 5.84516477 L 9.92290909 3.92292886 L 6.64509091 2 L 6.64509091 3.39490299 C 4.60218182 3.56606098 3 5.19795196 3 7.18878965 C 3 9.29396366 4.79054545 11 7 11 C 9.18836364 11 10.9636364 9.32514629 10.9970909 7.24699723 L 11 7.24699723 L 11 6.85756083 L 10.2778182 6.85756083 Z"></path>
+</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 14 14">
- <circle fill="rgb(191, 222, 255)" cx="7" cy="7" r="6.75"/>
+ <circle fill="rgb(191, 222, 255)" stroke="rgb(0, 122, 255)" cx="7" cy="7" r="6.25"/>
<path fill="white" d="M 7 0.5 c 3.584 0 6.5 2.916 6.5 6.5 s -2.916 6.5 -6.5 6.5 S 0.5 10.584 0.5 7 3.416 0.5 7 0.5 M 7 0 a 7 7 0 1 0 0 14 A 7 7 0 0 0 7 0 z"/>
<path fill="rgb(0, 122, 255)" d="M 5.8 10 H 4.2 a 0.2 0.2 0 0 1 -0.2 -0.2 V 4.2 c 0 -0.11 0.09 -0.2 0.2 -0.2 h 1.6 c 0.11 0 0.2 0.09 0.2 0.2 v 5.6 a 0.2 0.2 0 0 1 -0.2 0.2 z M 9.8 10 H 8.2 a 0.2 0.2 0 0 1 -0.2 -0.2 V 4.2 c 0 -0.11 0.09 -0.2 0.2 -0.2 h 1.6 c 0.11 0 0.2 0.09 0.2 0.2 v 5.6 a 0.2 0.2 0 0 1 -0.2 0.2 z"/>
</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 14 14">
- <circle fill="rgb(191, 222, 255)" cx="7" cy="7" r="6.75"/>
+ <circle fill="rgb(191, 222, 255)" stroke="rgb(0, 122, 255)" cx="7" cy="7" r="6.25"/>
<path fill="white" d="M 7 0.5 c 3.584 0 6.5 2.916 6.5 6.5 s -2.916 6.5 -6.5 6.5 S 0.5 10.584 0.5 7 3.416 0.5 7 0.5 M 7 0 a 7 7 0 1 0 0 14 A 7 7 0 0 0 7 0 z"/>
<path fill="rgb(0, 122, 255)" d="M 10.704 6.827 L 5.3 3.676 A 0.2 0.2 0 0 0 5 3.848 v 6.304 a 0.2 0.2 0 0 0 0.3 0.173 l 5.404 -3.152 a 0.2 0.2 0 0 0 0 -0.346 z"/>
</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 14 14">
- <circle fill="rgb(191, 222, 255)" cx="7" cy="7" r="6.75"/>
+ <circle fill="rgb(191, 222, 255)" stroke="rgb(0, 122, 255)" cx="7" cy="7" r="6.25"/>
<path fill="white" d="M 7 0.5 c 3.584 0 6.5 2.916 6.5 6.5 s -2.916 6.5 -6.5 6.5 S 0.5 10.584 0.5 7 3.416 0.5 7 0.5 M 7 0 a 7 7 0 1 0 0 14 A 7 7 0 0 0 7 0 z"/>
<path fill="rgb(0, 122, 255)" d="M 7 8.5 a 1.5 1.5 0 0 1 0 -3 1.5 1.5 0 0 1 0 3 z M 3 8.5 a 1.5 1.5 0 0 1 0 -3 1.5 1.5 0 0 1 0 3 z M 11 8.5 a 1.5 1.5 0 0 1 0 -3 1.5 1.5 0 0 1 0 3 z"/>
</svg>
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 14 14">
- <circle fill="rgb(191, 222, 255)" cx="7" cy="7" r="6.75"/>
+ <circle fill="rgb(191, 222, 255)" stroke="rgb(0, 122, 255)" cx="7" cy="7" r="6.25"/>
<path fill="white" d="M 7 0.5 c 3.584 0 6.5 2.916 6.5 6.5 s -2.916 6.5 -6.5 6.5 S 0.5 10.584 0.5 7 3.416 0.5 7 0.5 M 7 0 a 7 7 0 1 0 0 14 A 7 7 0 0 0 7 0 z"/>
<path fill="rgb(0, 122, 255)" d="M 9.8 10 H 4.2 a 0.2 0.2 0 0 1 -0.2 -0.2 V 4.2 c 0 -0.11 0.09 -0.2 0.2 -0.2 h 5.6 c 0.11 0 0.2 0.09 0.2 0.2 v 5.6 a 0.2 0.2 0 0 1 -0.2 0.2 z"/>
</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright © 2013 Apple Inc. All rights reserved. -->
-<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
- <path fill="rgb(148, 190, 164)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
- <path fill="rgb(101, 161, 134)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
- <path fill="white" d="M 6.78125 7.769531 L 7.136719 7.769531 C 7.757812 7.769531 8.246094 7.636719 8.605469 7.371094 C 8.96875 7.105469 9.148438 6.746094 9.148438 6.292969 C 9.148438 5.496094 8.59375 5.097656 7.488281 5.097656 L 6.78125 5.097656 Z M 5 12 L 5 4 L 8.042969 4 C 9.105469 4 9.863281 4.164062 10.316406 4.488281 C 10.773438 4.816406 11 5.359375 11 6.125 C 11 6.988281 10.695312 7.660156 10.085938 8.144531 C 9.480469 8.625 8.628906 8.863281 7.535156 8.863281 L 6.78125 8.863281 L 6.78125 12 Z"/>
- <path fill="rgb(101, 161, 134)" d="M 7.78125 6.679688 L 7.78125 6.113281 C 8.019531 6.136719 8.117188 6.1875 8.144531 6.203125 C 8.140625 6.210938 8.148438 6.242188 8.148438 6.292969 C 8.148438 6.441406 8.105469 6.496094 8.015625 6.566406 C 7.953125 6.613281 7.875 6.648438 7.78125 6.679688 M 8.042969 3 L 4 3 L 4 13 L 7.78125 13 L 7.78125 9.863281 C 8.988281 9.824219 9.96875 9.507812 10.707031 8.925781 C 11.550781 8.257812 12 7.289062 12 6.125 C 12 4.753906 11.402344 4.035156 10.902344 3.675781 C 10.265625 3.222656 9.332031 3 8.042969 3 M 6.78125 7.769531 L 7.136719 7.769531 C 7.757812 7.769531 8.246094 7.636719 8.605469 7.371094 C 8.96875 7.105469 9.148438 6.746094 9.148438 6.292969 C 9.148438 5.496094 8.59375 5.097656 7.488281 5.097656 L 6.78125 5.097656 L 6.78125 7.769531 M 8.042969 4 C 9.105469 4 9.863281 4.164062 10.316406 4.488281 C 10.773438 4.816406 11 5.359375 11 6.125 C 11 6.988281 10.695312 7.660156 10.085938 8.144531 C 9.480469 8.625 8.628906 8.863281 7.535156 8.863281 L 6.78125 8.863281 L 6.78125 12 L 5 12 L 5 4 L 8.042969 4"/>
-</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+ <path fill="rgb(148, 190, 164)" d="M 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 L 3 1 Z M 3 1"/>
+ <path fill="rgb(101, 161, 134)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
+ <path fill="rgb(101, 161, 134)" d="M 2.61147641 12.75 L 5.96029528 12.75 L 6.8114662 10.628418 L 8.70346974 10.628418 L 9.53482191 12.75 L 13.3777814 12.75 L 9.50565303 2.80078125 L 6.48360478 2.80078125 L 2.61147641 12.75 Z M 2.61147641 12.75"/>
+ <path fill="white" d="M 4.07373047 11.75 L 7.16748047 3.80078125 L 8.82177734 3.80078125 L 11.9155273 11.75 L 10.2182617 11.75 L 9.39111328 9.62841797 L 6.10400391 9.62841797 L 5.27685547 11.75 L 4.07373047 11.75 Z M 6.51220703 8.5703125 L 8.98291016 8.5703125 L 7.74755859 5.40136719 L 6.51220703 8.5703125 Z M 6.51220703 8.5703125"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+ <path fill="rgb(148, 190, 164)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
+ <path fill="rgb(101, 161, 134)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
+ <path fill="rgb(101, 161, 134)" d="M 6.980469 12.742188 C 6.429688 12.742188 5.980469 12.292969 5.980469 11.742188 C 5.980469 11.742188 5.980469 7.34375 5.980469 5.882812 C 5.140625 5.882812 4.136719 5.882812 4.136719 5.882812 C 3.582031 5.882812 3.136719 5.4375 3.136719 4.882812 L 3.136719 3.792969 C 3.136719 3.242188 3.582031 2.792969 4.136719 2.792969 L 11.488281 2.792969 C 12.042969 2.792969 12.488281 3.242188 12.488281 3.792969 L 12.488281 4.882812 C 12.488281 5.4375 12.042969 5.882812 11.488281 5.882812 C 11.488281 5.882812 10.484375 5.882812 9.640625 5.882812 C 9.640625 7.34375 9.640625 11.742188 9.640625 11.742188 C 9.640625 12.292969 9.195312 12.742188 8.640625 12.742188 L 6.980469 12.742188"/>
+ <path fill="white" d="M 6.980469 11.742188 L 6.980469 4.882812 L 4.136719 4.882812 L 4.136719 3.792969 L 11.488281 3.792969 L 11.488281 4.882812 L 8.640625 4.882812 L 8.640625 11.742188 Z"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
<path fill="rgb(148, 190, 164)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
<path fill="rgb(101, 161, 134)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
<script src="Protocol/PageTarget.js"></script>
<script src="Protocol/WorkerTarget.js"></script>
+ <script src="Protocol/AnimationObserver.js"></script>
<script src="Protocol/ApplicationCacheObserver.js"></script>
<script src="Protocol/CPUProfilerObserver.js"></script>
<script src="Protocol/CSSObserver.js"></script>
<script src="Models/LogObject.js"></script>
<script src="Models/LoggingChannel.js"></script>
<script src="Models/MediaInstrument.js"></script>
+ <script src="Models/MediaTimeline.js"></script>
<script src="Models/MediaTimelineRecord.js"></script>
<script src="Models/MemoryCategory.js"></script>
<script src="Models/MemoryInstrument.js"></script>
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
return new WI.CPUTimelineRecord(json);
}
this._domEvents = [];
this._powerEfficientPlaybackRanges = [];
- if (this._shouldListenForEventListeners())
+ if (this.isMediaElement())
WI.DOMNode.addEventListener(WI.DOMNode.Event.DidFireEvent, this._handleDOMNodeDidFireEvent, this);
}
return !!this.ownerSVGElement;
}
+ isMediaElement()
+ {
+ let lowerCaseName = this.localName() || this.nodeName().toLowerCase();
+ return lowerCaseName === "video" || lowerCaseName === "audio";
+ }
+
didFireEvent(eventName, timestamp, data)
{
// Called from WI.DOMManager.
this.dispatchEventToListeners(WI.DOMNode.Event.DidFireEvent, {domEvent});
}
- _shouldListenForEventListeners()
- {
- let lowerCaseName = this.localName() || this.nodeName().toLowerCase();
- return lowerCaseName === "video" || lowerCaseName === "audio";
- }
-
_setAttributesPayload(attrs)
{
this._attributes = [];
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
// NOTE: This just goes through and generates a new heap snapshot,
// it is not perfect but does what we want. It asynchronously creates
// recording, which on an import should be the newly imported recording.
let {timestamp, title, snapshotStringData} = json;
- let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
- workerProxy.createImportedSnapshot(snapshotStringData, title, ({objectId, snapshot: serializedSnapshot}) => {
- let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
- snapshot.snapshotStringData = snapshotStringData;
- WI.timelineManager.heapSnapshotAdded(timestamp, snapshot);
+ return await new Promise((resolve, reject) => {
+ let workerProxy = WI.HeapSnapshotWorkerProxy.singleton();
+ workerProxy.createImportedSnapshot(snapshotStringData, title, ({objectId, snapshot: serializedSnapshot}) => {
+ let snapshot = WI.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot);
+ snapshot.snapshotStringData = snapshotStringData;
+ resolve(new WI.HeapAllocationsTimelineRecord(timestamp, snapshot));
+ });
});
-
- return null;
}
toJSON()
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
let {eventType, startTime, endTime, callFrames, sourceCodeLocation, quad} = json;
quad = quad ? WI.Quad.fromJSON(quad) : null;
startInstrumentation(initiatedByBackend)
{
- // Nothing to do, media instrumentation is always happening.
+ // Audio/Video/Picture instrumentation is always happening.
+
+ if (!initiatedByBackend) {
+ // COMPATIBILITY (iOS 13): Animation domain did not exist yet.
+ if (InspectorBackend.hasDomain("Animation")) {
+ let target = WI.assumingMainTarget();
+ target.AnimationAgent.startTracking();
+ }
+ }
}
stopInstrumentation(initiatedByBackend)
{
- // Nothing to do, media instrumentation is always happening.
+ // Audio/Video/Picture instrumentation is always happening.
+
+ if (!initiatedByBackend) {
+ // COMPATIBILITY (iOS 13): Animation domain did not exist yet.
+ if (InspectorBackend.hasDomain("Animation")) {
+ let target = WI.assumingMainTarget();
+ target.AnimationAgent.stopTracking();
+ }
+ }
}
};
--- /dev/null
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 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.
+ */
+
+WI.MediaTimeline = class MediaTimeline extends WI.Timeline
+{
+ // Public
+
+ recordForTrackingAnimationId(trackingAnimationId)
+ {
+ return this._trackingAnimationIdRecordMap.get(trackingAnimationId) || null;
+ }
+
+ recordForMediaElementEvents(domNode)
+ {
+ console.assert(domNode.isMediaElement());
+ return this._mediaElementRecordMap.get(domNode) || null;
+ }
+
+ reset(suppressEvents)
+ {
+ this._trackingAnimationIdRecordMap = new Map;
+ this._mediaElementRecordMap = new Map;
+
+ super.reset(suppressEvents);
+ }
+
+ addRecord(record, options = {})
+ {
+ console.assert(record instanceof WI.MediaTimelineRecord);
+
+ if (record.trackingAnimationId) {
+ console.assert(!this._trackingAnimationIdRecordMap.has(record.trackingAnimationId));
+ this._trackingAnimationIdRecordMap.set(record.trackingAnimationId, record);
+ }
+
+ if (record.eventType === WI.MediaTimelineRecord.EventType.MediaElement) {
+ console.assert(!(record.domNode instanceof WI.DOMNode) || record.domNode.isMediaElement());
+ console.assert(!this._mediaElementRecordMap.has(record.domNode));
+ this._mediaElementRecordMap.set(record.domNode, record);
+ }
+
+ super.addRecord(record, options);
+ }
+};
WI.MediaTimelineRecord = class MediaTimelineRecord extends WI.TimelineRecord
{
- constructor(eventType, timestamp, {domNode, domEvent, isPowerEfficient} = {})
+ constructor(eventType, domNodeOrInfo, {trackingAnimationId, animationName, transitionProperty} = {})
{
- console.assert(Object.values(WI.MediaTimelineRecord.EventType).includes(eventType));
+ console.assert(Object.values(MediaTimelineRecord.EventType).includes(eventType));
+ console.assert(domNodeOrInfo instanceof WI.DOMNode || (!isEmptyObject(domNodeOrInfo) && domNodeOrInfo.displayName && domNodeOrInfo.cssPath));
- super(WI.TimelineRecord.Type.Media, timestamp, timestamp);
+ super(WI.TimelineRecord.Type.Media);
this._eventType = eventType;
- this._domNode = domNode || null;
- this._domEvent = domEvent || null;
- this._isPowerEfficient = isPowerEfficient || false;
+ this._domNode = domNodeOrInfo;
+ this._domNodeDisplayName = domNodeOrInfo.displayName;
+ this._domNodeCSSPath = domNodeOrInfo instanceof WI.DOMNode ? WI.cssPath(domNodeOrInfo, {full: true}) : domNodeOrInfo.cssPath;
+
+ // Web Animation
+ console.assert(trackingAnimationId === undefined || typeof trackingAnimationId === "string");
+ this._trackingAnimationId = trackingAnimationId || null;
+
+ // CSS Web Animation
+ console.assert(animationName === undefined || typeof animationName === "string");
+ console.assert(transitionProperty === undefined || typeof transitionProperty === "string");
+ this._animationName = animationName || null;
+ this._transitionProperty = transitionProperty || null;
+
+ this._timestamps = [];
+ this._activeStartTime = NaN;
}
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
- let {eventType, timestamp} = json;
+ let {eventType, domNodeDisplayName, domNodeCSSPath, animationName, transitionProperty, timestamps} = json;
+
+ let documentNode = null;
+ if (InspectorBackend.hasDomain("DOM"))
+ documentNode = await new Promise((resolve) => WI.domManager.requestDocument(resolve));
+
+ let domNode = null;
+ if (documentNode && domNodeCSSPath) {
+ try {
+ let nodeId = await WI.domManager.querySelector(documentNode, domNodeCSSPath);
+ if (nodeId)
+ domNode = WI.domManager.nodeForId(nodeId);
+ } catch { }
+ }
+ if (!domNode) {
+ domNode = {
+ displayName: domNodeDisplayName,
+ cssPath: domNodeCSSPath,
+ };
+ }
+
+ let record = new MediaTimelineRecord(eventType, domNode, {animationName, transitionProperty});
- // COMPATIBILITY (iOS 12.2): isLowPower was renamed to isPowerEfficient.
- if ("isLowPower" in json && !("isPowerEfficient" in json))
- json.isPowerEfficient = json.isLowPower;
+ if (Array.isArray(timestamps) && timestamps.length) {
+ record._timestamps = [];
+ for (let item of timestamps) {
+ if (item.type === MediaTimelineRecord.TimestampType.MediaElementDOMEvent) {
+ if (documentNode && item.originatorCSSPath) {
+ try {
+ let nodeId = await WI.domManager.querySelector(documentNode, item.originatorCSSPath);
+ if (nodeId)
+ item.originator = WI.domManager.nodeForId(nodeId);
+ } catch { }
+ if (!item.originator) {
+ item.originator = {
+ displayName: item.originatorDisplayName,
+ cssPath: item.originatorCSSPath,
+ };
+ }
+ }
+ }
+ record._timestamps.push(item);
+ }
+ }
- return new WI.MediaTimelineRecord(eventType, timestamp, json);
+ return record;
}
toJSON()
{
- // FIXME: DOMNode
+ let json = {
+ eventType: this._eventType,
+ domNodeDisplayName: this._domNodeDisplayName,
+ domNodeCSSPath: this._domNodeCSSPath,
+ };
+
+ if (this._animationName)
+ json.animationName = this._animationName;
- // Don't include the DOMEvent's originator.
- let domEvent = this._domEvent;
- if (domEvent && domEvent.originator) {
- domEvent = Object.shallowCopy(domEvent);
- domEvent.originator = undefined;
+ if (this._transitionProperty)
+ json.transitionProperty = this._transitionProperty;
+
+ if (this._timestamps.length) {
+ json.timestamps = this._timestamps.map((item) => {
+ if (item.type === MediaTimelineRecord.TimestampType.MediaElementDOMEvent && item.originator instanceof WI.DOMNode)
+ delete item.originator;
+ return item;
+ });
}
- return {
- type: this.type,
- eventType: this._eventType,
- timestamp: this.startTime,
- domEvent,
- isPowerEfficient: this._isPowerEfficient,
- };
+ return json;
}
// Public
get eventType() { return this._eventType; }
get domNode() { return this._domNode; }
- get domEvent() { return this._domEvent; }
- get isPowerEfficient() { return this._isPowerEfficient; }
+ get trackingAnimationId() { return this._trackingAnimationId; }
+ get timestamps() { return this._timestamps; }
+ get activeStartTime() { return this._activeStartTime; }
+
+ get updatesDynamically()
+ {
+ return true;
+ }
+
+ get usesActiveStartTime()
+ {
+ return true;
+ }
get displayName()
{
- if (this._eventType === WI.MediaTimelineRecord.EventType.DOMEvent && this._domEvent) {
- let eventName = this._domEvent.eventName;
- if (eventName === "webkitfullscreenchange" && this._domEvent.data)
- return this._domEvent.data.enabled ? WI.UIString("Entered Full-Screen Mode") : WI.UIString("Exited Full-Screen Mode");
- return eventName;
- }
+ switch (this._eventType) {
+ case MediaTimelineRecord.EventType.CSSAnimation:
+ return this._animationName;
- if (this._eventType === MediaTimelineRecord.EventType.PowerEfficientPlaybackStateChanged)
- return this._isPowerEfficient ? WI.UIString("Power Efficient Playback Started") : WI.UIString("Power Efficient Playback Stopped");
+ case MediaTimelineRecord.EventType.CSSTransition:
+ return this._transitionProperty;
- if (this._domNode)
- return this._domNode.displayName;
+ case MediaTimelineRecord.EventType.MediaElement:
+ return WI.UIString("Media Element");
+ }
console.error("Unknown media record event type: ", this._eventType, this);
return WI.UIString("Media Event");
}
+ get subtitle()
+ {
+ switch (this._eventType) {
+ case MediaTimelineRecord.EventType.CSSAnimation:
+ return WI.UIString("CSS Animation");
+
+ case MediaTimelineRecord.EventType.CSSTransition:
+ return WI.UIString("CSS Transition");
+ }
+
+ return "";
+ }
+
saveIdentityToCookie(cookie)
{
super.saveIdentityToCookie(cookie);
cookie["media-timeline-record-event-type"] = this._eventType;
- if (this._eventType === MediaTimelineRecord.EventType.PowerEfficientPlaybackStateChanged)
- cookie["media-timeline-record-power-efficient-playback"] = this._isPowerEfficient;
- if (this._domNode)
- cookie["media-timeline-record-dom-node"] = this._domNode.path();
- if (this._domEvent) {
- cookie["media-timeline-record-dom-event"] = this._domEvent.eventName;
- if (this._domEvent.data && this._domEvent.data.enabled)
- cookie["media-timeline-record-dom-event-active"] = true;
+ cookie["media-timeline-record-dom-node"] = this._domNode instanceof WI.DOMNode ? this._domNode.path() : this._domNode;
+ if (this._animationName)
+ cookie["media-timeline-record-animation-name"] = this._animationName;
+ if (this._transitionProperty)
+ cookie["media-timeline-record-transition-property"] = this._transitionProperty;
+ }
+
+ // TimelineManager
+
+ updateAnimationState(timestamp, animationState)
+ {
+ console.assert(this._eventType === MediaTimelineRecord.EventType.CSSAnimation || this._eventType === MediaTimelineRecord.EventType.CSSTransition);
+ console.assert(!this._timestamps.length || timestamp > this._timestamps.lastValue.timestamp);
+
+ let type;
+ switch (animationState) {
+ case InspectorBackend.Enum.Animation.AnimationState.Ready:
+ type = MediaTimelineRecord.TimestampType.CSSAnimationReady;
+ break;
+ case InspectorBackend.Enum.Animation.AnimationState.Delayed:
+ type = MediaTimelineRecord.TimestampType.CSSAnimationDelay;
+ break;
+ case InspectorBackend.Enum.Animation.AnimationState.Active:
+ type = MediaTimelineRecord.TimestampType.CSSAnimationActive;
+ break;
+ case InspectorBackend.Enum.Animation.AnimationState.Canceled:
+ type = MediaTimelineRecord.TimestampType.CSSAnimationCancel;
+ break;
+ case InspectorBackend.Enum.Animation.AnimationState.Done:
+ type = MediaTimelineRecord.TimestampType.CSSAnimationDone;
+ break;
+ }
+ console.assert(type);
+
+ this._timestamps.push({type, timestamp});
+
+ this._updateTimes();
+ }
+
+ addDOMEvent(timestamp, domEvent)
+ {
+ console.assert(this._eventType === MediaTimelineRecord.EventType.MediaElement);
+ console.assert(!this._timestamps.length || timestamp > this._timestamps.lastValue.timestamp);
+
+ let data = {
+ type: MediaTimelineRecord.TimestampType.MediaElementDOMEvent,
+ timestamp,
+ eventName: domEvent.eventName,
+ };
+ if (domEvent.originator instanceof WI.DOMNode) {
+ data.originator = domEvent.originator;
+ data.originatorDisplayName = data.originator.displayName;
+ data.originatorCSSPath = WI.cssPath(data.originator, {full: true});
}
+ if (!isEmptyObject(domEvent.data))
+ data.data = domEvent.data;
+ this._timestamps.push(data);
+
+ this._updateTimes();
+ }
+
+ powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient)
+ {
+ console.assert(this._eventType === MediaTimelineRecord.EventType.MediaElement);
+ console.assert(!this._timestamps.length || timestamp > this._timestamps.lastValue.timestamp);
+
+ this._timestamps.push({
+ type: MediaTimelineRecord.TimestampType.MediaElementPowerEfficientPlaybackStateChange,
+ timestamp,
+ isPowerEfficient,
+ });
+
+ this._updateTimes();
+ }
+
+ // Private
+
+ _updateTimes()
+ {
+ let oldStartTime = this.startTime;
+ let oldEndTime = this.endTime;
+
+ let firstItem = this._timestamps[0];
+ let lastItem = this._timestamps.lastValue;
+
+ if (isNaN(this._startTime))
+ this._startTime = firstItem.timestamp;
+
+ if (isNaN(this._activeStartTime)) {
+ if (this._eventType === MediaTimelineRecord.EventType.MediaElement)
+ this._activeStartTime = firstItem.timestamp;
+ else if (firstItem.type === MediaTimelineRecord.TimestampType.CSSAnimationActive)
+ this._activeStartTime = firstItem.timestamp;
+ }
+
+ switch (lastItem.type) {
+ case MediaTimelineRecord.TimestampType.CSSAnimationCancel:
+ case MediaTimelineRecord.TimestampType.CSSAnimationDone:
+ this._endTime = lastItem.timestamp;
+ break;
+
+ case MediaTimelineRecord.TimestampType.MediaElementDOMEvent:
+ if (WI.DOMNode.isPlayEvent(lastItem.eventName))
+ this._endTime = NaN;
+ else if (!isNaN(this._endTime) || WI.DOMNode.isPauseEvent(lastItem.eventName) || WI.DOMNode.isStopEvent(lastItem.eventName))
+ this._endTime = lastItem.timestamp;
+ break;
+ }
+
+ if (this.startTime !== oldStartTime || this.endTime !== oldEndTime)
+ this.dispatchEventToListeners(WI.TimelineRecord.Event.Updated);
}
};
WI.MediaTimelineRecord.EventType = {
- DOMEvent: "dom-event",
- PowerEfficientPlaybackStateChanged: "power-efficient-playback-state-changed",
+ CSSAnimation: "css-animation",
+ CSSTransition: "css-transition",
+ MediaElement: "media-element",
+};
+
+WI.MediaTimelineRecord.TimestampType = {
+ CSSAnimationReady: "css-animation-ready",
+ CSSAnimationDelay: "css-animation-delay",
+ CSSAnimationActive: "css-animation-active",
+ CSSAnimationCancel: "css-animation-cancel",
+ CSSAnimationDone: "css-animation-done",
+ // CSS transitions share the same timestamp types.
+
+ MediaElementDOMEvent: "media-element-dom-event",
+ MediaElementPowerEfficientPlaybackStateChange: "media-element-power-efficient-playback-state-change",
};
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
let {timestamp, categories} = json;
return new WI.MemoryTimelineRecord(timestamp, categories);
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
let {startTime, endTime} = json;
let record = new WI.RenderingFrameTimelineRecord(startTime, endTime);
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
let {entry, archiveStartTime} = json;
let localResource = WI.LocalResource.fromHAREntry(entry, archiveStartTime);
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
let {eventType, startTime, endTime, callFrames, sourceCodeLocation, details, profilePayload, extraDetails} = json;
if (type === WI.TimelineRecord.Type.Memory)
return new WI.MemoryTimeline(type);
+ if (type === WI.TimelineRecord.Type.Media)
+ return new WI.MediaTimeline(type);
+
return new WI.Timeline(type);
}
// Import / Export
- static fromJSON(json)
+ static async fromJSON(json)
{
switch (json.type) {
case WI.TimelineRecord.Type.Network:
get activeStartTime()
{
// Implemented by subclasses if needed.
- return this._startTime;
+ return this.startTime;
}
get unadjustedStartTime()
// Import / Export
- static import(identifier, json, displayName)
+ static async import(identifier, json, displayName)
{
let {startTime, endTime, discontinuities, instrumentTypes, records, markers, memoryPressureEvents, sampleStackTraces, sampleDurations} = json;
let importedDisplayName = WI.UIString("Imported - %s").format(displayName);
recording.initializeCallingContextTrees(sampleStackTraces, sampleDurations);
for (let recordJSON of records) {
- let record = WI.TimelineRecord.fromJSON(recordJSON);
+ let record = await WI.TimelineRecord.fromJSON(recordJSON);
if (record) {
recording.addRecord(record);
--- /dev/null
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 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.
+ */
+
+WI.AnimationObserver = class AnimationObserver extends InspectorBackend.Dispatcher
+{
+ // Events defined by the "Animation" domain.
+
+ trackingStart(timestamp)
+ {
+ WI.timelineManager.animationTrackingStarted(timestamp);
+ }
+
+ trackingUpdate(timestamp, event)
+ {
+ WI.timelineManager.animationTrackingUpdated(timestamp, event);
+ }
+
+ trackingComplete(timestamp)
+ {
+ WI.timelineManager.animationTrackingCompleted(timestamp);
+ }
+};
// Agents
- get AuditAgent() { return this._agents.Audit; }
+ get AnimationAgent() { return this._agents.Animation; }
get ApplicationCacheAgent() { return this._agents.ApplicationCache; }
+ get AuditAgent() { return this._agents.Audit; }
get CPUProfilerAgent() { return this._agents.CPUProfiler; }
get CSSAgent() { return this._agents.CSS; }
get CanvasAgent() { return this._agents.Canvas; }
<script src="Protocol/WorkerTarget.js"></script>
<script src="Protocol/InspectorObserver.js"></script>
+ <script src="Protocol/AnimationObserver.js"></script>
<script src="Protocol/ApplicationCacheObserver.js"></script>
<script src="Protocol/CPUProfilerObserver.js"></script>
<script src="Protocol/CSSObserver.js"></script>
<script src="Models/LocalResourceOverride.js"></script>
<script src="Models/LoggingChannel.js"></script>
<script src="Models/MediaInstrument.js"></script>
+ <script src="Models/MediaTimeline.js"></script>
<script src="Models/MediaTimelineRecord.js"></script>
<script src="Models/MemoryCategory.js"></script>
<script src="Models/MemoryInstrument.js"></script>
{
// Register observers for events from the InspectorBackend.
// The initialization order should match the same in Main.js.
+ InspectorBackend.registerAnimationDispatcher(WI.AnimationObserver);
InspectorBackend.registerApplicationCacheDispatcher(WI.ApplicationCacheObserver);
InspectorBackend.registerCPUProfilerDispatcher(WI.CPUProfilerObserver);
InspectorBackend.registerCSSDispatcher(WI.CSSObserver);
var node = domTreeElement.representedObject;
var title = null;
- var className = null;
switch (node.nodeType()) {
case Node.ELEMENT_NODE:
if (node.isPseudoElement()) {
- className = WI.DOMTreeElementPathComponent.DOMPseudoElementIconStyleClassName;
title = "::" + node.pseudoType();
} else {
- className = WI.DOMTreeElementPathComponent.DOMElementIconStyleClassName;
title = node.displayName;
}
break;
case Node.TEXT_NODE:
- className = WI.DOMTreeElementPathComponent.DOMTextNodeIconStyleClassName;
title = "\"" + node.nodeValue().truncateEnd(32) + "\"";
break;
case Node.COMMENT_NODE:
- className = WI.DOMTreeElementPathComponent.DOMCommentIconStyleClassName;
title = "<!--" + node.nodeValue().truncateEnd(32) + "-->";
break;
case Node.DOCUMENT_TYPE_NODE:
- className = WI.DOMTreeElementPathComponent.DOMDocumentTypeIconStyleClassName;
title = "<!DOCTYPE>";
break;
case Node.DOCUMENT_NODE:
- className = WI.DOMTreeElementPathComponent.DOMDocumentIconStyleClassName;
title = node.nodeNameInCorrectCase();
break;
case Node.CDATA_SECTION_NODE:
- className = WI.DOMTreeElementPathComponent.DOMCharacterDataIconStyleClassName;
title = "<![CDATA[" + node.truncateEnd(32) + "]]>";
break;
case Node.DOCUMENT_FRAGMENT_NODE:
// FIXME: At some point we might want a different icon for this.
// <rdar://problem/12800950> Need icon for DOCUMENT_FRAGMENT_NODE and PROCESSING_INSTRUCTION_NODE
- className = WI.DOMTreeElementPathComponent.DOMDocumentTypeIconStyleClassName;
if (node.shadowRootType())
title = WI.UIString("Shadow Content");
else
case Node.PROCESSING_INSTRUCTION_NODE:
// FIXME: At some point we might want a different icon for this.
// <rdar://problem/12800950> Need icon for DOCUMENT_FRAGMENT_NODE and PROCESSING_INSTRUCTION_NODE.
- className = WI.DOMTreeElementPathComponent.DOMDocumentTypeIconStyleClassName;
title = node.nodeNameInCorrectCase();
break;
default:
console.error("Unknown DOM node type: ", node.nodeType());
- className = WI.DOMTreeElementPathComponent.DOMNodeIconStyleClassName;
title = node.nodeNameInCorrectCase();
}
- super(title, className, representedObject || domTreeElement.representedObject);
+ super(title, DOMTreeElementPathComponent.iconClassNameForNode(node), representedObject || domTreeElement.representedObject);
this._domTreeElement = domTreeElement;
}
+ static iconClassNameForNode(domNode)
+ {
+ switch (domNode.nodeType()) {
+ case Node.ELEMENT_NODE:
+ if (domNode.isPseudoElement())
+ return WI.DOMTreeElementPathComponent.DOMPseudoElementIconStyleClassName;
+ return WI.DOMTreeElementPathComponent.DOMElementIconStyleClassName;
+
+ case Node.TEXT_NODE:
+ return WI.DOMTreeElementPathComponent.DOMTextNodeIconStyleClassName;
+
+ case Node.COMMENT_NODE:
+ return WI.DOMTreeElementPathComponent.DOMCommentIconStyleClassName;
+
+ case Node.DOCUMENT_TYPE_NODE:
+ return WI.DOMTreeElementPathComponent.DOMDocumentTypeIconStyleClassName;
+
+ case Node.DOCUMENT_NODE:
+ return WI.DOMTreeElementPathComponent.DOMDocumentIconStyleClassName;
+
+ case Node.CDATA_SECTION_NODE:
+ return WI.DOMTreeElementPathComponent.DOMCharacterDataIconStyleClassName;
+
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ // FIXME: At some point we might want a different icon for this.
+ // <rdar://problem/12800950> Need icon for DOCUMENT_FRAGMENT_NODE and PROCESSING_INSTRUCTION_NODE
+ return WI.DOMTreeElementPathComponent.DOMDocumentTypeIconStyleClassName;
+
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ // FIXME: At some point we might want a different icon for this.
+ // <rdar://problem/12800950> Need icon for DOCUMENT_FRAGMENT_NODE and PROCESSING_INSTRUCTION_NODE.
+ return WI.DOMTreeElementPathComponent.DOMDocumentTypeIconStyleClassName;
+ }
+
+ console.error("Unknown DOM node type: ", domNode.nodeType());
+ return WI.DOMTreeElementPathComponent.DOMNodeIconStyleClassName;
+ }
+
// Public
get domTreeElement()
this._cachedData = super.data;
this._cachedData.name = this.record.displayName;
- if (this.record.domNode)
- this._cachedData.element = this.record.domNode;
- this._cachedData.time = this.record.startTime - (this.graphDataSource ? this.graphDataSource.zeroTime : 0);
- if (this.record.eventType === WI.MediaTimelineRecord.EventType.DOMEvent && this.record.domEvent.originator)
- this._cachedData.originator = this.record.domEvent.originator;
+ this._cachedData.element = this.record.domNode;
+ this._cachedData.source = this.record.domNode; // Timeline Overview
return this._cachedData;
}
switch (columnIdentifier) {
case "name":
cell.classList.add(...this.iconClassNames());
- return value;
+ return this._createNameCellDocumentFragment();
- case "source": // Timeline Overview
case "element":
- return value ? WI.linkifyNodeReference(value) : emDash;
-
- case "time": {
- const higherResolution = true;
- return Number.secondsToString(value, higherResolution);
- }
-
- case "originator":
- return value ? WI.linkifyNodeReference(value) : zeroWidthSpace;
+ case "source": // Timeline Overview
+ if (!(value instanceof WI.DOMNode)) {
+ cell.classList.add(WI.DOMTreeElementPathComponent.DOMNodeIconStyleClassName);
+ return value.displayName;
+ }
+ break;
}
return super.createCellContent(columnIdentifier, cell);
}
- iconClassNames()
+ // TimelineRecordBar delegate
+
+ timelineRecordBarCustomChildren(timelineRecordBar)
{
- let iconClassNames = super.iconClassNames();
- if (this.record.eventType === WI.MediaTimelineRecord.EventType.DOMEvent && this.record.domEvent.eventName === "webkitfullscreenchange")
- iconClassNames.push("fullscreen");
- return iconClassNames;
+ let children = [];
+
+ let record = this.record;
+ let timestamps = record.timestamps;
+
+ switch (record.eventType) {
+ case WI.MediaTimelineRecord.EventType.CSSAnimation:
+ case WI.MediaTimelineRecord.EventType.CSSTransition: {
+ let readyStartTime = NaN;
+ function addReadySegment(startTime, endTime) {
+ children.push({
+ startTime,
+ endTime,
+ classNames: ["segment", "css-animation-ready"],
+ title: WI.UIString("Ready", "Tooltip for a time range bar that represents when a CSS animation/transition exists but has not started processing"),
+ });
+ readyStartTime = NaN;
+ }
+
+ let delayStartTime = NaN;
+ function addDelaySegment(startTime, endTime) {
+ children.push({
+ startTime,
+ endTime,
+ classNames: ["segment", "css-animation-delay"],
+ title: WI.UIString("Delay", "Tooltip for a time range bar that represents when a CSS animation/transition is delayed"),
+ });
+ delayStartTime = NaN;
+ }
+
+ let activeStartTime = NaN;
+ function addActiveSegment(startTime, endTime) {
+ children.push({
+ startTime,
+ endTime,
+ classNames: ["segment", "css-animation-active"],
+ title: WI.UIString("Active", "Tooltip for a time range bar that represents when a CSS animation/transition is running"),
+ });
+ activeStartTime = NaN;
+ }
+
+ for (let item of timestamps) {
+ switch (item.type) {
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationReady:
+ if (isNaN(readyStartTime))
+ readyStartTime = item.timestamp;
+ break;
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationDelay:
+ if (isNaN(delayStartTime))
+ delayStartTime = item.timestamp;
+ if (!isNaN(readyStartTime))
+ addReadySegment(readyStartTime, item.timestamp);
+ break;
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationActive:
+ if (isNaN(activeStartTime))
+ activeStartTime = item.timestamp;
+ if (!isNaN(readyStartTime))
+ addReadySegment(readyStartTime, item.timestamp);
+ if (!isNaN(delayStartTime))
+ addDelaySegment(delayStartTime, item.timestamp);
+ break;
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationCancel:
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationDone:
+ if (!isNaN(readyStartTime))
+ addReadySegment(readyStartTime, item.timestamp);
+ if (!isNaN(delayStartTime))
+ addDelaySegment(delayStartTime, item.timestamp);
+ if (!isNaN(activeStartTime))
+ addActiveSegment(activeStartTime, item.timestamp);
+ break;
+ }
+ }
+
+ if (!isNaN(readyStartTime))
+ addReadySegment(readyStartTime, NaN);
+ if (!isNaN(delayStartTime))
+ addDelaySegment(delayStartTime, NaN);
+ if (!isNaN(activeStartTime))
+ addActiveSegment(activeStartTime, NaN);
+
+ break;
+ }
+
+ case WI.MediaTimelineRecord.EventType.MediaElement: {
+ let fullScreenSegments = [];
+ let powerEfficientPlaybackSegments = [];
+ let activeSegments = [];
+
+ let fullScreenStartTime = NaN;
+ let fullScreenOriginator = null;
+ function addFullScreenSegment(startTime, endTime) {
+ fullScreenSegments.push({
+ startTime,
+ endTime,
+ classNames: ["segment", "media-element-full-screen"],
+ title: fullScreenOriginator ? WI.UIString("Full-Screen from \u201C%s\u201D").format(fullScreenOriginator.displayName) : WI.UIString("Full-Screen"),
+ });
+ fullScreenStartTime = NaN;
+ fullScreenOriginator = null;
+ }
+
+ let powerEfficientPlaybackStartTime = NaN;
+ function addPowerEfficientPlaybackSegment(startTime, endTime) {
+ powerEfficientPlaybackSegments.push({
+ startTime,
+ endTime,
+ classNames: ["segment", "media-element-power-efficient-playback"],
+ title: WI.UIString("Power Efficient Playback"),
+ });
+ powerEfficientPlaybackStartTime = NaN;
+ }
+
+ let pausedStartTime = NaN;
+ function addPausedSegment(startTime, endTime) {
+ activeSegments.push({
+ startTime,
+ endTime,
+ classNames: ["segment", "media-element-paused"],
+ title: WI.UIString("Paused", "Tooltip for a time range bar that represents when the playback of a audio/video element is paused"),
+ });
+ pausedStartTime = NaN;
+ }
+
+ let playingStartTime = NaN;
+ function addPlayingSegment(startTime, endTime) {
+ activeSegments.push({
+ startTime,
+ endTime,
+ classNames: ["segment", "media-element-playing"],
+ title: WI.UIString("Playing", "Tooltip for a time range bar that represents when the playback of a audio/video element is running"),
+ });
+ playingStartTime = NaN;
+ }
+
+ for (let item of timestamps) {
+ if (item.type === WI.MediaTimelineRecord.TimestampType.MediaElementDOMEvent) {
+ if (WI.DOMNode.isPlayEvent(item.eventName)) {
+ if (isNaN(playingStartTime))
+ playingStartTime = item.timestamp;
+ if (!isNaN(pausedStartTime))
+ addPausedSegment(pausedStartTime, item.timestamp);
+ } else if (WI.DOMNode.isPauseEvent(item.eventName)) {
+ if (isNaN(pausedStartTime))
+ pausedStartTime = item.timestamp;
+ if (!isNaN(playingStartTime))
+ addPlayingSegment(playingStartTime, item.timestamp);
+ } else if (WI.DOMNode.isStopEvent(item.eventName)) {
+ if (!isNaN(pausedStartTime))
+ addPausedSegment(pausedStartTime, item.timestamp);
+ if (!isNaN(playingStartTime))
+ addPlayingSegment(playingStartTime, item.timestamp);
+ } else if (item.eventName === "webkitfullscreenchange") {
+ if (!fullScreenOriginator && item.originator)
+ fullScreenOriginator = item.originator;
+
+ if (isNaN(fullScreenStartTime)) {
+ if (item.data && item.data.enabled)
+ fullScreenStartTime = item.timestamp;
+ else
+ addFullScreenSegment(this.graphDataSource ? this.graphDataSource.startTime : record.startTime, item.timestamp);
+ } else if (!item.data || !item.data.enabled)
+ addFullScreenSegment(fullScreenStartTime, item.timestamp);
+ }
+ } else if (item.type === WI.MediaTimelineRecord.TimestampType.MediaElementPowerEfficientPlaybackStateChange) {
+ if (isNaN(powerEfficientPlaybackStartTime)) {
+ if (item.isPowerEfficient)
+ powerEfficientPlaybackStartTime = item.timestamp;
+ else
+ addPowerEfficientPlaybackSegment(this.graphDataSource ? this.graphDataSource.startTime : record.startTime, item.timestamp);
+ } else if (!item.isPowerEfficient)
+ addPowerEfficientPlaybackSegment(powerEfficientPlaybackStartTime, item.timestamp);
+ }
+ }
+
+ if (!isNaN(fullScreenStartTime))
+ addFullScreenSegment(fullScreenStartTime, NaN);
+ if (!isNaN(powerEfficientPlaybackStartTime))
+ addPowerEfficientPlaybackSegment(powerEfficientPlaybackStartTime, NaN);
+ if (!isNaN(pausedStartTime))
+ addPausedSegment(pausedStartTime, NaN);
+ if (!isNaN(playingStartTime))
+ addPlayingSegment(playingStartTime, NaN);
+
+ children.pushAll(fullScreenSegments);
+ children.pushAll(powerEfficientPlaybackSegments);
+ children.pushAll(activeSegments);
+ break;
+ }
+ }
+
+ timestamps.forEach((item, i) => {
+ let image = {
+ startTime: item.timestamp,
+ classNames: [],
+ };
+
+ switch (item.type) {
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationReady:
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationDelay:
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationDone:
+ case WI.MediaTimelineRecord.TimestampType.MediaElementPowerEfficientPlaybackStateChange:
+ // These timestamps are handled by the range segments above.
+ return;
+
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationActive:
+ // Don't create a marker segment for the first active timestamp, as that will be
+ // handled by an active range segment above.
+ if (!i || timestamps[i - 1].type !== WI.MediaTimelineRecord.TimestampType.CSSAnimationActive)
+ return;
+
+ image.image = "Images/EventIteration.svg";
+ image.title = WI.UIString("Iteration", "Tooltip for a timestamp marker that represents when a CSS animation/transition iterates");
+ break;
+
+ case WI.MediaTimelineRecord.TimestampType.CSSAnimationCancel:
+ image.image = "Images/EventCancel.svg";
+ image.title = WI.UIString("Canceled", "Tooltip for a timestamp marker that represents when a CSS animation/transition is canceled");
+ break;
+
+ case WI.MediaTimelineRecord.TimestampType.MediaElementDOMEvent:
+ // Don't create a marker segment full-screen timestamps, as that will be handled by a
+ // range segment above.
+ if (item.eventName === "webkitfullscreenchange")
+ return;
+
+ image.title = WI.UIString("DOM Event \u201C%s\u201D").format(item.eventName);
+ if (WI.DOMNode.isPlayEvent(item.eventName))
+ image.image = "Images/EventPlay.svg";
+ else if (WI.DOMNode.isPauseEvent(item.eventName))
+ image.image = "Images/EventPause.svg";
+ else if (WI.DOMNode.isStopEvent(item.eventName))
+ image.image = "Images/EventStop.svg";
+ else
+ image.image = "Images/EventProcessing.svg";
+ break;
+ }
+
+ children.push(image);
+ });
+
+ return children;
}
// Protected
filterableDataForColumn(columnIdentifier)
{
- if (columnIdentifier === "element") {
+ switch (columnIdentifier) {
+ case "name":
+ return [this.record.displayName, this.record.subtitle];
+
+ case "element":
+ case "source": // Timeline Overview
if (this.record.domNode)
return this.record.domNode.displayName;
}
- if (columnIdentifier === "originator") {
- if (this.record.eventType === WI.MediaTimelineRecord.EventType.DOMEvent && this.record.domEvent.originator)
- return this.record.domEvent.originator.displayName;
+ return super.filterableDataForColumn(columnIdentifier);
+ }
+
+ // Private
+
+ _createNameCellDocumentFragment()
+ {
+ let fragment = document.createDocumentFragment();
+ fragment.append(this.record.displayName);
+
+ if (this.record.subtitle) {
+ let subtitleElement = fragment.appendChild(document.createElement("span"));
+ subtitleElement.className = "subtitle";
+ subtitleElement.textContent = this.record.subtitle;
}
- return super.filterableDataForColumn(columnIdentifier);
+ return fragment;
}
};
* THE POSSIBILITY OF SUCH DAMAGE.
*/
-.timeline-overview-graph.media > .timeline-record-bar {
- margin-top: 8px;
- height: 20px;
+.timeline-overview-graph.media {
+ padding-top: 2px;
}
-.timeline-overview-graph.media > .timeline-record-bar > .segment {
- border-radius: 2px;
+.timeline-overview-graph.media > .graph-row {
+ height: 5px;
+}
+
+.timeline-overview-graph.media > .graph-row > .timeline-record-bar {
+ height: 4px;
+ margin-top: 1px;
+}
+
+.timeline-overview-graph.media > .graph-row > .timeline-record-bar:not(.unfinished) > .segment:not(.inactive) {
+ box-shadow: var(--timeline-odd-background-color) 1px 0 0;
+}
+
+.timeline-overview-graph.media:nth-child(even) > .graph-row > .timeline-record-bar:not(.unfinished) > .segment:not(.inactive) {
+ box-shadow: var(--timeline-even-background-color) 1px 0 0;
}
super(timelineOverview);
- this._timeline = timeline;
- this._recordBars = [];
-
this.element.classList.add("media");
this.reset();
+
+ for (let record of timeline.records)
+ this._processRecord(record);
+
+ timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._handleRecordAdded, this);
+ timeline.addEventListener(WI.Timeline.Event.TimesUpdated, this._handleTimesUpdated, this);
+ }
+
+ // Static
+
+ static get maximumRowCount() {
+ return 6;
}
// Public
reset()
{
- this.element.removeChildren();
-
super.reset();
- }
- shown()
- {
- super.shown();
+ this._recordsWithoutStartTime = new Set;
- this._timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._handleRecordAdded, this);
- }
+ this.element.removeChildren();
- hidden()
- {
- this._timeline.removeEventListener(null, null, this);
+ this._nextDumpRow = 0;
+ this._timelineRecordGridRows = [];
+
+ for (let i = 0; i < MediaTimelineOverviewGraph.maximumRowCount; ++i) {
+ let rowElement = this.element.appendChild(document.createElement("div"));
+ rowElement.className = "graph-row";
- super.hidden();
+ this._timelineRecordGridRows.push({
+ records: [],
+ recordBars: [],
+ element: rowElement,
+ });
+ }
}
// Protected
if (!this.visible)
return;
+ let secondsPerPixel = this.timelineOverview.secondsPerPixel;
let recordBarIndex = 0;
- let createBar = (records, renderMode) => {
- let timelineRecordBar = this._recordBars[recordBarIndex];
- if (timelineRecordBar) {
+ let createBar = (element, recordBars, records, renderMode) => {
+ let timelineRecordBar = recordBars[recordBarIndex];
+ if (!timelineRecordBar)
+ timelineRecordBar = recordBars[recordBarIndex] = new WI.TimelineRecordBar(this, records, renderMode);
+ else {
timelineRecordBar.renderMode = renderMode;
timelineRecordBar.records = records;
- } else
- timelineRecordBar = this._recordBars[recordBarIndex] = new WI.TimelineRecordBar(this, records, renderMode);
+ }
timelineRecordBar.refresh(this);
if (!timelineRecordBar.element.parentNode)
- this.element.appendChild(timelineRecordBar.element);
+ element.appendChild(timelineRecordBar.element);
++recordBarIndex;
};
- WI.TimelineRecordBar.createCombinedBars(this._timeline.records, this.timelineOverview.secondsPerPixel, this, createBar);
+ for (let {records, recordBars, element} of this._timelineRecordGridRows) {
+ recordBarIndex = 0;
+
+ WI.TimelineRecordBar.createCombinedBars(records, secondsPerPixel, this, createBar.bind(this, element, recordBars));
- // Remove the remaining unused TimelineRecordBars.
- for (let i = recordBarIndex; i < this._recordBars.length; ++i) {
- this._recordBars[i].records = null;
- this._recordBars[i].element.remove();
+ // Remove the remaining unused `WI.TimelineRecordBar`.
+ for (; recordBarIndex < recordBars.length; ++recordBarIndex) {
+ recordBars[recordBarIndex].records = null;
+ recordBars[recordBarIndex].element.remove();
+ }
}
}
{
super.updateSelectedRecord();
- for (let recordBar of this._recordBars) {
- if (recordBar.records.includes(this.selectedRecord)) {
- this.selectedRecordBar = recordBar;
- return;
+ for (let {recordBars} of this._timelineRecordGridRows) {
+ for (let recordBar of recordBars) {
+ if (recordBar.records.includes(this.selectedRecord)) {
+ this.selectedRecordBar = recordBar;
+ return;
+ }
}
}
// Private
+ _processRecord(record)
+ {
+ console.assert(record instanceof WI.MediaTimelineRecord);
+
+ if (isNaN(record.startTime)) {
+ this._recordsWithoutStartTime.add(record);
+ record.singleFireEventListener(WI.TimelineRecord.Event.Updated, (event) => {
+ this._processRecord(record);
+
+ this.needsLayout();
+ });
+ return;
+ }
+
+ this._recordsWithoutStartTime.delete(record);
+
+ function compareByStartTime(a, b) {
+ return a.startTime - b.startTime;
+ }
+
+ let minimumBarPaddingTime = WI.TimelineOverview.MinimumDurationPerPixel * (WI.TimelineRecordBar.MinimumWidthPixels + WI.TimelineRecordBar.MinimumMarginPixels);
+
+ // Try to find a row that has room and does not overlap a previous record.
+ for (let i = 0; i < this._timelineRecordGridRows.length; ++i) {
+ let records = this._timelineRecordGridRows[i].records;
+ let lastRecord = records.lastValue;
+ if (!lastRecord || lastRecord.endTime + minimumBarPaddingTime <= record.startTime) {
+ insertObjectIntoSortedArray(record, records, compareByStartTime);
+ this._nextDumpRow = i + 1;
+ return;
+ }
+ }
+
+ // Try to find a row that does not overlap a previous record's active time, but it can overlap the inactive time.
+ for (let i = 0; i < this._timelineRecordGridRows.length; ++i) {
+ let records = this._timelineRecordGridRows[i].records;
+ let lastRecord = records.lastValue;
+ console.assert(lastRecord);
+ if (lastRecord.usesActiveStartTime && lastRecord.activeStartTime + minimumBarPaddingTime <= record.startTime) {
+ insertObjectIntoSortedArray(record, records, compareByStartTime);
+ this._nextDumpRow = i + 1;
+ break;
+ }
+ }
+
+ // We didn't find a empty spot, so dump into the designated dump row.
+ if (this._nextDumpRow >= MediaTimelineOverviewGraph.maximumRowCount)
+ this._nextDumpRow = 0;
+ insertObjectIntoSortedArray(record, this._timelineRecordGridRows[this._nextDumpRow++].records, compareByStartTime);
+ }
+
_handleRecordAdded(event)
{
+ this._processRecord(event.data.record);
+
+ this.needsLayout();
+ }
+
+ _handleTimesUpdated(event)
+ {
this.needsLayout();
}
};
title: WI.UIString("Element"),
width: "150px",
sortable: true,
- },
- time: {
- title: WI.UIString("Time"),
- width: "80px",
- sortable: true,
locked: true,
},
- originator: {
- title: WI.UIString("Originator"),
- width: "150px",
- sortable: true,
- hidden: true,
- },
graph: {
headerView: this._timelineRuler,
+ sortable: true,
locked: true,
},
};
this._dataGrid = new WI.TimelineDataGrid(columns);
this._dataGrid.sortDelegate = this;
- this._dataGrid.sortColumnIdentifier = "time";
+ this._dataGrid.sortColumnIdentifier = "graph";
this._dataGrid.sortOrder = WI.DataGrid.SortOrder.Ascending;
- this._dataGrid.setColumnVisible("originator", false);
this._dataGrid.createSettings("media-timeline-view");
this.setupDataGrid(this._dataGrid);
this.addSubview(this._dataGrid);
return [pathComponent];
}
+ shown()
+ {
+ super.shown();
+
+ this._dataGrid.shown();
+ }
+
+ hidden()
+ {
+ this._dataGrid.hidden();
+
+ super.hidden();
+ }
+
closed()
{
- this.representedObject.removeEventListener(null, null, this);
+ this.representedObject.removeEventListener(WI.Timeline.Event.RecordAdded, this._handleRecordAdded, this);
+
+ this._dataGrid.closed();
super.closed();
}
reset()
{
- super.reset();
+ this._dataGrid.reset();
this._pendingRecords = [];
+
+ super.reset();
}
// TimelineDataGrid delegate
return 1;
if (!a && !b)
return 0;
- return a.id - b.id;
+ return a.displayName.extendedLocaleCompare(b.displayName);
}
if (sortColumnIdentifier === "name") {
if (sortColumnIdentifier === "element")
return compareDOMNodes(node1.record.domNode, node2.record.domNode) * sortDirection;
- if (sortColumnIdentifier === "time")
+ if (sortColumnIdentifier === "graph")
return (node1.record.startTime - node2.record.startTime) * sortDirection;
- if (sortColumnIdentifier === "originator") {
- function getOriginator(record) {
- if (record.eventType !== WI.MediaTimelineRecord.EventType.DOMEvent || !record.domEvent)
- return null;
- return record.domEvent.originator;
- }
- return compareDOMNodes(getOriginator(node1.record), getOriginator(node2.record)) * sortDirection;
- }
-
return null;
}
return;
for (let timelineRecord of this._pendingRecords) {
- if (timelineRecord.domEvent && timelineRecord.domEvent.originator)
- this._dataGrid.setColumnVisible("originator", true);
-
this._dataGrid.addRowInSortOrder(new WI.MediaTimelineDataGridNode(timelineRecord, {
graphDataSource: this,
}));
return fragment;
}
+ if (value instanceof WI.DOMNode) {
+ cell.classList.add(WI.DOMTreeElementPathComponent.iconClassNameForNode(value));
+ return WI.linkifyNodeReference(value);
+ }
+
return super.createCellContent(columnIdentifier, cell);
}
content: url(../Images/TimelineRecordTimer.svg);
}
-.animation-record .icon {
- content: url(../Images/TimelineRecordAnimation.svg);
+.animation-frame-record .icon {
+ content: url(../Images/TimelineRecordAnimationFrame.svg);
}
-.heap-snapshot-record .icon {
- content: url(../Images/HeapSnapshot.svg);
+.css-animation-record .icon {
+ content: url(../Images/TimelineRecordCSSAnimation.svg);
}
-.dom-event-record .icon {
- content: url(../Images/DOMEvent.svg);
+.css-transition-record .icon {
+ content: url(../Images/TimelineRecordCSSTransition.svg);
}
-.dom-event-record.fullscreen .icon {
- content: url(../Images/DOMEventFullscreen.svg);
+.media-element-record .icon {
+ content: url(../Images/TimelineRecordMediaElement.svg);
}
-.power-efficient-playback-state-changed-record .icon {
- content: url(../Images/PowerEfficientPlaybackStateChanged.svg);
+.heap-snapshot-record .icon {
+ content: url(../Images/HeapSnapshot.svg);
}
@media (prefers-color-scheme: dark) {
.timeline-record-bar {
position: absolute;
+ z-index: var(--timeline-record-z-index);
height: 12px;
+}
+
+.timeline-record-bar > :matches(img, .segment) {
+ position: absolute;
z-index: var(--timeline-record-z-index);
+}
- --timeline-record-bar-has-inactive-segment-not-inactive-segment-border-start: 1px solid hsla(215, 67%, 53%, 0.7) !important;
+.timeline-record-bar > img {
+ width: 14px;
+ height: 14px;
+ margin-top: -1px;
+ -webkit-margin-start: -7px;
}
.timeline-record-bar > .segment {
- position: absolute;
+ min-width: 4px;
height: 100%;
background-color: hsl(0, 0%, 88%);
- border: 1px solid hsl(0, 0%, 78%);
- border-radius: 3px;
- min-width: 4px;
- z-index: var(--timeline-record-z-index);
+ border-top: 1px solid var(--record-bar-segment-border-color);
+ border-bottom: 1px solid var(--record-bar-segment-border-color);
+
+ --record-bar-segment-border-color: hsl(0, 0%, 78%);
+ --record-bar-segment-border-start: 1px solid var(--record-bar-segment-border-color);
+ --record-bar-segment-border-end: 1px solid var(--record-bar-segment-border-color);
+ --record-bar-segment-border-start-radius: 3px;
+ --record-bar-segment-border-end-radius: 3px;
+}
+
+body[dir=ltr] .timeline-record-bar > .segment {
+ border-right: var(--record-bar-segment-border-end);
+ border-left: var(--record-bar-segment-border-start);
+}
+
+body[dir=ltr] .timeline-record-bar > .segment:first-of-type {
+ border-top-left-radius: var(--record-bar-segment-border-start-radius);
+ border-bottom-left-radius: var(--record-bar-segment-border-start-radius);
+}
+
+body[dir=ltr] .timeline-record-bar > .segment:last-of-type {
+ border-top-right-radius: var(--record-bar-segment-border-end-radius);
+ border-bottom-right-radius: var(--record-bar-segment-border-end-radius);
+}
+
+body[dir=rtl] .timeline-record-bar > .segment {
+ border-right: var(--record-bar-segment-border-start);
+ border-left: var(--record-bar-segment-border-end);
+}
+
+body[dir=rtl] .timeline-record-bar > .segment:first-of-type {
+ border-top-left-radius: var(--record-bar-segment-border-end-radius);
+ border-bottom-left-radius: var(--record-bar-segment-border-end-radius);
+}
+
+body[dir=rtl] .timeline-record-bar > .segment:last-of-type {
+ border-top-right-radius: var(--record-bar-segment-border-start-radius);
+ border-bottom-right-radius: var(--record-bar-segment-border-start-radius);
+}
+
+.timeline-record-bar > .segment:not(:last-of-type) {
+ --record-bar-segment-border-end: none;
}
.timeline-record-bar.selected > .segment {
background-color: var(--selected-text-background-color) !important;
- border-color: var(--glyph-color-active) !important;
+ --record-bar-segment-border-color: var(--glyph-color-active) !important;
}
.timeline-record-bar:not(.has-inactive-segment) > .segment {
z-index: 0;
}
-/*
-The selector `.timeline-record-bar.has-inactive-segment > .segment:not(.inactive)` is used to
-differentiate between the "active" and "inactive" parts of a network entry. As an example, the
-"active" part would be the Response and the "inactive" part would be the Request. This selector
-ensures that there is a different style between "active" and "inactive" only when the network entry
-has an "inactive" segment.
-*/
-
-body[dir=ltr] .timeline-record-bar > .segment.inactive,
-body[dir=ltr] .timeline-record-bar.unfinished > .segment,
-body[dir=rtl] .timeline-record-bar.has-inactive-segment > .segment:not(.inactive) {
- border-top-right-radius: 0 !important;
- border-bottom-right-radius: 0 !important;
- border-right: none;
+.timeline-record-bar > .segment.inactive,
+.timeline-record-bar.unfinished > .segment:last-child {
+ --record-bar-segment-border-end: none;
+ --record-bar-segment-border-end-radius: 0;
}
-body[dir=ltr] .timeline-record-bar.has-inactive-segment > .segment:not(.inactive),
-body[dir=rtl] .timeline-record-bar > .segment.inactive,
-body[dir=rtl] .timeline-record-bar.unfinished > .segment {
- border-top-left-radius: 0 !important;
- border-bottom-left-radius: 0 !important;
+.timeline-record-bar.has-inactive-segment > .segment:not(.inactive) {
+ --record-bar-segment-border-start-radius: 0;
}
-:focus .selected .timeline-record-bar > .segment {
+:focus .selected .timeline-record-bar:not(.has-custom-children) > .segment {
background-color: white !important;
border: none !important;
}
-:focus .selected .timeline-record-bar > .segment.inactive {
+:focus .selected .timeline-record-bar:not(.has-custom-children) > .segment.inactive {
background-color: hsl(215, 63%, 85%) !important;
}
-body[dir=ltr] :focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive) {
- border-left: var(--timeline-record-bar-has-inactive-segment-not-inactive-segment-border-start);
-}
-
-body[dir=rtl] :focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive) {
- border-right: var(--timeline-record-bar-has-inactive-segment-not-inactive-segment-border-start);
+:focus .selected .timeline-record-bar.has-inactive-segment > .segment:not(.inactive) {
+ --record-bar-segment-border-start: 1px solid hsla(215, 67%, 53%, 0.7);
}
.timeline-record-bar.timeline-record-type-network > .segment {
background-color: hsl(207, 63%, 67%);
- border-color: hsl(202, 55%, 51%);
+ --record-bar-segment-border-color: hsl(202, 55%, 51%);
}
.timeline-record-bar.timeline-record-type-network > .segment.inactive {
background-color: hsl(208, 66%, 79%);
- border-color: hsl(202, 57%, 68%);
+ --record-bar-segment-border-color: hsl(202, 57%, 68%);
}
.timeline-record-bar.timeline-record-type-layout > .segment {
background-color: hsl(0, 65%, 75%);
- border-color: hsl(0, 54%, 62%);
+ --record-bar-segment-border-color: hsl(0, 54%, 62%);
}
.timeline-record-bar.timeline-record-type-layout.paint > .segment,
.timeline-record-bar.timeline-record-type-layout.composite > .segment {
background-color: hsl(76, 49%, 60%);
- border-color: hsl(79, 45%, 51%);
+ --record-bar-segment-border-color: hsl(79, 45%, 51%);
}
.timeline-record-bar.timeline-record-type-script > .segment {
background-color: hsl(269, 65%, 74%);
- border-color: hsl(273, 33%, 58%);
+ --record-bar-segment-border-color: hsl(273, 33%, 58%);
}
.timeline-record-bar.timeline-record-type-script.garbage-collected > .segment,
.timeline-record-bar.timeline-record-type-heap-allocations > .segment {
background-color: hsl(23, 69%, 73%);
- border-color: hsl(11, 54%, 62%);
+ --record-bar-segment-border-color: hsl(11, 54%, 62%);
}
.timeline-record-bar.timeline-record-type-media > .segment {
background-color: hsl(143, 24%, 66%);
- border-color: hsl(153, 24%, 51%);
+ --record-bar-segment-border-color: hsl(153, 24%, 51%);
+}
+
+.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment {
+ height: 8px;
+ margin-top: 2px;
+}
+
+.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment.css-animation-ready {
+ background-color: var(--text-color-quaternary);
+}
+
+.timeline-record-bar.has-custom-children.timeline-record-type-media > .segment:matches(.css-animation-delay, .media-element-paused) {
+ background-color: hsl(143, 24%, 84%);
+}
+
+.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment {
+ --record-bar-segment-border-start-radius: 0;
+ --record-bar-segment-border-end-radius: 0;
+}
+
+.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment.media-element-full-screen {
+ height: 14px;
+ margin-top: -1px;
+ background-color: lightgrey;
+ border: none;
+}
+
+.timeline-record-bar.has-custom-children.timeline-record-type-media.media-element > .segment.media-element-power-efficient-playback {
+ height: 12px;
+ margin-top: 0;
+ background-color: var(--network-request-color);
+ border: none;
}
{
super();
- this._delegate = delegate;
+ this._delegate = delegate || null;
this._element = document.createElement("div");
this._element.classList.add("timeline-record-bar");
this._element.classList.remove(oldRecordEventType);
this._element.classList.add(newRecord.eventType);
}
- if (newRecord.usesActiveStartTime !== oldRecordUsesActiveStartTime)
- this._element.classList.toggle("has-inactive-segment", newRecord.usesActiveStartTime);
+ if (newRecord.usesActiveStartTime !== oldRecordUsesActiveStartTime) {
+ if (!this._delegate || !this._delegate.timelineRecordBarCustomChildren)
+ this._element.classList.toggle("has-inactive-segment", newRecord.usesActiveStartTime);
+ }
} else
this._element.classList.remove(oldRecordType, oldRecordEventType, "has-inactive-segment");
}
refresh(graphDataSource)
{
if (isNaN(graphDataSource.secondsPerPixel))
- return;
+ return false;
console.assert(graphDataSource.zeroTime);
console.assert(graphDataSource.startTime);
if (barUnfinished)
barEndTime = graphCurrentTime;
+ var barDuration = barEndTime - barStartTime;
+
var graphDuration = graphEndTime - graphStartTime;
let newBarPosition = (barStartTime - graphStartTime) / graphDuration;
var newBarWidth = ((barEndTime - graphStartTime) / graphDuration) - newBarPosition;
this._updateElementPosition(this._element, newBarWidth, "width");
+ if (this._delegate && this._delegate.timelineRecordBarCustomChildren) {
+ this._element.removeChildren();
+
+ this._element.classList.add("has-custom-children");
+ this._element.classList.toggle("unfinished", barUnfinished);
+
+ let children = this._delegate.timelineRecordBarCustomChildren(this);
+ for (let child of children) {
+ let childElement;
+ if (child.image) {
+ childElement = this._element.appendChild(document.createElement("img"));
+ childElement.src = child.image;
+ } else
+ childElement = this._element.appendChild(document.createElement("div"));
+
+ childElement.classList.add(...child.classNames);
+ childElement.title = child.title;
+ this._updateElementPosition(childElement, (child.startTime - barStartTime) / barDuration, property);
+
+ if (typeof child.endTime === "number") {
+ let childEndTime = !isNaN(child.endTime) ? child.endTime : barEndTime;
+ this._updateElementPosition(childElement, (childEndTime - child.startTime) / barDuration, "width");
+ }
+ }
+
+ return true;
+ }
+
+ this._element.classList.remove("has-custom-children");
+
if (!this._activeBarElement && this._renderMode !== WI.TimelineRecordBar.RenderMode.InactiveOnly) {
this._activeBarElement = document.createElement("div");
this._activeBarElement.classList.add("segment");
else
var barActiveStartTime = this._records.reduce(function(previousValue, currentValue) { return Math.max(previousValue, currentValue.activeStartTime); }, 0);
- var barDuration = barEndTime - barStartTime;
-
var inactiveUnfinished = isNaN(barActiveStartTime) || barActiveStartTime >= graphCurrentTime;
this._element.classList.toggle("unfinished", inactiveUnfinished);
// Ensure that the container "click" listener added by `WI.TimelineOverview` isn't called.
event.__timelineRecordClickEventHandled = true;
- if (this._delegate.timelineRecordBarClicked)
+ if (this._delegate && this._delegate.timelineRecordBarClicked)
this._delegate.timelineRecordBarClicked(this);
}
};
WI.TimelineRecordTreeElement.EvaluatedRecordIconStyleClass = "evaluated-record";
WI.TimelineRecordTreeElement.EventRecordIconStyleClass = "event-record";
WI.TimelineRecordTreeElement.TimerRecordIconStyleClass = "timer-record";
-WI.TimelineRecordTreeElement.AnimationRecordIconStyleClass = "animation-record";
WI.TimelineRecordTreeElement.ProbeRecordIconStyleClass = "probe-record";
WI.TimelineRecordTreeElement.ConsoleProfileIconStyleClass = "console-profile-record";
WI.TimelineRecordTreeElement.GarbageCollectionIconStyleClass = "garbage-collection-profile-record";
case WI.TimelineRecord.Type.HeapAllocations:
return WI.UIString("JavaScript Allocations");
case WI.TimelineRecord.Type.Media:
+ // COMPATIBILITY (iOS 13): Animation domain did not exist yet.
+ if (InspectorBackend.hasDomain("Animation"))
+ return WI.UIString("Media & Animations");
return WI.UIString("Media");
default:
console.error("Unknown Timeline type:", timelineType);
case WI.ScriptTimelineRecord.EventType.AnimationFrameFired:
case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested:
case WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled:
- return WI.TimelineRecordTreeElement.AnimationRecordIconStyleClass;
+ return "animation-frame-record";
default:
console.error("Unknown ScriptTimelineRecord eventType: " + timelineRecord.eventType, timelineRecord);
}
case WI.TimelineRecord.Type.Media:
switch (timelineRecord.eventType) {
- case WI.MediaTimelineRecord.EventType.DOMEvent:
- return "dom-event-record";
- case WI.MediaTimelineRecord.EventType.PowerEfficientPlaybackStateChanged:
- return "power-efficient-playback-state-changed-record";
+ case WI.MediaTimelineRecord.EventType.CSSAnimation:
+ return "css-animation-record";
+ case WI.MediaTimelineRecord.EventType.CSSTransition:
+ return "css-transition-record";
+ case WI.MediaTimelineRecord.EventType.MediaElement:
+ return "media-element-record";
default:
console.error("Unknown MediaTimelineRecord eventType: " + timelineRecord.eventType, timelineRecord);
}
return WI.UIString("Snapshot %d \u2014 %s").format(timelineRecord.heapSnapshot.identifier, timelineRecord.heapSnapshot.title);
return WI.UIString("Snapshot %d").format(timelineRecord.heapSnapshot.identifier);
case WI.TimelineRecord.Type.Media:
+ // Since the `displayName` can be specific to an `animation-name`/`transition-property`,
+ // use the generic `subtitle` text instead of we are rendering from the overview.
+ if (includeDetailsInMainTitle && timelineRecord.subtitle)
+ return timelineRecord.subtitle;
return timelineRecord.displayName;
case WI.TimelineRecord.Type.CPU:
case WI.TimelineRecord.Type.Memory:
+2019-11-01 Devin Rousso <drousso@apple.com>
+
+ Web Inspector: Timelines: add a timeline that shows information about any recorded CSS animation/transition
+ https://bugs.webkit.org/show_bug.cgi?id=203651
+ <rdar://problem/56128726>
+
+ Reviewed by Brian Burg.
+
+ * TestWebKitAPI/Tests/WTF/Markable.cpp:
+ (TestWebKitAPI::TEST):
+ Add tests for extra utility operators.
+
2019-11-01 Brady Eidson <beidson@apple.com>
Promote "_getWebArchive" to API.
}
}
+TEST(WTF_Markable, Equality)
+{
+ Markable<int, IntegralMarkableTraits<int, 42>> unengaged1;
+ Markable<int, IntegralMarkableTraits<int, 42>> unengaged2;
+
+ Markable<int, IntegralMarkableTraits<int, 42>> engaged1 { 1 };
+ Markable<int, IntegralMarkableTraits<int, 42>> engaged2 { 2 };
+ Markable<int, IntegralMarkableTraits<int, 42>> engagedx2 { 2 };
+
+ EXPECT_TRUE(unengaged1 == unengaged2);
+ EXPECT_FALSE(engaged1 == engaged2);
+ EXPECT_FALSE(engaged1 == unengaged1);
+ EXPECT_TRUE(engaged2 == engagedx2);
+
+ EXPECT_FALSE(unengaged1 == 42);
+ EXPECT_FALSE(engaged1 == 42);
+ EXPECT_FALSE(42 == unengaged1);
+ EXPECT_FALSE(42 == engaged1);
+
+ EXPECT_TRUE(engaged1 == 1);
+ EXPECT_FALSE(engaged1 == 2);
+ EXPECT_TRUE(1 == engaged1);
+ EXPECT_FALSE(2 == engaged1);
+
+ EXPECT_FALSE(unengaged1 == 1);
+ EXPECT_FALSE(1 == unengaged1);
+}
+
+TEST(WTF_Markable, Inequality)
+{
+ Markable<int, IntegralMarkableTraits<int, 42>> unengaged1;
+ Markable<int, IntegralMarkableTraits<int, 42>> unengaged2;
+
+ Markable<int, IntegralMarkableTraits<int, 42>> engaged1 { 1 };
+ Markable<int, IntegralMarkableTraits<int, 42>> engaged2 { 2 };
+ Markable<int, IntegralMarkableTraits<int, 42>> engagedx2 { 2 };
+
+ EXPECT_FALSE(unengaged1 != unengaged2);
+ EXPECT_TRUE(engaged1 != engaged2);
+ EXPECT_TRUE(engaged1 != unengaged1);
+ EXPECT_FALSE(engaged2 != engagedx2);
+
+ EXPECT_TRUE(unengaged1 != 42);
+ EXPECT_TRUE(engaged1 != 42);
+ EXPECT_TRUE(42 != unengaged1);
+ EXPECT_TRUE(42 != engaged1);
+
+ EXPECT_FALSE(engaged1 != 1);
+ EXPECT_TRUE(engaged1 != 2);
+ EXPECT_FALSE(1 != engaged1);
+ EXPECT_TRUE(2 != engaged1);
+
+ EXPECT_TRUE(unengaged1 != 1);
+ EXPECT_TRUE(1 != unengaged1);
+}
+
} // namespace TestWebKitAPI