tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

clearkey-polyfill.js (13825B)


      1 (function(){
      2 
      3    // Save platform functions that will be modified
      4    var _requestMediaKeySystemAccess = navigator.requestMediaKeySystemAccess.bind( navigator ),
      5        _setMediaKeys = HTMLMediaElement.prototype.setMediaKeys;
      6 
      7    // Allow us to modify the target of Events
      8    Object.defineProperties( Event.prototype, {
      9        target: {   get: function() { return this._target || this.currentTarget; },
     10                    set: function( newtarget ) { this._target = newtarget; } }
     11    } );
     12 
     13    var EventTarget = function(){
     14        this.listeners = {};
     15    };
     16 
     17    EventTarget.prototype.listeners = null;
     18 
     19    EventTarget.prototype.addEventListener = function(type, callback){
     20      if(!(type in this.listeners)) {
     21        this.listeners[type] = [];
     22      }
     23      this.listeners[type].push(callback);
     24    };
     25 
     26    EventTarget.prototype.removeEventListener = function(type, callback){
     27      if(!(type in this.listeners)) {
     28        return;
     29      }
     30      var stack = this.listeners[type];
     31      for(var i = 0, l = stack.length; i < l; i++){
     32        if(stack[i] === callback){
     33          stack.splice(i, 1);
     34          return this.removeEventListener(type, callback);
     35        }
     36      }
     37    };
     38 
     39    EventTarget.prototype.dispatchEvent = function(event){
     40      if(!(event.type in this.listeners)) {
     41        return;
     42      }
     43      var stack = this.listeners[event.type];
     44      event.target = this;
     45      for(var i = 0, l = stack.length; i < l; i++) {
     46        stack[i].call(this, event);
     47      }
     48    };
     49 
     50    function MediaKeySystemAccessProxy( keysystem, access, configuration )
     51    {
     52        this._keysystem = keysystem;
     53        this._access = access;
     54        this._configuration = configuration;
     55    }
     56 
     57    Object.defineProperties( MediaKeySystemAccessProxy.prototype, {
     58        keysystem: { get: function() { return this._keysystem; } }
     59    });
     60 
     61    MediaKeySystemAccessProxy.prototype.getConfiguration = function getConfiguration()
     62    {
     63        return this._configuration;
     64    };
     65 
     66    MediaKeySystemAccessProxy.prototype.createMediaKeys = function createMediaKeys()
     67    {
     68        return new Promise( function( resolve, reject ) {
     69 
     70            this._access.createMediaKeys()
     71            .then( function( mediaKeys ) { resolve( new MediaKeysProxy( mediaKeys ) ); })
     72            .catch( function( error ) { reject( error ); } );
     73 
     74        }.bind( this ) );
     75    };
     76 
     77    function MediaKeysProxy( mediaKeys )
     78    {
     79        this._mediaKeys = mediaKeys;
     80        this._sessions = [ ];
     81        this._videoelement = undefined;
     82    }
     83 
     84    MediaKeysProxy.prototype._setVideoElement = function _setVideoElement( videoElement )
     85    {
     86        if ( videoElement !== this._videoelement )
     87        {
     88            this._videoelement = videoElement;
     89        }
     90    };
     91 
     92 
     93    MediaKeysProxy.prototype._removeSession = function _removeSession( session )
     94    {
     95        var index = this._sessions.indexOf( session );
     96        if ( index !== -1 ) this._sessions.splice( index, 1 );
     97    };
     98 
     99    MediaKeysProxy.prototype.createSession = function createSession( sessionType )
    100    {
    101        if ( !sessionType || sessionType === 'temporary' ) return this._mediaKeys.createSession();
    102 
    103        var session = new MediaKeySessionProxy( this, sessionType );
    104        this._sessions.push( session );
    105 
    106        return session;
    107    };
    108 
    109    MediaKeysProxy.prototype.setServerCertificate = function setServerCertificate( certificate )
    110    {
    111        return this._mediaKeys.setServerCertificate( certificate );
    112    };
    113 
    114    function MediaKeySessionProxy( mediaKeysProxy, sessionType )
    115    {
    116        EventTarget.call( this );
    117 
    118        this._mediaKeysProxy = mediaKeysProxy
    119        this._sessionType = sessionType;
    120        this._sessionId = "";
    121 
    122        // MediaKeySessionProxy states
    123        // 'created' - After initial creation
    124        // 'loading' - Persistent license session waiting for key message to load stored keys
    125        // 'active' - Normal active state - proxy all key messages
    126        // 'removing' - Release message generated, waiting for ack
    127        // 'closed' - Session closed
    128        this._state = 'created';
    129 
    130        this._closed = new Promise( function( resolve ) { this._resolveClosed = resolve; }.bind( this ) );
    131    }
    132 
    133    MediaKeySessionProxy.prototype = Object.create( EventTarget.prototype );
    134 
    135    Object.defineProperties( MediaKeySessionProxy.prototype, {
    136 
    137        sessionId:  { get: function() { return this._sessionId; } },
    138        expiration: { get: function() { return NaN; } },
    139        closed:     { get: function() { return this._closed; } },
    140        keyStatuses:{ get: function() { return this._session.keyStatuses; } },       // TODO this will fail if examined too early
    141        _kids:      { get: function() { return this._keys.map( function( key ) { return key.kid; } ); } },
    142    });
    143 
    144    MediaKeySessionProxy.prototype._createSession = function _createSession()
    145    {
    146        this._session = this._mediaKeysProxy._mediaKeys.createSession();
    147 
    148        this._session.addEventListener( 'message', MediaKeySessionProxy.prototype._onMessage.bind( this ) );
    149        this._session.addEventListener( 'keystatuseschange', MediaKeySessionProxy.prototype._onKeyStatusesChange.bind( this ) );
    150    };
    151 
    152    MediaKeySessionProxy.prototype._onMessage = function _onMessage( event )
    153    {
    154        switch( this._state )
    155        {
    156            case 'loading':
    157                this._session.update( toUtf8( { keys: this._keys } ) )
    158                .then( function() {
    159                    this._state = 'active';
    160                    this._loaded( true );
    161                }.bind(this)).catch( this._loadfailed );
    162 
    163                break;
    164 
    165            case 'active':
    166                this.dispatchEvent( event );
    167                break;
    168 
    169            default:
    170                // Swallow the event
    171                break;
    172        }
    173    };
    174 
    175    MediaKeySessionProxy.prototype._onKeyStatusesChange = function _onKeyStatusesChange( event )
    176    {
    177        switch( this._state )
    178        {
    179            case 'active' :
    180            case 'removing' :
    181                this.dispatchEvent( event );
    182                break;
    183 
    184            default:
    185                // Swallow the event
    186                break;
    187        }
    188    };
    189 
    190    MediaKeySessionProxy.prototype._queueMessage = function _queueMessage( messageType, message )
    191    {
    192        setTimeout( function() {
    193 
    194            var messageAsArray = toUtf8( message ).buffer;
    195 
    196            this.dispatchEvent( new MediaKeyMessageEvent( 'message', { messageType: messageType, message: messageAsArray } ) );
    197 
    198        }.bind( this ) );
    199    };
    200 
    201    function _storageKey( sessionId )
    202    {
    203        return sessionId;
    204    }
    205 
    206    MediaKeySessionProxy.prototype._store = function _store()
    207    {
    208        var data = { keys: this._keys };
    209 
    210        window.localStorage.setItem( _storageKey( this._sessionId ), JSON.stringify( data ) );
    211    };
    212 
    213    MediaKeySessionProxy.prototype._load = function _load( sessionId )
    214    {
    215        var store = window.localStorage.getItem( _storageKey( sessionId ) );
    216        if ( store === null ) return false;
    217 
    218        var data;
    219        try { data = JSON.parse( store ) } catch( error ) {
    220            return false;
    221        }
    222 
    223        this._sessionType = 'persistent-license';
    224        this._keys = data.keys;
    225 
    226        return true;
    227    };
    228 
    229    MediaKeySessionProxy.prototype._clear = function _clear()
    230    {
    231        window.localStorage.removeItem( _storageKey( this._sessionId ) );
    232    };
    233 
    234    MediaKeySessionProxy.prototype.generateRequest = function generateRequest( initDataType, initData )
    235    {
    236        if ( this._state !== 'created' ) return Promise.reject( new InvalidStateError() );
    237 
    238        this._createSession();
    239 
    240        this._state = 'active';
    241 
    242        return this._session.generateRequest( initDataType, initData )
    243        .then( function() {
    244            this._sessionId = Math.random().toString(36).slice(2);
    245        }.bind( this ) );
    246    };
    247 
    248    MediaKeySessionProxy.prototype.load = function load( sessionId )
    249    {
    250        if ( this._state !== 'created' ) return Promise.reject( new InvalidStateError() );
    251 
    252        return new Promise( function( resolve, reject ) {
    253 
    254            try
    255            {
    256                if ( !this._load( sessionId ) )
    257                {
    258                    resolve( false );
    259 
    260                    return;
    261                }
    262 
    263                this._sessionId = sessionId;
    264 
    265 
    266                this._createSession();
    267 
    268                this._state = 'loading';
    269                this._loaded = resolve;
    270                this._loadfailed = reject;
    271 
    272                var initData = { kids: this._kids };
    273 
    274                this._session.generateRequest( 'keyids', toUtf8( initData ) );
    275            }
    276            catch( error )
    277            {
    278                reject( error );
    279            }
    280        }.bind( this ) );
    281    };
    282 
    283    MediaKeySessionProxy.prototype.update = function update( response )
    284    {
    285        return new Promise( function( resolve, reject ) {
    286 
    287            switch( this._state ) {
    288 
    289                case 'active' :
    290 
    291                    var message = fromUtf8( response );
    292 
    293                    // JSON Web Key Set
    294                    this._keys = message.keys;
    295 
    296                    this._store();
    297 
    298                    resolve( this._session.update( response ) );
    299 
    300                    break;
    301 
    302                case 'removing' :
    303 
    304                    this._state = 'closed';
    305 
    306                    this._clear();
    307 
    308                    this._mediaKeysProxy._removeSession( this );
    309 
    310                    this._resolveClosed();
    311 
    312                    delete this._session;
    313 
    314                    resolve();
    315 
    316                    break;
    317 
    318                default:
    319                    reject( new InvalidStateError() );
    320            }
    321 
    322        }.bind( this ) );
    323    };
    324 
    325    MediaKeySessionProxy.prototype.close = function close()
    326    {
    327        if ( this._state === 'closed' ) return Promise.resolve();
    328 
    329        this._state = 'closed';
    330 
    331        this._mediaKeysProxy._removeSession( this );
    332 
    333        this._resolveClosed();
    334 
    335        var session = this._session;
    336        if ( !session ) return Promise.resolve();
    337 
    338        this._session = undefined;
    339 
    340        return session.close();
    341    };
    342 
    343    MediaKeySessionProxy.prototype.remove = function remove()
    344    {
    345        if ( this._state !== 'active' || !this._session ) return Promise.reject( new DOMException('InvalidStateError('+this._state+')') );
    346 
    347        this._state = 'removing';
    348 
    349        this._mediaKeysProxy._removeSession( this );
    350 
    351        return this._session.close()
    352        .then( function() {
    353 
    354            var msg = { kids: this._kids };
    355 
    356            this._queueMessage( 'license-release', msg );
    357 
    358        }.bind( this ) )
    359    };
    360 
    361    HTMLMediaElement.prototype.setMediaKeys = function setMediaKeys( mediaKeys )
    362    {
    363        if ( mediaKeys instanceof MediaKeysProxy )
    364        {
    365            mediaKeys._setVideoElement( this );
    366            return _setMediaKeys.call( this, mediaKeys._mediaKeys );
    367        }
    368        else
    369        {
    370            return _setMediaKeys.call( this, mediaKeys );
    371        }
    372    };
    373 
    374    navigator.requestMediaKeySystemAccess = function( keysystem, configurations )
    375    {
    376        // First, see if this is supported by the platform
    377        return new Promise( function( resolve, reject ) {
    378 
    379            _requestMediaKeySystemAccess( keysystem, configurations )
    380            .then( function( access ) { resolve( access ); } )
    381            .catch( function( error ) {
    382 
    383                if ( error instanceof TypeError ) reject( error );
    384 
    385                if ( keysystem !== 'org.w3.clearkey' ) reject( error );
    386 
    387                if ( !configurations.some( is_persistent_configuration ) ) reject( error );
    388 
    389                // Shallow copy the configurations, swapping out the labels and omitting the sessiontypes
    390                var configurations_copy = configurations.map( function( config, index ) {
    391 
    392                    var config_copy = copy_configuration( config );
    393                    config_copy.label = index.toString();
    394                    return config_copy;
    395 
    396                } );
    397 
    398                // And try again with these configurations
    399                _requestMediaKeySystemAccess( keysystem, configurations_copy )
    400                .then( function( access ) {
    401 
    402                    // Create the supported configuration based on the original request
    403                    var configuration = access.getConfiguration(),
    404                        original_configuration = configurations[ configuration.label ];
    405 
    406                    // If the original configuration did not need persistent session types, then we're done
    407                    if ( !is_persistent_configuration( original_configuration ) ) resolve( access );
    408 
    409                    // Create the configuration that we will return
    410                    var returned_configuration = copy_configuration( configuration );
    411 
    412                    if ( original_configuration.label )
    413                        returned_configuration.label = original_configuration;
    414                    else
    415                        delete returned_configuration.label;
    416 
    417                    returned_configuration.sessionTypes = original_configuration.sessionTypes;
    418 
    419                    resolve( new MediaKeySystemAccessProxy( keysystem, access, returned_configuration ) );
    420                } )
    421                .catch( function( error ) { reject( error ); } );
    422            } );
    423        } );
    424    };
    425 
    426    function is_persistent_configuration( configuration )
    427    {
    428        return configuration.sessionTypes &&
    429                ( configuration.sessionTypes.indexOf( 'persistent-license' ) !== -1 );
    430    }
    431 
    432    function copy_configuration( src )
    433    {
    434        var dst = {};
    435        [ 'label', 'initDataTypes', 'audioCapabilities', 'videoCapabilities', 'distinctiveIdenfifier', 'persistentState' ]
    436        .forEach( function( item ) { if ( src[item] ) dst[item] = src[item]; } );
    437        return dst;
    438    }
    439 }());