Index: LayoutTests/imported/web-platform-tests/uievents/resources/eventrecorder.js |
diff --git a/LayoutTests/imported/web-platform-tests/uievents/resources/eventrecorder.js b/LayoutTests/imported/web-platform-tests/uievents/resources/eventrecorder.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..936b136d8b3bf86e25e349a2f8596d5c5ee014cc |
--- /dev/null |
+++ b/LayoutTests/imported/web-platform-tests/uievents/resources/eventrecorder.js |
@@ -0,0 +1,283 @@ |
+// interface EventRecorder { |
+// static void start(); |
+// static void stop(); |
+// static void clearRecords(); |
+// static sequence<EventRecord> getRecords(); |
+// static void configure(EventRecorderOptions options); |
+// }; |
+// * getRecords |
+// * returns an array of EventRecord objects; the array represents the sequence of events captured at anytime after the last clear() |
+// call, between when the recorder was started and stopped (including multiple start/stop pairs) |
+// * configure |
+// * sets options that should apply to the recorder. If the recorder has any existing records, than this API throws an exception. |
+// * start |
+// * starts/un-pauses the recorder |
+// * stop |
+// * stops/pauses the recorder |
+// * clear |
+// * purges all recorded records |
+ |
+// ---------------------- |
+ |
+// dictionary EventRecorderOptions { |
+// sequence<SupportedEventTypes> mergeEventTypes; |
+// ObjectNamedMap objectMap; |
+// }; |
+// * mergeEventTypes |
+// * a list of event types that should be consolidated into one record when all of the following conditions are true: |
+// 1) The events are of the same type and follow each other chronologically |
+// 2) The events' currentTarget is the same |
+// * The default is an empty list (no event types are merged). |
+// * objectMap |
+// * Sets up a series |
+ |
+// dictionary ObjectNamedMap { |
+// //<keys will be 'targetTestID' names, with values of the objects which they label> |
+// }; |
+// * targetTestID = the string identifier that the associated target object should be known as (for purposes of unique identification. This |
+// need not be the same as the Node's id attribute if it has one. If no 'targetTestID' string mapping is provided via this |
+// map, but is encountered later when recording specific events, a generic targetTestID of 'UNKNOWN_OBJECT' is used. |
+ |
+// ---------------------- |
+ |
+// dictionary EventRecord { |
+// unsigned long chronologicalOrder; |
+// unsigned long sequentialOccurrences; |
+// sequence<EventRecord>? nestedEvents; |
+// DOMString interfaceType; |
+// EventRecordDetails event; |
+// }; |
+// * chronologicalOrder |
+// * Since some events may be dispatched re-entrantly (e.g., while existing events are being dispatched), and others may be merged |
+// given the 'mergeEventTypes' option in the EventRecorder, this value is the actual chronological order that the event fired |
+// * sequentialOccurrences |
+// * If this event was fired multiple times in a row (see the 'mergeEventTypes' option), this value is the count of occurrences. |
+// A value of 1 means this was the only occurrence of this event (that no events were merged with it). A value greater than 1 |
+// indicates that the event occurred that many times in a row. |
+// * nestedEvents |
+// * The holds all the events that were sequentially dispatched synchronously while the current event was still being dispatched |
+// (e.g., between the time that this event listener was triggered and when it returned). |
+// * Has the value null if no nested events were recorded during the invocation of this listener. |
+// * interfaceType |
+// * The string indicating which Event object (or derived Event object type) the recorded event object instance is based on. |
+// * event |
+// * Access to the recorded event properties for the event instance (not the actual event instance itself). A snapshot of the |
+// enumerable properties of the event object instance at the moment the listener was first triggered. |
+ |
+// ---------------------- |
+ |
+// dictionary EventRecordDetails { |
+// //<recorded property names with their values for all enumerable properties of the event object instance> |
+// }; |
+// * EventRecordDetails |
+// * For records with 'sequentialOccurrences' > 1, only the first occurence is recorded (subsequent event details are dropped). |
+// * Object reference values (e.g., event.target, event.currentTarget, etc.) are replaced with their mapped 'targetTestID' string. |
+// If no 'targetTestID' string mapping is available for a particular object, the value 'UNKNOWN_OBJECT' is returned. |
+ |
+// ---------------------- |
+ |
+// [NoInterfaceObject] |
+// interface EventRecorderRegistration { |
+// void addRecordedEventListener(SupportedEventTypes type, EventListener? handler, optional boolean capturePhase = false); |
+// void removeRecordedEventListener(SupportedEventTypes type, EventListener? handler, optional boolean capturePhase = false); |
+// }; |
+// Node implements EventRecorderRegistration; |
+// |
+// enum SupportedEventTypes = { |
+// "mousemove", |
+// etc... |
+// }; |
+// * addRecordedEventListener |
+// * handler = pass null if you want only a default recording of the event (and don't need any other special handling). Otherwise, |
+// the handler will be invoked normally as part of the event's dispatch. |
+// * <other params> are the same as those defined on addEventListener/removeEventListenter APIs (see DOM4) |
+// * Use this API *instead of* addEventListener to record your events for testing purposes. |
+ |
+(function EventRecorderScope(global) { |
+ "use strict"; |
+ |
+ if (global.EventRecorder) |
+ return; // Already initialized. |
+ |
+ // WeakMap polyfill |
+ if (!global.WeakMap) { |
+ throw new Error("EventRecorder depends on WeakMap! Please polyfill for completeness to run in this user agent!"); |
+ } |
+ |
+ // Globally applicable variables |
+ var allRecords = []; |
+ var recording = false; |
+ var rawOrder = 1; |
+ var mergeTypesTruthMap = {}; // format of { eventType: true, ... } |
+ var eventsInScope = []; // Tracks synchronous event dispatches |
+ var handlerMap = new WeakMap(); // Keeps original handlers (so that they can be used to un-register for events. |
+ |
+ // Find all Event Object Constructors on the global and add them to the map along with their name (sans 'Event') |
+ var eventConstructorsNameMap = new WeakMap(); // format of key: hostObject, value: alias to use. |
+ var regex = /[A-Z][A-Za-z0-9]+Event$/; |
+ Object.getOwnPropertyNames(global).forEach(function (propName) { |
+ if (regex.test(propName)) |
+ eventConstructorsNameMap.set(global[propName], propName); |
+ }); |
+ var knownObjectsMap = eventConstructorsNameMap; |
+ |
+ Object.defineProperty(global, "EventRecorder", { |
+ writable: true, |
+ configurable: true, |
+ value: Object.create(null, { |
+ start: { |
+ enumerable: true, configurable: true, writable: true, value: function start() { recording = true; } |
+ }, |
+ stop: { |
+ enumerable: true, configurable: true, writable: true, value: function stop() { recording = false; } |
+ }, |
+ clearRecords: { |
+ enumerable: true, configurable: true, writable: true, value: function clearRecords() { |
+ rawOrder = 1; |
+ allRecords = []; |
+ } |
+ }, |
+ getRecords: { |
+ enumerable: true, configurable: true, writable: true, value: function getRecords() { return allRecords; } |
+ }, |
+ configure: { |
+ enumerable: true, configurable: true, writable: true, value: function configure(options) { |
+ if (allRecords.length > 0) |
+ throw new Error("Wrong time to call me: EventRecorder.configure must only be called when no recorded events are present. Try 'clearRecords' first."); |
+ |
+ // Un-configure existing options by calling again with no options set... |
+ mergeTypesTruthMap = {}; |
+ knownObjectsMap = eventConstructorsNameMap; |
+ |
+ if (!(options instanceof Object)) |
+ return; |
+ // Sanitize the passed object (tease-out getter functions) |
+ var sanitizedOptions = {}; |
+ for (var x in options) { |
+ sanitizedOptions[x] = options[x]; |
+ } |
+ if (sanitizedOptions.mergeEventTypes && Array.isArray(sanitizedOptions.mergeEventTypes)) { |
+ sanitizedOptions.mergeEventTypes.forEach(function (eventType) { |
+ if (typeof eventType == "string") |
+ mergeTypesTruthMap[eventType] = true; |
+ }); |
+ } |
+ if (sanitizedOptions.objectMap && (sanitizedOptions.objectMap instanceof Object)) { |
+ for (var y in sanitizedOptions.objectMap) { |
+ knownObjectsMap.set(sanitizedOptions.objectMap[y], y); |
+ } |
+ } |
+ } |
+ } |
+ }) |
+ }); |
+ |
+ function EventRecord(rawEvent) { |
+ this.chronologicalOrder = rawOrder++; |
+ this.sequentialOccurrences = 1; |
+ this.nestedEvents = null; // potentially a [] |
+ this.interfaceType = knownObjectsMap.get(rawEvent.constructor); |
+ if (!this.interfaceType) // In case (somehow) this event's constructor is not named something with an 'Event' suffix... |
+ this.interfaceType = rawEvent.constructor.toString(); |
+ this.event = new CloneObjectLike(rawEvent); |
+ } |
+ |
+ // Only enumerable props including prototype-chain (non-recursive), w/no functions. |
+ function CloneObjectLike(object) { |
+ for (var prop in object) { |
+ var val = object[prop]; |
+ if (Array.isArray(val)) |
+ this[prop] = CloneArray(val); |
+ else if (typeof val == "function") |
+ continue; |
+ else if ((typeof val == "object") && (val != null)) { |
+ this[prop] = knownObjectsMap.get(val); |
+ if (this[prop] === undefined) |
+ this[prop] = "UNKNOWN_OBJECT (" + val.toString() + ")"; |
+ } |
+ else |
+ this[prop] = val; |
+ } |
+ } |
+ |
+ function CloneArray(array) { |
+ var dup = []; |
+ for (var i = 0, len = array.length; i < len; i++) { |
+ var val = array[i] |
+ if (typeof val == "undefined") |
+ throw new Error("Ugg. Sparce arrays are not supported. Sorry!"); |
+ else if (Array.isArray(val)) |
+ dup[i] = "UNKNOWN_ARRAY"; |
+ else if (typeof val == "function") |
+ dup[i] = "UNKNOWN_FUNCTION"; |
+ else if ((typeof val == "object") && (val != null)) { |
+ dup[i] = knownObjectsMap.get(val); |
+ if (dup[i] === undefined) |
+ dup[i] = "UNKNOWN_OBJECT (" + val.toString() + ")"; |
+ } |
+ else |
+ dup[i] = val; |
+ } |
+ return dup; |
+ } |
+ |
+ function generateRecordedEventHandlerWithCallback(callback) { |
+ return function(e) { |
+ if (recording) { |
+ // Setup the scope for any synchronous events |
+ eventsInScope.push(recordEvent(e)); |
+ callback.call(this, e); |
+ eventsInScope.pop(); |
+ } |
+ } |
+ } |
+ |
+ function recordedEventHandler(e) { |
+ if (recording) |
+ recordEvent(e); |
+ } |
+ |
+ function recordEvent(e) { |
+ var record = new EventRecord(e); |
+ var recordList = allRecords; |
+ // Adjust which sequential list to use depending on scope |
+ if (eventsInScope.length > 0) { |
+ recordList = eventsInScope[eventsInScope.length - 1].nestedEvents; |
+ if (recordList == null) // This top-of-stack event record hasn't had any nested events yet. |
+ recordList = eventsInScope[eventsInScope.length - 1].nestedEvents = []; |
+ } |
+ if (mergeTypesTruthMap[e.type] && (recordList.length > 0)) { |
+ var tail = recordList[recordList.length-1]; |
+ // Same type and currentTarget? |
+ if ((tail.event.type == record.event.type) && (tail.event.currentTarget == record.event.currentTarget)) { |
+ tail.sequentialOccurrences++; |
+ return; |
+ } |
+ } |
+ recordList.push(record); |
+ return record; |
+ } |
+ |
+ Object.defineProperties(Node.prototype, { |
+ addRecordedEventListener: { |
+ enumerable: true, writable: true, configurable: true, |
+ value: function addRecordedEventListener(type, handler, capture) { |
+ if (handler == null) |
+ this.addEventListener(type, recordedEventHandler, capture); |
+ else { |
+ var subvertedHandler = generateRecordedEventHandlerWithCallback(handler); |
+ handlerMap.set(handler, subvertedHandler); |
+ this.addEventListener(type, subvertedHandler, capture); |
+ } |
+ } |
+ }, |
+ removeRecordedEventListener: { |
+ enumerable: true, writable: true, configurable: true, |
+ value: function addRecordedEventListener(type, handler, capture) { |
+ var alternateHandlerUsed = handlerMap.get(handler); |
+ this.removeEventListenter(type, alternateHandlerUsed ? alternateHandlerUsed : recordedEventHandler, capture); |
+ } |
+ } |
+ }); |
+ |
+})(window); |