Comparing version 0.3.1 to 0.4.0
@@ -11,16 +11,3 @@ (function (global, factory) { | ||
*/ | ||
var MSE_CALLS = { | ||
MediaSource: { | ||
new: [], | ||
methods: {}, | ||
properties: {}, | ||
eventListeners: {} // TODO | ||
}, | ||
SourceBuffer: { | ||
new: [], | ||
methods: {}, | ||
properties: {}, | ||
eventListeners: {} // TODO | ||
} | ||
}; | ||
var MSE_CALLS = {}; | ||
@@ -32,11 +19,5 @@ function getMSECalls() { | ||
function resetMSECalls() { | ||
MSE_CALLS.MediaSource.new = []; | ||
MSE_CALLS.MediaSource.methods = []; | ||
MSE_CALLS.MediaSource.properties = []; | ||
MSE_CALLS.MediaSource.events = []; | ||
MSE_CALLS.SourceBuffer.new = []; | ||
MSE_CALLS.SourceBuffer.methods = []; | ||
MSE_CALLS.SourceBuffer.properties = []; | ||
MSE_CALLS.SourceBuffer.events = []; | ||
Object.key(MSE_CALLS).forEach(function (key) { | ||
delete MSE_CALLS[key]; | ||
}); | ||
} | ||
@@ -140,10 +121,98 @@ var NativeMediaSource = window.MediaSource; | ||
console.info(">>> " + pathName + " succeeded:", value); | ||
}, | ||
/** | ||
* Triggered when a function returned a Promise and that promise resolved. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {*} value - The value when the function resolved. | ||
*/ | ||
onFunctionPromiseResolve: function onFunctionPromiseResolve(pathName, value) { | ||
console.info(">>> " + pathName + " resolved:", value); | ||
}, | ||
/** | ||
* Triggered when a function returned a Promise and that promise rejected. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {*} value - The error when the function's promise rejected. | ||
*/ | ||
onFunctionPromiseReject: function onFunctionPromiseReject(pathName, value) { | ||
console.error(">>> " + pathName + " rejected:", value); | ||
} | ||
}; | ||
function stubRegularMethods(obj, methods, path, logObj) { | ||
var id = 0; | ||
/** | ||
* Generate a new number each time it is called. | ||
* /!\ Never check for an upper-bound. Please do not use if you can reach | ||
* `Number.MAX_VALUE` | ||
* @returns {number} | ||
*/ | ||
function generateId() { | ||
return id++; | ||
} | ||
/** | ||
* Log multiple method calls for an object. | ||
* Also populates an object with multiple data at the time of the call. | ||
* | ||
* @param {Object} baseObject - Object in which the method/function is. | ||
* For example to spy on the Date method `toLocaleDateString`, you will have to | ||
* set here `Date.prototype`. | ||
* @param {Array.<string>} methodNames - Every methods you want to spy on | ||
* @param {string} humanReadablePath - Path to the method. Used for logging | ||
* purposes. | ||
* For example `"Date.prototype"`, for spies of Date's methods. | ||
* @param {Object} logObject - Object where infos about the method calls will be | ||
* added. | ||
* The methods' name will be the key of the object. | ||
* | ||
* The values will be an array of object with the following properties: | ||
* | ||
* - self {Object}: Reference to the baseObject argument. | ||
* | ||
* - id {number}: a uniquely generated ascending ID for any stubbed | ||
* property/methods with this library. | ||
* | ||
* - date {number}: Timestamp at the time of the call. | ||
* | ||
* - args {Array}: Array of arguments given to the function | ||
* | ||
* - response {*}: Response of the function. | ||
* The property is not defined if the function did not respond yet or was on | ||
* error. | ||
* | ||
* - responseDate {number}: Timestamp at the time of the response. | ||
* The property is not defined if the function did not respond yet or was on | ||
* error. | ||
* | ||
* - error {*}: Error thrown by the function, if one. | ||
* The property is not defined if the function did not throw. | ||
* | ||
* - errorDate {number} Timestamp at the time of the error. | ||
* The property is not defined if the function did not throw. | ||
* | ||
* - responseResolved {*}: When the returned value (the response) is a promise | ||
* and that promise resolved, this property contains the value emitted by | ||
* the resolve. Else, that property is not set. | ||
* | ||
* - responseResolvedDate {number}: When the returned value (the response) is | ||
* a promise and that promise resolved, this property contains the date at | ||
* which the promise resolved. Else, that property is not set. | ||
* | ||
* - responseRejected {*}: When the returned value (the response) is a promise | ||
* and that promise rejected, this property contains the error emitted by | ||
* the reject. Else, that property is not set. | ||
* | ||
* - responseRejectedDate {number}: When the returned value (the response) is | ||
* a promise and that promise rejected, this property contains the date at | ||
* which the promise rejected. Else, that property is not set. | ||
*/ | ||
function spyOnMethods(baseObject, methodNames, humanReadablePath, logObject) { | ||
var _loop = function _loop(i) { | ||
var methodName = methods[i]; | ||
var completePath = path + "." + methodName; | ||
var oldMethod = obj[methodName]; | ||
var methodName = methodNames[i]; | ||
var completePath = humanReadablePath + "." + methodName; | ||
var oldMethod = baseObject[methodName]; | ||
@@ -154,3 +223,3 @@ if (!oldMethod) { | ||
obj[methodName] = function () { | ||
baseObject[methodName] = function () { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
@@ -161,4 +230,5 @@ args[_key] = arguments[_key]; | ||
Logger.onFunctionCall(completePath, args); | ||
var myObj = { | ||
self: obj, | ||
var currentLogObject = { | ||
self: baseObject, | ||
id: generateId(), | ||
date: Date.now(), | ||
@@ -168,6 +238,6 @@ args: args | ||
if (!logObj[methodName]) { | ||
logObj[methodName] = []; | ||
if (!logObject[methodName]) { | ||
logObject[methodName] = []; | ||
} | ||
logObj[methodName].push(myObj); | ||
logObject[methodName].push(currentLogObject); | ||
@@ -179,9 +249,26 @@ var res = void 0; | ||
Logger.onFunctionCallError(completePath, e); | ||
myObj.error = e; | ||
myObj.errorDate = Date.now(); | ||
currentLogObject.error = e; | ||
currentLogObject.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onFunctionCallSuccess(completePath, res); | ||
myObj.response = res; | ||
myObj.responseDate = Date.now(); | ||
currentLogObject.response = res; | ||
currentLogObject.responseDate = Date.now(); | ||
if (res instanceof Promise) { | ||
res.then( | ||
// on success | ||
function (value) { | ||
Logger.onFunctionPromiseResolve(completePath, value); | ||
currentLogObject.responseResolved = value; | ||
currentLogObject.responseResolvedDate = Date.now(); | ||
}, | ||
// on error | ||
function (err) { | ||
Logger.onFunctionPromiseReject(completePath, err); | ||
currentLogObject.responseRejected = err; | ||
currentLogObject.responseRejectedDate = Date.now(); | ||
}); | ||
} | ||
return res; | ||
@@ -191,3 +278,3 @@ }; | ||
for (var i = 0; i < methods.length; i++) { | ||
for (var i = 0; i < methodNames.length; i++) { | ||
_loop(i); | ||
@@ -197,27 +284,62 @@ } | ||
function stubReadOnlyProperties(obj, oldDescriptors, properties, path, logObj) { | ||
/** | ||
* Spy access and updates of an Object's read-only properties: | ||
* - log every access/updates | ||
* - add entries in a logging object | ||
* | ||
* @param {Object} baseObject - Object in which the property is. | ||
* For example to spy on the HTMLMediaElement property `currentTime`, you will | ||
* have to set here `HTMLMediaElement.prototype`. | ||
* @param {Object} baseDescriptors - Descriptors for the spied properties. | ||
* The keys are the properties' names, the values are the properties' | ||
* descriptors. | ||
* @param {Array.<string>} propertyNames - Every properties you want to spy on. | ||
* @param {string} humanReadablePath - Path to the property. Used for logging | ||
* purposes. | ||
* For example `"HTMLMediaElement.prototype"`, for spies of HTMLMediaElement's | ||
* class properties. | ||
* @param {Object} logObject - Object where infos about the properties access | ||
* will be added. | ||
* The methods' name will be the key of the object. | ||
* | ||
* The values will be an object with a single key ``get``, corresponding to | ||
* property accesses | ||
* | ||
* This key will then have as value an array of object. | ||
* | ||
* - self {Object}: Reference to the baseObject argument. | ||
* | ||
* - id {number}: a uniquely generated ID for any stubbed property/methods with | ||
* this library. | ||
* | ||
* - date {number}: Timestamp at the time of the property access. | ||
* | ||
* - value {*}: value of the property at the time of access. | ||
*/ | ||
function spyOnReadOnlyProperties(baseObject, baseDescriptors, propertyNames, humanReadablePath, logObject) { | ||
var _loop = function _loop(i) { | ||
var propertyName = properties[i]; | ||
var oldDescriptor = oldDescriptors[propertyName]; | ||
var completePath = path + "." + propertyName; | ||
var propertyName = propertyNames[i]; | ||
var baseDescriptor = baseDescriptors[propertyName]; | ||
var completePath = humanReadablePath + "." + propertyName; | ||
if (!oldDescriptor) { | ||
if (!baseDescriptor) { | ||
throw new Error("No descriptor for property " + completePath); | ||
} | ||
Object.defineProperty(obj, propertyName, { | ||
Object.defineProperty(baseObject, propertyName, { | ||
get: function get() { | ||
var value = oldDescriptor.get.bind(this)(); | ||
var value = baseDescriptor.get.bind(this)(); | ||
Logger.onPropertyAccess(completePath, value); | ||
var myObj = { | ||
var currentLogObject = { | ||
self: this, | ||
id: generateId(), | ||
date: Date.now(), | ||
value: value | ||
}; | ||
if (!logObj[propertyName]) { | ||
logObj[propertyName] = { | ||
if (!logObject[propertyName]) { | ||
logObject[propertyName] = { | ||
get: [] | ||
}; | ||
} | ||
logObj[propertyName].get.push(myObj); | ||
logObject[propertyName].get.push(currentLogObject); | ||
return value; | ||
@@ -228,3 +350,3 @@ } | ||
for (var i = 0; i < properties.length; i++) { | ||
for (var i = 0; i < propertyNames.length; i++) { | ||
_loop(i); | ||
@@ -234,18 +356,68 @@ } | ||
function stubProperties(obj, oldDescriptors, properties, path, logObj) { | ||
/** | ||
* Spy access and updates of an Object's read & write properties: | ||
* - log every access/updates | ||
* - add entries in a logging object | ||
* | ||
* @param {Object} baseObject - Object in which the property is. | ||
* For example to spy on the HTMLMediaElement property `currentTime`, you will | ||
* have to set here `HTMLMediaElement.prototype`. | ||
* @param {Object} baseDescriptors - Descriptors for the spied properties. | ||
* The keys are the properties' names, the values are the properties' | ||
* descriptors. | ||
* @param {Array.<string>} propertyNames - Every properties you want to spy on. | ||
* @param {string} humanReadablePath - Path to the property. Used for logging | ||
* purposes. | ||
* For example `"HTMLMediaElement.prototype"`, for spies of HTMLMediaElement's | ||
* class properties. | ||
* @param {Object} logObject - Object where infos about the properties access | ||
* will be added. | ||
* The methods' name will be the key of the object. | ||
* | ||
* The values will be an object with two keys ``get`` and ``set``, respectively | ||
* for property accesses and property updates. | ||
* | ||
* Each one of those properties will then have as values an array of object. | ||
* Those objects are under the following form: | ||
* | ||
* 1. for `get` (property access): | ||
* | ||
* - self {Object}: Reference to the baseObject argument. | ||
* | ||
* - id {number}: a uniquely generated ascending ID for any stubbed | ||
* property/methods with this library. | ||
* | ||
* - date {number}: Timestamp at the time of the property access. | ||
* | ||
* - value {*}: value of the property at the time of access. | ||
* | ||
* | ||
* 2. for `set` (property updates): | ||
* | ||
* - self {Object}: Reference to the baseObject argument. | ||
* | ||
* - id {number}: a uniquely generated ascending ID for any stubbed | ||
* property/methods with this library. | ||
* | ||
* - date {number}: Timestamp at the time of the property update. | ||
* | ||
* - value {*}: new value the property is set to | ||
*/ | ||
function spyOnProperties(baseObject, baseDescriptors, propertyNames, humanReadablePath, logObject) { | ||
var _loop = function _loop(i) { | ||
var propertyName = properties[i]; | ||
var oldDescriptor = oldDescriptors[propertyName]; | ||
var completePath = path + "." + propertyName; | ||
var propertyName = propertyNames[i]; | ||
var baseDescriptor = baseDescriptors[propertyName]; | ||
var completePath = humanReadablePath + "." + propertyName; | ||
if (!oldDescriptor) { | ||
if (!baseDescriptor) { | ||
throw new Error("No descriptor for property " + completePath); | ||
} | ||
Object.defineProperty(obj, propertyName, { | ||
Object.defineProperty(baseObject, propertyName, { | ||
get: function get() { | ||
var value = oldDescriptor.get.bind(this)(); | ||
var value = baseDescriptor.get.bind(this)(); | ||
Logger.onPropertyAccess(completePath, value); | ||
var myObj = { | ||
var currentLogObject = { | ||
self: this, | ||
id: generateId(), | ||
date: Date.now(), | ||
@@ -255,4 +427,4 @@ value: value | ||
if (!logObj[propertyName]) { | ||
logObj[propertyName] = { | ||
if (!logObject[propertyName]) { | ||
logObject[propertyName] = { | ||
set: [], | ||
@@ -262,3 +434,3 @@ get: [] | ||
} | ||
logObj[propertyName].get.push(myObj); | ||
logObject[propertyName].get.push(currentLogObject); | ||
@@ -269,4 +441,5 @@ return value; | ||
Logger.onSettingProperty(completePath, value); | ||
var myObj = { | ||
var currentLogObject = { | ||
self: this, | ||
id: generateId(), | ||
date: Date.now(), | ||
@@ -276,4 +449,4 @@ value: value | ||
if (!logObj[propertyName]) { | ||
logObj[propertyName] = { | ||
if (!logObject[propertyName]) { | ||
logObject[propertyName] = { | ||
set: [], | ||
@@ -283,4 +456,4 @@ get: [] | ||
} | ||
logObj[propertyName].set.push(myObj); | ||
oldDescriptor.set.bind(this)(value); | ||
logObject[propertyName].set.push(currentLogObject); | ||
baseDescriptor.set.bind(this)(value); | ||
} | ||
@@ -290,3 +463,3 @@ }); | ||
for (var i = 0; i < properties.length; i++) { | ||
for (var i = 0; i < propertyNames.length; i++) { | ||
_loop(i); | ||
@@ -296,138 +469,133 @@ } | ||
var MEDIASOURCE_SPY_OBJECT = { | ||
readOnlyProperties: ["sourceBuffers", "activeSourceBuffers", "readyState"], | ||
properties: ["duration", "onsourceopen", "onsourceended", "onsourceclose"], | ||
staticMethods: ["isTypeSupported"], | ||
methods: ["addEventListener", "removeEventListener", "dispatchEvent", "addSourceBuffer", "removeSourceBuffer", "endOfStream", "setLiveSeekableRange", "clearLiveSeekableRange"] | ||
}; | ||
var stubbedObjects = []; | ||
function spyOnWholeObject(BaseObject, objectName, readOnlyPropertyNames, propertyNames, staticMethodNames, methodNames, loggingObject) { | ||
if (BaseObject == null || !BaseObject.prototype) { | ||
throw new Error("Invalid object"); | ||
} | ||
if (stubbedObjects.includes(BaseObject)) { | ||
return; | ||
} | ||
var NativeMediaSourceProtoDescriptors = Object.getOwnPropertyDescriptors(NativeMediaSource.prototype); | ||
var BaseObjectProtoDescriptors = Object.getOwnPropertyDescriptors(BaseObject.prototype); | ||
var BaseObjectStaticMethods = staticMethodNames.reduce(function (acc, methodName) { | ||
acc[methodName] = BaseObject[methodName]; | ||
return acc; | ||
}, {}); | ||
var BaseObjectMethods = methodNames.reduce(function (acc, methodName) { | ||
acc[methodName] = BaseObject.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
var NativeMediaSourceStaticMethods = MEDIASOURCE_SPY_OBJECT.staticMethods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeMediaSource[methodName]; | ||
return acc; | ||
}, {}); | ||
var NativeMediaSourceMethods = MEDIASOURCE_SPY_OBJECT.methods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeMediaSource.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
if (loggingObject[objectName] == null) { | ||
loggingObject[objectName] = { | ||
new: [], | ||
methods: {}, | ||
staticMethods: {}, | ||
properties: {}, | ||
eventListeners: {} // TODO | ||
}; | ||
} | ||
function StubbedMediaSource() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
function StubbedObject() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
Logger.onObjectInstanciation(objectName, args); | ||
var now = Date.now(); | ||
var spyObj = { | ||
date: now, | ||
args: args | ||
}; | ||
loggingObject[objectName].new.push(spyObj); | ||
var baseObject = void 0; | ||
try { | ||
baseObject = new (Function.prototype.bind.apply(BaseObject, [null].concat(args)))(); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError(objectName, e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onObjectInstanciationSuccess(objectName, baseObject); | ||
spyObj.response = baseObject; | ||
spyObj.responseDate = Date.now(); | ||
return baseObject; | ||
} | ||
Logger.onObjectInstanciation("MediaSource", args); | ||
var now = Date.now(); | ||
var spyObj = { | ||
date: now, | ||
args: args | ||
spyOnMethods(BaseObject, staticMethodNames, objectName, loggingObject[objectName].staticMethods); | ||
staticMethodNames.forEach(function (method) { | ||
StubbedObject[method] = BaseObject[method].bind(BaseObject); | ||
}); | ||
spyOnReadOnlyProperties(BaseObject.prototype, BaseObjectProtoDescriptors, readOnlyPropertyNames, objectName + ".prototype", loggingObject[objectName].properties); | ||
spyOnProperties(BaseObject.prototype, BaseObjectProtoDescriptors, propertyNames, objectName + ".prototype", loggingObject[objectName].properties); | ||
spyOnMethods(BaseObject.prototype, methodNames, objectName + ".prototype", loggingObject[objectName].methods); | ||
window[objectName] = StubbedObject; | ||
stubbedObjects.push(BaseObject); | ||
return function stopSpying() { | ||
Object.defineProperties(BaseObject.prototype, propertyNames.concat(readOnlyPropertyNames).reduce(function (acc, propertyName) { | ||
acc[propertyName] = BaseObjectProtoDescriptors[propertyName]; | ||
return acc; | ||
}, {})); | ||
staticMethodNames.forEach(function (methodName) { | ||
BaseObject[methodName] = BaseObjectStaticMethods[methodName]; | ||
}); | ||
methodNames.forEach(function (methodName) { | ||
BaseObject.prototype[methodName] = BaseObjectMethods[methodName]; | ||
}); | ||
window[objectName] = BaseObject; | ||
}; | ||
MSE_CALLS.MediaSource.new.push(spyObj); | ||
var nativeMediaSource = void 0; | ||
try { | ||
nativeMediaSource = new (Function.prototype.bind.apply(NativeMediaSource, [null].concat(args)))(); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("MediaSource", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onObjectInstanciationSuccess("MediaSource", nativeMediaSource); | ||
spyObj.response = nativeMediaSource; | ||
spyObj.responseDate = Date.now(); | ||
return nativeMediaSource; | ||
} | ||
function spyOnMediaSource() { | ||
stubReadOnlyProperties(NativeMediaSource.prototype, NativeMediaSourceProtoDescriptors, MEDIASOURCE_SPY_OBJECT.readOnlyProperties, "MediaSource.prototype", MSE_CALLS.MediaSource.properties); | ||
stubRegularMethods(NativeMediaSource, MEDIASOURCE_SPY_OBJECT.staticMethods, "MediaSource", MSE_CALLS.MediaSource.methods); | ||
MEDIASOURCE_SPY_OBJECT.staticMethods.forEach(function (method) { | ||
StubbedMediaSource[method] = NativeMediaSource[method].bind(NativeMediaSource); | ||
}); | ||
stubProperties(NativeMediaSource.prototype, NativeMediaSourceProtoDescriptors, MEDIASOURCE_SPY_OBJECT.properties, "MediaSource.prototype", MSE_CALLS.MediaSource.properties); | ||
stubRegularMethods(NativeMediaSource.prototype, MEDIASOURCE_SPY_OBJECT.methods, "MediaSource.prototype", MSE_CALLS.MediaSource.methods); | ||
window.MediaSource = StubbedMediaSource; | ||
} | ||
return spyOnWholeObject( | ||
// Object to spy on | ||
NativeMediaSource, | ||
function stopSpyingOnMediaSource() { | ||
Object.defineProperties(NativeMediaSource.prototype, MEDIASOURCE_SPY_OBJECT.properties.concat(MEDIASOURCE_SPY_OBJECT.readOnlyProperties).reduce(function (acc, propertyName) { | ||
acc[propertyName] = NativeMediaSourceProtoDescriptors[propertyName]; | ||
return acc; | ||
}, {})); | ||
MEDIASOURCE_SPY_OBJECT.staticMethods.forEach(function (methodName) { | ||
NativeMediaSource[methodName] = NativeMediaSourceStaticMethods[methodName]; | ||
}); | ||
MEDIASOURCE_SPY_OBJECT.methods.forEach(function (methodName) { | ||
NativeMediaSource.prototype[methodName] = NativeMediaSourceMethods[methodName]; | ||
}); | ||
window.MediaSource = NativeMediaSource; | ||
} | ||
// name in window | ||
"MediaSource", | ||
var SOURCEBUFFER_SPY_OBJECT = { | ||
readOnlyProperties: ["updating", "buffered"], | ||
properties: ["mode", "timestampOffset", "appendWindowStart", "appendWindowEnd", "onupdate", "onupdatestart", "onupdateend", "onerror", "onabort"], | ||
staticMethods: [], | ||
methods: ["addEventListener", "removeEventListener", "dispatchEvent", "appendBuffer", "abort", "remove"] | ||
}; | ||
// read-only properties | ||
["sourceBuffers", "activeSourceBuffers", "readyState"], | ||
var NativeSourceBufferProtoDescriptors = Object.getOwnPropertyDescriptors(NativeSourceBuffer.prototype); | ||
// regular properties | ||
["duration", "onsourceopen", "onsourceended", "onsourceclose"], | ||
var NativeSourceBufferStaticMethods = SOURCEBUFFER_SPY_OBJECT.staticMethods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeSourceBuffer[methodName]; | ||
return acc; | ||
}, {}); | ||
var NativeSourceBufferMethods = SOURCEBUFFER_SPY_OBJECT.methods.reduce(function (acc, methodName) { | ||
acc[methodName] = NativeSourceBuffer.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
// static methods | ||
["isTypeSupported"], | ||
function StubbedSourceBuffer() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
// methods | ||
["addEventListener", "removeEventListener", "dispatchEvent", "addSourceBuffer", "removeSourceBuffer", "endOfStream", "setLiveSeekableRange", "clearLiveSeekableRange"], | ||
Logger.onObjectInstanciation("SourceBuffer", args); | ||
var now = Date.now(); | ||
var spyObj = { | ||
date: now, | ||
args: args | ||
}; | ||
MSE_CALLS.SourceBuffer.new.push(spyObj); | ||
var nativeSourceBuffer = void 0; | ||
try { | ||
nativeSourceBuffer = new (Function.prototype.bind.apply(NativeSourceBuffer, [null].concat(args)))(); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("SourceBuffer", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onObjectInstanciationSuccess("SourceBuffer", nativeSourceBuffer); | ||
spyObj.response = nativeSourceBuffer; | ||
spyObj.responseDate = Date.now(); | ||
return nativeSourceBuffer; | ||
// global logging object | ||
MSE_CALLS); | ||
} | ||
function spyOnSourceBuffer() { | ||
stubReadOnlyProperties(NativeSourceBuffer.prototype, NativeSourceBufferProtoDescriptors, SOURCEBUFFER_SPY_OBJECT.readOnlyProperties, "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.properties); | ||
stubProperties(NativeSourceBuffer.prototype, NativeSourceBufferProtoDescriptors, SOURCEBUFFER_SPY_OBJECT.properties, "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.properties); | ||
stubRegularMethods(NativeSourceBuffer.prototype, SOURCEBUFFER_SPY_OBJECT.methods, "SourceBuffer.prototype", MSE_CALLS.SourceBuffer.methods); | ||
window.SourceBuffer = StubbedSourceBuffer; | ||
} | ||
function spyOnMediaSource$1() { | ||
return spyOnWholeObject( | ||
// Object to spy on | ||
NativeSourceBuffer, | ||
function stopSpyingOnSourceBuffer() { | ||
Object.defineProperties(NativeSourceBuffer.prototype, SOURCEBUFFER_SPY_OBJECT.properties.concat(SOURCEBUFFER_SPY_OBJECT.readOnlyProperties).reduce(function (acc, propertyName) { | ||
acc[propertyName] = NativeSourceBufferProtoDescriptors[propertyName]; | ||
return acc; | ||
}, {})); | ||
SOURCEBUFFER_SPY_OBJECT.staticMethods.forEach(function (methodName) { | ||
NativeSourceBuffer[methodName] = NativeSourceBufferStaticMethods[methodName]; | ||
}); | ||
SOURCEBUFFER_SPY_OBJECT.methods.forEach(function (methodName) { | ||
NativeSourceBuffer.prototype[methodName] = NativeSourceBufferMethods[methodName]; | ||
}); | ||
window.SourceBuffer = NativeSourceBuffer; | ||
// name in window | ||
"SourceBuffer", | ||
// read-only properties | ||
["updating", "buffered"], | ||
// regular properties | ||
["mode", "timestampOffset", "appendWindowStart", "appendWindowEnd", "onupdate", "onupdatestart", "onupdateend", "onerror", "onabort"], | ||
// static methods | ||
[], | ||
// methods | ||
["addEventListener", "removeEventListener", "dispatchEvent", "appendBuffer", "abort", "remove"], | ||
// global logging object | ||
MSE_CALLS); | ||
} | ||
var resetSpyFunctions = []; | ||
/** | ||
@@ -437,9 +605,11 @@ * Start spying on MSE API calls. | ||
function start() { | ||
spyOnMediaSource(); | ||
spyOnSourceBuffer(); | ||
resetSpyFunctions.push(spyOnMediaSource()); | ||
resetSpyFunctions.push(spyOnMediaSource$1()); | ||
} | ||
function stop() { | ||
stopSpyingOnMediaSource(); | ||
stopSpyingOnSourceBuffer(); | ||
resetSpyFunctions.forEach(function (fn) { | ||
fn(); | ||
}); | ||
resetSpyFunctions.length = 0; | ||
} | ||
@@ -446,0 +616,0 @@ |
{ | ||
"name": "mse-spy", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "Report every MSE API a webpage calls", | ||
@@ -5,0 +5,0 @@ "main": "dist/bundle.js", |
@@ -157,2 +157,5 @@ # MSESpy.js #################################################################### | ||
{ | ||
id: 1, // {number} unique id, generated in ascending order for any | ||
// entry. | ||
// Generated here at the time of MediaSource creation. | ||
date: 1533722155401, // {number} timestamp at which the call was made | ||
@@ -182,2 +185,5 @@ args: [], // {Array} Eventual arguments the constructor has been called | ||
// call (usually the MediaSource) | ||
id: 4, // {number} unique id, generated in ascending order for any | ||
// entry. | ||
// Generated here at the time of the call. | ||
date: 1533722155401, // {number} timestamp at which the call was made | ||
@@ -210,2 +216,5 @@ args: [], // {Array} Eventual arguments this method has been called | ||
// mediaSource | ||
id: 3, // {number} unique id, generated in ascending order for any | ||
// entry. | ||
// Generated here at the time of the access. | ||
date: 1533722155401, // {number} timestamp at which the property | ||
@@ -220,2 +229,5 @@ // was accessed | ||
// mediaSource | ||
id: 2, // {number} unique id, generated in ascending order for any | ||
// entry. | ||
// Generated here at the time of the update. | ||
date: 1533722155401, // {number} timestamp at which the property | ||
@@ -316,4 +328,6 @@ // was set | ||
## Left to do | ||
## Left to do ################################################################## | ||
The next steps would be to: | ||
@@ -324,5 +338,2 @@ | ||
- use performance.now and/or an incrementing ID to better pinpoint when the | ||
calls or property access are made relatively to each other | ||
- simplify the MSECalls object exploitation |
@@ -5,16 +5,3 @@ /** | ||
*/ | ||
const MSE_CALLS = { | ||
MediaSource: { | ||
new: [], | ||
methods: {}, | ||
properties: {}, | ||
eventListeners: {}, // TODO | ||
}, | ||
SourceBuffer: { | ||
new: [], | ||
methods: {}, | ||
properties: {}, | ||
eventListeners: {}, // TODO | ||
}, | ||
}; | ||
const MSE_CALLS = {}; | ||
@@ -26,11 +13,5 @@ function getMSECalls() { | ||
function resetMSECalls() { | ||
MSE_CALLS.MediaSource.new = []; | ||
MSE_CALLS.MediaSource.methods = []; | ||
MSE_CALLS.MediaSource.properties = []; | ||
MSE_CALLS.MediaSource.events = []; | ||
MSE_CALLS.SourceBuffer.new = []; | ||
MSE_CALLS.SourceBuffer.methods = []; | ||
MSE_CALLS.SourceBuffer.properties = []; | ||
MSE_CALLS.SourceBuffer.events = []; | ||
Object.key(MSE_CALLS).forEach(key => { | ||
delete MSE_CALLS[key]; | ||
}); | ||
} | ||
@@ -37,0 +18,0 @@ const NativeMediaSource = window.MediaSource; |
@@ -6,9 +6,7 @@ import { | ||
import Logger from "./utils/logger.js"; | ||
import spyOnMediaSource, { | ||
stopSpyingOnMediaSource, | ||
} from "./spyOnMediaSource.js"; | ||
import spyOnSourceBuffer, { | ||
stopSpyingOnSourceBuffer, | ||
} from "./spyOnSourceBuffer.js"; | ||
import spyOnMediaSource from "./spyOnMediaSource.js"; | ||
import spyOnSourceBuffer from "./spyOnSourceBuffer.js"; | ||
const resetSpyFunctions = []; | ||
/** | ||
@@ -18,9 +16,9 @@ * Start spying on MSE API calls. | ||
function start() { | ||
spyOnMediaSource(); | ||
spyOnSourceBuffer(); | ||
resetSpyFunctions.push(spyOnMediaSource()); | ||
resetSpyFunctions.push(spyOnSourceBuffer()); | ||
} | ||
function stop() { | ||
stopSpyingOnMediaSource(); | ||
stopSpyingOnSourceBuffer(); | ||
resetSpyFunctions.forEach(fn => { fn(); }); | ||
resetSpyFunctions.length = 0; | ||
} | ||
@@ -27,0 +25,0 @@ |
@@ -1,5 +0,2 @@ | ||
import Logger from "./utils/logger.js"; | ||
import stubRegularMethods from "./utils/stubRegularMethods.js"; | ||
import stubReadOnlyProperties from "./utils/stubReadOnlyProperties.js"; | ||
import stubProperties from "./utils/stubProperties.js"; | ||
import spyOnWholeObject from "./utils/spyOnWholeObject.js"; | ||
import { | ||
@@ -10,115 +7,34 @@ MSE_CALLS, | ||
const MEDIASOURCE_SPY_OBJECT = { | ||
readOnlyProperties: [ | ||
"sourceBuffers", | ||
"activeSourceBuffers", | ||
"readyState", | ||
], | ||
properties: [ | ||
"duration", | ||
"onsourceopen", | ||
"onsourceended", | ||
"onsourceclose", | ||
], | ||
staticMethods: [ | ||
"isTypeSupported", | ||
], | ||
methods: [ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"addSourceBuffer", | ||
"removeSourceBuffer", | ||
"endOfStream", | ||
"setLiveSeekableRange", | ||
"clearLiveSeekableRange", | ||
], | ||
}; | ||
export default function spyOnMediaSource() { | ||
return spyOnWholeObject( | ||
// Object to spy on | ||
NativeMediaSource, | ||
const NativeMediaSourceProtoDescriptors = | ||
Object.getOwnPropertyDescriptors(NativeMediaSource.prototype); | ||
// name in window | ||
"MediaSource", | ||
const NativeMediaSourceStaticMethods = MEDIASOURCE_SPY_OBJECT.staticMethods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeMediaSource[methodName]; | ||
return acc; | ||
}, {}); | ||
const NativeMediaSourceMethods = MEDIASOURCE_SPY_OBJECT.methods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeMediaSource.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
// read-only properties | ||
["sourceBuffers", "activeSourceBuffers", "readyState"], | ||
function StubbedMediaSource(...args) { | ||
Logger.onObjectInstanciation("MediaSource", args); | ||
const now = Date.now(); | ||
const spyObj = { | ||
date: now, | ||
args, | ||
}; | ||
MSE_CALLS.MediaSource.new.push(spyObj); | ||
let nativeMediaSource; | ||
try { | ||
nativeMediaSource = new NativeMediaSource(...args); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("MediaSource", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onObjectInstanciationSuccess("MediaSource", nativeMediaSource); | ||
spyObj.response = nativeMediaSource; | ||
spyObj.responseDate = Date.now(); | ||
return nativeMediaSource; | ||
} | ||
// regular properties | ||
["duration", "onsourceopen", "onsourceended", "onsourceclose"], | ||
export default function spyOnMediaSource() { | ||
stubReadOnlyProperties( | ||
NativeMediaSource.prototype, | ||
NativeMediaSourceProtoDescriptors, | ||
MEDIASOURCE_SPY_OBJECT.readOnlyProperties, | ||
"MediaSource.prototype", | ||
MSE_CALLS.MediaSource.properties, | ||
); | ||
stubRegularMethods( | ||
NativeMediaSource, | ||
MEDIASOURCE_SPY_OBJECT.staticMethods, | ||
"MediaSource", | ||
MSE_CALLS.MediaSource.methods, | ||
); | ||
MEDIASOURCE_SPY_OBJECT.staticMethods.forEach((method) => { | ||
StubbedMediaSource[method] = NativeMediaSource[method].bind(NativeMediaSource); | ||
}); | ||
stubProperties( | ||
NativeMediaSource.prototype, | ||
NativeMediaSourceProtoDescriptors, | ||
MEDIASOURCE_SPY_OBJECT.properties, | ||
"MediaSource.prototype", | ||
MSE_CALLS.MediaSource.properties, | ||
); | ||
stubRegularMethods( | ||
NativeMediaSource.prototype, | ||
MEDIASOURCE_SPY_OBJECT.methods, | ||
"MediaSource.prototype", | ||
MSE_CALLS.MediaSource.methods, | ||
); | ||
window.MediaSource = StubbedMediaSource; | ||
} | ||
// static methods | ||
["isTypeSupported"], | ||
// methods | ||
[ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"addSourceBuffer", | ||
"removeSourceBuffer", | ||
"endOfStream", | ||
"setLiveSeekableRange", | ||
"clearLiveSeekableRange", | ||
], | ||
export function stopSpyingOnMediaSource() { | ||
Object.defineProperties(NativeMediaSource.prototype, | ||
MEDIASOURCE_SPY_OBJECT.properties | ||
.concat(MEDIASOURCE_SPY_OBJECT.readOnlyProperties) | ||
.reduce((acc, propertyName) => { | ||
acc[propertyName] = NativeMediaSourceProtoDescriptors[propertyName]; | ||
return acc; | ||
}, {}) | ||
// global logging object | ||
MSE_CALLS | ||
); | ||
MEDIASOURCE_SPY_OBJECT.staticMethods.forEach((methodName) => { | ||
NativeMediaSource[methodName] = NativeMediaSourceStaticMethods[methodName]; | ||
}); | ||
MEDIASOURCE_SPY_OBJECT.methods.forEach((methodName) => { | ||
NativeMediaSource.prototype[methodName] = NativeMediaSourceMethods[methodName]; | ||
}); | ||
window.MediaSource = NativeMediaSource; | ||
} |
@@ -1,5 +0,2 @@ | ||
import Logger from "./utils/logger.js"; | ||
import stubRegularMethods from "./utils/stubRegularMethods.js"; | ||
import stubReadOnlyProperties from "./utils/stubReadOnlyProperties.js"; | ||
import stubProperties from "./utils/stubProperties.js"; | ||
import spyOnWholeObject from "./utils/spyOnWholeObject.js"; | ||
import { | ||
@@ -10,109 +7,42 @@ MSE_CALLS, | ||
const SOURCEBUFFER_SPY_OBJECT = { | ||
readOnlyProperties: [ | ||
"updating", | ||
"buffered", | ||
// "audioTracks", | ||
// "videoTracks", | ||
// "textTracks", | ||
], | ||
properties: [ | ||
"mode", | ||
"timestampOffset", | ||
"appendWindowStart", | ||
"appendWindowEnd", | ||
"onupdate", | ||
"onupdatestart", | ||
"onupdateend", | ||
"onerror", | ||
"onabort", | ||
], | ||
staticMethods: [], | ||
methods: [ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"appendBuffer", | ||
"abort", | ||
"remove", | ||
], | ||
}; | ||
export default function spyOnMediaSource() { | ||
return spyOnWholeObject( | ||
// Object to spy on | ||
NativeSourceBuffer, | ||
const NativeSourceBufferProtoDescriptors = | ||
Object.getOwnPropertyDescriptors(NativeSourceBuffer.prototype); | ||
// name in window | ||
"SourceBuffer", | ||
const NativeSourceBufferStaticMethods = SOURCEBUFFER_SPY_OBJECT.staticMethods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeSourceBuffer[methodName]; | ||
return acc; | ||
}, {}); | ||
const NativeSourceBufferMethods = SOURCEBUFFER_SPY_OBJECT.methods | ||
.reduce((acc, methodName) => { | ||
acc[methodName] = NativeSourceBuffer.prototype[methodName]; | ||
return acc; | ||
}, {}); | ||
// read-only properties | ||
["updating", "buffered"], | ||
function StubbedSourceBuffer(...args) { | ||
Logger.onObjectInstanciation("SourceBuffer", args); | ||
const now = Date.now(); | ||
const spyObj = { | ||
date: now, | ||
args, | ||
}; | ||
MSE_CALLS.SourceBuffer.new.push(spyObj); | ||
let nativeSourceBuffer; | ||
try { | ||
nativeSourceBuffer = new NativeSourceBuffer(...args); | ||
} catch (e) { | ||
Logger.onObjectInstanciationError("SourceBuffer", e); | ||
spyObj.error = e; | ||
spyObj.errorDate = Date.now(); | ||
throw e; | ||
} | ||
Logger.onObjectInstanciationSuccess("SourceBuffer", nativeSourceBuffer); | ||
spyObj.response = nativeSourceBuffer; | ||
spyObj.responseDate = Date.now(); | ||
return nativeSourceBuffer; | ||
} | ||
// regular properties | ||
[ | ||
"mode", | ||
"timestampOffset", | ||
"appendWindowStart", | ||
"appendWindowEnd", | ||
"onupdate", | ||
"onupdatestart", | ||
"onupdateend", | ||
"onerror", | ||
"onabort", | ||
], | ||
export default function spyOnSourceBuffer() { | ||
stubReadOnlyProperties( | ||
NativeSourceBuffer.prototype, | ||
NativeSourceBufferProtoDescriptors, | ||
SOURCEBUFFER_SPY_OBJECT.readOnlyProperties, | ||
"SourceBuffer.prototype", | ||
MSE_CALLS.SourceBuffer.properties, | ||
); | ||
stubProperties( | ||
NativeSourceBuffer.prototype, | ||
NativeSourceBufferProtoDescriptors, | ||
SOURCEBUFFER_SPY_OBJECT.properties, | ||
"SourceBuffer.prototype", | ||
MSE_CALLS.SourceBuffer.properties, | ||
); | ||
stubRegularMethods( | ||
NativeSourceBuffer.prototype, | ||
SOURCEBUFFER_SPY_OBJECT.methods, | ||
"SourceBuffer.prototype", | ||
MSE_CALLS.SourceBuffer.methods, | ||
); | ||
window.SourceBuffer = StubbedSourceBuffer; | ||
} | ||
// static methods | ||
[], | ||
// methods | ||
[ | ||
"addEventListener", | ||
"removeEventListener", | ||
"dispatchEvent", | ||
"appendBuffer", | ||
"abort", | ||
"remove", | ||
], | ||
export function stopSpyingOnSourceBuffer() { | ||
Object.defineProperties(NativeSourceBuffer.prototype, | ||
SOURCEBUFFER_SPY_OBJECT.properties | ||
.concat(SOURCEBUFFER_SPY_OBJECT.readOnlyProperties) | ||
.reduce((acc, propertyName) => { | ||
acc[propertyName] = NativeSourceBufferProtoDescriptors[propertyName]; | ||
return acc; | ||
}, {}) | ||
// global logging object | ||
MSE_CALLS | ||
); | ||
SOURCEBUFFER_SPY_OBJECT.staticMethods.forEach((methodName) => { | ||
NativeSourceBuffer[methodName] = NativeSourceBufferStaticMethods[methodName]; | ||
}); | ||
SOURCEBUFFER_SPY_OBJECT.methods.forEach((methodName) => { | ||
NativeSourceBuffer.prototype[methodName] = NativeSourceBufferMethods[methodName]; | ||
}); | ||
window.SourceBuffer = NativeSourceBuffer; | ||
} |
@@ -89,2 +89,20 @@ /** | ||
}, | ||
/** | ||
* Triggered when a function returned a Promise and that promise resolved. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {*} value - The value when the function resolved. | ||
*/ | ||
onFunctionPromiseResolve(pathName, value) { | ||
console.info(`>>> ${pathName} resolved:`, value); | ||
}, | ||
/** | ||
* Triggered when a function returned a Promise and that promise rejected. | ||
* @param {string} pathName - human-readable path for the concerned function. | ||
* @param {*} value - The error when the function's promise rejected. | ||
*/ | ||
onFunctionPromiseReject(pathName, value) { | ||
console.error(`>>> ${pathName} rejected:`, value); | ||
}, | ||
/* eslint-enable no-console */ | ||
@@ -91,0 +109,0 @@ }; |
import Logger from "./logger.js"; | ||
import generateId from "./generate_id.js"; | ||
export default function stubReadOnlyProperties( | ||
obj, | ||
oldDescriptors, | ||
properties, | ||
path, | ||
logObj, | ||
/** | ||
* Spy access and updates of an Object's read-only properties: | ||
* - log every access/updates | ||
* - add entries in a logging object | ||
* | ||
* @param {Object} baseObject - Object in which the property is. | ||
* For example to spy on the HTMLMediaElement property `currentTime`, you will | ||
* have to set here `HTMLMediaElement.prototype`. | ||
* @param {Object} baseDescriptors - Descriptors for the spied properties. | ||
* The keys are the properties' names, the values are the properties' | ||
* descriptors. | ||
* @param {Array.<string>} propertyNames - Every properties you want to spy on. | ||
* @param {string} humanReadablePath - Path to the property. Used for logging | ||
* purposes. | ||
* For example `"HTMLMediaElement.prototype"`, for spies of HTMLMediaElement's | ||
* class properties. | ||
* @param {Object} logObject - Object where infos about the properties access | ||
* will be added. | ||
* The methods' name will be the key of the object. | ||
* | ||
* The values will be an object with a single key ``get``, corresponding to | ||
* property accesses | ||
* | ||
* This key will then have as value an array of object. | ||
* | ||
* - self {Object}: Reference to the baseObject argument. | ||
* | ||
* - id {number}: a uniquely generated ascending ID for any stubbed | ||
* property/methods with this library. | ||
* | ||
* - date {number}: Timestamp at the time of the property access. | ||
* | ||
* - value {*}: value of the property at the time of access. | ||
*/ | ||
export default function spyOnReadOnlyProperties( | ||
baseObject, | ||
baseDescriptors, | ||
propertyNames, | ||
humanReadablePath, | ||
logObject, | ||
) { | ||
for (let i = 0; i < properties.length; i++) { | ||
const propertyName = properties[i]; | ||
const oldDescriptor = oldDescriptors[propertyName]; | ||
const completePath = path + "." + propertyName; | ||
for (let i = 0; i < propertyNames.length; i++) { | ||
const propertyName = propertyNames[i]; | ||
const baseDescriptor = baseDescriptors[propertyName]; | ||
const completePath = humanReadablePath + "." + propertyName; | ||
if (!oldDescriptor) { | ||
if (!baseDescriptor) { | ||
throw new Error("No descriptor for property " + | ||
@@ -20,17 +55,18 @@ completePath); | ||
Object.defineProperty(obj, propertyName, { | ||
Object.defineProperty(baseObject, propertyName, { | ||
get() { | ||
const value = oldDescriptor.get.bind(this)(); | ||
const value = baseDescriptor.get.bind(this)(); | ||
Logger.onPropertyAccess(completePath, value); | ||
const myObj = { | ||
const currentLogObject = { | ||
self: this, | ||
id: generateId(), | ||
date: Date.now(), | ||
value: value, | ||
}; | ||
if (!logObj[propertyName]) { | ||
logObj[propertyName] = { | ||
if (!logObject[propertyName]) { | ||
logObject[propertyName] = { | ||
get: [], | ||
}; | ||
} | ||
logObj[propertyName].get.push(myObj); | ||
logObject[propertyName].get.push(currentLogObject); | ||
return value; | ||
@@ -37,0 +73,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
60395
18
1254
334
1