1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // Utils provide logging functions and other JS functions commonly used by the
6 // app and media players.
7 var Utils
= new function() {
8 this.titleChanged
= false;
11 // Adds options to document element.
12 Utils
.addOptions = function(elementID
, keyValueOptions
, disabledOptions
) {
13 disabledOptions
= disabledOptions
|| [];
14 var selectElement
= document
.getElementById(elementID
);
15 var keys
= Object
.keys(keyValueOptions
);
16 for (var i
= 0; i
< keys
.length
; i
++) {
18 var option
= new Option(key
, keyValueOptions
[key
]);
19 option
.title
= keyValueOptions
[key
];
20 if (disabledOptions
.indexOf(key
) >= 0)
21 option
.disabled
= true;
22 selectElement
.options
.add(option
);
26 Utils
.convertToArray = function(input
) {
27 if (Array
.isArray(input
))
32 Utils
.convertToUint8Array = function(msg
) {
33 if (typeof msg
== 'string') {
34 var ans
= new Uint8Array(msg
.length
);
35 for (var i
= 0; i
< msg
.length
; i
++) {
36 ans
[i
] = msg
.charCodeAt(i
);
40 // Assume it is an ArrayBuffer or ArrayBufferView. If it already is a
41 // Uint8Array, this will just make a copy of the view.
42 return new Uint8Array(msg
);
45 Utils
.createJWKData = function(keyId
, key
) {
46 // JWK routines copied from third_party/WebKit/LayoutTests/media/
47 // encrypted-media/encrypted-media-utils.js
49 // Encodes data (Uint8Array) into base64url string. There is no '=' padding,
50 // and the characters '-' and '_' must be used instead of '+' and '/',
52 function base64urlEncode(data
) {
53 var result
= btoa(String
.fromCharCode
.apply(null, data
));
54 return result
.replace(/=+$/g, '').replace(/\+/g, "-").replace(/\//g, "_");
57 // Creates a JWK from raw key ID and key.
58 function createJWK(keyId
, key
) {
59 var jwk
= '{"kty":"oct","alg":"A128KW","kid":"';
60 jwk
+= base64urlEncode(keyId
);
62 jwk
+= base64urlEncode(key
);
67 // Creates a JWK Set from an array of JWK(s).
68 function createJWKSet() {
69 var jwkSet
= '{"keys":[';
70 for (var i
= 0; i
< arguments
.length
; i
++) {
73 jwkSet
+= arguments
[i
];
79 return Utils
.convertToUint8Array(createJWKSet(createJWK(keyId
, key
)));
82 Utils
.extractFirstLicenseKeyId = function(message
) {
83 // Decodes data (Uint8Array) from base64url string.
84 function base64urlDecode(data
) {
85 return atob(data
.replace(/\-/g, "+").replace(/\_/g, "/"));
88 function convertToString(data
) {
89 return String
.fromCharCode
.apply(null, Utils
.convertToUint8Array(data
));
93 var json
= JSON
.parse(convertToString(message
));
94 // Decode the first element of 'kids', return it as an Uint8Array.
95 return Utils
.convertToUint8Array(base64urlDecode(json
.kids
[0]));
97 // Not valid JSON, so return message untouched as Uint8Array.
98 return Utils
.convertToUint8Array(message
);
102 Utils
.documentLog = function(log
, success
, time
) {
105 time
= time
|| Utils
.getCurrentTimeString();
106 var timeLog
= '<span style="color: green">' + time
+ '</span>';
107 var logColor
= !success
? 'red' : 'black'; // default is true.
108 log
= '<span style="color: "' + logColor
+ '>' + log
+ '</span>';
109 docLogs
.innerHTML
= timeLog
+ ' - ' + log
+ '<br>' + docLogs
.innerHTML
;
112 Utils
.ensureOptionInList = function(listID
, option
) {
113 var selectElement
= document
.getElementById(listID
);
114 for (var i
= 0; i
< selectElement
.length
; i
++) {
115 if (selectElement
.options
[i
].value
== option
) {
116 selectElement
.value
= option
;
120 // The list does not have the option, let's add it and select it.
121 var optionElement
= new Option(option
, option
);
122 optionElement
.title
= option
;
123 selectElement
.options
.add(optionElement
);
124 selectElement
.value
= option
;
127 Utils
.failTest = function(msg
, newTitle
) {
128 var failMessage
= 'FAIL: ';
129 var title
= 'FAILED';
130 // Handle exception messages;
132 title
= msg
.name
|| 'Error';
133 failMessage
+= title
+ ' ' + msg
.message
;
134 } else if (msg
instanceof Event
) {
135 // Handle failing events.
136 failMessage
= msg
.target
+ '.' + msg
.type
;
141 // Force newTitle if passed.
142 title
= newTitle
|| title
;
144 Utils
.documentLog(failMessage
, false);
145 console
.log(failMessage
, msg
);
146 Utils
.setResultInTitle(title
);
149 Utils
.getCurrentTimeString = function() {
150 var date
= new Date();
151 var hours
= ('0' + date
.getHours()).slice(-2);
152 var minutes
= ('0' + date
.getMinutes()).slice(-2);
153 var secs
= ('0' + date
.getSeconds()).slice(-2);
154 var milliSecs
= ('00' + date
.getMilliseconds()).slice(-3);
155 return hours
+ ':' + minutes
+ ':' + secs
+ '.' + milliSecs
;
158 Utils
.getDefaultKey = function(forceInvalidResponse
) {
159 if (forceInvalidResponse
) {
160 Utils
.timeLog('Forcing invalid key data.');
161 return new Uint8Array([0xAA]);
166 Utils
.getHexString = function(uintArray
) {
168 for (var i
= 0; i
< uintArray
.length
; i
++) {
169 var hex
= uintArray
[i
].toString(16);
177 Utils
.hasPrefix = function(msg
, prefix
) {
178 var message
= String
.fromCharCode
.apply(null, msg
);
179 return message
.substring(0, prefix
.length
) == prefix
;
182 Utils
.installTitleEventHandler = function(element
, event
) {
183 element
.addEventListener(event
, function(e
) {
184 Utils
.setResultInTitle(e
.type
);
188 Utils
.isRenewalMessage = function(message
) {
189 if (message
.messageType
!= 'license-renewal')
192 if (!Utils
.isRenewalMessagePrefixed(message
.message
)) {
193 Utils
.failTest('license-renewal message doesn\'t contain expected header',
194 PREFIXED_EME_RENEWAL_MISSING_HEADER
);
199 // For the prefixed API renewal messages are determined by looking at the
200 // message and finding a known string.
201 Utils
.isRenewalMessagePrefixed = function(msg
) {
202 return Utils
.hasPrefix(Utils
.convertToUint8Array(msg
),
203 PREFIXED_EME_RENEWAL_MESSAGE_HEADER
);
206 Utils
.resetTitleChange = function() {
207 this.titleChanged
= false;
211 Utils
.sendRequest = function(requestType
, responseType
, message
, serverURL
,
212 onSuccessCallbackFn
, forceInvalidResponse
) {
213 var requestAttemptCount
= 0;
214 var REQUEST_RETRY_DELAY_MS
= 3000;
215 var REQUEST_TIMEOUT_MS
= 1000;
217 function sendRequestAttempt() {
218 // No limit on the number of retries. This will retry on failures
219 // until the test framework stops the test.
220 requestAttemptCount
++;
221 var xmlhttp
= new XMLHttpRequest();
222 xmlhttp
.responseType
= responseType
;
223 xmlhttp
.open(requestType
, serverURL
, true);
224 xmlhttp
.onerror = function(e
) {
225 Utils
.timeLog('Request status: ' + this.statusText
);
226 Utils
.timeLog('FAILED: License request XHR failed with network error.');
227 Utils
.timeLog('Retrying request in ' + REQUEST_RETRY_DELAY_MS
+ 'ms');
228 setTimeout(sendRequestAttempt
, REQUEST_RETRY_DELAY_MS
);
230 xmlhttp
.onload = function(e
) {
231 if (this.status
== 200) {
232 if (onSuccessCallbackFn
)
233 onSuccessCallbackFn(this.response
);
235 Utils
.timeLog('Bad response status: ' + this.status
);
236 Utils
.timeLog('Bad response: ' + this.response
);
237 Utils
.timeLog('Retrying request in ' + REQUEST_RETRY_DELAY_MS
+ 'ms');
238 setTimeout(sendRequestAttempt
, REQUEST_RETRY_DELAY_MS
);
241 xmlhttp
.timeout
= REQUEST_TIMEOUT_MS
;
242 xmlhttp
.ontimeout = function(e
) {
243 Utils
.timeLog('Request timeout');
244 Utils
.timeLog('Retrying request in ' + REQUEST_RETRY_DELAY_MS
+ 'ms');
245 setTimeout(sendRequestAttempt
, REQUEST_RETRY_DELAY_MS
);
247 Utils
.timeLog('Attempt (' + requestAttemptCount
+
248 '): sending request to server: ' + serverURL
);
249 xmlhttp
.send(message
);
252 if (forceInvalidResponse
) {
253 Utils
.timeLog('Not sending request - forcing an invalid response.');
254 return onSuccessCallbackFn([0xAA]);
256 sendRequestAttempt();
259 Utils
.setResultInTitle = function(title
) {
260 // If document title is 'ENDED', then update it with new title to possibly
261 // mark a test as failure. Otherwise, keep the first title change in place.
262 if (!this.titleChanged
|| document
.title
.toUpperCase() == 'ENDED')
263 document
.title
= title
.toUpperCase();
264 Utils
.timeLog('Set document title to: ' + title
+ ', updated title: ' +
266 this.titleChanged
= true;
269 Utils
.timeLog = function(/**/) {
270 if (arguments
.length
== 0)
272 var time
= Utils
.getCurrentTimeString();
274 Utils
.documentLog(arguments
[0], time
);
275 // Log to JS console.
276 var logString
= time
+ ' - ';
277 for (var i
= 0; i
< arguments
.length
; i
++) {
278 logString
+= ' ' + arguments
[i
];
280 console
.log(logString
);