mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-17 12:47:30 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
|
|
@ -0,0 +1,80 @@
|
|||
(function() {
|
||||
|
||||
if ( /CrKey\/[0-9]+\.[0-9a-z]+\.[0-9a-z]+/i.exec( navigator.userAgent ) ) {
|
||||
|
||||
var castscript = document.createElement('script');
|
||||
castscript.type = 'text/javascript';
|
||||
castscript.src = 'https://www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js'
|
||||
document.head.appendChild( castscript );
|
||||
|
||||
var _requestMediaKeySystemAccess = navigator.requestMediaKeySystemAccess.bind( navigator ),
|
||||
_setMediaKeys = HTMLMediaElement.prototype.setMediaKeys,
|
||||
_load = MediaKeySession.prototype.load;
|
||||
|
||||
MediaKeySession.prototype.load = function load()
|
||||
{
|
||||
return _load.call( this ).then( function( success )
|
||||
{
|
||||
return success ? this.remove() : false;
|
||||
}.bind( this ) );
|
||||
};
|
||||
|
||||
function MediaKeys( mediaKeys )
|
||||
{
|
||||
this._mediaKeys = mediaKeys;
|
||||
}
|
||||
|
||||
MediaKeys.prototype.setServerCertificate = function setServerCertificate( certificate )
|
||||
{
|
||||
return this._mediaKeys.setServerCertificate( certificate );
|
||||
};
|
||||
|
||||
MediaKeys.prototype.createSession = function createSession( sessionType ) {
|
||||
|
||||
if ( sessionType === 'persistent-usage-record' )
|
||||
{
|
||||
return cast.receiver.eme.KeySession.createSession( this._mediaKeys, 'persistent-release-message' );
|
||||
}
|
||||
|
||||
return this._mediaKeys.createSession( sessionType );
|
||||
};
|
||||
|
||||
function MediaKeySystemAccess( access )
|
||||
{
|
||||
this._access = mediaKeySystemAccess;
|
||||
}
|
||||
|
||||
Object.defineProperty( MediaKeySystemAccess.prototype, 'keySystem', { get: function() { return this._access.keySystem; } } );
|
||||
|
||||
MediaKeySystemAccess.prototype.getConfiguration = function getConfiguration() { return this._access.getConfiguration(); };
|
||||
|
||||
MediaKeySystemAccess.prototype.createMediaKeys = function createMediaKeys() {
|
||||
|
||||
return this._access.createMediaKey().then( function( mediaKeys ) { return new MediaKeys( mediaKeys ); } );
|
||||
|
||||
};
|
||||
|
||||
HTMLMediaElement.prototype.setMediaKeys = function setMediaKeys( mediaKeys )
|
||||
{
|
||||
if ( mediaKeys instanceof MediaKeys )
|
||||
{
|
||||
return _setMediaKeys.call( this, mediaKeys._mediaKeys );
|
||||
}
|
||||
else
|
||||
{
|
||||
return _setMediaKeys.call( this, mediaKeys );
|
||||
}
|
||||
};
|
||||
|
||||
navigator.requestMediaKeySystemAccess = function requestMediaKeySystemAccess( keysystem, supportedConfigurations ) {
|
||||
|
||||
if ( keysystem !== 'com.chromecast.playready' )
|
||||
{
|
||||
return _requestMediaKeySystemAccess( keysystem, supportedConfigurations );
|
||||
}
|
||||
|
||||
return _requestMediaKeySystemAccess( keysystem, supportedConfigurations )
|
||||
.then( function( access ) { return new MediaKeySystemAccess( access ); } );
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
(function(){
|
||||
if( navigator.userAgent.toLowerCase().indexOf('edge') === -1
|
||||
&& navigator.userAgent.toLowerCase().indexOf('chrome') > -1){
|
||||
|
||||
if ( ( /chrome\/([0-9]*)\./.exec( navigator.userAgent.toLowerCase() )[1] | 0 ) < 54 ) {
|
||||
|
||||
// Work around https://bugs.chromium.org/p/chromium/issues/detail?id=622956
|
||||
// Chrome does not fire the empty keystatuschange event when a session is closed
|
||||
var _mediaKeySessionClose = MediaKeySession.prototype.close;
|
||||
var _mediaKeySessionKeyStatusesGetter = Object.getOwnPropertyDescriptor( MediaKeySession.prototype, 'keyStatuses' ).get;
|
||||
var _emptyMediaKeyStatusMap = { size: 0,
|
||||
has: function() { return false; },
|
||||
get: function() { return undefined; },
|
||||
entries:function() { return []; }, // this may not be correct, I think it should be some iterator thing
|
||||
keys: function() { return []; },
|
||||
values: function() { return []; },
|
||||
forEach:function() { return; } };
|
||||
|
||||
MediaKeySession.prototype.close = function close()
|
||||
{
|
||||
this.__closed = true;
|
||||
|
||||
setTimeout( function() {
|
||||
this.dispatchEvent( new Event( 'keystatuseschange' ) );
|
||||
}.bind( this ), 0 );
|
||||
|
||||
return _mediaKeySessionClose.call( this );
|
||||
};
|
||||
|
||||
Object.defineProperty( MediaKeySession.prototype, 'keyStatuses', { get: function() {
|
||||
|
||||
return this.__closed ? _emptyMediaKeyStatusMap : _mediaKeySessionKeyStatusesGetter.call( this );
|
||||
|
||||
} } );
|
||||
}
|
||||
}
|
||||
}());
|
||||
|
|
@ -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;
|
||||
}
|
||||
}());
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
(function() {
|
||||
|
||||
// This polyfill fixes the following problems with Edge browser
|
||||
// (1) Various maplike methods for keystatuses are not supported or suported incorrectly
|
||||
// (2) Key Ids exposed in keystatuses are incorrect (byte swaps)
|
||||
if ( navigator.userAgent.toLowerCase().indexOf('edge') > -1 ) {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// The following function is the core of this JS patch. The rest of this file is infrastructure
|
||||
// required to enable this function
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
function _proxyKeyStatusesChange( event ) {
|
||||
this._keyStatuses.clear();
|
||||
var keyStatuses = [];
|
||||
this._session.keyStatuses.forEach( function( keyId, status ) {
|
||||
var newKeyId = new Uint8Array( keyId );
|
||||
|
||||
function swap( arr, a, b ) { var t = arr[a]; arr[a] = arr[b]; arr[b] = t; }
|
||||
swap( newKeyId, 0, 3 );
|
||||
swap( newKeyId, 1, 2 );
|
||||
swap( newKeyId, 4, 5 );
|
||||
swap( newKeyId, 6, 7 );
|
||||
|
||||
keyStatuses.push( { key: newKeyId, status: status, ord: arrayBufferAsString( newKeyId ) } );
|
||||
});
|
||||
|
||||
function lexicographical( a, b ) { return a < b ? -1 : a === b ? 0 : +1; }
|
||||
function lexicographicalkey( a, b ) { return lexicographical( a.ord, b.ord ); }
|
||||
|
||||
keyStatuses.sort( lexicographicalkey ).forEach( function( obj ) {
|
||||
this._keyStatuses._set( obj.key, obj.status );
|
||||
}.bind( this ) );
|
||||
|
||||
this.dispatchEvent( event );
|
||||
};
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Override MediaKeys.createSession
|
||||
var _mediaKeysCreateSession = MediaKeys.prototype.createSession;
|
||||
MediaKeys.prototype.createSession = function ( sessionType ) {
|
||||
return new MediaKeySession( _mediaKeysCreateSession.call( this, sessionType ) );
|
||||
};
|
||||
|
||||
// MediaKeySession proxy
|
||||
function MediaKeySession( session ) {
|
||||
EventTarget.call( this );
|
||||
this._session = session;
|
||||
this._keyStatuses = new MediaKeyStatusMap();
|
||||
this._session.addEventListener("keystatuseschange",this._onKeyStatusesChange.bind(this));
|
||||
this._session.addEventListener("message",this.dispatchEvent.bind(this));
|
||||
}
|
||||
|
||||
MediaKeySession.prototype = Object.create( EventTarget.prototype );
|
||||
|
||||
Object.defineProperties( MediaKeySession.prototype, {
|
||||
sessionId: { get: function() { return this._session.sessionId; } },
|
||||
expiration: { get: function() { return this._session.expiration; } },
|
||||
closed: { get: function() { return this._session.closed; } },
|
||||
keyStatuses:{ get: function() { return this._keyStatuses; } }
|
||||
});
|
||||
|
||||
[ "generateRequest", "load", "update", "remove", "close" ].forEach( function( fnname ) {
|
||||
MediaKeySession.prototype[ fnname ] = function() {
|
||||
return window.MediaKeySession.prototype[ fnname ].apply( this._session, arguments );
|
||||
}
|
||||
} );
|
||||
|
||||
MediaKeySession.prototype._onKeyStatusesChange = _proxyKeyStatusesChange;
|
||||
|
||||
// MediaKeyStatusMap proxy
|
||||
//
|
||||
// We need a proxy class to replace the broken MediaKeyStatusMap one. We cannot use a
|
||||
// regular Map directly because we need get and has methods to compare by value not
|
||||
// as references.
|
||||
function MediaKeyStatusMap() { this._map = new Map(); }
|
||||
|
||||
Object.defineProperties( MediaKeyStatusMap.prototype, {
|
||||
size: { get: function() { return this._map.size; } },
|
||||
forEach: { get: function() { return function( f ) { return this._map.forEach( f ); } } },
|
||||
entries: { get: function() { return function() { return this._map.entries(); } } },
|
||||
values: { get: function() { return function() { return this._map.values(); } } },
|
||||
keys: { get: function() { return function() { return this._map.keys(); } } },
|
||||
clear: { get: function() { return function() { return this._map.clear(); } } } } );
|
||||
|
||||
MediaKeyStatusMap.prototype[ Symbol.iterator ] = function() { return this._map[ Symbol.iterator ]() };
|
||||
|
||||
MediaKeyStatusMap.prototype.has = function has( keyId ) {
|
||||
for ( var k of this._map.keys() ) { if ( arrayBufferEqual( k, keyId ) ) return true; }
|
||||
return false;
|
||||
};
|
||||
|
||||
MediaKeyStatusMap.prototype.get = function get( keyId ) {
|
||||
for ( var k of this._map.entries() ) { if ( arrayBufferEqual( k[ 0 ], keyId ) ) return k[ 1 ]; }
|
||||
};
|
||||
|
||||
MediaKeyStatusMap.prototype._set = function _set( keyId, status ) {
|
||||
this._map.set( new Uint8Array( keyId ), status );
|
||||
};
|
||||
|
||||
function arrayBufferEqual(buf1, buf2)
|
||||
{
|
||||
if (buf1.byteLength !== buf2.byteLength) return false;
|
||||
var a1 = Array.from( new Int8Array(buf1) ), a2 = Array.from( new Int8Array(buf2) );
|
||||
return a1.every( function( x, i ) { return x === a2[i]; } );
|
||||
}
|
||||
|
||||
// EventTarget
|
||||
function EventTarget(){
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
(function() {
|
||||
|
||||
// This polyfill fixes the following problems with Edge browser
|
||||
// (1) To retrieve a persisted usage record, you must use session type 'persistent-release-message' instead of 'persistent-usage-record'
|
||||
// (2) To retrieve a persisted usage record, you must call remove() after calling load()
|
||||
// (3) On providing a license release acknowledgement, the session does not automatically close as is should
|
||||
// (4) Retrieval of the usage record at the end of an active session is not supported
|
||||
|
||||
if ( navigator.userAgent.toLowerCase().indexOf('edge') > -1 ) {
|
||||
|
||||
var _mediaKeySystemAccessCreateMediaKeys = MediaKeySystemAccess.prototype.createMediaKeys;
|
||||
_mediaKeysCreateSession = MediaKeys.prototype.createSession;
|
||||
|
||||
// MediaKeySession proxy
|
||||
function MediaKeySession( mediaKeys, session )
|
||||
{
|
||||
EventTarget.call( this );
|
||||
|
||||
this._mediaKeys = mediaKeys;
|
||||
this._session = session;
|
||||
this._sessionId = undefined;
|
||||
this._removing = false;
|
||||
|
||||
session.addEventListener( 'message', this.dispatchEvent.bind( this ) );
|
||||
session.addEventListener( 'keystatuseschange', this.dispatchEvent.bind( this ) );
|
||||
session.closed.then( function() { if ( !this._removing ) this._resolveClosed(); }.bind ( this ) );
|
||||
|
||||
this._closed = new Promise( function( resolve ) { this._resolveClosed = resolve; }.bind( this ) );
|
||||
}
|
||||
|
||||
MediaKeySession.prototype = Object.create( EventTarget.prototype );
|
||||
|
||||
Object.defineProperties( MediaKeySession.prototype, {
|
||||
sessionId: { get: function() { return this._sessionId ? this._sessionId : this._session.sessionId; } },
|
||||
expiration: { get: function() { return this._session.expiration; } },
|
||||
closed: { get: function() { return this._closed; } },
|
||||
keyStatuses:{ get: function() { return this._session.keyStatuses; } }
|
||||
});
|
||||
|
||||
// load()
|
||||
//
|
||||
// Use a surrogate 'persistent-release-message' session to obtain the release message
|
||||
//
|
||||
MediaKeySession.prototype.load = function load( sessionId )
|
||||
{
|
||||
if ( this.sessionId ) return Promise.reject( new DOMException('InvalidAccessError') );
|
||||
|
||||
this._surrogate = this._mediaKeys.createSession( 'persistent-release-message' );
|
||||
this._surrogate.addEventListener( 'message', this.dispatchEvent.bind( this ) );
|
||||
|
||||
return this._surrogate.load( sessionId ).then( function( success ) {
|
||||
if (!success) return false;
|
||||
|
||||
this._sessionId = sessionId;
|
||||
this._removing = true;
|
||||
this._session.close();
|
||||
|
||||
return this._surrogate.remove().then( function() { return true; } );
|
||||
}.bind( this ) );
|
||||
};
|
||||
|
||||
// remove()
|
||||
//
|
||||
// On an existing session, use a surrogate 'persistent-release-message' session to obtain the release message
|
||||
//
|
||||
MediaKeySession.prototype.remove = function remove()
|
||||
{
|
||||
if ( this._sessionId !== undefined ) return Promise.reject( new DOMException('InvalidAccessError') );
|
||||
if ( this.sessionId === undefined ) return Promise.reject( new DOMException('InvalidAccessError') );
|
||||
|
||||
this._surrogate = this._mediaKeys.createSession( 'persistent-release-message' );
|
||||
this._surrogate.addEventListener( 'message', this.dispatchEvent.bind( this ) );
|
||||
this._removing = true;
|
||||
this._sessionId = this._session.sessionId;
|
||||
|
||||
var self = this;
|
||||
|
||||
return Promise.all( [ self._session.close(), self._session.closed ] ).then( function() {
|
||||
return self._surrogate.load( self._sessionId );
|
||||
}).then( function( success ) {
|
||||
if ( !success ) {
|
||||
throw new DOMException('InvalidAccessError');
|
||||
}
|
||||
|
||||
return self._surrogate.remove();
|
||||
}).then( function() { return true; } );
|
||||
}
|
||||
|
||||
// update()
|
||||
//
|
||||
// For a normal session, pass through, otherwise update the surrogate and close the proxy
|
||||
MediaKeySession.prototype.update = function update( message )
|
||||
{
|
||||
if ( !this._removing ) return this._session.update( message );
|
||||
|
||||
return this._surrogate.update( message ).then( function() {
|
||||
this._sessionId = undefined;
|
||||
this._resolveClosed();
|
||||
}.bind( this ) );
|
||||
};
|
||||
|
||||
// close() - pass through
|
||||
//
|
||||
MediaKeySession.prototype.close = function close()
|
||||
{
|
||||
if ( !this._removing ) return this._session.close();
|
||||
this._resolveClosed();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
// generateRequest() - pass through
|
||||
//
|
||||
MediaKeySession.prototype.generateRequest = function generateRequest( initDataType, initData )
|
||||
{
|
||||
if ( this.sessionId ) Promise.reject( new DOMException('InvalidAccessError') );
|
||||
return this._session.generateRequest( initDataType, initData );
|
||||
};
|
||||
|
||||
// Wrap PlayReady persistent-usage-record sessions in our Proxy
|
||||
MediaKeys.prototype.createSession = function createSession( sessionType ) {
|
||||
|
||||
var session = _mediaKeysCreateSession.call( this, sessionType );
|
||||
if ( this._keySystem !== 'com.microsoft.playready' || sessionType !== 'persistent-usage-record' )
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
return new MediaKeySession( this, session );
|
||||
|
||||
};
|
||||
|
||||
//
|
||||
// Annotation polyfills - annotate not otherwise available data
|
||||
//
|
||||
|
||||
// Annotate MediaKeys with the keysystem
|
||||
MediaKeySystemAccess.prototype.createMediaKeys = function createMediaKeys()
|
||||
{
|
||||
return _mediaKeySystemAccessCreateMediaKeys.call( this ).then( function( mediaKeys ) {
|
||||
mediaKeys._keySystem = this.keySystem;
|
||||
return mediaKeys;
|
||||
}.bind( this ) );
|
||||
};
|
||||
|
||||
//
|
||||
// Utilities
|
||||
//
|
||||
|
||||
// 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; } }
|
||||
} );
|
||||
|
||||
// Make an EventTarget base class
|
||||
function EventTarget(){
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
(function(){
|
||||
if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1){
|
||||
|
||||
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=1282142
|
||||
// Firefox does not correctly reject the Clear Key session types it does not support
|
||||
var _requestMediaKeySystemAccess = navigator.requestMediaKeySystemAccess.bind( navigator );
|
||||
|
||||
navigator.requestMediaKeySystemAccess = function( keysystem, configurations )
|
||||
{
|
||||
if ( keysystem !== 'org.w3.clearkey' ) return _requestMediaKeySystemAccess( keysystem, configurations );
|
||||
|
||||
var supported_configurations = configurations.filter( function( c ) {
|
||||
|
||||
return !c.sessionTypes || ( c.sessionTypes.length === 1 && c.sessionTypes[ 0 ] === 'temporary' );
|
||||
|
||||
} );
|
||||
|
||||
if ( supported_configurations.length === 0 ) return Promise.reject( new DOMException( 'None of the requested configurations were supported.' ) );
|
||||
|
||||
return _requestMediaKeySystemAccess( keysystem, supported_configurations );
|
||||
}
|
||||
}
|
||||
}());
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/python
|
||||
import os, re, os.path, glob
|
||||
|
||||
head = re.compile( r"^(\s*</head>)", re.MULTILINE )
|
||||
runtest = re.compile( r"runTest\(\s*(\S.*?)\s*\)", re.DOTALL )
|
||||
|
||||
scripts = '''
|
||||
<!-- Polyfill files (NOTE: These are added by auto-generation script) -->
|
||||
<script src=/encrypted-media/polyfill/chrome-polyfill.js></script>
|
||||
<script src=/encrypted-media/polyfill/firefox-polyfill.js></script>
|
||||
<script src=/encrypted-media/polyfill/edge-persistent-usage-record.js></script>
|
||||
<script src=/encrypted-media/polyfill/edge-keystatuses.js></script>
|
||||
<script src=/encrypted-media/polyfill/clearkey-polyfill.js></script>'''
|
||||
|
||||
def process_file( infile, outfile ) :
|
||||
with open( outfile, "w" ) as output :
|
||||
with open( infile, "r" ) as input :
|
||||
output.write( runtest.sub( r"runTest( \1, 'polyfill: ' )", head.sub( scripts + r"\1", input.read() ) ) )
|
||||
|
||||
if __name__ == '__main__' :
|
||||
if (not os.getcwd().endswith('polyfill')) :
|
||||
print "Please run from polyfill directory"
|
||||
exit( 1 )
|
||||
|
||||
for infile in glob.glob( "../*.html" ) :
|
||||
process_file( infile, os.path.basename( infile ) )
|
||||
|
||||
for infile in glob.glob( "../resources/*.html" ) :
|
||||
process_file( infile, os.path.join( "resources", os.path.basename( infile ) ) )
|
||||
Loading…
Add table
Add a link
Reference in a new issue