Index: third_party/WebKit/LayoutTests/imported/wpt/encrypted-media/polyfill/clearkey-polyfill.js |
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/encrypted-media/polyfill/clearkey-polyfill.js b/third_party/WebKit/LayoutTests/imported/wpt/encrypted-media/polyfill/clearkey-polyfill.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..057ea3e0301fafebb1ee8f194bab61363dfd2c11 |
--- /dev/null |
+++ b/third_party/WebKit/LayoutTests/imported/wpt/encrypted-media/polyfill/clearkey-polyfill.js |
@@ -0,0 +1,510 @@ |
+(function(){ |
+ |
+ // Save platform functions that will be modified |
+ var _requestMediaKeySystemAccess = navigator.requestMediaKeySystemAccess.bind( navigator ), |
+ _setMediaKeys = HTMLMediaElement.prototype.setMediaKeys; |
+ |
+ // Allow us to modify the target of Events |
+ Object.defineProperties( Event.prototype, { |
+ target: { get: function() { return this._target || this.currentTarget; }, |
+ set: function( newtarget ) { this._target = newtarget; } } |
+ } ); |
+ |
+ var EventTarget = function(){ |
+ this.listeners = {}; |
+ }; |
+ |
+ EventTarget.prototype.listeners = null; |
+ |
+ EventTarget.prototype.addEventListener = function(type, callback){ |
+ if(!(type in this.listeners)) { |
+ this.listeners[type] = []; |
+ } |
+ this.listeners[type].push(callback); |
+ }; |
+ |
+ EventTarget.prototype.removeEventListener = function(type, callback){ |
+ if(!(type in this.listeners)) { |
+ return; |
+ } |
+ var stack = this.listeners[type]; |
+ for(var i = 0, l = stack.length; i < l; i++){ |
+ if(stack[i] === callback){ |
+ stack.splice(i, 1); |
+ return this.removeEventListener(type, callback); |
+ } |
+ } |
+ }; |
+ |
+ EventTarget.prototype.dispatchEvent = function(event){ |
+ if(!(event.type in this.listeners)) { |
+ return; |
+ } |
+ var stack = this.listeners[event.type]; |
+ event.target = this; |
+ for(var i = 0, l = stack.length; i < l; i++) { |
+ stack[i].call(this, event); |
+ } |
+ }; |
+ |
+ function MediaKeySystemAccessProxy( keysystem, access, configuration ) |
+ { |
+ this._keysystem = keysystem; |
+ this._access = access; |
+ this._configuration = configuration; |
+ } |
+ |
+ Object.defineProperties( MediaKeySystemAccessProxy.prototype, { |
+ keysystem: { get: function() { return this._keysystem; } } |
+ }); |
+ |
+ MediaKeySystemAccessProxy.prototype.getConfiguration = function getConfiguration() |
+ { |
+ return this._configuration; |
+ }; |
+ |
+ MediaKeySystemAccessProxy.prototype.createMediaKeys = function createMediaKeys() |
+ { |
+ return new Promise( function( resolve, reject ) { |
+ |
+ this._access.createMediaKeys() |
+ .then( function( mediaKeys ) { resolve( new MediaKeysProxy( mediaKeys ) ); }) |
+ .catch( function( error ) { reject( error ); } ); |
+ |
+ }.bind( this ) ); |
+ }; |
+ |
+ function MediaKeysProxy( mediaKeys ) |
+ { |
+ this._mediaKeys = mediaKeys; |
+ this._sessions = [ ]; |
+ this._videoelement = undefined; |
+ this._onTimeUpdateListener = MediaKeysProxy.prototype._onTimeUpdate.bind( this ); |
+ } |
+ |
+ MediaKeysProxy.prototype._setVideoElement = function _setVideoElement( videoElement ) |
+ { |
+ if ( videoElement !== this._videoelement ) |
+ { |
+ if ( this._videoelement ) |
+ { |
+ this._videoelement.removeEventListener( 'timeupdate', this._onTimeUpdateListener ); |
+ } |
+ |
+ this._videoelement = videoElement; |
+ |
+ if ( this._videoelement ) |
+ { |
+ this._videoelement.addEventListener( 'timeupdate', this._onTimeUpdateListener ); |
+ } |
+ } |
+ }; |
+ |
+ MediaKeysProxy.prototype._onTimeUpdate = function( event ) |
+ { |
+ this._sessions.forEach( function( session ) { |
+ |
+ if ( session._sessionType === 'persistent-usage-record' ) |
+ { |
+ session._onTimeUpdate( event ); |
+ } |
+ |
+ } ); |
+ }; |
+ |
+ MediaKeysProxy.prototype._removeSession = function _removeSession( session ) |
+ { |
+ var index = this._sessions.indexOf( session ); |
+ if ( index !== -1 ) this._sessions.splice( index, 1 ); |
+ }; |
+ |
+ MediaKeysProxy.prototype.createSession = function createSession( sessionType ) |
+ { |
+ if ( !sessionType || sessionType === 'temporary' ) return this._mediaKeys.createSession(); |
+ |
+ var session = new MediaKeySessionProxy( this, sessionType ); |
+ this._sessions.push( session ); |
+ |
+ return session; |
+ }; |
+ |
+ MediaKeysProxy.prototype.setServerCertificate = function setServerCertificate( certificate ) |
+ { |
+ return this._mediaKeys.setServerCertificate( certificate ); |
+ }; |
+ |
+ function MediaKeySessionProxy( mediaKeysProxy, sessionType ) |
+ { |
+ EventTarget.call( this ); |
+ |
+ this._mediaKeysProxy = mediaKeysProxy |
+ this._sessionType = sessionType; |
+ this._sessionId = ""; |
+ |
+ // MediaKeySessionProxy states |
+ // 'created' - After initial creation |
+ // 'loading' - Persistent license session waiting for key message to load stored keys |
+ // 'active' - Normal active state - proxy all key messages |
+ // 'removing' - Release message generated, waiting for ack |
+ // 'closed' - Session closed |
+ this._state = 'created'; |
+ |
+ this._closed = new Promise( function( resolve ) { this._resolveClosed = resolve; }.bind( this ) ); |
+ } |
+ |
+ MediaKeySessionProxy.prototype = Object.create( EventTarget.prototype ); |
+ |
+ Object.defineProperties( MediaKeySessionProxy.prototype, { |
+ |
+ sessionId: { get: function() { return this._sessionId; } }, |
+ expiration: { get: function() { return NaN; } }, |
+ closed: { get: function() { return this._closed; } }, |
+ keyStatuses:{ get: function() { return this._session.keyStatuses; } }, // TODO this will fail if examined too early |
+ _kids: { get: function() { return this._keys.map( function( key ) { return key.kid; } ); } }, |
+ }); |
+ |
+ MediaKeySessionProxy.prototype._createSession = function _createSession() |
+ { |
+ this._session = this._mediaKeysProxy._mediaKeys.createSession(); |
+ |
+ this._session.addEventListener( 'message', MediaKeySessionProxy.prototype._onMessage.bind( this ) ); |
+ this._session.addEventListener( 'keystatuseschange', MediaKeySessionProxy.prototype._onKeyStatusesChange.bind( this ) ); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype._onMessage = function _onMessage( event ) |
+ { |
+ switch( this._state ) |
+ { |
+ case 'loading': |
+ this._session.update( toUtf8( { keys: this._keys } ) ) |
+ .then( function() { |
+ this._state = 'active'; |
+ this._loaded( true ); |
+ }.bind(this)).catch( this._loadfailed ); |
+ |
+ break; |
+ |
+ case 'active': |
+ this.dispatchEvent( event ); |
+ break; |
+ |
+ default: |
+ // Swallow the event |
+ break; |
+ } |
+ }; |
+ |
+ MediaKeySessionProxy.prototype._onKeyStatusesChange = function _onKeyStatusesChange( event ) |
+ { |
+ switch( this._state ) |
+ { |
+ case 'active' : |
+ case 'removing' : |
+ this.dispatchEvent( event ); |
+ break; |
+ |
+ default: |
+ // Swallow the event |
+ break; |
+ } |
+ }; |
+ |
+ MediaKeySessionProxy.prototype._onTimeUpdate = function _onTimeUpdate( event ) |
+ { |
+ if ( !this._firstTime ) this._firstTime = Date.now(); |
+ this._latestTime = Date.now(); |
+ this._store(); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype._queueMessage = function _queueMessage( messageType, message ) |
+ { |
+ setTimeout( function() { |
+ |
+ var messageAsArray = toUtf8( message ).buffer; |
+ |
+ this.dispatchEvent( new MediaKeyMessageEvent( 'message', { messageType: messageType, message: messageAsArray } ) ); |
+ |
+ }.bind( this ) ); |
+ }; |
+ |
+ function _storageKey( sessionId ) |
+ { |
+ return sessionId; |
+ } |
+ |
+ MediaKeySessionProxy.prototype._store = function _store() |
+ { |
+ var data; |
+ |
+ if ( this._sessionType === 'persistent-usage-record' ) |
+ { |
+ data = { kids: this._kids }; |
+ if ( this._firstTime ) data.firstTime = this._firstTime; |
+ if ( this._latestTime ) data.latestTime = this._latestTime; |
+ } |
+ else |
+ { |
+ data = { keys: this._keys }; |
+ } |
+ |
+ window.localStorage.setItem( _storageKey( this._sessionId ), JSON.stringify( data ) ); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype._load = function _load( sessionId ) |
+ { |
+ var store = window.localStorage.getItem( _storageKey( sessionId ) ); |
+ if ( store === null ) return false; |
+ |
+ var data; |
+ try { data = JSON.parse( store ) } catch( error ) { |
+ return false; |
+ } |
+ |
+ if ( data.kids ) |
+ { |
+ this._sessionType = 'persistent-usage-record'; |
+ this._keys = data.kids.map( function( kid ) { return { kid: kid }; } ); |
+ if ( data.firstTime ) this._firstTime = data.firstTime; |
+ if ( data.latestTime ) this._latestTime = data.latestTime; |
+ } |
+ else |
+ { |
+ this._sessionType = 'persistent-license'; |
+ this._keys = data.keys; |
+ } |
+ |
+ return true; |
+ }; |
+ |
+ MediaKeySessionProxy.prototype._clear = function _clear() |
+ { |
+ window.localStorage.removeItem( _storageKey( this._sessionId ) ); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype.generateRequest = function generateRequest( initDataType, initData ) |
+ { |
+ if ( this._state !== 'created' ) return Promise.reject( new InvalidStateError() ); |
+ |
+ this._createSession(); |
+ |
+ this._state = 'active'; |
+ |
+ return this._session.generateRequest( initDataType, initData ) |
+ .then( function() { |
+ this._sessionId = Math.random().toString(36).slice(2); |
+ }.bind( this ) ); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype.load = function load( sessionId ) |
+ { |
+ if ( this._state !== 'created' ) return Promise.reject( new InvalidStateError() ); |
+ |
+ return new Promise( function( resolve, reject ) { |
+ |
+ try |
+ { |
+ if ( !this._load( sessionId ) ) |
+ { |
+ resolve( false ); |
+ |
+ return; |
+ } |
+ |
+ this._sessionId = sessionId; |
+ |
+ if ( this._sessionType === 'persistent-usage-record' ) |
+ { |
+ var msg = { kids: this._kids }; |
+ if ( this._firstTime ) msg.firstTime = this._firstTime; |
+ if ( this._latestTime ) msg.latestTime = this._latestTime; |
+ |
+ this._queueMessage( 'license-release', msg ); |
+ |
+ this._state = 'removing'; |
+ |
+ resolve( true ); |
+ } |
+ else |
+ { |
+ this._createSession(); |
+ |
+ this._state = 'loading'; |
+ this._loaded = resolve; |
+ this._loadfailed = reject; |
+ |
+ var initData = { kids: this._kids }; |
+ |
+ this._session.generateRequest( 'keyids', toUtf8( initData ) ); |
+ } |
+ } |
+ catch( error ) |
+ { |
+ reject( error ); |
+ } |
+ }.bind( this ) ); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype.update = function update( response ) |
+ { |
+ return new Promise( function( resolve, reject ) { |
+ |
+ switch( this._state ) { |
+ |
+ case 'active' : |
+ |
+ var message = fromUtf8( response ); |
+ |
+ // JSON Web Key Set |
+ this._keys = message.keys; |
+ |
+ this._store(); |
+ |
+ resolve( this._session.update( response ) ); |
+ |
+ break; |
+ |
+ case 'removing' : |
+ |
+ this._state = 'closed'; |
+ |
+ this._clear(); |
+ |
+ this._mediaKeysProxy._removeSession( this ); |
+ |
+ this._resolveClosed(); |
+ |
+ delete this._session; |
+ |
+ resolve(); |
+ |
+ break; |
+ |
+ default: |
+ reject( new InvalidStateError() ); |
+ } |
+ |
+ }.bind( this ) ); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype.close = function close() |
+ { |
+ if ( this._state === 'closed' ) return Promise.resolve(); |
+ |
+ this._state = 'closed'; |
+ |
+ this._mediaKeysProxy._removeSession( this ); |
+ |
+ this._resolveClosed(); |
+ |
+ var session = this._session; |
+ if ( !session ) return Promise.resolve(); |
+ |
+ this._session = undefined; |
+ |
+ return session.close(); |
+ }; |
+ |
+ MediaKeySessionProxy.prototype.remove = function remove() |
+ { |
+ if ( this._state !== 'active' || !this._session ) return Promise.reject( new DOMException('InvalidStateError('+this._state+')') ); |
+ |
+ this._state = 'removing'; |
+ |
+ this._mediaKeysProxy._removeSession( this ); |
+ |
+ return this._session.close() |
+ .then( function() { |
+ |
+ var msg = { kids: this._kids }; |
+ |
+ if ( this._sessionType === 'persistent-usage-record' ) |
+ { |
+ if ( this._firstTime ) msg.firstTime = this._firstTime; |
+ if ( this._latestTime ) msg.latestTime = this._latestTime; |
+ } |
+ |
+ this._queueMessage( 'license-release', msg ); |
+ |
+ }.bind( this ) ) |
+ }; |
+ |
+ HTMLMediaElement.prototype.setMediaKeys = function setMediaKeys( mediaKeys ) |
+ { |
+ if ( mediaKeys instanceof MediaKeysProxy ) |
+ { |
+ mediaKeys._setVideoElement( this ); |
+ return _setMediaKeys.call( this, mediaKeys._mediaKeys ); |
+ } |
+ else |
+ { |
+ return _setMediaKeys.call( this, mediaKeys ); |
+ } |
+ }; |
+ |
+ navigator.requestMediaKeySystemAccess = function( keysystem, configurations ) |
+ { |
+ // First, see if this is supported by the platform |
+ return new Promise( function( resolve, reject ) { |
+ |
+ _requestMediaKeySystemAccess( keysystem, configurations ) |
+ .then( function( access ) { resolve( access ); } ) |
+ .catch( function( error ) { |
+ |
+ if ( error instanceof TypeError ) reject( error ); |
+ |
+ if ( keysystem !== 'org.w3.clearkey' ) reject( error ); |
+ |
+ if ( !configurations.some( is_persistent_configuration ) ) reject( error ); |
+ |
+ // Shallow copy the configurations, swapping out the labels and omitting the sessiontypes |
+ var configurations_copy = configurations.map( function( config, index ) { |
+ |
+ var config_copy = copy_configuration( config ); |
+ config_copy.label = index.toString(); |
+ return config_copy; |
+ |
+ } ); |
+ |
+ // And try again with these configurations |
+ _requestMediaKeySystemAccess( keysystem, configurations_copy ) |
+ .then( function( access ) { |
+ |
+ // Create the supported configuration based on the original request |
+ var configuration = access.getConfiguration(), |
+ original_configuration = configurations[ configuration.label ]; |
+ |
+ // If the original configuration did not need persistent session types, then we're done |
+ if ( !is_persistent_configuration( original_configuration ) ) resolve( access ); |
+ |
+ // Create the configuration that we will return |
+ var returned_configuration = copy_configuration( configuration ); |
+ |
+ if ( original_configuration.label ) |
+ returned_configuration.label = original_configuration; |
+ else |
+ delete returned_configuration.label; |
+ |
+ returned_configuration.sessionTypes = original_configuration.sessionTypes; |
+ |
+ resolve( new MediaKeySystemAccessProxy( keysystem, access, returned_configuration ) ); |
+ } ) |
+ .catch( function( error ) { reject( error ); } ); |
+ } ); |
+ } ); |
+ }; |
+ |
+ function is_persistent_configuration( configuration ) |
+ { |
+ return configuration.sessionTypes && |
+ ( configuration.sessionTypes.indexOf( 'persistent-usage-record' ) !== -1 |
+ || configuration.sessionTypes.indexOf( 'persistent-license' ) !== -1 ); |
+ } |
+ |
+ function copy_configuration( src ) |
+ { |
+ var dst = {}; |
+ [ 'label', 'initDataTypes', 'audioCapabilities', 'videoCapabilities', 'distinctiveIdenfifier', 'persistentState' ] |
+ .forEach( function( item ) { if ( src[item] ) dst[item] = src[item]; } ); |
+ return dst; |
+ } |
+}()); |