315 lines
12 KiB
JavaScript
315 lines
12 KiB
JavaScript
/*
|
|
* Copyright (C) 2018 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.MediaTimelineRecord = class MediaTimelineRecord extends WI.TimelineRecord
|
|
{
|
|
constructor(eventType, domNodeOrInfo, {trackingAnimationId, animationName, transitionProperty} = {})
|
|
{
|
|
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);
|
|
|
|
this._eventType = eventType;
|
|
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 async fromJSON(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 documentNode.querySelector(domNodeCSSPath);
|
|
if (nodeId)
|
|
domNode = WI.domManager.nodeForId(nodeId);
|
|
} catch { }
|
|
}
|
|
if (!domNode) {
|
|
domNode = {
|
|
displayName: domNodeDisplayName,
|
|
cssPath: domNodeCSSPath,
|
|
};
|
|
}
|
|
|
|
let record = new MediaTimelineRecord(eventType, domNode, {animationName, transitionProperty});
|
|
|
|
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 documentNode.querySelector(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 record;
|
|
}
|
|
|
|
toJSON()
|
|
{
|
|
let json = {
|
|
eventType: this._eventType,
|
|
domNodeDisplayName: this._domNodeDisplayName,
|
|
domNodeCSSPath: this._domNodeCSSPath,
|
|
};
|
|
|
|
if (this._animationName)
|
|
json.animationName = this._animationName;
|
|
|
|
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 json;
|
|
}
|
|
|
|
// Public
|
|
|
|
get eventType() { return this._eventType; }
|
|
get domNode() { return this._domNode; }
|
|
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()
|
|
{
|
|
switch (this._eventType) {
|
|
case MediaTimelineRecord.EventType.CSSAnimation:
|
|
return this._animationName;
|
|
|
|
case MediaTimelineRecord.EventType.CSSTransition:
|
|
return this._transitionProperty;
|
|
|
|
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;
|
|
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 = {
|
|
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",
|
|
};
|