summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/encrypted-media/polyfill/edge-keystatuses.js
blob: 8861444591bbd8ec5c5e1e86ccad2d5c45adab39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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);
            }
        };
    }
})();