Skip to content

Commit

Permalink
feat: Add configurable mapping of event names to associated properties
Browse files Browse the repository at this point in the history
Now events can have associated properties logged even if the property
name bears no relation to the event name.  Events can now also have
multiple properties associated with them.

Change-Id: Icf88ca8ea4bd061d6e4ce7d0d05e940e5935147d
  • Loading branch information
joeyparrish committed Nov 8, 2021
1 parent 16ae0f5 commit d4007cf
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 10 deletions.
14 changes: 14 additions & 0 deletions eme-trace-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ TraceAnything.traceClass(MediaKeySession, combineOptions(options, {
}));

// Trace media element types, and monitor the document for new instances.
const playbackStateProperties = [
'currentTime',
'paused',
'ended',
];
const elementOptions = combineOptions(options, {
// Skip all property access on media elements.
// It's a little noisy and unhelpful (currentTime getter, for example).
Expand All @@ -299,6 +304,15 @@ const elementOptions = combineOptions(options, {
'querySelector',
'querySelectorAll',
]),

eventProperties: {
'ratechange': 'playbackRate',
'resize': 'getBoundingClientRect',
'play': playbackStateProperties,
'playing': playbackStateProperties,
'pause': playbackStateProperties,
'ended': playbackStateProperties,
},
});
TraceAnything.traceElement('video', elementOptions);
TraceAnything.traceElement('audio', elementOptions);
24 changes: 24 additions & 0 deletions spec/eme-trace-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,24 @@ describe('EME tracing', () => {
encryptedEvent.initData = new Uint8Array([1, 2, 3]);
mediaElement.dispatchEvent(encryptedEvent);

// Should trigger a ratechange event with this value associated, even
// though the event name differs from the property name.
mediaElement.playbackRate = 2;

// A small delay for the ratechange event to fire.
await delay(0.5);

expect(emeLogger).toHaveBeenCalledWith(
jasmine.objectContaining({
'type': TraceAnything.LogTypes.Event,
'className': 'HTMLVideoElement',
'eventName': 'play',
// Playback events have multiple values associated:
'value': jasmine.objectContaining({
'currentTime': jasmine.any(Number),
'paused': jasmine.any(Boolean),
'ended': jasmine.any(Boolean),
}),
}));
expect(emeLogger).toHaveBeenCalledWith(
jasmine.objectContaining({
Expand All @@ -340,6 +353,13 @@ describe('EME tracing', () => {
'initData': new Uint8Array([1, 2, 3]),
}),
}));
expect(emeLogger).toHaveBeenCalledWith(
jasmine.objectContaining({
'type': TraceAnything.LogTypes.Event,
'className': 'HTMLVideoElement',
'eventName': 'ratechange',
'value': 2, /* playbackRate set above */
}));
});
});

Expand Down Expand Up @@ -404,4 +424,8 @@ describe('EME tracing', () => {
}));
});
});

async function delay(seconds) {
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
});
66 changes: 56 additions & 10 deletions trace-anything.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ class TraceAnything {
/**
* Shim an event listener for tracing.
*
* @param {!Object} object The traced object.
* @param {!Object} traced The traced object.
* @param {function} listener The event listener.
* @param {string} k The member name.
* @param {string} className The class name.
Expand All @@ -651,16 +651,21 @@ class TraceAnything {
* @return {function} A shim for the event listener which logs events.
* @private
*/
static _shimEventListener(object, listener, className, eventName, options) {
static _shimEventListener(traced, listener, className, eventName, options) {
// If this event corresponds to a change in a specific property, try to
// find it now. Then we can log the specific value it has in the listener.
let correspondingPropertyName = null;
const canonicalEventName = eventName.toLowerCase();
const lowerCasePropertyName = canonicalEventName.replace(/change$/, '');
for (const k in object) {
if (k.toLowerCase() == lowerCasePropertyName) {
correspondingPropertyName = k;
break;

if (eventName in options.eventProperties) {
correspondingPropertyName = options.eventProperties[eventName];
} else {
const canonicalEventName = eventName.toLowerCase();
const lowerCasePropertyName = canonicalEventName.replace(/change$/, '');
for (const k in traced) {
if (k.toLowerCase() == lowerCasePropertyName) {
correspondingPropertyName = k;
break;
}
}
}

Expand All @@ -673,9 +678,20 @@ class TraceAnything {
eventName,
event,
};
if (correspondingPropertyName) {
log.value = object[correspondingPropertyName];

// The corresponding property may be an array of multiple properties
// which should be logged with this event. If so, create an Object
// mapping names to values.
if (Array.isArray(correspondingPropertyName)) {
log.value = {};
for (const name of correspondingPropertyName) {
log.value[name] = TraceAnything._extractProperty(traced, name);
}
} else if (correspondingPropertyName) {
log.value = TraceAnything._extractProperty(
traced, correspondingPropertyName);
}

options.logger(log);

// This supports the EventListener interface, in which "listener" could be
Expand All @@ -688,6 +704,26 @@ class TraceAnything {
};
}

/**
* Extract a single property value from an object by name.
*
* @param {!Object} object The object from which to extract the value.
* @param {string} name The name of the property. If the property is a
* method, the method will be called to get the value.
* @return {?} The extracted value, which could be anything.
* @private
*/
static _extractProperty(object, name) {
const value = object[name];

// If the property is a method, call it now.
if (value instanceof Function) {
return value.call(object);
} else {
return value;
}
}

/**
* Find a property descriptor for a particular property of an object. This
* allows access to getters and setters.
Expand Down Expand Up @@ -889,6 +925,15 @@ TraceAnything.defaultLogger = (log) => {
* Skip event listeners for these events. This allows certain noisy events
* to be suppressed, while still tracing events generally.
* By default, empty.
* @property {!Object<string, (string|!Array<string>)>} eventProperties
* A map of event names to their associated properties. If a property matches
* an event name (case-insensitive, with "change" removed from the event
* name), its value will be logged with the event automatically. This
* configuration allows values to be associated with an event if the property
* name differs from the event name. An array of property names can also be
* used. If a property name refers to a method, the method will be called and
* its return value will be logged.
* By default, empty.
* @property {!Array<string>} exploreResultFields
* Explore specific fields of the results of a method. This allows tracing
* into return values that are plain objects.
Expand Down Expand Up @@ -924,6 +969,7 @@ TraceAnything.defaultOptions = {
events: true,
extraEvents: [],
skipEvents: [],
eventProperties: {},
exploreResultFields: [],
logger: TraceAnything.defaultLogger,
logAsyncResultsImmediately: true,
Expand Down

0 comments on commit d4007cf

Please sign in to comment.