diff --git a/eme-trace-config.js b/eme-trace-config.js index 588d331..3d107d6 100644 --- a/eme-trace-config.js +++ b/eme-trace-config.js @@ -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). @@ -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); diff --git a/spec/eme-trace-tests.js b/spec/eme-trace-tests.js index e32f89e..f571f08 100644 --- a/spec/eme-trace-tests.js +++ b/spec/eme-trace-tests.js @@ -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({ @@ -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 */ + })); }); }); @@ -404,4 +424,8 @@ describe('EME tracing', () => { })); }); }); + + async function delay(seconds) { + await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); + } }); diff --git a/trace-anything.js b/trace-anything.js index 5deca62..251e475 100644 --- a/trace-anything.js +++ b/trace-anything.js @@ -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. @@ -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; + } } } @@ -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 @@ -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. @@ -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)>} 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} exploreResultFields * Explore specific fields of the results of a method. This allows tracing * into return values that are plain objects. @@ -924,6 +969,7 @@ TraceAnything.defaultOptions = { events: true, extraEvents: [], skipEvents: [], + eventProperties: {}, exploreResultFields: [], logger: TraceAnything.defaultLogger, logAsyncResultsImmediately: true,