forked from mirrors/gecko-dev
816 lines
25 KiB
JavaScript
816 lines
25 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* global Components */
|
|
|
|
var loop = loop || {};
|
|
loop.shared = loop.shared || {};
|
|
var inChrome = typeof Components != "undefined" && "utils" in Components;
|
|
|
|
(function() {
|
|
"use strict";
|
|
|
|
/**
|
|
* Root object, by default set to window if we're not in chrome code.
|
|
* @type {DOMWindow|Object}
|
|
*/
|
|
var rootObject = inChrome ? {} : window;
|
|
/**
|
|
* Root navigator, by default set to navigator if we're not in chrome code.
|
|
* @type {Navigator|Object}
|
|
*/
|
|
var rootNavigator = inChrome ? {} : navigator;
|
|
|
|
/**
|
|
* Sets new root objects. This is useful for testing native DOM events, and also
|
|
* so we can easily stub items like navigator.mediaDevices.
|
|
* can fake them. In beforeEach(), loop.shared.utils.setRootObjects is used to
|
|
* substitute fake objects, and in afterEach(), the real window object is
|
|
* replaced.
|
|
*
|
|
* @param {Object} windowObj The fake window object, undefined to use window.
|
|
* @param {Object} navigatorObj The fake navigator object, undefined to use navigator.
|
|
*/
|
|
function setRootObjects(windowObj, navigatorObj) {
|
|
rootObject = windowObj || window;
|
|
rootNavigator = navigatorObj || navigator;
|
|
}
|
|
|
|
var mozL10n;
|
|
if (inChrome) {
|
|
this.EXPORTED_SYMBOLS = ["utils"];
|
|
mozL10n = { get: function() {
|
|
throw new Error("mozL10n.get not availabled from chrome!");
|
|
}};
|
|
} else {
|
|
mozL10n = document.mozL10n || navigator.mozL10n;
|
|
}
|
|
|
|
/**
|
|
* Call types used for determining if a call is audio/video or audio-only.
|
|
*/
|
|
var CALL_TYPES = {
|
|
AUDIO_VIDEO: "audio-video",
|
|
AUDIO_ONLY: "audio"
|
|
};
|
|
|
|
var REST_ERRNOS = {
|
|
INVALID_TOKEN: 105,
|
|
EXPIRED: 111,
|
|
USER_UNAVAILABLE: 122,
|
|
ROOM_FULL: 202
|
|
};
|
|
|
|
var WEBSOCKET_REASONS = {
|
|
ANSWERED_ELSEWHERE: "answered-elsewhere",
|
|
BUSY: "busy",
|
|
CANCEL: "cancel",
|
|
CLOSED: "closed",
|
|
MEDIA_FAIL: "media-fail",
|
|
REJECT: "reject",
|
|
TIMEOUT: "timeout"
|
|
};
|
|
|
|
var FAILURE_DETAILS = {
|
|
MEDIA_DENIED: "reason-media-denied",
|
|
NO_MEDIA: "reason-no-media",
|
|
ROOM_ALREADY_OPEN: "reason-room-already-open",
|
|
UNABLE_TO_PUBLISH_MEDIA: "unable-to-publish-media",
|
|
USER_UNAVAILABLE: "reason-user-unavailable",
|
|
COULD_NOT_CONNECT: "reason-could-not-connect",
|
|
NETWORK_DISCONNECTED: "reason-network-disconnected",
|
|
EXPIRED_OR_INVALID: "reason-expired-or-invalid",
|
|
// TOS_FAILURE reflects the sdk error code 1026:
|
|
// https://tokbox.com/developer/sdks/js/reference/ExceptionEvent.html
|
|
TOS_FAILURE: "reason-tos-failure",
|
|
UNKNOWN: "reason-unknown",
|
|
ICE_FAILED: "reason-ice-failed"
|
|
};
|
|
|
|
var ROOM_INFO_FAILURES = {
|
|
// There's no data available from the server.
|
|
NO_DATA: "no_data",
|
|
// WebCrypto is unsupported in this browser.
|
|
WEB_CRYPTO_UNSUPPORTED: "web_crypto_unsupported",
|
|
// The room is missing the crypto key information.
|
|
NO_CRYPTO_KEY: "no_crypto_key",
|
|
// Decryption failed.
|
|
DECRYPT_FAILED: "decrypt_failed"
|
|
};
|
|
|
|
var STREAM_PROPERTIES = {
|
|
VIDEO_DIMENSIONS: "videoDimensions",
|
|
HAS_AUDIO: "hasAudio",
|
|
HAS_VIDEO: "hasVideo"
|
|
};
|
|
|
|
var SCREEN_SHARE_STATES = {
|
|
INACTIVE: "ss-inactive",
|
|
// Pending is when the user is being prompted, aka gUM in progress.
|
|
PENDING: "ss-pending",
|
|
ACTIVE: "ss-active"
|
|
};
|
|
|
|
var CHAT_CONTENT_TYPES = {
|
|
CONTEXT: "chat-context",
|
|
TEXT: "chat-text",
|
|
ROOM_NAME: "room-name"
|
|
};
|
|
|
|
/**
|
|
* Format a given date into an l10n-friendly string.
|
|
*
|
|
* @param {Integer} The timestamp in seconds to format.
|
|
* @return {String} The formatted string.
|
|
*/
|
|
function formatDate(timestamp) {
|
|
var date = (new Date(timestamp * 1000));
|
|
var options = {year: "numeric", month: "long", day: "numeric"};
|
|
return date.toLocaleDateString(navigator.language, options);
|
|
}
|
|
|
|
/**
|
|
* Used for getting a boolean preference. It will either use the browser preferences
|
|
* (if navigator.mozLoop is defined) or try to get them from localStorage.
|
|
*
|
|
* @param {String} prefName The name of the preference. Note that mozLoop adds
|
|
* 'loop.' to the start of the string.
|
|
*
|
|
* @return The value of the preference, or false if not available.
|
|
*/
|
|
function getBoolPreference(prefName) {
|
|
if (navigator.mozLoop) {
|
|
return !!navigator.mozLoop.getLoopPref(prefName);
|
|
}
|
|
|
|
return !!localStorage.getItem(prefName);
|
|
}
|
|
|
|
function isChrome(platform) {
|
|
return platform.toLowerCase().indexOf("chrome") > -1 ||
|
|
platform.toLowerCase().indexOf("chromium") > -1;
|
|
}
|
|
|
|
function isFirefox(platform) {
|
|
return platform.toLowerCase().indexOf("firefox") !== -1;
|
|
}
|
|
|
|
function isOpera(platform) {
|
|
return platform.toLowerCase().indexOf("opera") > -1 ||
|
|
platform.toLowerCase().indexOf("opr") > -1;
|
|
}
|
|
|
|
/**
|
|
* Helper to get the platform if it is unsupported.
|
|
*
|
|
* @param {String} platform The platform this is running on.
|
|
* @return null for supported platforms, a string for unsupported platforms.
|
|
*/
|
|
function getUnsupportedPlatform(platform) {
|
|
if (/^(iPad|iPhone|iPod)/.test(platform)) {
|
|
return "ios";
|
|
}
|
|
|
|
if (/Windows Phone/i.test(platform)) {
|
|
return "windows_phone";
|
|
}
|
|
|
|
if (/BlackBerry/i.test(platform)) {
|
|
return "blackberry";
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Helper to get the Operating System name.
|
|
*
|
|
* @param {String} [platform] The platform this is running on, will fall
|
|
* back to navigator.oscpu and navigator.userAgent
|
|
* respectively if not supplied.
|
|
* @param {Boolean} [withVersion] Optional flag to keep the version number
|
|
* included in the resulting string. Defaults to
|
|
* `false`.
|
|
* @return {String} The platform we're currently running on, in lower-case.
|
|
*/
|
|
var getOS = function(platform, withVersion) {
|
|
if (!platform) {
|
|
if ("oscpu" in window.navigator) {
|
|
// See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/oscpu
|
|
platform = window.navigator.oscpu.split(";")[0].trim();
|
|
} else {
|
|
// Fall back to navigator.userAgent as a last resort.
|
|
platform = window.navigator.userAgent;
|
|
}
|
|
}
|
|
|
|
if (!platform) {
|
|
return "unknown";
|
|
}
|
|
|
|
// Support passing in navigator.userAgent.
|
|
var platformPart = platform.match(/\((.*)\)/);
|
|
if (platformPart) {
|
|
platform = platformPart[1];
|
|
}
|
|
platform = platform.toLowerCase().split(";");
|
|
if (/macintosh/.test(platform[0]) || /x11/.test(platform[0])) {
|
|
platform = platform[1];
|
|
} else {
|
|
if (platform[0].indexOf("win") > -1 && platform.length > 4) {
|
|
// Skip the security notation.
|
|
platform = platform[2];
|
|
} else {
|
|
platform = platform[0];
|
|
}
|
|
}
|
|
|
|
if (!withVersion) {
|
|
platform = platform.replace(/\s[0-9.]+/g, "");
|
|
}
|
|
|
|
return platform.trim();
|
|
};
|
|
|
|
/**
|
|
* Helper to get the Operating System version.
|
|
* See http://en.wikipedia.org/wiki/Windows_NT for a table of Windows NT
|
|
* versions.
|
|
*
|
|
* @param {String} [platform] The platform this is running on, will fall back
|
|
* to navigator.oscpu and navigator.userAgent
|
|
* respectively if not supplied.
|
|
* @return {String} The current version of the platform we're currently running
|
|
* on.
|
|
*/
|
|
var getOSVersion = function(platform) {
|
|
var os = getOS(platform, true);
|
|
var digitsRE = /\s([0-9.]+)/;
|
|
|
|
var version = os.match(digitsRE);
|
|
if (!version) {
|
|
if (os.indexOf("win") > -1) {
|
|
if (os.indexOf("xp")) {
|
|
return { major: 5, minor: 2 };
|
|
} else if (os.indexOf("vista") > -1) {
|
|
return { major: 6, minor: 0 };
|
|
}
|
|
}
|
|
} else {
|
|
version = version[1];
|
|
// Windows versions have an interesting scheme.
|
|
if (os.indexOf("win") > -1) {
|
|
switch (parseFloat(version)) {
|
|
case 98:
|
|
return { major: 4, minor: 1 };
|
|
case 2000:
|
|
return { major: 5, minor: 0 };
|
|
case 2003:
|
|
return { major: 5, minor: 2 };
|
|
case 7:
|
|
case 2008:
|
|
case 2011:
|
|
return { major: 6, minor: 1 };
|
|
case 8:
|
|
return { major: 6, minor: 2 };
|
|
case 8.1:
|
|
case 2012:
|
|
return { major: 6, minor: 3 };
|
|
}
|
|
}
|
|
|
|
version = version.split(".");
|
|
return {
|
|
major: parseInt(version[0].trim(), 10),
|
|
minor: parseInt(version[1] ? version[1].trim() : 0, 10)
|
|
};
|
|
}
|
|
|
|
return { major: Infinity, minor: 0 };
|
|
};
|
|
|
|
/**
|
|
* Helper to get the current short platform string, based on the return value
|
|
* of `getOS`.
|
|
* Possible return values are 'mac', 'win' or 'other'.
|
|
*
|
|
* @param {String} [os] Optional string for the OS, used in tests only.
|
|
* @return {String} 'mac', 'win' or 'other'.
|
|
*/
|
|
var getPlatform = function(os) {
|
|
os = getOS(os);
|
|
var platform = "other";
|
|
if (os.indexOf("mac") > -1) {
|
|
platform = "mac";
|
|
} else if (os.indexOf("win") > -1) {
|
|
platform = "win";
|
|
}
|
|
return platform;
|
|
};
|
|
|
|
/**
|
|
* Determines if the user has any audio devices installed.
|
|
*
|
|
* @param {Function} callback Called with a boolean which is true if there
|
|
* are audio devices present.
|
|
*/
|
|
function hasAudioOrVideoDevices(callback) {
|
|
// mediaDevices is the official API for the spec.
|
|
// Older versions of FF had mediaDevices but not enumerateDevices.
|
|
if ("mediaDevices" in rootNavigator &&
|
|
"enumerateDevices" in rootNavigator.mediaDevices) {
|
|
rootNavigator.mediaDevices.enumerateDevices().then(function(result) {
|
|
function checkForInput(device) {
|
|
return device.kind === "audioinput" || device.kind === "videoinput";
|
|
}
|
|
|
|
callback(result.some(checkForInput));
|
|
}).catch(function() {
|
|
callback(false);
|
|
});
|
|
// MediaStreamTrack is the older version of the API, implemented originally
|
|
// by Google Chrome.
|
|
} else if ("MediaStreamTrack" in rootObject &&
|
|
"getSources" in rootObject.MediaStreamTrack) {
|
|
rootObject.MediaStreamTrack.getSources(function(result) {
|
|
function checkForInput(device) {
|
|
return device.kind === "audio" || device.kind === "video";
|
|
}
|
|
|
|
callback(result.some(checkForInput));
|
|
});
|
|
} else {
|
|
// We don't know, so assume true.
|
|
callback(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to allow getting some of the location data in a way that's compatible
|
|
* with stubbing for unit tests.
|
|
*/
|
|
function locationData() {
|
|
return {
|
|
hash: window.location.hash,
|
|
pathname: window.location.pathname
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Formats a url for display purposes. This includes converting the
|
|
* domain to punycode, and then decoding the url.
|
|
*
|
|
* @param {String} url The url to format.
|
|
* @return {Object} An object containing the hostname and full location.
|
|
*/
|
|
function formatURL(url) {
|
|
// We're using new URL to pass this through the browser's ACE/punycode
|
|
// processing system. If the browser considers a url to need to be
|
|
// punycode encoded for it to be displayed, then new URL will do that for
|
|
// us. This saves us needing our own punycode library.
|
|
var urlObject;
|
|
try {
|
|
urlObject = new URL(url);
|
|
} catch (ex) {
|
|
console.error("Error occurred whilst parsing URL:", ex);
|
|
return null;
|
|
}
|
|
|
|
// Finally, ensure we look good.
|
|
return {
|
|
hostname: urlObject.hostname,
|
|
location: decodeURI(urlObject.href)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generates and opens a mailto: url with call URL information prefilled.
|
|
* Note: This only works for Desktop.
|
|
*
|
|
* @param {String} callUrl The call URL.
|
|
* @param {String} [recipient] The recipient email address (optional).
|
|
* @param {String} [contextDescription] The context description (optional).
|
|
* @param {String} [from] The area from which this function is called.
|
|
*/
|
|
function composeCallUrlEmail(callUrl, recipient, contextDescription, from) {
|
|
var mozLoop = navigator.mozLoop;
|
|
if (typeof mozLoop === "undefined") {
|
|
console.warn("composeCallUrlEmail isn't available for Loop standalone.");
|
|
return;
|
|
}
|
|
|
|
var subject, body;
|
|
var footer = mozL10n.get("share_email_footer");
|
|
|
|
if (contextDescription) {
|
|
subject = mozL10n.get("share_email_subject6");
|
|
body = mozL10n.get("share_email_body_context2", {
|
|
callUrl: callUrl,
|
|
title: contextDescription
|
|
});
|
|
} else {
|
|
subject = mozL10n.get("share_email_subject6");
|
|
body = mozL10n.get("share_email_body6", {
|
|
callUrl: callUrl
|
|
});
|
|
}
|
|
var bodyFooter = body + footer;
|
|
bodyFooter = bodyFooter.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n");
|
|
mozLoop.composeEmail(
|
|
subject,
|
|
bodyFooter,
|
|
recipient
|
|
);
|
|
|
|
var bucket = mozLoop.SHARING_ROOM_URL["EMAIL_FROM_" + (from || "").toUpperCase()];
|
|
if (typeof bucket === "undefined") {
|
|
console.error("No URL sharing type bucket found for '" + from + "'");
|
|
return;
|
|
}
|
|
mozLoop.telemetryAddValue("LOOP_SHARING_ROOM_URL", bucket);
|
|
}
|
|
|
|
// We can alias `subarray` to `slice` when the latter is not available, because
|
|
// they're semantically identical.
|
|
if (!Uint8Array.prototype.slice) {
|
|
/* eslint-disable */
|
|
// Eslint disabled for no-extend-native; Specific override needed for Firefox 37
|
|
// and earlier, also for other browsers.
|
|
Uint8Array.prototype.slice = Uint8Array.prototype.subarray;
|
|
/* eslint-enable */
|
|
}
|
|
|
|
/**
|
|
* Binary-compatible Base64 decoding.
|
|
*
|
|
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
|
*
|
|
* @param {String} base64str The string to decode.
|
|
* @return {Uint8Array} The decoded result in array format.
|
|
*/
|
|
function atob(base64str) {
|
|
var strippedEncoding = base64str.replace(/[^A-Za-z0-9\+\/]/g, "");
|
|
var inLength = strippedEncoding.length;
|
|
var outLength = inLength * 3 + 1 >> 2;
|
|
var result = new Uint8Array(outLength);
|
|
|
|
var mod3;
|
|
var mod4;
|
|
var uint24 = 0;
|
|
var outIndex = 0;
|
|
|
|
for (var inIndex = 0; inIndex < inLength; inIndex++) {
|
|
mod4 = inIndex & 3;
|
|
uint24 |= _b64ToUint6(strippedEncoding.charCodeAt(inIndex)) << 6 * (3 - mod4);
|
|
|
|
if (mod4 === 3 || inLength - inIndex === 1) {
|
|
for (mod3 = 0; mod3 < 3 && outIndex < outLength; mod3++, outIndex++) {
|
|
result[outIndex] = uint24 >>> (16 >>> mod3 & 24) & 255;
|
|
}
|
|
uint24 = 0;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Binary-compatible Base64 encoding.
|
|
*
|
|
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
|
*
|
|
* @param {Uint8Array} bytes The data to encode.
|
|
* @return {String} The base64 encoded string.
|
|
*/
|
|
function btoa(bytes) {
|
|
var mod3 = 2;
|
|
var result = "";
|
|
var length = bytes.length;
|
|
var uint24 = 0;
|
|
|
|
for (var index = 0; index < length; index++) {
|
|
mod3 = index % 3;
|
|
if (index > 0 && (index * 4 / 3) % 76 === 0) {
|
|
result += "\r\n";
|
|
}
|
|
uint24 |= bytes[index] << (16 >>> mod3 & 24);
|
|
if (mod3 === 2 || length - index === 1) {
|
|
result += String.fromCharCode(_uint6ToB64(uint24 >>> 18 & 63),
|
|
_uint6ToB64(uint24 >>> 12 & 63),
|
|
_uint6ToB64(uint24 >>> 6 & 63),
|
|
_uint6ToB64(uint24 & 63));
|
|
uint24 = 0;
|
|
}
|
|
}
|
|
|
|
return result.substr(0, result.length - 2 + mod3) +
|
|
(mod3 === 2 ? "" : mod3 === 1 ? "=" : "==");
|
|
}
|
|
|
|
/**
|
|
* Utility function to decode a base64 character into an integer.
|
|
*
|
|
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
|
*
|
|
* @param {Number} chr The character code to decode.
|
|
* @return {Number} The decoded value.
|
|
*/
|
|
function _b64ToUint6 (chr) {
|
|
return chr > 64 && chr < 91 ? chr - 65 :
|
|
chr > 96 && chr < 123 ? chr - 71 :
|
|
chr > 47 && chr < 58 ? chr + 4 :
|
|
chr === 43 ? 62 :
|
|
chr === 47 ? 63 : 0;
|
|
}
|
|
|
|
/**
|
|
* Utility function to encode an integer into a base64 character code.
|
|
*
|
|
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
|
*
|
|
* @param {Number} uint6 The number to encode.
|
|
* @return {Number} The encoded value.
|
|
*/
|
|
function _uint6ToB64 (uint6) {
|
|
return uint6 < 26 ? uint6 + 65 :
|
|
uint6 < 52 ? uint6 + 71 :
|
|
uint6 < 62 ? uint6 - 4 :
|
|
uint6 === 62 ? 43 :
|
|
uint6 === 63 ? 47 : 65;
|
|
}
|
|
|
|
/**
|
|
* Utility function to convert a string into a uint8 array.
|
|
*
|
|
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
|
*
|
|
* @param {String} inString The string to convert.
|
|
* @return {Uint8Array} The converted string in array format.
|
|
*/
|
|
function strToUint8Array(inString) {
|
|
var inLength = inString.length;
|
|
var arrayLength = 0;
|
|
var chr;
|
|
|
|
// Mapping.
|
|
for (var mapIndex = 0; mapIndex < inLength; mapIndex++) {
|
|
chr = inString.charCodeAt(mapIndex);
|
|
arrayLength += chr < 0x80 ? 1 :
|
|
chr < 0x800 ? 2 :
|
|
chr < 0x10000 ? 3 :
|
|
chr < 0x200000 ? 4 :
|
|
chr < 0x4000000 ? 5 : 6;
|
|
}
|
|
|
|
var result = new Uint8Array(arrayLength);
|
|
var index = 0;
|
|
|
|
// Transcription.
|
|
for (var chrIndex = 0; index < arrayLength; chrIndex++) {
|
|
chr = inString.charCodeAt(chrIndex);
|
|
if (chr < 128) {
|
|
// One byte.
|
|
result[index++] = chr;
|
|
} else if (chr < 0x800) {
|
|
// Two bytes.
|
|
result[index++] = 192 + (chr >>> 6);
|
|
result[index++] = 128 + (chr & 63);
|
|
} else if (chr < 0x10000) {
|
|
// Three bytes.
|
|
result[index++] = 224 + (chr >>> 12);
|
|
result[index++] = 128 + (chr >>> 6 & 63);
|
|
result[index++] = 128 + (chr & 63);
|
|
} else if (chr < 0x200000) {
|
|
// Four bytes.
|
|
result[index++] = 240 + (chr >>> 18);
|
|
result[index++] = 128 + (chr >>> 12 & 63);
|
|
result[index++] = 128 + (chr >>> 6 & 63);
|
|
result[index++] = 128 + (chr & 63);
|
|
} else if (chr < 0x4000000) {
|
|
// Five bytes.
|
|
result[index++] = 248 + (chr >>> 24);
|
|
result[index++] = 128 + (chr >>> 18 & 63);
|
|
result[index++] = 128 + (chr >>> 12 & 63);
|
|
result[index++] = 128 + (chr >>> 6 & 63);
|
|
result[index++] = 128 + (chr & 63);
|
|
} else { // if (chr <= 0x7fffffff)
|
|
// Six bytes.
|
|
result[index++] = 252 + (chr >>> 30);
|
|
result[index++] = 128 + (chr >>> 24 & 63);
|
|
result[index++] = 128 + (chr >>> 18 & 63);
|
|
result[index++] = 128 + (chr >>> 12 & 63);
|
|
result[index++] = 128 + (chr >>> 6 & 63);
|
|
result[index++] = 128 + (chr & 63);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Utility function to change a uint8 based integer array to a string.
|
|
*
|
|
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
|
*
|
|
* @param {Uint8Array} arrayBytes Array to convert.
|
|
* @param {String} The array as a string.
|
|
*/
|
|
function Uint8ArrayToStr(arrayBytes) {
|
|
var result = "";
|
|
var length = arrayBytes.length;
|
|
var part;
|
|
|
|
for (var index = 0; index < length; index++) {
|
|
part = arrayBytes[index];
|
|
result += String.fromCharCode(
|
|
part > 251 && part < 254 && index + 5 < length ?
|
|
// Six bytes.
|
|
// (part - 252 << 30) may be not so safe in ECMAScript! So...:
|
|
(part - 252) * 1073741824 +
|
|
(arrayBytes[++index] - 128 << 24) +
|
|
(arrayBytes[++index] - 128 << 18) +
|
|
(arrayBytes[++index] - 128 << 12) +
|
|
(arrayBytes[++index] - 128 << 6) +
|
|
arrayBytes[++index] - 128 :
|
|
part > 247 && part < 252 && index + 4 < length ?
|
|
// Five bytes.
|
|
(part - 248 << 24) +
|
|
(arrayBytes[++index] - 128 << 18) +
|
|
(arrayBytes[++index] - 128 << 12) +
|
|
(arrayBytes[++index] - 128 << 6) +
|
|
arrayBytes[++index] - 128 :
|
|
part > 239 && part < 248 && index + 3 < length ?
|
|
// Four bytes.
|
|
(part - 240 << 18) +
|
|
(arrayBytes[++index] - 128 << 12) +
|
|
(arrayBytes[++index] - 128 << 6) +
|
|
arrayBytes[++index] - 128 :
|
|
part > 223 && part < 240 && index + 2 < length ?
|
|
// Three bytes.
|
|
(part - 224 << 12) +
|
|
(arrayBytes[++index] - 128 << 6) +
|
|
arrayBytes[++index] - 128 :
|
|
part > 191 && part < 224 && index + 1 < length ?
|
|
// Two bytes.
|
|
(part - 192 << 6) +
|
|
arrayBytes[++index] - 128 :
|
|
// One byte.
|
|
part
|
|
);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the difference after comparing two different objects. It compares property
|
|
* names and their respective values if necessary.
|
|
* This function does _not_ recurse into object values to keep this functions'
|
|
* complexity predictable to O(2).
|
|
*
|
|
* @param {Object} a Object number 1, the comparator.
|
|
* @param {Object} b Object number 2, the comparison.
|
|
* @return {Object} The diff output, which is itself an object structured as:
|
|
* {
|
|
* updated: [prop1, prop6],
|
|
* added: [prop2],
|
|
* removed: [prop3]
|
|
* }
|
|
*/
|
|
function objectDiff(a, b) {
|
|
var propsA = a ? Object.getOwnPropertyNames(a) : [];
|
|
var propsB = b ? Object.getOwnPropertyNames(b) : [];
|
|
var diff = {
|
|
updated: [],
|
|
added: [],
|
|
removed: []
|
|
};
|
|
|
|
var prop;
|
|
for (var i = 0, lA = propsA.length; i < lA; ++i) {
|
|
prop = propsA[i];
|
|
if (propsB.indexOf(prop) === -1) {
|
|
diff.removed.push(prop);
|
|
} else if (a[prop] !== b[prop]) {
|
|
diff.updated.push(prop);
|
|
}
|
|
}
|
|
|
|
for (var j = 0, lB = propsB.length; j < lB; ++j) {
|
|
prop = propsB[j];
|
|
if (propsA.indexOf(prop) === -1) {
|
|
diff.added.push(prop);
|
|
}
|
|
}
|
|
|
|
return diff;
|
|
}
|
|
|
|
/**
|
|
* When comparing two object, you sometimes want to ignore falsy values when
|
|
* they're not persisted on the server, for example.
|
|
* This function removes all the empty/ falsy properties from the target object.
|
|
*
|
|
* @param {Object} obj Target object to strip the falsy properties from
|
|
* @return {Object}
|
|
*/
|
|
function stripFalsyValues(obj) {
|
|
var props = Object.getOwnPropertyNames(obj);
|
|
var prop;
|
|
for (var i = props.length; i >= 0; --i) {
|
|
prop = props[i];
|
|
// If the value of the object property evaluates to |false|, delete it.
|
|
if (!obj[prop]) {
|
|
delete obj[prop];
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* Truncate a string if it exceeds the length as defined in `maxLen`, which
|
|
* is defined as '72' characters by default. If the string needs trimming,
|
|
* it'll be suffixed with the unicode ellipsis char, \u2026.
|
|
*
|
|
* @param {String} str The string to truncate, if needed.
|
|
* @param {Number} maxLen Maximum number of characters that the string is
|
|
* allowed to contain. Optional, defaults to 72.
|
|
* @return {String} Truncated version of `str`.
|
|
*/
|
|
function truncate(str, maxLen) {
|
|
maxLen = maxLen || 72;
|
|
|
|
if (str.length > maxLen) {
|
|
var substring = str.substr(0, maxLen);
|
|
// XXX Due to the fact that we have two different l10n libraries.
|
|
var direction = mozL10n.getDirection ? mozL10n.getDirection() :
|
|
mozL10n.language.direction;
|
|
if (direction === "rtl") {
|
|
return "…" + substring;
|
|
}
|
|
|
|
return substring + "…";
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Look up the DOM hierarchy for a node matching `selector`.
|
|
* If it is not found return the parent node, this is a sane default so
|
|
* that subsequent queries on the result do no fail.
|
|
* Better choice than the alternative `document.querySelector(selector)`
|
|
* because we ensure it works in the UI showcase as well.
|
|
*
|
|
* @param {HTMLElement} node Child element of the node we are looking for.
|
|
* @param {String} selector CSS class value of element we are looking for.
|
|
* @return {HTMLElement} Parent of node that matches selector query.
|
|
*/
|
|
function findParentNode(node, selector) {
|
|
var parentNode = node.parentNode;
|
|
|
|
while (parentNode) {
|
|
if (parentNode.classList.contains(selector)) {
|
|
return parentNode;
|
|
}
|
|
|
|
parentNode = parentNode.parentNode;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
this.utils = {
|
|
CALL_TYPES: CALL_TYPES,
|
|
CHAT_CONTENT_TYPES: CHAT_CONTENT_TYPES,
|
|
FAILURE_DETAILS: FAILURE_DETAILS,
|
|
REST_ERRNOS: REST_ERRNOS,
|
|
WEBSOCKET_REASONS: WEBSOCKET_REASONS,
|
|
STREAM_PROPERTIES: STREAM_PROPERTIES,
|
|
SCREEN_SHARE_STATES: SCREEN_SHARE_STATES,
|
|
ROOM_INFO_FAILURES: ROOM_INFO_FAILURES,
|
|
setRootObjects: setRootObjects,
|
|
composeCallUrlEmail: composeCallUrlEmail,
|
|
findParentNode: findParentNode,
|
|
formatDate: formatDate,
|
|
formatURL: formatURL,
|
|
getBoolPreference: getBoolPreference,
|
|
getOS: getOS,
|
|
getOSVersion: getOSVersion,
|
|
getPlatform: getPlatform,
|
|
isChrome: isChrome,
|
|
isFirefox: isFirefox,
|
|
isOpera: isOpera,
|
|
getUnsupportedPlatform: getUnsupportedPlatform,
|
|
hasAudioOrVideoDevices: hasAudioOrVideoDevices,
|
|
locationData: locationData,
|
|
atob: atob,
|
|
btoa: btoa,
|
|
strToUint8Array: strToUint8Array,
|
|
Uint8ArrayToStr: Uint8ArrayToStr,
|
|
objectDiff: objectDiff,
|
|
stripFalsyValues: stripFalsyValues,
|
|
truncate: truncate
|
|
};
|
|
}).call(inChrome ? this : loop.shared);
|