| 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;
|
| + }
|
| +}());
|
|
|