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 base64 string without trailing '='.
50 // TODO(jrummell): Update once the EME spec is updated to say base64url
52 function base64Encode(data) {
53 var result = btoa(String.fromCharCode.apply(null, data));
54 return result.replace(/=+$/g, '');
57 // Creates a JWK from raw key ID and key.
58 function createJWK(keyId, key) {
59 var jwk = '{"kty":"oct","kid":"';
60 jwk += base64Encode(keyId);
62 jwk += base64Encode(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.extractFirstLicenseKey = function(message) {
83 // Decodes data (Uint8Array) from base64 string.
84 // TODO(jrummell): Update once the EME spec is updated to say base64url
86 function base64Decode(data) {
90 function convertToString(data) {
91 return String.fromCharCode.apply(null, Utils.convertToUint8Array(data));
95 var json = JSON.parse(convertToString(message));
96 // Decode the first element of 'kids', return it as an Uint8Array.
97 return Utils.convertToUint8Array(base64Decode(json.kids[0]));
99 // Not valid JSON, so return message untouched as Uint8Array.
100 return Utils.convertToUint8Array(message);
104 Utils.documentLog = function(log, success, time) {
107 time = time || Utils.getCurrentTimeString();
108 var timeLog = '<span style="color: green">' + time + '</span>';
109 var logColor = !success ? 'red' : 'black'; // default is true.
110 log = '<span style="color: "' + logColor + '>' + log + '</span>';
111 docLogs.innerHTML = timeLog + ' - ' + log + '<br>' + docLogs.innerHTML;
114 Utils.ensureOptionInList = function(listID, option) {
115 var selectElement = document.getElementById(listID);
116 for (var i = 0; i < selectElement.length; i++) {
117 if (selectElement.options[i].value == option) {
118 selectElement.value = option;
122 // The list does not have the option, let's add it and select it.
123 var optionElement = new Option(option, option);
124 optionElement.title = option;
125 selectElement.options.add(optionElement);
126 selectElement.value = option;
129 Utils.failTest = function(msg, newTitle) {
130 var failMessage = 'FAIL: ';
131 var title = 'FAILED';
132 // Handle exception messages;
134 title = msg.name || 'Error';
135 failMessage += title + ' ' + msg.message;
136 } else if (msg instanceof Event) {
137 // Handle failing events.
138 failMessage = msg.target + '.' + msg.type;
143 // Force newTitle if passed.
144 title = newTitle || title;
146 Utils.documentLog(failMessage, false);
147 console.log(failMessage, msg);
148 Utils.setResultInTitle(title);
151 Utils.getCurrentTimeString = function() {
152 var date = new Date();
153 var hours = ('0' + date.getHours()).slice(-2);
154 var minutes = ('0' + date.getMinutes()).slice(-2);
155 var secs = ('0' + date.getSeconds()).slice(-2);
156 var milliSecs = ('00' + date.getMilliseconds()).slice(-3);
157 return hours + ':' + minutes + ':' + secs + '.' + milliSecs;
160 Utils.getDefaultKey = function(forceInvalidResponse) {
161 if (forceInvalidResponse) {
162 Utils.timeLog('Forcing invalid key data.');
163 return new Uint8Array([0xAA]);
168 Utils.getHexString = function(uintArray) {
170 for (var i = 0; i < uintArray.length; i++) {
171 var hex = uintArray[i].toString(16);
179 Utils.getInitDataFromMessage = function(message, mediaType, decodeJSONMessage) {
181 if (mediaType.indexOf('mp4') != -1) {
182 // Temporary hack for Clear Key in v0.1.
183 // If content uses mp4, then message.message is PSSH data. Instead of
184 // parsing that data we hard code the initData.
185 initData = Utils.convertToUint8Array(KEY_ID);
186 } else if (decodeJSONMessage) {
187 initData = Utils.extractFirstLicenseKey(message.message);
189 initData = Utils.convertToUint8Array(message.message);
194 Utils.hasPrefix = function(msg, prefix) {
195 var message = String.fromCharCode.apply(null, msg);
196 return message.substring(0, prefix.length) == prefix;
199 Utils.installTitleEventHandler = function(element, event) {
200 element.addEventListener(event, function(e) {
201 Utils.setResultInTitle(e.type);
205 Utils.isHeartBeatMessage = function(msg) {
206 return Utils.hasPrefix(Utils.convertToUint8Array(msg), HEART_BEAT_HEADER);
209 Utils.resetTitleChange = function() {
210 this.titleChanged = false;
214 Utils.sendRequest = function(requestType, responseType, message, serverURL,
215 onSuccessCallbackFn, forceInvalidResponse) {
216 var requestAttemptCount = 0;
217 var REQUEST_RETRY_DELAY_MS = 3000;
218 var REQUEST_TIMEOUT_MS = 1000;
220 function sendRequestAttempt() {
221 // No limit on the number of retries. This will retry on failures
222 // until the test framework stops the test.
223 requestAttemptCount++;
224 var xmlhttp = new XMLHttpRequest();
225 xmlhttp.responseType = responseType;
226 xmlhttp.open(requestType, serverURL, true);
227 xmlhttp.onerror = function(e) {
228 Utils.timeLog('Request status: ' + this.statusText);
229 Utils.timeLog('FAILED: License request XHR failed with network error.');
230 Utils.timeLog('Retrying request in ' + REQUEST_RETRY_DELAY_MS + 'ms');
231 setTimeout(sendRequestAttempt, REQUEST_RETRY_DELAY_MS);
233 xmlhttp.onload = function(e) {
234 if (this.status == 200) {
235 if (onSuccessCallbackFn)
236 onSuccessCallbackFn(this.response);
238 Utils.timeLog('Bad response status: ' + this.status);
239 Utils.timeLog('Bad response: ' + this.response);
240 Utils.timeLog('Retrying request in ' + REQUEST_RETRY_DELAY_MS + 'ms');
241 setTimeout(sendRequestAttempt, REQUEST_RETRY_DELAY_MS);
244 xmlhttp.timeout = REQUEST_TIMEOUT_MS;
245 xmlhttp.ontimeout = function(e) {
246 Utils.timeLog('Request timeout');
247 Utils.timeLog('Retrying request in ' + REQUEST_RETRY_DELAY_MS + 'ms');
248 setTimeout(sendRequestAttempt, REQUEST_RETRY_DELAY_MS);
250 Utils.timeLog('Attempt (' + requestAttemptCount +
251 '): sending request to server: ' + serverURL);
252 xmlhttp.send(message);
255 if (forceInvalidResponse) {
256 Utils.timeLog('Not sending request - forcing an invalid response.');
257 return onSuccessCallbackFn([0xAA]);
259 sendRequestAttempt();
262 Utils.setResultInTitle = function(title) {
263 // If document title is 'ENDED', then update it with new title to possibly
264 // mark a test as failure. Otherwise, keep the first title change in place.
265 if (!this.titleChanged || document.title.toUpperCase() == 'ENDED')
266 document.title = title.toUpperCase();
267 Utils.timeLog('Set document title to: ' + title + ', updated title: ' +
269 this.titleChanged = true;
272 Utils.timeLog = function(/**/) {
273 if (arguments.length == 0)
275 var time = Utils.getCurrentTimeString();
277 Utils.documentLog(arguments[0], time);
278 // Log to JS console.
279 var logString = time + ' - ';
280 for (var i = 0; i < arguments.length; i++) {
281 logString += ' ' + arguments[i];
283 console.log(logString);