From a276f4e93eb431f242bdc1bc9b705651fe4ec271 Mon Sep 17 00:00:00 2001 From: "Jonathan Almeida (:jonalmeida)" Date: Tue, 10 Nov 2015 00:03:55 -0500 Subject: [PATCH 01/71] Bug 1209293 - Flip default options for click-to-view images r=mfinkle --- mobile/android/app/mobile.js | 6 +++--- mobile/android/components/ImageBlockingPolicy.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js index f012635e6d7..67f53df3f39 100644 --- a/mobile/android/app/mobile.js +++ b/mobile/android/app/mobile.js @@ -350,9 +350,9 @@ pref("browser.link.open_newwindow", 3); // 0=force all new windows to tabs, 1=don't force, 2=only force those with no features set pref("browser.link.open_newwindow.restriction", 0); -// image blocking policy -// 0=disabled, 1=enabled, 2=cellular-only -pref("browser.image_blocking", 0); +// show images option +// 0=never, 1=always, 2=cellular-only +pref("browser.image_blocking", 1); // controls which bits of private data to clear. by default we clear them all. pref("privacy.item.cache", true); diff --git a/mobile/android/components/ImageBlockingPolicy.js b/mobile/android/components/ImageBlockingPolicy.js index 22441fe84c4..e4156392066 100644 --- a/mobile/android/components/ImageBlockingPolicy.js +++ b/mobile/android/components/ImageBlockingPolicy.js @@ -45,7 +45,7 @@ ImageBlockingPolicy.prototype = { // nsIContentPolicy interface implementation shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra) { // When enabled or when on cellular, and option for cellular-only is selected - if (this._enabled() == OPTION_ALWAYS || (this._enabled() == OPTION_WIFI_ONLY && this._usingCellular())) { + if (this._enabled() == OPTION_NEVER || (this._enabled() == OPTION_WIFI_ONLY && this._usingCellular())) { if (contentType === Ci.nsIContentPolicy.TYPE_IMAGE || contentType === Ci.nsIContentPolicy.TYPE_IMAGESET) { // Accept any non-http(s) image URLs if (!contentLocation.schemeIs("http") && !contentLocation.schemeIs("https")) { From 4e72fb07acfe7ffe7b656ba10eab5ee34e80f92b Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Tue, 10 Nov 2015 08:58:10 +0000 Subject: [PATCH 02/71] Bug 1210865 - Update OpenTok library to version 2.6.8. r=dmose --- .../loop/content/shared/libs/sdk.js | 16126 ++++++++-------- 1 file changed, 8537 insertions(+), 7589 deletions(-) diff --git a/browser/components/loop/content/shared/libs/sdk.js b/browser/components/loop/content/shared/libs/sdk.js index 5cc2c378639..d55d50882f4 100755 --- a/browser/components/loop/content/shared/libs/sdk.js +++ b/browser/components/loop/content/shared/libs/sdk.js @@ -1,26 +1,23 @@ /** - * @license OpenTok JavaScript Library v2.5.2 f4508e1 2015Q1.patch.1 - * http://www.tokbox.com/ + * @license OpenTok.js v2.6.8 fae7901 HEAD * - * Copyright (c) 2014 TokBox, Inc. - * Released under the MIT license - * http://opensource.org/licenses/MIT + * Copyright (c) 2010-2015 TokBox, Inc. + * Subject to the applicable Software Development Kit (SDK) License Agreement: + * https://tokbox.com/support/sdk_license * - * Date: July 13 05:38:08 2015 + * Date: October 28 03:45:23 2015 */ - - !(function(window) { !(function(window, OTHelpers, undefined) { /** - * @license Common JS Helpers on OpenTok 0.3.0 f151b47 HEAD + * @license Common JS Helpers on OpenTok 0.4.1 259ca46 v0.4.1-branch * http://www.tokbox.com/ * * Copyright (c) 2015 TokBox, Inc. * - * Date: July 13 05:37:51 2015 + * Date: October 28 03:45:12 2015 * */ @@ -556,6 +553,22 @@ OTHelpers.invert = function(obj) { return result; }; + +// A helper for the common case of making a simple promise that is either +// resolved or rejected straight away. +// +// If the +err+ param is provide then the promise will be rejected, otherwise +// it will resolve. +// +OTHelpers.makeSimplePromise = function(err) { + return new OTHelpers.RSVP.Promise(function(resolve, reject) { + if (err === void 0) { + resolve(); + } else { + reject(err); + } + }); +}; // tb_require('../../../helpers.js') /* exported EventableEvent */ @@ -634,6 +647,1919 @@ OTHelpers.statable = function(self, possibleStates, initialState, stateChanged, return setState; }; +/*jshint browser:true, smarttabs:true */ + +// tb_require('../helpers.js') + + +var getErrorLocation; + +// Properties that we'll acknowledge from the JS Error object +var safeErrorProps = [ + 'description', + 'fileName', + 'lineNumber', + 'message', + 'name', + 'number', + 'stack' +]; + + +// OTHelpers.Error +// +// A construct to contain error information that also helps with extracting error +// context, such as stack trace. +// +// @constructor +// @memberof OTHelpers +// @method Error +// +// @param {String} message +// Optional. The error message +// +// @param {Object} props +// Optional. A dictionary of properties containing extra Error info. +// +// +// @example Create a simple error with juts a custom message +// var error = new OTHelpers.Error('Something Broke!'); +// error.message === 'Something Broke!'; +// +// @example Create an Error with a message and a name +// var error = new OTHelpers.Error('Something Broke!', 'FooError'); +// error.message === 'Something Broke!'; +// error.name === 'FooError'; +// +// @example Create an Error with a message, name, and custom properties +// var error = new OTHelpers.Error('Something Broke!', 'FooError', { +// foo: 'bar', +// listOfImportantThings: [1,2,3,4] +// }); +// error.message === 'Something Broke!'; +// error.name === 'FooError'; +// error.foo === 'bar'; +// error.listOfImportantThings == [1,2,3,4]; +// +// @example Create an Error from a Javascript Error +// var error = new OTHelpers.Error(domSyntaxError); +// error.message === domSyntaxError.message; +// error.name === domSyntaxError.name === 'SyntaxError'; +// // ...continues for each properties of domSyntaxError +// +// @references +// * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi +// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack +// * http://www.w3.org/TR/dom/#interface-domerror +// +// +// @todo +// * update usage in OTMedia +// * replace error handling in OT.js +// * normalise stack behaviour under Chrome/Node/Safari with other browsers +// * unit test for stack parsing +// +// +OTHelpers.Error = function (message, name, props) { + switch (arguments.length) { + case 1: + if ($.isObject(message)) { + props = message; + name = void 0; + message = void 0; + } + // Otherwise it's the message + break; + + case 2: + if ($.isObject(name)) { + props = name; + name = void 0; + } + // Otherwise name is actually the name + + break; + } + + if ( props instanceof Error) { + // Special handling of this due to Chrome weirdness. It seems that + // properties of the Error object, and it's children, are not + // enumerable in Chrome? + for (var i = 0, num = safeErrorProps.length; i < num; ++i) { + this[safeErrorProps[i]] = props[safeErrorProps[i]]; + } + } + else if ( $.isObject(props)) { + // Use an custom properties that are provided + for (var key in props) { + if (props.hasOwnProperty(key)) { + this[key] = props[key]; + } + } + } + + // If any of the fundamental properties are missing then try and + // extract them. + if ( !(this.fileName && this.lineNumber && this.columnNumber && this.stack) ) { + var err = getErrorLocation(); + + if (!this.fileName && err.fileName) { + this.fileName = err.fileName; + } + + if (!this.lineNumber && err.lineNumber) { + this.lineNumber = err.lineNumber; + } + + if (!this.columnNumber && err.columnNumber) { + this.columnNumber = err.columnNumber; + } + + if (!this.stack && err.stack) { + this.stack = err.stack; + } + } + + if (!this.message && message) this.message = message; + if (!this.name && name) this.name = name; +}; + +OTHelpers.Error.prototype.toString = +OTHelpers.Error.prototype.valueOf = function() { + var locationDetails = ''; + if (this.fileName) locationDetails += ' ' + this.fileName; + if (this.lineNumber) { + locationDetails += ' ' + this.lineNumber; + if (this.columnNumber) locationDetails += ':' + this.columnNumber; + } + + return '<' + (this.name ? this.name + ' ' : '') + this.message + locationDetails + '>'; +}; + + +// Normalise err.stack so that it is the same format as the other browsers +// We skip the first two frames so that we don't capture getErrorLocation() and +// the callee. +// +// Used by Environments that support the StackTrace API. (Chrome, Node, Opera) +// +var prepareStackTrace = function prepareStackTrace (_, stack){ + return $.map(stack.slice(2), function(frame) { + var _f = { + fileName: frame.getFileName(), + linenumber: frame.getLineNumber(), + columnNumber: frame.getColumnNumber() + }; + + if (frame.getFunctionName()) _f.functionName = frame.getFunctionName(); + if (frame.getMethodName()) _f.methodName = frame.getMethodName(); + if (frame.getThis()) _f.self = frame.getThis(); + + return _f; + }); +}; + + +// Black magic to retrieve error location info for various environments +getErrorLocation = function getErrorLocation () { + var info = {}, + callstack, + errLocation, + err; + + switch ($.env.name) { + case 'Firefox': + case 'Safari': + case 'IE': + + if ($.env.name !== 'IE') { + err = new Error(); + } + else { + try { + window.call.js.is.explody; + } + catch(e) { err = e; } + } + + callstack = (err.stack || '').split('\n'); + + //Remove call to getErrorLocation() and the callee + callstack.shift(); + callstack.shift(); + + info.stack = callstack; + + if ($.env.name === 'IE') { + // IE also includes the error message in it's stack trace + info.stack.shift(); + + // each line begins with some amounts of spaces and 'at', we remove + // these to normalise with the other browsers. + info.stack = $.map(callstack, function(call) { + return call.replace(/^\s+at\s+/g, ''); + }); + } + + errLocation = /@(.+?):([0-9]+)(:([0-9]+))?$/.exec(callstack[0]); + if (errLocation) { + info.fileName = errLocation[1]; + info.lineNumber = parseInt(errLocation[2], 10); + if (errLocation.length > 3) info.columnNumber = parseInt(errLocation[4], 10); + } + break; + + case 'Chrome': + case 'Node': + case 'Opera': + var currentPST = Error.prepareStackTrace; + Error.prepareStackTrace = prepareStackTrace; + err = new Error(); + info.stack = err.stack; + Error.prepareStackTrace = currentPST; + + var topFrame = info.stack[0]; + info.lineNumber = topFrame.lineNumber; + info.columnNumber = topFrame.columnNumber; + info.fileName = topFrame.fileName; + if (topFrame.functionName) info.functionName = topFrame.functionName; + if (topFrame.methodName) info.methodName = topFrame.methodName; + if (topFrame.self) info.self = topFrame.self; + break; + + default: + err = new Error(); + if (err.stack) info.stack = err.stack.split('\n'); + break; + } + + if (err.message) info.message = err.message; + return info; +}; + + +/*jshint browser:true, smarttabs:true*/ +/* global process */ + +// tb_require('../helpers.js') + + +// OTHelpers.env +// +// Contains information about the current environment. +// * **OTHelpers.env.name** The name of the Environment (Chrome, FF, Node, etc) +// * **OTHelpers.env.version** Usually a Float, except in Node which uses a String +// * **OTHelpers.env.userAgent** The raw user agent +// * **OTHelpers.env.versionGreaterThan** A helper method that returns true if the +// current version is greater than the argument +// +// Example +// if (OTHelpers.env.versionGreaterThan('0.10.30')) { +// // do something +// } +// +(function() { + // @todo make exposing userAgent unnecessary + var version = -1; + + // Returns true if otherVersion is greater than the current environment + // version. + var versionGEThan = function versionGEThan (otherVersion) { + if (otherVersion === version) return true; + + if (typeof(otherVersion) === 'number' && typeof(version) === 'number') { + return otherVersion > version; + } + + // The versions have multiple components (i.e. 0.10.30) and + // must be compared piecewise. + // Note: I'm ignoring the case where one version has multiple + // components and the other doesn't. + var v1 = otherVersion.split('.'), + v2 = version.split('.'), + versionLength = (v1.length > v2.length ? v2 : v1).length; + + for (var i = 0; i < versionLength; ++i) { + if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) { + return true; + } + } + + // Special case, v1 has extra components but the initial components + // were identical, we assume this means newer but it might also mean + // that someone changed versioning systems. + if (i < v1.length) { + return true; + } + + return false; + }; + + var env = function() { + if (typeof(process) !== 'undefined' && + typeof(process.versions) !== 'undefined' && + typeof(process.versions.node) === 'string') { + + version = process.versions.node; + if (version.substr(1) === 'v') version = version.substr(1); + + // Special casing node to avoid gating window.navigator. + // Version will be a string rather than a float. + return { + name: 'Node', + version: version, + userAgent: 'Node ' + version, + iframeNeedsLoad: false, + versionGreaterThan: versionGEThan + }; + } + + var userAgent = window.navigator.userAgent.toLowerCase(), + appName = window.navigator.appName, + navigatorVendor, + name = 'unknown'; + + if (userAgent.indexOf('opera') > -1 || userAgent.indexOf('opr') > -1) { + name = 'Opera'; + + if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (userAgent.indexOf('firefox') > -1) { + name = 'Firefox'; + + if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (appName === 'Microsoft Internet Explorer') { + // IE 10 and below + name = 'IE'; + + if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) { + // IE 11+ + + name = 'IE'; + + if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (userAgent.indexOf('chrome') > -1) { + name = 'Chrome'; + + if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if ((navigatorVendor = window.navigator.vendor) && + navigatorVendor.toLowerCase().indexOf('apple') > -1) { + name = 'Safari'; + + if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + } + + return { + name: name, + version: version, + userAgent: window.navigator.userAgent, + iframeNeedsLoad: userAgent.indexOf('webkit') < 0, + versionGreaterThan: versionGEThan + }; + }(); + + + OTHelpers.env = env; + + OTHelpers.browser = function() { + return OTHelpers.env.name; + }; + + OTHelpers.browserVersion = function() { + return OTHelpers.env; + }; + +})(); +// tb_require('../../environment.js') +// tb_require('./event.js') + +var nodeEventing; + +if($.env.name === 'Node') { + (function() { + var EventEmitter = require('events').EventEmitter, + util = require('util'); + + // container for the EventEmitter behaviour. This prevents tight coupling + // caused by accidentally bleeding implementation details and API into whatever + // objects nodeEventing is applied to. + var NodeEventable = function NodeEventable () { + EventEmitter.call(this); + + this.events = {}; + }; + util.inherits(NodeEventable, EventEmitter); + + + nodeEventing = function nodeEventing (/* self */) { + var api = new NodeEventable(), + _on = api.on, + _off = api.removeListener; + + + api.addListeners = function (eventNames, handler, context, closure) { + var listener = {handler: handler}; + if (context) listener.context = context; + if (closure) listener.closure = closure; + + $.forEach(eventNames, function(name) { + if (!api.events[name]) api.events[name] = []; + api.events[name].push(listener); + + _on(name, handler); + + var addedListener = name + ':added'; + if (api.events[addedListener]) { + api.emit(addedListener, api.events[name].length); + } + }); + }; + + api.removeAllListenersNamed = function (eventNames) { + var _eventNames = eventNames.split(' '); + api.removeAllListeners(_eventNames); + + $.forEach(_eventNames, function(name) { + if (api.events[name]) delete api.events[name]; + }); + }; + + api.removeListeners = function (eventNames, handler, closure) { + function filterHandlers(listener) { + return !(listener.handler === handler && listener.closure === closure); + } + + $.forEach(eventNames.split(' '), function(name) { + if (api.events[name]) { + _off(name, handler); + api.events[name] = $.filter(api.events[name], filterHandlers); + if (api.events[name].length === 0) delete api.events[name]; + + var removedListener = name + ':removed'; + if (api.events[removedListener]) { + api.emit(removedListener, api.events[name] ? api.events[name].length : 0); + } + } + }); + }; + + api.removeAllListeners = function () { + api.events = {}; + api.removeAllListeners(); + }; + + api.dispatchEvent = function(event, defaultAction) { + this.emit(event.type, event); + + if (defaultAction) { + defaultAction.call(null, event); + } + }; + + api.trigger = $.bind(api.emit, api); + + + return api; + }; + })(); +} + +// tb_require('../../environment.js') +// tb_require('./event.js') + +var browserEventing; + +if($.env.name !== 'Node') { + + browserEventing = function browserEventing (self, syncronous) { + var api = { + events: {} + }; + + + // Call the defaultAction, passing args + function executeDefaultAction(defaultAction, args) { + if (!defaultAction) return; + + defaultAction.apply(null, args.slice()); + } + + // Execute each handler in +listeners+ with +args+. + // + // Each handler will be executed async. On completion the defaultAction + // handler will be executed with the args. + // + // @param [Array] listeners + // An array of functions to execute. Each will be passed args. + // + // @param [Array] args + // An array of arguments to execute each function in +listeners+ with. + // + // @param [String] name + // The name of this event. + // + // @param [Function, Null, Undefined] defaultAction + // An optional function to execute after every other handler. This will execute even + // if +listeners+ is empty. +defaultAction+ will be passed args as a normal + // handler would. + // + // @return Undefined + // + function executeListenersAsyncronously(name, args, defaultAction) { + var listeners = api.events[name]; + if (!listeners || listeners.length === 0) return; + + var listenerAcks = listeners.length; + + $.forEach(listeners, function(listener) { // , index + function filterHandlers(_listener) { + return _listener.handler === listener.handler; + } + + // We run this asynchronously so that it doesn't interfere with execution if an + // error happens + $.callAsync(function() { + try { + // have to check if the listener has not been removed + if (api.events[name] && $.some(api.events[name], filterHandlers)) { + (listener.closure || listener.handler).apply(listener.context || null, args); + } + } + finally { + listenerAcks--; + + if (listenerAcks === 0) { + executeDefaultAction(defaultAction, args); + } + } + }); + }); + } + + + // This is identical to executeListenersAsyncronously except that handlers will + // be executed syncronously. + // + // On completion the defaultAction handler will be executed with the args. + // + // @param [Array] listeners + // An array of functions to execute. Each will be passed args. + // + // @param [Array] args + // An array of arguments to execute each function in +listeners+ with. + // + // @param [String] name + // The name of this event. + // + // @param [Function, Null, Undefined] defaultAction + // An optional function to execute after every other handler. This will execute even + // if +listeners+ is empty. +defaultAction+ will be passed args as a normal + // handler would. + // + // @return Undefined + // + function executeListenersSyncronously(name, args) { // defaultAction is not used + var listeners = api.events[name]; + if (!listeners || listeners.length === 0) return; + + $.forEach(listeners, function(listener) { // index + (listener.closure || listener.handler).apply(listener.context || null, args); + }); + } + + var executeListeners = syncronous === true ? + executeListenersSyncronously : executeListenersAsyncronously; + + + api.addListeners = function (eventNames, handler, context, closure) { + var listener = {handler: handler}; + if (context) listener.context = context; + if (closure) listener.closure = closure; + + $.forEach(eventNames, function(name) { + if (!api.events[name]) api.events[name] = []; + api.events[name].push(listener); + + var addedListener = name + ':added'; + if (api.events[addedListener]) { + executeListeners(addedListener, [api.events[name].length]); + } + }); + }; + + api.removeListeners = function(eventNames, handler, context) { + function filterListeners(listener) { + var isCorrectHandler = ( + listener.handler.originalHandler === handler || + listener.handler === handler + ); + + return !(isCorrectHandler && listener.context === context); + } + + $.forEach(eventNames, function(name) { + if (api.events[name]) { + api.events[name] = $.filter(api.events[name], filterListeners); + if (api.events[name].length === 0) delete api.events[name]; + + var removedListener = name + ':removed'; + if (api.events[ removedListener]) { + executeListeners(removedListener, [api.events[name] ? api.events[name].length : 0]); + } + } + }); + }; + + api.removeAllListenersNamed = function (eventNames) { + $.forEach(eventNames, function(name) { + if (api.events[name]) { + delete api.events[name]; + } + }); + }; + + api.removeAllListeners = function () { + api.events = {}; + }; + + api.dispatchEvent = function(event, defaultAction) { + if (!api.events[event.type] || api.events[event.type].length === 0) { + executeDefaultAction(defaultAction, [event]); + return; + } + + executeListeners(event.type, [event], defaultAction); + }; + + api.trigger = function(eventName, args) { + if (!api.events[eventName] || api.events[eventName].length === 0) { + return; + } + + executeListeners(eventName, args); + }; + + + return api; + }; +} + +/*jshint browser:false, smarttabs:true*/ +/* global window, require */ + +// tb_require('../../helpers.js') +// tb_require('../environment.js') + +if (window.OTHelpers.env.name === 'Node') { + var request = require('request'); + + OTHelpers.request = function(url, options, callback) { + var completion = function(error, response, body) { + var event = {response: response, body: body}; + + // We need to detect things that Request considers a success, + // but we consider to be failures. + if (!error && response.statusCode >= 200 && + (response.statusCode < 300 || response.statusCode === 304) ) { + callback(null, event); + } else { + callback(error, event); + } + }; + + if (options.method.toLowerCase() === 'get') { + request.get(url, completion); + } + else { + request.post(url, options.body, completion); + } + }; + + OTHelpers.getJSON = function(url, options, callback) { + var extendedHeaders = require('underscore').extend( + { + 'Accept': 'application/json' + }, + options.headers || {} + ); + + request.get({ + url: url, + headers: extendedHeaders, + json: true + }, function(err, response) { + callback(err, response && response.body); + }); + }; +} +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../../helpers.js') +// tb_require('../environment.js') + +function formatPostData(data) { //, contentType + // If it's a string, we assume it's properly encoded + if (typeof(data) === 'string') return data; + + var queryString = []; + + for (var key in data) { + queryString.push( + encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + ); + } + + return queryString.join('&').replace(/\+/g, '%20'); +} + +if (window.OTHelpers.env.name !== 'Node') { + + OTHelpers.xdomainRequest = function(url, options, callback) { + /*global XDomainRequest*/ + var xdr = new XDomainRequest(), + _options = options || {}, + _method = _options.method.toLowerCase(); + + if(!_method) { + callback(new Error('No HTTP method specified in options')); + return; + } + + _method = _method.toUpperCase(); + + if(!(_method === 'GET' || _method === 'POST')) { + callback(new Error('HTTP method can only be ')); + return; + } + + function done(err, event) { + xdr.onload = xdr.onerror = xdr.ontimeout = function() {}; + xdr = void 0; + callback(err, event); + } + + + xdr.onload = function() { + done(null, { + target: { + responseText: xdr.responseText, + headers: { + 'content-type': xdr.contentType + } + } + }); + }; + + xdr.onerror = function() { + done(new Error('XDomainRequest of ' + url + ' failed')); + }; + + xdr.ontimeout = function() { + done(new Error('XDomainRequest of ' + url + ' timed out')); + }; + + xdr.open(_method, url); + xdr.send(options.body && formatPostData(options.body)); + + }; + + OTHelpers.request = function(url, options, callback) { + var request = new XMLHttpRequest(), + _options = options || {}, + _method = _options.method; + + if(!_method) { + callback(new Error('No HTTP method specified in options')); + return; + } + + if (options.overrideMimeType) { + if (request.overrideMimeType) { + request.overrideMimeType(options.overrideMimeType); + } + delete options.overrideMimeType; + } + + // Setup callbacks to correctly respond to success and error callbacks. This includes + // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore + // by default. + if (callback) { + OTHelpers.on(request, 'load', function(event) { + var status = event.target.status; + + // We need to detect things that XMLHttpRequest considers a success, + // but we consider to be failures. + if ( status >= 200 && (status < 300 || status === 304) ) { + callback(null, event); + } else { + callback(event); + } + }); + + OTHelpers.on(request, 'error', callback); + } + + request.open(options.method, url, true); + + if (!_options.headers) _options.headers = {}; + + for (var name in _options.headers) { + if (!Object.prototype.hasOwnProperty.call(_options.headers, name)) { + continue; + } + request.setRequestHeader(name, _options.headers[name]); + } + + request.send(options.body && formatPostData(options.body)); + }; + + + OTHelpers.getJSON = function(url, options, callback) { + options = options || {}; + + var done = function(error, event) { + if(error) { + callback(error, event && event.target && event.target.responseText); + } else { + var response; + + try { + response = JSON.parse(event.target.responseText); + } catch(e) { + // Badly formed JSON + callback(e, event && event.target && event.target.responseText); + return; + } + + callback(null, response, event); + } + }; + + if(options.xdomainrequest) { + OTHelpers.xdomainRequest(url, { method: 'GET' }, done); + } else { + var extendedHeaders = OTHelpers.extend({ + 'Accept': 'application/json' + }, options.headers || {}); + + OTHelpers.get(url, OTHelpers.extend(options || {}, { + headers: extendedHeaders + }), done); + } + + }; + +} +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../helpers.js') +// tb_require('./environment.js') + + +// Log levels for OTLog.setLogLevel +var LOG_LEVEL_DEBUG = 5, + LOG_LEVEL_LOG = 4, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_WARN = 2, + LOG_LEVEL_ERROR = 1, + LOG_LEVEL_NONE = 0; + + +// There is a single global log level for every component that uses +// the logs. +var _logLevel = LOG_LEVEL_NONE; + +var setLogLevel = function setLogLevel (level) { + _logLevel = typeof(level) === 'number' ? level : 0; + return _logLevel; +}; + + +OTHelpers.useLogHelpers = function(on){ + + // Log levels for OTLog.setLogLevel + on.DEBUG = LOG_LEVEL_DEBUG; + on.LOG = LOG_LEVEL_LOG; + on.INFO = LOG_LEVEL_INFO; + on.WARN = LOG_LEVEL_WARN; + on.ERROR = LOG_LEVEL_ERROR; + on.NONE = LOG_LEVEL_NONE; + + var _logs = [], + _canApplyConsole = true; + + try { + Function.prototype.bind.call(window.console.log, window.console); + } catch (err) { + _canApplyConsole = false; + } + + // Some objects can't be logged in the console, mostly these are certain + // types of native objects that are exposed to JS. This is only really a + // problem with IE, hence only the IE version does anything. + var makeLogArgumentsSafe = function(args) { return args; }; + + if (OTHelpers.env.name === 'IE') { + makeLogArgumentsSafe = function(args) { + return [toDebugString(prototypeSlice.apply(args))]; + }; + } + + // Generates a logging method for a particular method and log level. + // + // Attempts to handle the following cases: + // * the desired log method doesn't exist, call fallback (if available) instead + // * the console functionality isn't available because the developer tools (in IE) + // aren't open, call fallback (if available) + // * attempt to deal with weird IE hosted logging methods as best we can. + // + function generateLoggingMethod(method, level, fallback) { + return function() { + if (on.shouldLog(level)) { + var cons = window.console, + args = makeLogArgumentsSafe(arguments); + + // In IE, window.console may not exist if the developer tools aren't open + // This also means that cons and cons[method] can appear at any moment + // hence why we retest this every time. + if (cons && cons[method]) { + // the desired console method isn't a real object, which means + // that we can't use apply on it. We force it to be a real object + // using Function.bind, assuming that's available. + if (cons[method].apply || _canApplyConsole) { + if (!cons[method].apply) { + cons[method] = Function.prototype.bind.call(cons[method], cons); + } + + cons[method].apply(cons, args); + } + else { + // This isn't the same result as the above, but it's better + // than nothing. + cons[method](args); + } + } + else if (fallback) { + fallback.apply(on, args); + + // Skip appendToLogs, we delegate entirely to the fallback + return; + } + + appendToLogs(method, makeLogArgumentsSafe(arguments)); + } + }; + } + + on.log = generateLoggingMethod('log', on.LOG); + + // Generate debug, info, warn, and error logging methods, these all fallback to on.log + on.debug = generateLoggingMethod('debug', on.DEBUG, on.log); + on.info = generateLoggingMethod('info', on.INFO, on.log); + on.warn = generateLoggingMethod('warn', on.WARN, on.log); + on.error = generateLoggingMethod('error', on.ERROR, on.log); + + + on.setLogLevel = function(level) { + on.debug('TB.setLogLevel(' + _logLevel + ')'); + return setLogLevel(level); + }; + + on.getLogs = function() { + return _logs; + }; + + // Determine if the level is visible given the current logLevel. + on.shouldLog = function(level) { + return _logLevel >= level; + }; + + // Format the current time nicely for logging. Returns the current + // local time. + function formatDateStamp() { + var now = new Date(); + return now.toLocaleTimeString() + now.getMilliseconds(); + } + + function toJson(object) { + try { + return JSON.stringify(object); + } catch(e) { + return object.toString(); + } + } + + function toDebugString(object) { + var components = []; + + if (typeof(object) === 'undefined') { + // noop + } + else if (object === null) { + components.push('NULL'); + } + else if (OTHelpers.isArray(object)) { + for (var i=0; i 0) { + queueRunning = true; + var curr = logQueue[0]; + + // Remove the current item and send the next log + var processNextItem = function() { + logQueue.shift(); + queueRunning = false; + throttledPost(); + }; + + if (curr) { + send(curr.data, curr.isQos, function(err) { + if (err) { + var debugMsg = 'Failed to send ClientEvent, moving on to the next item.'; + if (debugFn) { + debugFn(debugMsg); + } else { + console.log(debugMsg); + } + if (curr.onComplete) { + curr.onComplete(err); + } + // There was an error, move onto the next item + } + if (curr.onComplete) { + curr.onComplete(err); + } + setTimeout(processNextItem, 50); + }); + } + } + }, + + post = function(data, onComplete, isQos) { + logQueue.push({ + data: data, + onComplete: onComplete, + isQos: isQos + }); + + throttledPost(); + }, + + shouldThrottleError = function(code, type, partnerId) { + if (!partnerId) return false; + + var errKey = [partnerId, type, code].join('_'), + //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId); + msgLimit = 100; + if (msgLimit === null || msgLimit === undefined) return false; + return (reportedErrors[errKey] || 0) <= msgLimit; + }; + + // Log an error via ClientEvents. + // + // @param [String] code + // @param [String] type + // @param [String] message + // @param [Hash] details additional error details + // + // @param [Hash] options the options to log the client event with. + // @option options [String] action The name of the Event that we are logging. E.g. + // 'TokShowLoaded'. Required. + // @option options [String] variation Usually used for Split A/B testing, when you + // have multiple variations of the +_action+. + // @option options [String] payload The payload. Required. + // @option options [String] sessionId The active OpenTok session, if there is one + // @option options [String] connectionId The active OpenTok connectionId, if there is one + // @option options [String] partnerId + // @option options [String] guid ... + // @option options [String] streamId ... + // @option options [String] section ... + // @option options [String] clientVersion ... + // + // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner + // from the dynamic config for X) of each error type for each partner. Reports can be + // disabled/enabled globally or on a per partner basis (per partner settings + // take precedence) using exceptionLogging.enabled. + // + this.logError = function(code, type, message, details, options) { + if (!options) options = {}; + var partnerId = options.partnerId; + + if (shouldThrottleError(code, type, partnerId)) { + //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' + + // code + ' for partner ' + (partnerId || 'No Partner Id')); + return; + } + + var errKey = [partnerId, type, code].join('_'), + payload = details ? details : null; + + reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ? + reportedErrors[errKey] + 1 : 1; + this.logEvent(OTHelpers.extend(options, { + action: type + '.' + code, + payload: payload + }), false); + }; + + // Log a client event to the analytics backend. + // + // @example Logs a client event called 'foo' + // this.logEvent({ + // action: 'foo', + // payload: 'bar', + // sessionId: sessionId, + // connectionId: connectionId + // }, false) + // + // @param [Hash] data the data to log the client event with. + // @param [Boolean] qos Whether this is a QoS event. + // @param [Boolean] throttle A number specifying the ratio of events to be sent + // out of the total number of events (other events are not ignored). If not + // set to a number, all events are sent. + // @param [Number] completionHandler A completion handler function to call when the + // client event POST request succeeds or fails. If it fails, an error + // object is passed into the function. (See throttledPost().) + // + this.logEvent = function(data, qos, throttle, completionHandler) { + if (!qos) qos = false; + + if (throttle && !isNaN(throttle)) { + if (Math.random() > throttle) { + return; + } + } + + // remove properties that have null values: + for (var key in data) { + if (data.hasOwnProperty(key) && data[key] === null) { + delete data[key]; + } + } + + // TODO: catch error when stringifying an object that has a circular reference + data = JSON.stringify(data); + + post(data, completionHandler, qos); + }; + + // Log a client QOS to the analytics backend. + // Log a client QOS to the analytics backend. + // @option options [String] action The name of the Event that we are logging. + // E.g. 'TokShowLoaded'. Required. + // @option options [String] variation Usually used for Split A/B testing, when + // you have multiple variations of the +_action+. + // @option options [String] payload The payload. Required. + // @option options [String] sessionId The active OpenTok session, if there is one + // @option options [String] connectionId The active OpenTok connectionId, if there is one + // @option options [String] partnerId + // @option options [String] guid ... + // @option options [String] streamId ... + // @option options [String] section ... + // @option options [String] clientVersion ... + // + this.logQOS = function(options) { + this.logEvent(options, true); + }; + }; + +})(); + +// AJAX helpers + +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../helpers.js') +// tb_require('./ajax/node.js') +// tb_require('./ajax/browser.js') + +OTHelpers.get = function(url, options, callback) { + var _options = OTHelpers.extend(options || {}, { + method: 'GET' + }); + OTHelpers.request(url, _options, callback); +}; + + +OTHelpers.post = function(url, options, callback) { + var _options = OTHelpers.extend(options || {}, { + method: 'POST' + }); + + if(_options.xdomainrequest) { + OTHelpers.xdomainRequest(url, _options, callback); + } else { + OTHelpers.request(url, _options, callback); + } +}; + /*! * This is a modified version of Robert Kieffer awesome uuid.js library. * The only modifications we've made are to remove the Node.js specific @@ -775,6 +2701,1360 @@ OTHelpers.statable = function(self, possibleStates, initialState, stateChanged, OTHelpers.uuid = uuid; }()); +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../helpers.js') +// tb_require('../vendor/uuid.js') +// tb_require('./dom_events.js') + +(function() { + + var _callAsync; + + // Is true if window.postMessage is supported. + // This is not quite as simple as just looking for + // window.postMessage as some older versions of IE + // have a broken implementation of it. + // + var supportsPostMessage = (function () { + if (window.postMessage) { + // Check to see if postMessage fires synchronously, + // if it does, then the implementation of postMessage + // is broken. + var postMessageIsAsynchronous = true; + var oldOnMessage = window.onmessage; + window.onmessage = function() { + postMessageIsAsynchronous = false; + }; + window.postMessage('', '*'); + window.onmessage = oldOnMessage; + return postMessageIsAsynchronous; + } + })(); + + if (supportsPostMessage) { + var timeouts = [], + messageName = 'OTHelpers.' + OTHelpers.uuid.v4() + '.zero-timeout'; + + var removeMessageHandler = function() { + timeouts = []; + + if(window.removeEventListener) { + window.removeEventListener('message', handleMessage); + } else if(window.detachEvent) { + window.detachEvent('onmessage', handleMessage); + } + }; + + var handleMessage = function(event) { + if (event.source === window && + event.data === messageName) { + + if(OTHelpers.isFunction(event.stopPropagation)) { + event.stopPropagation(); + } + event.cancelBubble = true; + + if (!window.___othelpers) { + removeMessageHandler(); + return; + } + + if (timeouts.length > 0) { + var args = timeouts.shift(), + fn = args.shift(); + + fn.apply(null, args); + } + } + }; + + // Ensure that we don't receive messages after unload + // Yes, this seems to really happen in IE sometimes, usually + // when iFrames are involved. + OTHelpers.on(window, 'unload', removeMessageHandler); + + if(window.addEventListener) { + window.addEventListener('message', handleMessage, true); + } else if(window.attachEvent) { + window.attachEvent('onmessage', handleMessage); + } + + _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) { + timeouts.push(prototypeSlice.call(arguments)); + window.postMessage(messageName, '*'); + }; + } + else { + _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) { + var args = prototypeSlice.call(arguments), + fn = args.shift(); + + setTimeout(function() { + fn.apply(null, args); + }, 0); + }; + } + + + // Calls the function +fn+ asynchronously with the current execution. + // This is most commonly used to execute something straight after + // the current function. + // + // Any arguments in addition to +fn+ will be passed to +fn+ when it's + // called. + // + // You would use this inplace of setTimeout(fn, 0) type constructs. callAsync + // is preferable as it executes in a much more predictable time window, + // unlike setTimeout which could execute anywhere from 2ms to several thousand + // depending on the browser/context. + // + // It does this using window.postMessage, although if postMessage won't + // work it will fallback to setTimeout. + // + OTHelpers.callAsync = _callAsync; + + + // Wraps +handler+ in a function that will execute it asynchronously + // so that it doesn't interfere with it's exceution context if it raises + // an exception. + OTHelpers.createAsyncHandler = function(handler) { + return function() { + var args = prototypeSlice.call(arguments); + + OTHelpers.callAsync(function() { + handler.apply(null, args); + }); + }; + }; + +})(); + +/*jshint browser:true, smarttabs:true */ + +// tb_require('../helpers.js') +// tb_require('./callbacks.js') +// tb_require('./dom_events.js') + +OTHelpers.createElement = function(nodeName, attributes, children, doc) { + var element = (doc || document).createElement(nodeName); + + if (attributes) { + for (var name in attributes) { + if (typeof(attributes[name]) === 'object') { + if (!element[name]) element[name] = {}; + + var subAttrs = attributes[name]; + for (var n in subAttrs) { + element[name][n] = subAttrs[n]; + } + } + else if (name === 'className') { + element.className = attributes[name]; + } + else { + element.setAttribute(name, attributes[name]); + } + } + } + + var setChildren = function(child) { + if(typeof child === 'string') { + element.innerHTML = element.innerHTML + child; + } else { + element.appendChild(child); + } + }; + + if($.isArray(children)) { + $.forEach(children, setChildren); + } else if(children) { + setChildren(children); + } + + return element; +}; + +OTHelpers.createButton = function(innerHTML, attributes, events) { + var button = $.createElement('button', attributes, innerHTML); + + if (events) { + for (var name in events) { + if (events.hasOwnProperty(name)) { + $.on(button, name, events[name]); + } + } + + button._boundEvents = events; + } + + return button; +}; +/*jshint browser:true, smarttabs:true */ + +// tb_require('../helpers.js') +// tb_require('./callbacks.js') + +// DOM helpers + +var firstElementChild; + +// This mess is for IE8 +if( typeof(document) !== 'undefined' && + document.createElement('div').firstElementChild !== void 0 ){ + firstElementChild = function firstElementChild (parentElement) { + return parentElement.firstElementChild; + }; +} +else { + firstElementChild = function firstElementChild (parentElement) { + var el = parentElement.firstChild; + + do { + if(el.nodeType===1){ + return el; + } + el = el.nextSibling; + } while(el); + + return null; + }; +} + + +ElementCollection.prototype.appendTo = function(parentElement) { + if (!parentElement) throw new Error('appendTo requires a DOMElement to append to.'); + + return this.forEach(function(child) { + parentElement.appendChild(child); + }); +}; + +ElementCollection.prototype.append = function() { + var parentElement = this.first; + if (!parentElement) return this; + + $.forEach(prototypeSlice.call(arguments), function(child) { + parentElement.appendChild(child); + }); + + return this; +}; + +ElementCollection.prototype.prepend = function() { + if (arguments.length === 0) return this; + + var parentElement = this.first, + elementsToPrepend; + + if (!parentElement) return this; + + elementsToPrepend = prototypeSlice.call(arguments); + + if (!firstElementChild(parentElement)) { + parentElement.appendChild(elementsToPrepend.shift()); + } + + $.forEach(elementsToPrepend, function(element) { + parentElement.insertBefore(element, firstElementChild(parentElement)); + }); + + return this; +}; + +ElementCollection.prototype.after = function(prevElement) { + if (!prevElement) throw new Error('after requires a DOMElement to insert after'); + + return this.forEach(function(element) { + if (element.parentElement) { + if (prevElement !== element.parentNode.lastChild) { + element.parentElement.insertBefore(element, prevElement); + } + else { + element.parentElement.appendChild(element); + } + } + }); +}; + +ElementCollection.prototype.before = function(nextElement) { + if (!nextElement) { + throw new Error('before requires a DOMElement to insert before'); + } + + return this.forEach(function(element) { + if (element.parentElement) { + element.parentElement.insertBefore(element, nextElement); + } + }); +}; + +ElementCollection.prototype.remove = function () { + return this.forEach(function(element) { + if (element.parentNode) { + element.parentNode.removeChild(element); + } + }); +}; + +ElementCollection.prototype.empty = function () { + return this.forEach(function(element) { + // elements is a "live" NodesList collection. Meaning that the collection + // itself will be mutated as we remove elements from the DOM. This means + // that "while there are still elements" is safer than "iterate over each + // element" as the collection length and the elements indices will be modified + // with each iteration. + while (element.firstChild) { + element.removeChild(element.firstChild); + } + }); +}; + + +// Detects when an element is not part of the document flow because +// it or one of it's ancesters has display:none. +ElementCollection.prototype.isDisplayNone = function() { + return this.some(function(element) { + if ( (element.offsetWidth === 0 || element.offsetHeight === 0) && + $(element).css('display') === 'none') return true; + + if (element.parentNode && element.parentNode.style) { + return $(element.parentNode).isDisplayNone(); + } + }); +}; + +ElementCollection.prototype.findElementWithDisplayNone = function(element) { + return $.findElementWithDisplayNone(element); +}; + + + +OTHelpers.isElementNode = function(node) { + return node && typeof node === 'object' && node.nodeType === 1; +}; + + +// @remove +OTHelpers.removeElement = function(element) { + $(element).remove(); +}; + +// @remove +OTHelpers.removeElementById = function(elementId) { + return $('#'+elementId).remove(); +}; + +// @remove +OTHelpers.removeElementsByType = function(parentElem, type) { + return $(type, parentElem).remove(); +}; + +// @remove +OTHelpers.emptyElement = function(element) { + return $(element).empty(); +}; + + + + + +// @remove +OTHelpers.isDisplayNone = function(element) { + return $(element).isDisplayNone(); +}; + +OTHelpers.findElementWithDisplayNone = function(element) { + if ( (element.offsetWidth === 0 || element.offsetHeight === 0) && + $.css(element, 'display') === 'none') return element; + + if (element.parentNode && element.parentNode.style) { + return $.findElementWithDisplayNone(element.parentNode); + } + + return null; +}; + + +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../helpers.js') +// tb_require('./environment.js') +// tb_require('./dom.js') + +OTHelpers.Modal = function(options) { + + OTHelpers.eventing(this, true); + + var callback = arguments[arguments.length - 1]; + + if(!OTHelpers.isFunction(callback)) { + throw new Error('OTHelpers.Modal2 must be given a callback'); + } + + if(arguments.length < 2) { + options = {}; + } + + var domElement = document.createElement('iframe'); + + domElement.id = options.id || OTHelpers.uuid(); + domElement.style.position = 'absolute'; + domElement.style.position = 'fixed'; + domElement.style.height = '100%'; + domElement.style.width = '100%'; + domElement.style.top = '0px'; + domElement.style.left = '0px'; + domElement.style.right = '0px'; + domElement.style.bottom = '0px'; + domElement.style.zIndex = 1000; + domElement.style.border = '0'; + + try { + domElement.style.backgroundColor = 'rgba(0,0,0,0.2)'; + } catch (err) { + // Old IE browsers don't support rgba and we still want to show the upgrade message + // but we just make the background of the iframe completely transparent. + domElement.style.backgroundColor = 'transparent'; + domElement.setAttribute('allowTransparency', 'true'); + } + + domElement.scrolling = 'no'; + domElement.setAttribute('scrolling', 'no'); + + // This is necessary for IE, as it will not inherit it's doctype from + // the parent frame. + var frameContent = '' + + '' + + '' + + ''; + + var wrappedCallback = function() { + var doc = domElement.contentDocument || domElement.contentWindow.document; + + if (OTHelpers.env.iframeNeedsLoad) { + doc.body.style.backgroundColor = 'transparent'; + doc.body.style.border = 'none'; + + if (OTHelpers.env.name !== 'IE') { + // Skip this for IE as we use the bookmarklet workaround + // for THAT browser. + doc.open(); + doc.write(frameContent); + doc.close(); + } + } + + callback( + domElement.contentWindow, + doc + ); + }; + + document.body.appendChild(domElement); + + if(OTHelpers.env.iframeNeedsLoad) { + if (OTHelpers.env.name === 'IE') { + // This works around some issues with IE and document.write. + // Basically this works by slightly abusing the bookmarklet/scriptlet + // functionality that all browsers support. + domElement.contentWindow.contents = frameContent; + /*jshint scripturl:true*/ + domElement.src = 'javascript:window["contents"]'; + /*jshint scripturl:false*/ + } + + OTHelpers.on(domElement, 'load', wrappedCallback); + } else { + setTimeout(wrappedCallback, 0); + } + + this.close = function() { + OTHelpers.removeElement(domElement); + this.trigger('closed'); + this.element = domElement = null; + return this; + }; + + this.element = domElement; + +}; + +/* + * getComputedStyle from + * https://github.com/jonathantneal/Polyfills-for-IE8/blob/master/getComputedStyle.js + +// tb_require('../helpers.js') +// tb_require('./dom.js') + +/*jshint strict: false, eqnull: true, browser:true, smarttabs:true*/ + +(function() { + + /*jshint eqnull: true, browser: true */ + + + function getPixelSize(element, style, property, fontSize) { + var sizeWithSuffix = style[property], + size = parseFloat(sizeWithSuffix), + suffix = sizeWithSuffix.split(/\d/)[0], + rootSize; + + fontSize = fontSize != null ? + fontSize : /%|em/.test(suffix) && element.parentElement ? + getPixelSize(element.parentElement, element.parentElement.currentStyle, 'fontSize', null) : + 16; + rootSize = property === 'fontSize' ? + fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight; + + return (suffix === 'em') ? + size * fontSize : (suffix === 'in') ? + size * 96 : (suffix === 'pt') ? + size * 96 / 72 : (suffix === '%') ? + size / 100 * rootSize : size; + } + + function setShortStyleProperty(style, property) { + var + borderSuffix = property === 'border' ? 'Width' : '', + t = property + 'Top' + borderSuffix, + r = property + 'Right' + borderSuffix, + b = property + 'Bottom' + borderSuffix, + l = property + 'Left' + borderSuffix; + + style[property] = (style[t] === style[r] === style[b] === style[l] ? [style[t]] + : style[t] === style[b] && style[l] === style[r] ? [style[t], style[r]] + : style[l] === style[r] ? [style[t], style[r], style[b]] + : [style[t], style[r], style[b], style[l]]).join(' '); + } + + function CSSStyleDeclaration(element) { + var currentStyle = element.currentStyle, + style = this, + fontSize = getPixelSize(element, currentStyle, 'fontSize', null), + property; + + for (property in currentStyle) { + if (/width|height|margin.|padding.|border.+W/.test(property) && style[property] !== 'auto') { + style[property] = getPixelSize(element, currentStyle, property, fontSize) + 'px'; + } else if (property === 'styleFloat') { + /*jshint -W069 */ + style['float'] = currentStyle[property]; + } else { + style[property] = currentStyle[property]; + } + } + + setShortStyleProperty(style, 'margin'); + setShortStyleProperty(style, 'padding'); + setShortStyleProperty(style, 'border'); + + style.fontSize = fontSize + 'px'; + + return style; + } + + CSSStyleDeclaration.prototype = { + constructor: CSSStyleDeclaration, + getPropertyPriority: function () {}, + getPropertyValue: function ( prop ) { + return this[prop] || ''; + }, + item: function () {}, + removeProperty: function () {}, + setProperty: function () {}, + getPropertyCSSValue: function () {} + }; + + function getComputedStyle(element) { + return new CSSStyleDeclaration(element); + } + + + OTHelpers.getComputedStyle = function(element) { + if(element && + element.ownerDocument && + element.ownerDocument.defaultView && + element.ownerDocument.defaultView.getComputedStyle) { + return element.ownerDocument.defaultView.getComputedStyle(element); + } else { + return getComputedStyle(element); + } + }; + +})(); + +/*jshint browser:true, smarttabs:true */ + +// tb_require('../helpers.js') +// tb_require('./callbacks.js') +// tb_require('./dom.js') + +var observeStyleChanges = function observeStyleChanges (element, stylesToObserve, onChange) { + var oldStyles = {}; + + var getStyle = function getStyle(style) { + switch (style) { + case 'width': + return $(element).width(); + + case 'height': + return $(element).height(); + + default: + return $(element).css(style); + } + }; + + // get the inital values + $.forEach(stylesToObserve, function(style) { + oldStyles[style] = getStyle(style); + }); + + var observer = new MutationObserver(function(mutations) { + var changeSet = {}; + + $.forEach(mutations, function(mutation) { + if (mutation.attributeName !== 'style') return; + + var isHidden = $.isDisplayNone(element); + + $.forEach(stylesToObserve, function(style) { + if(isHidden && (style === 'width' || style === 'height')) return; + + var newValue = getStyle(style); + + if (newValue !== oldStyles[style]) { + changeSet[style] = [oldStyles[style], newValue]; + oldStyles[style] = newValue; + } + }); + }); + + if (!$.isEmpty(changeSet)) { + // Do this after so as to help avoid infinite loops of mutations. + $.callAsync(function() { + onChange.call(null, changeSet); + }); + } + }); + + observer.observe(element, { + attributes:true, + attributeFilter: ['style'], + childList:false, + characterData:false, + subtree:false + }); + + return observer; +}; + +var observeNodeOrChildNodeRemoval = function observeNodeOrChildNodeRemoval (element, onChange) { + var observer = new MutationObserver(function(mutations) { + var removedNodes = []; + + $.forEach(mutations, function(mutation) { + if (mutation.removedNodes.length) { + removedNodes = removedNodes.concat(prototypeSlice.call(mutation.removedNodes)); + } + }); + + if (removedNodes.length) { + // Do this after so as to help avoid infinite loops of mutations. + $.callAsync(function() { + onChange($(removedNodes)); + }); + } + }); + + observer.observe(element, { + attributes:false, + childList:true, + characterData:false, + subtree:true + }); + + return observer; +}; + +var observeSize = function (element, onChange) { + var previousSize = { + width: 0, + height: 0 + }; + + var interval = setInterval(function() { + var rect = element.getBoundingClientRect(); + if (previousSize.width !== rect.width || previousSize.height !== rect.height) { + onChange(rect, previousSize); + previousSize = { + width: rect.width, + height: rect.height + }; + } + }, 1000 / 5); + + return { + disconnect: function() { + clearInterval(interval); + } + }; +}; + +// Allows an +onChange+ callback to be triggered when specific style properties +// of +element+ are notified. The callback accepts a single parameter, which is +// a hash where the keys are the style property that changed and the values are +// an array containing the old and new values ([oldValue, newValue]). +// +// Width and Height changes while the element is display: none will not be +// fired until such time as the element becomes visible again. +// +// This function returns the MutationObserver itself. Once you no longer wish +// to observe the element you should call disconnect on the observer. +// +// Observing changes: +// // observe changings to the width and height of object +// dimensionsObserver = OTHelpers(object).observeStyleChanges(, +// ['width', 'height'], function(changeSet) { +// OT.debug("The new width and height are " + +// changeSet.width[1] + ',' + changeSet.height[1]); +// }); +// +// Cleaning up +// // stop observing changes +// dimensionsObserver.disconnect(); +// dimensionsObserver = null; +// +ElementCollection.prototype.observeStyleChanges = function(stylesToObserve, onChange) { + var observers = []; + + this.forEach(function(element) { + observers.push( + observeStyleChanges(element, stylesToObserve, onChange) + ); + }); + + return observers; +}; + +// trigger the +onChange+ callback whenever +// 1. +element+ is removed +// 2. or an immediate child of +element+ is removed. +// +// This function returns the MutationObserver itself. Once you no longer wish +// to observe the element you should call disconnect on the observer. +// +// Observing changes: +// // observe changings to the width and height of object +// nodeObserver = OTHelpers(object).observeNodeOrChildNodeRemoval(function(removedNodes) { +// OT.debug("Some child nodes were removed"); +// removedNodes.forEach(function(node) { +// OT.debug(node); +// }); +// }); +// +// Cleaning up +// // stop observing changes +// nodeObserver.disconnect(); +// nodeObserver = null; +// +ElementCollection.prototype.observeNodeOrChildNodeRemoval = function(onChange) { + var observers = []; + + this.forEach(function(element) { + observers.push( + observeNodeOrChildNodeRemoval(element, onChange) + ); + }); + + return observers; +}; + +// trigger the +onChange+ callback whenever the width or the height of the element changes +// +// Once you no longer wish to observe the element you should call disconnect on the observer. +// +// Observing changes: +// // observe changings to the width and height of object +// sizeObserver = OTHelpers(object).observeSize(function(newSize, previousSize) { +// OT.debug("The new width and height are " + +// newSize.width + ',' + newSize.height); +// }); +// +// Cleaning up +// // stop observing changes +// sizeObserver.disconnect(); +// sizeObserver = null; +// +ElementCollection.prototype.observeSize = function(onChange) { + var observers = []; + + this.forEach(function(element) { + observers.push( + observeSize(element, onChange) + ); + }); + + return observers; +}; + + +// @remove +OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) { + return $(element).observeStyleChanges(stylesToObserve, onChange)[0]; +}; + +// @remove +OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) { + return $(element).observeNodeOrChildNodeRemoval(onChange)[0]; +}; + +/*jshint browser:true, smarttabs:true */ + +// tb_require('../helpers.js') +// tb_require('./dom.js') +// tb_require('./capabilities.js') + +// Returns true if the client supports element.classList +OTHelpers.registerCapability('classList', function() { + return (typeof document !== 'undefined') && ('classList' in document.createElement('a')); +}); + + +function hasClass (element, className) { + if (!className) return false; + + if ($.hasCapabilities('classList')) { + return element.classList.contains(className); + } + + return element.className.indexOf(className) > -1; +} + +function toggleClasses (element, classNames) { + if (!classNames || classNames.length === 0) return; + + // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc + if (element.nodeType !== 1) { + return; + } + + var numClasses = classNames.length, + i = 0; + + if ($.hasCapabilities('classList')) { + for (; i 0) { + return element.offsetWidth + 'px'; + } + + return $(element).css('width'); + }, + + _height = function(element) { + if (element.offsetHeight > 0) { + return element.offsetHeight + 'px'; + } + + return $(element).css('height'); + }; + + ElementCollection.prototype.width = function (newWidth) { + if (newWidth) { + this.css('width', newWidth); + return this; + } + else { + if (this.isDisplayNone()) { + return this.makeVisibleAndYield(function(element) { + return _width(element); + })[0]; + } + else { + return _width(this.get(0)); + } + } + }; + + ElementCollection.prototype.height = function (newHeight) { + if (newHeight) { + this.css('height', newHeight); + return this; + } + else { + if (this.isDisplayNone()) { + // We can't get the height, probably since the element is hidden. + return this.makeVisibleAndYield(function(element) { + return _height(element); + })[0]; + } + else { + return _height(this.get(0)); + } + } + }; + + // @remove + OTHelpers.width = function(element, newWidth) { + var ret = $(element).width(newWidth); + return newWidth ? OTHelpers : ret; + }; + + // @remove + OTHelpers.height = function(element, newHeight) { + var ret = $(element).height(newHeight); + return newHeight ? OTHelpers : ret; + }; + +})(); + + +// CSS helpers helpers + +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../helpers.js') +// tb_require('./dom.js') +// tb_require('./getcomputedstyle.js') + +(function() { + + var displayStateCache = {}, + defaultDisplays = {}; + + var defaultDisplayValueForElement = function (element) { + if (defaultDisplays[element.ownerDocument] && + defaultDisplays[element.ownerDocument][element.nodeName]) { + return defaultDisplays[element.ownerDocument][element.nodeName]; + } + + if (!defaultDisplays[element.ownerDocument]) defaultDisplays[element.ownerDocument] = {}; + + // We need to know what display value to use for this node. The easiest way + // is to actually create a node and read it out. + var testNode = element.ownerDocument.createElement(element.nodeName), + defaultDisplay; + + element.ownerDocument.body.appendChild(testNode); + defaultDisplay = defaultDisplays[element.ownerDocument][element.nodeName] = + $(testNode).css('display'); + + $(testNode).remove(); + testNode = null; + + return defaultDisplay; + }; + + var isHidden = function (element) { + var computedStyle = $.getComputedStyle(element); + return computedStyle.getPropertyValue('display') === 'none'; + }; + + var setCssProperties = function (element, hash) { + var style = element.style; + + for (var cssName in hash) { + if (hash.hasOwnProperty(cssName)) { + style[cssName] = hash[cssName]; + } + } + }; + + var setCssProperty = function (element, name, value) { + element.style[name] = value; + }; + + var getCssProperty = function (element, unnormalisedName) { + // Normalise vendor prefixes from the form MozTranform to -moz-transform + // except for ms extensions, which are weird... + + var name = unnormalisedName.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(), + computedStyle = $.getComputedStyle(element), + currentValue = computedStyle.getPropertyValue(name); + + if (currentValue === '') { + currentValue = element.style[name]; + } + + return currentValue; + }; + + var applyCSS = function(element, styles, callback) { + var oldStyles = {}, + name, + ret; + + // Backup the old styles + for (name in styles) { + if (styles.hasOwnProperty(name)) { + // We intentionally read out of style here, instead of using the css + // helper. This is because the css helper uses querySelector and we + // only want to pull values out of the style (domeElement.style) hash. + oldStyles[name] = element.style[name]; + + $(element).css(name, styles[name]); + } + } + + ret = callback(element); + + // Restore the old styles + for (name in styles) { + if (styles.hasOwnProperty(name)) { + $(element).css(name, oldStyles[name] || ''); + } + } + + return ret; + }; + + ElementCollection.prototype.show = function() { + return this.forEach(function(element) { + var display = element.style.display; + + if (display === '' || display === 'none') { + element.style.display = displayStateCache[element] || ''; + delete displayStateCache[element]; + } + + if (isHidden(element)) { + // It's still hidden so there's probably a stylesheet that declares this + // element as display:none; + displayStateCache[element] = 'none'; + + element.style.display = defaultDisplayValueForElement(element); + } + }); + }; + + ElementCollection.prototype.hide = function() { + return this.forEach(function(element) { + if (element.style.display === 'none') return; + + displayStateCache[element] = element.style.display; + element.style.display = 'none'; + }); + }; + + ElementCollection.prototype.css = function(nameOrHash, value) { + if (this.length === 0) return; + + if (typeof(nameOrHash) !== 'string') { + + return this.forEach(function(element) { + setCssProperties(element, nameOrHash); + }); + + } else if (value !== undefined) { + + return this.forEach(function(element) { + setCssProperty(element, nameOrHash, value); + }); + + } else { + return getCssProperty(this.first, nameOrHash, value); + } + }; + + // Apply +styles+ to +element+ while executing +callback+, restoring the previous + // styles after the callback executes. + ElementCollection.prototype.applyCSS = function (styles, callback) { + var results = []; + + this.forEach(function(element) { + results.push(applyCSS(element, styles, callback)); + }); + + return results; + }; + + + // Make +element+ visible while executing +callback+. + ElementCollection.prototype.makeVisibleAndYield = function (callback) { + var hiddenVisually = { + display: 'block', + visibility: 'hidden' + }, + results = []; + + this.forEach(function(element) { + // find whether it's the element or an ancestor that's display none and + // then apply to whichever it is + var targetElement = $.findElementWithDisplayNone(element); + if (!targetElement) { + results.push(void 0); + } + else { + results.push( + applyCSS(targetElement, hiddenVisually, callback) + ); + } + }); + + return results; + }; + + + // @remove + OTHelpers.show = function(element) { + return $(element).show(); + }; + + // @remove + OTHelpers.hide = function(element) { + return $(element).hide(); + }; + + // @remove + OTHelpers.css = function(element, nameOrHash, value) { + return $(element).css(nameOrHash, value); + }; + + // @remove + OTHelpers.applyCSS = function(element, styles, callback) { + return $(element).applyCSS(styles, callback); + }; + + // @remove + OTHelpers.makeVisibleAndYield = function(element, callback) { + return $(element).makeVisibleAndYield(callback); + }; + +})(); + // tb_require('../helpers.js') /*! @@ -2445,1685 +5725,6 @@ OTHelpers.statable = function(self, possibleStates, initialState, stateChanged, }).call(this); /* jshint ignore:end */ -/*jshint browser:true, smarttabs:true */ - -// tb_require('../helpers.js') - - -var getErrorLocation; - -// Properties that we'll acknowledge from the JS Error object -var safeErrorProps = [ - 'description', - 'fileName', - 'lineNumber', - 'message', - 'name', - 'number', - 'stack' -]; - - -// OTHelpers.Error -// -// A construct to contain error information that also helps with extracting error -// context, such as stack trace. -// -// @constructor -// @memberof OTHelpers -// @method Error -// -// @param {String} message -// Optional. The error message -// -// @param {Object} props -// Optional. A dictionary of properties containing extra Error info. -// -// -// @example Create a simple error with juts a custom message -// var error = new OTHelpers.Error('Something Broke!'); -// error.message === 'Something Broke!'; -// -// @example Create an Error with a message and a name -// var error = new OTHelpers.Error('Something Broke!', 'FooError'); -// error.message === 'Something Broke!'; -// error.name === 'FooError'; -// -// @example Create an Error with a message, name, and custom properties -// var error = new OTHelpers.Error('Something Broke!', 'FooError', { -// foo: 'bar', -// listOfImportantThings: [1,2,3,4] -// }); -// error.message === 'Something Broke!'; -// error.name === 'FooError'; -// error.foo === 'bar'; -// error.listOfImportantThings == [1,2,3,4]; -// -// @example Create an Error from a Javascript Error -// var error = new OTHelpers.Error(domSyntaxError); -// error.message === domSyntaxError.message; -// error.name === domSyntaxError.name === 'SyntaxError'; -// // ...continues for each properties of domSyntaxError -// -// @references -// * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi -// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack -// * http://www.w3.org/TR/dom/#interface-domerror -// -// -// @todo -// * update usage in OTMedia -// * replace error handling in OT.js -// * normalise stack behaviour under Chrome/Node/Safari with other browsers -// * unit test for stack parsing -// -// -OTHelpers.Error = function (message, name, props) { - switch (arguments.length) { - case 1: - if ($.isObject(message)) { - props = message; - name = void 0; - message = void 0; - } - // Otherwise it's the message - break; - - case 2: - if ($.isObject(name)) { - props = name; - name = void 0; - } - // Otherwise name is actually the name - - break; - } - - if ( props instanceof Error) { - // Special handling of this due to Chrome weirdness. It seems that - // properties of the Error object, and it's children, are not - // enumerable in Chrome? - for (var i = 0, num = safeErrorProps.length; i < num; ++i) { - this[safeErrorProps[i]] = props[safeErrorProps[i]]; - } - } - else if ( $.isObject(props)) { - // Use an custom properties that are provided - for (var key in props) { - if (props.hasOwnProperty(key)) { - this[key] = props[key]; - } - } - } - - // If any of the fundamental properties are missing then try and - // extract them. - if ( !(this.fileName && this.lineNumber && this.columnNumber && this.stack) ) { - var err = getErrorLocation(); - - if (!this.fileName && err.fileName) { - this.fileName = err.fileName; - } - - if (!this.lineNumber && err.lineNumber) { - this.lineNumber = err.lineNumber; - } - - if (!this.columnNumber && err.columnNumber) { - this.columnNumber = err.columnNumber; - } - - if (!this.stack && err.stack) { - this.stack = err.stack; - } - } - - if (!this.message && message) this.message = message; - if (!this.name && name) this.name = name; -}; - -OTHelpers.Error.prototype.toString = -OTHelpers.Error.prototype.valueOf = function() { - var locationDetails = ''; - if (this.fileName) locationDetails += ' ' + this.fileName; - if (this.lineNumber) { - locationDetails += ' ' + this.lineNumber; - if (this.columnNumber) locationDetails += ':' + this.columnNumber; - } - - return '<' + (this.name ? this.name + ' ' : '') + this.message + locationDetails + '>'; -}; - - -// Normalise err.stack so that it is the same format as the other browsers -// We skip the first two frames so that we don't capture getErrorLocation() and -// the callee. -// -// Used by Environments that support the StackTrace API. (Chrome, Node, Opera) -// -var prepareStackTrace = function prepareStackTrace (_, stack){ - return $.map(stack.slice(2), function(frame) { - var _f = { - fileName: frame.getFileName(), - linenumber: frame.getLineNumber(), - columnNumber: frame.getColumnNumber() - }; - - if (frame.getFunctionName()) _f.functionName = frame.getFunctionName(); - if (frame.getMethodName()) _f.methodName = frame.getMethodName(); - if (frame.getThis()) _f.self = frame.getThis(); - - return _f; - }); -}; - - -// Black magic to retrieve error location info for various environments -getErrorLocation = function getErrorLocation () { - var info = {}, - callstack, - errLocation, - err; - - switch ($.env.name) { - case 'Firefox': - case 'Safari': - case 'IE': - - if ($.env.name === 'IE') { - err = new Error(); - } - else { - try { - window.call.js.is.explody; - } - catch(e) { err = e; } - } - - callstack = err.stack.split('\n'); - - //Remove call to getErrorLocation() and the callee - callstack.shift(); - callstack.shift(); - - info.stack = callstack; - - if ($.env.name === 'IE') { - // IE also includes the error message in it's stack trace - info.stack.shift(); - - // each line begins with some amounts of spaces and 'at', we remove - // these to normalise with the other browsers. - info.stack = $.map(callstack, function(call) { - return call.replace(/^\s+at\s+/g, ''); - }); - } - - errLocation = /@(.+?):([0-9]+)(:([0-9]+))?$/.exec(callstack[0]); - if (errLocation) { - info.fileName = errLocation[1]; - info.lineNumber = parseInt(errLocation[2], 10); - if (errLocation.length > 3) info.columnNumber = parseInt(errLocation[4], 10); - } - break; - - case 'Chrome': - case 'Node': - case 'Opera': - var currentPST = Error.prepareStackTrace; - Error.prepareStackTrace = prepareStackTrace; - err = new Error(); - info.stack = err.stack; - Error.prepareStackTrace = currentPST; - - var topFrame = info.stack[0]; - info.lineNumber = topFrame.lineNumber; - info.columnNumber = topFrame.columnNumber; - info.fileName = topFrame.fileName; - if (topFrame.functionName) info.functionName = topFrame.functionName; - if (topFrame.methodName) info.methodName = topFrame.methodName; - if (topFrame.self) info.self = topFrame.self; - break; - - default: - err = new Error(); - if (err.stack) info.stack = err.stack.split('\n'); - break; - } - - if (err.message) info.message = err.message; - return info; -}; - - -/*jshint browser:true, smarttabs:true*/ -/* global process */ - -// tb_require('../helpers.js') - - -// OTHelpers.env -// -// Contains information about the current environment. -// * **OTHelpers.env.name** The name of the Environment (Chrome, FF, Node, etc) -// * **OTHelpers.env.version** Usually a Float, except in Node which uses a String -// * **OTHelpers.env.userAgent** The raw user agent -// * **OTHelpers.env.versionGreaterThan** A helper method that returns true if the -// current version is greater than the argument -// -// Example -// if (OTHelpers.env.versionGreaterThan('0.10.30')) { -// // do something -// } -// -(function() { - // @todo make exposing userAgent unnecessary - var version = -1; - - // Returns true if otherVersion is greater than the current environment - // version. - var versionGEThan = function versionGEThan (otherVersion) { - if (otherVersion === version) return true; - - if (typeof(otherVersion) === 'number' && typeof(version) === 'number') { - return otherVersion > version; - } - - // The versions have multiple components (i.e. 0.10.30) and - // must be compared piecewise. - // Note: I'm ignoring the case where one version has multiple - // components and the other doesn't. - var v1 = otherVersion.split('.'), - v2 = version.split('.'), - versionLength = (v1.length > v2.length ? v2 : v1).length; - - for (var i = 0; i < versionLength; ++i) { - if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) { - return true; - } - } - - // Special case, v1 has extra components but the initial components - // were identical, we assume this means newer but it might also mean - // that someone changed versioning systems. - if (i < v1.length) { - return true; - } - - return false; - }; - - var env = function() { - if (typeof(process) !== 'undefined' && - typeof(process.versions) !== 'undefined' && - typeof(process.versions.node) === 'string') { - - version = process.versions.node; - if (version.substr(1) === 'v') version = version.substr(1); - - // Special casing node to avoid gating window.navigator. - // Version will be a string rather than a float. - return { - name: 'Node', - version: version, - userAgent: 'Node ' + version, - iframeNeedsLoad: false, - versionGreaterThan: versionGEThan - }; - } - - var userAgent = window.navigator.userAgent.toLowerCase(), - appName = window.navigator.appName, - navigatorVendor, - name = 'unknown'; - - if (userAgent.indexOf('opera') > -1 || userAgent.indexOf('opr') > -1) { - name = 'Opera'; - - if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (userAgent.indexOf('firefox') > -1) { - name = 'Firefox'; - - if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (appName === 'Microsoft Internet Explorer') { - // IE 10 and below - name = 'IE'; - - if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) { - // IE 11+ - - name = 'IE'; - - if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (userAgent.indexOf('chrome') > -1) { - name = 'Chrome'; - - if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if ((navigatorVendor = window.navigator.vendor) && - navigatorVendor.toLowerCase().indexOf('apple') > -1) { - name = 'Safari'; - - if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - } - - return { - name: name, - version: version, - userAgent: window.navigator.userAgent, - iframeNeedsLoad: userAgent.indexOf('webkit') < 0, - versionGreaterThan: versionGEThan - }; - }(); - - - OTHelpers.env = env; - - OTHelpers.browser = function() { - return OTHelpers.env.name; - }; - - OTHelpers.browserVersion = function() { - return OTHelpers.env; - }; - -})(); -// tb_require('../../environment.js') -// tb_require('./event.js') - -var nodeEventing; - -if($.env.name === 'Node') { - (function() { - var EventEmitter = require('events').EventEmitter, - util = require('util'); - - // container for the EventEmitter behaviour. This prevents tight coupling - // caused by accidentally bleeding implementation details and API into whatever - // objects nodeEventing is applied to. - var NodeEventable = function NodeEventable () { - EventEmitter.call(this); - - this.events = {}; - }; - util.inherits(NodeEventable, EventEmitter); - - - nodeEventing = function nodeEventing (/* self */) { - var api = new NodeEventable(), - _on = api.on, - _off = api.removeListener; - - - api.addListeners = function (eventNames, handler, context, closure) { - var listener = {handler: handler}; - if (context) listener.context = context; - if (closure) listener.closure = closure; - - $.forEach(eventNames, function(name) { - if (!api.events[name]) api.events[name] = []; - api.events[name].push(listener); - - _on(name, handler); - - var addedListener = name + ':added'; - if (api.events[addedListener]) { - api.emit(addedListener, api.events[name].length); - } - }); - }; - - api.removeAllListenersNamed = function (eventNames) { - var _eventNames = eventNames.split(' '); - api.removeAllListeners(_eventNames); - - $.forEach(_eventNames, function(name) { - if (api.events[name]) delete api.events[name]; - }); - }; - - api.removeListeners = function (eventNames, handler, closure) { - function filterHandlers(listener) { - return !(listener.handler === handler && listener.closure === closure); - } - - $.forEach(eventNames.split(' '), function(name) { - if (api.events[name]) { - _off(name, handler); - api.events[name] = $.filter(api.events[name], filterHandlers); - if (api.events[name].length === 0) delete api.events[name]; - - var removedListener = name + ':removed'; - if (api.events[removedListener]) { - api.emit(removedListener, api.events[name] ? api.events[name].length : 0); - } - } - }); - }; - - api.removeAllListeners = function () { - api.events = {}; - api.removeAllListeners(); - }; - - api.dispatchEvent = function(event, defaultAction) { - this.emit(event.type, event); - - if (defaultAction) { - defaultAction.call(null, event); - } - }; - - api.trigger = $.bind(api.emit, api); - - - return api; - }; - })(); -} - -// tb_require('../../environment.js') -// tb_require('./event.js') - -var browserEventing; - -if($.env.name !== 'Node') { - - browserEventing = function browserEventing (self, syncronous) { - var api = { - events: {} - }; - - - // Call the defaultAction, passing args - function executeDefaultAction(defaultAction, args) { - if (!defaultAction) return; - - defaultAction.apply(null, args.slice()); - } - - // Execute each handler in +listeners+ with +args+. - // - // Each handler will be executed async. On completion the defaultAction - // handler will be executed with the args. - // - // @param [Array] listeners - // An array of functions to execute. Each will be passed args. - // - // @param [Array] args - // An array of arguments to execute each function in +listeners+ with. - // - // @param [String] name - // The name of this event. - // - // @param [Function, Null, Undefined] defaultAction - // An optional function to execute after every other handler. This will execute even - // if +listeners+ is empty. +defaultAction+ will be passed args as a normal - // handler would. - // - // @return Undefined - // - function executeListenersAsyncronously(name, args, defaultAction) { - var listeners = api.events[name]; - if (!listeners || listeners.length === 0) return; - - var listenerAcks = listeners.length; - - $.forEach(listeners, function(listener) { // , index - function filterHandlers(_listener) { - return _listener.handler === listener.handler; - } - - // We run this asynchronously so that it doesn't interfere with execution if an - // error happens - $.callAsync(function() { - try { - // have to check if the listener has not been removed - if (api.events[name] && $.some(api.events[name], filterHandlers)) { - (listener.closure || listener.handler).apply(listener.context || null, args); - } - } - finally { - listenerAcks--; - - if (listenerAcks === 0) { - executeDefaultAction(defaultAction, args); - } - } - }); - }); - } - - - // This is identical to executeListenersAsyncronously except that handlers will - // be executed syncronously. - // - // On completion the defaultAction handler will be executed with the args. - // - // @param [Array] listeners - // An array of functions to execute. Each will be passed args. - // - // @param [Array] args - // An array of arguments to execute each function in +listeners+ with. - // - // @param [String] name - // The name of this event. - // - // @param [Function, Null, Undefined] defaultAction - // An optional function to execute after every other handler. This will execute even - // if +listeners+ is empty. +defaultAction+ will be passed args as a normal - // handler would. - // - // @return Undefined - // - function executeListenersSyncronously(name, args) { // defaultAction is not used - var listeners = api.events[name]; - if (!listeners || listeners.length === 0) return; - - $.forEach(listeners, function(listener) { // index - (listener.closure || listener.handler).apply(listener.context || null, args); - }); - } - - var executeListeners = syncronous === true ? - executeListenersSyncronously : executeListenersAsyncronously; - - - api.addListeners = function (eventNames, handler, context, closure) { - var listener = {handler: handler}; - if (context) listener.context = context; - if (closure) listener.closure = closure; - - $.forEach(eventNames, function(name) { - if (!api.events[name]) api.events[name] = []; - api.events[name].push(listener); - - var addedListener = name + ':added'; - if (api.events[addedListener]) { - executeListeners(addedListener, [api.events[name].length]); - } - }); - }; - - api.removeListeners = function(eventNames, handler, context) { - function filterListeners(listener) { - var isCorrectHandler = ( - listener.handler.originalHandler === handler || - listener.handler === handler - ); - - return !(isCorrectHandler && listener.context === context); - } - - $.forEach(eventNames, function(name) { - if (api.events[name]) { - api.events[name] = $.filter(api.events[name], filterListeners); - if (api.events[name].length === 0) delete api.events[name]; - - var removedListener = name + ':removed'; - if (api.events[ removedListener]) { - executeListeners(removedListener, [api.events[name] ? api.events[name].length : 0]); - } - } - }); - }; - - api.removeAllListenersNamed = function (eventNames) { - $.forEach(eventNames, function(name) { - if (api.events[name]) { - delete api.events[name]; - } - }); - }; - - api.removeAllListeners = function () { - api.events = {}; - }; - - api.dispatchEvent = function(event, defaultAction) { - if (!api.events[event.type] || api.events[event.type].length === 0) { - executeDefaultAction(defaultAction, [event]); - return; - } - - executeListeners(event.type, [event], defaultAction); - }; - - api.trigger = function(eventName, args) { - if (!api.events[eventName] || api.events[eventName].length === 0) { - return; - } - - executeListeners(eventName, args); - }; - - - return api; - }; -} - -/*jshint browser:false, smarttabs:true*/ -/* global window, require */ - -// tb_require('../../helpers.js') -// tb_require('../environment.js') - -if (window.OTHelpers.env.name === 'Node') { - var request = require('request'); - - OTHelpers.request = function(url, options, callback) { - var completion = function(error, response, body) { - var event = {response: response, body: body}; - - // We need to detect things that Request considers a success, - // but we consider to be failures. - if (!error && response.statusCode >= 200 && - (response.statusCode < 300 || response.statusCode === 304) ) { - callback(null, event); - } else { - callback(error, event); - } - }; - - if (options.method.toLowerCase() === 'get') { - request.get(url, completion); - } - else { - request.post(url, options.body, completion); - } - }; - - OTHelpers.getJSON = function(url, options, callback) { - var extendedHeaders = require('underscore').extend( - { - 'Accept': 'application/json' - }, - options.headers || {} - ); - - request.get({ - url: url, - headers: extendedHeaders, - json: true - }, function(err, response) { - callback(err, response && response.body); - }); - }; -} -/*jshint browser:true, smarttabs:true*/ - -// tb_require('../../helpers.js') -// tb_require('../environment.js') - -function formatPostData(data) { //, contentType - // If it's a string, we assume it's properly encoded - if (typeof(data) === 'string') return data; - - var queryString = []; - - for (var key in data) { - queryString.push( - encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) - ); - } - - return queryString.join('&').replace(/\+/g, '%20'); -} - -if (window.OTHelpers.env.name !== 'Node') { - - OTHelpers.xdomainRequest = function(url, options, callback) { - /*global XDomainRequest*/ - var xdr = new XDomainRequest(), - _options = options || {}, - _method = _options.method.toLowerCase(); - - if(!_method) { - callback(new Error('No HTTP method specified in options')); - return; - } - - _method = _method.toUpperCase(); - - if(!(_method === 'GET' || _method === 'POST')) { - callback(new Error('HTTP method can only be ')); - return; - } - - function done(err, event) { - xdr.onload = xdr.onerror = xdr.ontimeout = function() {}; - xdr = void 0; - callback(err, event); - } - - - xdr.onload = function() { - done(null, { - target: { - responseText: xdr.responseText, - headers: { - 'content-type': xdr.contentType - } - } - }); - }; - - xdr.onerror = function() { - done(new Error('XDomainRequest of ' + url + ' failed')); - }; - - xdr.ontimeout = function() { - done(new Error('XDomainRequest of ' + url + ' timed out')); - }; - - xdr.open(_method, url); - xdr.send(options.body && formatPostData(options.body)); - - }; - - OTHelpers.request = function(url, options, callback) { - var request = new XMLHttpRequest(), - _options = options || {}, - _method = _options.method; - - if(!_method) { - callback(new Error('No HTTP method specified in options')); - return; - } - - // Setup callbacks to correctly respond to success and error callbacks. This includes - // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore - // by default. - if(callback) { - OTHelpers.on(request, 'load', function(event) { - var status = event.target.status; - - // We need to detect things that XMLHttpRequest considers a success, - // but we consider to be failures. - if ( status >= 200 && (status < 300 || status === 304) ) { - callback(null, event); - } else { - callback(event); - } - }); - - OTHelpers.on(request, 'error', callback); - } - - request.open(options.method, url, true); - - if (!_options.headers) _options.headers = {}; - - for (var name in _options.headers) { - request.setRequestHeader(name, _options.headers[name]); - } - - request.send(options.body && formatPostData(options.body)); - }; - - - OTHelpers.getJSON = function(url, options, callback) { - options = options || {}; - - var done = function(error, event) { - if(error) { - callback(error, event && event.target && event.target.responseText); - } else { - var response; - - try { - response = JSON.parse(event.target.responseText); - } catch(e) { - // Badly formed JSON - callback(e, event && event.target && event.target.responseText); - return; - } - - callback(null, response, event); - } - }; - - if(options.xdomainrequest) { - OTHelpers.xdomainRequest(url, { method: 'GET' }, done); - } else { - var extendedHeaders = OTHelpers.extend({ - 'Accept': 'application/json' - }, options.headers || {}); - - OTHelpers.get(url, OTHelpers.extend(options || {}, { - headers: extendedHeaders - }), done); - } - - }; - -} -/*jshint browser:true, smarttabs:true*/ - -// tb_require('../helpers.js') -// tb_require('./environment.js') - - -// Log levels for OTLog.setLogLevel -var LOG_LEVEL_DEBUG = 5, - LOG_LEVEL_LOG = 4, - LOG_LEVEL_INFO = 3, - LOG_LEVEL_WARN = 2, - LOG_LEVEL_ERROR = 1, - LOG_LEVEL_NONE = 0; - - -// There is a single global log level for every component that uses -// the logs. -var _logLevel = LOG_LEVEL_NONE; - -var setLogLevel = function setLogLevel (level) { - _logLevel = typeof(level) === 'number' ? level : 0; - return _logLevel; -}; - - -OTHelpers.useLogHelpers = function(on){ - - // Log levels for OTLog.setLogLevel - on.DEBUG = LOG_LEVEL_DEBUG; - on.LOG = LOG_LEVEL_LOG; - on.INFO = LOG_LEVEL_INFO; - on.WARN = LOG_LEVEL_WARN; - on.ERROR = LOG_LEVEL_ERROR; - on.NONE = LOG_LEVEL_NONE; - - var _logs = [], - _canApplyConsole = true; - - try { - Function.prototype.bind.call(window.console.log, window.console); - } catch (err) { - _canApplyConsole = false; - } - - // Some objects can't be logged in the console, mostly these are certain - // types of native objects that are exposed to JS. This is only really a - // problem with IE, hence only the IE version does anything. - var makeLogArgumentsSafe = function(args) { return args; }; - - if (OTHelpers.env.name === 'IE') { - makeLogArgumentsSafe = function(args) { - return [toDebugString(prototypeSlice.apply(args))]; - }; - } - - // Generates a logging method for a particular method and log level. - // - // Attempts to handle the following cases: - // * the desired log method doesn't exist, call fallback (if available) instead - // * the console functionality isn't available because the developer tools (in IE) - // aren't open, call fallback (if available) - // * attempt to deal with weird IE hosted logging methods as best we can. - // - function generateLoggingMethod(method, level, fallback) { - return function() { - if (on.shouldLog(level)) { - var cons = window.console, - args = makeLogArgumentsSafe(arguments); - - // In IE, window.console may not exist if the developer tools aren't open - // This also means that cons and cons[method] can appear at any moment - // hence why we retest this every time. - if (cons && cons[method]) { - // the desired console method isn't a real object, which means - // that we can't use apply on it. We force it to be a real object - // using Function.bind, assuming that's available. - if (cons[method].apply || _canApplyConsole) { - if (!cons[method].apply) { - cons[method] = Function.prototype.bind.call(cons[method], cons); - } - - cons[method].apply(cons, args); - } - else { - // This isn't the same result as the above, but it's better - // than nothing. - cons[method](args); - } - } - else if (fallback) { - fallback.apply(on, args); - - // Skip appendToLogs, we delegate entirely to the fallback - return; - } - - appendToLogs(method, makeLogArgumentsSafe(arguments)); - } - }; - } - - on.log = generateLoggingMethod('log', on.LOG); - - // Generate debug, info, warn, and error logging methods, these all fallback to on.log - on.debug = generateLoggingMethod('debug', on.DEBUG, on.log); - on.info = generateLoggingMethod('info', on.INFO, on.log); - on.warn = generateLoggingMethod('warn', on.WARN, on.log); - on.error = generateLoggingMethod('error', on.ERROR, on.log); - - - on.setLogLevel = function(level) { - on.debug('TB.setLogLevel(' + _logLevel + ')'); - return setLogLevel(level); - }; - - on.getLogs = function() { - return _logs; - }; - - // Determine if the level is visible given the current logLevel. - on.shouldLog = function(level) { - return _logLevel >= level; - }; - - // Format the current time nicely for logging. Returns the current - // local time. - function formatDateStamp() { - var now = new Date(); - return now.toLocaleTimeString() + now.getMilliseconds(); - } - - function toJson(object) { - try { - return JSON.stringify(object); - } catch(e) { - return object.toString(); - } - } - - function toDebugString(object) { - var components = []; - - if (typeof(object) === 'undefined') { - // noop - } - else if (object === null) { - components.push('NULL'); - } - else if (OTHelpers.isArray(object)) { - for (var i=0; i 0) { - var args = timeouts.shift(), - fn = args.shift(); - - fn.apply(null, args); - } - } - }; - - // Ensure that we don't receive messages after unload - // Yes, this seems to really happen in IE sometimes, usually - // when iFrames are involved. - OTHelpers.on(window, 'unload', removeMessageHandler); - - if(window.addEventListener) { - window.addEventListener('message', handleMessage, true); - } else if(window.attachEvent) { - window.attachEvent('onmessage', handleMessage); - } - - _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) { - timeouts.push(prototypeSlice.call(arguments)); - window.postMessage(messageName, '*'); - }; - } - else { - _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) { - var args = prototypeSlice.call(arguments), - fn = args.shift(); - - setTimeout(function() { - fn.apply(null, args); - }, 0); - }; - } - - - // Calls the function +fn+ asynchronously with the current execution. - // This is most commonly used to execute something straight after - // the current function. - // - // Any arguments in addition to +fn+ will be passed to +fn+ when it's - // called. - // - // You would use this inplace of setTimeout(fn, 0) type constructs. callAsync - // is preferable as it executes in a much more predictable time window, - // unlike setTimeout which could execute anywhere from 2ms to several thousand - // depending on the browser/context. - // - // It does this using window.postMessage, although if postMessage won't - // work it will fallback to setTimeout. - // - OTHelpers.callAsync = _callAsync; - - - // Wraps +handler+ in a function that will execute it asynchronously - // so that it doesn't interfere with it's exceution context if it raises - // an exception. - OTHelpers.createAsyncHandler = function(handler) { - return function() { - var args = prototypeSlice.call(arguments); - - OTHelpers.callAsync(function() { - handler.apply(null, args); - }); - }; - }; - -})(); - /*global nodeEventing:true, browserEventing:true */ // tb_require('../../helpers.js') @@ -4534,1583 +6135,17 @@ OTHelpers.eventing = function(self, syncronous) { return self; }; -/*jshint browser:true, smarttabs:true */ - -// tb_require('../helpers.js') -// tb_require('./callbacks.js') -// tb_require('./dom_events.js') - -OTHelpers.createElement = function(nodeName, attributes, children, doc) { - var element = (doc || document).createElement(nodeName); - - if (attributes) { - for (var name in attributes) { - if (typeof(attributes[name]) === 'object') { - if (!element[name]) element[name] = {}; - - var subAttrs = attributes[name]; - for (var n in subAttrs) { - element[name][n] = subAttrs[n]; - } - } - else if (name === 'className') { - element.className = attributes[name]; - } - else { - element.setAttribute(name, attributes[name]); - } - } - } - - var setChildren = function(child) { - if(typeof child === 'string') { - element.innerHTML = element.innerHTML + child; - } else { - element.appendChild(child); - } - }; - - if($.isArray(children)) { - $.forEach(children, setChildren); - } else if(children) { - setChildren(children); - } - - return element; -}; - -OTHelpers.createButton = function(innerHTML, attributes, events) { - var button = $.createElement('button', attributes, innerHTML); - - if (events) { - for (var name in events) { - if (events.hasOwnProperty(name)) { - $.on(button, name, events[name]); - } - } - - button._boundEvents = events; - } - - return button; -}; -/*jshint browser:true, smarttabs:true */ - -// tb_require('../helpers.js') -// tb_require('./callbacks.js') - -// DOM helpers - -var firstElementChild; - -// This mess is for IE8 -if( typeof(document) !== 'undefined' && - document.createElement('div').firstElementChild !== void 0 ){ - firstElementChild = function firstElementChild (parentElement) { - return parentElement.firstElementChild; - }; -} -else { - firstElementChild = function firstElementChild (parentElement) { - var el = parentElement.firstChild; - - do { - if(el.nodeType===1){ - return el; - } - el = el.nextSibling; - } while(el); - - return null; - }; -} - - -ElementCollection.prototype.appendTo = function(parentElement) { - if (!parentElement) throw new Error('appendTo requires a DOMElement to append to.'); - - return this.forEach(function(child) { - parentElement.appendChild(child); - }); -}; - -ElementCollection.prototype.append = function() { - var parentElement = this.first; - if (!parentElement) return this; - - $.forEach(prototypeSlice.call(arguments), function(child) { - parentElement.appendChild(child); - }); - - return this; -}; - -ElementCollection.prototype.prepend = function() { - if (arguments.length === 0) return this; - - var parentElement = this.first, - elementsToPrepend; - - if (!parentElement) return this; - - elementsToPrepend = prototypeSlice.call(arguments); - - if (!firstElementChild(parentElement)) { - parentElement.appendChild(elementsToPrepend.shift()); - } - - $.forEach(elementsToPrepend, function(element) { - parentElement.insertBefore(element, firstElementChild(parentElement)); - }); - - return this; -}; - -ElementCollection.prototype.after = function(prevElement) { - if (!prevElement) throw new Error('after requires a DOMElement to insert after'); - - return this.forEach(function(element) { - if (element.parentElement) { - if (prevElement !== element.parentNode.lastChild) { - element.parentElement.insertBefore(element, prevElement); - } - else { - element.parentElement.appendChild(element); - } - } - }); -}; - -ElementCollection.prototype.before = function(nextElement) { - if (!nextElement) { - throw new Error('before requires a DOMElement to insert before'); - } - - return this.forEach(function(element) { - if (element.parentElement) { - element.parentElement.insertBefore(element, nextElement); - } - }); -}; - -ElementCollection.prototype.remove = function () { - return this.forEach(function(element) { - if (element.parentNode) { - element.parentNode.removeChild(element); - } - }); -}; - -ElementCollection.prototype.empty = function () { - return this.forEach(function(element) { - // elements is a "live" NodesList collection. Meaning that the collection - // itself will be mutated as we remove elements from the DOM. This means - // that "while there are still elements" is safer than "iterate over each - // element" as the collection length and the elements indices will be modified - // with each iteration. - while (element.firstChild) { - element.removeChild(element.firstChild); - } - }); -}; - - -// Detects when an element is not part of the document flow because -// it or one of it's ancesters has display:none. -ElementCollection.prototype.isDisplayNone = function() { - return this.some(function(element) { - if ( (element.offsetWidth === 0 || element.offsetHeight === 0) && - $(element).css('display') === 'none') return true; - - if (element.parentNode && element.parentNode.style) { - return $(element.parentNode).isDisplayNone(); - } - }); -}; - -ElementCollection.prototype.findElementWithDisplayNone = function(element) { - return $.findElementWithDisplayNone(element); -}; - - - -OTHelpers.isElementNode = function(node) { - return node && typeof node === 'object' && node.nodeType === 1; -}; - - -// @remove -OTHelpers.removeElement = function(element) { - $(element).remove(); -}; - -// @remove -OTHelpers.removeElementById = function(elementId) { - return $('#'+elementId).remove(); -}; - -// @remove -OTHelpers.removeElementsByType = function(parentElem, type) { - return $(type, parentElem).remove(); -}; - -// @remove -OTHelpers.emptyElement = function(element) { - return $(element).empty(); -}; - - - - - -// @remove -OTHelpers.isDisplayNone = function(element) { - return $(element).isDisplayNone(); -}; - -OTHelpers.findElementWithDisplayNone = function(element) { - if ( (element.offsetWidth === 0 || element.offsetHeight === 0) && - $.css(element, 'display') === 'none') return element; - - if (element.parentNode && element.parentNode.style) { - return $.findElementWithDisplayNone(element.parentNode); - } - - return null; -}; - - -/*jshint browser:true, smarttabs:true*/ - -// tb_require('../helpers.js') -// tb_require('./environment.js') -// tb_require('./dom.js') - -OTHelpers.Modal = function(options) { - - OTHelpers.eventing(this, true); - - var callback = arguments[arguments.length - 1]; - - if(!OTHelpers.isFunction(callback)) { - throw new Error('OTHelpers.Modal2 must be given a callback'); - } - - if(arguments.length < 2) { - options = {}; - } - - var domElement = document.createElement('iframe'); - - domElement.id = options.id || OTHelpers.uuid(); - domElement.style.position = 'absolute'; - domElement.style.position = 'fixed'; - domElement.style.height = '100%'; - domElement.style.width = '100%'; - domElement.style.top = '0px'; - domElement.style.left = '0px'; - domElement.style.right = '0px'; - domElement.style.bottom = '0px'; - domElement.style.zIndex = 1000; - domElement.style.border = '0'; - - try { - domElement.style.backgroundColor = 'rgba(0,0,0,0.2)'; - } catch (err) { - // Old IE browsers don't support rgba and we still want to show the upgrade message - // but we just make the background of the iframe completely transparent. - domElement.style.backgroundColor = 'transparent'; - domElement.setAttribute('allowTransparency', 'true'); - } - - domElement.scrolling = 'no'; - domElement.setAttribute('scrolling', 'no'); - - // This is necessary for IE, as it will not inherit it's doctype from - // the parent frame. - var frameContent = '' + - '' + - '' + - ''; - - var wrappedCallback = function() { - var doc = domElement.contentDocument || domElement.contentWindow.document; - - if (OTHelpers.env.iframeNeedsLoad) { - doc.body.style.backgroundColor = 'transparent'; - doc.body.style.border = 'none'; - - if (OTHelpers.env.name !== 'IE') { - // Skip this for IE as we use the bookmarklet workaround - // for THAT browser. - doc.open(); - doc.write(frameContent); - doc.close(); - } - } - - callback( - domElement.contentWindow, - doc - ); - }; - - document.body.appendChild(domElement); - - if(OTHelpers.env.iframeNeedsLoad) { - if (OTHelpers.env.name === 'IE') { - // This works around some issues with IE and document.write. - // Basically this works by slightly abusing the bookmarklet/scriptlet - // functionality that all browsers support. - domElement.contentWindow.contents = frameContent; - /*jshint scripturl:true*/ - domElement.src = 'javascript:window["contents"]'; - /*jshint scripturl:false*/ - } - - OTHelpers.on(domElement, 'load', wrappedCallback); - } else { - setTimeout(wrappedCallback, 0); - } - - this.close = function() { - OTHelpers.removeElement(domElement); - this.trigger('closed'); - this.element = domElement = null; - return this; - }; - - this.element = domElement; - -}; - -/* - * getComputedStyle from - * https://github.com/jonathantneal/Polyfills-for-IE8/blob/master/getComputedStyle.js - -// tb_require('../helpers.js') -// tb_require('./dom.js') - -/*jshint strict: false, eqnull: true, browser:true, smarttabs:true*/ - -(function() { - - /*jshint eqnull: true, browser: true */ - - - function getPixelSize(element, style, property, fontSize) { - var sizeWithSuffix = style[property], - size = parseFloat(sizeWithSuffix), - suffix = sizeWithSuffix.split(/\d/)[0], - rootSize; - - fontSize = fontSize != null ? - fontSize : /%|em/.test(suffix) && element.parentElement ? - getPixelSize(element.parentElement, element.parentElement.currentStyle, 'fontSize', null) : - 16; - rootSize = property === 'fontSize' ? - fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight; - - return (suffix === 'em') ? - size * fontSize : (suffix === 'in') ? - size * 96 : (suffix === 'pt') ? - size * 96 / 72 : (suffix === '%') ? - size / 100 * rootSize : size; - } - - function setShortStyleProperty(style, property) { - var - borderSuffix = property === 'border' ? 'Width' : '', - t = property + 'Top' + borderSuffix, - r = property + 'Right' + borderSuffix, - b = property + 'Bottom' + borderSuffix, - l = property + 'Left' + borderSuffix; - - style[property] = (style[t] === style[r] === style[b] === style[l] ? [style[t]] - : style[t] === style[b] && style[l] === style[r] ? [style[t], style[r]] - : style[l] === style[r] ? [style[t], style[r], style[b]] - : [style[t], style[r], style[b], style[l]]).join(' '); - } - - function CSSStyleDeclaration(element) { - var currentStyle = element.currentStyle, - style = this, - fontSize = getPixelSize(element, currentStyle, 'fontSize', null), - property; - - for (property in currentStyle) { - if (/width|height|margin.|padding.|border.+W/.test(property) && style[property] !== 'auto') { - style[property] = getPixelSize(element, currentStyle, property, fontSize) + 'px'; - } else if (property === 'styleFloat') { - /*jshint -W069 */ - style['float'] = currentStyle[property]; - } else { - style[property] = currentStyle[property]; - } - } - - setShortStyleProperty(style, 'margin'); - setShortStyleProperty(style, 'padding'); - setShortStyleProperty(style, 'border'); - - style.fontSize = fontSize + 'px'; - - return style; - } - - CSSStyleDeclaration.prototype = { - constructor: CSSStyleDeclaration, - getPropertyPriority: function () {}, - getPropertyValue: function ( prop ) { - return this[prop] || ''; - }, - item: function () {}, - removeProperty: function () {}, - setProperty: function () {}, - getPropertyCSSValue: function () {} - }; - - function getComputedStyle(element) { - return new CSSStyleDeclaration(element); - } - - - OTHelpers.getComputedStyle = function(element) { - if(element && - element.ownerDocument && - element.ownerDocument.defaultView && - element.ownerDocument.defaultView.getComputedStyle) { - return element.ownerDocument.defaultView.getComputedStyle(element); - } else { - return getComputedStyle(element); - } - }; - -})(); - -/*jshint browser:true, smarttabs:true */ - -// tb_require('../helpers.js') -// tb_require('./callbacks.js') -// tb_require('./dom.js') - -var observeStyleChanges = function observeStyleChanges (element, stylesToObserve, onChange) { - var oldStyles = {}; - - var getStyle = function getStyle(style) { - switch (style) { - case 'width': - return $(element).width(); - - case 'height': - return $(element).height(); - - default: - return $(element).css(style); - } - }; - - // get the inital values - $.forEach(stylesToObserve, function(style) { - oldStyles[style] = getStyle(style); - }); - - var observer = new MutationObserver(function(mutations) { - var changeSet = {}; - - $.forEach(mutations, function(mutation) { - if (mutation.attributeName !== 'style') return; - - var isHidden = $.isDisplayNone(element); - - $.forEach(stylesToObserve, function(style) { - if(isHidden && (style === 'width' || style === 'height')) return; - - var newValue = getStyle(style); - - if (newValue !== oldStyles[style]) { - changeSet[style] = [oldStyles[style], newValue]; - oldStyles[style] = newValue; - } - }); - }); - - if (!$.isEmpty(changeSet)) { - // Do this after so as to help avoid infinite loops of mutations. - $.callAsync(function() { - onChange.call(null, changeSet); - }); - } - }); - - observer.observe(element, { - attributes:true, - attributeFilter: ['style'], - childList:false, - characterData:false, - subtree:false - }); - - return observer; -}; - -var observeNodeOrChildNodeRemoval = function observeNodeOrChildNodeRemoval (element, onChange) { - var observer = new MutationObserver(function(mutations) { - var removedNodes = []; - - $.forEach(mutations, function(mutation) { - if (mutation.removedNodes.length) { - removedNodes = removedNodes.concat(prototypeSlice.call(mutation.removedNodes)); - } - }); - - if (removedNodes.length) { - // Do this after so as to help avoid infinite loops of mutations. - $.callAsync(function() { - onChange($(removedNodes)); - }); - } - }); - - observer.observe(element, { - attributes:false, - childList:true, - characterData:false, - subtree:true - }); - - return observer; -}; - -var observeSize = function (element, onChange) { - var previousSize = { - width: 0, - height: 0 - }; - - var interval = setInterval(function() { - var rect = element.getBoundingClientRect(); - if (previousSize.width !== rect.width || previousSize.height !== rect.height) { - onChange(rect, previousSize); - previousSize = { - width: rect.width, - height: rect.height - }; - } - }, 1000 / 5); - - return { - disconnect: function() { - clearInterval(interval); - } - }; -}; - -// Allows an +onChange+ callback to be triggered when specific style properties -// of +element+ are notified. The callback accepts a single parameter, which is -// a hash where the keys are the style property that changed and the values are -// an array containing the old and new values ([oldValue, newValue]). -// -// Width and Height changes while the element is display: none will not be -// fired until such time as the element becomes visible again. -// -// This function returns the MutationObserver itself. Once you no longer wish -// to observe the element you should call disconnect on the observer. -// -// Observing changes: -// // observe changings to the width and height of object -// dimensionsObserver = OTHelpers(object).observeStyleChanges(, -// ['width', 'height'], function(changeSet) { -// OT.debug("The new width and height are " + -// changeSet.width[1] + ',' + changeSet.height[1]); -// }); -// -// Cleaning up -// // stop observing changes -// dimensionsObserver.disconnect(); -// dimensionsObserver = null; -// -ElementCollection.prototype.observeStyleChanges = function(stylesToObserve, onChange) { - var observers = []; - - this.forEach(function(element) { - observers.push( - observeStyleChanges(element, stylesToObserve, onChange) - ); - }); - - return observers; -}; - -// trigger the +onChange+ callback whenever -// 1. +element+ is removed -// 2. or an immediate child of +element+ is removed. -// -// This function returns the MutationObserver itself. Once you no longer wish -// to observe the element you should call disconnect on the observer. -// -// Observing changes: -// // observe changings to the width and height of object -// nodeObserver = OTHelpers(object).observeNodeOrChildNodeRemoval(function(removedNodes) { -// OT.debug("Some child nodes were removed"); -// removedNodes.forEach(function(node) { -// OT.debug(node); -// }); -// }); -// -// Cleaning up -// // stop observing changes -// nodeObserver.disconnect(); -// nodeObserver = null; -// -ElementCollection.prototype.observeNodeOrChildNodeRemoval = function(onChange) { - var observers = []; - - this.forEach(function(element) { - observers.push( - observeNodeOrChildNodeRemoval(element, onChange) - ); - }); - - return observers; -}; - -// trigger the +onChange+ callback whenever the width or the height of the element changes -// -// Once you no longer wish to observe the element you should call disconnect on the observer. -// -// Observing changes: -// // observe changings to the width and height of object -// sizeObserver = OTHelpers(object).observeSize(function(newSize, previousSize) { -// OT.debug("The new width and height are " + -// newSize.width + ',' + newSize.height); -// }); -// -// Cleaning up -// // stop observing changes -// sizeObserver.disconnect(); -// sizeObserver = null; -// -ElementCollection.prototype.observeSize = function(onChange) { - var observers = []; - - this.forEach(function(element) { - observers.push( - observeSize(element, onChange) - ); - }); - - return observers; -}; - - -// @remove -OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) { - return $(element).observeStyleChanges(stylesToObserve, onChange)[0]; -}; - -// @remove -OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) { - return $(element).observeNodeOrChildNodeRemoval(onChange)[0]; -}; - -/*jshint browser:true, smarttabs:true */ - -// tb_require('../helpers.js') -// tb_require('./dom.js') -// tb_require('./capabilities.js') - -// Returns true if the client supports element.classList -OTHelpers.registerCapability('classList', function() { - return (typeof document !== 'undefined') && ('classList' in document.createElement('a')); -}); - - -function hasClass (element, className) { - if (!className) return false; - - if ($.hasCapabilities('classList')) { - return element.classList.contains(className); - } - - return element.className.indexOf(className) > -1; -} - -function toggleClasses (element, classNames) { - if (!classNames || classNames.length === 0) return; - - // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc - if (element.nodeType !== 1) { - return; - } - - var numClasses = classNames.length, - i = 0; - - if ($.hasCapabilities('classList')) { - for (; i 0) { - return element.offsetWidth + 'px'; - } - - return $(element).css('width'); - }, - - _height = function(element) { - if (element.offsetHeight > 0) { - return element.offsetHeight + 'px'; - } - - return $(element).css('height'); - }; - - ElementCollection.prototype.width = function (newWidth) { - if (newWidth) { - this.css('width', newWidth); - return this; - } - else { - if (this.isDisplayNone()) { - return this.makeVisibleAndYield(function(element) { - return _width(element); - })[0]; - } - else { - return _width(this.get(0)); - } - } - }; - - ElementCollection.prototype.height = function (newHeight) { - if (newHeight) { - this.css('height', newHeight); - return this; - } - else { - if (this.isDisplayNone()) { - // We can't get the height, probably since the element is hidden. - return this.makeVisibleAndYield(function(element) { - return _height(element); - })[0]; - } - else { - return _height(this.get(0)); - } - } - }; - - // @remove - OTHelpers.width = function(element, newWidth) { - var ret = $(element).width(newWidth); - return newWidth ? OTHelpers : ret; - }; - - // @remove - OTHelpers.height = function(element, newHeight) { - var ret = $(element).height(newHeight); - return newHeight ? OTHelpers : ret; - }; - -})(); - - -// CSS helpers helpers - -/*jshint browser:true, smarttabs:true*/ - -// tb_require('../helpers.js') -// tb_require('./dom.js') -// tb_require('./getcomputedstyle.js') - -(function() { - - var displayStateCache = {}, - defaultDisplays = {}; - - var defaultDisplayValueForElement = function (element) { - if (defaultDisplays[element.ownerDocument] && - defaultDisplays[element.ownerDocument][element.nodeName]) { - return defaultDisplays[element.ownerDocument][element.nodeName]; - } - - if (!defaultDisplays[element.ownerDocument]) defaultDisplays[element.ownerDocument] = {}; - - // We need to know what display value to use for this node. The easiest way - // is to actually create a node and read it out. - var testNode = element.ownerDocument.createElement(element.nodeName), - defaultDisplay; - - element.ownerDocument.body.appendChild(testNode); - defaultDisplay = defaultDisplays[element.ownerDocument][element.nodeName] = - $(testNode).css('display'); - - $(testNode).remove(); - testNode = null; - - return defaultDisplay; - }; - - var isHidden = function (element) { - var computedStyle = $.getComputedStyle(element); - return computedStyle.getPropertyValue('display') === 'none'; - }; - - var setCssProperties = function (element, hash) { - var style = element.style; - - for (var cssName in hash) { - if (hash.hasOwnProperty(cssName)) { - style[cssName] = hash[cssName]; - } - } - }; - - var setCssProperty = function (element, name, value) { - element.style[name] = value; - }; - - var getCssProperty = function (element, unnormalisedName) { - // Normalise vendor prefixes from the form MozTranform to -moz-transform - // except for ms extensions, which are weird... - - var name = unnormalisedName.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(), - computedStyle = $.getComputedStyle(element), - currentValue = computedStyle.getPropertyValue(name); - - if (currentValue === '') { - currentValue = element.style[name]; - } - - return currentValue; - }; - - var applyCSS = function(element, styles, callback) { - var oldStyles = {}, - name, - ret; - - // Backup the old styles - for (name in styles) { - if (styles.hasOwnProperty(name)) { - // We intentionally read out of style here, instead of using the css - // helper. This is because the css helper uses querySelector and we - // only want to pull values out of the style (domeElement.style) hash. - oldStyles[name] = element.style[name]; - - $(element).css(name, styles[name]); - } - } - - ret = callback(element); - - // Restore the old styles - for (name in styles) { - if (styles.hasOwnProperty(name)) { - $(element).css(name, oldStyles[name] || ''); - } - } - - return ret; - }; - - ElementCollection.prototype.show = function() { - return this.forEach(function(element) { - var display = element.style.display; - - if (display === '' || display === 'none') { - element.style.display = displayStateCache[element] || ''; - delete displayStateCache[element]; - } - - if (isHidden(element)) { - // It's still hidden so there's probably a stylesheet that declares this - // element as display:none; - displayStateCache[element] = 'none'; - - element.style.display = defaultDisplayValueForElement(element); - } - }); - }; - - ElementCollection.prototype.hide = function() { - return this.forEach(function(element) { - if (element.style.display === 'none') return; - - displayStateCache[element] = element.style.display; - element.style.display = 'none'; - }); - }; - - ElementCollection.prototype.css = function(nameOrHash, value) { - if (this.length === 0) return; - - if (typeof(nameOrHash) !== 'string') { - - return this.forEach(function(element) { - setCssProperties(element, nameOrHash); - }); - - } else if (value !== undefined) { - - return this.forEach(function(element) { - setCssProperty(element, nameOrHash, value); - }); - - } else { - return getCssProperty(this.first, nameOrHash, value); - } - }; - - // Apply +styles+ to +element+ while executing +callback+, restoring the previous - // styles after the callback executes. - ElementCollection.prototype.applyCSS = function (styles, callback) { - var results = []; - - this.forEach(function(element) { - results.push(applyCSS(element, styles, callback)); - }); - - return results; - }; - - - // Make +element+ visible while executing +callback+. - ElementCollection.prototype.makeVisibleAndYield = function (callback) { - var hiddenVisually = { - display: 'block', - visibility: 'hidden' - }, - results = []; - - this.forEach(function(element) { - // find whether it's the element or an ancestor that's display none and - // then apply to whichever it is - var targetElement = $.findElementWithDisplayNone(element); - if (!targetElement) { - results.push(void 0); - } - else { - results.push( - applyCSS(targetElement, hiddenVisually, callback) - ); - } - }); - - return results; - }; - - - // @remove - OTHelpers.show = function(element) { - return $(element).show(); - }; - - // @remove - OTHelpers.hide = function(element) { - return $(element).hide(); - }; - - // @remove - OTHelpers.css = function(element, nameOrHash, value) { - return $(element).css(nameOrHash, value); - }; - - // @remove - OTHelpers.applyCSS = function(element, styles, callback) { - return $(element).applyCSS(styles, callback); - }; - - // @remove - OTHelpers.makeVisibleAndYield = function(element, callback) { - return $(element).makeVisibleAndYield(callback); - }; - -})(); - -// tb_require('../helpers.js') - -/**@licence - * Copyright (c) 2010 Caolan McMahon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - **/ - - -(function() { - - OTHelpers.setImmediate = (function() { - if (typeof process === 'undefined' || !(process.nextTick)) { - if (typeof setImmediate === 'function') { - return function (fn) { - // not a direct alias for IE10 compatibility - setImmediate(fn); - }; - } - return function (fn) { - setTimeout(fn, 0); - }; - } - if (typeof setImmediate !== 'undefined') { - return setImmediate; - } - return process.nextTick; - })(); - - OTHelpers.iterator = function(tasks) { - var makeCallback = function (index) { - var fn = function () { - if (tasks.length) { - tasks[index].apply(null, arguments); - } - return fn.next(); - }; - fn.next = function () { - return (index < tasks.length - 1) ? makeCallback(index + 1) : null; - }; - return fn; - }; - return makeCallback(0); - }; - - OTHelpers.waterfall = function(array, done) { - done = done || function () {}; - if (array.constructor !== Array) { - return done(new Error('First argument to waterfall must be an array of functions')); - } - - if (!array.length) { - return done(); - } - - var next = function(iterator) { - return function (err) { - if (err) { - done.apply(null, arguments); - done = function () {}; - } else { - var args = prototypeSlice.call(arguments, 1), - nextFn = iterator.next(); - if (nextFn) { - args.push(next(nextFn)); - } else { - args.push(done); - } - OTHelpers.setImmediate(function() { - iterator.apply(null, args); - }); - } - }; - }; - - next(OTHelpers.iterator(array))(); - }; - -})(); - -/*jshint browser:true, smarttabs:true*/ - -// tb_require('../helpers.js') - -(function() { - - var requestAnimationFrame = window.requestAnimationFrame || - window.mozRequestAnimationFrame || - window.webkitRequestAnimationFrame || - window.msRequestAnimationFrame; - - if (requestAnimationFrame) { - requestAnimationFrame = OTHelpers.bind(requestAnimationFrame, window); - } - else { - var lastTime = 0; - var startTime = OTHelpers.now(); - - requestAnimationFrame = function(callback){ - var currTime = OTHelpers.now(); - var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = window.setTimeout(function() { callback(currTime - startTime); }, timeToCall); - lastTime = currTime + timeToCall; - return id; - }; - } - - OTHelpers.requestAnimationFrame = requestAnimationFrame; -})(); -/*jshint browser:true, smarttabs:true*/ - -// tb_require('../helpers.js') - -(function() { - - // Singleton interval - var logQueue = [], - queueRunning = false; - - OTHelpers.Analytics = function(loggingUrl, debugFn) { - - var endPoint = loggingUrl + '/logging/ClientEvent', - endPointQos = loggingUrl + '/logging/ClientQos', - - reportedErrors = {}, - - send = function(data, isQos, callback) { - OTHelpers.post((isQos ? endPointQos : endPoint) + '?_=' + OTHelpers.uuid.v4(), { - body: data, - xdomainrequest: ($.env.name === 'IE' && $.env.version < 10), - headers: { - 'Content-Type': 'application/json' - } - }, callback); - }, - - throttledPost = function() { - // Throttle logs so that they only happen 1 at a time - if (!queueRunning && logQueue.length > 0) { - queueRunning = true; - var curr = logQueue[0]; - - // Remove the current item and send the next log - var processNextItem = function() { - logQueue.shift(); - queueRunning = false; - throttledPost(); - }; - - if (curr) { - send(curr.data, curr.isQos, function(err) { - if (err) { - var debugMsg = 'Failed to send ClientEvent, moving on to the next item.'; - if (debugFn) { - debugFn(debugMsg); - } else { - console.log(debugMsg); - } - // There was an error, move onto the next item - } else { - curr.onComplete(); - } - setTimeout(processNextItem, 50); - }); - } - } - }, - - post = function(data, onComplete, isQos) { - logQueue.push({ - data: data, - onComplete: onComplete, - isQos: isQos - }); - - throttledPost(); - }, - - shouldThrottleError = function(code, type, partnerId) { - if (!partnerId) return false; - - var errKey = [partnerId, type, code].join('_'), - //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId); - msgLimit = 100; - if (msgLimit === null || msgLimit === undefined) return false; - return (reportedErrors[errKey] || 0) <= msgLimit; - }; - - // Log an error via ClientEvents. - // - // @param [String] code - // @param [String] type - // @param [String] message - // @param [Hash] details additional error details - // - // @param [Hash] options the options to log the client event with. - // @option options [String] action The name of the Event that we are logging. E.g. - // 'TokShowLoaded'. Required. - // @option options [String] variation Usually used for Split A/B testing, when you - // have multiple variations of the +_action+. - // @option options [String] payload The payload. Required. - // @option options [String] sessionId The active OpenTok session, if there is one - // @option options [String] connectionId The active OpenTok connectionId, if there is one - // @option options [String] partnerId - // @option options [String] guid ... - // @option options [String] streamId ... - // @option options [String] section ... - // @option options [String] clientVersion ... - // - // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner - // from the dynamic config for X) of each error type for each partner. Reports can be - // disabled/enabled globally or on a per partner basis (per partner settings - // take precedence) using exceptionLogging.enabled. - // - this.logError = function(code, type, message, details, options) { - if (!options) options = {}; - var partnerId = options.partnerId; - - if (shouldThrottleError(code, type, partnerId)) { - //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' + - // code + ' for partner ' + (partnerId || 'No Partner Id')); - return; - } - - var errKey = [partnerId, type, code].join('_'), - payload = details ? details : null; - - reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ? - reportedErrors[errKey] + 1 : 1; - this.logEvent(OTHelpers.extend(options, { - action: type + '.' + code, - payload: payload - }), false); - }; - - // Log a client event to the analytics backend. - // - // @example Logs a client event called 'foo' - // this.logEvent({ - // action: 'foo', - // payload: 'bar', - // sessionId: sessionId, - // connectionId: connectionId - // }, false) - // - // @param [Hash] data the data to log the client event with. - // @param [Boolean] qos Whether this is a QoS event. - // - this.logEvent = function(data, qos, throttle) { - if (!qos) qos = false; - - if (throttle && !isNaN(throttle)) { - if (Math.random() > throttle) { - return; - } - } - - // remove properties that have null values: - for (var key in data) { - if (data.hasOwnProperty(key) && data[key] === null) { - delete data[key]; - } - } - - // TODO: catch error when stringifying an object that has a circular reference - data = JSON.stringify(data); - - var onComplete = function() { - // OT.log('logged: ' + '{action: ' + data['action'] + ', variation: ' + data['variation'] - // + ', payload: ' + data['payload'] + '}'); - }; - - post(data, onComplete, qos); - }; - - // Log a client QOS to the analytics backend. - // Log a client QOS to the analytics backend. - // @option options [String] action The name of the Event that we are logging. - // E.g. 'TokShowLoaded'. Required. - // @option options [String] variation Usually used for Split A/B testing, when - // you have multiple variations of the +_action+. - // @option options [String] payload The payload. Required. - // @option options [String] sessionId The active OpenTok session, if there is one - // @option options [String] connectionId The active OpenTok connectionId, if there is one - // @option options [String] partnerId - // @option options [String] guid ... - // @option options [String] streamId ... - // @option options [String] section ... - // @option options [String] clientVersion ... - // - this.logQOS = function(options) { - this.logEvent(options, true); - }; - }; - -})(); - -// AJAX helpers - -/*jshint browser:true, smarttabs:true*/ - -// tb_require('../helpers.js') -// tb_require('./ajax/node.js') -// tb_require('./ajax/browser.js') - -OTHelpers.get = function(url, options, callback) { - var _options = OTHelpers.extend(options || {}, { - method: 'GET' - }); - OTHelpers.request(url, _options, callback); -}; - - -OTHelpers.post = function(url, options, callback) { - var _options = OTHelpers.extend(options || {}, { - method: 'POST' - }); - - if(_options.xdomainrequest) { - OTHelpers.xdomainRequest(url, _options, callback); - } else { - OTHelpers.request(url, _options, callback); - } -}; - })(window, window.OTHelpers); /** - * @license TB Plugin 0.4.0.10 6935b20 HEAD + * @license TB Plugin 0.4.0.12 6e40a4e v0.4.0.12-branch * http://www.tokbox.com/ * * Copyright (c) 2015 TokBox, Inc. * - * Date: July 13 05:38:06 2015 + * Date: October 28 03:45:04 2015 * */ @@ -6126,24 +6161,25 @@ if (scope.OTPlugin !== void 0) return; var $ = OTHelpers; +// Magic number to avoid plugin crashes through a settimeout call +window.EmpiricDelay = 3000; + // TB must exist first, otherwise we can't do anything // if (scope.OT === void 0) return; // Establish the environment that we're running in // Note: we don't currently support 64bit IE -var isSupported = $.env.name === 'Safari' || - ($.env.name === 'IE' && $.env.version >= 8 && +var isSupported = ($.env.name === 'IE' && $.env.version >= 8 && $.env.userAgent.indexOf('x64') === -1), pluginIsReady = false; - var OTPlugin = { - isSupported: function () { return isSupported; }, + isSupported: function() { return isSupported; }, isReady: function() { return pluginIsReady; }, meta: { - mimeType: 'application/x-opentokie,version=0.4.0.10', - activeXName: 'TokBox.OpenTokIE.0.4.0.10', - version: '0.4.0.10' + mimeType: 'application/x-opentokplugin,version=0.4.0.12', + activeXName: 'TokBox.OpenTokPlugin.0.4.0.12', + version: '0.4.0.12' }, useLoggingFrom: function(host) { @@ -6156,11 +6192,9 @@ var OTPlugin = { } }; - // Add logging methods $.useLogHelpers(OTPlugin); - scope.OTPlugin = OTPlugin; $.registerCapability('otplugin', function() { @@ -6170,7 +6204,7 @@ $.registerCapability('otplugin', function() { // If this client isn't supported we still make sure that OTPlugin is defined // and the basic API (isSupported() and isInstalled()) is created. if (!OTPlugin.isSupported()) { - OTPlugin.isInstalled = function isInstalled () { return false; }; + OTPlugin.isInstalled = function isInstalled() { return false; }; return; } @@ -6180,9 +6214,9 @@ if (!OTPlugin.isSupported()) { // Shims for various missing things from JS // Applied only after init is called to prevent unnecessary polution -var shim = function shim () { +var shim = function shim() { if (!Function.prototype.bind) { - Function.prototype.bind = function (oThis) { + Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); @@ -6190,8 +6224,8 @@ var shim = function shim () { var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, - FNOP = function () {}, - fBound = function () { + FNOP = function() {}, + fBound = function() { return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; @@ -6203,14 +6237,14 @@ var shim = function shim () { }; } - if(!Array.isArray) { - Array.isArray = function (vArg) { + if (!Array.isArray) { + Array.isArray = function(vArg) { return Object.prototype.toString.call(vArg) === '[object Array]'; }; } if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (searchElement, fromIndex) { + Array.prototype.indexOf = function(searchElement, fromIndex) { var i, pivot = (fromIndex) ? fromIndex : 0, length; @@ -6270,33 +6304,59 @@ var shim = function shim () { }; } }; + +// tb_require('./header.js') +// tb_require('./shims.js') + +/* exported TaskQueue */ + +var TaskQueue = function() { + var Proto = function TaskQueue() {}, + api = new Proto(), + tasks = []; + + api.add = function(fn, context) { + if (context) { + tasks.push($.bind(fn, context)); + } else { + tasks.push(fn); + } + }; + + api.runAll = function() { + var task; + while ((task = tasks.shift())) { + task(); + } + }; + + return api; +}; + // tb_require('./header.js') // tb_require('./shims.js') /* global curryCallAsync:true */ /* exported RumorSocket */ -var RumorSocket = function (plugin, server) { - var Proto = function RumorSocket () {}, +var RumorSocket = function(plugin, server) { + var Proto = function RumorSocket() {}, api = new Proto(), connected = false, rumorID, _onOpen, _onClose; - try { rumorID = plugin._.RumorInit(server, ''); - } - catch(e) { + } catch (e) { OTPlugin.error('Error creating the Rumor Socket: ', e.message); } - if(!rumorID) { + if (!rumorID) { throw new Error('Could not initialise OTPlugin rumor connection'); } - api.open = function() { connected = true; plugin._.RumorOpen(rumorID); @@ -6359,15 +6419,15 @@ var RumorSocket = function (plugin, server) { /* exported refCountBehaviour */ -var refCountBehaviour = function refCountBehaviour (api) { +var refCountBehaviour = function refCountBehaviour(api) { var _liveObjects = []; - api.addRef = function (ref) { + api.addRef = function(ref) { _liveObjects.push(ref); return api; }; - api.removeRef = function (ref) { + api.removeRef = function(ref) { if (_liveObjects.length === 0) return; var index = _liveObjects.indexOf(ref); @@ -6382,7 +6442,7 @@ var refCountBehaviour = function refCountBehaviour (api) { return api; }; - api.removeAllRefs = function () { + api.removeAllRefs = function() { while (_liveObjects.length) { _liveObjects.shift().destroy(); } @@ -6391,19 +6451,28 @@ var refCountBehaviour = function refCountBehaviour (api) { // tb_require('./header.js') // tb_require('./shims.js') +// tb_require('./task_queue.js') -/* global curryCallAsync:true */ +/* global curryCallAsync:true, TaskQueue:true */ /* exported pluginEventingBehaviour */ -var pluginEventingBehaviour = function pluginEventingBehaviour (api) { - var eventHandlers = {}; +var pluginEventingBehaviour = function pluginEventingBehaviour(api) { + var eventHandlers = {}, + pendingTasks = new TaskQueue(), + isReady = false; var onCustomEvent = function() { var args = Array.prototype.slice.call(arguments); api.emit(args.shift(), args); }; - api.on = function (name, callback, context) { + var devicesChanged = function() { + var args = Array.prototype.slice.call(arguments); + OTPlugin.debug(args); + api.emit('devicesChanged', args); + }; + + api.on = function(name, callback, context) { if (!eventHandlers.hasOwnProperty(name)) { eventHandlers[name] = []; } @@ -6412,7 +6481,7 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) { return api; }; - api.off = function (name, callback, context) { + api.off = function(name, callback, context) { if (!eventHandlers.hasOwnProperty(name) || eventHandlers[name].length === 0) { return; @@ -6426,8 +6495,8 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) { return api; }; - api.once = function (name, callback, context) { - var fn = function () { + api.once = function(name, callback, context) { + var fn = function() { api.off(name, fn); return callback.apply(context, arguments); }; @@ -6436,7 +6505,7 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) { return api; }; - api.emit = function (name, args) { + api.emit = function(name, args) { $.callAsync(function() { if (!eventHandlers.hasOwnProperty(name) && eventHandlers[name].length) { return; @@ -6450,7 +6519,20 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) { return api; }; - var onReady = function onReady (readyCallback) { + // Calling this will bind a listener to the devicesChanged events that + // the plugin emits and then rebroadcast them. + api.listenForDeviceChanges = function() { + if (!isReady) { + pendingTasks.add(api.listenForDeviceChanges, api); + return; + } + + setTimeout(function() { + api._.registerXCallback('devicesChanged', devicesChanged); + }, window.EmpiricDelay); + }; + + var onReady = function onReady(readyCallback) { if (api._.on) { // If the plugin supports custom events we'll use them api._.on(-1, { @@ -6458,17 +6540,25 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) { }); } + var internalReadyCallback = function() { + // It's not safe to do most plugin operations until the plugin + // is ready for us to do so. We use isReady as a guard in + isReady = true; + + pendingTasks.runAll(); + readyCallback.call(api); + }; + // Only the main plugin has an initialise method if (api._.initialise) { - api.on('ready', curryCallAsync(readyCallback)); + api.on('ready', curryCallAsync(internalReadyCallback)); api._.initialise(); - } - else { - readyCallback.call(api); + } else { + internalReadyCallback.call(api); } }; - return function (completion) { + return function(completion) { onReady(function(err) { if (err) { OTPlugin.error('Error while starting up plugin ' + api.uuid + ': ' + err); @@ -6481,6 +6571,7 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) { }); }; }; + // tb_require('./header.js') // tb_require('./shims.js') // tb_require('./ref_count_behaviour.js') @@ -6493,7 +6584,7 @@ var PROXY_LOAD_TIMEOUT = 5000; var objectTimeouts = {}; -var curryCallAsync = function curryCallAsync (fn) { +var curryCallAsync = function curryCallAsync(fn) { return function() { var args = Array.prototype.slice.call(arguments); args.unshift(fn); @@ -6501,7 +6592,7 @@ var curryCallAsync = function curryCallAsync (fn) { }; }; -var clearGlobalCallback = function clearGlobalCallback (callbackId) { +var clearGlobalCallback = function clearGlobalCallback(callbackId) { if (!callbackId) return; if (objectTimeouts[callbackId]) { @@ -6518,7 +6609,7 @@ var clearGlobalCallback = function clearGlobalCallback (callbackId) { } }; -var waitOnGlobalCallback = function waitOnGlobalCallback (callbackId, completion) { +var waitOnGlobalCallback = function waitOnGlobalCallback(callbackId, completion) { objectTimeouts[callbackId] = setTimeout(function() { clearGlobalCallback(callbackId); completion('The object timed out while loading.'); @@ -6533,11 +6624,11 @@ var waitOnGlobalCallback = function waitOnGlobalCallback (callbackId, completion }; }; -var generateCallbackID = function generateCallbackID () { +var generateCallbackID = function generateCallbackID() { return 'OTPlugin_loaded_' + $.uuid().replace(/\-+/g, ''); }; -var generateObjectHtml = function generateObjectHtml (callbackId, options) { +var generateObjectHtml = function generateObjectHtml(callbackId, options) { options = options || {}; var objBits = [], @@ -6553,7 +6644,6 @@ var generateObjectHtml = function generateObjectHtml (callbackId, options) { onload: callbackId }; - if (options.isVisible !== true) { attrs.push('visibility="hidden"'); } @@ -6570,9 +6660,7 @@ var generateObjectHtml = function generateObjectHtml (callbackId, options) { return objBits.join(''); }; - - -var createObject = function createObject (callbackId, options, completion) { +var createObject = function createObject(callbackId, options, completion) { options = options || {}; var html = generateObjectHtml(callbackId, options), @@ -6587,9 +6675,8 @@ var createObject = function createObject (callbackId, options, completion) { // } // else { - doc.body.insertAdjacentHTML('beforeend', html); - var object = doc.body.querySelector('#'+callbackId+'_obj'); + var object = doc.body.querySelector('#' + callbackId + '_obj'); // object.setAttribute('type', options.mimeType); @@ -6598,7 +6685,7 @@ var createObject = function createObject (callbackId, options, completion) { }; // Reference counted wrapper for a plugin object -var createPluginProxy = function (options, completion) { +var createPluginProxy = function(options, completion) { var Proto = function PluginProxy() {}, api = new Proto(), waitForReadySignal = pluginEventingBehaviour(api); @@ -6608,20 +6695,18 @@ var createPluginProxy = function (options, completion) { // Assign +plugin+ to this object and setup all the public // accessors that relate to the DOM Object. // - var setPlugin = function setPlugin (plugin) { + var setPlugin = function setPlugin(plugin) { if (plugin) { api._ = plugin; api.parentElement = plugin.parentElement; api.$ = $(plugin); - } - else { + } else { api._ = null; api.parentElement = null; api.$ = $(); } }; - api.uuid = generateCallbackID(); api.isValid = function() { @@ -6637,11 +6722,12 @@ var createPluginProxy = function (options, completion) { api.emit('destroy'); }; - + api.enumerateDevices = function(completion) { + api._.enumerateDevices(completion); + }; /// Initialise - // The next statement creates the raw plugin object accessor on the Proxy. // This is null until we actually have created the Object. setPlugin(null); @@ -6678,22 +6764,18 @@ var createPluginProxy = function (options, completion) { return api; }; - - - // Specialisation for the MediaCapturer API surface -var makeMediaCapturerProxy = function makeMediaCapturerProxy (api) { - +var makeMediaCapturerProxy = function makeMediaCapturerProxy(api) { api.selectSources = function() { return api._.selectSources.apply(api._, arguments); }; + api.listenForDeviceChanges(); return api; }; - // Specialisation for the MediaPeer API surface -var makeMediaPeerProxy = function makeMediaPeerProxy (api) { +var makeMediaPeerProxy = function makeMediaPeerProxy(api) { api.setStream = function(stream, completion) { api._.setStream(stream); @@ -6710,15 +6792,13 @@ var makeMediaPeerProxy = function makeMediaPeerProxy (api) { if (api._.videoWidth > 0) { // This fires a little too soon. setTimeout(completion, 200); - } - else { + } else { setTimeout(verifyStream, 500); } }; setTimeout(verifyStream, 500); - } - else { + } else { // TODO Investigate whether there is a good way to detect // when the audio is ready. Does it even matter? @@ -6733,15 +6813,14 @@ var makeMediaPeerProxy = function makeMediaPeerProxy (api) { return api; }; - // tb_require('./header.js') // tb_require('./shims.js') // tb_require('./proxy.js') /* exported VideoContainer */ -var VideoContainer = function (plugin, stream) { - var Proto = function VideoContainer () {}, +var VideoContainer = function(plugin, stream) { + var Proto = function VideoContainer() {}, api = new Proto(); api.domElement = plugin._; @@ -6750,7 +6829,7 @@ var VideoContainer = function (plugin, stream) { plugin.addRef(api); - api.appendTo = function (parentDomElement) { + api.appendTo = function(parentDomElement) { if (parentDomElement && plugin._.parentNode !== parentDomElement) { OTPlugin.debug('VideoContainer appendTo', parentDomElement); parentDomElement.appendChild(plugin._); @@ -6758,7 +6837,7 @@ var VideoContainer = function (plugin, stream) { } }; - api.show = function (completion) { + api.show = function(completion) { OTPlugin.debug('VideoContainer show'); plugin._.removeAttribute('width'); plugin._.removeAttribute('height'); @@ -6773,7 +6852,7 @@ var VideoContainer = function (plugin, stream) { return api; }; - api.width = function (newWidth) { + api.width = function(newWidth) { if (newWidth !== void 0) { OTPlugin.debug('VideoContainer set width to ' + newWidth); plugin._.setAttribute('width', newWidth); @@ -6782,7 +6861,7 @@ var VideoContainer = function (plugin, stream) { return plugin._.getAttribute('width'); }; - api.height = function (newHeight) { + api.height = function(newHeight) { if (newHeight !== void 0) { OTPlugin.debug('VideoContainer set height to ' + newHeight); plugin._.setAttribute('height', newHeight); @@ -6791,31 +6870,30 @@ var VideoContainer = function (plugin, stream) { return plugin._.getAttribute('height'); }; - api.volume = function (newVolume) { + api.volume = function(newVolume) { if (newVolume !== void 0) { // TODO OTPlugin.debug('VideoContainer setVolume not implemented: called with ' + newVolume); - } - else { + } else { OTPlugin.debug('VideoContainer getVolume not implemented'); } return 0.5; }; - api.getImgData = function () { + api.getImgData = function() { return plugin._.getImgData('image/png'); }; - api.videoWidth = function () { + api.videoWidth = function() { return plugin._.videoWidth; }; - api.videoHeight = function () { + api.videoHeight = function() { return plugin._.videoHeight; }; - api.destroy = function () { + api.destroy = function() { plugin._.setStream(null); plugin.removeRef(api); }; @@ -6829,7 +6907,7 @@ var VideoContainer = function (plugin, stream) { /* exported RTCStatsReport */ -var RTCStatsReport = function RTCStatsReport (reports) { +var RTCStatsReport = function RTCStatsReport(reports) { for (var id in reports) { if (reports.hasOwnProperty(id)) { this[id] = reports[id]; @@ -6837,7 +6915,7 @@ var RTCStatsReport = function RTCStatsReport (reports) { } }; -RTCStatsReport.prototype.forEach = function (callback, context) { +RTCStatsReport.prototype.forEach = function(callback, context) { for (var id in this) { if (this.hasOwnProperty(id)) { callback.call(context, this[id]); @@ -6852,21 +6930,18 @@ RTCStatsReport.prototype.forEach = function (callback, context) { /* global createPluginProxy:true, makeMediaPeerProxy:true, makeMediaCapturerProxy:true */ /* exported PluginProxies */ - var PluginProxies = (function() { - var Proto = function PluginProxies () {}, + var Proto = function PluginProxies() {}, api = new Proto(), proxies = {}; - /// Private API // This is called whenever a Proxy's destroy event fires. - var cleanupProxyOnDestroy = function cleanupProxyOnDestroy (object) { + var cleanupProxyOnDestroy = function cleanupProxyOnDestroy(object) { if (api.mediaCapturer && api.mediaCapturer.id === object.id) { api.mediaCapturer = null; - } - else if (proxies.hasOwnProperty(object.id)) { + } else if (proxies.hasOwnProperty(object.id)) { delete proxies[object.id]; } @@ -6875,14 +6950,12 @@ var PluginProxies = (function() { } }; - /// Public API - // Public accessor for the MediaCapturer api.mediaCapturer = null; - api.removeAll = function removeAll () { + api.removeAll = function removeAll() { for (var id in proxies) { if (proxies.hasOwnProperty(id)) { proxies[id].destroy(); @@ -6892,7 +6965,7 @@ var PluginProxies = (function() { if (api.mediaCapturer) api.mediaCapturer.destroy(); }; - api.create = function create (options, completion) { + api.create = function create(options, completion) { var proxy = createPluginProxy(options, completion); proxies[proxy.uuid] = proxy; @@ -6905,7 +6978,7 @@ var PluginProxies = (function() { return proxy; }; - api.createMediaPeer = function createMediaPeer (options, completion) { + api.createMediaPeer = function createMediaPeer(options, completion) { if ($.isFunction(options)) { completion = options; options = {}; @@ -6928,7 +7001,7 @@ var PluginProxies = (function() { makeMediaPeerProxy(mediaPeer); }; - api.createMediaCapturer = function createMediaCapturer (completion) { + api.createMediaCapturer = function createMediaCapturer(completion) { if (api.mediaCapturer) { completion.call(OTPlugin, void 0, api.mediaCapturer); return api; @@ -6959,8 +7032,8 @@ var PluginProxies = (function() { // Our RTCPeerConnection shim, it should look like a normal PeerConection // from the outside, but it actually delegates to our plugin. // -var PeerConnection = function (iceServers, options, plugin, ready) { - var Proto = function PeerConnection () {}, +var PeerConnection = function(iceServers, options, plugin, ready) { + var Proto = function PeerConnection() {}, api = new Proto(), id = $.uuid(), hasLocalDescription = false, @@ -6980,21 +7053,20 @@ var PeerConnection = function (iceServers, options, plugin, ready) { iceconnectionstatechange: [] }; - var onAddIceCandidate = function onAddIceCandidate () {/* success */}, + var onAddIceCandidate = function onAddIceCandidate() {/* success */}, - onAddIceCandidateFailed = function onAddIceCandidateFailed (err) { + onAddIceCandidateFailed = function onAddIceCandidateFailed(err) { OTPlugin.error('Failed to process candidate'); OTPlugin.error(err); }, - processPendingCandidates = function processPendingCandidates () { - for (var i=0; i version2; - } - - // The versions have multiple components (i.e. 0.10.30) and - // must be compared piecewise. - // Note: I'm ignoring the case where one version has multiple - // components and the other doesn't. - var v1 = version1.split('.'), - v2 = version2.split('.'), - versionLength = (v1.length > v2.length ? v2 : v1).length; - - - for (var i = 0; i < versionLength; ++i) { - if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) { - return true; - } - } - - // Special case, v1 has extra components but the initial components - // were identical, we assume this means newer but it might also mean - // that someone changed versioning systems. - if (i < v1.length) { - return true; - } - - return false; - }; - - - // Work out the full mimeType (including the currently installed version) - // of the installer. - var findMimeTypeAndVersion = function findMimeTypeAndVersion () { - - if (updaterMimeType !== void 0) { - return updaterMimeType; - } - - var activeXControlId = 'TokBox.otiePluginInstaller', - installPluginName = 'otiePluginInstaller', - unversionedMimeType = 'application/x-otieplugininstaller', - plugin = navigator.plugins[activeXControlId] || navigator.plugins[installPluginName]; - - installedVersion = -1; - - if (plugin) { - // Look through the supported mime-types for the version - // There should only be one mime-type in our use case, and - // if there's more than one they should all have the same - // version. - var numMimeTypes = plugin.length, - extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') + - ',version=([0-9a-zA-Z-_.]+)', 'i'), - mimeType, - bits; - - - for (var i=0; i version2; + } + + // The versions have multiple components (i.e. 0.10.30) and + // must be compared piecewise. + // Note: I'm ignoring the case where one version has multiple + // components and the other doesn't. + var v1 = version1.split('.'), + v2 = version2.split('.'), + versionLength = (v1.length > v2.length ? v2 : v1).length; + + for (var i = 0; i < versionLength; ++i) { + if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) { + return true; + } + } + + // Special case, v1 has extra components but the initial components + // were identical, we assume this means newer but it might also mean + // that someone changed versioning systems. + if (i < v1.length) { + return true; + } + + return false; + }; + + // Work out the full mimeType (including the currently installed version) + // of the installer. + var findMimeTypeAndVersion = function findMimeTypeAndVersion() { + + if (updaterMimeType !== void 0) { + return updaterMimeType; + } + + var activeXControlId = 'TokBox.OpenTokPluginInstaller', + installPluginName = 'OpenTokPluginInstaller', + unversionedMimeType = 'application/x-opentokplugininstaller', + plugin = navigator.plugins[activeXControlId] || navigator.plugins[installPluginName]; + + installedVersion = -1; + + if (plugin) { + // Look through the supported mime-types for the version + // There should only be one mime-type in our use case, and + // if there's more than one they should all have the same + // version. + var numMimeTypes = plugin.length, + extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') + + ',version=([0-9a-zA-Z-_.]+)', 'i'), + mimeType, + bits; + + for (var i = 0; i < numMimeTypes; ++i) { + mimeType = plugin[i]; + + // Look through the supported mimeTypes and find + // the newest one. + if (mimeType && mimeType.enabledPlugin && + (mimeType.enabledPlugin.name === plugin.name) && + mimeType.type.indexOf(unversionedMimeType) !== -1) { + + bits = extractVersion.exec(mimeType.type); + + if (bits !== null && versionGreaterThan(bits[1], installedVersion)) { + installedVersion = bits[1]; + } + } + } + } else if ($.env.name === 'IE') { + // This may mean that the installer plugin is not installed. + // Although it could also mean that we're on IE 9 and below, + // which does not support navigator.plugins. Fallback to + // using 'ActiveXObject' instead. + try { + plugin = new ActiveXObject(activeXControlId); + installedVersion = plugin.getMasterVersion(); + } catch (e) {} + } + + updaterMimeType = installedVersion !== -1 ? + unversionedMimeType + ',version=' + installedVersion : + null; + }; + + var getInstallerMimeType = function getInstallerMimeType() { + if (updaterMimeType === void 0) { + findMimeTypeAndVersion(); + } + + return updaterMimeType; + }; + + var getInstalledVersion = function getInstalledVersion() { + if (installedVersion === void 0) { + findMimeTypeAndVersion(); + } + + return installedVersion; + }; + + // Version 0.4.0.4 autoupdate was broken. We want to prompt + // for install on 0.4.0.4 or earlier. We're also including + // earlier versions just in case. Version 0.4.0.10 also + // had a broken updater, we'll treat that version the same + // way. + var hasBrokenUpdater = function() { + var _broken = getInstalledVersion() === '0.4.0.9' || + !versionGreaterThan(getInstalledVersion(), '0.4.0.4'); + + hasBrokenUpdater = function() { return _broken; }; + return _broken; + }; + + AutoUpdater = function() { + var plugin; + + var getControllerCurry = function getControllerFirstCurry(fn) { + return function() { + if (plugin) { + return fn(void 0, arguments); + } + + PluginProxies.create({ + mimeType: getInstallerMimeType(), + isVisible: false, + windowless: false + }, function(err, p) { + plugin = p; + + if (err) { + OTPlugin.error('Error while loading the AutoUpdater: ' + err); + return; + } + + return fn.apply(void 0, arguments); + }); + }; + }; + + // Returns true if the version of the plugin installed on this computer + // does not match the one expected by this version of OTPlugin. + this.isOutOfDate = function() { + return versionGreaterThan(OTPlugin.meta.version, getInstalledVersion()); + }; + + this.autoUpdate = getControllerCurry(function() { + var modal = OT.Dialogs.Plugin.updateInProgress(), + analytics = new OT.Analytics(), + payload = { + ieVersion: $.env.version, + pluginOldVersion: OTPlugin.installedVersion(), + pluginNewVersion: OTPlugin.version() + }; + + var success = curryCallAsync(function() { + analytics.logEvent({ + action: 'OTPluginAutoUpdate', + variation: 'Success', + partnerId: OT.APIKEY, + payload: JSON.stringify(payload) + }); + + plugin.destroy(); + + modal.close(); + OT.Dialogs.Plugin.updateComplete().on({ + reload: function() { + window.location.reload(); + } + }); + }), + + error = curryCallAsync(function(errorCode, errorMessage, systemErrorCode) { + payload.errorCode = errorCode; + payload.systemErrorCode = systemErrorCode; + + analytics.logEvent({ + action: 'OTPluginAutoUpdate', + variation: 'Failure', + partnerId: OT.APIKEY, + payload: JSON.stringify(payload) + }); + + plugin.destroy(); + + modal.close(); + var updateMessage = errorMessage + ' (' + errorCode + + '). Please restart your browser and try again.'; + + modal = OT.Dialogs.Plugin.updateComplete(updateMessage).on({ + reload: function() { + modal.close(); + } + }); + + OTPlugin.error('autoUpdate failed: ' + errorMessage + ' (' + errorCode + + '). Please restart your browser and try again.'); + // TODO log client event + }), + + progress = curryCallAsync(function(progress) { + modal.setUpdateProgress(progress.toFixed()); + // modalBody.innerHTML = 'Updating...' + progress.toFixed() + '%'; + }); + + plugin._.updatePlugin(OTPlugin.pathToInstaller(), success, error, progress); + }); + + this.destroy = function() { + if (plugin) plugin.destroy(); + }; + + // Refresh the plugin list so that we'll hopefully detect newer versions + if (navigator.plugins) { + navigator.plugins.refresh(false); + } + }; + + AutoUpdater.get = function(completion) { + if (!autoUpdaterController) { + if (!this.isinstalled()) { + completion.call(null, 'Plugin was not installed'); + return; + } + + autoUpdaterController = new AutoUpdater(); + } + + completion.call(null, void 0, autoUpdaterController); + }; + + AutoUpdater.isinstalled = function() { + return getInstallerMimeType() !== null && !hasBrokenUpdater(); + }; + + AutoUpdater.installedVersion = function() { + return getInstalledVersion(); + }; + +})(); + +// tb_require('./header.js') +// tb_require('./shims.js') +// tb_require('./proxy.js') +// tb_require('./auto_updater.js') + +/* global PluginProxies */ +/* exported MediaDevices */ + +// Exposes a enumerateDevices method and emits a devicechange event +// +// http://w3c.github.io/mediacapture-main/#idl-def-MediaDevices +// +var MediaDevices = function() { + var Proto = function MediaDevices() {}, + api = new Proto(); + + api.enumerateDevices = function enumerateDevices(completion) { + OTPlugin.ready(function(error) { + if (error) { + completion(error); + } else { + PluginProxies.mediaCapturer.enumerateDevices(completion); + } + }); + }; + + api.addListener = function addListener(fn, context) { + OTPlugin.ready(function(error) { + if (error) { + // No error message here, ready failing would have + // created a bunch elsewhere + return; + } + + PluginProxies.mediaCapturer.on('devicesChanged', fn, context); + }); + }; + + api.removeListener = function removeListener(fn, context) { + if (OTPlugin.isReady()) { + PluginProxies.mediaCapturer.off('devicesChanged', fn, context); + } + }; + + return api; +}; + // tb_require('./header.js') // tb_require('./shims.js') // tb_require('./proxy.js') @@ -7854,32 +7895,112 @@ var createFrame = function createFrame (bodyContent, callbackId, callback) { // tb_require('./media_stream.js') // tb_require('./video_container.js') // tb_require('./rumor.js') + +/* global scope, shim, pluginIsReady:true, PluginProxies, AutoUpdater */ +/* export registerReadyListener, notifyReadyListeners, onDomReady */ + +var readyCallbacks = [], + + // This stores the error from the load attempt. We use + // this if registerReadyListener gets called after a load + // attempt fails. + loadError; + +var // jshint -W098 + destroy = function destroy() { + PluginProxies.removeAll(); + }, + + registerReadyListener = function registerReadyListener(callback) { + if (!$.isFunction(callback)) { + OTPlugin.warn('registerReadyListener was called with something that was not a function.'); + return; + } + + if (OTPlugin.isReady()) { + callback.call(OTPlugin, loadError); + } else { + readyCallbacks.push(callback); + } + }, + + notifyReadyListeners = function notifyReadyListeners() { + var callback; + + while ((callback = readyCallbacks.pop()) && $.isFunction(callback)) { + callback.call(OTPlugin, loadError); + } + }, + + onDomReady = function onDomReady() { + AutoUpdater.get(function(err, updater) { + if (err) { + loadError = 'Error while loading the AutoUpdater: ' + err; + notifyReadyListeners(); + return; + } + + // If the plugin is out of date then we kick off the + // auto update process and then bail out. + if (updater.isOutOfDate()) { + updater.autoUpdate(); + return; + } + + // Inject the controller object into the page, wait for it to load or timeout... + PluginProxies.createMediaCapturer(function(err) { + loadError = err; + + if (!loadError && (!PluginProxies.mediaCapturer || + !PluginProxies.mediaCapturer.isValid())) { + loadError = 'The TB Plugin failed to load properly'; + } + + pluginIsReady = true; + notifyReadyListeners(); + + $.onDOMUnload(destroy); + }); + }); + }; + +// tb_require('./header.js') +// tb_require('./shims.js') +// tb_require('./proxy.js') +// tb_require('./auto_updater.js') +// tb_require('./media_constraints.js') +// tb_require('./peer_connection.js') +// tb_require('./media_stream.js') +// tb_require('./media_devices.js') +// tb_require('./video_container.js') +// tb_require('./rumor.js') // tb_require('./lifecycle.js') /* global AutoUpdater, RumorSocket, MediaConstraints, PeerConnection, MediaStream, registerReadyListener, - PluginProxies */ + PluginProxies, + MediaDevices */ -OTPlugin.isInstalled = function isInstalled () { +OTPlugin.isInstalled = function isInstalled() { if (!this.isSupported()) return false; return AutoUpdater.isinstalled(); }; -OTPlugin.version = function version () { +OTPlugin.version = function version() { return OTPlugin.meta.version; }; -OTPlugin.installedVersion = function installedVersion () { +OTPlugin.installedVersion = function installedVersion() { return AutoUpdater.installedVersion(); }; // Returns a URI to the OTPlugin installer that is paired with // this version of OTPlugin.js. -OTPlugin.pathToInstaller = function pathToInstaller () { +OTPlugin.pathToInstaller = function pathToInstaller() { return 'https://s3.amazonaws.com/otplugin.tokbox.com/v' + - OTPlugin.meta.version + '/otiePluginMain.msi'; + OTPlugin.meta.version + '/OpenTokPluginMain.msi'; }; // Trigger +callback+ when the plugin is ready @@ -7887,19 +8008,8 @@ OTPlugin.pathToInstaller = function pathToInstaller () { // Most of the public API cannot be called until // the plugin is ready. // -OTPlugin.ready = function ready (callback) { - if (OTPlugin.isReady()) { - var err; - - if (!PluginProxies.mediaCapturer || !PluginProxies.mediaCapturer.isValid()) { - err = 'The TB Plugin failed to load properly'; - } - - callback.call(OTPlugin, err); - } - else { - registerReadyListener(callback); - } +OTPlugin.ready = function ready(callback) { + registerReadyListener(callback); }; // Helper function for OTPlugin.getUserMedia @@ -7919,13 +8029,12 @@ var _getUserMedia = function _getUserMedia(mediaConstraints, success, error) { // Equivalent to: window.getUserMedia(constraints, success, error); // // Except that the constraints won't be identical -OTPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) { +OTPlugin.getUserMedia = function getUserMedia(userConstraints, success, error) { var constraints = new MediaConstraints(userConstraints); if (constraints.screenSharing) { _getUserMedia(constraints, success, error); - } - else { + } else { var sources = []; if (constraints.hasVideo) sources.push('video'); if (constraints.hasAudio) sources.push('audio'); @@ -7946,9 +8055,19 @@ OTPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) } }; +OTPlugin.enumerateDevices = function(completion) { + OTPlugin.ready(function(error) { + if (error) { + completion(error); + } else { + PluginProxies.mediaCapturer.enumerateDevices(completion); + } + }); +}; + OTPlugin.initRumorSocket = function(messagingURL, completion) { OTPlugin.ready(function(error) { - if(error) { + if (error) { completion(error); } else { completion(null, new RumorSocket(PluginProxies.mediaCapturer, messagingURL)); @@ -7956,14 +8075,13 @@ OTPlugin.initRumorSocket = function(messagingURL, completion) { }); }; - // Equivalent to: var pc = new window.RTCPeerConnection(iceServers, options); // // Except that it is async and takes a completion handler -OTPlugin.initPeerConnection = function initPeerConnection (iceServers, - options, - localStream, - completion) { +OTPlugin.initPeerConnection = function initPeerConnection(iceServers, + options, + localStream, + completion) { var gotPeerObject = function(err, plugin) { if (err) { @@ -7991,25 +8109,27 @@ OTPlugin.initPeerConnection = function initPeerConnection (iceServers, // a new one. if (localStream && localStream._.plugin) { gotPeerObject(null, localStream._.plugin); - } - else { + } else { PluginProxies.createMediaPeer(gotPeerObject); } }; // A RTCSessionDescription like object exposed for native WebRTC compatability -OTPlugin.RTCSessionDescription = function RTCSessionDescription (options) { +OTPlugin.RTCSessionDescription = function RTCSessionDescription(options) { this.type = options.type; this.sdp = options.sdp; }; // A RTCIceCandidate like object exposed for native WebRTC compatability -OTPlugin.RTCIceCandidate = function RTCIceCandidate (options) { +OTPlugin.RTCIceCandidate = function RTCIceCandidate(options) { this.sdpMid = options.sdpMid; this.sdpMLineIndex = parseInt(options.sdpMLineIndex, 10); this.candidate = options.candidate; }; +OTPlugin.mediaDevices = new MediaDevices(); + + // tb_require('./api.js') /* global shim, onDomReady */ @@ -8024,6 +8144,72 @@ $.onDOMLoad(onDomReady); +/* exported _setCertificates */ + +var _setCertificates = function(pcConfig, completion) { + if ( + OT.$.env.name === 'Firefox' && + window.mozRTCPeerConnection && + window.mozRTCPeerConnection.generateCertificate + ) { + window.mozRTCPeerConnection.generateCertificate({ + name: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]) + }).catch(function(err) { + completion(err); + }).then(function(cert) { + pcConfig.certificates = [cert]; + completion(undefined, pcConfig); + }); + } else { + OT.$.callAsync(function() { + completion(undefined, pcConfig); + }); + } +}; + +/* exported videoContentResizesMixin */ + +var videoContentResizesMixin = function(self, domElement) { + + var width = domElement.videoWidth, + height = domElement.videoHeight, + stopped = true; + + function actor() { + if (stopped) { + return; + } + if (width !== domElement.videoWidth || height !== domElement.videoHeight) { + self.trigger('videoDimensionsChanged', + { width: width, height: height }, + { width: domElement.videoWidth, height: domElement.videoHeight } + ); + width = domElement.videoWidth; + height = domElement.videoHeight; + } + waiter(); + } + + function waiter() { + self.whenTimeIncrements(function() { + window.requestAnimationFrame(actor); + }); + } + + self.startObservingSize = function() { + stopped = false; + waiter(); + }; + + self.stopObservingSize = function() { + stopped = true; + }; + +}; + /* jshint ignore:start */ !(function(window, OT) { /* jshint ignore:end */ @@ -8045,9 +8231,9 @@ if (location.protocol === 'file:') { var OT = window.OT || {}; // Define the APIKEY this is a global parameter which should not change -OT.APIKEY = (function(){ +OT.APIKEY = (function() { // Script embed - var scriptSrc = (function(){ + var scriptSrc = (function() { var s = document.getElementsByTagName('script'); s = s[s.length - 1]; s = s.getAttribute('src') || s.src; @@ -8058,15 +8244,14 @@ OT.APIKEY = (function(){ return m ? m[1] : ''; })(); - if (!window.OT) window.OT = OT; if (!window.TB) window.TB = OT; // tb_require('../js/ot.js') OT.properties = { - version: 'v2.5.2', // The current version (eg. v2.0.4) (This is replaced by gradle) - build: 'f4508e1', // The current build hash (This is replaced by gradle) + version: 'v2.6.8', // The current version (eg. v2.0.4) (This is replaced by gradle) + build: 'fae7901', // The current build hash (This is replaced by gradle) // Whether or not to turn on debug logging by default debug: 'false', @@ -8097,12 +8282,11 @@ OT.properties = { apiURLSSL: 'https://anvil.opentok.com', minimumVersion: { - firefox: parseFloat('29'), - chrome: parseFloat('34') + firefox: parseFloat('37'), + chrome: parseFloat('39') } }; - // tb_require('../ot.js') // tb_require('../../conf/properties.js'); @@ -8110,7 +8294,6 @@ OT.properties = { trailing: true, browser: true, smarttabs:true */ /* global OT */ - // Mount OTHelpers on OT.$ OT.$ = window.OTHelpers; @@ -8125,7 +8308,7 @@ OT.$.defineGetters = function(self, getters, enumerable) { if (enumerable === void 0) enumerable = false; for (var key in getters) { - if(!getters.hasOwnProperty(key)) { + if (!getters.hasOwnProperty(key)) { continue; } @@ -8169,7 +8352,6 @@ OT.setLogLevel = function(level) { var debugTrue = OT.properties.debug === 'true' || OT.properties.debug === true; OT.setLogLevel(debugTrue ? OT.DEBUG : OT.ERROR); - // Patch the userAgent to ref OTPlugin, if it's installed. if (OTPlugin && OTPlugin.isInstalled()) { OT.$.env.userAgent += '; OTPlugin ' + OTPlugin.version(); @@ -8178,7 +8360,6 @@ if (OTPlugin && OTPlugin.isInstalled()) { // @todo remove this OT.$.userAgent = function() { return OT.$.env.userAgent; }; - /** * Sets the API log level. *

@@ -8253,6 +8434,122 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; }; // tb_require('../../../helpers/helpers.js') +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +// Rumor Messaging for JS +// +// https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork +// +// @todo Rumor { +// Add error codes for all the error cases +// Add Dependability commands +// } + +OT.Rumor = { + MessageType: { + // This is used to subscribe to address/addresses. The address/addresses the + // client specifies here is registered on the server. Once any message is sent to + // that address/addresses, the client receives that message. + SUBSCRIBE: 0, + + // This is used to unsubscribe to address / addresses. Once the client unsubscribe + // to an address, it will stop getting messages sent to that address. + UNSUBSCRIBE: 1, + + // This is used to send messages to arbitrary address/ addresses. Messages can be + // anything and Rumor will not care about what is included. + MESSAGE: 2, + + // This will be the first message that the client sends to the server. It includes + // the uniqueId for that client connection and a disconnect_notify address that will + // be notified once the client disconnects. + CONNECT: 3, + + // This will be the message used by the server to notify an address that a + // client disconnected. + DISCONNECT: 4, + + //Enhancements to support Keepalives + PING: 7, + PONG: 8, + STATUS: 9 + } +}; + +// tb_require('../../../helpers/helpers.js') +// tb_require('./rumor.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT, OTPlugin */ + +!(function() { + + OT.Rumor.PluginSocket = function(messagingURL, events) { + + var webSocket, + state = 'initializing'; + + OTPlugin.initRumorSocket(messagingURL, OT.$.bind(function(err, rumorSocket) { + if (err) { + state = 'closed'; + events.onClose({ code: 4999 }); + } else if (state === 'initializing') { + webSocket = rumorSocket; + + webSocket.onOpen(function() { + state = 'open'; + events.onOpen(); + }); + webSocket.onClose(function(error) { + state = 'closed'; /* CLOSED */ + events.onClose({ code: error }); + }); + webSocket.onError(function(error) { + state = 'closed'; /* CLOSED */ + events.onError(error); + /* native websockets seem to do this, so should we */ + events.onClose({ code: error }); + }); + + webSocket.onMessage(function(type, addresses, headers, payload) { + var msg = new OT.Rumor.Message(type, addresses, headers, payload); + events.onMessage(msg); + }); + + webSocket.open(); + } else { + this.close(); + } + }, this)); + + this.close = function() { + if (state === 'initializing' || state === 'closed') { + state = 'closed'; + return; + } + + webSocket.close(1000, ''); + }; + + this.send = function(msg) { + if (state === 'open') { + webSocket.send(msg); + } + }; + + this.isClosed = function() { + return state === 'closed'; + }; + + }; + +}(this)); + +// tb_require('../../../helpers/helpers.js') + // https://code.google.com/p/stringencoding/ // An implementation of http://encoding.spec.whatwg.org/#api // Modified by TokBox to remove all encoding support except for utf-8 @@ -8279,11 +8576,11 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; }; (function(global) { 'use strict'; - if(OT.$.env && OT.$.env.name === 'IE' && OT.$.env.version < 10) { + if (OT.$.env && OT.$.env.name === 'IE' && OT.$.env.version < 10) { return; // IE 8 doesn't do websockets. No websockets, no encoding. } - if ( (global.TextEncoder !== void 0) && (global.TextDecoder !== void 0)) { + if ((global.TextEncoder !== void 0) && (global.TextDecoder !== void 0)) { // defer to the native ones return; } @@ -8313,7 +8610,6 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; }; return Math.floor(n / d); } - // // Implementation of Encoding specification // http://dvcs.w3.org/hg/encoding/raw-file/tip/Overview.html @@ -9308,122 +9604,6 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; }; }(this)); -// tb_require('../../../helpers/helpers.js') - -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global OT */ - -// Rumor Messaging for JS -// -// https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork -// -// @todo Rumor { -// Add error codes for all the error cases -// Add Dependability commands -// } - -OT.Rumor = { - MessageType: { - // This is used to subscribe to address/addresses. The address/addresses the - // client specifies here is registered on the server. Once any message is sent to - // that address/addresses, the client receives that message. - SUBSCRIBE: 0, - - // This is used to unsubscribe to address / addresses. Once the client unsubscribe - // to an address, it will stop getting messages sent to that address. - UNSUBSCRIBE: 1, - - // This is used to send messages to arbitrary address/ addresses. Messages can be - // anything and Rumor will not care about what is included. - MESSAGE: 2, - - // This will be the first message that the client sends to the server. It includes - // the uniqueId for that client connection and a disconnect_notify address that will - // be notified once the client disconnects. - CONNECT: 3, - - // This will be the message used by the server to notify an address that a - // client disconnected. - DISCONNECT: 4, - - //Enhancements to support Keepalives - PING: 7, - PONG: 8, - STATUS: 9 - } -}; - -// tb_require('../../../helpers/helpers.js') -// tb_require('./rumor.js') - -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global OT, OTPlugin */ - -!(function() { - - OT.Rumor.PluginSocket = function(messagingURL, events) { - - var webSocket, - state = 'initializing'; - - OTPlugin.initRumorSocket(messagingURL, OT.$.bind(function(err, rumorSocket) { - if(err) { - state = 'closed'; - events.onClose({ code: 4999 }); - } else if(state === 'initializing') { - webSocket = rumorSocket; - - webSocket.onOpen(function() { - state = 'open'; - events.onOpen(); - }); - webSocket.onClose(function(error) { - state = 'closed'; /* CLOSED */ - events.onClose({ code: error }); - }); - webSocket.onError(function(error) { - state = 'closed'; /* CLOSED */ - events.onError(error); - /* native websockets seem to do this, so should we */ - events.onClose({ code: error }); - }); - - webSocket.onMessage(function(type, addresses, headers, payload) { - var msg = new OT.Rumor.Message(type, addresses, headers, payload); - events.onMessage(msg); - }); - - webSocket.open(); - } else { - this.close(); - } - }, this)); - - this.close = function() { - if(state === 'initializing' || state === 'closed') { - state = 'closed'; - return; - } - - webSocket.close(1000, ''); - }; - - this.send = function(msg) { - if(state === 'open') { - webSocket.send(msg); - } - }; - - this.isClosed = function() { - return state === 'closed'; - }; - - }; - -}(this)); - // tb_require('../../../helpers/helpers.js') // tb_require('./encoding.js') // tb_require('./rumor.js') @@ -9438,7 +9618,7 @@ OT.Rumor = { // * https://tbwiki.tokbox.com/index.php/Rumor_Message_Packet // * https://tbwiki.tokbox.com/index.php/Rumor_Protocol // -OT.Rumor.Message = function (type, toAddress, headers, data) { +OT.Rumor.Message = function(type, toAddress, headers, data) { this.type = type; this.toAddress = toAddress; this.headers = headers; @@ -9449,7 +9629,7 @@ OT.Rumor.Message = function (type, toAddress, headers, data) { this.isError = !(this.status && this.status[0] === '2'); }; -OT.Rumor.Message.prototype.serialize = function () { +OT.Rumor.Message.prototype.serialize = function() { var offset = 8, cBuf = 7, address = [], @@ -9478,7 +9658,7 @@ OT.Rumor.Message.prototype.serialize = function () { i = 0; for (var key in this.headers) { - if(!this.headers.hasOwnProperty(key)) { + if (!this.headers.hasOwnProperty(key)) { continue; } headerKey.push(new TextEncoder('utf-8').encode(key)); @@ -9560,9 +9740,9 @@ function toArrayBuffer(buffer) { return ab; } -OT.Rumor.Message.deserialize = function (buffer) { +OT.Rumor.Message.deserialize = function(buffer) { - if(typeof Buffer !== 'undefined' && + if (typeof Buffer !== 'undefined' && Buffer.isBuffer(buffer)) { buffer = toArrayBuffer(buffer); } @@ -9620,8 +9800,7 @@ OT.Rumor.Message.deserialize = function (buffer) { return new OT.Rumor.Message(type, address, headers, data); }; - -OT.Rumor.Message.Connect = function (uniqueId, notifyDisconnectAddress) { +OT.Rumor.Message.Connect = function(uniqueId, notifyDisconnectAddress) { var headers = { uniqueId: uniqueId, notifyDisconnectAddress: notifyDisconnectAddress @@ -9630,7 +9809,7 @@ OT.Rumor.Message.Connect = function (uniqueId, notifyDisconnectAddress) { return new OT.Rumor.Message(OT.Rumor.MessageType.CONNECT, [], headers, ''); }; -OT.Rumor.Message.Disconnect = function () { +OT.Rumor.Message.Disconnect = function() { return new OT.Rumor.Message(OT.Rumor.MessageType.DISCONNECT, [], {}, ''); }; @@ -9643,7 +9822,7 @@ OT.Rumor.Message.Unsubscribe = function(topics) { }; OT.Rumor.Message.Publish = function(topics, message, headers) { - return new OT.Rumor.Message(OT.Rumor.MessageType.MESSAGE, topics, headers||{}, message || ''); + return new OT.Rumor.Message(OT.Rumor.MessageType.MESSAGE, topics, headers || {}, message || ''); }; // This message is used to implement keepalives on the persistent @@ -9697,7 +9876,7 @@ OT.Rumor.Message.Ping = function() { // the socket. If the buffer doesn't drain after a certain length of time // we give up and close it anyway. disconnectWhenSendBufferIsDrained = - function disconnectWhenSendBufferIsDrained (bufferDrainRetries) { + function disconnectWhenSendBufferIsDrained(bufferDrainRetries) { if (!webSocket) return; if (bufferDrainRetries === void 0) bufferDrainRetries = 0; @@ -9706,7 +9885,7 @@ OT.Rumor.Message.Ping = function() { if (webSocket.bufferedAmount > 0 && (bufferDrainRetries + 1) <= BUFFER_DRAIN_MAX_RETRIES) { bufferDrainTimeout = setTimeout(disconnectWhenSendBufferIsDrained, - BUFFER_DRAIN_INTERVAL, bufferDrainRetries+1); + BUFFER_DRAIN_INTERVAL, bufferDrainRetries + 1); } else { close(); @@ -9735,7 +9914,6 @@ OT.Rumor.Message.Ping = function() { }; - }(this)); // tb_require('../../../helpers/helpers.js') @@ -9752,9 +9930,10 @@ var WEB_SOCKET_KEEP_ALIVE_INTERVAL = 9000, // Magic Connectivity Timeout Constant: We wait 9*the keep alive interval, // on the third keep alive we trigger the timeout if we haven't received the // server pong. - WEB_SOCKET_CONNECTIVITY_TIMEOUT = 5*WEB_SOCKET_KEEP_ALIVE_INTERVAL - 100, + WEB_SOCKET_CONNECTIVITY_TIMEOUT = 5 * WEB_SOCKET_KEEP_ALIVE_INTERVAL - 100, - wsCloseErrorCodes; + wsCloseErrorCodes, + errorMap; // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Close_codes // http://docs.oracle.com/javaee/7/api/javax/websocket/CloseReason.CloseCodes.html @@ -9783,8 +9962,22 @@ wsCloseErrorCodes = { 'unexpected condition that prevented it from fulfilling the request', // .... codes in the 4000-4999 range are available for use by applications. - 4001: 'Connectivity loss was detected as it was too long since the socket received the ' + - 'last PONG message' + 4001: 'Connectivity loss was detected as it was too long since the socket received the ' + + 'last PONG message', + 4010: 'Timed out while waiting for the Rumor socket to connect.', + 4020: 'Error code unavailable.', + 4030: 'Exception was thrown during Rumor connection, possibly because of a blocked port.' +}; + +errorMap = { + CLOSE_PROTOCOL_ERROR: 1002, + CLOSE_UNSUPPORTED: 1003, + CLOSE_TOO_LARGE: 1004, + CLOSE_NO_STATUS: 1005, + CLOSE_ABNORMAL: 1006, + CLOSE_TIMEOUT: 4010, + CLOSE_FALLBACK_CODE: 4020, + CLOSE_CONNECT_EXCEPTION: 4030 }; OT.Rumor.SocketError = function(code, message) { @@ -9796,6 +9989,8 @@ OT.Rumor.SocketError = function(code, message) { // so in normal operation you would omit it. OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) { + var _this = this; + var states = ['disconnected', 'error', 'connected', 'connecting', 'disconnecting'], webSocket, id, @@ -9808,7 +10003,6 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) lastMessageTimestamp, // The timestamp of the last message received keepAliveTimer; // Timer for the connectivity checks - //// Private API var stateChanged = function(newState) { switch (newState) { @@ -9817,7 +10011,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) webSocket = null; if (onClose) { var error; - if(hasLostConnectivity()) { + if (hasLostConnectivity()) { error = new Error(wsCloseErrorCodes[4001]); error.code = 4001; } @@ -9829,60 +10023,63 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) setState = OT.$.statable(this, states, 'disconnected', stateChanged), - validateCallback = function validateCallback (name, callback) { - if (callback === null || !OT.$.isFunction(callback) ) { + validateCallback = function validateCallback(name, callback) { + if (callback === null || !OT.$.isFunction(callback)) { throw new Error('The Rumor.Socket ' + name + ' callback must be a valid function or null'); } }, - error = OT.$.bind(function error (errorMessage) { - OT.error('Rumor.Socket: ' + errorMessage); + raiseError = function raiseError(code, extraDetail) { + code = code || errorMap.CLOSE_FALLBACK_CODE; - var socketError = new OT.Rumor.SocketError(null, errorMessage || 'Unknown Socket Error'); + var messageFromCode = wsCloseErrorCodes[code] || 'No message available from code.'; + var message = messageFromCode + (extraDetail ? ' ' + extraDetail : ''); + + OT.error('Rumor.Socket: ' + message); + + var socketError = new OT.Rumor.SocketError(code, message); if (connectTimeout) clearTimeout(connectTimeout); setState('error'); - if (this.previousState === 'connecting' && connectCallback) { + if (_this.previousState === 'connecting' && connectCallback) { connectCallback(socketError, void 0); connectCallback = null; } if (onError) onError(socketError); - }, this), + }, - hasLostConnectivity = function hasLostConnectivity () { + hasLostConnectivity = function hasLostConnectivity() { if (!lastMessageTimestamp) return false; return (OT.$.now() - lastMessageTimestamp) >= WEB_SOCKET_CONNECTIVITY_TIMEOUT; }, - sendKeepAlive = OT.$.bind(function() { - if (!this.is('connected')) return; + sendKeepAlive = function() { + if (!_this.is('connected')) return; - if ( hasLostConnectivity() ) { + if (hasLostConnectivity()) { webSocketDisconnected({code: 4001}); - } - else { + } else { webSocket.send(OT.Rumor.Message.Ping()); keepAliveTimer = setTimeout(sendKeepAlive, WEB_SOCKET_KEEP_ALIVE_INTERVAL); } - }, this), + }, // Returns true if we think the DOM has been unloaded // It detects this by looking for the OT global, which // should always exist until the DOM is cleaned up. - isDOMUnloaded = function isDOMUnloaded () { + isDOMUnloaded = function isDOMUnloaded() { return !window.OT; }; - //// Private Event Handlers - var webSocketConnected = OT.$.bind(function webSocketConnected () { + var webSocketConnected = function webSocketConnected() { if (connectTimeout) clearTimeout(connectTimeout); - if (this.isNot('connecting')) { + if (_this.isNot('connecting')) { OT.debug('webSocketConnected reached in state other than connecting'); return; } @@ -9905,21 +10102,21 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) lastMessageTimestamp = OT.$.now(); sendKeepAlive(); }, WEB_SOCKET_KEEP_ALIVE_INTERVAL); - }, this), + }, - webSocketConnectTimedOut = function webSocketConnectTimedOut () { + webSocketConnectTimedOut = function webSocketConnectTimedOut() { var webSocketWas = webSocket; - error('Timed out while waiting for the Rumor socket to connect.'); + raiseError(errorMap.CLOSE_TIMEOUT); // This will prevent a socket eventually connecting // But call it _after_ the error just in case any of // the callbacks fire synchronously, breaking the error // handling code. try { webSocketWas.close(); - } catch(x) {} + } catch (x) {} }, - webSocketError = function webSocketError () {}, + webSocketError = function webSocketError() {}, // var errorMessage = 'Unknown Socket Error'; // @fixme We MUST be able to do better than this! @@ -9930,7 +10127,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) // until the socket is closed. // error(errorMessage); - webSocketDisconnected = OT.$.bind(function webSocketDisconnected (closeEvent) { + webSocketDisconnected = function webSocketDisconnected(closeEvent) { if (connectTimeout) clearTimeout(connectTimeout); if (keepAliveTimer) clearTimeout(keepAliveTimer); @@ -9943,18 +10140,20 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) } if (closeEvent.code !== 1000 && closeEvent.code !== 1001) { - var reason = closeEvent.reason || closeEvent.message; - if (!reason && wsCloseErrorCodes.hasOwnProperty(closeEvent.code)) { - reason = wsCloseErrorCodes[closeEvent.code]; + if (closeEvent.code) { + raiseError(closeEvent.code); + } else { + raiseError( + errorMap.CLOSE_FALLBACK_CODE, + closeEvent.reason || closeEvent.message + ); } - - error('Rumor Socket Disconnected: ' + reason); } - if (this.isNot('error')) setState('disconnected'); - }, this), + if (_this.isNot('error')) setState('disconnected'); + }, - webSocketReceivedMessage = function webSocketReceivedMessage (msg) { + webSocketReceivedMessage = function webSocketReceivedMessage(msg) { lastMessageTimestamp = OT.$.now(); if (onMessage) { @@ -9964,10 +10163,9 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) } }; - //// Public API - this.publish = function (topics, message, headers) { + this.publish = function(topics, message, headers) { webSocket.send(OT.Rumor.Message.Publish(topics, message, headers)); }; @@ -9979,7 +10177,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) webSocket.send(OT.Rumor.Message.Unsubscribe(topics)); }; - this.connect = function (connectionId, complete) { + this.connect = function(connectionId, complete) { if (this.is('connecting', 'connected')) { complete(new OT.Rumor.SocketError(null, 'Rumor.Socket cannot connect when it is already connecting or connected.')); @@ -10001,19 +10199,17 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) }; try { - if(typeof TheWebSocket !== 'undefined') { + if (typeof TheWebSocket !== 'undefined') { webSocket = new OT.Rumor.NativeSocket(TheWebSocket, messagingURL, events); } else { webSocket = new OT.Rumor.PluginSocket(messagingURL, events); } connectTimeout = setTimeout(webSocketConnectTimedOut, OT.Rumor.Socket.CONNECT_TIMEOUT); - } - catch(e) { + } catch (e) { OT.error(e); - // @todo add an actual error message - error('Could not connect to the Rumor socket, possibly because of a blocked port.'); + raiseError(errorMap.CLOSE_CONNECT_EXCEPTION); } }; @@ -10028,8 +10224,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) if (webSocket.isClosed()) { if (this.isNot('error')) setState('disconnected'); - } - else { + } else { if (this.is('connected')) { // Look! We are nice to the rumor server ;-) webSocket.send(OT.Rumor.Message.Disconnect()); @@ -10040,8 +10235,6 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) } }; - - OT.$.defineProperties(this, { id: { get: function() { return id; } @@ -10088,7 +10281,6 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) // The number of ms to wait for the websocket to connect OT.Rumor.Socket.CONNECT_TIMEOUT = 15000; - // tb_require('../../../helpers/helpers.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, @@ -10197,11 +10389,10 @@ OT.Raptor = { trailing: true, browser: true, smarttabs:true */ /* global OT */ -OT.Raptor.serializeMessage = function (message) { +OT.Raptor.serializeMessage = function(message) { return JSON.stringify(message); }; - // Deserialising a Raptor message mainly means doing a JSON.parse on it. // We do decorate the final message with a few extra helper properies though. // @@ -10211,7 +10402,7 @@ OT.Raptor.serializeMessage = function (message) { // * signature: typeName and actionName combined. This is mainly for debugging. E.g. A type // of 102 and an action of 101 would result in a signature of "STREAM:CREATE" // -OT.Raptor.deserializeMessage = function (msg) { +OT.Raptor.deserializeMessage = function(msg) { if (msg.length === 0) return {}; var message = JSON.parse(msg), @@ -10219,28 +10410,27 @@ OT.Raptor.deserializeMessage = function (msg) { // Remove the Raptor protocol version bits.shift(); - if (bits[bits.length-1] === '') bits.pop(); + if (bits[bits.length - 1] === '') bits.pop(); message.params = {}; - for (var i=0, numBits=bits.length ; i 6) { - message.resource = bits[bits.length-4] + '_' + bits[bits.length-2]; + if (bits[bits.length - 2] === 'channel' && bits.length > 6) { + message.resource = bits[bits.length - 4] + '_' + bits[bits.length - 2]; } else { - message.resource = bits[bits.length-2]; + message.resource = bits[bits.length - 2]; } - } - else { - if (bits[bits.length-1] === 'channel' && bits.length > 5) { - message.resource = bits[bits.length-3] + '_' + bits[bits.length-1]; + } else { + if (bits[bits.length - 1] === 'channel' && bits.length > 5) { + message.resource = bits[bits.length - 3] + '_' + bits[bits.length - 1]; } else { - message.resource = bits[bits.length-1]; + message.resource = bits[bits.length - 1]; } } @@ -10248,7 +10438,7 @@ OT.Raptor.deserializeMessage = function (msg) { return message; }; -OT.Raptor.unboxFromRumorMessage = function (rumorMessage) { +OT.Raptor.unboxFromRumorMessage = function(rumorMessage) { var message = OT.Raptor.deserializeMessage(rumorMessage.data); message.transactionId = rumorMessage.transactionId; message.fromAddress = rumorMessage.headers['X-TB-FROM-ADDRESS']; @@ -10256,7 +10446,7 @@ OT.Raptor.unboxFromRumorMessage = function (rumorMessage) { return message; }; -OT.Raptor.parseIceServers = function (message) { +OT.Raptor.parseIceServers = function(message) { try { return JSON.parse(message.data).content.iceServers; } catch (e) { @@ -10266,8 +10456,7 @@ OT.Raptor.parseIceServers = function (message) { OT.Raptor.Message = {}; - -OT.Raptor.Message.offer = function (uri, offerSdp) { +OT.Raptor.Message.offer = function(uri, offerSdp) { return OT.Raptor.serializeMessage({ method: 'offer', uri: uri, @@ -10277,10 +10466,9 @@ OT.Raptor.Message.offer = function (uri, offerSdp) { }); }; - OT.Raptor.Message.connections = {}; -OT.Raptor.Message.connections.create = function (apiKey, sessionId, connectionId) { +OT.Raptor.Message.connections.create = function(apiKey, sessionId, connectionId) { return OT.Raptor.serializeMessage({ method: 'create', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId, @@ -10290,7 +10478,7 @@ OT.Raptor.Message.connections.create = function (apiKey, sessionId, connectionId }); }; -OT.Raptor.Message.connections.destroy = function (apiKey, sessionId, connectionId) { +OT.Raptor.Message.connections.destroy = function(apiKey, sessionId, connectionId) { return OT.Raptor.serializeMessage({ method: 'delete', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId, @@ -10298,10 +10486,9 @@ OT.Raptor.Message.connections.destroy = function (apiKey, sessionId, connectionI }); }; - OT.Raptor.Message.sessions = {}; -OT.Raptor.Message.sessions.get = function (apiKey, sessionId) { +OT.Raptor.Message.sessions.get = function(apiKey, sessionId) { return OT.Raptor.serializeMessage({ method: 'read', uri: '/v2/partner/' + apiKey + '/session/' + sessionId, @@ -10309,10 +10496,9 @@ OT.Raptor.Message.sessions.get = function (apiKey, sessionId) { }); }; - OT.Raptor.Message.streams = {}; -OT.Raptor.Message.streams.get = function (apiKey, sessionId, streamId) { +OT.Raptor.Message.streams.get = function(apiKey, sessionId, streamId) { return OT.Raptor.serializeMessage({ method: 'read', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, @@ -10341,7 +10527,7 @@ OT.Raptor.Message.streams.channelFromOTChannel = function(channel) { return raptorChannel; }; -OT.Raptor.Message.streams.create = function (apiKey, sessionId, streamId, name, +OT.Raptor.Message.streams.create = function(apiKey, sessionId, streamId, name, audioFallbackEnabled, channels, minBitrate, maxBitrate) { var messageContent = { id: streamId, @@ -10362,7 +10548,7 @@ OT.Raptor.Message.streams.create = function (apiKey, sessionId, streamId, name, }); }; -OT.Raptor.Message.streams.destroy = function (apiKey, sessionId, streamId) { +OT.Raptor.Message.streams.destroy = function(apiKey, sessionId, streamId) { return OT.Raptor.serializeMessage({ method: 'delete', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, @@ -10370,8 +10556,7 @@ OT.Raptor.Message.streams.destroy = function (apiKey, sessionId, streamId) { }); }; - -OT.Raptor.Message.streams.answer = function (apiKey, sessionId, streamId, answerSdp) { +OT.Raptor.Message.streams.answer = function(apiKey, sessionId, streamId, answerSdp) { return OT.Raptor.serializeMessage({ method: 'answer', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, @@ -10381,7 +10566,7 @@ OT.Raptor.Message.streams.answer = function (apiKey, sessionId, streamId, answer }); }; -OT.Raptor.Message.streams.candidate = function (apiKey, sessionId, streamId, candidate) { +OT.Raptor.Message.streams.candidate = function(apiKey, sessionId, streamId, candidate) { return OT.Raptor.serializeMessage({ method: 'candidate', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, @@ -10391,7 +10576,7 @@ OT.Raptor.Message.streams.candidate = function (apiKey, sessionId, streamId, can OT.Raptor.Message.streamChannels = {}; OT.Raptor.Message.streamChannels.update = - function (apiKey, sessionId, streamId, channelId, attributes) { + function(apiKey, sessionId, streamId, channelId, attributes) { return OT.Raptor.serializeMessage({ method: 'update', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + @@ -10400,11 +10585,10 @@ OT.Raptor.Message.streamChannels.update = }); }; - OT.Raptor.Message.subscribers = {}; OT.Raptor.Message.subscribers.create = - function (apiKey, sessionId, streamId, subscriberId, connectionId, channelsToSubscribeTo) { + function(apiKey, sessionId, streamId, subscriberId, connectionId, channelsToSubscribeTo) { var content = { id: subscriberId, connection: connectionId, @@ -10422,7 +10606,7 @@ OT.Raptor.Message.subscribers.create = }); }; -OT.Raptor.Message.subscribers.destroy = function (apiKey, sessionId, streamId, subscriberId) { +OT.Raptor.Message.subscribers.destroy = function(apiKey, sessionId, streamId, subscriberId) { return OT.Raptor.serializeMessage({ method: 'delete', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + @@ -10432,7 +10616,7 @@ OT.Raptor.Message.subscribers.destroy = function (apiKey, sessionId, streamId, s }; OT.Raptor.Message.subscribers.update = - function (apiKey, sessionId, streamId, subscriberId, attributes) { + function(apiKey, sessionId, streamId, subscriberId, attributes) { return OT.Raptor.serializeMessage({ method: 'update', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + @@ -10441,9 +10625,8 @@ OT.Raptor.Message.subscribers.update = }); }; - OT.Raptor.Message.subscribers.candidate = - function (apiKey, sessionId, streamId, subscriberId, candidate) { + function(apiKey, sessionId, streamId, subscriberId, candidate) { return OT.Raptor.serializeMessage({ method: 'candidate', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + @@ -10452,9 +10635,8 @@ OT.Raptor.Message.subscribers.candidate = }); }; - OT.Raptor.Message.subscribers.answer = - function (apiKey, sessionId, streamId, subscriberId, answerSdp) { + function(apiKey, sessionId, streamId, subscriberId, answerSdp) { return OT.Raptor.serializeMessage({ method: 'answer', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + @@ -10465,11 +10647,10 @@ OT.Raptor.Message.subscribers.answer = }); }; - OT.Raptor.Message.subscriberChannels = {}; OT.Raptor.Message.subscriberChannels.update = - function (apiKey, sessionId, streamId, subscriberId, channelId, attributes) { + function(apiKey, sessionId, streamId, subscriberId, channelId, attributes) { return OT.Raptor.serializeMessage({ method: 'update', uri: '/v2/partner/' + apiKey + '/session/' + sessionId + @@ -10478,10 +10659,9 @@ OT.Raptor.Message.subscriberChannels.update = }); }; - OT.Raptor.Message.signals = {}; -OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, data) { +OT.Raptor.Message.signals.create = function(apiKey, sessionId, toAddress, type, data) { var content = {}; if (type !== void 0) content.type = type; if (data !== void 0) content.data = data; @@ -10497,7 +10677,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, // tb_require('../../../helpers/helpers.js') // tb_require('./message.js') - !(function() { /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ @@ -10512,17 +10691,16 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, 1004: 'The token passed is invalid.' }; - - OT.Raptor.Dispatcher = function () { + OT.Raptor.Dispatcher = function() { OT.$.eventing(this, true); this.callbacks = {}; }; - OT.Raptor.Dispatcher.prototype.registerCallback = function (transactionId, completion) { + OT.Raptor.Dispatcher.prototype.registerCallback = function(transactionId, completion) { this.callbacks[transactionId] = completion; }; - OT.Raptor.Dispatcher.prototype.triggerCallback = function (transactionId) { + OT.Raptor.Dispatcher.prototype.triggerCallback = function(transactionId) { /*, arg1, arg2, argN-1, argN*/ if (!transactionId) return; @@ -10542,7 +10720,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, this.emit('close', reason); }; - OT.Raptor.Dispatcher.prototype.dispatch = function(rumorMessage) { // The special casing of STATUS messages is ugly. Need to think about // how to better integrate this. @@ -10566,7 +10743,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, OT.debug('OT.Raptor.dispatch ' + message.signature); OT.debug(rumorMessage.data); - switch(message.resource) { + switch (message.resource) { case 'session': this.dispatchSession(message); break; @@ -10604,26 +10781,24 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, } }; - OT.Raptor.Dispatcher.prototype.dispatchSession = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchSession = function(message) { switch (message.method) { case 'read': this.emit('session#read', message.content, message.transactionId); break; - default: OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented'); } }; - OT.Raptor.Dispatcher.prototype.dispatchConnection = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchConnection = function(message) { switch (message.method) { case 'created': this.emit('connection#created', message.content); break; - case 'deleted': this.emit('connection#deleted', message.params.connection, message.reason); break; @@ -10633,7 +10808,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, } }; - OT.Raptor.Dispatcher.prototype.dispatchStream = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchStream = function(message) { switch (message.method) { case 'created': @@ -10645,13 +10820,11 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, message.reason); break; - case 'updated': this.emit('stream#updated', message.params.stream, message.content); break; - // The JSEP process case 'generateoffer': case 'answer': @@ -10666,7 +10839,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, } }; - OT.Raptor.Dispatcher.prototype.dispatchStreamChannel = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchStreamChannel = function(message) { switch (message.method) { case 'updated': this.emit('streamChannel#updated', message.params.stream, @@ -10694,45 +10867,40 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, // candidate // // - OT.Raptor.Dispatcher.prototype.dispatchJsep = function (method, message) { + OT.Raptor.Dispatcher.prototype.dispatchJsep = function(method, message) { this.emit('jsep#' + method, message.params.stream, message.fromAddress, message); }; - - OT.Raptor.Dispatcher.prototype.dispatchSubscriberChannel = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchSubscriberChannel = function(message) { switch (message.method) { case 'updated': this.emit('subscriberChannel#updated', message.params.stream, message.params.channel, message.content); break; - case 'update': // subscriberId, streamId, content this.emit('subscriberChannel#update', message.params.subscriber, message.params.stream, message.content); break; - default: OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented'); } }; - OT.Raptor.Dispatcher.prototype.dispatchSubscriber = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchSubscriber = function(message) { switch (message.method) { case 'created': this.emit('subscriber#created', message.params.stream, message.fromAddress, message.content.id); break; - case 'deleted': this.dispatchJsep('unsubscribe', message); this.emit('subscriber#deleted', message.params.stream, message.fromAddress); break; - // The JSEP process case 'generateoffer': case 'answer': @@ -10742,13 +10910,12 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, this.dispatchJsep(message.method, message); break; - default: OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented'); } }; - OT.Raptor.Dispatcher.prototype.dispatchSignal = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchSignal = function(message) { if (message.method !== 'signal') { OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented'); return; @@ -10757,7 +10924,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, message.content.data); }; - OT.Raptor.Dispatcher.prototype.dispatchArchive = function (message) { + OT.Raptor.Dispatcher.prototype.dispatchArchive = function(message) { switch (message.method) { case 'created': this.emit('archive#created', message.content); @@ -10793,27 +10960,27 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, var connectionId = dict.connectionId ? dict.connectionId : dict.connection.id; - return new OT.Stream( dict.id, - dict.name, - dict.creationTime, - session.connections.get(connectionId), - session, - channel ); + return new OT.Stream(dict.id, + dict.name, + dict.creationTime, + session.connections.get(connectionId), + session, + channel); } function parseAndAddStreamToSession(dict, session) { if (session.streams.has(dict.id)) return; var stream = parseStream(dict, session); - session.streams.add( stream ); + session.streams.add(stream); return stream; } function parseArchive(dict) { - return new OT.Archive( dict.id, - dict.name, - dict.status ); + return new OT.Archive(dict.id, + dict.name, + dict.status); } function parseAndAddArchiveToSession(dict, session) { @@ -10825,14 +10992,14 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, return archive; } - var DelayedEventQueue = function DelayedEventQueue (eventDispatcher) { + var DelayedEventQueue = function DelayedEventQueue(eventDispatcher) { var queue = []; - this.enqueue = function enqueue (/* arg1, arg2, ..., argN */) { - queue.push( Array.prototype.slice.call(arguments) ); + this.enqueue = function enqueue(/* arg1, arg2, ..., argN */) { + queue.push(Array.prototype.slice.call(arguments)); }; - this.triggerAll = function triggerAll () { + this.triggerAll = function triggerAll() { var event; // Array.prototype.shift is actually pretty inefficient for longer Arrays, @@ -10855,7 +11022,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, // @todo benchmark and see if we should actually care about shift's performance // for our common queue sizes. // - while( (event = queue.shift()) ) { + while ((event = queue.shift())) { eventDispatcher.trigger.apply(eventDispatcher, event); } }; @@ -10864,7 +11031,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, var DelayedSessionEvents = function(dispatcher) { var eventQueues = {}; - this.enqueue = function enqueue (/* key, arg1, arg2, ..., argN */) { + this.enqueue = function enqueue(/* key, arg1, arg2, ..., argN */) { var key = arguments[0]; var eventArgs = Array.prototype.slice.call(arguments, 1); if (!eventQueues[key]) { @@ -10873,13 +11040,13 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, eventQueues[key].enqueue.apply(eventQueues[key], eventArgs); }; - this.triggerConnectionCreated = function triggerConnectionCreated (connection) { + this.triggerConnectionCreated = function triggerConnectionCreated(connection) { if (eventQueues['connectionCreated' + connection.id]) { eventQueues['connectionCreated' + connection.id].triggerAll(); } }; - this.triggerSessionConnected = function triggerSessionConnected (connections) { + this.triggerSessionConnected = function triggerSessionConnected(connections) { if (eventQueues.sessionConnected) { eventQueues.sessionConnected.triggerAll(); } @@ -10912,16 +11079,16 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, return; } - connection.destroy( reason ); + connection.destroy(reason); }); // This method adds connections to the session both on a connection#created and // on a session#read. In the case of session#read sessionRead is set to true and // we include our own connection. - var addConnection = function (connection, sessionRead) { + var addConnection = function(connection, sessionRead) { connection = OT.Connection.fromHash(connection); if (sessionRead || session.connection && connection.id !== session.connection.id) { - session.connections.add( connection ); + session.connections.add(connection); delayedSessionEvents.triggerConnectionCreated(connection); } @@ -10935,8 +11102,8 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, var payload = { debug: sessionRead ? 'connection came in session#read' : 'connection came in connection#created', - streamId : stream.id, - connectionId : connection.id + streamId: stream.id, + connectionId: connection.id }; session.logEvent('streamCreated', 'warning', payload); } @@ -10960,11 +11127,11 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, }); OT.$.forEach(content.stream, function(streamParams) { - state.streams.push( parseAndAddStreamToSession(streamParams, session) ); + state.streams.push(parseAndAddStreamToSession(streamParams, session)); }); OT.$.forEach(content.archive || content.archives, function(archiveParams) { - state.archives.push( parseAndAddArchiveToSession(archiveParams, session) ); + state.archives.push(parseAndAddArchiveToSession(archiveParams, session)); }); session._.subscriberMap = {}; @@ -10992,8 +11159,8 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, unconnectedStreams[stream.id] = stream; var payload = { - debug : 'eventOrderError -- streamCreated event before connectionCreated', - streamId : stream.id, + debug: 'eventOrderError -- streamCreated event before connectionCreated', + streamId: stream.id }; session.logEvent('streamCreated', 'warning', payload); } @@ -11072,7 +11239,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, if (subscriber) actors.push(subscriber); break; - // Messages for Publishers case 'answer': case 'pranswer': @@ -11081,7 +11247,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, actors = OT.publishers.where({streamId: streamId}); break; - // Messages for Publishers and Subscribers case 'candidate': // send to whichever of your publisher or subscribers are @@ -11090,7 +11255,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, .concat(OT.subscribers.where({streamId: streamId})); break; - default: OT.warn('OT.Raptor.dispatch: jsep#' + method + ' is not currently implemented'); @@ -11103,14 +11267,14 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, // until we find the actor that the message relates to this stream, and then // we grab the session from it. fromConnection = actors[0].session.connections.get(fromAddress); - if(!fromConnection && fromAddress.match(/^symphony\./)) { + if (!fromConnection && fromAddress.match(/^symphony\./)) { fromConnection = OT.Connection.fromHash({ id: fromAddress, creationTime: Math.floor(OT.$.now()) }); actors[0].session.connections.add(fromConnection); - } else if(!fromConnection) { + } else if (!fromConnection) { OT.warn('OT.Raptor.dispatch: Messsage comes from a connection (' + fromAddress + ') that we do not know about. The message was ignored.'); return; @@ -11374,6 +11538,9 @@ OT.httpTest = httpTest; /* exported SDPHelpers */ +var START_MEDIA_SSRC = 10000, + START_RTX_SSRC = 20000; + // Here are the structure of the rtpmap attribute and the media line, most of the // complex Regular Expressions in this code are matching against one of these two // formats: @@ -11384,108 +11551,329 @@ OT.httpTest = httpTest; // * https://tools.ietf.org/html/rfc4566 // * http://en.wikipedia.org/wiki/Session_Description_Protocol // -var SDPHelpers = { - // Search through sdpLines to find the Media Line of type +mediaType+. - getMLineIndex: function getMLineIndex(sdpLines, mediaType) { - var targetMLine = 'm=' + mediaType; +var SDPHelpers = {}; - // Find the index of the media line for +type+ - return OT.$.findIndex(sdpLines, function(line) { - if (line.indexOf(targetMLine) !== -1) { - return true; - } +// Search through sdpLines to find the Media Line of type +mediaType+. +SDPHelpers.getMLineIndex = function getMLineIndex(sdpLines, mediaType) { + var targetMLine = 'm=' + mediaType; - return false; - }); - }, - - // Extract the payload types for a give Media Line. - // - getMLinePayloadTypes: function getMLinePayloadTypes (mediaLine, mediaType) { - var mLineSelector = new RegExp('^m=' + mediaType + - ' \\d+(/\\d+)? [a-zA-Z0-9/]+(( [a-zA-Z0-9/]+)+)$', 'i'); - - // Get all payload types that the line supports - var payloadTypes = mediaLine.match(mLineSelector); - if (!payloadTypes || payloadTypes.length < 2) { - // Error, invalid M line? - return []; + // Find the index of the media line for +type+ + return OT.$.findIndex(sdpLines, function(line) { + if (line.indexOf(targetMLine) !== -1) { + return true; } - return OT.$.trim(payloadTypes[2]).split(' '); - }, + return false; + }); +}; - removeTypesFromMLine: function removeTypesFromMLine (mediaLine, payloadTypes) { - return OT.$.trim( - mediaLine.replace(new RegExp(' ' + payloadTypes.join(' |'), 'ig') , ' ') - .replace(/\s+/g, ' ') ); - }, +// Grab a M line of a particular +mediaType+ from sdpLines. +SDPHelpers.getMLine = function getMLine(sdpLines, mediaType) { + var mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType); + return mLineIndex > -1 ? sdpLines[mLineIndex] : void 0; +}; - - // Remove all references to a particular encodingName from a particular media type - // - removeMediaEncoding: function removeMediaEncoding (sdp, mediaType, encodingName) { - var sdpLines = sdp.split('\r\n'), - mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType), - mLine = mLineIndex > -1 ? sdpLines[mLineIndex] : void 0, - typesToRemove = [], - payloadTypes, - match; - - if (mLineIndex === -1) { - // Error, missing M line - return sdpLines.join('\r\n'); - } - - // Get all payload types that the line supports +SDPHelpers.hasMLinePayloadType = function hasMLinePayloadType(sdpLines, mediaType, payloadType) { + var mLine = SDPHelpers.getMLine(sdpLines, mediaType), payloadTypes = SDPHelpers.getMLinePayloadTypes(mLine, mediaType); - if (payloadTypes.length === 0) { - // Error, invalid M line? - return sdpLines.join('\r\n'); + + return OT.$.arrayIndexOf(payloadTypes, payloadType) > -1; +}; + +// Extract the payload types for a give Media Line. +// +SDPHelpers.getMLinePayloadTypes = function getMLinePayloadTypes(mediaLine, mediaType) { + var mLineSelector = new RegExp('^m=' + mediaType + + ' \\d+(/\\d+)? [a-zA-Z0-9/]+(( [a-zA-Z0-9/]+)+)$', 'i'); + + // Get all payload types that the line supports + var payloadTypes = mediaLine.match(mLineSelector); + if (!payloadTypes || payloadTypes.length < 2) { + // Error, invalid M line? + return []; + } + + return OT.$.trim(payloadTypes[2]).split(' '); +}; + +SDPHelpers.removeTypesFromMLine = function removeTypesFromMLine(mediaLine, payloadTypes) { + return OT.$.trim( + mediaLine.replace(new RegExp(' ' + payloadTypes.join(' |'), 'ig'), ' ') + .replace(/\s+/g, ' ')); +}; + +// Remove all references to a particular encodingName from a particular media type +// +SDPHelpers.removeMediaEncoding = function removeMediaEncoding(sdp, mediaType, encodingName) { + var sdpLines = sdp.split('\r\n'), + mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType), + mLine = mLineIndex > -1 ? sdpLines[mLineIndex] : void 0, + typesToRemove = [], + payloadTypes, + match; + + if (mLineIndex === -1) { + // Error, missing M line + return sdpLines.join('\r\n'); + } + + // Get all payload types that the line supports + payloadTypes = SDPHelpers.getMLinePayloadTypes(mLine, mediaType); + if (payloadTypes.length === 0) { + // Error, invalid M line? + return sdpLines.join('\r\n'); + } + + // Find the location of all the rtpmap lines that relate to +encodingName+ + // and any of the supported payload types + var matcher = new RegExp('a=rtpmap:(' + payloadTypes.join('|') + ') ' + + encodingName + '\\/\\d+', 'i'); + + sdpLines = OT.$.filter(sdpLines, function(line, index) { + match = line.match(matcher); + if (match === null) return true; + + typesToRemove.push(match[1]); + + if (index < mLineIndex) { + // This removal changed the index of the mline, track it + mLineIndex--; } - // Find the location of all the rtpmap lines that relate to +encodingName+ - // and any of the supported payload types - var matcher = new RegExp('a=rtpmap:(' + payloadTypes.join('|') + ') ' + - encodingName + '\\/\\d+', 'i'); + // remove this one + return false; + }); - sdpLines = OT.$.filter(sdpLines, function(line, index) { - match = line.match(matcher); - if (match === null) return true; + if (typesToRemove.length > 0 && mLineIndex > -1) { + // Remove all the payload types and we've removed from the media line + sdpLines[mLineIndex] = SDPHelpers.removeTypesFromMLine(mLine, typesToRemove); + } - typesToRemove.push(match[1]); + return sdpLines.join('\r\n'); +}; - if (index < mLineIndex) { - // This removal changed the index of the mline, track it - mLineIndex--; +// Removes all Confort Noise from +sdp+. +// +// See https://jira.tokbox.com/browse/OPENTOK-7176 +// +SDPHelpers.removeComfortNoise = function removeComfortNoise(sdp) { + return SDPHelpers.removeMediaEncoding(sdp, 'audio', 'CN'); +}; + +SDPHelpers.removeVideoCodec = function removeVideoCodec(sdp, codec) { + return SDPHelpers.removeMediaEncoding(sdp, 'video', codec); +}; + +// Used to identify whether Video media (for a given set of SDP) supports +// retransmissions. +// +// The algorithm to do could be summarised as: +// +// IF ssrc-group:FID exists AND IT HAS AT LEAST TWO IDS THEN +// we are using RTX +// ELSE IF "a=rtpmap: (\\d+):rtxPayloadId(/\\d+)? rtx/90000" +// AND SDPHelpers.hasMLinePayloadType(sdpLines, 'Video', rtxPayloadId) +// we are using RTX +// ELSE +// we are not using RTX +// +// The ELSE IF clause basically covers the case where ssrc-group:FID +// is probably malformed or missing. In that case we verify whether +// we want RTX by looking at whether it's mentioned in the video +// media line instead. +// +var isUsingRTX = function isUsingRTX(sdpLines, videoAttrs) { + var groupFID = videoAttrs.filterByName('ssrc-group:FID'), + missingFID = groupFID.length === 0; + + if (groupFID.length > 1) { + // @todo Wut? + throw new Error('WTF Simulcast?!'); + } + + if (!missingFID) groupFID = groupFID[0].value.split(' '); + else groupFID = []; + + switch (groupFID.length) { + case 0: + case 1: + // possibly no RTX, double check for the RTX payload type and that + // the Video Media line contains that payload type + // + // Details: Look for a rtpmap line for rtx/90000 + // If there is one, grab the payload ID for rtx + // Look to see if that payload ID is listed under the payload types for the m=Video line + // If it is: RTX + // else: No RTX for you + + var rtxAttr = videoAttrs.find(function(attr) { + return attr.name.indexOf('rtpmap:') === 0 && + attr.value.indexOf('rtx/90000') > -1; + }); + + if (!rtxAttr) { + return false; } - // remove this one - return false; - }); + var rtxPayloadId = rtxAttr.name.split(':')[1]; + if (rtxPayloadId.indexOf('/') > -1) rtxPayloadId = rtxPayloadId.split('/')[0]; + return SDPHelpers.hasMLinePayloadType(sdpLines, 'video', rtxPayloadId); - if (typesToRemove.length > 0 && mLineIndex > -1) { - // Remove all the payload types and we've removed from the media line - sdpLines[mLineIndex] = SDPHelpers.removeTypesFromMLine(mLine, typesToRemove); - } - - return sdpLines.join('\r\n'); - }, - - // Removes all Confort Noise from +sdp+. - // - // See https://jira.tokbox.com/browse/OPENTOK-7176 - // - removeComfortNoise: function removeComfortNoise (sdp) { - return SDPHelpers.removeMediaEncoding(sdp, 'audio', 'CN'); - }, - - removeVideoCodec: function removeVideoCodec (sdp, codec) { - return SDPHelpers.removeMediaEncoding(sdp, 'video', codec); + default: + // two or more: definitely RTX + return true; } }; +// This returns an Array, which is decorated with several +// SDP specific helper methods. +// +SDPHelpers.getAttributesForMediaType = function getAttributesForMediaType(sdpLines, mediaType) { + var mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType), + matchOtherMLines = new RegExp('m=(^' + mediaType + ') ', 'i'), + matchSSRCLines = new RegExp('a=ssrc:\\d+ .*', 'i'), + matchSSRCGroup = new RegExp('a=ssrc-group:FID (\\d+).*?', 'i'), + matchAttrLine = new RegExp('a=([a-z0-9:/-]+) (.*)', 'i'), + ssrcStartIndex, + ssrcEndIndex, + attrs = [], + regResult, + ssrc, ssrcGroup, + msidMatch, msid; + for (var i = mLineIndex + 1; i < sdpLines.length; i++) { + if (matchOtherMLines.test(sdpLines[i])) { + break; + } + + // Get the ssrc + ssrcGroup = sdpLines[i].match(matchSSRCGroup); + if (ssrcGroup) { + ssrcStartIndex = i; + ssrc = ssrcGroup[1]; + } + + // Get the msid + msidMatch = sdpLines[i].match('a=ssrc:' + ssrc + ' msid:(.+)'); + if (msidMatch) { + msid = msidMatch[1]; + } + + // find where the ssrc lines end + var isSSRCLine = matchSSRCLines.test(sdpLines[i]); + if (ssrcStartIndex !== undefined && ssrcEndIndex === undefined && !isSSRCLine || + i === sdpLines.length - 1) { + ssrcEndIndex = i; + } + + regResult = matchAttrLine.exec(sdpLines[i]); + if (regResult && regResult.length === 3) { + attrs.push({ + name: regResult[1], + value: regResult[2] + }); + } + } + + /// The next section decorates the attributes array + /// with some useful helpers. + + // Store references to the start and end indices + // of the media section for this mediaType + attrs.ssrcStartIndex = ssrcStartIndex; + attrs.ssrcEndIndex = ssrcEndIndex; + attrs.msid = msid; + + // Add filter support is + if (!Array.prototype.filter) { + attrs.filter = OT.$.bind(OT.$.filter, OT.$, attrs); + } + + if (!Array.prototype.find) { + attrs.find = OT.$.bind(OT.$.find, OT.$, attrs); + } + + attrs.isUsingRTX = OT.$.bind(isUsingRTX, null, sdpLines, attrs); + + attrs.filterByName = function(name) { + return this.filter(function(attr) { + return attr.name === name; + }); + }; + + return attrs; +}; + +// Modifies +sdp+ to enable Simulcast for +numberOfStreams+. +// +// Ok, here's the plan: +// - add the 'a=ssrc-group:SIM' line, it will have numberOfStreams ssrcs +// - if RTX then add one 'a=ssrc-group:FID', we need to add numberOfStreams lines +// - add numberOfStreams 'a=ssrc:...' lines for the media ssrc +// - if RTX then add numberOfStreams 'a=ssrc:...' lines for the RTX ssrc +// +// Re: media and rtx ssrcs: +// We just generate these. The Mantis folk would like us to use sequential numbers +// here for ease of debugging. We can use the same starting number each time as well. +// We should confirm with Oscar/Jose that whether we need to verify that the numbers +// that we choose don't clash with any other ones in the SDP. +// +// I think we do need to check but I can't remember. +// +// Re: The format of the 'a=ssrc:' lines +// Just use the following pattern: +// a=ssrc: cname:localCname +// a=ssrc: msid: +// +// It doesn't matter that they are all the same and are static. +// +// +SDPHelpers.enableSimulcast = function enableSimulcast(sdp, numberOfStreams) { + var sdpLines = sdp.split('\r\n'), + videoAttrs = SDPHelpers.getAttributesForMediaType(sdpLines, 'video'), + usingRTX = videoAttrs.isUsingRTX(), + mediaSSRC = [], + rtxSSRC = [], + linesToAdd, + i; + + // generate new media (and rtx if needed) ssrcs + for (i = 0; i < numberOfStreams; ++i) { + mediaSSRC.push(START_MEDIA_SSRC + i); + if (usingRTX) rtxSSRC.push(START_RTX_SSRC + i); + } + + linesToAdd = [ + 'a=ssrc-group:SIM ' + mediaSSRC.join(' ') + ]; + + if (usingRTX) { + for (i = 0; i < numberOfStreams; ++i) { + linesToAdd.push('a=ssrc-group:FID ' + mediaSSRC[i] + ' ' + rtxSSRC[i]); + } + } + + for (i = 0; i < numberOfStreams; ++i) { + linesToAdd.push('a=ssrc:' + mediaSSRC[i] + ' cname:localCname', + 'a=ssrc:' + mediaSSRC[i] + ' msid:' + videoAttrs.msid); + + } + + if (usingRTX) { + for (i = 0; i < numberOfStreams; ++i) { + linesToAdd.push('a=ssrc:' + rtxSSRC[i] + ' cname:localCname', + 'a=ssrc:' + rtxSSRC[i] + ' msid:' + videoAttrs.msid); + } + } + + // Replace the previous video ssrc section with our new video ssrc section by + // deleting the old ssrcs section and inserting the new lines + linesToAdd.unshift(videoAttrs.ssrcStartIndex, videoAttrs.ssrcEndIndex - + videoAttrs.ssrcStartIndex); + sdpLines.splice.apply(sdpLines, linesToAdd); + + return sdpLines.join('\r\n'); +}; // tb_require('../../helpers/helpers.js') @@ -11556,9 +11944,9 @@ OT.getStatsHelpers = getStatsHelpers; function getStatsAdapter() { /// -// Get Stats using the older API. Used by all current versions -// of Chrome. -// + // Get Stats using the older API. Used by all current versions + // of Chrome. + // function getStatsOldAPI(peerConnection, completion) { peerConnection.getStats(function(rtcStatsReport) { @@ -11583,9 +11971,9 @@ function getStatsAdapter() { }); } -/// -// Get Stats using the newer API. -// + /// + // Get Stats using the newer API. + // function getStatsNewAPI(peerConnection, completion) { peerConnection.getStats(null, function(rtcStatsReport) { @@ -11632,13 +12020,11 @@ function webrtcTest(config) { NativeRTCSessionDescription = (window.mozRTCSessionDescription || window.RTCSessionDescription); NativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate); - } - else { + } else { NativeRTCSessionDescription = OTPlugin.RTCSessionDescription; NativeRTCIceCandidate = OTPlugin.RTCIceCandidate; } - function isCandidateRelay(candidate) { return candidate.candidate.indexOf('relay') !== -1; } @@ -11815,7 +12201,7 @@ function webrtcTest(config) { if (stat.hasOwnProperty('bytesReceived')) { _statsSamples.videoBytesReceived = parseInt(stat.bytesReceived, 10); } - } else if(OT.getStatsHelpers.isAudioStat(stat)) { + } else if (OT.getStatsHelpers.isAudioStat(stat)) { if (stat.hasOwnProperty('bytesReceived')) { _statsSamples.audioBytesReceived = parseInt(stat.bytesReceived, 10); } @@ -11984,7 +12370,6 @@ OT.Chrome = function(properties) { } }; - // Adds the widget to the chrome and to the DOM. Also creates a accessor // property for it on the chrome. // @@ -12000,7 +12385,7 @@ OT.Chrome = function(properties) { // chrome.foo.setDisplayMode('on'); // this.set = function(widgetName, widget) { - if (typeof(widgetName) === 'string' && widget) { + if (typeof widgetName === 'string' && widget) { _set.call(this, widgetName, widget); } else { @@ -12015,7 +12400,6 @@ OT.Chrome = function(properties) { }; - // tb_require('../../../helpers/helpers.js') // tb_require('../chrome.js') @@ -12107,7 +12491,6 @@ OT.Chrome.Behaviour.Widget = function(widget, options) { }, 2000); } - // add the widget to the parent parent.appendChild(this.domElement); @@ -12123,39 +12506,38 @@ OT.Chrome.Behaviour.Widget = function(widget, options) { /* global OT */ OT.Chrome.VideoDisabledIndicator = function(options) { - var _videoDisabled = false, - _warning = false, + var videoDisabled = false, + warning = false, updateClasses; - updateClasses = OT.$.bind(function(domElement) { - if (_videoDisabled) { - OT.$.addClass(domElement, 'OT_video-disabled'); - } else { - OT.$.removeClass(domElement, 'OT_video-disabled'); + updateClasses = OT.$.bind(function(element) { + var shouldDisplay = ['auto', 'on'].indexOf(this.getDisplayMode()) > -1; + + OT.$.removeClass(element, 'OT_video-disabled OT_video-disabled-warning OT_active'); + + if (!shouldDisplay) { + return; } - if(_warning) { - OT.$.addClass(domElement, 'OT_video-disabled-warning'); - } else { - OT.$.removeClass(domElement, 'OT_video-disabled-warning'); - } - if ((_videoDisabled || _warning) && - (this.getDisplayMode() === 'auto' || this.getDisplayMode() === 'on')) { - OT.$.addClass(domElement, 'OT_active'); - } else { - OT.$.removeClass(domElement, 'OT_active'); + + if (videoDisabled) { + OT.$.addClass(element, 'OT_video-disabled'); + } else if (warning) { + OT.$.addClass(element, 'OT_video-disabled-warning'); } + + OT.$.addClass(element, 'OT_active'); }, this); this.disableVideo = function(value) { - _videoDisabled = value; - if(value === true) { - _warning = false; + videoDisabled = value; + if (value === true) { + warning = false; } updateClasses(this.domElement); }; this.setWarning = function(value) { - _warning = value; + warning = value; updateClasses(this.domElement); }; @@ -12320,13 +12702,13 @@ OT.Chrome.BackingBar = function(options) { _muteMode = options.muteMode; function getDisplayMode() { - if(_nameMode === 'on' || _muteMode === 'on') { + if (_nameMode === 'on' || _muteMode === 'on') { return 'on'; - } else if(_nameMode === 'mini' || _muteMode === 'mini') { + } else if (_nameMode === 'mini' || _muteMode === 'mini') { return 'mini'; - } else if(_nameMode === 'mini-auto' || _muteMode === 'mini-auto') { + } else if (_nameMode === 'mini-auto' || _muteMode === 'mini-auto') { return 'mini-auto'; - } else if(_nameMode === 'auto' || _muteMode === 'auto') { + } else if (_nameMode === 'auto' || _muteMode === 'auto') { return 'auto'; } else { return 'off'; @@ -12355,7 +12737,6 @@ OT.Chrome.BackingBar = function(options) { }; - // tb_require('../../helpers/helpers.js') // tb_require('./behaviour/widget.js') @@ -12363,7 +12744,6 @@ OT.Chrome.BackingBar = function(options) { trailing: true, browser: true, smarttabs:true */ /* global OT */ - OT.Chrome.AudioLevelMeter = function(options) { var widget = this, @@ -12432,7 +12812,6 @@ OT.Chrome.AudioLevelMeter = function(options) { trailing: true, browser: true, smarttabs:true */ /* global OT */ - // Archving Chrome Widget // // mode (String) @@ -12465,12 +12844,12 @@ OT.Chrome.Archiving = function(options) { }; renderStage = OT.$.bind(function() { - if(renderStageDelayedAction) { + if (renderStageDelayedAction) { clearTimeout(renderStageDelayedAction); renderStageDelayedAction = null; } - if(_archiving) { + if (_archiving) { OT.$.addClass(_light, 'OT_active'); } else { OT.$.removeClass(_light, 'OT_active'); @@ -12478,7 +12857,7 @@ OT.Chrome.Archiving = function(options) { OT.$.removeClass(this.domElement, 'OT_archiving-' + (!_archiving ? 'on' : 'off')); OT.$.addClass(this.domElement, 'OT_archiving-' + (_archiving ? 'on' : 'off')); - if(options.show && _archiving) { + if (options.show && _archiving) { renderText(_archivingStarted); OT.$.addClass(_text, 'OT_mode-on'); OT.$.removeClass(_text, 'OT_mode-auto'); @@ -12487,7 +12866,7 @@ OT.Chrome.Archiving = function(options) { OT.$.addClass(_text, 'OT_mode-auto'); OT.$.removeClass(_text, 'OT_mode-on'); }, 5000); - } else if(options.show && !_initialState) { + } else if (options.show && !_initialState) { OT.$.addClass(_text, 'OT_mode-on'); OT.$.removeClass(_text, 'OT_mode-auto'); this.setDisplayMode('on'); @@ -12526,7 +12905,7 @@ OT.Chrome.Archiving = function(options) { this.setShowArchiveStatus = OT.$.bind(function(show) { options.show = show; - if(this.domElement) { + if (this.domElement) { renderStage.call(this); } }, this); @@ -12534,7 +12913,7 @@ OT.Chrome.Archiving = function(options) { this.setArchiving = OT.$.bind(function(status) { _archiving = status; _initialState = false; - if(this.domElement) { + if (this.domElement) { renderStage.call(this); } }, this); @@ -12550,7 +12929,7 @@ OT.Chrome.Archiving = function(options) { // Web OT Helpers !(function(window) { // guard for Node.js - if (window && typeof(navigator) !== 'undefined') { + if (window && typeof navigator !== 'undefined') { var NativeRTCPeerConnection = (window.webkitRTCPeerConnection || window.mozRTCPeerConnection); @@ -12619,7 +12998,7 @@ OT.Chrome.Archiving = function(options) { // all the good browsers down to IE's level than bootstrap it up. if (typeof window.MediaStreamTrack !== 'undefined') { if (!window.MediaStreamTrack.prototype.setEnabled) { - window.MediaStreamTrack.prototype.setEnabled = function (enabled) { + window.MediaStreamTrack.prototype.setEnabled = function(enabled) { this.enabled = OT.$.castToBoolean(enabled); }; } @@ -12629,17 +13008,16 @@ OT.Chrome.Archiving = function(options) { window.URL = window.webkitURL; } - OT.$.createPeerConnection = function (config, options, publishersWebRtcStream, completion) { + OT.$.createPeerConnection = function(config, options, publishersWebRtcStream, completion) { if (OTPlugin.isInstalled()) { OTPlugin.initPeerConnection(config, options, publishersWebRtcStream, completion); - } - else { + } else { var pc; try { pc = new NativeRTCPeerConnection(config, options); - } catch(e) { + } catch (e) { completion(e.message); return; } @@ -12676,7 +13054,7 @@ OT.Chrome.Archiving = function(options) { // If there are no issues, the +success+ callback will be executed on completion. // Errors during any step will result in the +failure+ callback being executed. // -var subscribeProcessor = function(peerConnection, success, failure) { +var subscribeProcessor = function(peerConnection, numberOfSimulcastStreams, success, failure) { var generateErrorCallback, setLocalDescription; @@ -12693,6 +13071,9 @@ var subscribeProcessor = function(peerConnection, success, failure) { offer.sdp = SDPHelpers.removeComfortNoise(offer.sdp); offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'ulpfec'); offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'red'); + if (numberOfSimulcastStreams > 1) { + offer.sdp = SDPHelpers.enableSimulcast(offer.sdp, numberOfSimulcastStreams); + } peerConnection.setLocalDescription( offer, @@ -12781,7 +13162,7 @@ var offerProcessor = function(peerConnection, offer, success, failure) { if (offer.sdp.indexOf('a=rtcp-fb') === -1) { var rtcpFbLine = 'a=rtcp-fb:* ccm fir\r\na=rtcp-fb:* nack '; - offer.sdp = offer.sdp.replace(/^m=video(.*)$/gmi, 'm=video$1\r\n'+rtcpFbLine); + offer.sdp = offer.sdp.replace(/^m=video(.*)$/gmi, 'm=video$1\r\n' + rtcpFbLine); } peerConnection.setRemoteDescription( @@ -12795,6 +13176,7 @@ var offerProcessor = function(peerConnection, offer, success, failure) { ); }; + // tb_require('../../helpers/helpers.js') // tb_require('../../helpers/lib/web_rtc.js') @@ -12805,8 +13187,7 @@ var NativeRTCIceCandidate; if (!OTPlugin.isInstalled()) { NativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate); -} -else { +} else { NativeRTCIceCandidate = OTPlugin.RTCIceCandidate; } @@ -12844,24 +13225,24 @@ var IceCandidateProcessor = function() { }; this.processPending = function() { - while(_pendingIceCandidates.length) { + while (_pendingIceCandidates.length) { _peerConnection.addIceCandidate(_pendingIceCandidates.shift()); } }; }; + // tb_require('../../helpers/helpers.js') // tb_require('../../helpers/lib/web_rtc.js') /* exported DataChannel */ - // Wraps up a native RTCDataChannelEvent object for the message event. This is // so we never accidentally leak the native DataChannel. // // @constructor // @private // -var DataChannelMessageEvent = function DataChanneMessageEvent (event) { +var DataChannelMessageEvent = function DataChanneMessageEvent(event) { this.data = event.data; this.source = event.source; this.lastEventId = event.lastEventId; @@ -12872,7 +13253,6 @@ var DataChannelMessageEvent = function DataChanneMessageEvent (event) { this.path = event.path; }; - // DataChannel is a wrapper of the native browser RTCDataChannel object. // // It exposes an interface that is very similar to the native one except @@ -12912,54 +13292,61 @@ var DataChannelMessageEvent = function DataChanneMessageEvent (event) { // @constructor // @private // -var DataChannel = function DataChannel (dataChannel) { +var DataChannel = function DataChannel(dataChannel) { var api = {}; /// Private Data var bufferedMessages = []; + var qosData = { + sentMessages: 0, + recvMessages: 0 + }; /// Private API - var bufferMessage = function bufferMessage (data) { + var bufferMessage = function bufferMessage(data) { bufferedMessages.push(data); return api; }, - sendMessage = function sendMessage (data) { + sendMessage = function sendMessage(data) { dataChannel.send(data); + qosData.sentMessages++; + return api; }, - flushBufferedMessages = function flushBufferedMessages () { + flushBufferedMessages = function flushBufferedMessages() { var data; - while ( (data = bufferedMessages.shift()) ) { + while ((data = bufferedMessages.shift())) { api.send(data); } }, - onOpen = function onOpen () { + onOpen = function onOpen() { api.send = sendMessage; flushBufferedMessages(); }, - onClose = function onClose (event) { + onClose = function onClose(event) { api.send = bufferMessage; api.trigger('close', event); }, - onError = function onError (event) { + onError = function onError(event) { OT.error('Data Channel Error:', event); }, - onMessage = function onMessage (domEvent) { + onMessage = function onMessage(domEvent) { var event = new DataChannelMessageEvent(domEvent); + qosData.recvMessages++; + api.trigger('message', event); }; - /// Public API OT.$.eventing(api, true); @@ -12973,11 +13360,12 @@ var DataChannel = function DataChannel (dataChannel) { api.ordered = dataChannel.ordered; api.protocol = dataChannel.protocol; api._channel = dataChannel; - api.close = function () { + + api.close = function() { dataChannel.close(); }; - api.equals = function (label, options) { + api.equals = function(label, options) { if (api.label !== label) return false; for (var key in options) { @@ -12991,11 +13379,14 @@ var DataChannel = function DataChannel (dataChannel) { return true; }; + api.getQosData = function() { + return qosData; + }; + // Send initially just buffers all messages until // the channel is open. api.send = bufferMessage; - /// Init dataChannel.addEventListener('open', onOpen, false); dataChannel.addEventListener('close', onClose, false); @@ -13004,6 +13395,7 @@ var DataChannel = function DataChannel (dataChannel) { return api; }; + // tb_require('../../helpers/helpers.js') // tb_require('../../helpers/lib/web_rtc.js') // tb_require('./data_channel.js') @@ -13018,21 +13410,25 @@ var DataChannel = function DataChannel (dataChannel) { // @constructor // @private // -var PeerConnectionChannels = function PeerConnectionChannels (pc) { +var PeerConnectionChannels = function PeerConnectionChannels(pc) { /// Private Data var channels = [], api = {}; + var lastQos = { + sentMessages: 0, + recvMessages: 0 + }; /// Private API - var remove = function remove (channel) { + var remove = function remove(channel) { OT.$.filter(channels, function(c) { return channel !== c; }); }; - var add = function add (nativeChannel) { + var add = function add(nativeChannel) { var channel = new DataChannel(nativeChannel); channels.push(channel); @@ -13043,14 +13439,13 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) { return channel; }; - /// Public API - api.add = function (label, options) { + api.add = function(label, options) { return add(pc.createDataChannel(label, options)); }; - api.addMany = function (newChannels) { + api.addMany = function(newChannels) { for (var label in newChannels) { if (newChannels.hasOwnProperty(label)) { api.add(label, newChannels[label]); @@ -13058,13 +13453,13 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) { } }; - api.get = function (label, options) { + api.get = function(label, options) { return OT.$.find(channels, function(channel) { return channel.equals(label, options); }); }; - api.getOrAdd = function (label, options) { + api.getOrAdd = function(label, options) { var channel = api.get(label, options); if (!channel) { channel = api.add(label, options); @@ -13073,7 +13468,34 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) { return channel; }; - api.destroy = function () { + api.getQosData = function() { + var qosData = { + sentMessages: 0, + recvMessages: 0 + }; + + OT.$.forEach(channels, function(channel) { + qosData.sentMessages += channel.getQosData().sentMessages; + qosData.recvMessages += channel.getQosData().recvMessages; + }); + + return qosData; + }; + + api.sampleQos = function() { + var newQos = api.getQosData(); + + var sampleQos = { + sentMessages: newQos.sentMessages - lastQos.sentMessages, + recvMessages: newQos.recvMessages - lastQos.recvMessages + }; + + lastQos = newQos; + + return sampleQos; + }; + + api.destroy = function() { OT.$.forEach(channels, function(channel) { channel.close(); }); @@ -13081,7 +13503,6 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) { channels = []; }; - /// Init pc.addEventListener('datachannel', function(event) { @@ -13129,7 +13550,7 @@ var connectionStateLogger = function(pc) { var trackState = function() { var now = OT.$.now(), - lastState = states[states.length-1], + lastState = states[states.length - 1], state = {delta: finishTime ? now - finishTime : 0}; if (!lastState || lastState.iceConnection !== pc.iceConnectionState) { @@ -13153,7 +13574,7 @@ var connectionStateLogger = function(pc) { pc.addEventListener('icegatheringstatechange', trackState, false); return { - stop: function () { + stop: function() { pc.removeEventListener('iceconnectionstatechange', trackState, false); pc.removeEventListener('signalingstatechange', trackState, false); pc.removeEventListener('icegatheringstatechange', trackState, false); @@ -13196,12 +13617,10 @@ if (!OTPlugin.isInstalled()) { // order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless NativeRTCSessionDescription = (window.mozRTCSessionDescription || window.RTCSessionDescription); -} -else { +} else { NativeRTCSessionDescription = OTPlugin.RTCSessionDescription; } - // Helper function to forward Ice Candidates via +messageDelegate+ var iceCandidateForwarder = function(messageDelegate) { return function(event) { @@ -13239,7 +13658,6 @@ OT.PeerConnection = function(config) { _messageDelegates = [], api = {}; - OT.$.eventing(api); // if ice servers doesn't exist Firefox will throw an exception. Chrome @@ -13271,7 +13689,7 @@ OT.PeerConnection = function(config) { // of OTPlugin, it's not used for vanilla WebRTC. Hopefully this can // be tidied up later. // - createPeerConnection = function (completion, localWebRtcStream) { + createPeerConnection = function(completion, localWebRtcStream) { if (_peerConnection) { completion.call(null, null, _peerConnection); return; @@ -13296,12 +13714,15 @@ OT.PeerConnection = function(config) { ] }; - OT.debug('Creating peer connection config "' + JSON.stringify(config) + '".'); if (!config.iceServers || config.iceServers.length === 0) { // This should never happen unless something is misconfigured OT.error('No ice servers present'); + OT.analytics.logEvent({ + action: 'Error', + variation: 'noIceServers' + }); } OT.$.createPeerConnection(config, pcConstraints, localWebRtcStream, @@ -13337,7 +13758,7 @@ OT.PeerConnection = function(config) { if (_peerConnection.oniceconnectionstatechange !== void 0) { var failedStateTimer; - _peerConnection.addEventListener('iceconnectionstatechange', function (event) { + _peerConnection.addEventListener('iceconnectionstatechange', function(event) { if (event.target.iceConnectionState === 'failed') { if (failedStateTimer) { clearTimeout(failedStateTimer); @@ -13346,12 +13767,18 @@ OT.PeerConnection = function(config) { // We wait 5 seconds and make sure that it's still in the failed state // before we trigger the error. This is because we sometimes see // 'failed' and then 'connected' afterwards. - failedStateTimer = setTimeout(function () { + failedStateTimer = setTimeout(function() { if (event.target.iceConnectionState === 'failed') { triggerError('The stream was unable to connect due to a network error.' + ' Make sure your connection isn\'t blocked by a firewall.', 'ICEWorkflow'); } }, 5000); + } else if (event.target.iceConnectionState === 'disconnected') { + OT.analytics.logEvent({ + action: 'attachEventsToPeerConnection', + variation: 'iceconnectionstatechange', + payload: 'disconnected' + }); } }, false); } @@ -13359,7 +13786,7 @@ OT.PeerConnection = function(config) { triggerPeerConnectionCompletion(null); }, - triggerPeerConnectionCompletion = function () { + triggerPeerConnectionCompletion = function() { while (_peerConnectionCompletionHandlers.length) { _peerConnectionCompletionHandlers.shift().call(null); } @@ -13394,7 +13821,7 @@ OT.PeerConnection = function(config) { _state = newState; OT.debug('PeerConnection.stateChange: ' + _state); - switch(_state) { + switch (_state) { case 'closed': tearDownPeerConnection(); break; @@ -13403,6 +13830,7 @@ OT.PeerConnection = function(config) { }, qosCallback = function(parsedStats) { + parsedStats.dataChannels = _channels.sampleQos(); api.trigger('qos', parsedStats); }, @@ -13435,14 +13863,12 @@ OT.PeerConnection = function(config) { // ICE Negotiation messages - // Relays a SDP payload (+sdp+), that is part of a message of type +messageType+ // via the registered message delegators relaySDP = function(messageType, sdp, uri) { delegateMessage(messageType, sdp, uri); }, - // Process an offer that processOffer = function(message) { var offer = new NativeRTCSessionDescription({type: 'offer', sdp: message.content.sdp}), @@ -13480,9 +13906,9 @@ OT.PeerConnection = function(config) { _answer = new NativeRTCSessionDescription({type: 'answer', sdp: message.content.sdp}); _peerConnection.setRemoteDescription(_answer, - function () { + function() { OT.debug('setRemoteDescription Success'); - }, function (errorReason) { + }, function(errorReason) { triggerError('Error while setting RemoteDescription ' + errorReason, 'SetRemoteDescription'); }); @@ -13506,6 +13932,7 @@ OT.PeerConnection = function(config) { createPeerConnection(function() { subscribeProcessor( _peerConnection, + config.numberOfSimulcastStreams, // Success: Relay Offer function(offer) { @@ -13566,7 +13993,7 @@ OT.PeerConnection = function(config) { OT.debug(message); - switch(type) { + switch (type) { case 'generateoffer': processSubscribe(message); break; @@ -13592,7 +14019,7 @@ OT.PeerConnection = function(config) { return api; }; - api.setIceServers = function (iceServers) { + api.setIceServers = function(iceServers) { if (iceServers) { config.iceServers = iceServers; } @@ -13605,7 +14032,7 @@ OT.PeerConnection = function(config) { api.unregisterMessageDelegate = function(delegateFn) { var index = OT.$.arrayIndexOf(_messageDelegates, delegateFn); - if ( index !== -1 ) { + if (index !== -1) { _messageDelegates.splice(index, 1); } return _messageDelegates.length; @@ -13616,18 +14043,21 @@ OT.PeerConnection = function(config) { }; api.getStats = function(callback) { - createPeerConnection(function() { - _getStatsAdapter(_peerConnection, callback); - }); + if (!_peerConnection) { + callback(new OT.$.Error('Cannot call getStats before there is a connection.', 1015)); + return; + } + _getStatsAdapter(_peerConnection, callback); }; - var waitForChannel = function waitForChannel (timesToWait, label, options, completion) { + var waitForChannel = function waitForChannel(timesToWait, label, options, completion) { var channel = _channels.get(label, options), err; if (!channel) { if (timesToWait > 0) { - setTimeout(OT.$.bind(waitForChannel, null, timesToWait-1, label, options, completion), 200); + setTimeout(OT.$.bind(waitForChannel, null, timesToWait - 1, label, options, completion), + 200); return; } @@ -13638,11 +14068,13 @@ OT.PeerConnection = function(config) { completion(err, channel); }; - api.getDataChannel = function (label, options, completion) { - createPeerConnection(function() { - // Wait up to 20 sec for the channel to appear, then fail - waitForChannel(100, label, options, completion); - }); + api.getDataChannel = function(label, options, completion) { + if (!_peerConnection) { + completion(new OT.$.Error('Cannot create a DataChannel before there is a connection.')); + return; + } + // Wait up to 20 sec for the channel to appear, then fail + waitForChannel(100, label, options, completion); }; var qos = new OT.PeerConnection.QOS(qosCallback); @@ -13676,13 +14108,13 @@ OT.PeerConnection = function(config) { // Get Stats using the older API. Used by all current versions // of Chrome. // - var parseStatsOldAPI = function parseStatsOldAPI (peerConnection, - prevStats, - currentStats, - completion) { + var parseStatsOldAPI = function parseStatsOldAPI(peerConnection, + prevStats, + currentStats, + completion) { /* this parses a result if there it contains the video bitrate */ - var parseVideoStats = function (result) { + var parseVideoStats = function(result) { if (result.stat('googFrameRateSent')) { currentStats.videoSentBytes = Number(result.stat('bytesSent')); currentStats.videoSentPackets = Number(result.stat('packetsSent')); @@ -13691,20 +14123,26 @@ OT.PeerConnection = function(config) { currentStats.videoFrameRate = Number(result.stat('googFrameRateInput')); currentStats.videoWidth = Number(result.stat('googFrameWidthSent')); currentStats.videoHeight = Number(result.stat('googFrameHeightSent')); + currentStats.videoFrameRateSent = Number(result.stat('googFrameRateSent')); + currentStats.videoWidthInput = Number(result.stat('googFrameWidthInput')); + currentStats.videoHeightInput = Number(result.stat('googFrameHeightInput')); currentStats.videoCodec = result.stat('googCodecName'); } else if (result.stat('googFrameRateReceived')) { currentStats.videoRecvBytes = Number(result.stat('bytesReceived')); currentStats.videoRecvPackets = Number(result.stat('packetsReceived')); currentStats.videoRecvPacketsLost = Number(result.stat('packetsLost')); currentStats.videoFrameRate = Number(result.stat('googFrameRateOutput')); + currentStats.videoFrameRateReceived = Number(result.stat('googFrameRateReceived')); + currentStats.videoFrameRateDecoded = Number(result.stat('googFrameRateDecoded')); currentStats.videoWidth = Number(result.stat('googFrameWidthReceived')); currentStats.videoHeight = Number(result.stat('googFrameHeightReceived')); currentStats.videoCodec = result.stat('googCodecName'); } + return null; }, - parseAudioStats = function (result) { + parseAudioStats = function(result) { if (result.stat('audioInputLevel')) { currentStats.audioSentPackets = Number(result.stat('packetsSent')); currentStats.audioSentPacketsLost = Number(result.stat('packetsLost')); @@ -13719,15 +14157,14 @@ OT.PeerConnection = function(config) { } }, - parseStatsReports = function (stats) { + parseStatsReports = function(stats) { if (stats.result) { var resultList = stats.result(); for (var resultIndex = 0; resultIndex < resultList.length; resultIndex++) { var result = resultList[resultIndex]; if (result.stat) { - - if(result.stat('googActiveConnection') === 'true') { + if (result.stat('googActiveConnection') === 'true') { currentStats.localCandidateType = result.stat('googLocalCandidateType'); currentStats.remoteCandidateType = result.stat('googRemoteCandidateType'); currentStats.transportType = result.stat('googTransportType'); @@ -13749,12 +14186,12 @@ OT.PeerConnection = function(config) { // Get Stats for the OT Plugin, newer than Chromes version, but // still not in sync with the spec. // - var parseStatsOTPlugin = function parseStatsOTPlugin (peerConnection, + var parseStatsOTPlugin = function parseStatsOTPlugin(peerConnection, prevStats, currentStats, completion) { - var onStatsError = function onStatsError (error) { + var onStatsError = function onStatsError(error) { completion(error); }, @@ -13763,7 +14200,7 @@ OT.PeerConnection = function(config) { // * avgAudioBitrate // * audioBytesTransferred // - parseAudioStats = function (statsReport) { + parseAudioStats = function(statsReport) { var lastBytesSent = prevStats.audioBytesTransferred || 0, transferDelta; @@ -13773,8 +14210,7 @@ OT.PeerConnection = function(config) { currentStats.audioSentPacketsLost = Number(statsReport.packetsLost); currentStats.audioRtt = Number(statsReport.googRtt); currentStats.audioCodec = statsReport.googCodecName; - } - else if (statsReport.audioOutputLevel) { + } else if (statsReport.audioOutputLevel) { currentStats.audioBytesTransferred = Number(statsReport.bytesReceived); currentStats.audioCodec = statsReport.googCodecName; } @@ -13782,7 +14218,7 @@ OT.PeerConnection = function(config) { if (currentStats.audioBytesTransferred) { transferDelta = currentStats.audioBytesTransferred - lastBytesSent; currentStats.avgAudioBitrate = Math.round(transferDelta * 8 / - (currentStats.period/1000)); + (currentStats.period / 1000)); } }, @@ -13792,7 +14228,7 @@ OT.PeerConnection = function(config) { // * avgVideoBitrate // * videoBytesTransferred // - parseVideoStats = function (statsReport) { + parseVideoStats = function(statsReport) { var lastBytesSent = prevStats.videoBytesTransferred || 0, transferDelta; @@ -13805,13 +14241,17 @@ OT.PeerConnection = function(config) { currentStats.videoCodec = statsReport.googCodecName; currentStats.videoWidth = Number(statsReport.googFrameWidthSent); currentStats.videoHeight = Number(statsReport.googFrameHeightSent); - } - else if (statsReport.googFrameHeightReceived) { + currentStats.videoFrameRateSent = Number(statsReport.googFrameRateSent); + currentStats.videoWidthInput = Number(statsReport.googFrameWidthInput); + currentStats.videoHeightInput = Number(statsReport.googFrameHeightInput); + } else if (statsReport.googFrameHeightReceived) { currentStats.videoRecvBytes = Number(statsReport.bytesReceived); currentStats.videoRecvPackets = Number(statsReport.packetsReceived); currentStats.videoRecvPacketsLost = Number(statsReport.packetsLost); currentStats.videoRtt = Number(statsReport.googRtt); currentStats.videoCodec = statsReport.googCodecName; + currentStats.videoFrameRateReceived = Number(statsReport.googFrameRateReceived); + currentStats.videoFrameRateDecoded = Number(statsReport.googFrameRateDecoded); currentStats.videoWidth = Number(statsReport.googFrameWidthReceived); currentStats.videoHeight = Number(statsReport.googFrameHeightReceived); } @@ -13819,13 +14259,13 @@ OT.PeerConnection = function(config) { if (currentStats.videoBytesTransferred) { transferDelta = currentStats.videoBytesTransferred - lastBytesSent; currentStats.avgVideoBitrate = Math.round(transferDelta * 8 / - (currentStats.period/1000)); + (currentStats.period / 1000)); } - if (statsReport.googFrameRateSent) { - currentStats.videoFrameRate = Number(statsReport.googFrameRateSent); - } else if (statsReport.googFrameRateReceived) { - currentStats.videoFrameRate = Number(statsReport.googFrameRateReceived); + if (statsReport.googFrameRateInput) { + currentStats.videoFrameRate = Number(statsReport.googFrameRateInput); + } else if (statsReport.googFrameRateOutput) { + currentStats.videoFrameRate = Number(statsReport.googFrameRateOutput); } }, @@ -13846,11 +14286,9 @@ OT.PeerConnection = function(config) { currentStats.localCandidateType = statsReport.googLocalCandidateType; currentStats.remoteCandidateType = statsReport.googRemoteCandidateType; currentStats.transportType = statsReport.googTransportType; - } - else if (isStatsForVideoTrack(statsReport)) { + } else if (isStatsForVideoTrack(statsReport)) { parseVideoStats(statsReport); - } - else { + } else { parseAudioStats(statsReport); } }); @@ -13859,37 +14297,36 @@ OT.PeerConnection = function(config) { }, onStatsError); }; - /// // Get Stats using the newer API. // - var parseStatsNewAPI = function parseStatsNewAPI (peerConnection, - prevStats, - currentStats, - completion) { + var parseStatsNewAPI = function parseStatsNewAPI(peerConnection, + prevStats, + currentStats, + completion) { - var onStatsError = function onStatsError (error) { + var onStatsError = function onStatsError(error) { completion(error); }, - parseAudioStats = function (result) { - if (result.type==='outboundrtp') { + parseAudioStats = function(result) { + if (result.type === 'outboundrtp') { currentStats.audioSentPackets = result.packetsSent; currentStats.audioSentPacketsLost = result.packetsLost; currentStats.audioSentBytes = result.bytesSent; - } else if (result.type==='inboundrtp') { + } else if (result.type === 'inboundrtp') { currentStats.audioRecvPackets = result.packetsReceived; currentStats.audioRecvPacketsLost = result.packetsLost; currentStats.audioRecvBytes = result.bytesReceived; } }, - parseVideoStats = function (result) { - if (result.type==='outboundrtp') { + parseVideoStats = function(result) { + if (result.type === 'outboundrtp') { currentStats.videoSentPackets = result.packetsSent; currentStats.videoSentPacketsLost = result.packetsLost; currentStats.videoSentBytes = result.bytesSent; - } else if (result.type==='inboundrtp') { + } else if (result.type === 'inboundrtp') { currentStats.videoRecvPackets = result.packetsReceived; currentStats.videoRecvPacketsLost = result.packetsLost; currentStats.videoRecvBytes = result.bytesReceived; @@ -13917,27 +14354,24 @@ OT.PeerConnection = function(config) { }, onStatsError); }; - - var parseQOS = function (peerConnection, prevStats, currentStats, completion) { + var parseQOS = function(peerConnection, prevStats, currentStats, completion) { if (OTPlugin.isInstalled()) { parseQOS = parseStatsOTPlugin; return parseStatsOTPlugin(peerConnection, prevStats, currentStats, completion); - } - else if (OT.$.env.name === 'Firefox' && OT.$.env.version >= 27) { + } else if (OT.$.env.name === 'Firefox' && OT.$.env.version >= 27) { parseQOS = parseStatsNewAPI; return parseStatsNewAPI(peerConnection, prevStats, currentStats, completion); - } - else { + } else { parseQOS = parseStatsOldAPI; return parseStatsOldAPI(peerConnection, prevStats, currentStats, completion); } }; - OT.PeerConnection.QOS = function (qosCallback) { + OT.PeerConnection.QOS = function(qosCallback) { var _creationTime = OT.$.now(), _peerConnection; - var calculateQOS = OT.$.bind(function calculateQOS (prevStats) { + var calculateQOS = OT.$.bind(function calculateQOS(prevStats, interval) { if (!_peerConnection) { // We don't have a PeerConnection yet, or we did and // it's been closed. Either way we're done. @@ -13952,7 +14386,7 @@ OT.PeerConnection = function(config) { period: Math.round(now - prevStats.timeStamp) }; - var onParsedStats = function (err, parsedStats) { + var onParsedStats = function(err, parsedStats) { if (err) { OT.error('Failed to Parse QOS Stats: ' + JSON.stringify(err)); return; @@ -13960,15 +14394,19 @@ OT.PeerConnection = function(config) { qosCallback(parsedStats, prevStats); + var nextInterval = interval === OT.PeerConnection.QOS.INITIAL_INTERVAL ? + OT.PeerConnection.QOS.INTERVAL - OT.PeerConnection.QOS.INITIAL_INTERVAL : + OT.PeerConnection.QOS.INTERVAL; + // Recalculate the stats - setTimeout(OT.$.bind(calculateQOS, null, parsedStats), OT.PeerConnection.QOS.INTERVAL); + setTimeout(OT.$.bind(calculateQOS, null, parsedStats, nextInterval), + interval); }; parseQOS(_peerConnection, prevStats, currentStats, onParsedStats); }, this); - - this.startCollecting = function (peerConnection) { + this.startCollecting = function(peerConnection) { if (!peerConnection || !peerConnection.getStats) { // It looks like this browser doesn't support getStats // Bail. @@ -13977,17 +14415,17 @@ OT.PeerConnection = function(config) { _peerConnection = peerConnection; - calculateQOS({ - timeStamp: OT.$.now() - }); + calculateQOS({timeStamp: OT.$.now()}, OT.PeerConnection.QOS.INITIAL_INTERVAL); }; - this.stopCollecting = function () { + this.stopCollecting = function() { _peerConnection = null; }; }; - // Recalculate the stats in 30 sec + // Send stats after 1 sec + OT.PeerConnection.QOS.INITIAL_INTERVAL = 1000; + // Recalculate the stats every 30 sec OT.PeerConnection.QOS.INTERVAL = 30000; })(); @@ -14030,9 +14468,13 @@ OT.PeerConnections = (function() { } }; })(); + // tb_require('../../helpers/helpers.js') // tb_require('../messaging/raptor/raptor.js') // tb_require('./peer_connections.js') +// tb_require('./set_certificates.js') + +/* global _setCertificates */ /* * Abstracts PeerConnection related stuff away from OT.Subscriber. @@ -14060,7 +14502,8 @@ OT.PeerConnections = (function() { OT.SubscriberPeerConnection = function(remoteConnection, session, stream, subscriber, properties) { - var _peerConnection, + var _subscriberPeerConnection = this, + _peerConnection, _destroyed = false, _hasRelayCandidates = false, _onPeerClosed, @@ -14091,7 +14534,7 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream, }; _relayMessageToPeer = OT.$.bind(function(type, payload) { - if (!_hasRelayCandidates){ + if (!_hasRelayCandidates) { var extractCandidates = type === OT.Raptor.Actions.CANDIDATE || type === OT.Raptor.Actions.OFFER || type === OT.Raptor.Actions.ANSWER || @@ -14103,7 +14546,7 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream, } } - switch(type) { + switch (type) { case OT.Raptor.Actions.ANSWER: case OT.Raptor.Actions.PRANSWER: this.trigger('connected'); @@ -14136,11 +14579,11 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream, return; } - for (var i=0, num=remoteStreams.length; i 0) { - OT.$.forEach(_peerConnection.remoteStreams(), _onRemoteStreamAdded, this); - } else if (numDelegates === 1) { - // We only bother with the PeerConnection negotiation if we don't already - // have a remote stream. - - var channelsToSubscribeTo; - - if (properties.subscribeToVideo || properties.subscribeToAudio) { - var audio = stream.getChannelsOfType('audio'), - video = stream.getChannelsOfType('video'); - - channelsToSubscribeTo = OT.$.map(audio, function(channel) { - return { - id: channel.id, - type: channel.type, - active: properties.subscribeToAudio - }; - }).concat(OT.$.map(video, function(channel) { - return { - id: channel.id, - type: channel.type, - active: properties.subscribeToVideo, - restrictFrameRate: properties.restrictFrameRate !== void 0 ? - properties.restrictFrameRate : false - }; - })); + _setCertificates(pcConfig, function(err, pcConfigWithCerts) { + if (err) { + completion(err); + return; } - session._.subscriberCreate(stream, subscriber, channelsToSubscribeTo, - OT.$.bind(function(err, message) { - if (err) { - this.trigger('error', err.message, this, 'Subscribe'); + _peerConnection = OT.PeerConnections.add( + remoteConnection, + stream.streamId, + pcConfigWithCerts + ); + + _peerConnection.on({ + close: _onPeerClosed, + streamAdded: _onRemoteStreamAdded, + streamRemoved: _onRemoteStreamRemoved, + error: _onPeerError, + qos: _onQOS + }, _subscriberPeerConnection); + + var numDelegates = _peerConnection.registerMessageDelegate(_relayMessageToPeer); + + // If there are already remoteStreams, add them immediately + if (_peerConnection.remoteStreams().length > 0) { + OT.$.forEach( + _peerConnection.remoteStreams(), + _onRemoteStreamAdded, + _subscriberPeerConnection + ); + } else if (numDelegates === 1) { + // We only bother with the PeerConnection negotiation if we don't already + // have a remote stream. + + var channelsToSubscribeTo; + + if (properties.subscribeToVideo || properties.subscribeToAudio) { + var audio = stream.getChannelsOfType('audio'), + video = stream.getChannelsOfType('video'); + + channelsToSubscribeTo = OT.$.map(audio, function(channel) { + return { + id: channel.id, + type: channel.type, + active: properties.subscribeToAudio + }; + }).concat(OT.$.map(video, function(channel) { + var props = { + id: channel.id, + type: channel.type, + active: properties.subscribeToVideo, + restrictFrameRate: properties.restrictFrameRate !== void 0 ? + properties.restrictFrameRate : false + }; + + if (properties.maxFrameRate !== void 0) { + props.maxFrameRate = parseFloat(properties.maxFrameRate); + } + + if (properties.maxHeight !== void 0) { + props.maxHeight = parseInt(properties.maxHeight, 10); + } + + if (properties.maxWidth !== void 0) { + props.maxWidth = parseInt(properties.maxWidth, 10); + } + + return props; + })); + } + + session._.subscriberCreate( + stream, + subscriber, + channelsToSubscribeTo, + function(err, message) { + if (err) { + _subscriberPeerConnection.trigger( + 'error', + err.message, + _subscriberPeerConnection, + 'Subscribe' + ); + } + if (_peerConnection) { + _peerConnection.setIceServers(OT.Raptor.parseIceServers(message)); + } } - if (_peerConnection) { - _peerConnection.setIceServers(OT.Raptor.parseIceServers(message)); - } - }, this)); - } + ); + + completion(undefined, _subscriberPeerConnection); + } + }); }; }; // tb_require('../../helpers/helpers.js') // tb_require('../messaging/raptor/raptor.js') // tb_require('./peer_connections.js') +// tb_require('./set_certificates.js') +/* global _setCertificates */ /* * Abstracts PeerConnection related stuff away from OT.Publisher. @@ -14282,8 +14769,10 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream, * * connected * * disconnected */ -OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRTCStream, channels) { - var _peerConnection, +OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRTCStream, channels, + numberOfSimulcastStreams) { + var _publisherPeerConnection = this, + _peerConnection, _hasRelayCandidates = false, _subscriberId = session._.subscriberMap[remoteConnection.id + '_' + streamId], _onPeerClosed, @@ -14304,7 +14793,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT }; _relayMessageToPeer = OT.$.bind(function(type, payload, uri) { - if (!_hasRelayCandidates){ + if (!_hasRelayCandidates) { var extractCandidates = type === OT.Raptor.Actions.CANDIDATE || type === OT.Raptor.Actions.OFFER || type === OT.Raptor.Actions.ANSWER || @@ -14316,7 +14805,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT } } - switch(type) { + switch (type) { case OT.Raptor.Actions.ANSWER: case OT.Raptor.Actions.PRANSWER: if (session.sessionInfo.p2pEnabled) { @@ -14343,7 +14832,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT } }, this); - _onQOS = OT.$.bind(function _onQOS (parsedStats, prevStats) { + _onQOS = OT.$.bind(function _onQOS(parsedStats, prevStats) { this.trigger('qos', remoteConnection, parsedStats, prevStats); }, this); @@ -14351,7 +14840,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT /// Public API - this.getDataChannel = function (label, options, completion) { + this.getDataChannel = function(label, options, completion) { _peerConnection.getDataChannel(label, options, completion); }; @@ -14370,21 +14859,33 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT }; // Init - this.init = function(iceServers) { - _peerConnection = OT.PeerConnections.add(remoteConnection, streamId, { + this.init = function(iceServers, completion) { + var pcConfig = { iceServers: iceServers, - channels: channels + channels: channels, + numberOfSimulcastStreams: numberOfSimulcastStreams + }; + + _setCertificates(pcConfig, function(err, pcConfigWithCerts) { + if (err) { + completion(err); + return; + } + + _peerConnection = OT.PeerConnections.add(remoteConnection, streamId, pcConfigWithCerts); + + _peerConnection.on({ + close: _onPeerClosed, + error: _onPeerError, + qos: _onQOS + }, _publisherPeerConnection); + + _peerConnection.registerMessageDelegate(_relayMessageToPeer); + _peerConnection.addLocalStream(webRTCStream); + + completion(undefined, _publisherPeerConnection); }); - _peerConnection.on({ - close: _onPeerClosed, - error: _onPeerError, - qos: _onQOS - }, this); - - _peerConnection.registerMessageDelegate(_relayMessageToPeer); - _peerConnection.addLocalStream(webRTCStream); - this.remoteConnection = function() { return remoteConnection; }; @@ -14392,7 +14893,6 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT this.hasRelayCandidates = function() { return _hasRelayCandidates; }; - }; this.getSenders = function() { @@ -14400,51 +14900,13 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT }; }; - // tb_require('../helpers.js') // tb_require('./web_rtc.js') +// tb_require('./videoContentResizesMixin.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ -/* global OT, OTPlugin */ - -var videoContentResizesMixin = function(self, domElement) { - - var width = domElement.videoWidth, - height = domElement.videoHeight, - stopped = true; - - function actor() { - if (stopped) { - return; - } - if (width !== domElement.videoWidth || height !== domElement.videoHeight) { - self.trigger('videoDimensionsChanged', - { width: width, height: height }, - { width: domElement.videoWidth, height: domElement.videoHeight } - ); - width = domElement.videoWidth; - height = domElement.videoHeight; - } - waiter(); - } - - function waiter() { - self.whenTimeIncrements(function() { - window.requestAnimationFrame(actor); - }); - } - - self.startObservingSize = function() { - stopped = false; - waiter(); - }; - - self.stopObservingSize = function() { - stopped = true; - }; - -}; +/* global OT, OTPlugin, videoContentResizesMixin */ (function(window) { @@ -14487,12 +14949,12 @@ var videoContentResizesMixin = function(self, domElement) { // // OT.VideoElement = function(/* optional */ options/*, optional errorHandler*/) { - var _options = OT.$.defaults( options && !OT.$.isFunction(options) ? options : {}, { + var _options = OT.$.defaults(options && !OT.$.isFunction(options) ? options : {}, { fallbackText: 'Sorry, Web RTC is not available in your browser' }), - errorHandler = OT.$.isFunction(arguments[arguments.length-1]) ? - arguments[arguments.length-1] : void 0, + errorHandler = OT.$.isFunction(arguments[arguments.length - 1]) ? + arguments[arguments.length - 1] : void 0, orientationHandler = OT.$.bind(function(orientation) { this.trigger('orientationChanged', orientation); @@ -14503,7 +14965,7 @@ var videoContentResizesMixin = function(self, domElement) { new NativeDOMVideoElement(_options, errorHandler, orientationHandler), _streamBound = false, _stream, - _preInitialisedVolue; + _preInitialisedVolume; OT.$.eventing(this); @@ -14590,9 +15052,9 @@ var videoContentResizesMixin = function(self, domElement) { _streamBound = true; - if (_preInitialisedVolue) { - this.setAudioVolume(_preInitialisedVolue); - _preInitialisedVolue = null; + if (typeof _preInitialisedVolume !== 'undefined') { + this.setAudioVolume(_preInitialisedVolume); + _preInitialisedVolume = undefined; } _videoElement.on('aspectRatioAvailable', @@ -14607,25 +15069,32 @@ var videoContentResizesMixin = function(self, domElement) { this.unbindStream = function() { if (!_stream) return this; + _stream.onended = null; _stream = null; _videoElement.unbindStream(); return this; }; - this.setAudioVolume = function (value) { - if (_streamBound) _videoElement.setAudioVolume( OT.$.roundFloat(value / 100, 2) ); - else _preInitialisedVolue = value; + this.setAudioVolume = function(value) { + if (_streamBound) _videoElement.setAudioVolume(OT.$.roundFloat(value / 100, 2)); + else _preInitialisedVolume = parseFloat(value); return this; }; - this.getAudioVolume = function () { - if (_streamBound) return parseInt(_videoElement.getAudioVolume() * 100, 10); - else return _preInitialisedVolue || 50; + this.getAudioVolume = function() { + if (_streamBound) { + return parseInt(_videoElement.getAudioVolume() * 100, 10); + } + + if (typeof _preInitialisedVolume !== 'undefined') { + return _preInitialisedVolume; + } + + return 50; }; - - this.whenTimeIncrements = function (callback, context) { + this.whenTimeIncrements = function(callback, context) { _videoElement.whenTimeIncrements(callback, context); return this; }; @@ -14635,7 +15104,7 @@ var videoContentResizesMixin = function(self, domElement) { return this; }; - this.destroy = function () { + this.destroy = function() { // unbind all events so they don't fire after the object is dead this.off(); @@ -14644,9 +15113,9 @@ var videoContentResizesMixin = function(self, domElement) { }; }; - var PluginVideoElement = function PluginVideoElement (options, - errorHandler, - orientationChangedHandler) { + var PluginVideoElement = function PluginVideoElement(options, + errorHandler, + orientationChangedHandler) { var _videoProxy, _parentDomElement, _ratioAvailable = false, @@ -14742,7 +15211,7 @@ var videoContentResizesMixin = function(self, domElement) { }; this.onRatioAvailable = function(callback) { - if(_ratioAvailable) { + if (_ratioAvailable) { callback(); } else { _ratioAvailableListeners.push(callback); @@ -14756,10 +15225,9 @@ var videoContentResizesMixin = function(self, domElement) { }; }; - - var NativeDOMVideoElement = function NativeDOMVideoElement (options, - errorHandler, - orientationChangedHandler) { + var NativeDOMVideoElement = function NativeDOMVideoElement(options, + errorHandler, + orientationChangedHandler) { var _domElement, _videoElementMovedWarning = false; @@ -14776,7 +15244,7 @@ var videoContentResizesMixin = function(self, domElement) { // unfortunate. This function plays the video again and is triggered // on the pause event. _playVideoOnPause = function() { - if(!_videoElementMovedWarning) { + if (!_videoElementMovedWarning) { OT.warn('Video element paused, auto-resuming. If you intended to do this, ' + 'use publishVideo(false) or subscribeToVideo(false) instead.'); @@ -14786,7 +15254,6 @@ var videoContentResizesMixin = function(self, domElement) { _domElement.play(); }; - _domElement = createNativeVideoElement(options.fallbackText, options.attributes); // dirty but it is the only way right now to get the aspect ratio in FF @@ -14839,7 +15306,7 @@ var videoContentResizesMixin = function(self, domElement) { document.body.appendChild(canvas); try { canvas.getContext('2d').drawImage(_domElement, 0, 0, canvas.width, canvas.height); - } catch(err) { + } catch (err) { OT.warn('Cannot get image data yet'); return null; } @@ -14859,29 +15326,42 @@ var videoContentResizesMixin = function(self, domElement) { // Bind a stream to the video element. this.bindToStream = function(webRtcStream, completion) { var _this = this; + bindStreamToNativeVideoElement(_domElement, webRtcStream, function(err) { if (err) { completion(err); return; } + if (!_domElement) { + completion('Can\'t bind because _domElement no longer exists'); + return; + } + _this.startObservingSize(); - webRtcStream.onended = function() { + function handleEnded() { + webRtcStream.onended = null; + + if (_domElement) { + _domElement.onended = null; + } + _this.trigger('mediaStopped', this); - }; - - - if (_domElement) { - _domElement.addEventListener('error', _onVideoError, false); } + + // OPENTOK-22428: Firefox doesn't emit the ended event on the webRtcStream when the user + // stops sharing their camera, but we do get the ended event on the video element. + webRtcStream.onended = handleEnded; + _domElement.onended = handleEnded; + + _domElement.addEventListener('error', _onVideoError, false); completion(null); }); return this; }; - // Unbind the currently bound stream from the video element. this.unbindStream = function() { if (_domElement) { @@ -14951,7 +15431,7 @@ var videoContentResizesMixin = function(self, domElement) { }; this.onRatioAvailable = function(callback) { - if(ratioAvailable) { + if (ratioAvailable) { callback(); } else { ratioAvailableListeners.push(callback); @@ -14959,7 +15439,7 @@ var videoContentResizesMixin = function(self, domElement) { }; }; -/// Private Helper functions + /// Private Helper functions // A mixin to create the orientation API implementation on +self+ // +getDomElementCallback+ is a function that the mixin will call when it wants to @@ -14968,10 +15448,10 @@ var videoContentResizesMixin = function(self, domElement) { // +initialOrientation+ sets the initial orientation (shockingly), it's currently unused // so the initial value is actually undefined. // - var canBeOrientatedMixin = function canBeOrientatedMixin (self, - getDomElementCallback, - orientationChangedHandler, - initialOrientation) { + var canBeOrientatedMixin = function canBeOrientatedMixin(self, + getDomElementCallback, + orientationChangedHandler, + initialOrientation) { var _orientation = initialOrientation; OT.$.defineProperties(self, { @@ -14991,7 +15471,7 @@ var videoContentResizesMixin = function(self, domElement) { var transform = VideoOrientationTransforms[orientation.videoOrientation] || VideoOrientationTransforms.ROTATED_NORMAL; - switch(OT.$.env.name) { + switch (OT.$.env.name) { case 'Chrome': case 'Safari': getDomElementCallback().style.webkitTransform = transform; @@ -15000,8 +15480,7 @@ var videoContentResizesMixin = function(self, domElement) { case 'IE': if (OT.$.env.version >= 9) { getDomElementCallback().style.msTransform = transform; - } - else { + } else { // So this basically defines matrix that represents a rotation // of a single vector in a 2d basis. // @@ -15027,13 +15506,12 @@ var videoContentResizesMixin = function(self, domElement) { // element.filters.item(0).M22 = costheta; element.style.filter = 'progid:DXImageTransform.Microsoft.Matrix(' + - 'M11='+costheta+',' + - 'M12='+(-sintheta)+',' + - 'M21='+sintheta+',' + - 'M22='+costheta+',SizingMethod=\'auto expand\')'; + 'M11=' + costheta + ',' + + 'M12=' + (-sintheta) + ',' + + 'M21=' + sintheta + ',' + + 'M22=' + costheta + ',SizingMethod=\'auto expand\')'; } - break; default: @@ -15078,7 +15556,7 @@ var videoContentResizesMixin = function(self, domElement) { } for (var key in attributes) { - if(!attributes.hasOwnProperty(key)) { + if (!attributes.hasOwnProperty(key)) { continue; } videoElement.setAttribute(key, attributes[key]); @@ -15088,7 +15566,6 @@ var videoContentResizesMixin = function(self, domElement) { return videoElement; } - // See http://www.w3.org/TR/2010/WD-html5-20101019/video.html#error-codes var _videoErrorCodes = {}; @@ -15117,26 +15594,27 @@ var videoContentResizesMixin = function(self, domElement) { loadedEvent = webRtcStream.getVideoTracks().length > minVideoTracksForTimeUpdate ? 'timeupdate' : 'loadedmetadata'; - var cleanup = function cleanup () { + var cleanup = function cleanup() { clearTimeout(timeout); videoElement.removeEventListener(loadedEvent, onLoad, false); videoElement.removeEventListener('error', onError, false); webRtcStream.onended = null; + videoElement.onended = null; }, - onLoad = function onLoad () { + onLoad = function onLoad() { cleanup(); completion(null); }, - onError = function onError (event) { + onError = function onError(event) { cleanup(); unbindNativeStream(videoElement); completion('There was an unexpected problem with the Video Stream: ' + videoElementErrorCodeToStr(event.target.error.code)); }, - onStoppedLoading = function onStoppedLoading () { + onStoppedLoading = function onStoppedLoading() { // The stream ended before we fully bound it. Maybe the other end called // stop on it or something else went wrong. cleanup(); @@ -15144,10 +15622,6 @@ var videoContentResizesMixin = function(self, domElement) { completion('Stream ended while trying to bind it to a video element.'); }; - videoElement.addEventListener(loadedEvent, onLoad, false); - videoElement.addEventListener('error', onError, false); - webRtcStream.onended = onStoppedLoading; - // The official spec way is 'srcObject', we are slowly converging there. if (videoElement.srcObject !== void 0) { videoElement.srcObject = webRtcStream; @@ -15156,10 +15630,16 @@ var videoContentResizesMixin = function(self, domElement) { } else { videoElement.src = window.URL.createObjectURL(webRtcStream); } + + videoElement.addEventListener(loadedEvent, onLoad, false); + videoElement.addEventListener('error', onError, false); + webRtcStream.onended = onStoppedLoading; + videoElement.onended = onStoppedLoading; } - function unbindNativeStream(videoElement) { + videoElement.onended = null; + if (videoElement.srcObject !== void 0) { videoElement.srcObject = null; } else if (videoElement.mozSrcObject !== void 0) { @@ -15169,7 +15649,6 @@ var videoContentResizesMixin = function(self, domElement) { } } - })(window); // tb_require('../helpers.js') @@ -15180,9 +15659,9 @@ var videoContentResizesMixin = function(self, domElement) { /* global OT */ !(function() { -/*global OT:true */ + /*global OT:true */ - var defaultAspectRatio = 4.0/3.0, + var defaultAspectRatio = 4.0 / 3.0, miniWidth = 128, miniHeight = 128, microWidth = 64, @@ -15267,7 +15746,6 @@ var videoContentResizesMixin = function(self, domElement) { var cssProps = {left: '', top: ''}; - if (OTPlugin.isInstalled()) { intrinsicRatio = intrinsicRatio || defaultAspectRatio; @@ -15310,12 +15788,12 @@ var videoContentResizesMixin = function(self, domElement) { var w = parseInt(width, 10), h = parseInt(height, 10); - if(w < microWidth || h < microHeight) { + if (w < microWidth || h < microHeight) { OT.$.addClass(container, 'OT_micro'); } else { OT.$.removeClass(container, 'OT_micro'); } - if(w < miniWidth || h < miniHeight) { + if (w < miniWidth || h < miniHeight) { OT.$.addClass(container, 'OT_mini'); } else { OT.$.removeClass(container, 'OT_mini'); @@ -15350,16 +15828,16 @@ var videoContentResizesMixin = function(self, domElement) { container.style.backgroundColor = '#000000'; document.body.appendChild(container); } else { - if(!(insertMode == null || insertMode === 'replace')) { + if (!(insertMode == null || insertMode === 'replace')) { var placeholder = document.createElement('div'); placeholder.id = ('OT_' + OT.$.uuid()); - if(insertMode === 'append') { + if (insertMode === 'append') { container.appendChild(placeholder); container = placeholder; - } else if(insertMode === 'before') { + } else if (insertMode === 'before') { container.parentNode.insertBefore(placeholder, container); container = placeholder; - } else if(insertMode === 'after') { + } else if (insertMode === 'after') { container.parentNode.insertBefore(placeholder, container.nextSibling); container = placeholder; } @@ -15399,13 +15877,13 @@ var videoContentResizesMixin = function(self, domElement) { height = properties.height; if (width) { - if (typeof(width) === 'number') { + if (typeof width === 'number') { width = width + 'px'; } } if (height) { - if (typeof(height) === 'number') { + if (typeof height === 'number') { height = height + 'px'; } } @@ -15463,7 +15941,6 @@ var videoContentResizesMixin = function(self, domElement) { } })[0]; - // @todo observe if the video container or the video element get removed // if they do we should do some cleanup videoObserver = OT.$.observeNodeOrChildNodeRemoval(container, function(removedNodes) { @@ -15544,6 +16021,11 @@ var videoContentResizesMixin = function(self, domElement) { widgetView.bindVideo = function(webRTCStream, options, completion) { // remove the old video element if it exists // @todo this might not be safe, publishers/subscribers use this as well... + + if (typeof options.audioVolume !== 'undefined') { + options.audioVolume = parseFloat(options.audioVolume); + } + if (videoElement) { videoElement.destroy(); videoElement = null; @@ -15555,7 +16037,9 @@ var videoContentResizesMixin = function(self, domElement) { var video = new OT.VideoElement({attributes: options}, onError); // Initialize the audio volume - if (options.audioVolume) video.setAudioVolume(options.audioVolume); + if (typeof options.audioVolume !== 'undefined') { + video.setAudioVolume(options.audioVolume); + } // makes the incoming audio streams take priority (will impact only FF OS for now) video.audioChannelType('telephony'); @@ -15612,7 +16096,7 @@ var videoContentResizesMixin = function(self, domElement) { return !OT.$.isDisplayNone(posterContainer); }, set: function(newValue) { - if(newValue) { + if (newValue) { OT.$.show(posterContainer); } else { OT.$.hide(posterContainer); @@ -15673,7 +16157,7 @@ var videoContentResizesMixin = function(self, domElement) { (helpMsg ? ' ' + helpMsg + '' : '') + '

'; OT.$.addClass(container, classNames || 'OT_subscriber_error'); - if(container.querySelector('p').offsetHeight > container.offsetHeight) { + if (container.querySelector('p').offsetHeight > container.offsetHeight) { container.querySelector('span').style.display = 'none'; } }; @@ -15694,13 +16178,8 @@ if (!OT.properties) { 'properties file.'); } -OT.useSSL = function () { - return OT.properties.supportSSL && (window.location.protocol.indexOf('https') >= 0 || - window.location.protocol.indexOf('chrome-extension') >= 0); -}; - // Consumes and overwrites OT.properties. Makes it better and stronger! -OT.properties = function(properties) { +OT.properties = (function(properties) { var props = OT.$.clone(properties); props.debug = properties.debug === 'true' || properties.debug === true; @@ -15716,7 +16195,7 @@ OT.properties = function(properties) { } if (!props.assetURL) { - if (OT.useSSL()) { + if (OT.properties.supportSSL) { props.assetURL = props.cdnURLSSL + '/webrtc/' + props.version; } else { props.assetURL = props.cdnURL + '/webrtc/' + props.version; @@ -15733,7 +16212,7 @@ OT.properties = function(properties) { if (!props.cssURL) props.cssURL = props.assetURL + '/css/TB.min.css'; return props; -}(OT.properties); +})(OT.properties); // tb_require('../helpers.js') @@ -15745,11 +16224,11 @@ OT.properties = function(properties) { var currentGuidStorage, currentGuid; - var isInvalidStorage = function isInvalidStorage (storageInterface) { + var isInvalidStorage = function isInvalidStorage(storageInterface) { return !(OT.$.isFunction(storageInterface.get) && OT.$.isFunction(storageInterface.set)); }; - var getClientGuid = function getClientGuid (completion) { + var getClientGuid = function getClientGuid(completion) { if (currentGuid) { completion(null, currentGuid); return; @@ -15798,7 +16277,7 @@ OT.properties = function(properties) { * OT.overrideGuidStorage(new ComplexStorage()); * */ - OT.overrideGuidStorage = function (storageInterface) { + OT.overrideGuidStorage = function(storageInterface) { if (isInvalidStorage(storageInterface)) { throw new Error('The storageInterface argument does not seem to be valid, ' + 'it must implement get and set methods'); @@ -15824,7 +16303,7 @@ OT.properties = function(properties) { }; if (!OT._) OT._ = {}; - OT._.getClientGuid = function (completion) { + OT._.getClientGuid = function(completion) { getClientGuid(function(error, guid) { if (error) { completion(error); @@ -15843,8 +16322,7 @@ OT.properties = function(properties) { currentGuid = guid; }); - } - else if (!currentGuid) { + } else if (!currentGuid) { currentGuid = guid; } @@ -15852,7 +16330,6 @@ OT.properties = function(properties) { }); }; - // Implement our default storage mechanism, which sets/gets a cookie // called 'opentok_client_id' OT.overrideGuidStorage({ @@ -15926,7 +16403,7 @@ OT.properties = function(properties) { mapVendorErrorName = function mapVendorErrorName(vendorErrorName, vendorErrors) { var errorName, errorMessage; - if(vendorErrors.hasOwnProperty(vendorErrorName)) { + if (vendorErrors.hasOwnProperty(vendorErrorName)) { errorName = vendorErrors[vendorErrorName]; } else { // This doesn't map to a known error from the Media Capture spec, it's @@ -15934,7 +16411,7 @@ OT.properties = function(properties) { errorName = vendorErrorName; } - if(gumNamesToMessages.hasOwnProperty(errorName)) { + if (gumNamesToMessages.hasOwnProperty(errorName)) { errorMessage = gumNamesToMessages[errorName]; } else { errorMessage = 'Unknown Error while getting user media'; @@ -15971,7 +16448,7 @@ OT.properties = function(properties) { if (!constraints || !OT.$.isObject(constraints)) return true; for (var key in constraints) { - if(!constraints.hasOwnProperty(key)) { + if (!constraints.hasOwnProperty(key)) { continue; } if (constraints[key]) return false; @@ -15980,7 +16457,6 @@ OT.properties = function(properties) { return true; }; - // A wrapper for the builtin navigator.getUserMedia. In addition to the usual // getUserMedia behaviour, this helper method also accepts a accessDialogOpened // and accessDialogClosed callback. @@ -16016,7 +16492,7 @@ OT.properties = function(properties) { var getUserMedia = nativeGetUserMedia; - if(OT.$.isFunction(customGetUserMedia)) { + if (OT.$.isFunction(customGetUserMedia)) { getUserMedia = customGetUserMedia; } @@ -16100,6 +16576,138 @@ OT.properties = function(properties) { })(); +// tb_require('../helpers.js') +// tb_require('./web_rtc.js') + +// Web OT Helpers +!(function(window) { + + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT */ + + /// + // Device Helpers + // + // Support functions to enumerating and guerying device info + // + + // Map all the various formats for device kinds + // to the ones that our API exposes. Also includes + // an identity map. + var deviceKindsMap = { + audio: 'audioInput', + video: 'videoInput', + audioinput: 'audioInput', + videoinput: 'videoInput', + audioInput: 'audioInput', + videoInput: 'videoInput' + }; + + var getNativeEnumerateDevices = function() { + if (navigator.mediaDevices) { + return navigator.mediaDevices.enumerateDevices; + } else if (OT.$.hasCapabilities('otplugin')) { + return OTPlugin.mediaDevices.enumerateDevices; + } else if (window.MediaStreamTrack && window.MediaStreamTrack.getSources) { + return window.MediaStreamTrack.getSources; + } + }; + + var enumerateDevices = function(completion) { + var fn = getNativeEnumerateDevices(); + + if (navigator.mediaDevices && OT.$.isFunction(fn)) { + // De-promisify the newer style APIs. We aren't ready for Promises yet... + fn = function(completion) { + navigator.mediaDevices.enumerateDevices().then(function(devices) { + completion(void 0, devices); + })['catch'](function(err) { + completion(err); + }); + }; + } else if (window.MediaStreamTrack && window.MediaStreamTrack.getSources) { + fn = function(completion) { + window.MediaStreamTrack.getSources(function(devices) { + completion(void 0, devices.map(function(device) { + return OT.$.clone(device); + })); + }); + }; + } + + return fn(completion); + }; + + var fakeShouldAskForDevices = function fakeShouldAskForDevices(callback) { + setTimeout(OT.$.bind(callback, null, { video: true, audio: true })); + }; + + // Indicates whether this browser supports the enumerateDevices (getSources) API. + // + OT.$.registerCapability('enumerateDevices', function() { + return OT.$.isFunction(getNativeEnumerateDevices()); + }); + + OT.$.getMediaDevices = function(completion, customGetDevices) { + if (!OT.$.hasCapabilities('enumerateDevices')) { + completion(new Error('This browser does not support enumerateDevices APIs')); + return; + } + + var getDevices = OT.$.isFunction(customGetDevices) ? customGetDevices + : enumerateDevices; + + getDevices(function(err, devices) { + if (err) { + completion(err); + return; + } + + // Normalise the device kinds + var filteredDevices = OT.$(devices).map(function(device) { + return { + deviceId: device.deviceId || device.id, + label: device.label, + kind: deviceKindsMap[device.kind] + }; + }).filter(function(device) { + return device.kind === 'audioInput' || device.kind === 'videoInput'; + }); + + completion(void 0, filteredDevices.get()); + }); + }; + + OT.$.shouldAskForDevices = function(callback) { + if (OT.$.hasCapabilities('enumerateDevices')) { + OT.$.getMediaDevices(function(err, devices) { + if (err) { + // There was an error. It may be temporally. Just assume + // all devices exist for now. + fakeShouldAskForDevices(callback); + return; + } + + var hasAudio = OT.$.some(devices, function(device) { + return device.kind === 'audioInput'; + }); + + var hasVideo = OT.$.some(devices, function(device) { + return device.kind === 'videoInput'; + }); + + callback.call(null, { video: hasVideo, audio: hasAudio }); + }); + + } else { + // This environment can't enumerate devices anyway, so we'll memorise this result. + OT.$.shouldAskForDevices = fakeShouldAskForDevices; + OT.$.shouldAskForDevices(callback); + } + }; +})(window); + // tb_require('../helpers.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, @@ -16139,7 +16747,7 @@ OT.properties = function(properties) { var remainingStylesheets = allURLs.length; OT.$.forEach(allURLs, function(stylesheetUrl) { addCss(document, stylesheetUrl, function() { - if(--remainingStylesheets <= 0) { + if (--remainingStylesheets <= 0) { callback(); } }); @@ -16154,7 +16762,7 @@ OT.properties = function(properties) { return el; }; - var checkBoxElement = function (classes, nameAndId, onChange) { + var checkBoxElement = function(classes, nameAndId, onChange) { var checkbox = templateElement.call(this, '', null, 'input'); checkbox = OT.$(checkbox).on('change', onChange); @@ -16235,8 +16843,7 @@ OT.properties = function(properties) { function onToggleEULA() { if (checkbox.checked) { enableButtons(); - } - else { + } else { disableButtons(); } } @@ -16399,18 +17006,18 @@ OT.properties = function(properties) { addDialogCSS(document, [], function() { document.body.appendChild(root); - if(progressValue != null) { + if (progressValue != null) { modal.setUpdateProgress(progressValue); } }); })); modal.setUpdateProgress = function(newProgress) { - if(progressBar && progressText) { - if(newProgress > 99) { + if (progressBar && progressText) { + if (newProgress > 99) { OT.$.css(progressBar, 'width', ''); progressText.innerHTML = '100%'; - } else if(newProgress < 1) { + } else if (newProgress < 1) { OT.$.css(progressBar, 'width', '0%'); progressText.innerHTML = '0%'; } else { @@ -16439,7 +17046,7 @@ OT.properties = function(properties) { var msgs; - if(error) { + if (error) { msgs = ['Update Failed.', error + '' || 'NO ERROR']; } else { msgs = ['Update Complete.', @@ -16469,78 +17076,8 @@ OT.properties = function(properties) { }; - })(); -// tb_require('../helpers.js') -// tb_require('./web_rtc.js') - -// Web OT Helpers -!(function(window) { - - /* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ - /* global OT */ - - /// - // Device Helpers - // - // Support functions to enumerating and guerying device info - // - - var chromeToW3CDeviceKinds = { - audio: 'audioInput', - video: 'videoInput' - }; - - - OT.$.shouldAskForDevices = function(callback) { - var MST = window.MediaStreamTrack; - - if(MST != null && OT.$.isFunction(MST.getSources)) { - window.MediaStreamTrack.getSources(function(sources) { - var hasAudio = sources.some(function(src) { - return src.kind === 'audio'; - }); - - var hasVideo = sources.some(function(src) { - return src.kind === 'video'; - }); - - callback.call(null, { video: hasVideo, audio: hasAudio }); - }); - - } else { - // This environment can't enumerate devices anyway, so we'll memorise this result. - OT.$.shouldAskForDevices = function(callback) { - setTimeout(OT.$.bind(callback, null, { video: true, audio: true })); - }; - - OT.$.shouldAskForDevices(callback); - } - }; - - - OT.$.getMediaDevices = function(callback) { - if(OT.$.hasCapabilities('getMediaDevices')) { - window.MediaStreamTrack.getSources(function(sources) { - var filteredSources = OT.$.filter(sources, function(source) { - return chromeToW3CDeviceKinds[source.kind] != null; - }); - callback(void 0, OT.$.map(filteredSources, function(source) { - return { - deviceId: source.id, - label: source.label, - kind: chromeToW3CDeviceKinds[source.kind] - }; - })); - }); - } else { - callback(new Error('This browser does not support getMediaDevices APIs')); - } - }; - -})(window); // tb_require('../helpers.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, @@ -16557,140 +17094,6 @@ var loadCSS = function loadCSS(cssURL) { head.appendChild(style); }; -// tb_require('../helpers.js') - -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global OTPlugin, OT */ - -/// -// Capabilities -// -// Support functions to query browser/client Media capabilities. -// - - -// Indicates whether this client supports the getUserMedia -// API. -// -OT.$.registerCapability('getUserMedia', function() { - if (OT.$.env === 'Node') return false; - return !!(navigator.webkitGetUserMedia || - navigator.mozGetUserMedia || - OTPlugin.isInstalled()); -}); - - - -// TODO Remove all PeerConnection stuff, that belongs to the messaging layer not the Media layer. -// Indicates whether this client supports the PeerConnection -// API. -// -// Chrome Issues: -// * The explicit prototype.addStream check is because webkitRTCPeerConnection was -// partially implemented, but not functional, in Chrome 22. -// -// Firefox Issues: -// * No real support before Firefox 19 -// * Firefox 19 has issues with generating Offers. -// * Firefox 20 doesn't interoperate with Chrome. -// -OT.$.registerCapability('PeerConnection', function() { - if (OT.$.env === 'Node') { - return false; - } - else if (typeof(window.webkitRTCPeerConnection) === 'function' && - !!window.webkitRTCPeerConnection.prototype.addStream) { - return true; - } else if (typeof(window.mozRTCPeerConnection) === 'function' && OT.$.env.version > 20.0) { - return true; - } else { - return OTPlugin.isInstalled(); - } -}); - - - -// Indicates whether this client supports WebRTC -// -// This is defined as: getUserMedia + PeerConnection + exceeds min browser version -// -OT.$.registerCapability('webrtc', function() { - if (OT.properties) { - var minimumVersions = OT.properties.minimumVersion || {}, - minimumVersion = minimumVersions[OT.$.env.name.toLowerCase()]; - - if(minimumVersion && OT.$.env.versionGreaterThan(minimumVersion)) { - OT.debug('Support for', OT.$.env.name, 'is disabled because we require', - minimumVersion, 'but this is', OT.$.env.version); - return false; - } - } - - if (OT.$.env === 'Node') { - // Node works, even though it doesn't have getUserMedia - return true; - } - - return OT.$.hasCapabilities('getUserMedia', 'PeerConnection'); -}); - - -// TODO Remove all transport stuff, that belongs to the messaging layer not the Media layer. -// Indicates if the browser supports bundle -// -// Broadly: -// * Firefox doesn't support bundle -// * Chrome support bundle -// * OT Plugin supports bundle -// * We assume NodeJs supports bundle (e.g. 'you're on your own' mode) -// -OT.$.registerCapability('bundle', function() { - return OT.$.hasCapabilities('webrtc') && - (OT.$.env.name === 'Chrome' || - OT.$.env.name === 'Node' || - OTPlugin.isInstalled()); -}); - -// Indicates if the browser supports RTCP Mux -// -// Broadly: -// * Older versions of Firefox (<= 25) don't support RTCP Mux -// * Older versions of Firefox (>= 26) support RTCP Mux (not tested yet) -// * Chrome support RTCP Mux -// * OT Plugin supports RTCP Mux -// * We assume NodeJs supports RTCP Mux (e.g. 'you're on your own' mode) -// -OT.$.registerCapability('RTCPMux', function() { - return OT.$.hasCapabilities('webrtc') && - (OT.$.env.name === 'Chrome' || - OT.$.env.name === 'Node' || - OTPlugin.isInstalled()); -}); - - - -// Indicates whether this browser supports the getMediaDevices (getSources) API. -// -OT.$.registerCapability('getMediaDevices', function() { - return OT.$.isFunction(window.MediaStreamTrack) && - OT.$.isFunction(window.MediaStreamTrack.getSources); -}); - - -OT.$.registerCapability('audioOutputLevelStat', function() { - return OT.$.env.name === 'Chrome' || OT.$.env.name === 'IE'; -}); - -OT.$.registerCapability('webAudioCapableRemoteStream', function() { - return OT.$.env.name === 'Firefox'; -}); - -OT.$.registerCapability('webAudio', function() { - return 'AudioContext' in window; -}); - - // tb_require('../helpers.js') // tb_require('./properties.js') @@ -16723,8 +17126,8 @@ OT.Config = (function() { if (_script) { _script.onload = _script.onreadystatechange = null; - if ( _head && _script.parentNode ) { - _head.removeChild( _script ); + if (_head && _script.parentNode) { + _head.removeChild(_script); } _script = undefined; @@ -16733,8 +17136,8 @@ OT.Config = (function() { _onLoad = function() { // Only IE and Opera actually support readyState on Script elements. - if (_script.readyState && !/loaded|complete/.test( _script.readyState )) { - // Yeah, we're not ready yet... + if (_script.readyState && !/loaded|complete/.test(_script.readyState)) { + // Yeah, we're not ready yet... return; } @@ -16782,20 +17185,19 @@ OT.Config = (function() { _loaded = false; setTimeout(function() { - _script = document.createElement( 'script' ); + _script = document.createElement('script'); _script.async = 'async'; _script.src = configUrl; _script.onload = _script.onreadystatechange = OT.$.bind(_onLoad, _this); _script.onerror = OT.$.bind(_onLoadError, _this); _head.appendChild(_script); - },1); + }, 1); _loadTimer = setTimeout(function() { _this._onLoadTimeout(); }, this.loadTimeout); }, - isLoaded: function() { return _loaded; }, @@ -16840,6 +17242,122 @@ OT.Config = (function() { return _this; })(); +// tb_require('../helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OTPlugin, OT */ + +/// +// Capabilities +// +// Support functions to query browser/client Media capabilities. +// + +// Indicates whether this client supports the getUserMedia +// API. +// +OT.$.registerCapability('getUserMedia', function() { + if (OT.$.env === 'Node') return false; + return !!(navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + OTPlugin.isInstalled()); +}); + +// TODO Remove all PeerConnection stuff, that belongs to the messaging layer not the Media layer. +// Indicates whether this client supports the PeerConnection +// API. +// +// Chrome Issues: +// * The explicit prototype.addStream check is because webkitRTCPeerConnection was +// partially implemented, but not functional, in Chrome 22. +// +// Firefox Issues: +// * No real support before Firefox 19 +// * Firefox 19 has issues with generating Offers. +// * Firefox 20 doesn't interoperate with Chrome. +// +OT.$.registerCapability('PeerConnection', function() { + if (OT.$.env === 'Node') { + return false; + } else if (typeof window.webkitRTCPeerConnection === 'function' && + !!window.webkitRTCPeerConnection.prototype.addStream) { + return true; + } else if (typeof window.mozRTCPeerConnection === 'function' && OT.$.env.version > 20.0) { + return true; + } else { + return OTPlugin.isInstalled(); + } +}); + +// Indicates whether this client supports WebRTC +// +// This is defined as: getUserMedia + PeerConnection + exceeds min browser version +// +OT.$.registerCapability('webrtc', function() { + if (OT.properties) { + var minimumVersions = OT.properties.minimumVersion || {}, + minimumVersion = minimumVersions[OT.$.env.name.toLowerCase()]; + + if (minimumVersion && OT.$.env.versionGreaterThan(minimumVersion)) { + OT.debug('Support for', OT.$.env.name, 'is disabled because we require', + minimumVersion, 'but this is', OT.$.env.version); + return false; + } + } + + if (OT.$.env === 'Node') { + // Node works, even though it doesn't have getUserMedia + return true; + } + + return OT.$.hasCapabilities('getUserMedia', 'PeerConnection'); +}); + +// TODO Remove all transport stuff, that belongs to the messaging layer not the Media layer. +// Indicates if the browser supports bundle +// +// Broadly: +// * Firefox doesn't support bundle +// * Chrome support bundle +// * OT Plugin supports bundle +// * We assume NodeJs supports bundle (e.g. 'you're on your own' mode) +// +OT.$.registerCapability('bundle', function() { + return OT.$.hasCapabilities('webrtc') && + (OT.$.env.name === 'Chrome' || + OT.$.env.name === 'Node' || + OTPlugin.isInstalled()); +}); + +// Indicates if the browser supports RTCP Mux +// +// Broadly: +// * Older versions of Firefox (<= 25) don't support RTCP Mux +// * Older versions of Firefox (>= 26) support RTCP Mux (not tested yet) +// * Chrome support RTCP Mux +// * OT Plugin supports RTCP Mux +// * We assume NodeJs supports RTCP Mux (e.g. 'you're on your own' mode) +// +OT.$.registerCapability('RTCPMux', function() { + return OT.$.hasCapabilities('webrtc') && + (OT.$.env.name === 'Chrome' || + OT.$.env.name === 'Node' || + OTPlugin.isInstalled()); +}); + +OT.$.registerCapability('audioOutputLevelStat', function() { + return OT.$.env.name === 'Chrome' || OT.$.env.name === 'IE' || OT.$.env.name === 'Opera'; +}); + +OT.$.registerCapability('webAudioCapableRemoteStream', function() { + return OT.$.env.name === 'Firefox'; +}); + +OT.$.registerCapability('webAudio', function() { + return 'AudioContext' in window; +}); + // tb_require('../helpers.js') // tb_require('./config.js') @@ -16847,7 +17365,6 @@ OT.Config = (function() { trailing: true, browser: true, smarttabs:true */ /* global OT */ - OT.Analytics = function(loggingUrl) { var LOG_VERSION = '1'; @@ -16868,19 +17385,19 @@ OT.Analytics = function(loggingUrl) { } var data = OT.$.extend({ // TODO: OT.properties.version only gives '2.2', not '2.2.9.3'. - 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''), - 'guid' : guid, - 'partnerId' : partnerId, - 'source' : window.location.href, - 'logVersion' : LOG_VERSION, - 'clientSystemTime' : new Date().getTime() + clientVersion: 'js-' + OT.properties.version.replace('v', ''), + guid: guid, + partnerId: partnerId, + source: window.location.href, + logVersion: LOG_VERSION, + clientSystemTime: new Date().getTime() }, options); _analytics.logError(code, type, message, details, data); }); }; - this.logEvent = function(options, throttle) { + this.logEvent = function(options, throttle, completionHandler) { var partnerId = options.partnerId; if (!options) options = {}; @@ -16894,14 +17411,14 @@ OT.Analytics = function(loggingUrl) { // Set a bunch of defaults var data = OT.$.extend({ // TODO: OT.properties.version only gives '2.2', not '2.2.9.3'. - 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''), - 'guid' : guid, - 'partnerId' : partnerId, - 'source' : window.location.href, - 'logVersion' : LOG_VERSION, - 'clientSystemTime' : new Date().getTime() + clientVersion: 'js-' + OT.properties.version.replace('v', ''), + guid: guid, + partnerId: partnerId, + source: window.location.href, + logVersion: LOG_VERSION, + clientSystemTime: new Date().getTime() }, options); - _analytics.logEvent(data, false, throttle); + _analytics.logEvent(data, false, throttle, completionHandler); }); }; @@ -16919,13 +17436,13 @@ OT.Analytics = function(loggingUrl) { // Set a bunch of defaults var data = OT.$.extend({ // TODO: OT.properties.version only gives '2.2', not '2.2.9.3'. - 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''), - 'guid' : guid, - 'partnerId' : partnerId, - 'source' : window.location.href, - 'logVersion' : LOG_VERSION, - 'clientSystemTime' : new Date().getTime(), - 'duration' : 0 //in milliseconds + clientVersion: 'js-' + OT.properties.version.replace('v', ''), + guid: guid, + partnerId: partnerId, + source: window.location.href, + logVersion: LOG_VERSION, + clientSystemTime: new Date().getTime(), + duration: 0 //in milliseconds }, options); _analytics.logQOS(data); @@ -16941,7 +17458,7 @@ OT.Analytics = function(loggingUrl) { trailing: true, browser: true, smarttabs:true */ /* global OT */ -OT.ConnectivityAttemptPinger = function (options) { +OT.ConnectivityAttemptPinger = function(options) { var _state = 'Initial', _previousState, states = ['Initial', 'Attempt', 'Success', 'Failure'], @@ -17071,7 +17588,7 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit } }; - if(showControls === false) { + if (showControls === false) { initalStyles = { buttonDisplayMode: 'off', nameDisplayMode: 'off', @@ -17086,195 +17603,181 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit var _style = new Style(initalStyles, onStyleChange); -/** - * Returns an object that has the properties that define the current user interface controls of - * the Publisher. You can modify the properties of this object and pass the object to the - * setStyle() method of thePublisher object. (See the documentation for - * setStyle() to see the styles that define this object.) - * @return {Object} The object that defines the styles of the Publisher. - * @see setStyle() - * @method #getStyle - * @memberOf Publisher - */ - -/** - * Returns an object that has the properties that define the current user interface controls of - * the Subscriber. You can modify the properties of this object and pass the object to the - * setStyle() method of the Subscriber object. (See the documentation for - * setStyle() to see the styles that define this object.) - * @return {Object} The object that defines the styles of the Subscriber. - * @see setStyle() - * @method #getStyle - * @memberOf Subscriber - */ + /** + * Returns an object that has the properties that define the current user interface controls of + * the Publisher. You can modify the properties of this object and pass the object to the + * setStyle() method of thePublisher object. (See the documentation for + * setStyle() to see the styles that define this object.) + * @return {Object} The object that defines the styles of the Publisher. + * @see setStyle() + * @method #getStyle + * @memberOf Publisher + */ + + /** + * Returns an object that has the properties that define the current user interface controls of + * the Subscriber. You can modify the properties of this object and pass the object to the + * setStyle() method of the Subscriber object. (See the documentation for + * setStyle() to see the styles that define this object.) + * @return {Object} The object that defines the styles of the Subscriber. + * @see setStyle() + * @method #getStyle + * @memberOf Subscriber + */ // If +key+ is falsly then all styles will be returned. self.getStyle = function(key) { return _style.get(key); }; -/** - * Sets properties that define the appearance of some user interface controls of the Publisher. - * - *

You can either pass one parameter or two parameters to this method.

- * - *

If you pass one parameter, style, it is an object that has the following - * properties: - * - *

    - *
  • audioLevelDisplayMode (String) — How to display the audio level - * indicator. Possible values are: "auto" (the indicator is displayed when the - * video is disabled), "off" (the indicator is not displayed), and - * "on" (the indicator is always displayed).
  • - * - *
  • backgroundImageURI (String) — A URI for an image to display as - * the background image when a video is not displayed. (A video may not be displayed if - * you call publishVideo(false) on the Publisher object). You can pass an http - * or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the - * data URI scheme (instead of http or https) and pass in base-64-encrypted - * PNG data, such as that obtained from the - * Publisher.getImgData() method. For example, - * you could set the property to "data:VBORw0KGgoAA...", where the portion of - * the string after "data:" is the result of a call to - * Publisher.getImgData(). If the URL or the image data is invalid, the - * property is ignored (the attempt to set the image fails silently). - *

    - * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), - * you cannot set the backgroundImageURI style to a string larger than - * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this - * limitation, you cannot set the backgroundImageURI style to a string obtained - * with the getImgData() method. - *

  • - * - *
  • buttonDisplayMode (String) — How to display the microphone - * controls. Possible values are: "auto" (controls are displayed when the - * stream is first displayed and when the user mouses over the display), "off" - * (controls are not displayed), and "on" (controls are always displayed).
  • - * - *
  • nameDisplayMode (String) — Whether to display the stream name. - * Possible values are: "auto" (the name is displayed when the stream is first - * displayed and when the user mouses over the display), "off" (the name is not - * displayed), and "on" (the name is always displayed).
  • - *
- *

- * - *

For example, the following code passes one parameter to the method:

- * - *
myPublisher.setStyle({nameDisplayMode: "off"});
- * - *

If you pass two parameters, style and value, they are - * key-value pair that define one property of the display style. For example, the following - * code passes two parameter values to the method:

- * - *
myPublisher.setStyle("nameDisplayMode", "off");
- * - *

You can set the initial settings when you call the Session.publish() - * or OT.initPublisher() method. Pass a style property as part of the - * properties parameter of the method.

- * - *

The OT object dispatches an exception event if you pass in an invalid style - * to the method. The code property of the ExceptionEvent object is set to 1011.

- * - * @param {Object} style Either an object containing properties that define the style, or a - * String defining this single style property to set. - * @param {String} value The value to set for the style passed in. Pass a value - * for this parameter only if the value of the style parameter is a String.

- * - * @see getStyle() - * @return {Publisher} The Publisher object - * @see setStyle() - * - * @see Session.publish() - * @see OT.initPublisher() - * @method #setStyle - * @memberOf Publisher - */ - -/** - * Sets properties that define the appearance of some user interface controls of the Subscriber. - * - *

You can either pass one parameter or two parameters to this method.

- * - *

If you pass one parameter, style, it is an object that has the following - * properties: - * - *

    - *
  • audioLevelDisplayMode (String) — How to display the audio level - * indicator. Possible values are: "auto" (the indicator is displayed when the - * video is disabled), "off" (the indicator is not displayed), and - * "on" (the indicator is always displayed).
  • - * - *
  • backgroundImageURI (String) — A URI for an image to display as - * the background image when a video is not displayed. (A video may not be displayed if - * you call subscribeToVideo(false) on the Publisher object). You can pass an - * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the - * data URI scheme (instead of http or https) and pass in base-64-encrypted - * PNG data, such as that obtained from the - * Subscriber.getImgData() method. For example, - * you could set the property to "data:VBORw0KGgoAA...", where the portion of - * the string after "data:" is the result of a call to - * Publisher.getImgData(). If the URL or the image data is invalid, the - * property is ignored (the attempt to set the image fails silently). - *

    - * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), - * you cannot set the backgroundImageURI style to a string larger than - * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this - * limitation, you cannot set the backgroundImageURI style to a string obtained - * with the getImgData() method. - *

  • - * - *
  • buttonDisplayMode (String) — How to display the speaker - * controls. Possible values are: "auto" (controls are displayed when the - * stream is first displayed and when the user mouses over the display), "off" - * (controls are not displayed), and "on" (controls are always displayed).
  • - * - *
  • nameDisplayMode (String) — Whether to display the stream name. - * Possible values are: "auto" (the name is displayed when the stream is first - * displayed and when the user mouses over the display), "off" (the name is not - * displayed), and "on" (the name is always displayed).
  • - * - *
  • videoDisabledDisplayMode (String) — Whether to display the video - * disabled indicator and video disabled warning icons for a Subscriber. These icons - * indicate that the video has been disabled (or is in risk of being disabled for - * the warning icon) due to poor stream quality. Possible values are: "auto" - * (the icons are automatically when the displayed video is disabled or in risk of being - * disabled due to poor stream quality), "off" (do not display the icons), and - * "on" (display the icons).
  • - *
- *

- * - *

For example, the following code passes one parameter to the method:

- * - *
mySubscriber.setStyle({nameDisplayMode: "off"});
- * - *

If you pass two parameters, style and value, they are key-value - * pair that define one property of the display style. For example, the following code passes - * two parameter values to the method:

- * - *
mySubscriber.setStyle("nameDisplayMode", "off");
- * - *

You can set the initial settings when you call the Session.subscribe() method. - * Pass a style property as part of the properties parameter of the - * method.

- * - *

The OT object dispatches an exception event if you pass in an invalid style - * to the method. The code property of the ExceptionEvent object is set to 1011.

- * - * @param {Object} style Either an object containing properties that define the style, or a - * String defining this single style property to set. - * @param {String} value The value to set for the style passed in. Pass a value - * for this parameter only if the value of the style parameter is a String.

- * - * @returns {Subscriber} The Subscriber object. - * - * @see getStyle() - * @see setStyle() - * - * @see Session.subscribe() - * @method #setStyle - * @memberOf Subscriber - */ - - if(_readOnly) { + /** + * Sets properties that define the appearance of some user interface controls of the Publisher. + * + *

You can either pass one parameter or two parameters to this method.

+ * + *

If you pass one parameter, style, it is an object that has the following + * properties: + * + *

    + *
  • audioLevelDisplayMode (String) — How to display the audio level + * indicator. Possible values are: "auto" (the indicator is displayed when the + * video is disabled), "off" (the indicator is not displayed), and + * "on" (the indicator is always displayed).
  • + * + *
  • backgroundImageURI (String) — A URI for an image to display as + * the background image when a video is not displayed. (A video may not be displayed if + * you call publishVideo(false) on the Publisher object). You can pass an http + * or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the + * data URI scheme (instead of http or https) and pass in base-64-encrypted + * PNG data, such as that obtained from the + * Publisher.getImgData() method. For example, + * you could set the property to "data:VBORw0KGgoAA...", where the portion of + * the string after "data:" is the result of a call to + * Publisher.getImgData(). If the URL or the image data is invalid, the + * property is ignored (the attempt to set the image fails silently).
  • + * + *
  • buttonDisplayMode (String) — How to display the microphone + * controls. Possible values are: "auto" (controls are displayed when the + * stream is first displayed and when the user mouses over the display), "off" + * (controls are not displayed), and "on" (controls are always displayed).
  • + * + *
  • nameDisplayMode (String) — Whether to display the stream name. + * Possible values are: "auto" (the name is displayed when the stream is first + * displayed and when the user mouses over the display), "off" (the name is not + * displayed), and "on" (the name is always displayed).
  • + *
+ *

+ * + *

For example, the following code passes one parameter to the method:

+ * + *
myPublisher.setStyle({nameDisplayMode: "off"});
+ * + *

If you pass two parameters, style and value, they are + * key-value pair that define one property of the display style. For example, the following + * code passes two parameter values to the method:

+ * + *
myPublisher.setStyle("nameDisplayMode", "off");
+ * + *

You can set the initial settings when you call the Session.publish() + * or OT.initPublisher() method. Pass a style property as part of the + * properties parameter of the method.

+ * + *

The OT object dispatches an exception event if you pass in an invalid style + * to the method. The code property of the ExceptionEvent object is set to 1011.

+ * + * @param {Object} style Either an object containing properties that define the style, or a + * String defining this single style property to set. + * @param {String} value The value to set for the style passed in. Pass a value + * for this parameter only if the value of the style parameter is a String.

+ * + * @see getStyle() + * @return {Publisher} The Publisher object + * @see setStyle() + * + * @see Session.publish() + * @see OT.initPublisher() + * @method #setStyle + * @memberOf Publisher + */ + + /** + * Sets properties that define the appearance of some user interface controls of the Subscriber. + * + *

You can either pass one parameter or two parameters to this method.

+ * + *

If you pass one parameter, style, it is an object that has the following + * properties: + * + *

    + *
  • audioLevelDisplayMode (String) — How to display the audio level + * indicator. Possible values are: "auto" (the indicator is displayed when the + * video is disabled), "off" (the indicator is not displayed), and + * "on" (the indicator is always displayed).
  • + * + *
  • backgroundImageURI (String) — A URI for an image to display as + * the background image when a video is not displayed. (A video may not be displayed if + * you call subscribeToVideo(false) on the Publisher object). You can pass an + * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the + * data URI scheme (instead of http or https) and pass in base-64-encrypted + * PNG data, such as that obtained from the + * Subscriber.getImgData() method. For example, + * you could set the property to "data:VBORw0KGgoAA...", where the portion of + * the string after "data:" is the result of a call to + * Publisher.getImgData(). If the URL or the image data is invalid, the + * property is ignored (the attempt to set the image fails silently).
  • + * + *
  • buttonDisplayMode (String) — How to display the speaker + * controls. Possible values are: "auto" (controls are displayed when the + * stream is first displayed and when the user mouses over the display), "off" + * (controls are not displayed), and "on" (controls are always displayed).
  • + * + *
  • nameDisplayMode (String) — Whether to display the stream name. + * Possible values are: "auto" (the name is displayed when the stream is first + * displayed and when the user mouses over the display), "off" (the name is not + * displayed), and "on" (the name is always displayed).
  • + * + *
  • videoDisabledDisplayMode (String) — Whether to display the video + * disabled indicator and video disabled warning icons for a Subscriber. These icons + * indicate that the video has been disabled (or is in risk of being disabled for + * the warning icon) due to poor stream quality. Possible values are: "auto" + * (the icons are automatically when the displayed video is disabled or in risk of being + * disabled due to poor stream quality), "off" (do not display the icons), and + * "on" (display the icons).
  • + *
+ *

+ * + *

For example, the following code passes one parameter to the method:

+ * + *
mySubscriber.setStyle({nameDisplayMode: "off"});
+ * + *

If you pass two parameters, style and value, they are key-value + * pair that define one property of the display style. For example, the following code passes + * two parameter values to the method:

+ * + *
mySubscriber.setStyle("nameDisplayMode", "off");
+ * + *

You can set the initial settings when you call the Session.subscribe() method. + * Pass a style property as part of the properties parameter of the + * method.

+ * + *

The OT object dispatches an exception event if you pass in an invalid style + * to the method. The code property of the ExceptionEvent object is set to 1011.

+ * + * @param {Object} style Either an object containing properties that define the style, or a + * String defining this single style property to set. + * @param {String} value The value to set for the style passed in. Pass a value + * for this parameter only if the value of the style parameter is a String.

+ * + * @returns {Subscriber} The Subscriber object. + * + * @see getStyle() + * @see setStyle() + * + * @see Session.subscribe() + * @method #setStyle + * @memberOf Subscriber + */ + + if (_readOnly) { self.setStyle = function() { OT.warn('Calling setStyle() has no effect because the' + 'showControls option was set to false'); @@ -17283,7 +17786,7 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit } else { self.setStyle = function(keyOrStyleHash, value, silent) { var logPayload = {}; - if (typeof(keyOrStyleHash) !== 'string') { + if (typeof keyOrStyleHash !== 'string') { _style.setAll(keyOrStyleHash, silent); logPayload = keyOrStyleHash; } else { @@ -17296,17 +17799,15 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit } }; - /*jshint latedef:false */ var Style = function(initalStyles, onStyleChange) { -/*jshint latedef:true */ + /*jshint latedef:true */ var _style = {}, _COMPONENT_STYLES, _validStyleValues, isValidStyle, castValue; - _COMPONENT_STYLES = [ 'showMicButton', 'showSpeakerButton', @@ -17332,11 +17833,11 @@ var Style = function(initalStyles, onStyleChange) { isValidStyle = function(key, value) { return key === 'backgroundImageURI' || (_validStyleValues.hasOwnProperty(key) && - OT.$.arrayIndexOf(_validStyleValues[key], value) !== -1 ); + OT.$.arrayIndexOf(_validStyleValues[key], value) !== -1); }; castValue = function(value) { - switch(value) { + switch (value) { case 'true': return true; case 'false': @@ -17351,7 +17852,7 @@ var Style = function(initalStyles, onStyleChange) { var style = OT.$.clone(_style); for (var key in style) { - if(!style.hasOwnProperty(key)) { + if (!style.hasOwnProperty(key)) { continue; } if (OT.$.arrayIndexOf(_COMPONENT_STYLES, key) < 0) { @@ -17378,7 +17879,7 @@ var Style = function(initalStyles, onStyleChange) { var oldValue, newValue; for (var key in newStyles) { - if(!newStyles.hasOwnProperty(key)) { + if (!newStyles.hasOwnProperty(key)) { continue; } newValue = castValue(newStyles[key]); @@ -17429,7 +17930,6 @@ var Style = function(initalStyles, onStyleChange) { trailing: true, browser: true, smarttabs:true */ /* global OT */ - // A Factory method for generating simple state machine classes. // // @usage @@ -17447,7 +17947,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { var validStates = states.slice(), validTransitions = OT.$.clone(transitions); - var isValidState = function (state) { + var isValidState = function(state) { return OT.$.arrayIndexOf(validStates, state) !== -1; }; @@ -17489,7 +17989,6 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { return true; } - this.set = function(newState) { if (!handleInvalidStateChanges(newState)) return; previousState = currentState; @@ -17508,63 +18007,63 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { !(function() { -// Models a Subscriber's subscribing State -// -// Valid States: -// NotSubscribing (the initial state -// Init (basic setup of DOM -// ConnectingToPeer (Failure Cases -> No Route, Bad Offer, Bad Answer -// BindingRemoteStream (Failure Cases -> Anything to do with the media being -// (invalid, the media never plays -// Subscribing (this is 'onLoad' -// Failed (terminal state, with a reason that maps to one of the -// (failure cases above -// Destroyed (The subscriber has been cleaned up, terminal state -// -// -// Valid Transitions: -// NotSubscribing -> -// Init -// -// Init -> -// ConnectingToPeer -// | BindingRemoteStream (if we are subscribing to ourselves and we alreay -// (have a stream -// | NotSubscribing (destroy() -// -// ConnectingToPeer -> -// BindingRemoteStream -// | NotSubscribing -// | Failed -// | NotSubscribing (destroy() -// -// BindingRemoteStream -> -// Subscribing -// | Failed -// | NotSubscribing (destroy() -// -// Subscribing -> -// NotSubscribing (unsubscribe -// | Failed (probably a peer connection failure after we began -// (subscribing -// -// Failed -> -// Destroyed -// -// Destroyed -> (terminal state) -// -// -// @example -// var state = new SubscribingState(function(change) { -// console.log(change.message); -// }); -// -// state.set('Init'); -// state.current; -> 'Init' -// -// state.set('Subscribing'); -> triggers stateChangeFailed and logs out the error message -// -// + // Models a Subscriber's subscribing State + // + // Valid States: + // NotSubscribing (the initial state + // Init (basic setup of DOM + // ConnectingToPeer (Failure Cases -> No Route, Bad Offer, Bad Answer + // BindingRemoteStream (Failure Cases -> Anything to do with the media being + // (invalid, the media never plays + // Subscribing (this is 'onLoad' + // Failed (terminal state, with a reason that maps to one of the + // (failure cases above + // Destroyed (The subscriber has been cleaned up, terminal state + // + // + // Valid Transitions: + // NotSubscribing -> + // Init + // + // Init -> + // ConnectingToPeer + // | BindingRemoteStream (if we are subscribing to ourselves and we alreay + // (have a stream + // | NotSubscribing (destroy() + // + // ConnectingToPeer -> + // BindingRemoteStream + // | NotSubscribing + // | Failed + // | NotSubscribing (destroy() + // + // BindingRemoteStream -> + // Subscribing + // | Failed + // | NotSubscribing (destroy() + // + // Subscribing -> + // NotSubscribing (unsubscribe + // | Failed (probably a peer connection failure after we began + // (subscribing + // + // Failed -> + // Destroyed + // + // Destroyed -> (terminal state) + // + // + // @example + // var state = new SubscribingState(function(change) { + // console.log(change.message); + // }); + // + // state.set('Init'); + // state.current; -> 'Init' + // + // state.set('Subscribing'); -> triggers stateChangeFailed and logs out the error message + // + // var validStates, validTransitions, initialState = 'NotSubscribing'; @@ -17601,7 +18100,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { OT.SubscribingState.prototype.isAttemptingToSubscribe = function() { return OT.$.arrayIndexOf( - [ 'Init', 'ConnectingToPeer', 'BindingRemoteStream' ], + ['Init', 'ConnectingToPeer', 'BindingRemoteStream'], this.current ) !== -1; }; @@ -17617,62 +18116,62 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { !(function() { -// Models a Publisher's publishing State -// -// Valid States: -// NotPublishing -// GetUserMedia -// BindingMedia -// MediaBound -// PublishingToSession -// Publishing -// Failed -// Destroyed -// -// -// Valid Transitions: -// NotPublishing -> -// GetUserMedia -// -// GetUserMedia -> -// BindingMedia -// | Failed (Failure Reasons -> stream error, constraints, -// (permission denied -// | NotPublishing (destroy() -// -// -// BindingMedia -> -// MediaBound -// | Failed (Failure Reasons -> Anything to do with the media -// (being invalid, the media never plays -// | NotPublishing (destroy() -// -// MediaBound -> -// PublishingToSession (MediaBound could transition to PublishingToSession -// (if a stand-alone publish is bound to a session -// | Failed (Failure Reasons -> media issues with a stand-alone publisher -// | NotPublishing (destroy() -// -// PublishingToSession -// Publishing -// | Failed (Failure Reasons -> timeout while waiting for ack of -// (stream registered. We do not do this right now -// | NotPublishing (destroy() -// -// -// Publishing -> -// NotPublishing (Unpublish -// | Failed (Failure Reasons -> loss of network, media error, anything -// (that causes *all* Peer Connections to fail (less than all -// (failing is just an error, all is failure) -// | NotPublishing (destroy() -// -// Failed -> -// Destroyed -// -// Destroyed -> (Terminal state -// -// + // Models a Publisher's publishing State + // + // Valid States: + // NotPublishing + // GetUserMedia + // BindingMedia + // MediaBound + // PublishingToSession + // Publishing + // Failed + // Destroyed + // + // + // Valid Transitions: + // NotPublishing -> + // GetUserMedia + // + // GetUserMedia -> + // BindingMedia + // | Failed (Failure Reasons -> stream error, constraints, + // (permission denied + // | NotPublishing (destroy() + // + // + // BindingMedia -> + // MediaBound + // | Failed (Failure Reasons -> Anything to do with the media + // (being invalid, the media never plays + // | NotPublishing (destroy() + // + // MediaBound -> + // PublishingToSession (MediaBound could transition to PublishingToSession + // (if a stand-alone publish is bound to a session + // | Failed (Failure Reasons -> media issues with a stand-alone publisher + // | NotPublishing (destroy() + // + // PublishingToSession + // Publishing + // | Failed (Failure Reasons -> timeout while waiting for ack of + // (stream registered. We do not do this right now + // | NotPublishing (destroy() + // + // + // Publishing -> + // NotPublishing (Unpublish + // | Failed (Failure Reasons -> loss of network, media error, anything + // (that causes *all* Peer Connections to fail (less than all + // (failing is just an error, all is failure) + // | NotPublishing (destroy() + // + // Failed -> + // Destroyed + // + // Destroyed -> (Terminal state + // + // var validStates = [ 'NotPublishing', 'GetUserMedia', 'BindingMedia', 'MediaBound', @@ -17701,7 +18200,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { OT.PublishingState.prototype.isAttemptingToPublish = function() { return OT.$.arrayIndexOf( - [ 'GetUserMedia', 'BindingMedia', 'MediaBound', 'PublishingToSession' ], + ['GetUserMedia', 'BindingMedia', 'MediaBound', 'PublishingToSession'], this.current) !== -1; }; @@ -17716,16 +18215,16 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { !(function() { -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global OT */ + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT */ -/* - * A Publishers Microphone. - * - * TODO - * * bind to changes in mute/unmute/volume/etc and respond to them - */ + /* + * A Publishers Microphone. + * + * TODO + * * bind to changes in mute/unmute/volume/etc and respond to them + */ OT.Microphone = function(webRTCStream, muted) { var _muted; @@ -17741,7 +18240,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) { var audioTracks = webRTCStream.getAudioTracks(); - for (var i=0, num=audioTracks.length; iwindow.setInterval. - * - * @param {function()} callback - * @param {number} frequency how many times per second we want to execute the callback - * @constructor - */ -OT.IntervalRunner = function(callback, frequency) { - var _callback = callback, - _frequency = frequency, - _intervalId = null; - - this.start = function() { - _intervalId = window.setInterval(_callback, 1000 / _frequency); - }; - - this.stop = function() { - window.clearInterval(_intervalId); - _intervalId = null; - }; -}; - -// tb_require('../helpers/helpers.js') - !(function() { /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ @@ -17888,7 +18359,6 @@ OT.IntervalRunner = function(callback, frequency) { STREAM_PROPERTY_CHANGED: 'streamPropertyChanged', MICROPHONE_LEVEL_CHANGED: 'microphoneLevelChanged', - // Publisher Events RESIZE: 'resize', SETTINGS_BUTTON_CLICK: 'settingsButtonClick', @@ -17912,8 +18382,8 @@ OT.IntervalRunner = function(callback, frequency) { DEVICES_SELECTED: 'devicesSelected', CLOSE_BUTTON_CLICK: 'closeButtonClick', - MICLEVEL : 'microphoneActivityLevel', - MICGAINCHANGED : 'microphoneGainChanged', + MICLEVEL: 'microphoneActivityLevel', + MICGAINCHANGED: 'microphoneGainChanged', // Environment Loader ENV_LOADED: 'envLoaded', @@ -17931,6 +18401,7 @@ OT.IntervalRunner = function(callback, frequency) { CONNECT_REJECTED: 1007, CONNECTION_TIMEOUT: 1008, NOT_CONNECTED: 1010, + INVALID_PARAMETER: 1011, P2P_CONNECTION_FAILED: 1013, API_RESPONSE_FAILURE: 1014, TERMS_OF_SERVICE_FAILURE: 1026, @@ -17940,7 +18411,8 @@ OT.IntervalRunner = function(callback, frequency) { UNABLE_TO_FORCE_UNPUBLISH: 1530, PUBLISHER_ICE_WORKFLOW_FAILED: 1553, SUBSCRIBER_ICE_WORKFLOW_FAILED: 1554, - UNEXPECTED_SERVER_RESPONSE: 2001 + UNEXPECTED_SERVER_RESPONSE: 2001, + REPORT_ISSUE_ERROR: 2011 }; /** @@ -18131,7 +18603,7 @@ OT.IntervalRunner = function(callback, frequency) { * @property {String} title The error title. * @augments Event */ - OT.ExceptionEvent = function (type, message, title, code, component, target) { + OT.ExceptionEvent = function(type, message, title, code, component, target) { OT.Event.call(this, type); this.message = message; @@ -18141,106 +18613,104 @@ OT.IntervalRunner = function(callback, frequency) { this.target = target; }; - - OT.IssueReportedEvent = function (type, issueId) { + OT.IssueReportedEvent = function(type, issueId) { OT.Event.call(this, type); this.issueId = issueId; }; // Triggered when the JS dynamic config and the DOM have loaded. - OT.EnvLoadedEvent = function (type) { + OT.EnvLoadedEvent = function(type) { OT.Event.call(this, type); }; - -/** - * Defines connectionCreated and connectionDestroyed events dispatched by - * the {@link Session} object. - *

- * The Session object dispatches a connectionCreated event when a client (including - * your own) connects to a Session. It also dispatches a connectionCreated event for - * every client in the session when you first connect. (when your local client connects, the Session - * object also dispatches a sessionConnected event, defined by the - * {@link SessionConnectEvent} class.) - *

- * While you are connected to the session, the Session object dispatches a - * connectionDestroyed event when another client disconnects from the Session. - * (When you disconnect, the Session object also dispatches a sessionDisconnected - * event, defined by the {@link SessionDisconnectEvent} class.) - * - *

Example
- * - *

The following code keeps a running total of the number of connections to a session - * by monitoring the connections property of the sessionConnect, - * connectionCreated and connectionDestroyed events:

- * - *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
- * var sessionID = ""; // Replace with your own session ID.
- *                     // See https://dashboard.tokbox.com/projects
- * var token = ""; // Replace with a generated token that has been assigned the moderator role.
- *                 // See https://dashboard.tokbox.com/projects
- * var connectionCount = 0;
- *
- * var session = OT.initSession(apiKey, sessionID);
- * session.on("connectionCreated", function(event) {
- *    connectionCount++;
- *    displayConnectionCount();
- * });
- * session.on("connectionDestroyed", function(event) {
- *    connectionCount--;
- *    displayConnectionCount();
- * });
- * session.connect(token);
- *
- * function displayConnectionCount() {
- *     document.getElementById("connectionCountField").value = connectionCount.toString();
- * }
- * - *

This example assumes that there is an input text field in the HTML DOM - * with the id set to "connectionCountField":

- * - *
<input type="text" id="connectionCountField" value="0"></input>
- * - * - * @property {Connection} connection A Connection objects for the connections that was - * created or deleted. - * - * @property {Array} connections Deprecated. Use the connection property. A - * connectionCreated or connectionDestroyed event is dispatched - * for each connection created and destroyed in the session. - * - * @property {String} reason For a connectionDestroyed event, - * a description of why the connection ended. This property can have two values: - *

- *
    - *
  • "clientDisconnected" — A client disconnected from the session by calling - * the disconnect() method of the Session object or by closing the browser. - * (See Session.disconnect().)
  • - * - *
  • "forceDisconnected" — A moderator has disconnected the publisher - * from the session, by calling the forceDisconnect() method of the Session - * object. (See Session.forceDisconnect().)
  • - * - *
  • "networkDisconnected" — The network connection terminated abruptly - * (for example, the client lost their internet connection).
  • - *
- * - *

Depending on the context, this description may allow the developer to refine - * the course of action they take in response to an event.

- * - *

For a connectionCreated event, this string is undefined.

- * - * @class ConnectionEvent - * @augments Event - */ + /** + * Defines connectionCreated and connectionDestroyed events dispatched + * by the {@link Session} object. + *

+ * The Session object dispatches a connectionCreated event when a client (including + * your own) connects to a Session. It also dispatches a connectionCreated event for + * every client in the session when you first connect. (when your local client connects, the + * Session object also dispatches a sessionConnected event, defined by the + * {@link SessionConnectEvent} class.) + *

+ * While you are connected to the session, the Session object dispatches a + * connectionDestroyed event when another client disconnects from the Session. + * (When you disconnect, the Session object also dispatches a sessionDisconnected + * event, defined by the {@link SessionDisconnectEvent} class.) + * + *

Example
+ * + *

The following code keeps a running total of the number of connections to a session + * by monitoring the connections property of the sessionConnect, + * connectionCreated and connectionDestroyed events:

+ * + *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+   * var sessionID = ""; // Replace with your own session ID.
+   *                     // See https://dashboard.tokbox.com/projects
+   * var token = ""; // Replace with a generated token that has been assigned the moderator role.
+   *                 // See https://dashboard.tokbox.com/projects
+   * var connectionCount = 0;
+   *
+   * var session = OT.initSession(apiKey, sessionID);
+   * session.on("connectionCreated", function(event) {
+   *    connectionCount++;
+   *    displayConnectionCount();
+   * });
+   * session.on("connectionDestroyed", function(event) {
+   *    connectionCount--;
+   *    displayConnectionCount();
+   * });
+   * session.connect(token);
+   *
+   * function displayConnectionCount() {
+   *     document.getElementById("connectionCountField").value = connectionCount.toString();
+   * }
+ * + *

This example assumes that there is an input text field in the HTML DOM + * with the id set to "connectionCountField":

+ * + *
<input type="text" id="connectionCountField" value="0"></input>
+ * + * + * @property {Connection} connection A Connection objects for the connections that was + * created or deleted. + * + * @property {Array} connections Deprecated. Use the connection property. A + * connectionCreated or connectionDestroyed event is dispatched + * for each connection created and destroyed in the session. + * + * @property {String} reason For a connectionDestroyed event, + * a description of why the connection ended. This property can have two values: + *

+ *
    + *
  • "clientDisconnected" — A client disconnected from the session by calling + * the disconnect() method of the Session object or by closing the browser. + * (See Session.disconnect().)
  • + * + *
  • "forceDisconnected" — A moderator has disconnected the publisher + * from the session, by calling the forceDisconnect() method of the Session + * object. (See Session.forceDisconnect().)
  • + * + *
  • "networkDisconnected" — The network connection terminated abruptly + * (for example, the client lost their internet connection).
  • + *
+ * + *

Depending on the context, this description may allow the developer to refine + * the course of action they take in response to an event.

+ * + *

For a connectionCreated event, this string is undefined.

+ * + * @class ConnectionEvent + * @augments Event + */ var connectionEventPluralDeprecationWarningShown = false; - OT.ConnectionEvent = function (type, connection, reason) { + OT.ConnectionEvent = function(type, connection, reason) { OT.Event.call(this, type, false); if (OT.$.canDefineProperty) { Object.defineProperty(this, 'connections', { get: function() { - if(!connectionEventPluralDeprecationWarningShown) { + if (!connectionEventPluralDeprecationWarningShown) { OT.warn('OT.ConnectionEvent connections property is deprecated, ' + 'use connection instead.'); connectionEventPluralDeprecationWarningShown = true; @@ -18256,119 +18726,120 @@ OT.IntervalRunner = function(callback, frequency) { this.reason = reason; }; -/** - * StreamEvent is an event that can have the type "streamCreated" or "streamDestroyed". - * These events are dispatched by the Session object when another client starts or - * stops publishing a stream to a {@link Session}. For a local client's stream, the - * Publisher object dispatches the event. - * - *

Example — streamCreated event dispatched - * by the Session object

- *

The following code initializes a session and sets up an event listener for when - * a stream published by another client is created:

- * - *
- * session.on("streamCreated", function(event) {
- *   // streamContainer is a DOM element
- *   subscriber = session.subscribe(event.stream, targetElement);
- * }).connect(token);
- * 
- * - *

Example — streamDestroyed event dispatched - * by the Session object

- * - *

The following code initializes a session and sets up an event listener for when - * other clients' streams end:

- * - *
- * session.on("streamDestroyed", function(event) {
- *     console.log("Stream " + event.stream.name + " ended. " + event.reason);
- * }).connect(token);
- * 
- * - *

Example — streamCreated event dispatched - * by a Publisher object

- *

The following code publishes a stream and adds an event listener for when the streaming - * starts

- * - *
- * var publisher = session.publish(targetElement)
- *   .on("streamCreated", function(event) {
- *     console.log("Publisher started streaming.");
- *   );
- * 
- * - *

Example — streamDestroyed event - * dispatched by a Publisher object

- * - *

The following code publishes a stream, and leaves the Publisher in the HTML DOM - * when the streaming stops:

- * - *
- * var publisher = session.publish(targetElement)
- *   .on("streamDestroyed", function(event) {
- *     event.preventDefault();
- *     console.log("Publisher stopped streaming.");
- *   );
- * 
- * - * @class StreamEvent - * - * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable - * (true) or not (false). You can cancel the default behavior by calling - * the preventDefault() method of the StreamEvent object in the event listener - * function. The streamDestroyed - * event is cancelable. (See preventDefault().) - * - * @property {String} reason For a streamDestroyed event, - * a description of why the session disconnected. This property can have one of the following - * values: - *

- *
    - *
  • "clientDisconnected" — A client disconnected from the session by calling - * the disconnect() method of the Session object or by closing the browser. - * (See Session.disconnect().)
  • - * - *
  • "forceDisconnected" — A moderator has disconnected the publisher of the - * stream from the session, by calling the forceDisconnect() method of the Session -* object. (See Session.forceDisconnect().)
  • - * - *
  • "forceUnpublished" — A moderator has forced the publisher of the stream - * to stop publishing the stream, by calling the forceUnpublish() method of the - * Session object. (See Session.forceUnpublish().)
  • - * - *
  • "mediaStopped" — The user publishing the stream has stopped sharing the - * screen. This value is only used in screen-sharing video streams.
  • - * - *
  • "networkDisconnected" — The network connection terminated abruptly (for - * example, the client lost their internet connection).
  • - * - *
- * - *

Depending on the context, this description may allow the developer to refine - * the course of action they take in response to an event.

- * - *

For a streamCreated event, this string is undefined.

- * - * @property {Stream} stream A Stream object corresponding to the stream that was added (in the - * case of a streamCreated event) or deleted (in the case of a - * streamDestroyed event). - * - * @property {Array} streams Deprecated. Use the stream property. A - * streamCreated or streamDestroyed event is dispatched for - * each stream added or destroyed. - * - * @augments Event - */ + /** + * StreamEvent is an event that can have the type "streamCreated" or "streamDestroyed". + * These events are dispatched by the Session object when another client starts or + * stops publishing a stream to a {@link Session}. For a local client's stream, the + * Publisher object dispatches the event. + * + *

Example — streamCreated event dispatched + * by the Session object

+ *

The following code initializes a session and sets up an event listener for when + * a stream published by another client is created:

+ * + *
+   * session.on("streamCreated", function(event) {
+   *   // streamContainer is a DOM element
+   *   subscriber = session.subscribe(event.stream, targetElement);
+   * }).connect(token);
+   * 
+ * + *

Example — streamDestroyed event dispatched + * by the Session object

+ * + *

The following code initializes a session and sets up an event listener for when + * other clients' streams end:

+ * + *
+   * session.on("streamDestroyed", function(event) {
+   *     console.log("Stream " + event.stream.name + " ended. " + event.reason);
+   * }).connect(token);
+   * 
+ * + *

Example — streamCreated event dispatched + * by a Publisher object

+ *

The following code publishes a stream and adds an event listener for when the streaming + * starts

+ * + *
+   * var publisher = session.publish(targetElement)
+   *   .on("streamCreated", function(event) {
+   *     console.log("Publisher started streaming.");
+   *   );
+   * 
+ * + *

Example — streamDestroyed event + * dispatched by a Publisher object

+ * + *

The following code publishes a stream, and leaves the Publisher in the HTML DOM + * when the streaming stops:

+ * + *
+   * var publisher = session.publish(targetElement)
+   *   .on("streamDestroyed", function(event) {
+   *     event.preventDefault();
+   *     console.log("Publisher stopped streaming.");
+   *   );
+   * 
+ * + * @class StreamEvent + * + * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable + * (true) or not (false). You can cancel the default behavior by + * calling the preventDefault() method of the StreamEvent object in the event + * listener function. The streamDestroyed event is cancelable. + * (See preventDefault().) + * + * @property {String} reason For a streamDestroyed event, + * a description of why the session disconnected. This property can have one of the following + * values: + *

+ *
    + *
  • "clientDisconnected" — A client disconnected from the session by calling + * the disconnect() method of the Session object or by closing the browser. + * (See Session.disconnect().)
  • + * + *
  • "forceDisconnected" — A moderator has disconnected the publisher of the + * stream from the session, by calling the forceDisconnect() method of the Session + * object. (See Session.forceDisconnect().)
  • + * + *
  • "forceUnpublished" — A moderator has forced the publisher of the stream + * to stop publishing the stream, by calling the forceUnpublish() method of the + * Session object. + * (See Session.forceUnpublish().)
  • + * + *
  • "mediaStopped" — The user publishing the stream has stopped sharing the + * screen. This value is only used in screen-sharing video streams.
  • + * + *
  • "networkDisconnected" — The network connection terminated abruptly (for + * example, the client lost their internet connection).
  • + * + *
+ * + *

Depending on the context, this description may allow the developer to refine + * the course of action they take in response to an event.

+ * + *

For a streamCreated event, this string is undefined.

+ * + * @property {Stream} stream A Stream object corresponding to the stream that was added (in the + * case of a streamCreated event) or deleted (in the case of a + * streamDestroyed event). + * + * @property {Array} streams Deprecated. Use the stream property. A + * streamCreated or streamDestroyed event is dispatched for + * each stream added or destroyed. + * + * @augments Event + */ var streamEventPluralDeprecationWarningShown = false; - OT.StreamEvent = function (type, stream, reason, cancelable) { + OT.StreamEvent = function(type, stream, reason, cancelable) { OT.Event.call(this, type, cancelable); if (OT.$.canDefineProperty) { Object.defineProperty(this, 'streams', { get: function() { - if(!streamEventPluralDeprecationWarningShown) { + if (!streamEventPluralDeprecationWarningShown) { OT.warn('OT.StreamEvent streams property is deprecated, use stream instead.'); streamEventPluralDeprecationWarningShown = true; } @@ -18383,69 +18854,71 @@ OT.IntervalRunner = function(callback, frequency) { this.reason = reason; }; -/** -* Prevents the default behavior associated with the event from taking place. -* -*

For the streamDestroyed event dispatched by the Session object, -* the default behavior is that all Subscriber objects that are subscribed to the stream are -* unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a -* destroyed event when the element is removed from the HTML DOM. If you call the -* preventDefault() method in the event listener for the streamDestroyed -* event, the default behavior is prevented and you can clean up Subscriber objects using your -* own code. See -* Session.getSubscribersForStream().

-*

-* For the streamDestroyed event dispatched by a Publisher object, the default -* behavior is that the Publisher object is removed from the HTML DOM. The Publisher object -* dispatches a destroyed event when the element is removed from the HTML DOM. -* If you call the preventDefault() method in the event listener for the -* streamDestroyed event, the default behavior is prevented, and you can -* retain the Publisher for reuse or clean it up using your own code. -*

-*

To see whether an event has a default behavior, check the cancelable property of -* the event object.

-* -*

Call the preventDefault() method in the event listener function for the event.

-* -* @method #preventDefault -* @memberof StreamEvent + /** + * Prevents the default behavior associated with the event from taking place. + * + *

For the streamDestroyed event dispatched by the Session object, + * the default behavior is that all Subscriber objects that are subscribed to the stream are + * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a + * destroyed event when the element is removed from the HTML DOM. If you call the + * preventDefault() method in the event listener for the streamDestroyed + * event, the default behavior is prevented and you can clean up Subscriber objects using your + * own code. See + * Session.getSubscribersForStream().

+ *

+ * For the streamDestroyed event dispatched by a Publisher object, the default + * behavior is that the Publisher object is removed from the HTML DOM. The Publisher object + * dispatches a destroyed event when the element is removed from the HTML DOM. + * If you call the preventDefault() method in the event listener for the + * streamDestroyed event, the default behavior is prevented, and you can + * retain the Publisher for reuse or clean it up using your own code. + *

+ *

To see whether an event has a default behavior, check the cancelable property of + * the event object.

+ * + *

+ * Call the preventDefault() method in the event listener function for the event. + *

+ * + * @method #preventDefault + * @memberof StreamEvent */ -/** - * The Session object dispatches SessionConnectEvent object when a session has successfully - * connected in response to a call to the connect() method of the Session object. - *

- * In version 2.2, the completionHandler of the Session.connect() method - * indicates success or failure in connecting to the session. - * - * @class SessionConnectEvent - * @property {Array} connections Deprecated in version 2.2 (and set to an empty array). In - * version 2.2, listen for the connectionCreated event dispatched by the Session - * object. In version 2.2, the Session object dispatches a connectionCreated event - * for each connection (including your own). This includes connections present when you first - * connect to the session. - * - * @property {Array} streams Deprecated in version 2.2 (and set to an empty array). In version - * 2.2, listen for the streamCreated event dispatched by the Session object. In - * version 2.2, the Session object dispatches a streamCreated event for each stream - * other than those published by your client. This includes streams - * present when you first connect to the session. - * - * @see Session.connect()

- * @augments Event - */ + /** + * The Session object dispatches SessionConnectEvent object when a session has successfully + * connected in response to a call to the connect() method of the Session object. + *

+ * In version 2.2, the completionHandler of the Session.connect() method + * indicates success or failure in connecting to the session. + * + * @class SessionConnectEvent + * @property {Array} connections Deprecated in version 2.2 (and set to an empty array). In + * version 2.2, listen for the connectionCreated event dispatched by the Session + * object. In version 2.2, the Session object dispatches a connectionCreated event + * for each connection (including your own). This includes connections present when you first + * connect to the session. + * + * @property {Array} streams Deprecated in version 2.2 (and set to an empty array). In version + * 2.2, listen for the streamCreated event dispatched by the Session object. In + * version 2.2, the Session object dispatches a streamCreated event for each stream + * other than those published by your client. This includes streams + * present when you first connect to the session. + * + * @see Session.connect()

+ * @augments Event + */ var sessionConnectedConnectionsDeprecationWarningShown = false; var sessionConnectedStreamsDeprecationWarningShown = false; var sessionConnectedArchivesDeprecationWarningShown = false; - OT.SessionConnectEvent = function (type) { + OT.SessionConnectEvent = function(type) { OT.Event.call(this, type, false); if (OT.$.canDefineProperty) { Object.defineProperties(this, { connections: { get: function() { - if(!sessionConnectedConnectionsDeprecationWarningShown) { + if (!sessionConnectedConnectionsDeprecationWarningShown) { OT.warn('OT.SessionConnectedEvent no longer includes connections. Listen ' + 'for connectionCreated events instead.'); sessionConnectedConnectionsDeprecationWarningShown = true; @@ -18455,7 +18928,7 @@ OT.IntervalRunner = function(callback, frequency) { }, streams: { get: function() { - if(!sessionConnectedStreamsDeprecationWarningShown) { + if (!sessionConnectedStreamsDeprecationWarningShown) { OT.warn('OT.SessionConnectedEvent no longer includes streams. Listen for ' + 'streamCreated events instead.'); sessionConnectedConnectionsDeprecationWarningShown = true; @@ -18465,7 +18938,7 @@ OT.IntervalRunner = function(callback, frequency) { }, archives: { get: function() { - if(!sessionConnectedArchivesDeprecationWarningShown) { + if (!sessionConnectedArchivesDeprecationWarningShown) { OT.warn('OT.SessionConnectedEvent no longer includes archives. Listen for ' + 'archiveStarted events instead.'); sessionConnectedArchivesDeprecationWarningShown = true; @@ -18481,109 +18954,124 @@ OT.IntervalRunner = function(callback, frequency) { } }; -/** - * The Session object dispatches SessionDisconnectEvent object when a session has disconnected. - * This event may be dispatched asynchronously in response to a successful call to the - * disconnect() method of the session object. - * - *

- * Example - *

- *

- * The following code initializes a session and sets up an event listener for when a session is - * disconnected. - *

- *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
- *  var sessionID = ""; // Replace with your own session ID.
- *                      // See https://dashboard.tokbox.com/projects
- *  var token = ""; // Replace with a generated token that has been assigned the moderator role.
- *                  // See https://dashboard.tokbox.com/projects
- *
- *  var session = OT.initSession(apiKey, sessionID);
- *  session.on("sessionDisconnected", function(event) {
- *      alert("The session disconnected. " + event.reason);
- *  });
- *  session.connect(token);
- *  
- * - * @property {String} reason A description of why the session disconnected. - * This property can have two values: - *

- *
    - *
  • "clientDisconnected" — A client disconnected from the session by calling - * the disconnect() method of the Session object or by closing the browser. - * ( See Session.disconnect().)
  • - *
  • "forceDisconnected" — A moderator has disconnected you from the session - * by calling the forceDisconnect() method of the Session object. (See - * Session.forceDisconnect().)
  • - *
  • "networkDisconnected" — The network connection terminated abruptly - * (for example, the client lost their internet connection).
  • - *
- * - * @class SessionDisconnectEvent - * @augments Event - */ - OT.SessionDisconnectEvent = function (type, reason, cancelable) { + /** + * The Session object dispatches SessionDisconnectEvent object when a session has disconnected. + * This event may be dispatched asynchronously in response to a successful call to the + * disconnect() method of the session object. + * + *

+ * Example + *

+ *

+ * The following code initializes a session and sets up an event listener for when a session is + * disconnected. + *

+ *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+   *  var sessionID = ""; // Replace with your own session ID.
+   *                      // See https://dashboard.tokbox.com/projects
+   *  var token = ""; // Replace with a generated token that has been assigned the moderator role.
+   *                  // See https://dashboard.tokbox.com/projects
+   *
+   *  var session = OT.initSession(apiKey, sessionID);
+   *  session.on("sessionDisconnected", function(event) {
+   *      alert("The session disconnected. " + event.reason);
+   *  });
+   *  session.connect(token);
+   *  
+ * + * @property {String} reason A description of why the session disconnected. + * This property can have two values: + *

+ *
    + *
  • + * "clientDisconnected" — A client disconnected from the + * session by calling the disconnect() method of the Session + * object or by closing the browser. ( See Session.disconnect().) + *
  • + * + *
  • + * "forceDisconnected" — A moderator has disconnected you from + * the session by calling the forceDisconnect() method of the + * Session object. (See Session.forceDisconnect().) + *
  • + * + *
  • "networkDisconnected" — The network connection terminated + * abruptly (for example, the client lost their internet connection).
  • + *
+ *
    + * + * @class SessionDisconnectEvent + * @augments Event + */ + OT.SessionDisconnectEvent = function(type, reason, cancelable) { OT.Event.call(this, type, cancelable); this.reason = reason; }; -/** -* Prevents the default behavior associated with the event from taking place. -* -*

    For the sessionDisconnectEvent, the default behavior is that all Subscriber -* objects are unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a -* destroyed event when the element is removed from the HTML DOM. If you call the -* preventDefault() method in the event listener for the sessionDisconnect -* event, the default behavior is prevented, and you can, optionally, clean up Subscriber objects -* using your own code). -* -*

    To see whether an event has a default behavior, check the cancelable property of -* the event object.

    -* -*

    Call the preventDefault() method in the event listener function for the event.

    -* -* @method #preventDefault -* @memberof SessionDisconnectEvent -*/ + /** + * Prevents the default behavior associated with the event from taking place. + * + *

    + * For the sessionDisconnectEvent, the default behavior is that all + * Subscriber objects are unsubscribed and removed from the HTML DOM. Each + * Subscriber object dispatches a destroyed event when the element + * is removed from the HTML DOM. If you call the preventDefault() + * method in the event listener for the sessionDisconnect event, + * the default behavior is prevented, and you can, optionally, clean up + * Subscriber objects using your own code). * + *

    + *

    + * To see whether an event has a default behavior, check the + * cancelable property of the event object. + *

    * + *

    + * Call the preventDefault() method in the event listener function + * for the event. + *

    + * + * @method #preventDefault + * @memberof SessionDisconnectEvent + */ -/** - * The Session object dispatches a streamPropertyChanged event in the - * following circumstances: - * - *
      - *
    • A stream has started or stopped publishing audio or video (see - * Publisher.publishAudio() and - * Publisher.publishVideo()). - * This change results from a call to the publishAudio() or - * publishVideo() methods of the Publish object. Note that a - * subscriber's video can be disabled or enabled for reasons other than the - * publisher disabling or enabling it. A Subscriber object dispatches - * videoDisabled and videoEnabled events in all - * conditions that cause the subscriber's stream to be disabled or enabled. - *
    • - *
    • The videoDimensions property of the Stream object has - * changed (see Stream.videoDimensions). - *
    • - *
    • The videoType property of the Stream object has changed. - * This can happen in a stream published by a mobile device. (See - * Stream.videoType.) - *
    • - *
    - * - * @class StreamPropertyChangedEvent - * @property {String} changedProperty The property of the stream that changed. This value - * is either "hasAudio", "hasVideo", or "videoDimensions". - * @property {Object} newValue The new value of the property (after the change). - * @property {Object} oldValue The old value of the property (before the change). - * @property {Stream} stream The Stream object for which a property has changed. - * - * @see Publisher.publishAudio()

    - * @see Publisher.publishVideo()

    - * @see Stream.videoDimensions

    - * @augments Event - */ - OT.StreamPropertyChangedEvent = function (type, stream, changedProperty, oldValue, newValue) { + /** + * The Session object dispatches a streamPropertyChanged event in the + * following circumstances: + * + *
      + *
    • A stream has started or stopped publishing audio or video (see + * Publisher.publishAudio() and + * Publisher.publishVideo()). + * This change results from a call to the publishAudio() or + * publishVideo() methods of the Publish object. Note that a + * subscriber's video can be disabled or enabled for reasons other than the + * publisher disabling or enabling it. A Subscriber object dispatches + * videoDisabled and videoEnabled events in all + * conditions that cause the subscriber's stream to be disabled or enabled. + *
    • + *
    • The videoDimensions property of the Stream object has + * changed (see Stream.videoDimensions). + *
    • + *
    • The videoType property of the Stream object has changed. + * This can happen in a stream published by a mobile device. (See + * Stream.videoType.) + *
    • + *
    + * + * @class StreamPropertyChangedEvent + * @property {String} changedProperty The property of the stream that changed. This value + * is either "hasAudio", "hasVideo", or "videoDimensions". + * @property {Object} newValue The new value of the property (after the change). + * @property {Object} oldValue The old value of the property (before the change). + * @property {Stream} stream The Stream object for which a property has changed. + * + * @see Publisher.publishAudio()

    + * @see Publisher.publishVideo()

    + * @see Stream.videoDimensions

    + * @augments Event + */ + OT.StreamPropertyChangedEvent = function(type, stream, changedProperty, oldValue, newValue) { OT.Event.call(this, type, false); this.type = type; this.stream = stream; @@ -18592,7 +19080,7 @@ OT.IntervalRunner = function(callback, frequency) { this.newValue = newValue; }; - OT.VideoDimensionsChangedEvent = function (target, oldValue, newValue) { + OT.VideoDimensionsChangedEvent = function(target, oldValue, newValue) { OT.Event.call(this, 'videoDimensionsChanged', false); this.type = 'videoDimensionsChanged'; this.target = target; @@ -18600,20 +19088,20 @@ OT.IntervalRunner = function(callback, frequency) { this.newValue = newValue; }; -/** - * Defines event objects for the archiveStarted and archiveStopped events. - * The Session object dispatches these events when an archive recording of the session starts and - * stops. - * - * @property {String} id The archive ID. - * @property {String} name The name of the archive. You can assign an archive a name when you create - * it, using the OpenTok REST API or one of the - * OpenTok server SDKs. - * - * @class ArchiveEvent - * @augments Event - */ - OT.ArchiveEvent = function (type, archive) { + /** + * Defines event objects for the archiveStarted and archiveStopped + * events. The Session object dispatches these events when an archive recording of the session + * starts and stops. + * + * @property {String} id The archive ID. + * @property {String} name The name of the archive. You can assign an archive a name when you + * create it, using the OpenTok REST API or one + * of the OpenTok server SDKs. + * + * @class ArchiveEvent + * @augments Event + */ + OT.ArchiveEvent = function(type, archive) { OT.Event.call(this, type, false); this.type = type; this.id = archive.id; @@ -18622,7 +19110,7 @@ OT.IntervalRunner = function(callback, frequency) { this.archive = archive; }; - OT.ArchiveUpdatedEvent = function (stream, key, oldValue, newValue) { + OT.ArchiveUpdatedEvent = function(stream, key, oldValue, newValue) { OT.Event.call(this, 'updated', false); this.target = stream; this.changedProperty = key; @@ -18630,26 +19118,28 @@ OT.IntervalRunner = function(callback, frequency) { this.newValue = newValue; }; -/** - * The Session object dispatches a signal event when the client receives a signal from the session. - * - * @class SignalEvent - * @property {String} type The type assigned to the signal (if there is one). Use the type to - * filter signals received (by adding an event handler for signal:type1 or signal:type2, etc.) - * @property {String} data The data string sent with the signal (if there is one). - * @property {Connection} from The Connection corresponding to the client that sent with the signal. - * - * @see Session.signal()

    - * @see Session events (signal and signal:type)

    - * @augments Event - */ + /** + * The Session object dispatches a signal event when the client receives a signal from the + * session. + * + * @class SignalEvent + * @property {String} type The type assigned to the signal (if there is one). Use the type to + * filter signals received (by adding an event handler for signal:type1 or signal:type2, etc.) + * @property {String} data The data string sent with the signal (if there is one). + * @property {Connection} from The Connection corresponding to the client that sent with the + * signal. + * + * @see Session.signal()

    + * @see Session events (signal and signal:type)

    + * @augments Event + */ OT.SignalEvent = function(type, data, from) { OT.Event.call(this, type ? 'signal:' + type : OT.Event.names.SIGNAL, false); this.data = data; this.from = from; }; - OT.StreamUpdatedEvent = function (stream, key, oldValue, newValue) { + OT.StreamUpdatedEvent = function(stream, key, oldValue, newValue) { OT.Event.call(this, 'updated', false); this.target = stream; this.changedProperty = key; @@ -18663,61 +19153,61 @@ OT.IntervalRunner = function(callback, frequency) { this.reason = reason; }; -/** - * Defines the event object for the videoDisabled and videoEnabled events - * dispatched by the Subscriber. - * - * @class VideoEnabledChangedEvent - * - * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable - * (true) or not (false). You can cancel the default behavior by - * calling the preventDefault() method of the event object in the callback - * function. (See preventDefault().) - * - * @property {String} reason The reason the video was disabled or enabled. This can be set to one of - * the following values: - * - *
      - * - *
    • "publishVideo" — The publisher started or stopped publishing video, - * by calling publishVideo(true) or publishVideo(false).
    • - * - *
    • "quality" — The OpenTok Media Router starts or stops sending video - * to the subscriber based on stream quality changes. This feature of the OpenTok Media - * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber - * continues to receive the audio stream, if there is one.) - *

      - * If connectivity improves to support video again, the Subscriber object dispatches - * a videoEnabled event, and the Subscriber resumes receiving video. - *

      - * By default, the Subscriber displays a video disabled indicator when a - * videoDisabled event with this reason is dispatched and removes the indicator - * when the videoDisabled event with this reason is dispatched. You can control - * the display of this icon by calling the setStyle() method of the Subscriber, - * setting the videoDisabledDisplayMode property(or you can set the style when - * calling the Session.subscribe() method, setting the style property - * of the properties parameter). - *

      - * This feature is only available in sessions that use the OpenTok Media Router (sessions with - * the media mode - * set to routed), not in sessions with the media mode set to relayed. - *

    • - * - *
    • "subscribeToVideo" — The subscriber started or stopped subscribing to - * video, by calling subscribeToVideo(true) or subscribeToVideo(false). - *
    • - * - *
    - * - * @property {Object} target The object that dispatched the event. - * - * @property {String} type The type of event: "videoDisabled" or - * "videoEnabled". - * - * @see Subscriber videoDisabled event

    - * @see Subscriber videoEnabled event

    - * @augments Event - */ + /** + * Defines the event object for the videoDisabled and videoEnabled + * events dispatched by the Subscriber. + * + * @class VideoEnabledChangedEvent + * + * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable + * (true) or not (false). You can cancel the default behavior by + * calling the preventDefault() method of the event object in the callback + * function. (See preventDefault().) + * + * @property {String} reason The reason the video was disabled or enabled. This can be set to one + * of the following values: + * + *
      + * + *
    • "publishVideo" — The publisher started or stopped publishing video, + * by calling publishVideo(true) or publishVideo(false).
    • + * + *
    • "quality" — The OpenTok Media Router starts or stops sending video + * to the subscriber based on stream quality changes. This feature of the OpenTok Media + * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber + * continues to receive the audio stream, if there is one.) + *

      + * If connectivity improves to support video again, the Subscriber object dispatches + * a videoEnabled event, and the Subscriber resumes receiving video. + *

      + * By default, the Subscriber displays a video disabled indicator when a + * videoDisabled event with this reason is dispatched and removes the indicator + * when the videoDisabled event with this reason is dispatched. You can control + * the display of this icon by calling the setStyle() method of the Subscriber, + * setting the videoDisabledDisplayMode property(or you can set the style when + * calling the Session.subscribe() method, setting the style property + * of the properties parameter). + *

      + * This feature is only available in sessions that use the OpenTok Media Router (sessions with + * the media mode + * set to routed), not in sessions with the media mode set to relayed. + *

    • + * + *
    • "subscribeToVideo" — The subscriber started or stopped subscribing to + * video, by calling subscribeToVideo(true) or + * subscribeToVideo(false).
    • + * + *
    + * + * @property {Object} target The object that dispatched the event. + * + * @property {String} type The type of event: "videoDisabled" or + * "videoEnabled". + * + * @see Subscriber videoDisabled event

    + * @see Subscriber videoEnabled event

    + * @augments Event + */ OT.VideoEnabledChangedEvent = function(type, properties) { OT.Event.call(this, type, false); this.reason = properties.reason; @@ -18727,17 +19217,17 @@ OT.IntervalRunner = function(callback, frequency) { OT.Event.call(this, type, false); }; -/** - * Dispatched periodically by a Subscriber or Publisher object to indicate the audio - * level. This event is dispatched up to 60 times per second, depending on the browser. - * - * @property {String} audioLevel The audio level, from 0 to 1.0. Adjust this value logarithmically - * for use in adjusting a user interface element, such as a volume meter. Use a moving average - * to smooth the data. - * - * @class AudioLevelUpdatedEvent - * @augments Event - */ + /** + * Dispatched periodically by a Subscriber or Publisher object to indicate the audio + * level. This event is dispatched up to 60 times per second, depending on the browser. + * + * @property {String} audioLevel The audio level, from 0 to 1.0. Adjust this value logarithmically + * for use in adjusting a user interface element, such as a volume meter. Use a moving average + * to smooth the data. + * + * @class AudioLevelUpdatedEvent + * @augments Event + */ OT.AudioLevelUpdatedEvent = function(audioLevel) { OT.Event.call(this, OT.Event.names.AUDIO_LEVEL_UPDATED, false); this.audioLevel = audioLevel; @@ -18980,8 +19470,8 @@ OT.registerScreenSharingExtensionHelper('chrome', { window: false, browser: false }, - register: function (extensionID) { - if(!extensionID) { + register: function(extensionID) { + if (!extensionID) { throw new Error('initChromeScreenSharingExtensionHelper: extensionID is required.'); } @@ -19004,7 +19494,7 @@ OT.registerScreenSharingExtensionHelper('chrome', { timeout = null; fn.apply(null, arguments); }; - if(timeToWait) { + if (timeToWait) { timeout = setTimeout(function() { delete callbackRegistry[requestId]; fn(new Error('Timeout waiting for response to request.')); @@ -19014,19 +19504,19 @@ OT.registerScreenSharingExtensionHelper('chrome', { }; var isAvailable = function(callback) { - if(!callback) { + if (!callback) { throw new Error('isAvailable: callback is required.'); } - if(!isChrome) { + if (!isChrome) { setTimeout(callback.bind(null, false)); } - if(isInstalled !== void 0) { + if (isInstalled !== void 0) { setTimeout(callback.bind(null, isInstalled)); } else { var requestId = addCallback(function(error, event) { - if(isInstalled !== true) { + if (isInstalled !== true) { isInstalled = (event === 'extensionLoaded'); } callback(isInstalled); @@ -19037,13 +19527,13 @@ OT.registerScreenSharingExtensionHelper('chrome', { }; var getConstraints = function(source, constraints, callback) { - if(!callback) { + if (!callback) { throw new Error('getSourceId: callback is required'); } isAvailable(function(isInstalled) { - if(isInstalled) { + if (isInstalled) { var requestId = addCallback(function(error, event, payload) { - if(event === 'permissionDenied') { + if (event === 'permissionDenied') { callback(new Error('PermissionDeniedError')); } else { if (!constraints.video) { @@ -19070,26 +19560,26 @@ OT.registerScreenSharingExtensionHelper('chrome', { return; } - if(!(event.data != null && typeof event.data === 'object')) { + if (!(event.data != null && typeof event.data === 'object')) { return; } - if(event.data.from !== 'extension') { + if (event.data.from !== 'extension') { return; } var method = event.data[prefix], payload = event.data.payload; - if(payload && payload.requestId) { + if (payload && payload.requestId) { var callback = callbackRegistry[payload.requestId]; delete callbackRegistry[payload.requestId]; - if(callback) { + if (callback) { callback(null, method, payload); } } - if(method === 'extensionLoaded') { + if (method === 'extensionLoaded') { isInstalled = true; } }); @@ -19106,11 +19596,6 @@ OT.registerScreenSharingExtensionHelper('chrome', { // tb_require('../helpers/lib/video_element.js') // tb_require('./events.js') -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global OT */ - - // id: String | mandatory | immutable // type: String {video/audio/data/...} | mandatory | immutable // active: Boolean | mandatory | mutable @@ -19118,12 +19603,19 @@ OT.registerScreenSharingExtensionHelper('chrome', { // frameRate: Float | optional | mutable // height: Integer | optional | mutable // width: Integer | optional | mutable +// maxFrameRate: Float | optional | mutable +// maxHeight: Integer | optional | mutable +// maxWidth: Integer | optional | mutable +// OT.StreamChannel = function(options) { this.id = options.id; this.type = options.type; this.active = OT.$.castToBoolean(options.active); this.orientation = options.orientation || OT.VideoOrientation.ROTATED_NORMAL; - if (options.frameRate) this.frameRate = parseFloat(options.frameRate, 10); + if (options.frameRate) this.frameRate = parseFloat(options.frameRate); + if (options.maxFrameRate) this.maxFrameRate = parseFloat(options.maxFrameRate); + if (options.maxWidth) this.maxWidth = parseInt(options.maxWidth, 10); + if (options.maxHeight) this.maxHeight = parseInt(options.maxHeight, 10); this.width = parseInt(options.width, 10); this.height = parseInt(options.height, 10); @@ -19139,14 +19631,14 @@ OT.StreamChannel = function(options) { oldVideoDimensions = {}; for (var key in attributes) { - if(!attributes.hasOwnProperty(key)) { + if (!attributes.hasOwnProperty(key)) { continue; } // we shouldn't really read this before we know the key is valid var oldValue = this[key]; - switch(key) { + switch (key) { case 'active': this.active = OT.$.castToBoolean(attributes[key]); break; @@ -19206,80 +19698,75 @@ OT.StreamChannel = function(options) { // tb_require('./events.js') // tb_require('./stream_channel.js') -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global OT */ - !(function() { var validPropertyNames = ['name', 'archiving']; -/** - * Specifies a stream. A stream is a representation of a published stream in a session. When a - * client calls the Session.publish() method, a new stream is - * created. Properties of the Stream object provide information about the stream. - * - *

    When a stream is added to a session, the Session object dispatches a - * streamCreatedEvent. When a stream is destroyed, the Session object dispatches a - * streamDestroyed event. The StreamEvent object, which defines these event objects, - * has a stream property, which is an array of Stream object. For details and a code - * example, see {@link StreamEvent}.

    - * - *

    When a connection to a session is made, the Session object dispatches a - * sessionConnected event, defined by the SessionConnectEvent object. The - * SessionConnectEvent object has a streams property, which is an array of Stream - * objects pertaining to the streams in the session at that time. For details and a code example, - * see {@link SessionConnectEvent}.

    - * - * @class Stream - * @property {Connection} connection The Connection object corresponding - * to the connection that is publishing the stream. You can compare this to to the - * connection property of the Session object to see if the stream is being published - * by the local web page. - * - * @property {Number} creationTime The timestamp for the creation - * of the stream. This value is calculated in milliseconds. You can convert this value to a - * Date object by calling new Date(creationTime), where creationTime is - * the creationTime property of the Stream object. - * - * @property {Number} frameRate The frame rate of the video stream. This property is only set if the - * publisher of the stream specifies a frame rate when calling the OT.initPublisher() - * method; otherwise, this property is undefined. - * - * @property {Boolean} hasAudio Whether the stream has audio. This property can change if the - * publisher turns on or off audio (by calling - * Publisher.publishAudio()). When this occurs, the - * {@link Session} object dispatches a streamPropertyChanged event (see - * {@link StreamPropertyChangedEvent}). - * - * @property {Boolean} hasVideo Whether the stream has video. This property can change if the - * publisher turns on or off video (by calling - * Publisher.publishVideo()). When this occurs, the - * {@link Session} object dispatches a streamPropertyChanged event (see - * {@link StreamPropertyChangedEvent}). - * - * @property {String} name The name of the stream. Publishers can specify a name when publishing - * a stream (using the publish() method of the publisher's Session object). - * - * @property {String} streamId The unique ID of the stream. - * - * @property {Object} videoDimensions This object has two properties: width and - * height. Both are numbers. The width property is the width of the - * encoded stream; the height property is the height of the encoded stream. (These - * are independent of the actual width of Publisher and Subscriber objects corresponding to the - * stream.) This property can change if a stream published from a mobile device resizes, based on - * a change in the device orientation. When the video dimensions change, - * the {@link Session} object dispatches a streamPropertyChanged event - * (see {@link StreamPropertyChangedEvent}). - * - * @property {String} videoType The type of video — either "camera" or - * "screen". A "screen" video uses screen sharing on the publisher - * as the video source; for other videos, this property is set to "camera". - * This property can change if a stream published from a mobile device changes from a - * camera to a screen-sharing video type. When the video type changes, the {@link Session} object - * dispatches a streamPropertyChanged event (see {@link StreamPropertyChangedEvent}). - */ - + /** + * Specifies a stream. A stream is a representation of a published stream in a session. When a + * client calls the Session.publish() method, a new stream is + * created. Properties of the Stream object provide information about the stream. + * + *

    When a stream is added to a session, the Session object dispatches a + * streamCreatedEvent. When a stream is destroyed, the Session object dispatches a + * streamDestroyed event. The StreamEvent object, which defines these event objects, + * has a stream property, which is an array of Stream object. For details and a code + * example, see {@link StreamEvent}.

    + * + *

    When a connection to a session is made, the Session object dispatches a + * sessionConnected event, defined by the SessionConnectEvent object. The + * SessionConnectEvent object has a streams property, which is an array of Stream + * objects pertaining to the streams in the session at that time. For details and a code example, + * see {@link SessionConnectEvent}.

    + * + * @class Stream + * @property {Connection} connection The Connection object corresponding + * to the connection that is publishing the stream. You can compare this to to the + * connection property of the Session object to see if the stream is being published + * by the local web page. + * + * @property {Number} creationTime The timestamp for the creation + * of the stream. This value is calculated in milliseconds. You can convert this value to a + * Date object by calling new Date(creationTime), where creationTime is + * the creationTime property of the Stream object. + * + * @property {Number} frameRate The frame rate of the video stream. This property is only set if + * the publisher of the stream specifies a frame rate when calling the + * OT.initPublisher() method; otherwise, this property is undefined. + * + * @property {Boolean} hasAudio Whether the stream has audio. This property can change if the + * publisher turns on or off audio (by calling + * Publisher.publishAudio()). When this occurs, the + * {@link Session} object dispatches a streamPropertyChanged event (see + * {@link StreamPropertyChangedEvent}). + * + * @property {Boolean} hasVideo Whether the stream has video. This property can change if the + * publisher turns on or off video (by calling + * Publisher.publishVideo()). When this occurs, the + * {@link Session} object dispatches a streamPropertyChanged event (see + * {@link StreamPropertyChangedEvent}). + * + * @property {String} name The name of the stream. Publishers can specify a name when publishing + * a stream (using the publish() method of the publisher's Session object). + * + * @property {String} streamId The unique ID of the stream. + * + * @property {Object} videoDimensions This object has two properties: width and + * height. Both are numbers. The width property is the width of the + * encoded stream; the height property is the height of the encoded stream. (These + * are independent of the actual width of Publisher and Subscriber objects corresponding to the + * stream.) This property can change if a stream published from a mobile device resizes, based on + * a change in the device orientation. When the video dimensions change, + * the {@link Session} object dispatches a streamPropertyChanged event + * (see {@link StreamPropertyChangedEvent}). + * + * @property {String} videoType The type of video — either "camera" or + * "screen". A "screen" video uses screen sharing on the publisher + * as the video source; for other videos, this property is set to "camera". + * This property can change if a stream published from a mobile device changes from a + * camera to a screen-sharing video type. When the video type changes, the {@link Session} object + * dispatches a streamPropertyChanged event (see {@link StreamPropertyChangedEvent}). + */ OT.Stream = function(id, name, creationTime, connection, session, channel) { var destroyedReason; @@ -19297,14 +19784,14 @@ OT.StreamChannel = function(options) { var onChannelUpdate = OT.$.bind(function(channel, key, oldValue, newValue) { var _key = key; - switch(_key) { + switch (_key) { case 'active': _key = channel.type === 'audio' ? 'hasAudio' : 'hasVideo'; this[_key] = newValue; break; case 'disableWarning': - _key = channel.type === 'audio' ? 'audioDisableWarning': 'videoDisableWarning'; + _key = channel.type === 'audio' ? 'audioDisableWarning' : 'videoDisableWarning'; this[_key] = newValue; if (!this[channel.type === 'audio' ? 'hasAudio' : 'hasVideo']) { return; // Do NOT event in this case. @@ -19321,49 +19808,62 @@ OT.StreamChannel = function(options) { this[_key] = newValue; break; + case 'videoDimensions': + this.videoDimensions = newValue; + break; + case 'orientation': case 'width': case 'height': - this.videoDimensions = { - width: channel.width, - height: channel.height, - orientation: channel.orientation - }; - - // We dispatch this via the videoDimensions key instead + // We dispatch this via the videoDimensions key instead so do not + // trigger an event for them. return; } - this.dispatchEvent( new OT.StreamUpdatedEvent(this, _key, oldValue, newValue) ); + this.dispatchEvent(new OT.StreamUpdatedEvent(this, _key, oldValue, newValue)); }, this); var associatedWidget = OT.$.bind(function() { - if(this.publisher) { + if (this.publisher) { return this.publisher; } else { return OT.subscribers.find(function(subscriber) { - return subscriber.stream.id === this.id && + return subscriber.stream && subscriber.stream.id === this.id && subscriber.session.id === session.id; - }); + }, this); } }, this); + // Returns true if this stream is subscribe to. + var isBeingSubscribedTo = OT.$.bind(function() { + // @fixme This is not strictly speaking the right test as a stream + // can be published and subscribed by the same connection. But the + // update features don't handle this case properly right now anyway. + // + // The issue is that the stream needs to know whether the stream is + // 'owned' by a publisher or a subscriber. The reason for that is that + // when a Publisher updates a stream channel then we need to send the + // `streamChannelUpdate` message, whereas if a Subscriber does then we + // need to send `subscriberChannelUpdate`. The current code will always + // send `streamChannelUpdate`. + return !this.publisher; + }, this); + // Returns all channels that have a type of +type+. - this.getChannelsOfType = function (type) { + this.getChannelsOfType = function(type) { return OT.$.filter(this.channel, function(channel) { return channel.type === type; }); }; - this.getChannel = function (id) { - for (var i=0; iwindow.setInterval. + * + * @param {function()} callback + * @param {number} frequency how many times per second we want to execute the callback + * @constructor + */ +OT.IntervalRunner = function(callback, frequency) { + var _callback = callback, + _frequency = frequency, + _intervalId = null; - var parseErrorFromJSONDocument, - onGetResponseCallback, - onGetErrorCallback; - - OT.SessionInfo = function(jsonDocument) { - var sessionJSON = jsonDocument[0]; - - OT.log('SessionInfo Response:'); - OT.log(jsonDocument); - - /*jshint camelcase:false*/ - - this.sessionId = sessionJSON.session_id; - this.partnerId = sessionJSON.partner_id; - this.sessionStatus = sessionJSON.session_status; - - this.messagingServer = sessionJSON.messaging_server_url; - - this.messagingURL = sessionJSON.messaging_url; - this.symphonyAddress = sessionJSON.symphony_address; - - this.p2pEnabled = !!(sessionJSON.properties && - sessionJSON.properties.p2p && - sessionJSON.properties.p2p.preference && - sessionJSON.properties.p2p.preference.value === 'enabled'); + this.start = function() { + _intervalId = window.setInterval(_callback, 1000 / _frequency); }; - // Retrieves Session Info for +session+. The SessionInfo object will be passed - // to the +onSuccess+ callback. The +onFailure+ callback will be passed an error - // object and the DOMEvent that relates to the error. - OT.SessionInfo.get = function(session, onSuccess, onFailure) { - var sessionInfoURL = OT.properties.apiURL + '/session/' + session.id + '?extended=true', - - startTime = OT.$.now(), - - options, - - validateRawSessionInfo = function(sessionInfo) { - session.logEvent('Instrumentation', null, 'gsi', OT.$.now() - startTime); - var error = parseErrorFromJSONDocument(sessionInfo); - if (error === false) { - onGetResponseCallback(session, onSuccess, sessionInfo); - } else { - onGetErrorCallback(session, onFailure, error, JSON.stringify(sessionInfo)); - } - }; - - - if(OT.$.env.name === 'IE' && OT.$.env.version < 10) { - sessionInfoURL = sessionInfoURL + '&format=json&token=' + encodeURIComponent(session.token) + - '&version=1&cache=' + OT.$.uuid(); - options = { - xdomainrequest: true - }; - } - else { - options = { - headers: { - 'X-TB-TOKEN-AUTH': session.token, - 'X-TB-VERSION': 1 - } - }; - } - session.logEvent('SessionInfo', 'Attempt'); - OT.$.getJSON(sessionInfoURL, options, function(error, sessionInfo) { - if(error) { - var responseText = sessionInfo; - onGetErrorCallback(session, onFailure, - new OT.Error(error.target && error.target.status || error.code, error.message || - 'Could not connect to the OpenTok API Server.'), responseText); - } else { - validateRawSessionInfo(sessionInfo); - } - }); + this.stop = function() { + window.clearInterval(_intervalId); + _intervalId = null; }; - - var messageServerToClientErrorCodes = {}; - messageServerToClientErrorCodes['404'] = OT.ExceptionCodes.INVALID_SESSION_ID; - messageServerToClientErrorCodes['409'] = OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE; - messageServerToClientErrorCodes['400'] = OT.ExceptionCodes.INVALID_SESSION_ID; - messageServerToClientErrorCodes['403'] = OT.ExceptionCodes.AUTHENTICATION_ERROR; - - // Return the error in +jsonDocument+, if there is one. Otherwise it will return - // false. - parseErrorFromJSONDocument = function(jsonDocument) { - if(OT.$.isArray(jsonDocument)) { - - var errors = OT.$.filter(jsonDocument, function(node) { - return node.error != null; - }); - - var numErrorNodes = errors.length; - if(numErrorNodes === 0) { - return false; - } - - var errorCode = errors[0].error.code; - var errorMessage; - if (messageServerToClientErrorCodes[errorCode.toString()]) { - errorCode = messageServerToClientErrorCodes[errorCode]; - errorMessage = errors[0].error.errorMessage && errors[0].error.errorMessage.message; - } else { - errorCode = OT.ErrorCodes.UNEXPECTED_SERVER_RESPONSE; - errorMessage = 'Unexpected server response. Try this operation again later.'; - } - - return { - code: errorCode, - message: errorMessage - }; - } else { - return { - code: null, - message: 'Unknown error: getSessionInfo JSON response was badly formed' - }; - } - }; - - /* jshint camelcase:false */ - onGetResponseCallback = function(session, onSuccess, rawSessionInfo) { - session.logEvent('SessionInfo', 'Success', - {messagingServer: rawSessionInfo[0].messaging_server_url}); - - onSuccess( new OT.SessionInfo(rawSessionInfo) ); - }; - /* jshint camelcase:true */ - - onGetErrorCallback = function(session, onFailure, error, responseText) { - var payload = { - reason:'GetSessionInfo', - code: (error.code || 'No code'), - message: error.message + ':' + - (responseText || 'Empty responseText from API server') - }; - session.logConnectivityEvent('Failure', payload); - - onFailure(error, session); - }; - -})(window); +}; // tb_require('../helpers/helpers.js') // tb_require('../helpers/lib/properties.js') @@ -19684,6 +20179,7 @@ OT.StreamChannel = function(options) { *
  • Session.publish()
  • *
  • Session.subscribe()
  • *
  • OT.initPublisher()
  • + *
  • OT.reportIssue()
  • *
* *

@@ -19712,12 +20208,12 @@ OT.StreamChannel = function(options) { * in an expired token when trying to connect to a session. It can also occur if you pass * in an invalid token or API key. Make sure that you are generating the token using the * current version of one of the - * OpenTok server SDKs. + * OpenTok server SDKs. * * * 1005 * Invalid Session ID. Make sure you generate the session ID using the current version of - * one of the OpenTok server + * one of the OpenTok server * SDKs. * * @@ -19895,7 +20391,7 @@ OT.StreamChannel = function(options) { * pass in an expired token when trying to connect to a session. It can also occur if you * pass in an invalid token or API key. Make sure that you are generating the token using * the current version of one of the - * OpenTok server SDKs. + * OpenTok server SDKs. * * * 1550 @@ -19925,6 +20421,19 @@ OT.StreamChannel = function(options) { * * * + *

Errors when calling OT.initPublisher():

+ * + * + * + * + * + * + * + * + * + * + *
codeDescription
2011Error calling OT.reportIssue(). Check the client's network connection.
+ * *

General errors that can occur when calling any method:

* * @@ -19981,7 +20490,7 @@ OT.StreamChannel = function(options) { }; function _exceptionHandler(component, msg, errorCode, context) { - var title = errorsCodesToTitle[errorCode], + var title = OT.getErrorTitleByCode(errorCode), contextCopy = context ? OT.$.clone(context) : {}; OT.error('OT.exception :: title: ' + title + ' (' + errorCode + ') msg: ' + msg); @@ -19994,7 +20503,7 @@ OT.StreamChannel = function(options) { OT.dispatchEvent( new OT.ExceptionEvent(OT.Event.names.EXCEPTION, msg, title, errorCode, component, component) ); - } catch(err) { + } catch (err) { OT.error('OT.exception :: Failed to dispatch exception - ' + err.toString()); // Don't throw an error because this is asynchronous // don't do an exceptionHandler because that would be recursive @@ -20015,15 +20524,15 @@ OT.StreamChannel = function(options) { return errorsCodesToTitle[+code]; }; -// @todo redo this when we have time to tidy up -// -// @example -// -// OT.handleJsException("Descriptive error message", 2000, { -// session: session, -// target: stream|publisher|subscriber|session|etc -// }); -// + // @todo redo this when we have time to tidy up + // + // @example + // + // OT.handleJsException("Descriptive error message", 2000, { + // session: session, + // target: stream|publisher|subscriber|session|etc + // }); + // OT.handleJsException = function(errorMsg, code, options) { options = options || {}; @@ -20049,19 +20558,6 @@ OT.StreamChannel = function(options) { _exceptionHandler(options.target, errorMsg, code, context); }; - // This is a placeholder until error handling can be rewritten - OT.dispatchError = function (code, message, completionHandler, session) { - OT.error(code, message); - - if (completionHandler && OT.$.isFunction(completionHandler)) { - completionHandler.call(null, new OT.Error(code, message)); - } - - OT.handleJsException(message, code, { - session: session - }); - }; - })(window); // tb_require('../helpers/helpers.js') @@ -20100,7 +20596,6 @@ OT.StreamChannel = function(options) { } }, - onDomReady = function() { OT.$.onDOMUnload(onDomUnload); @@ -20138,7 +20633,6 @@ OT.StreamChannel = function(options) { configLoaded(); }; - OT.Config.on('dynamicConfigChanged', configLoaded); OT.Config.on('dynamicConfigLoadFailed', configLoadFailed); @@ -20224,7 +20718,7 @@ OT.checkSystemRequirements = function() { return systemRequirementsMet; }; - if(systemRequirementsMet === this.NOT_HAS_REQUIREMENTS) { + if (systemRequirementsMet === this.NOT_HAS_REQUIREMENTS) { OT.analytics.logEvent({ action: 'checkSystemRequirements', variation: 'notHasRequirements', @@ -20236,7 +20730,6 @@ OT.checkSystemRequirements = function() { return systemRequirementsMet; }; - /** * Displays information about system requirments for OpenTok for WebRTC. This * information is displayed in an iframe element that fills the browser window. @@ -20249,11 +20742,11 @@ OT.checkSystemRequirements = function() { * @method OT.upgradeSystemRequirements * @memberof OT */ -OT.upgradeSystemRequirements = function(){ +OT.upgradeSystemRequirements = function() { // trigger after the OT environment has loaded - OT.onLoad( function() { + OT.onLoad(function() { - if(OTPlugin.isSupported()) { + if (OTPlugin.isSupported()) { OT.Dialogs.Plugin.promptToInstall().on({ download: function() { window.location = OTPlugin.pathToInstaller(); @@ -20268,7 +20761,7 @@ OT.upgradeSystemRequirements = function(){ var id = '_upgradeFlash'; - // Load the iframe over the whole page. + // Load the iframe over the whole page. document.body.appendChild((function() { var d = document.createElement('iframe'); d.id = id; @@ -20308,12 +20801,12 @@ OT.upgradeSystemRequirements = function(){ // Since this is from an IFRAME within another domain we are going to listen to hash // changes. The best cross browser solution is to poll for a change in the hashtag. if (_intervalId) clearInterval(_intervalId); - _intervalId = setInterval(function(){ + _intervalId = setInterval(function() { var hash = document.location.hash, re = /^#?\d+&/; if (hash !== _lastHash && re.test(hash)) { _lastHash = hash; - if (hash.replace(re, '') === 'close_window'){ + if (hash.replace(re, '') === 'close_window') { document.body.removeChild(document.getElementById(id)); document.location.hash = ''; } @@ -20321,6 +20814,7 @@ OT.upgradeSystemRequirements = function(){ }, 100); }); }; + // tb_require('../helpers/helpers.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, @@ -20346,7 +20840,6 @@ OT.ConnectionCapabilities = function(capabilitiesHash) { trailing: true, browser: true, smarttabs:true */ /* global OT */ - /** * A class defining properties of the capabilities property of a * Session object. See Session.capabilities. @@ -20355,8 +20848,7 @@ OT.ConnectionCapabilities = function(capabilitiesHash) { * and the Session object has dispatched the sessionConnected event. *

* For more information on token roles, see the - * generate_token() - * method of the OpenTok server-side libraries. + * Token Creation Overview. * * @class Capabilities * @@ -20425,8 +20917,8 @@ OT.Capabilities = function(permissions) { * @property {String} data A string containing metadata describing the * connection. When you generate a user token string pass the connection data string to the * generate_token() method of our - * server-side libraries. You can also generate a token - * and define connection data on the + * server-side libraries. You can + * also generate a token and define connection data on the * Dashboard page. */ OT.Connection = function(id, creationTime, data, capabilitiesHash, permissionsHash) { @@ -20471,10 +20963,9 @@ OT.Connection.fromHash = function(hash) { hash.creationTime, hash.data, OT.$.extend(hash.capablities || {}, { supportsWebRTC: true }), - hash.permissions || [] ); + hash.permissions || []); }; - // tb_require('../../../helpers/helpers.js') // tb_require('./message.js') // tb_require('../../connection.js') @@ -20515,7 +21006,7 @@ OT.Connection.fromHash = function(hash) { }; } - if ( !(toAddress instanceof OT.Connection || toAddress instanceof OT.Session) ) { + if (!(toAddress instanceof OT.Connection || toAddress instanceof OT.Session)) { return { code: 400, reason: 'The To field was invalid' @@ -20534,15 +21025,13 @@ OT.Connection.fromHash = function(hash) { reason: 'The signal type was null or undefined. Either set it to a String value or ' + 'omit it' }; - } - else if (type.length > MAX_SIGNAL_TYPE_LENGTH) { + } else if (type.length > MAX_SIGNAL_TYPE_LENGTH) { error = { code: 413, reason: 'The signal type was too long, the maximum length of it is ' + MAX_SIGNAL_TYPE_LENGTH + ' characters' }; - } - else if ( isInvalidType(type) ) { + } else if (isInvalidType(type)) { error = { code: 400, reason: 'The signal type was invalid, it can only contain letters, ' + @@ -20561,8 +21050,7 @@ OT.Connection.fromHash = function(hash) { reason: 'The signal data was null or undefined. Either set it to a String value or ' + 'omit it' }; - } - else { + } else { try { if (JSON.stringify(data).length > MAX_SIGNAL_DATA_LENGTH) { error = { @@ -20571,8 +21059,7 @@ OT.Connection.fromHash = function(hash) { MAX_SIGNAL_DATA_LENGTH + ' characters' }; } - } - catch(e) { + } catch (e) { error = {code: 400, reason: 'The data field was not valid JSON'}; } } @@ -20580,11 +21067,10 @@ OT.Connection.fromHash = function(hash) { return error; }; - this.toRaptorMessage = function() { var to = this.to; - if (to && typeof(to) !== 'string') { + if (to && typeof to !== 'string') { to = to.id; } @@ -20595,7 +21081,6 @@ OT.Connection.fromHash = function(hash) { return options; }; - this.error = null; if (options) { @@ -20653,15 +21138,13 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony _dispatcher, _completion; - //// Private API var setState = OT.$.statable(this, _states, 'disconnected'), onConnectComplete = function onConnectComplete(error) { if (error) { setState('error'); - } - else { + } else { setState('connected'); } @@ -20682,13 +21165,12 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony _dispatcher.onClose(reason); }, this), - onError = function onError () {}; - // @todo what does having an error mean? Are they always fatal? Are we disconnected now? - + onError = function onError() {}; + // @todo what does having an error mean? Are they always fatal? Are we disconnected now? //// Public API - this.connect = function (token, sessionInfo, completion) { + this.connect = function(token, sessionInfo, completion) { if (!this.is('disconnected', 'error')) { OT.warn('Cannot connect the Raptor Socket as it is currently connected. You should ' + 'disconnect first.'); @@ -20764,8 +21246,8 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony return; } - this.publish( OT.Raptor.Message.sessions.get(OT.APIKEY, _sessionId), - function (error) { + this.publish(OT.Raptor.Message.sessions.get(OT.APIKEY, _sessionId), + function(error) { if (error) { var errorCode, errorMessage, @@ -20800,8 +21282,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony }, this)); }; - - this.disconnect = function (drainSocketBuffer) { + this.disconnect = function(drainSocketBuffer) { if (this.is('disconnected')) return; setState('disconnecting'); @@ -20814,7 +21295,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony // dict, but if you provide the completion handler it must // be the last argument. // - this.publish = function (message, headers, completion) { + this.publish = function(message, headers, completion) { if (_rumor.isNot('connected')) { OT.error('OT.Raptor.Socket: cannot publish until the socket is connected.' + message); return; @@ -20830,14 +21311,12 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony if (OT.$.isFunction(headers)) { _headers = {}; _completion = headers; - } - else { + } else { _headers = headers; } } if (!_completion && completion && OT.$.isFunction(completion)) _completion = completion; - if (_completion) _dispatcher.registerCallback(transactionId, _completion); OT.debug('OT.Raptor.Socket Publish (ID:' + transactionId + ') '); @@ -20855,14 +21334,14 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony // Register a new stream against _sessionId this.streamCreate = function(name, streamId, audioFallbackEnabled, channels, minBitrate, maxBitrate, completion) { - var message = OT.Raptor.Message.streams.create( OT.APIKEY, - _sessionId, - streamId, - name, - audioFallbackEnabled, - channels, - minBitrate, - maxBitrate); + var message = OT.Raptor.Message.streams.create(OT.APIKEY, + _sessionId, + streamId, + name, + audioFallbackEnabled, + channels, + minBitrate, + maxBitrate); this.publish(message, function(error, message) { completion(error, streamId, message); @@ -20870,42 +21349,42 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony }; this.streamDestroy = function(streamId) { - this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, streamId) ); + this.publish(OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, streamId)); }; this.streamChannelUpdate = function(streamId, channelId, attributes) { - this.publish( OT.Raptor.Message.streamChannels.update(OT.APIKEY, _sessionId, - streamId, channelId, attributes) ); + this.publish(OT.Raptor.Message.streamChannels.update(OT.APIKEY, _sessionId, + streamId, channelId, attributes)); }; this.subscriberCreate = function(streamId, subscriberId, channelsToSubscribeTo, completion) { - this.publish( OT.Raptor.Message.subscribers.create(OT.APIKEY, _sessionId, - streamId, subscriberId, _rumor.id(), channelsToSubscribeTo), completion ); + this.publish(OT.Raptor.Message.subscribers.create(OT.APIKEY, _sessionId, + streamId, subscriberId, _rumor.id(), channelsToSubscribeTo), completion); }; this.subscriberDestroy = function(streamId, subscriberId) { - this.publish( OT.Raptor.Message.subscribers.destroy(OT.APIKEY, _sessionId, - streamId, subscriberId) ); + this.publish(OT.Raptor.Message.subscribers.destroy(OT.APIKEY, _sessionId, + streamId, subscriberId)); }; this.subscriberUpdate = function(streamId, subscriberId, attributes) { - this.publish( OT.Raptor.Message.subscribers.update(OT.APIKEY, _sessionId, - streamId, subscriberId, attributes) ); + this.publish(OT.Raptor.Message.subscribers.update(OT.APIKEY, _sessionId, + streamId, subscriberId, attributes)); }; this.subscriberChannelUpdate = function(streamId, subscriberId, channelId, attributes) { - this.publish( OT.Raptor.Message.subscriberChannels.update(OT.APIKEY, _sessionId, - streamId, subscriberId, channelId, attributes) ); + this.publish(OT.Raptor.Message.subscriberChannels.update(OT.APIKEY, _sessionId, + streamId, subscriberId, channelId, attributes)); }; this.forceDisconnect = function(connectionIdToDisconnect, completion) { - this.publish( OT.Raptor.Message.connections.destroy(OT.APIKEY, _sessionId, - connectionIdToDisconnect), completion ); + this.publish(OT.Raptor.Message.connections.destroy(OT.APIKEY, _sessionId, + connectionIdToDisconnect), completion); }; this.forceUnpublish = function(streamIdToUnpublish, completion) { - this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, - streamIdToUnpublish), completion ); + this.publish(OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, + streamIdToUnpublish), completion); }; this.jsepCandidate = function(streamId, candidate) { @@ -20922,16 +21401,16 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony }; this.jsepOffer = function(uri, offerSdp) { - this.publish( OT.Raptor.Message.offer(uri, offerSdp) ); + this.publish(OT.Raptor.Message.offer(uri, offerSdp)); }; this.jsepAnswer = function(streamId, answerSdp) { - this.publish( OT.Raptor.Message.streams.answer(OT.APIKEY, _sessionId, streamId, answerSdp) ); + this.publish(OT.Raptor.Message.streams.answer(OT.APIKEY, _sessionId, streamId, answerSdp)); }; this.jsepAnswerP2p = function(streamId, subscriberId, answerSdp) { - this.publish( OT.Raptor.Message.subscribers.answer(OT.APIKEY, _sessionId, streamId, - subscriberId, answerSdp) ); + this.publish(OT.Raptor.Message.subscribers.answer(OT.APIKEY, _sessionId, streamId, + subscriberId, answerSdp)); }; this.signal = function(options, completion, logEventFn) { @@ -20939,13 +21418,13 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony if (!signal.valid) { if (completion && OT.$.isFunction(completion)) { - completion( new SignalError(signal.error.code, signal.error.reason), signal.toHash() ); + completion(new SignalError(signal.error.code, signal.error.reason), signal.toHash()); } return; } - this.publish( signal.toRaptorMessage(), function(err) { + this.publish(signal.toRaptorMessage(), function(err) { var error, errorCode, errorMessage, @@ -20960,7 +21439,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony } error = new OT.SignalError(errorCode, errorMessage); } else { - var typeStr = signal.data? typeof(signal.data) : null; + var typeStr = signal.data ? typeof (signal.data) : null; logEventFn('signal', 'send', {type: typeStr}); } @@ -20972,7 +21451,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony return _rumor && _rumor.id(); }; - if(dispatcher == null) { + if (dispatcher == null) { dispatcher = new OT.Raptor.Dispatcher(); } _dispatcher = dispatcher; @@ -21032,7 +21511,6 @@ OT.GetStatsAudioLevelSampler = function(peerConnection) { }; }; - /* * An AudioContext based audio level sampler. It returns the maximum value in the * last 1024 samples. @@ -21163,9 +21641,9 @@ OT.Archive = function(id, name, status) { OT.$.eventing(this); // Mass update, called by Raptor.Dispatcher - this._.update = OT.$.bind(function (attributes) { + this._.update = OT.$.bind(function(attributes) { for (var key in attributes) { - if(!attributes.hasOwnProperty(key)) { + if (!attributes.hasOwnProperty(key)) { continue; } var oldValue = this[key]; @@ -21180,6 +21658,240 @@ OT.Archive = function(id, name, status) { }; +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/properties.js') +// tb_require('./events.js') + +!(function() { + var Anvil = {}; + + // @todo These aren't the same for all resource types. + var httpToClientCode = { + 400: OT.ExceptionCodes.INVALID_SESSION_ID, + 403: OT.ExceptionCodes.AUTHENTICATION_ERROR, + 404: OT.ExceptionCodes.INVALID_SESSION_ID, + 409: OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE + }; + + var anvilErrors = { + RESPONSE_BADLY_FORMED: { + code: null, + message: 'Unknown error: JSON response was badly formed' + }, + + UNEXPECTED_SERVER_RESPONSE: { + code: OT.ExceptionCodes.UNEXPECTED_SERVER_RESPONSE, + message: 'Unexpected server response. Try this operation again later.' + } + }; + + // Transform an error that we don't understand (+originalError+) to a general + // UNEXPECTED_SERVER_RESPONSE one. We also include the original error details + // on the `error.details` property. + // + var normaliseUnexpectedError = function normaliseUnexpectedError(originalError) { + var error = OT.$.clone(anvilErrors.UNEXPECTED_SERVER_RESPONSE); + + // We don't know this code...capture whatever the original + // error and code were for debugging purposes. + error.details = { + originalCode: originalError.code.toString(), + originalMessage: originalError.message + }; + + return error; + }; + + Anvil.getRequestParams = function getApiRequestOptions(resourcePath, token) { + var url = OT.properties.apiURL + '/' + resourcePath; + var options; + + if (OT.$.env.name === 'IE' && OT.$.env.version < 10) { + url = url + '&format=json&token=' + encodeURIComponent(token) + + '&version=1&cache=' + OT.$.uuid(); + options = { + xdomainrequest: true + }; + } else { + options = { + headers: { + 'X-TB-TOKEN-AUTH': token, + 'X-TB-VERSION': 1 + } + }; + } + + return { + url: url, + options: options + }; + }; + + Anvil.getErrorsFromHTTP = function(httpError) { + if (!httpError) { + return false; + } + + var error = { + code: httpError.target && httpError.target.status + }; + + return error; + }; + + Anvil.getErrorsFromResponse = function getErrorsFromResponse(responseJson) { + if (!OT.$.isArray(responseJson)) { + return anvilErrors.RESPONSE_BADLY_FORMED; + } + + var error = OT.$.find(responseJson, function(node) { + return node.error !== void 0 && node.error !== null; + }); + + if (!error) { + return false; + } + + // Yup :-( + error = error.error; + error.message = error.errorMessage && error.errorMessage.message; + + return error; + }; + + var normaliseError = function normaliseError(error, responseText) { + if (error.code && !httpToClientCode[error.code]) { + error = normaliseUnexpectedError(error); + } else { + error.code = httpToClientCode[error.code]; + } + + if (responseText.length === 0) { + // This is a weird edge case that usually means that there was connectivity + // loss after Anvil sent the response but before the client had fully received it + error.code = OT.ExceptionCodes.CONNECT_FAILED; + responseText = 'Response body was empty, probably due to connectivity loss'; + } else if (!error.code) { + error = normaliseUnexpectedError(error); + } + + if (!error.details) error.details = {}; + error.details.responseText = responseText; + + return error; + }; + + Anvil.get = function getFromAnvil(resourcePath, token) { + var params = Anvil.getRequestParams(resourcePath, token); + + return new OT.$.RSVP.Promise(function(resolve, reject) { + OT.$.getJSON(params.url, params.options, function(httpError, responseJson) { + var err = Anvil.getErrorsFromHTTP(httpError) || Anvil.getErrorsFromResponse(responseJson); + var responseText; + + if (err) { + responseText = responseJson && !OT.$.isEmpty(responseJson) ? + JSON.stringify(responseJson) : ''; + err = normaliseError(err, responseText); + + reject(new OT.$.Error(err.message, 'AnvilError', { + code: err.code, + details: err.details + })); + + return; + } + + resolve(responseJson[0]); + }); + }); + }; + + OT.Anvil = Anvil; + +})(window); + +// tb_require('./anvil.js') + +!(function() { + + // This sequence defines the delay before retry. Therefore a 0 indicates + // that a retry should happen immediately. + // + // e.g. 0, 600, 1200 means retry immediately, then in 600 ms, then in 1200ms + // + var retryDelays = [0, 600, 1200]; + + // These codes are possibly transient and it's worth retrying if a Anvil request + // fails with one of these codes. + var transientErrorCodes = [ + OT.ExceptionCodes.CONNECT_FAILED, + OT.ExceptionCodes.UNEXPECTED_SERVER_RESPONSE + ]; + + OT.SessionInfo = function(rawSessionInfo) { + OT.log('SessionInfo Response:'); + OT.log(rawSessionInfo); + + /*jshint camelcase:false*/ + //jscs:disable requireCamelCaseOrUpperCaseIdentifiers + this.sessionId = rawSessionInfo.session_id; + this.partnerId = rawSessionInfo.partner_id; + this.sessionStatus = rawSessionInfo.session_status; + this.messagingServer = rawSessionInfo.messaging_server_url; + this.messagingURL = rawSessionInfo.messaging_url; + this.symphonyAddress = rawSessionInfo.symphony_address; + + if (rawSessionInfo.properties) { + // `simulcast` is tri-state: + // true: simulcast is on for this session + // false: simulcast is off for this session + // undefined: the developer can choose + // + this.simulcast = rawSessionInfo.properties.simulcast; + + this.p2pEnabled = !!(rawSessionInfo.properties.p2p && + rawSessionInfo.properties.p2p.preference && + rawSessionInfo.properties.p2p.preference.value === 'enabled'); + } else { + this.p2pEnabled = false; + } + }; + + // Retrieves Session Info for +session+. The SessionInfo object will be passed + // to the +onSuccess+ callback. The +onFailure+ callback will be passed an error + // object and the DOMEvent that relates to the error. + // + OT.SessionInfo.get = function(id, token) { + var remainingRetryDelays = retryDelays.slice(); + + var attempt = function(err, resolve, reject) { + if (remainingRetryDelays.length === 0) { + reject(err); + return; + } + + OT.Anvil.get('session/' + id + '?extended=true', token).then(function(anvilResponse) { + resolve(new OT.SessionInfo(anvilResponse)); + }, function(err) { + if (OT.$.arrayIndexOf(transientErrorCodes, err.code) > -1) { + // This error is possibly transient, so retry + setTimeout(function() { + attempt(err, resolve, reject); + }, remainingRetryDelays.shift()); + } else { + reject(err); + } + }); + }; + + return new OT.$.RSVP.Promise(function(resolve, reject) { + attempt(void 0, resolve, reject); + }); + }; + +})(window); + // tb_require('../helpers/helpers.js') // tb_require('../helpers/lib/properties.js') // tb_require('../helpers/lib/analytics.js') @@ -21189,6 +21901,7 @@ OT.Archive = function(id, name, status) { /* global OT */ OT.analytics = new OT.Analytics(OT.properties.loggingURL); + // tb_require('../helpers/helpers.js') // tb_require('../helpers/lib/widget_view.js') // tb_require('./analytics.js') @@ -21211,7 +21924,6 @@ OT.analytics = new OT.Analytics(OT.properties.loggingURL); // tb_require('./peer_connection/get_stats_adapter.js') // tb_require('./peer_connection/get_stats_helpers.js') - /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ /* global OT */ @@ -21253,10 +21965,12 @@ OT.Subscriber = function(targetElement, options, completionHandler) { _audioLevelRunner, _frameRateRestricted = false, _connectivityAttemptPinger, - _subscriber = this; + _subscriber = this, + _isLocalStream; _properties = OT.$.defaults({}, options, { showControls: true, + testNetwork: false, fitMode: _stream.defaultFitMode || 'cover' }); @@ -21285,7 +21999,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { this.once('subscribeComplete', completionHandler); } - if(_audioLevelCapable) { + if (_audioLevelCapable) { this.on({ 'audioLevelUpdated:added': function(count) { if (count === 1 && _audioLevelRunner) { @@ -21310,7 +22024,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { connectionId: _session && _session.isConnected() ? _session.connection.connectionId : null, partnerId: _session && _session.isConnected() ? _session.sessionInfo.partnerId : null, - subscriberId: _widgetId, + subscriberId: _widgetId }]; if (throttle) args.push(throttle); OT.analytics.logEvent.apply(OT.analytics, args); @@ -21333,7 +22047,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { recordQOS = OT.$.bind(function(parsedStats) { var QoSBlob = { - streamType : 'WebRTC', + streamType: 'WebRTC', width: _container ? Number(OT.$.width(_container.domElement).replace('px', '')) : null, height: _container ? Number(OT.$.height(_container.domElement).replace('px', '')) : null, sessionId: _session ? _session.sessionId : null, @@ -21348,11 +22062,10 @@ OT.Subscriber = function(targetElement, options, completionHandler) { remoteConnectionId: _stream.connection.connectionId }; - OT.analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) ); + OT.analytics.logQOS(OT.$.extend(QoSBlob, parsedStats)); this.trigger('qos', parsedStats); }, this), - stateChangeFailed = function(changeFailed) { OT.error('Subscriber State Change Failed: ', changeFailed.message); OT.debug(changeFailed); @@ -21375,11 +22088,11 @@ OT.Subscriber = function(targetElement, options, completionHandler) { _container.loading(false); _chrome.showAfterLoading(); - if(_frameRateRestricted) { + if (_frameRateRestricted) { _stream.setRestrictFrameRate(true); } - if(_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') { + if (_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') { _audioLevelMeter[_container.audioOnly() ? 'show' : 'hide'](); } @@ -21462,6 +22175,8 @@ OT.Subscriber = function(targetElement, options, completionHandler) { _lastSubscribeToVideoReason = 'loading'; this.subscribeToVideo(_properties.subscribeToVideo, 'loading'); + this.setPreferredResolution(_properties.preferredResolution); + this.setPreferredFrameRate(_properties.preferredFrameRate); var videoContainerOptions = { error: onPeerConnectionFailure, @@ -21477,7 +22192,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { // We still need to investigate further. // var tracks = webRTCStream.getVideoTracks(); - if(tracks.length > 0) { + if (tracks.length > 0) { OT.$.forEach(tracks, function(track) { track.setEnabled(_stream.hasVideo && _properties.subscribeToVideo); }); @@ -21518,17 +22233,16 @@ OT.Subscriber = function(targetElement, options, completionHandler) { _streamContainer = null; } - this.trigger('streamRemoved', this); }, - streamDestroyed = function () { + streamDestroyed = function() { this.disconnect(); }, streamUpdated = function(event) { - switch(event.changedProperty) { + switch (event.changedProperty) { case 'videoDimensions': if (!_streamContainer) { // Ignore videoEmension updates before streamContainer is created OPENTOK-17253 @@ -21564,7 +22278,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { break; case 'hasAudio': - // noop + // noop } }, @@ -21588,7 +22302,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { updateChromeForStyleChange = function(key, value/*, oldValue*/) { if (!_chrome) return; - switch(key) { + switch (key) { case 'nameDisplayMode': _chrome.name.setDisplayMode(value); _chrome.backingBar.setNameMode(value); @@ -21702,7 +22416,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { backgroundImageURI: null, showArchiveStatus: true, showMicButton: true - }, _properties.showControls, function (payload) { + }, _properties.showControls, function(payload) { logAnalyticsEvent('SetStyle', 'Subscriber', payload, 0.1); }); @@ -21721,12 +22435,12 @@ OT.Subscriber = function(targetElement, options, completionHandler) { } }; - // logs an analytics event for getStats every 100 calls + // logs an analytics event for getStats on the first call var notifyGetStatsCalled = (function() { var callCount = 0; return function throttlingNotifyGetStatsCalled() { - if (callCount % 100 === 0) { - logAnalyticsEvent('getStats', 'Called'); + if (callCount === 0) { + logAnalyticsEvent('GetStats', 'Called'); } callCount++; }; @@ -21735,17 +22449,20 @@ OT.Subscriber = function(targetElement, options, completionHandler) { this.destroy = function(reason, quiet) { if (_state.isDestroyed()) return; - if(reason === 'streamDestroyed') { - if (_state.isAttemptingToSubscribe()) { + if (_state.isAttemptingToSubscribe()) { + if (reason === 'streamDestroyed') { // We weren't subscribing yet so the stream was destroyed before we setup // the PeerConnection or receiving the initial stream. this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID')); + } else { + logConnectivityEvent('Canceled', {streamId: _stream.id}); + _connectivityAttemptPinger.stop(); } } _state.set('Destroyed'); - if(_audioLevelRunner) { + if (_audioLevelRunner) { _audioLevelRunner.stop(); } @@ -21770,7 +22487,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { this.stream = _stream = null; this.streamId = null; - this.session =_session = null; + this.session = _session = null; _properties = null; if (quiet !== true) { @@ -21834,7 +22551,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { } } this.subscribeToVideo(active, 'auto'); - if(!active) { + if (!active) { _chrome.videoDisabledIndicator.disableVideo(true); } var payload = active ? {videoEnabled: true} : {videoDisabled: true}; @@ -21939,7 +22656,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { if (_audioVolume !== value) { OT.warn('OT.Subscriber.setAudioVolume: value should be an integer between 0 and 100'); } - if(_properties.muted && _audioVolume > 0) { + if (_properties.muted && _audioVolume > 0) { _properties.premuteVolume = value; muteAudio.call(this, false); } @@ -21961,7 +22678,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { * @memberOf Subscriber */ this.getAudioVolume = function() { - if(_properties.muted) { + if (_properties.muted) { return 0; } if (_streamContainer) return _streamContainer.getAudioVolume(); @@ -22013,18 +22730,18 @@ OT.Subscriber = function(targetElement, options, completionHandler) { var muteAudio = function(_mute) { _chrome.muteButton.muted(_mute); - if(_mute === _properties.mute) { + if (_mute === _properties.mute) { return; } - if(OT.$.env.name === 'Chrome' || OTPlugin.isInstalled()) { + if (OT.$.env.name === 'Chrome' || OTPlugin.isInstalled()) { _properties.subscribeMute = _properties.muted = _mute; this.subscribeToAudio(_properties.subscribeToAudio); } else { - if(_mute) { + if (_mute) { _properties.premuteVolume = this.getAudioVolume(); _properties.muted = true; this.setAudioVolume(0); - } else if(_properties.premuteVolume || _properties.audioVolume) { + } else if (_properties.premuteVolume || _properties.audioVolume) { _properties.muted = false; this.setAudioVolume(_properties.premuteVolume || _properties.audioVolume); } @@ -22038,7 +22755,6 @@ OT.Subscriber = function(targetElement, options, completionHandler) { subscribeToVideo: 'subscribeToVideo' }; - /** * Toggles video on and off. Starts subscribing to video (if it is available and * currently not being subscribed to) when the value is true; @@ -22070,7 +22786,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { setAudioOnly(!(value && _stream.hasVideo)); - if ( value && _container && _container.video()) { + if (value && _container && _container.video() && _stream.hasVideo) { _container.loading(value); _container.video().whenTimeIncrements(function() { _container.loading(false); @@ -22103,6 +22819,55 @@ OT.Subscriber = function(targetElement, options, completionHandler) { return this; }; + this.setPreferredResolution = function(preferredResolution) { + if (_state.isDestroyed() || !_peerConnection) { + OT.warn('Cannot set the max Resolution when not subscribing to a publisher'); + return; + } + + _properties.preferredResolution = preferredResolution; + + if (_session.sessionInfo.p2pEnabled) { + OT.warn('Subscriber.setPreferredResolution will not work in a P2P Session'); + return; + } + + var curMaxResolution = _stream.getPreferredResolution(); + + var isUnchanged = (curMaxResolution && preferredResolution && + curMaxResolution.width === preferredResolution.width && + curMaxResolution.height === preferredResolution.height) || + (!curMaxResolution && !preferredResolution); + + if (isUnchanged) { + return; + } + + _stream.setPreferredResolution(preferredResolution); + }; + + this.setPreferredFrameRate = function(preferredFrameRate) { + if (_state.isDestroyed() || !_peerConnection) { + OT.warn('Cannot set the max frameRate when not subscribing to a publisher'); + return; + } + + _properties.preferredFrameRate = preferredFrameRate; + + if (_session.sessionInfo.p2pEnabled) { + OT.warn('Subscriber.setPreferredFrameRate will not work in a P2P Session'); + return; + } + + /* jshint -W116 */ + if (_stream.getPreferredFrameRate() == preferredFrameRate) { + return; + } + /* jshint +W116 */ + + _stream.setPreferredFrameRate(preferredFrameRate); + }; + this.isSubscribing = function() { return _state.isSubscribing(); }; @@ -22195,12 +22960,12 @@ OT.Subscriber = function(targetElement, options, completionHandler) { this._ = { archivingStatus: function(status) { - if(_chrome) { + if (_chrome) { _chrome.archive.setArchiving(status); } }, - getDataChannel: function (label, options, completion) { + getDataChannel: function(label, options, completion) { // @fixme this will fail if it's called before we have a SubscriberPeerConnection. // I.e. before we have a publisher connection. if (!_peerConnection) { @@ -22254,8 +23019,23 @@ OT.Subscriber = function(targetElement, options, completionHandler) { _startConnectingTime = OT.$.now(); - if (_stream.connection.id !== _session.connection.id) { - logAnalyticsEvent('createPeerConnection', 'Attempt', ''); + logAnalyticsEvent('createPeerConnection', 'Attempt', ''); + + _isLocalStream = _stream.connection.id === _session.connection.id; + + if (!_properties.testNetwork && _isLocalStream) { + // Subscribe to yourself edge-case + var publisher = _session.getPublisherForStream(_stream); + if (!(publisher && publisher._.webRtcStream())) { + this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID')); + return this; + } + + onRemoteStreamAdded.call(this, publisher._.webRtcStream()); + } else { + if (_properties.testNetwork) { + this.setAudioVolume(0); + } _state.set('ConnectingToPeer'); @@ -22271,7 +23051,11 @@ OT.Subscriber = function(targetElement, options, completionHandler) { }, this); // initialize the peer connection AFTER we've added the event listeners - _peerConnection.init(); + _peerConnection.init(function(err) { + if (err) { + throw err; + } + }); if (OT.$.hasCapabilities('audioOutputLevelStat')) { _audioLevelSampler = new OT.GetStatsAudioLevelSampler(_peerConnection, 'out'); @@ -22279,7 +23063,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext()); } - if(_audioLevelSampler) { + if (_audioLevelSampler) { var subscriber = this; // sample with interval to minimise disturbance on animation loop but dispatch the // event with RAF since the main purpose is animation of a meter @@ -22294,210 +23078,199 @@ OT.Subscriber = function(targetElement, options, completionHandler) { }); }, 60); } - } else { - logAnalyticsEvent('createPeerConnection', 'Attempt', ''); - var publisher = _session.getPublisherForStream(_stream); - if(!(publisher && publisher._.webRtcStream())) { - this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID')); - return this; - } - - // Subscribe to yourself edge-case - onRemoteStreamAdded.call(this, publisher._.webRtcStream()); } logConnectivityEvent('Attempt', {streamId: _stream.id}); + /** + * Dispatched periodically to indicate the subscriber's audio level. The event is dispatched + * up to 60 times per second, depending on the browser. The audioLevel property + * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more + * information. + *

+ * The following example adjusts the value of a meter element that shows volume of the + * subscriber. Note that the audio level is adjusted logarithmically and a moving average + * is applied: + *

+  * var movingAvg = null;
+  * subscriber.on('audioLevelUpdated', function(event) {
+  *   if (movingAvg === null || movingAvg <= event.audioLevel) {
+  *     movingAvg = event.audioLevel;
+  *   } else {
+  *     movingAvg = 0.7 * movingAvg + 0.3 * event.audioLevel;
+  *   }
+  *
+  *   // 1.5 scaling to map the -30 - 0 dBm range to [0,1]
+  *   var logLevel = (Math.log(movingAvg) / Math.LN10) / 1.5 + 1;
+  *   logLevel = Math.min(Math.max(logLevel, 0), 1);
+  *   document.getElementById('subscriberMeter').value = logLevel;
+  * });
+  * 
+ *

This example shows the algorithm used by the default audio level indicator displayed + * in an audio-only Subscriber. + * + * @name audioLevelUpdated + * @event + * @memberof Subscriber + * @see AudioLevelUpdatedEvent + */ - /** - * Dispatched periodically to indicate the subscriber's audio level. The event is dispatched - * up to 60 times per second, depending on the browser. The audioLevel property - * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more - * information. - *

- * The following example adjusts the value of a meter element that shows volume of the - * subscriber. Note that the audio level is adjusted logarithmically and a moving average - * is applied: - *

- * var movingAvg = null;
- * subscriber.on('audioLevelUpdated', function(event) {
- *   if (movingAvg === null || movingAvg <= event.audioLevel) {
- *     movingAvg = event.audioLevel;
- *   } else {
- *     movingAvg = 0.7 * movingAvg + 0.3 * event.audioLevel;
- *   }
- *
- *   // 1.5 scaling to map the -30 - 0 dBm range to [0,1]
- *   var logLevel = (Math.log(movingAvg) / Math.LN10) / 1.5 + 1;
- *   logLevel = Math.min(Math.max(logLevel, 0), 1);
- *   document.getElementById('subscriberMeter').value = logLevel;
- * });
- * 
- *

This example shows the algorithm used by the default audio level indicator displayed - * in an audio-only Subscriber. - * - * @name audioLevelUpdated - * @event - * @memberof Subscriber - * @see AudioLevelUpdatedEvent - */ - -/** -* Dispatched when the video for the subscriber is disabled. -*

-* The reason property defines the reason the video was disabled. This can be set to -* one of the following values: -*

-* -*

    -* -*
  • "publishVideo" — The publisher stopped publishing video by calling -* publishVideo(false).
  • -* -*
  • "quality" — The OpenTok Media Router stopped sending video -* to the subscriber based on stream quality changes. This feature of the OpenTok Media -* Router has a subscriber drop the video stream when connectivity degrades. (The subscriber -* continues to receive the audio stream, if there is one.) -*

    -* Before sending this event, when the Subscriber's stream quality deteriorates to a level -* that is low enough that the video stream is at risk of being disabled, the Subscriber -* dispatches a videoDisableWarning event. -*

    -* If connectivity improves to support video again, the Subscriber object dispatches -* a videoEnabled event, and the Subscriber resumes receiving video. -*

    -* By default, the Subscriber displays a video disabled indicator when a -* videoDisabled event with this reason is dispatched and removes the indicator -* when the videoDisabled event with this reason is dispatched. You can control -* the display of this icon by calling the setStyle() method of the Subscriber, -* setting the videoDisabledDisplayMode property(or you can set the style when -* calling the Session.subscribe() method, setting the style property -* of the properties parameter). -*

    -* This feature is only available in sessions that use the OpenTok Media Router (sessions with -* the media mode -* set to routed), not in sessions with the media mode set to relayed. -*

    -* You can disable this audio-only fallback feature, by setting the -* audioFallbackEnabled property to false in the options you pass -* into the OT.initPublisher() method on the publishing client. (See -* OT.initPublisher().) -*

  • -* -*
  • "subscribeToVideo" — The subscriber started or stopped subscribing to -* video, by calling subscribeToVideo(false). -*
  • -* -*
-* -* @see VideoEnabledChangedEvent -* @see event:videoDisableWarning -* @see event:videoEnabled -* @name videoDisabled -* @event -* @memberof Subscriber + /** + * Dispatched when the video for the subscriber is disabled. + *

+ * The reason property defines the reason the video was disabled. This can be set to + * one of the following values: + *

+ * + *

    + * + *
  • "publishVideo" — The publisher stopped publishing video by calling + * publishVideo(false).
  • + * + *
  • "quality" — The OpenTok Media Router stopped sending video + * to the subscriber based on stream quality changes. This feature of the OpenTok Media + * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber + * continues to receive the audio stream, if there is one.) + *

    + * Before sending this event, when the Subscriber's stream quality deteriorates to a level + * that is low enough that the video stream is at risk of being disabled, the Subscriber + * dispatches a videoDisableWarning event. + *

    + * If connectivity improves to support video again, the Subscriber object dispatches + * a videoEnabled event, and the Subscriber resumes receiving video. + *

    + * By default, the Subscriber displays a video disabled indicator when a + * videoDisabled event with this reason is dispatched and removes the indicator + * when the videoDisabled event with this reason is dispatched. You can control + * the display of this icon by calling the setStyle() method of the Subscriber, + * setting the videoDisabledDisplayMode property(or you can set the style when + * calling the Session.subscribe() method, setting the style property + * of the properties parameter). + *

    + * This feature is only available in sessions that use the OpenTok Media Router (sessions with + * the media mode + * set to routed), not in sessions with the media mode set to relayed. + *

    + * You can disable this audio-only fallback feature, by setting the + * audioFallbackEnabled property to false in the options you pass + * into the OT.initPublisher() method on the publishing client. (See + * OT.initPublisher().) + *

  • + * + *
  • "subscribeToVideo" — The subscriber started or stopped subscribing to + * video, by calling subscribeToVideo(false). + *
  • + * + *
+ * + * @see VideoEnabledChangedEvent + * @see event:videoDisableWarning + * @see event:videoEnabled + * @name videoDisabled + * @event + * @memberof Subscriber */ -/** -* Dispatched when the OpenTok Media Router determines that the stream quality has degraded -* and the video will be disabled if the quality degrades more. If the quality degrades further, -* the Subscriber disables the video and dispatches a videoDisabled event. -*

-* By default, the Subscriber displays a video disabled warning indicator when this event -* is dispatched (and the video is disabled). You can control the display of this icon by -* calling the setStyle() method and setting the -* videoDisabledDisplayMode property (or you can set the style when calling -* the Session.subscribe() method and setting the style property -* of the properties parameter). -*

-* This feature is only available in sessions that use the OpenTok Media Router (sessions with -* the media mode -* set to routed), not in sessions with the media mode set to relayed. -* -* @see Event -* @see event:videoDisabled -* @see event:videoDisableWarningLifted -* @name videoDisableWarning -* @event -* @memberof Subscriber + /** + * Dispatched when the OpenTok Media Router determines that the stream quality has degraded + * and the video will be disabled if the quality degrades more. If the quality degrades further, + * the Subscriber disables the video and dispatches a videoDisabled event. + *

+ * By default, the Subscriber displays a video disabled warning indicator when this event + * is dispatched (and the video is disabled). You can control the display of this icon by + * calling the setStyle() method and setting the + * videoDisabledDisplayMode property (or you can set the style when calling + * the Session.subscribe() method and setting the style property + * of the properties parameter). + *

+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with + * the media mode + * set to routed), not in sessions with the media mode set to relayed. + * + * @see Event + * @see event:videoDisabled + * @see event:videoDisableWarningLifted + * @name videoDisableWarning + * @event + * @memberof Subscriber */ -/** -* Dispatched when the OpenTok Media Router determines that the stream quality has improved -* to the point at which the video being disabled is not an immediate risk. This event is -* dispatched after the Subscriber object dispatches a videoDisableWarning event. -*

-* This feature is only available in sessions that use the OpenTok Media Router (sessions with -* the media mode -* set to routed), not in sessions with the media mode set to relayed. -* -* @see Event -* @see event:videoDisabled -* @see event:videoDisableWarning -* @name videoDisableWarningLifted -* @event -* @memberof Subscriber + /** + * Dispatched when the OpenTok Media Router determines that the stream quality has improved + * to the point at which the video being disabled is not an immediate risk. This event is + * dispatched after the Subscriber object dispatches a videoDisableWarning event. + *

+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with + * the media mode + * set to routed), not in sessions with the media mode set to relayed. + * + * @see Event + * @see event:videoDisabled + * @see event:videoDisableWarning + * @name videoDisableWarningLifted + * @event + * @memberof Subscriber */ -/** -* Dispatched when the OpenTok Media Router resumes sending video to the subscriber -* after video was previously disabled. -*

-* The reason property defines the reason the video was enabled. This can be set to -* one of the following values: -*

-* -*

    -* -*
  • "publishVideo" — The publisher started publishing video by calling -* publishVideo(true).
  • -* -*
  • "quality" — The OpenTok Media Router resumed sending video -* to the subscriber based on stream quality changes. This feature of the OpenTok Media -* Router has a subscriber drop the video stream when connectivity degrades and then resume -* the video stream if the stream quality improves. -*

    -* This feature is only available in sessions that use the OpenTok Media Router (sessions with -* the media mode -* set to routed), not in sessions with the media mode set to relayed. -*

  • -* -*
  • "subscribeToVideo" — The subscriber started or stopped subscribing to -* video, by calling subscribeToVideo(false). -*
  • -* -*
-* -*

-* To prevent video from resuming, in the videoEnabled event listener, -* call subscribeToVideo(false) on the Subscriber object. -* -* @see VideoEnabledChangedEvent -* @see event:videoDisabled -* @name videoEnabled -* @event -* @memberof Subscriber + /** + * Dispatched when the OpenTok Media Router resumes sending video to the subscriber + * after video was previously disabled. + *

+ * The reason property defines the reason the video was enabled. This can be set to + * one of the following values: + *

+ * + *

    + * + *
  • "publishVideo" — The publisher started publishing video by calling + * publishVideo(true).
  • + * + *
  • "quality" — The OpenTok Media Router resumed sending video + * to the subscriber based on stream quality changes. This feature of the OpenTok Media + * Router has a subscriber drop the video stream when connectivity degrades and then resume + * the video stream if the stream quality improves. + *

    + * This feature is only available in sessions that use the OpenTok Media Router (sessions with + * the media mode + * set to routed), not in sessions with the media mode set to relayed. + *

  • + * + *
  • "subscribeToVideo" — The subscriber started or stopped subscribing to + * video, by calling subscribeToVideo(false). + *
  • + * + *
+ * + *

+ * To prevent video from resuming, in the videoEnabled event listener, + * call subscribeToVideo(false) on the Subscriber object. + * + * @see VideoEnabledChangedEvent + * @see event:videoDisabled + * @name videoEnabled + * @event + * @memberof Subscriber */ -/** -* Dispatched when the Subscriber element is removed from the HTML DOM. When this event is -* dispatched, you may choose to adjust or remove HTML DOM elements related to the subscriber. -* @see Event -* @name destroyed -* @event -* @memberof Subscriber + /** + * Dispatched when the Subscriber element is removed from the HTML DOM. When this event is + * dispatched, you may choose to adjust or remove HTML DOM elements related to the subscriber. + * @see Event + * @name destroyed + * @event + * @memberof Subscriber */ -/** -* Dispatched when the video dimensions of the video change. This can occur when the -* stream.videoType property is set to "screen" (for a screen-sharing -* video stream), and the user resizes the window being captured. It can also occur if the video -* is being published by a mobile device and the user rotates the device (causing the camera -* orientation to change). -* @name videoDimensionsChanged -* @event -* @memberof Subscriber + /** + * Dispatched when the video dimensions of the video change. This can occur when the + * stream.videoType property is set to "screen" (for a screen-sharing + * video stream), and the user resizes the window being captured. It can also occur if the video + * is being published by a mobile device and the user rotates the device (causing the camera + * orientation to change). + * @name videoDimensionsChanged + * @event + * @memberof Subscriber */ }; @@ -22521,7 +23294,6 @@ OT.Subscriber = function(targetElement, options, completionHandler) { trailing: true, browser: true, smarttabs:true */ /* global OT */ - /** * The Session object returned by the OT.initSession() method provides access to * much of the OpenTok functionality. @@ -22536,7 +23308,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { * @property {Connection} connection The {@link Connection} object for this session. The * connection property is only available once the Session object dispatches the sessionConnected * event. The Session object asynchronously dispatches a sessionConnected event in response - * to a successful call to the connect() method. See: connect and + * to a successful call to the connect() method. See: connect and * {@link Connection}. * @property {String} sessionId The session ID for this session. You pass this value into the * OT.initSession() method when you create the Session object. (Note: a Session @@ -22544,7 +23316,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) { * object and the object dispatches a connected event. See {@link OT.initSession} and * {@link connect}). * For more information on sessions and session IDs, see - * Session creation. + * Session creation. */ OT.Session = function(apiKey, sessionId) { OT.$.eventing(this); @@ -22556,7 +23328,7 @@ OT.Session = function(apiKey, sessionId) { return; } - if(sessionId == null) { + if (sessionId == null) { sessionId = apiKey; apiKey = null; } @@ -22593,8 +23365,6 @@ OT.Session = function(apiKey, sessionId) { _connectivityAttemptPinger, dispatchError; - - var setState = OT.$.statable(this, [ 'disconnected', 'connecting', 'connected', 'disconnecting' ], 'disconnected'); @@ -22604,13 +23374,12 @@ OT.Session = function(apiKey, sessionId) { this.streams = new OT.$.Collection(); this.archives = new OT.$.Collection(); + //-------------------------------------- + // MESSAGE HANDLERS + //-------------------------------------- -//-------------------------------------- -// MESSAGE HANDLERS -//-------------------------------------- - -// The duplication of this and sessionConnectionFailed will go away when -// session and messenger are refactored + // The duplication of this and sessionConnectionFailed will go away when + // session and messenger are refactored sessionConnectFailed = function(reason, code) { setState('disconnected'); @@ -22626,7 +23395,7 @@ OT.Session = function(apiKey, sessionId) { sessionDisconnectedHandler = function(event) { var reason = event.reason; - if(reason === 'networkTimedout') { + if (reason === 'networkTimedout') { reason = 'networkDisconnected'; this.logEvent('Connect', 'TimeOutDisconnect', {reason: event.reason}); } else { @@ -22677,7 +23446,7 @@ OT.Session = function(apiKey, sessionId) { }; streamCreatedHandler = function(stream) { - if(stream.connection.id !== this.connection.id) { + if (stream.connection.id !== this.connection.id) { this.dispatchEvent(new OT.StreamEvent( OT.Event.names.STREAM_CREATED, stream, @@ -22696,8 +23465,7 @@ OT.Session = function(apiKey, sessionId) { return; // These are not public properties, skip top level event for them. } - if (propertyName === 'orientation') { - propertyName = 'videoDimensions'; + if (propertyName === 'videoDimensions') { newValue = {width: newValue.width, height: newValue.height}; } @@ -22714,7 +23482,7 @@ OT.Session = function(apiKey, sessionId) { // if the stream is one of ours we delegate handling // to the publisher itself. - if(stream.connection.id === this.connection.id) { + if (stream.connection.id === this.connection.id) { OT.$.forEach(OT.publishers.where({ streamId: stream.id }), OT.$.bind(function(publisher) { publisher._.unpublishFromSession(this, reason); }, this)); @@ -22728,14 +23496,13 @@ OT.Session = function(apiKey, sessionId) { // If we are subscribed to any of the streams we should unsubscribe OT.$.forEach(OT.subscribers.where({streamId: stream.id}), function(subscriber) { if (subscriber.session.id === this.id) { - if(subscriber.stream) { + if (subscriber.stream) { subscriber.destroy('streamDestroyed'); } } }, this); - } else { - // @TODO Add a one time warning that this no longer cleans up the publisher } + // @TODO Add a else with a one time warning that this no longer cleans up the publisher }, this); this.dispatchEvent(event, defaultAction); @@ -22754,7 +23521,7 @@ OT.Session = function(apiKey, sessionId) { propertyName = event.changedProperty, newValue = event.newValue; - if(propertyName === 'status' && newValue === 'stopped') { + if (propertyName === 'status' && newValue === 'stopped') { this.dispatchEvent(new OT.ArchiveEvent('archiveStopped', archive)); } else { this.dispatchEvent(new OT.ArchiveEvent('archiveUpdated', archive)); @@ -22812,7 +23579,6 @@ OT.Session = function(apiKey, sessionId) { _socket = new OT.Raptor.Socket(_connectionId, _widgetId, socketUrl, symphonyUrl, OT.SessionDispatcher(this)); - _socket.connect(_token, this.sessionInfo, OT.$.bind(function(error, sessionState) { if (error) { _socket = void 0; @@ -22825,7 +23591,7 @@ OT.Session = function(apiKey, sessionId) { OT.debug('OT.Session: Received session state from Raptor', sessionState); this.connection = this.connections.get(_socket.id()); - if(this.connection) { + if (this.connection) { this.capabilities = this.connection.permissions; } @@ -22868,19 +23634,32 @@ OT.Session = function(apiKey, sessionId) { getSessionInfo = function() { if (this.is('connecting')) { - OT.SessionInfo.get( - this, - OT.$.bind(onSessionInfoResponse, this), - OT.$.bind(function(error) { - sessionConnectFailed.call(this, error.message + - (error.code ? ' (' + error.code + ')' : ''), error.code); - }, this) + var session = this; + this.logEvent('SessionInfo', 'Attempt'); + + OT.SessionInfo.get(sessionId, _token).then( + onSessionInfoResponse, + function(error) { + session.logConnectivityEvent('Failure', { + reason:'GetSessionInfo', + code: (error.code || 'No code'), + message: error.message + }); + + sessionConnectFailed.call(session, + error.message + (error.code ? ' (' + error.code + ')' : ''), + error.code); + } ); } }; - onSessionInfoResponse = function(sessionInfo) { + onSessionInfoResponse = OT.$.bind(function(sessionInfo) { if (this.is('connecting')) { + this.logEvent('SessionInfo', 'Success', { + messagingServer: sessionInfo.messagingServer + }); + var overrides = OT.properties.sessionInfoOverrides; this.sessionInfo = sessionInfo; if (overrides != null && typeof overrides === 'object') { @@ -22902,15 +23681,24 @@ OT.Session = function(apiKey, sessionId) { connectMessenger.call(this); } } - }; + }, this); // Check whether we have permissions to perform the action. permittedTo = OT.$.bind(function(action) { return this.capabilities.permittedTo(action); }, this); + // This is a placeholder until error handling can be rewritten dispatchError = OT.$.bind(function(code, message, completionHandler) { - OT.dispatchError(code, message, completionHandler, this); + OT.error(code, message); + + if (completionHandler && OT.$.isFunction(completionHandler)) { + completionHandler.call(null, new OT.Error(code, message)); + } + + OT.handleJsException(message, code, { + session: this + }); }, this); this.logEvent = function(action, variation, payload, options) { @@ -22984,7 +23772,7 @@ OT.Session = function(apiKey, sessionId) { _session.logEvent('TestNetwork', 'Attempt', {}); - if(this.isConnected()) { + if (this.isConnected()) { callback(new OT.$.Error('Session connected, cannot test network', 1015)); return; } @@ -23030,7 +23818,7 @@ OT.Session = function(apiKey, sessionId) { .then(function(stats) { OT.debug('Received stats from webrtcTest: ', stats); if (stats.bandwidth < testConfig.media.thresholdBitsPerSecond) { - return OT.$.RSVP.Promise.reject(new OT.$.Error('The detect bandwidth form the WebRTC ' + + return OT.$.RSVP.Promise.reject(new OT.$.Error('The detect bandwidth from the WebRTC ' + 'stage of the test was not sufficient to run the HTTP stage of the test', 1553)); } @@ -23038,7 +23826,7 @@ OT.Session = function(apiKey, sessionId) { }) .then(function() { // run the HTTP test only if the PC test was not extended - if(!webrtcStats.extended) { + if (!webrtcStats.extended) { return OT.httpTest({httpConfig: testConfig.http}); } }) @@ -23074,103 +23862,103 @@ OT.Session = function(apiKey, sessionId) { this.logEvent('Connect', variation, payload, options); }; -/** -* Connects to an OpenTok session. -*

-* Upon a successful connection, the completion handler (the second parameter of the method) is -* invoked without an error object passed in. (If there is an error connecting, the completion -* handler is invoked with an error object.) Make sure that you have successfully connected to the -* session before calling other methods of the Session object. -*

-*

-* The Session object dispatches a connectionCreated event when any client -* (including your own) connects to to the session. -*

-* -*
-* Example -*
-*

-* The following code initializes a session and sets up an event listener for when the session -* connects: -*

-*
-*  var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-*  var sessionID = ""; // Replace with your own session ID.
-*                      // See https://dashboard.tokbox.com/projects
-*  var token = ""; // Replace with a generated token that has been assigned the moderator role.
-*                  // See https://dashboard.tokbox.com/projects
-*
-*  var session = OT.initSession(apiKey, sessionID);
-*  session.on("sessionConnected", function(sessionConnectEvent) {
-*      //
-*  });
-*  session.connect(token);
-*  
-*

-*

-* In this example, the sessionConnectHandler() function is passed an event -* object of type {@link SessionConnectEvent}. -*

-* -*
-* Events dispatched: -*
-* -*

-* exception (ExceptionEvent) — Dispatched -* by the OT class locally in the event of an error. -*

-*

-* connectionCreated (ConnectionEvent) — -* Dispatched by the Session object on all clients connected to the session. -*

-*

-* sessionConnected (SessionConnectEvent) -* — Dispatched locally by the Session object when the connection is established. -*

-* -* @param {String} token The session token. You generate a session token using our -* server-side libraries or the -* Dashboard page. For more information, see -* Connection token creation. -* -* @param {Function} completionHandler (Optional) A function to be called when the call to the -* connect() method succeeds or fails. This function takes one parameter — -* error (see the Error object). -* On success, the completionHandler function is not passed any -* arguments. On error, the function is passed an error object parameter -* (see the Error object). The -* error object has two properties: code (an integer) and -* message (a string), which identify the cause of the failure. The following -* code adds a completionHandler when calling the connect() method: -*
-* session.connect(token, function (error) {
-*   if (error) {
-*       console.log(error.message);
-*   } else {
-*     console.log("Connected to session.");
-*   }
-* });
-* 
-*

-* Note that upon connecting to the session, the Session object dispatches a -* sessionConnected event in addition to calling the completionHandler. -* The SessionConnectEvent object, which defines the sessionConnected event, -* includes connections and streams properties, which -* list the connections and streams in the session when you connect. -*

-* -* @see SessionConnectEvent -* @method #connect -* @memberOf Session + /** + * Connects to an OpenTok session. + *

+ * Upon a successful connection, the completion handler (the second parameter of the method) is + * invoked without an error object passed in. (If there is an error connecting, the completion + * handler is invoked with an error object.) Make sure that you have successfully connected to the + * session before calling other methods of the Session object. + *

+ *

+ * The Session object dispatches a connectionCreated event when any client + * (including your own) connects to to the session. + *

+ * + *
+ * Example + *
+ *

+ * The following code initializes a session and sets up an event listener for when the session + * connects: + *

+ *
+  *  var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+  *  var sessionID = ""; // Replace with your own session ID.
+  *                      // See https://dashboard.tokbox.com/projects
+  *  var token = ""; // Replace with a generated token that has been assigned the moderator role.
+  *                  // See https://dashboard.tokbox.com/projects
+  *
+  *  var session = OT.initSession(apiKey, sessionID);
+  *  session.on("sessionConnected", function(sessionConnectEvent) {
+  *      //
+  *  });
+  *  session.connect(token);
+  *  
+ *

+ *

+ * In this example, the sessionConnectHandler() function is passed an event + * object of type {@link SessionConnectEvent}. + *

+ * + *
+ * Events dispatched: + *
+ * + *

+ * exception (ExceptionEvent) — Dispatched + * by the OT class locally in the event of an error. + *

+ *

+ * connectionCreated (ConnectionEvent) — + * Dispatched by the Session object on all clients connected to the session. + *

+ *

+ * sessionConnected (SessionConnectEvent) + * — Dispatched locally by the Session object when the connection is established. + *

+ * + * @param {String} token The session token. You generate a session token using our + * server-side libraries or the + * Dashboard page. For more information, see + * Connection token creation. + * + * @param {Function} completionHandler (Optional) A function to be called when the call to the + * connect() method succeeds or fails. This function takes one parameter — + * error (see the Error object). + * On success, the completionHandler function is not passed any + * arguments. On error, the function is passed an error object parameter + * (see the Error object). The + * error object has two properties: code (an integer) and + * message (a string), which identify the cause of the failure. The following + * code adds a completionHandler when calling the connect() method: + *
+  * session.connect(token, function (error) {
+  *   if (error) {
+  *       console.log(error.message);
+  *   } else {
+  *     console.log("Connected to session.");
+  *   }
+  * });
+  * 
+ *

+ * Note that upon connecting to the session, the Session object dispatches a + * sessionConnected event in addition to calling the completionHandler. + * The SessionConnectEvent object, which defines the sessionConnected event, + * includes connections and streams properties, which + * list the connections and streams in the session when you connect. + *

+ * + * @see SessionConnectEvent + * @method #connect + * @memberOf Session */ this.connect = function(token) { - if(apiKey == null && arguments.length > 1 && + if (arguments.length > 1 && (typeof arguments[0] === 'string' || typeof arguments[0] === 'number') && typeof arguments[1] === 'string') { - _apiKey = token.toString(); + if (apiKey == null) _apiKey = token.toString(); token = arguments[1]; } @@ -23198,7 +23986,7 @@ OT.Session = function(apiKey, sessionId) { this.once('sessionConnectFailed', completionHandler); } - if(_apiKey == null || OT.$.isFunction(_apiKey)) { + if (_apiKey == null || OT.$.isFunction(_apiKey)) { setTimeout(OT.$.bind( sessionConnectFailed, this, @@ -23213,7 +24001,7 @@ OT.Session = function(apiKey, sessionId) { if (!_sessionId || OT.$.isObject(_sessionId) || OT.$.isArray(_sessionId)) { var errorMsg; - if(!_sessionId) { + if (!_sessionId) { errorMsg = 'SessionID is undefined. You must pass a sessionID to initSession.'; } else { errorMsg = 'SessionID is not a string. You must use string as the session ID passed into ' + @@ -23244,63 +24032,63 @@ OT.Session = function(apiKey, sessionId) { return this; }; -/** -* Disconnects from the OpenTok session. -* -*

-* Calling the disconnect() method ends your connection with the session. In the -* course of terminating your connection, it also ceases publishing any stream(s) you were -* publishing. -*

-*

-* Session objects on remote clients dispatch streamDestroyed events for any -* stream you were publishing. The Session object dispatches a sessionDisconnected -* event locally. The Session objects on remote clients dispatch connectionDestroyed -* events, letting other connections know you have left the session. The -* {@link SessionDisconnectEvent} and {@link StreamEvent} objects that define the -* sessionDisconnect and connectionDestroyed events each have a -* reason property. The reason property lets the developer determine -* whether the connection is being terminated voluntarily and whether any streams are being -* destroyed as a byproduct of the underlying connection's voluntary destruction. -*

-*

-* If the session is not currently connected, calling this method causes a warning to be logged. -* See OT.setLogLevel(). -*

-* -*

-* Note: If you intend to reuse a Publisher object created using -* OT.initPublisher() to publish to different sessions sequentially, call either -* Session.disconnect() or Session.unpublish(). Do not call both. -* Then call the preventDefault() method of the streamDestroyed or -* sessionDisconnected event object to prevent the Publisher object from being -* removed from the page. Be sure to call preventDefault() only if the -* connection.connectionId property of the Stream object in the event matches the -* connection.connectionId property of your Session object (to ensure that you are -* preventing the default behavior for your published streams, not for other streams that you -* subscribe to). -*

-* -*
-* Events dispatched: -*
-*

-* sessionDisconnected -* (SessionDisconnectEvent) -* — Dispatched locally when the connection is disconnected. -*

-*

-* connectionDestroyed (ConnectionEvent) — -* Dispatched on other clients, along with the streamDestroyed event (as warranted). -*

-* -*

-* streamDestroyed (StreamEvent) — -* Dispatched on other clients if streams are lost as a result of the session disconnecting. -*

-* -* @method #disconnect -* @memberOf Session + /** + * Disconnects from the OpenTok session. + * + *

+ * Calling the disconnect() method ends your connection with the session. In the + * course of terminating your connection, it also ceases publishing any stream(s) you were + * publishing. + *

+ *

+ * Session objects on remote clients dispatch streamDestroyed events for any + * stream you were publishing. The Session object dispatches a sessionDisconnected + * event locally. The Session objects on remote clients dispatch connectionDestroyed + * events, letting other connections know you have left the session. The + * {@link SessionDisconnectEvent} and {@link StreamEvent} objects that define the + * sessionDisconnect and connectionDestroyed events each have a + * reason property. The reason property lets the developer determine + * whether the connection is being terminated voluntarily and whether any streams are being + * destroyed as a byproduct of the underlying connection's voluntary destruction. + *

+ *

+ * If the session is not currently connected, calling this method causes a warning to be logged. + * See OT.setLogLevel(). + *

+ * + *

+ * Note: If you intend to reuse a Publisher object created using + * OT.initPublisher() to publish to different sessions sequentially, call either + * Session.disconnect() or Session.unpublish(). Do not call both. + * Then call the preventDefault() method of the streamDestroyed or + * sessionDisconnected event object to prevent the Publisher object from being + * removed from the page. Be sure to call preventDefault() only if the + * connection.connectionId property of the Stream object in the event matches the + * connection.connectionId property of your Session object (to ensure that you are + * preventing the default behavior for your published streams, not for other streams that you + * subscribe to). + *

+ * + *
+ * Events dispatched: + *
+ *

+ * sessionDisconnected + * (SessionDisconnectEvent) + * — Dispatched locally when the connection is disconnected. + *

+ *

+ * connectionDestroyed (ConnectionEvent) — + * Dispatched on other clients, along with the streamDestroyed event (as warranted). + *

+ * + *

+ * streamDestroyed (StreamEvent) — + * Dispatched on other clients if streams are lost as a result of the session disconnecting. + *

+ * + * @method #disconnect + * @memberOf Session */ var disconnect = OT.$.bind(function disconnect(drainSocketBuffer) { if (_socket && _socket.isNot('disconnected')) { @@ -23308,8 +24096,7 @@ OT.Session = function(apiKey, sessionId) { setState('disconnecting'); _socket.disconnect(drainSocketBuffer); } - } - else { + } else { reset(); } }, this); @@ -23325,129 +24112,129 @@ OT.Session = function(apiKey, sessionId) { disconnect(reason !== 'unloaded'); }; -/** -* The publish() method starts publishing an audio-video stream to the session. -* The audio-video stream is captured from a local microphone and webcam. Upon successful -* publishing, the Session objects on all connected clients dispatch the -* streamCreated event. -*

-* -* -*

You pass a Publisher object as the one parameter of the method. You can initialize a -* Publisher object by calling the OT.initPublisher() -* method. Before calling Session.publish(). -*

-* -*

This method takes an alternate form: publish([targetElement:String, -* properties:Object]):Publisher — In this form, you do not pass a Publisher -* object into the function. Instead, you pass in a targetElement (the ID of the -* DOM element that the Publisher will replace) and a properties object that -* defines options for the Publisher (see OT.initPublisher().) -* The method returns a new Publisher object, which starts sending an audio-video stream to the -* session. The remainder of this documentation describes the form that takes a single Publisher -* object as a parameter. -* -*

-* A local display of the published stream is created on the web page by replacing -* the specified element in the DOM with a streaming video display. The video stream -* is automatically mirrored horizontally so that users see themselves and movement -* in their stream in a natural way. If the width and height of the display do not match -* the 4:3 aspect ratio of the video signal, the video stream is cropped to fit the -* display. -*

-* -*

-* If calling this method creates a new Publisher object and the OpenTok library does not -* have access to the camera or microphone, the web page alerts the user to grant access -* to the camera and microphone. -*

-* -*

-* The OT object dispatches an exception event if the user's role does not -* include permissions required to publish. For example, if the user's role is set to subscriber, -* then they cannot publish. You define a user's role when you create the user token using the -* generate_token() method of the -* OpenTok server-side -* libraries or the Dashboard page. -* You pass the token string as a parameter of the connect() method of the Session -* object. See ExceptionEvent and -* OT.on(). -*

-*

-* The application throws an error if the session is not connected. -*

-* -*
Events dispatched:
-*

-* exception (ExceptionEvent) — Dispatched -* by the OT object. This can occur when user's role does not allow publishing (the -* code property of event object is set to 1500); it can also occur if the c -* onnection fails to connect (the code property of event object is set to 1013). -* WebRTC is a peer-to-peer protocol, and it is possible that connections will fail to connect. -* The most common cause for failure is a firewall that the protocol cannot traverse. -*

-*

-* streamCreated (StreamEvent) — -* The stream has been published. The Session object dispatches this on all clients -* subscribed to the stream, as well as on the publisher's client. -*

-* -*
Example
-* -*

-* The following example publishes a video once the session connects: -*

-*
-* var sessionId = ""; // Replace with your own session ID.
-*                     // See https://dashboard.tokbox.com/projects
-* var token = ""; // Replace with a generated token that has been assigned the moderator role.
-*                 // See https://dashboard.tokbox.com/projects
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("sessionConnected", function (event) {
-*     var publisherOptions = {width: 400, height:300, name:"Bob's stream"};
-*     // This assumes that there is a DOM element with the ID 'publisher':
-*     publisher = OT.initPublisher('publisher', publisherOptions);
-*     session.publish(publisher);
-* });
-* session.connect(token);
-* 
-* -* @param {Publisher} publisher A Publisher object, which you initialize by calling the -* OT.initPublisher() method. -* -* @param {Function} completionHandler (Optional) A function to be called when the call to the -* publish() method succeeds or fails. This function takes one parameter — -* error. On success, the completionHandler function is not passed any -* arguments. On error, the function is passed an error object parameter -* (see the Error object). The -* error object has two properties: code (an integer) and -* message (a string), which identify the cause of the failure. Calling -* publish() fails if the role assigned to your token is not "publisher" or -* "moderator"; in this case error.code is set to 1500. Calling -* publish() also fails the client fails to connect; in this case -* error.code is set to 1013. The following code adds a -* completionHandler when calling the publish() method: -*
-* session.publish(publisher, null, function (error) {
-*   if (error) {
-*     console.log(error.message);
-*   } else {
-*     console.log("Publishing a stream.");
-*   }
-* });
-* 
-* -* @returns The Publisher object for this stream. -* -* @method #publish -* @memberOf Session + /** + * The publish() method starts publishing an audio-video stream to the session. + * The audio-video stream is captured from a local microphone and webcam. Upon successful + * publishing, the Session objects on all connected clients dispatch the + * streamCreated event. + *

+ * + * + *

You pass a Publisher object as the one parameter of the method. You can initialize a + * Publisher object by calling the OT.initPublisher() + * method. Before calling Session.publish(). + *

+ * + *

This method takes an alternate form: publish([targetElement:String, + * properties:Object]):Publisher — In this form, you do not pass a Publisher + * object into the function. Instead, you pass in a targetElement (the ID of the + * DOM element that the Publisher will replace) and a properties object that + * defines options for the Publisher (see OT.initPublisher().) + * The method returns a new Publisher object, which starts sending an audio-video stream to the + * session. The remainder of this documentation describes the form that takes a single Publisher + * object as a parameter. + * + *

+ * A local display of the published stream is created on the web page by replacing + * the specified element in the DOM with a streaming video display. The video stream + * is automatically mirrored horizontally so that users see themselves and movement + * in their stream in a natural way. If the width and height of the display do not match + * the 4:3 aspect ratio of the video signal, the video stream is cropped to fit the + * display. + *

+ * + *

+ * If calling this method creates a new Publisher object and the OpenTok library does not + * have access to the camera or microphone, the web page alerts the user to grant access + * to the camera and microphone. + *

+ * + *

+ * The OT object dispatches an exception event if the user's role does not + * include permissions required to publish. For example, if the user's role is set to subscriber, + * then they cannot publish. You define a user's role when you create the user token using the + * generate_token() method of the + * OpenTok server-side + * libraries or the Dashboard page. + * You pass the token string as a parameter of the connect() method of the Session + * object. See ExceptionEvent and + * OT.on(). + *

+ *

+ * The application throws an error if the session is not connected. + *

+ * + *
Events dispatched:
+ *

+ * exception (ExceptionEvent) — Dispatched + * by the OT object. This can occur when user's role does not allow publishing (the + * code property of event object is set to 1500); it can also occur if the c + * onnection fails to connect (the code property of event object is set to 1013). + * WebRTC is a peer-to-peer protocol, and it is possible that connections will fail to connect. + * The most common cause for failure is a firewall that the protocol cannot traverse. + *

+ *

+ * streamCreated (StreamEvent) — + * The stream has been published. The Session object dispatches this on all clients + * subscribed to the stream, as well as on the publisher's client. + *

+ * + *
Example
+ * + *

+ * The following example publishes a video once the session connects: + *

+ *
+  * var sessionId = ""; // Replace with your own session ID.
+  *                     // See https://dashboard.tokbox.com/projects
+  * var token = ""; // Replace with a generated token that has been assigned the moderator role.
+  *                 // See https://dashboard.tokbox.com/projects
+  * var session = OT.initSession(apiKey, sessionID);
+  * session.on("sessionConnected", function (event) {
+  *     var publisherOptions = {width: 400, height:300, name:"Bob's stream"};
+  *     // This assumes that there is a DOM element with the ID 'publisher':
+  *     publisher = OT.initPublisher('publisher', publisherOptions);
+  *     session.publish(publisher);
+  * });
+  * session.connect(token);
+  * 
+ * + * @param {Publisher} publisher A Publisher object, which you initialize by calling the + * OT.initPublisher() method. + * + * @param {Function} completionHandler (Optional) A function to be called when the call to the + * publish() method succeeds or fails. This function takes one parameter — + * error. On success, the completionHandler function is not passed any + * arguments. On error, the function is passed an error object parameter + * (see the Error object). The + * error object has two properties: code (an integer) and + * message (a string), which identify the cause of the failure. Calling + * publish() fails if the role assigned to your token is not "publisher" or + * "moderator"; in this case error.code is set to 1500. Calling + * publish() also fails the client fails to connect; in this case + * error.code is set to 1013. The following code adds a + * completionHandler when calling the publish() method: + *
+  * session.publish(publisher, null, function (error) {
+  *   if (error) {
+  *     console.log(error.message);
+  *   } else {
+  *     console.log("Publishing a stream.");
+  *   }
+  * });
+  * 
+ * + * @returns The Publisher object for this stream. + * + * @method #publish + * @memberOf Session */ this.publish = function(publisher, properties, completionHandler) { - if(typeof publisher === 'function') { + if (typeof publisher === 'function') { completionHandler = publisher; publisher = undefined; } - if(typeof properties === 'function') { + if (typeof properties === 'function') { completionHandler = properties; properties = undefined; } @@ -23463,7 +24250,7 @@ OT.Session = function(apiKey, sessionId) { }, sessionId: _sessionId, streamId: (publisher && publisher.stream) ? publisher.stream.id : null, - partnerId: _apiKey, + partnerId: _apiKey }); if (completionHandler && OT.$.isFunction(completionHandler)) { @@ -23488,21 +24275,21 @@ OT.Session = function(apiKey, sessionId) { } // If the user has passed in an ID of a element then we create a new publisher. - if (!publisher || typeof(publisher)==='string' || OT.$.isElementNode(publisher)) { + if (!publisher || typeof (publisher) === 'string' || OT.$.isElementNode(publisher)) { // Initiate a new Publisher with the new session credentials publisher = OT.initPublisher(publisher, properties); - } else if (publisher instanceof OT.Publisher){ + } else if (publisher instanceof OT.Publisher) { // If the publisher already has a session attached to it we can if ('session' in publisher && publisher.session && 'sessionId' in publisher.session) { // send a warning message that we can't publish again. - if( publisher.session.sessionId === this.sessionId){ + if (publisher.session.sessionId === this.sessionId) { OT.warn('Cannot publish ' + publisher.guid() + ' again to ' + this.sessionId + '. Please call session.unpublish(publisher) first.'); } else { OT.warn('Cannot publish ' + publisher.guid() + ' publisher already attached to ' + - publisher.session.sessionId+ '. Please call session.unpublish(publisher) first.'); + publisher.session.sessionId + '. Please call session.unpublish(publisher) first.'); } } @@ -23514,16 +24301,19 @@ OT.Session = function(apiKey, sessionId) { return; } - publisher.once('publishComplete', function(err) { + publisher.once('publishComplete', function() { + var args = Array.prototype.slice.call(arguments), + err = args[0]; + if (err) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - 'Session.publish :: ' + err.message, - completionHandler); - return; + err.message = 'Session.publish :: ' + err.message; + args[0] = err; + + OT.error(err.code, err.message); } if (completionHandler && OT.$.isFunction(completionHandler)) { - completionHandler.apply(null, arguments); + completionHandler.apply(null, args); } }); @@ -23534,86 +24324,86 @@ OT.Session = function(apiKey, sessionId) { return publisher; }; -/** -* Ceases publishing the specified publisher's audio-video stream -* to the session. By default, the local representation of the audio-video stream is -* removed from the web page. Upon successful termination, the Session object on every -* connected web page dispatches -* a streamDestroyed event. -*

-* -*

-* To prevent the Publisher from being removed from the DOM, add an event listener for the -* streamDestroyed event dispatched by the Publisher object and call the -* preventDefault() method of the event object. -*

-* -*

-* Note: If you intend to reuse a Publisher object created using -* OT.initPublisher() to publish to different sessions sequentially, call -* either Session.disconnect() or Session.unpublish(). Do not call -* both. Then call the preventDefault() method of the streamDestroyed -* or sessionDisconnected event object to prevent the Publisher object from being -* removed from the page. Be sure to call preventDefault() only if the -* connection.connectionId property of the Stream object in the event matches the -* connection.connectionId property of your Session object (to ensure that you are -* preventing the default behavior for your published streams, not for other streams that you -* subscribe to). -*

-* -*
Events dispatched:
-* -*

-* streamDestroyed (StreamEvent) — -* The stream associated with the Publisher has been destroyed. Dispatched on by the -* Publisher on on the Publisher's browser. Dispatched by the Session object on -* all other connections subscribing to the publisher's stream. -*

-* -*
Example
-* -* The following example publishes a stream to a session and adds a Disconnect link to the -* web page. Clicking this link causes the stream to stop being published. -* -*
-* <script>
-*     var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-*     var sessionID = ""; // Replace with your own session ID.
-*                      // See https://dashboard.tokbox.com/projects
-*     var token = "Replace with the TokBox token string provided to you."
-*     var session = OT.initSession(apiKey, sessionID);
-*     session.on("sessionConnected", function sessionConnectHandler(event) {
-*         // This assumes that there is a DOM element with the ID 'publisher':
-*         publisher = OT.initPublisher('publisher');
-*         session.publish(publisher);
-*     });
-*     session.connect(token);
-*     var publisher;
-*
-*     function unpublish() {
-*         session.unpublish(publisher);
-*     }
-* </script>
-*
-* <body>
-*
-*     <div id="publisherContainer/>
-*     <br/>
-*
-*     <a href="javascript:unpublish()">Stop Publishing</a>
-*
-* </body>
-*
-* 
-* -* @see publish() -* -* @see streamDestroyed event -* -* @param {Publisher} publisher The Publisher object to stop streaming. -* -* @method #unpublish -* @memberOf Session + /** + * Ceases publishing the specified publisher's audio-video stream + * to the session. By default, the local representation of the audio-video stream is + * removed from the web page. Upon successful termination, the Session object on every + * connected web page dispatches + * a streamDestroyed event. + *

+ * + *

+ * To prevent the Publisher from being removed from the DOM, add an event listener for the + * streamDestroyed event dispatched by the Publisher object and call the + * preventDefault() method of the event object. + *

+ * + *

+ * Note: If you intend to reuse a Publisher object created using + * OT.initPublisher() to publish to different sessions sequentially, call + * either Session.disconnect() or Session.unpublish(). Do not call + * both. Then call the preventDefault() method of the streamDestroyed + * or sessionDisconnected event object to prevent the Publisher object from being + * removed from the page. Be sure to call preventDefault() only if the + * connection.connectionId property of the Stream object in the event matches the + * connection.connectionId property of your Session object (to ensure that you are + * preventing the default behavior for your published streams, not for other streams that you + * subscribe to). + *

+ * + *
Events dispatched:
+ * + *

+ * streamDestroyed (StreamEvent) — + * The stream associated with the Publisher has been destroyed. Dispatched on by the + * Publisher on on the Publisher's browser. Dispatched by the Session object on + * all other connections subscribing to the publisher's stream. + *

+ * + *
Example
+ * + * The following example publishes a stream to a session and adds a Disconnect link to the + * web page. Clicking this link causes the stream to stop being published. + * + *
+  * <script>
+  *     var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+  *     var sessionID = ""; // Replace with your own session ID.
+  *                      // See https://dashboard.tokbox.com/projects
+  *     var token = "Replace with the TokBox token string provided to you."
+  *     var session = OT.initSession(apiKey, sessionID);
+  *     session.on("sessionConnected", function sessionConnectHandler(event) {
+  *         // This assumes that there is a DOM element with the ID 'publisher':
+  *         publisher = OT.initPublisher('publisher');
+  *         session.publish(publisher);
+  *     });
+  *     session.connect(token);
+  *     var publisher;
+  *
+  *     function unpublish() {
+  *         session.unpublish(publisher);
+  *     }
+  * </script>
+  *
+  * <body>
+  *
+  *     <div id="publisherContainer/>
+  *     <br/>
+  *
+  *     <a href="javascript:unpublish()">Stop Publishing</a>
+  *
+  * </body>
+  *
+  * 
+ * + * @see publish() + * + * @see streamDestroyed event + * + * @param {Publisher} publisher The Publisher object to stop streaming. + * + * @method #unpublish + * @memberOf Session */ this.unpublish = function(publisher) { if (!publisher) { @@ -23625,211 +24415,219 @@ OT.Session = function(apiKey, sessionId) { publisher._.unpublishFromSession(this, 'unpublished'); }; - -/** -* Subscribes to a stream that is available to the session. You can get an array of -* available streams from the streams property of the sessionConnected -* and streamCreated events (see -* SessionConnectEvent and -* StreamEvent). -*

-*

-* The subscribed stream is displayed on the local web page by replacing the specified element -* in the DOM with a streaming video display. If the width and height of the display do not -* match the 4:3 aspect ratio of the video signal, the video stream is cropped to fit -* the display. If the stream lacks a video component, a blank screen with an audio indicator -* is displayed in place of the video stream. -*

-* -*

-* The application throws an error if the session is not connected or if the -* targetElement does not exist in the HTML DOM. -*

-* -*
Example
-* -* The following code subscribes to other clients' streams: -* -*
-* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-* var sessionID = ""; // Replace with your own session ID.
-*                     // See https://dashboard.tokbox.com/projects
-*
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("streamCreated", function(event) {
-*   subscriber = session.subscribe(event.stream, targetElement);
-* });
-* session.connect(token);
-* 
-* -* @param {Stream} stream The Stream object representing the stream to which we are trying to -* subscribe. -* -* @param {Object} targetElement (Optional) The DOM element or the id attribute of -* the existing DOM element used to determine the location of the Subscriber video in the HTML -* DOM. See the insertMode property of the properties parameter. If -* you do not specify a targetElement, the application appends a new DOM element -* to the HTML body. -* -* @param {Object} properties This is an object that contains the following properties: -*
    -*
  • audioVolume (Number) — The desired audio volume, between 0 and -* 100, when the Subscriber is first opened (default: 50). After you subscribe to the -* stream, you can adjust the volume by calling the -* setAudioVolume() method of the -* Subscriber object. This volume setting affects local playback only; it does not affect -* the stream's volume on other clients.
  • -* -*
  • -* fitMode (String) — Determines how the video is displayed if the its -* dimensions do not match those of the DOM element. You can set this property to one of -* the following values: -*

    -*

      -*
    • -* "cover" — The video is cropped if its dimensions do not match -* those of the DOM element. This is the default setting for videos that have a -* camera as the source (for Stream objects with the videoType property -* set to "camera"). -*
    • -*
    • -* "contain" — The video is letter-boxed if its dimensions do not -* match those of the DOM element. This is the default setting for screen-sharing -* videos (for Stream objects with the videoType property set to -* "screen"). -*
    • -*
    -*
  • -* -*
  • height (Number) — The desired height, in pixels, of the -* displayed Subscriber video stream (default: 198). Note: Use the -* height and width properties to set the dimensions -* of the Subscriber video; do not set the height and width of the DOM element -* (using CSS).
  • -* -*
  • -* insertMode (String) — Specifies how the Subscriber object will -* be inserted in the HTML DOM. See the targetElement parameter. This -* string can have the following values: -*
      -*
    • "replace" — The Subscriber object replaces contents of the -* targetElement. This is the default.
    • -*
    • "after" — The Subscriber object is a new element inserted -* after the targetElement in the HTML DOM. (Both the Subscriber and targetElement -* have the same parent element.)
    • -*
    • "before" — The Subscriber object is a new element inserted -* before the targetElement in the HTML DOM. (Both the Subsciber and targetElement -* have the same parent element.)
    • -*
    • "append" — The Subscriber object is a new element added as a -* child of the targetElement. If there are other child elements, the Subscriber is -* appended as the last child element of the targetElement.
    • -*
    -*
  • -* -*
  • -* showControls (Boolean) — Whether to display the built-in user interface -* controls for the Subscriber (default: true). These controls include the name -* display, the audio level indicator, the speaker control button, the video disabled indicator, -* and the video disabled warning icon. You can turn off all user interface controls by setting -* this property to false. You can control the display of individual user interface -* controls by leaving this property set to true (the default) and setting individual -* properties of the style property. -*
  • -*
  • -* style (Object) — An object containing properties that define the initial -* appearance of user interface controls of the Subscriber. The style object -* includes the following properties: -*
      -*
    • audioLevelDisplayMode (String) — How to display the audio level -* indicator. Possible values are: "auto" (the indicator is displayed when the -* video is disabled), "off" (the indicator is not displayed), and -* "on" (the indicator is always displayed).
    • -* -*
    • backgroundImageURI (String) — A URI for an image to display as -* the background image when a video is not displayed. (A video may not be displayed if -* you call subscribeToVideo(false) on the Subscriber object). You can pass an -* http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the -* data URI scheme (instead of http or https) and pass in base-64-encrypted -* PNG data, such as that obtained from the -* Subscriber.getImgData() method. For example, -* you could set the property to "data:VBORw0KGgoAA...", where the portion of -* the string after "data:" is the result of a call to -* Subscriber.getImgData(). If the URL or the image data is invalid, the -* property is ignored (the attempt to set the image fails silently). -*

      -* Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), -* you cannot set the backgroundImageURI style to a string larger than -* 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this -* limitation, you cannot set the backgroundImageURI style to a string obtained -* with the getImgData() method. -*

    • -* -*
    • buttonDisplayMode (String) — How to display the speaker controls -* Possible values are: "auto" (controls are displayed when the stream is first -* displayed and when the user mouses over the display), "off" (controls are not -* displayed), and "on" (controls are always displayed).
    • -* -*
    • nameDisplayMode (String) — Whether to display the stream name. -* Possible values are: "auto" (the name is displayed when the stream is first -* displayed and when the user mouses over the display), "off" (the name is not -* displayed), and "on" (the name is always displayed).
    • -* -*
    • videoDisabledDisplayMode (String) — Whether to display the video -* disabled indicator and video disabled warning icons for a Subscriber. These icons -* indicate that the video has been disabled (or is in risk of being disabled for -* the warning icon) due to poor stream quality. This style only applies to the Subscriber -* object. Possible values are: "auto" (the icons are automatically when the -* displayed video is disabled or in risk of being disabled due to poor stream quality), -* "off" (do not display the icons), and "on" (display the -* icons). The default setting is "auto"
    • -*
    -*
  • -* -*
  • subscribeToAudio (Boolean) — Whether to initially subscribe to audio -* (if available) for the stream (default: true).
  • -* -*
  • subscribeToVideo (Boolean) — Whether to initially subscribe to video -* (if available) for the stream (default: true).
  • -* -*
  • width (Number) — The desired width, in pixels, of the -* displayed Subscriber video stream (default: 264). Note: Use the -* height and width properties to set the dimensions -* of the Subscriber video; do not set the height and width of the DOM element -* (using CSS).
  • -* -*
-* -* @param {Function} completionHandler (Optional) A function to be called when the call to the -* subscribe() method succeeds or fails. This function takes one parameter — -* error. On success, the completionHandler function is not passed any -* arguments. On error, the function is passed an error object, defined by the -* Error class, has two properties: code (an integer) and -* message (a string), which identify the cause of the failure. The following -* code adds a completionHandler when calling the subscribe() method: -*
-* session.subscribe(stream, "subscriber", null, function (error) {
-*   if (error) {
-*     console.log(error.message);
-*   } else {
-*     console.log("Subscribed to stream: " + stream.id);
-*   }
-* });
-* 
-* -* @signature subscribe(stream, targetElement, properties, completionHandler) -* @returns {Subscriber} The Subscriber object for this stream. Stream control functions -* are exposed through the Subscriber object. -* @method #subscribe -* @memberOf Session + /** + * Subscribes to a stream that is available to the session. You can get an array of + * available streams from the streams property of the sessionConnected + * and streamCreated events (see + * SessionConnectEvent and + * StreamEvent). + *

+ *

+ * The subscribed stream is displayed on the local web page by replacing the specified element + * in the DOM with a streaming video display. If the width and height of the display do not + * match the 4:3 aspect ratio of the video signal, the video stream is cropped to fit + * the display. If the stream lacks a video component, a blank screen with an audio indicator + * is displayed in place of the video stream. + *

+ * + *

+ * The application throws an error if the session is not connected or if the + * targetElement does not exist in the HTML DOM. + *

+ * + *
Example
+ * + * The following code subscribes to other clients' streams: + * + *
+  * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+  * var sessionID = ""; // Replace with your own session ID.
+  *                     // See https://dashboard.tokbox.com/projects
+  *
+  * var session = OT.initSession(apiKey, sessionID);
+  * session.on("streamCreated", function(event) {
+  *   subscriber = session.subscribe(event.stream, targetElement);
+  * });
+  * session.connect(token);
+  * 
+ * + * @param {Stream} stream The Stream object representing the stream to which we are trying to + * subscribe. + * + * @param {Object} targetElement (Optional) The DOM element or the id attribute of + * the existing DOM element used to determine the location of the Subscriber video in the HTML + * DOM. See the insertMode property of the properties parameter. If + * you do not specify a targetElement, the application appends a new DOM element + * to the HTML body. + * + * @param {Object} properties This is an object that contains the following properties: + *
    + *
  • audioVolume (Number) — The desired audio volume, between 0 and + * 100, when the Subscriber is first opened (default: 50). After you subscribe to the + * stream, you can adjust the volume by calling the + * setAudioVolume() method of the + * Subscriber object. This volume setting affects local playback only; it does not affect + * the stream's volume on other clients.
  • + * + *
  • + * fitMode (String) — Determines how the video is displayed if the its + * dimensions do not match those of the DOM element. You can set this property to one of + * the following values: + *

    + *

      + *
    • + * "cover" — The video is cropped if its dimensions do not match + * those of the DOM element. This is the default setting for videos that have a + * camera as the source (for Stream objects with the videoType property + * set to "camera"). + *
    • + *
    • + * "contain" — The video is letter-boxed if its dimensions do not + * match those of the DOM element. This is the default setting for screen-sharing + * videos (for Stream objects with the videoType property set to + * "screen"). + *
    • + *
    + *
  • + * + *
  • + * height (Number) — The desired initial height of the displayed + * video in the HTML page (default: 198 pixels). You can specify the number of pixels as + * either a number (such as 300) or a string ending in "px" (such as "300px"). Or you can + * specify a percentage of the size of the parent element, with a string ending in "%" + * (such as "100%"). Note: To resize the video, adjust the CSS of the subscriber's + * DOM element (the element property of the Subscriber object) or (if the + * height is specified as a percentage) its parent DOM element (see + * + * Resizing or repositioning a video). + *
  • + *
  • + * insertMode (String) — Specifies how the Subscriber object will + * be inserted in the HTML DOM. See the targetElement parameter. This + * string can have the following values: + *

    + *

      + *
    • "replace" — The Subscriber object replaces contents of the + * targetElement. This is the default.
    • + *
    • "after" — The Subscriber object is a new element inserted + * after the targetElement in the HTML DOM. (Both the Subscriber and targetElement + * have the same parent element.)
    • + *
    • "before" — The Subscriber object is a new element inserted + * before the targetElement in the HTML DOM. (Both the Subsciber and targetElement + * have the same parent element.)
    • + *
    • "append" — The Subscriber object is a new element added as a + * child of the targetElement. If there are other child elements, the Subscriber is + * appended as the last child element of the targetElement.
    • + *

    + *

    Do not move the publisher element or its parent elements in the DOM + * heirarchy. Use CSS to resize or reposition the publisher video's element + * (the element property of the Publisher object) or its parent element (see + * + * Resizing or repositioning a video).

    + *
  • + *
  • + * showControls (Boolean) — Whether to display the built-in user interface + * controls for the Subscriber (default: true). These controls include the name + * display, the audio level indicator, the speaker control button, the video disabled indicator, + * and the video disabled warning icon. You can turn off all user interface controls by setting + * this property to false. You can control the display of individual user interface + * controls by leaving this property set to true (the default) and setting + * individual properties of the style property. + *
  • + *
  • + * style (Object) — An object containing properties that define the initial + * appearance of user interface controls of the Subscriber. The style object + * includes the following properties: + *
      + *
    • audioLevelDisplayMode (String) — How to display the audio level + * indicator. Possible values are: "auto" (the indicator is displayed when the + * video is disabled), "off" (the indicator is not displayed), and + * "on" (the indicator is always displayed).
    • + * + *
    • backgroundImageURI (String) — A URI for an image to display as + * the background image when a video is not displayed. (A video may not be displayed if + * you call subscribeToVideo(false) on the Subscriber object). You can pass an + * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the + * data URI scheme (instead of http or https) and pass in base-64-encrypted + * PNG data, such as that obtained from the + * Subscriber.getImgData() method. For example, + * you could set the property to "data:VBORw0KGgoAA...", where the portion of + * the string after "data:" is the result of a call to + * Subscriber.getImgData(). If the URL or the image data is invalid, the + * property is ignored (the attempt to set the image fails silently).
    • + * + *
    • buttonDisplayMode (String) — How to display the speaker controls + * Possible values are: "auto" (controls are displayed when the stream is first + * displayed and when the user mouses over the display), "off" (controls are not + * displayed), and "on" (controls are always displayed).
    • + * + *
    • nameDisplayMode (String) — Whether to display the stream name. + * Possible values are: "auto" (the name is displayed when the stream is first + * displayed and when the user mouses over the display), "off" (the name is not + * displayed), and "on" (the name is always displayed).
    • + * + *
    • videoDisabledDisplayMode (String) — Whether to display the video + * disabled indicator and video disabled warning icons for a Subscriber. These icons + * indicate that the video has been disabled (or is in risk of being disabled for + * the warning icon) due to poor stream quality. This style only applies to the Subscriber + * object. Possible values are: "auto" (the icons are automatically when the + * displayed video is disabled or in risk of being disabled due to poor stream quality), + * "off" (do not display the icons), and "on" (display the + * icons). The default setting is "auto"
    • + *
    + *
  • + * + *
  • subscribeToAudio (Boolean) — Whether to initially subscribe to audio + * (if available) for the stream (default: true).
  • + * + *
  • subscribeToVideo (Boolean) — Whether to initially subscribe to video + * (if available) for the stream (default: true).
  • + * + *
  • + * width (Number) — The desired initial width of the displayed + * video in the HTML page (default: 264 pixels). You can specify the number of pixels as + * either a number (such as 400) or a string ending in "px" (such as "400px"). Or you can + * specify a percentage of the size of the parent element, with a string ending in "%" + * (such as "100%"). Note: To resize the video, adjust the CSS of the subscriber's + * DOM element (the element property of the Subscriber object) or (if the + * width is specified as a percentage) its parent DOM element (see + * + * Resizing or repositioning a video). + *
  • + * + *
+ * + * @param {Function} completionHandler (Optional) A function to be called when the call to the + * subscribe() method succeeds or fails. This function takes one parameter — + * error. On success, the completionHandler function is not passed any + * arguments. On error, the function is passed an error object, defined by the + * Error class, has two properties: code (an integer) and + * message (a string), which identify the cause of the failure. The following + * code adds a completionHandler when calling the subscribe() method: + *
+  * session.subscribe(stream, "subscriber", null, function (error) {
+  *   if (error) {
+  *     console.log(error.message);
+  *   } else {
+  *     console.log("Subscribed to stream: " + stream.id);
+  *   }
+  * });
+  * 
+ * + * @signature subscribe(stream, targetElement, properties, completionHandler) + * @returns {Subscriber} The Subscriber object for this stream. Stream control functions + * are exposed through the Subscriber object. + * @method #subscribe + * @memberOf Session */ this.subscribe = function(stream, targetElement, properties, completionHandler) { - if(typeof targetElement === 'function') { + if (typeof targetElement === 'function') { completionHandler = targetElement; targetElement = undefined; properties = undefined; } - if(typeof properties === 'function') { + if (typeof properties === 'function') { completionHandler = properties; properties = undefined; } @@ -23855,7 +24653,6 @@ OT.Session = function(apiKey, sessionId) { return; } - var subscriber = new OT.Subscriber(targetElement, OT.$.extend(properties || {}, { stream: stream, session: this @@ -23875,7 +24672,7 @@ OT.Session = function(apiKey, sessionId) { dispatchError(errorCode, errorMessage, completionHandler); - } else if (completionHandler && OT.$.isFunction(completionHandler)) { + } else if (completionHandler && OT.$.isFunction(completionHandler)) { completionHandler.apply(null, arguments); } @@ -23887,63 +24684,63 @@ OT.Session = function(apiKey, sessionId) { }; -/** -* Stops subscribing to a stream in the session. the display of the audio-video stream is -* removed from the local web page. -* -*
Example
-*

-* The following code subscribes to other clients' streams. For each stream, the code also -* adds an Unsubscribe link. -*

-*
-* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-* var sessionID = ""; // Replace with your own session ID.
-*                     // See https://dashboard.tokbox.com/projects
-* var streams = [];
-*
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("streamCreated", function(event) {
-*     var stream = event.stream;
-*     displayStream(stream);
-* });
-* session.connect(token);
-*
-* function displayStream(stream) {
-*     var div = document.createElement('div');
-*     div.setAttribute('id', 'stream' + stream.streamId);
-*
-*     var subscriber = session.subscribe(stream, div);
-*     subscribers.push(subscriber);
-*
-*     var aLink = document.createElement('a');
-*     aLink.setAttribute('href', 'javascript: unsubscribe("' + subscriber.id + '")');
-*     aLink.innerHTML = "Unsubscribe";
-*
-*     var streamsContainer = document.getElementById('streamsContainer');
-*     streamsContainer.appendChild(div);
-*     streamsContainer.appendChild(aLink);
-*
-*     streams = event.streams;
-* }
-*
-* function unsubscribe(subscriberId) {
-*     console.log("unsubscribe called");
-*     for (var i = 0; i < subscribers.length; i++) {
-*         var subscriber = subscribers[i];
-*         if (subscriber.id == subscriberId) {
-*             session.unsubscribe(subscriber);
-*         }
-*     }
-* }
-* 
-* -* @param {Subscriber} subscriber The Subscriber object to unsubcribe. -* -* @see subscribe() -* -* @method #unsubscribe -* @memberOf Session + /** + * Stops subscribing to a stream in the session. the display of the audio-video stream is + * removed from the local web page. + * + *
Example
+ *

+ * The following code subscribes to other clients' streams. For each stream, the code also + * adds an Unsubscribe link. + *

+ *
+  * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+  * var sessionID = ""; // Replace with your own session ID.
+  *                     // See https://dashboard.tokbox.com/projects
+  * var streams = [];
+  *
+  * var session = OT.initSession(apiKey, sessionID);
+  * session.on("streamCreated", function(event) {
+  *     var stream = event.stream;
+  *     displayStream(stream);
+  * });
+  * session.connect(token);
+  *
+  * function displayStream(stream) {
+  *     var div = document.createElement('div');
+  *     div.setAttribute('id', 'stream' + stream.streamId);
+  *
+  *     var subscriber = session.subscribe(stream, div);
+  *     subscribers.push(subscriber);
+  *
+  *     var aLink = document.createElement('a');
+  *     aLink.setAttribute('href', 'javascript: unsubscribe("' + subscriber.id + '")');
+  *     aLink.innerHTML = "Unsubscribe";
+  *
+  *     var streamsContainer = document.getElementById('streamsContainer');
+  *     streamsContainer.appendChild(div);
+  *     streamsContainer.appendChild(aLink);
+  *
+  *     streams = event.streams;
+  * }
+  *
+  * function unsubscribe(subscriberId) {
+  *     console.log("unsubscribe called");
+  *     for (var i = 0; i < subscribers.length; i++) {
+  *         var subscriber = subscribers[i];
+  *         if (subscriber.id == subscriberId) {
+  *             session.unsubscribe(subscriber);
+  *         }
+  *     }
+  * }
+  * 
+ * + * @param {Subscriber} subscriber The Subscriber object to unsubcribe. + * + * @see subscribe() + * + * @method #unsubscribe + * @memberOf Session */ this.unsubscribe = function(subscriber) { if (!subscriber) { @@ -23964,38 +24761,38 @@ OT.Session = function(apiKey, sessionId) { return true; }; -/** -* Returns an array of local Subscriber objects for a given stream. -* -* @param {Stream} stream The stream for which you want to find subscribers. -* -* @returns {Array} An array of {@link Subscriber} objects for the specified stream. -* -* @see unsubscribe() -* @see Subscriber -* @see StreamEvent -* @method #getSubscribersForStream -* @memberOf Session + /** + * Returns an array of local Subscriber objects for a given stream. + * + * @param {Stream} stream The stream for which you want to find subscribers. + * + * @returns {Array} An array of {@link Subscriber} objects for the specified stream. + * + * @see unsubscribe() + * @see Subscriber + * @see StreamEvent + * @method #getSubscribersForStream + * @memberOf Session */ this.getSubscribersForStream = function(stream) { return OT.subscribers.where({streamId: stream.id}); }; -/** -* Returns the local Publisher object for a given stream. -* -* @param {Stream} stream The stream for which you want to find the Publisher. -* -* @returns {Publisher} A Publisher object for the specified stream. Returns -* null if there is no local Publisher object -* for the specified stream. -* -* @see forceUnpublish() -* @see Subscriber -* @see StreamEvent -* -* @method #getPublisherForStream -* @memberOf Session + /** + * Returns the local Publisher object for a given stream. + * + * @param {Stream} stream The stream for which you want to find the Publisher. + * + * @returns {Publisher} A Publisher object for the specified stream. Returns + * null if there is no local Publisher object + * for the specified stream. + * + * @see forceUnpublish() + * @see Subscriber + * @see StreamEvent + * + * @method #getPublisherForStream + * @memberOf Session */ this.getPublisherForStream = function(stream) { var streamId, @@ -24093,116 +24890,115 @@ OT.Session = function(apiKey, sessionId) { } }; - -/** -* Sends a signal to each client or a specified client in the session. Specify a -* to property of the signal parameter to limit the signal to -* be sent to a specific client; otherwise the signal is sent to each client connected to -* the session. -*

-* The following example sends a signal of type "foo" with a specified data payload ("hello") -* to all clients connected to the session: -*

-* session.signal({
-*     type: "foo",
-*     data: "hello"
-*   },
-*   function(error) {
-*     if (error) {
-*       console.log("signal error: " + error.message);
-*     } else {
-*       console.log("signal sent");
-*     }
-*   }
-* );
-* 
-*

-* Calling this method without specifying a recipient client (by setting the to -* property of the signal parameter) results in multiple signals sent (one to each -* client in the session). For information on charges for signaling, see the -* OpenTok pricing page. -*

-* The following example sends a signal of type "foo" with a data payload ("hello") to a -* specific client connected to the session: -*

-* session.signal({
-*     type: "foo",
-*     to: recipientConnection; // a Connection object
-*     data: "hello"
-*   },
-*   function(error) {
-*     if (error) {
-*       console.log("signal error: " + error.message);
-*     } else {
-*       console.log("signal sent");
-*     }
-*   }
-* );
-* 
-*

-* Add an event handler for the signal event to listen for all signals sent in -* the session. Add an event handler for the signal:type event to listen for -* signals of a specified type only (replace type, in signal:type, -* with the type of signal to listen for). The Session object dispatches these events. (See -* events.) -* -* @param {Object} signal An object that contains the following properties defining the signal: -*

    -*
  • data — (String) The data to send. The limit to the length of data -* string is 8kB. Do not set the data string to null or -* undefined.
  • -*
  • to — (Connection) A Connection -* object corresponding to the client that the message is to be sent to. If you do not -* specify this property, the signal is sent to all clients connected to the session.
  • -*
  • type — (String) The type of the signal. You can use the type to -* filter signals when setting an event handler for the signal:type event -* (where you replace type with the type string). The maximum length of the -* type string is 128 characters, and it must contain only letters (A-Z and a-z), -* numbers (0-9), '-', '_', and '~'.
  • -* -*
-* -*

Each property is optional. If you set none of the properties, you will send a signal -* with no data or type to each client connected to the session.

-* -* @param {Function} completionHandler A function that is called when sending the signal -* succeeds or fails. This function takes one parameter — error. -* On success, the completionHandler function is not passed any -* arguments. On error, the function is passed an error object, defined by the -* Error class. The error object has the following -* properties: -* -*
    -*
  • code — (Number) An error code, which can be one of the following: -*
-* -* -* -* -* -* -* -* -* -* -* -* -*
400 One of the signal properties — data, type, or to — -* is invalid.
404 The client specified by the to property is not connected to -* the session.
413 The type string exceeds the maximum length (128 bytes), -* or the data string exceeds the maximum size (8 kB).
500 You are not connected to the OpenTok session.
-* -*
  • message — (String) A description of the error.
  • -* -* -*

    Note that the completionHandler success result (error == null) -* indicates that the options passed into the Session.signal() method are valid -* and the signal was sent. It does not indicate that the signal was successfully -* received by any of the intended recipients. -* -* @method #signal -* @memberOf Session -* @see signal and signal:type events + /** + * Sends a signal to each client or a specified client in the session. Specify a + * to property of the signal parameter to limit the signal to + * be sent to a specific client; otherwise the signal is sent to each client connected to + * the session. + *

    + * The following example sends a signal of type "foo" with a specified data payload ("hello") + * to all clients connected to the session: + *

    +  * session.signal({
    +  *     type: "foo",
    +  *     data: "hello"
    +  *   },
    +  *   function(error) {
    +  *     if (error) {
    +  *       console.log("signal error: " + error.message);
    +  *     } else {
    +  *       console.log("signal sent");
    +  *     }
    +  *   }
    +  * );
    +  * 
    + *

    + * Calling this method without specifying a recipient client (by setting the to + * property of the signal parameter) results in multiple signals sent (one to each + * client in the session). For information on charges for signaling, see the + * OpenTok pricing page. + *

    + * The following example sends a signal of type "foo" with a data payload ("hello") to a + * specific client connected to the session: + *

    +  * session.signal({
    +  *     type: "foo",
    +  *     to: recipientConnection; // a Connection object
    +  *     data: "hello"
    +  *   },
    +  *   function(error) {
    +  *     if (error) {
    +  *       console.log("signal error: " + error.message);
    +  *     } else {
    +  *       console.log("signal sent");
    +  *     }
    +  *   }
    +  * );
    +  * 
    + *

    + * Add an event handler for the signal event to listen for all signals sent in + * the session. Add an event handler for the signal:type event to listen for + * signals of a specified type only (replace type, in signal:type, + * with the type of signal to listen for). The Session object dispatches these events. (See + * events.) + * + * @param {Object} signal An object that contains the following properties defining the signal: + *

      + *
    • data — (String) The data to send. The limit to the length of data + * string is 8kB. Do not set the data string to null or + * undefined.
    • + *
    • to — (Connection) A Connection + * object corresponding to the client that the message is to be sent to. If you do not + * specify this property, the signal is sent to all clients connected to the session.
    • + *
    • type — (String) The type of the signal. You can use the type to + * filter signals when setting an event handler for the signal:type event + * (where you replace type with the type string). The maximum length of the + * type string is 128 characters, and it must contain only letters (A-Z and a-z), + * numbers (0-9), '-', '_', and '~'.
    • + * + *
    + * + *

    Each property is optional. If you set none of the properties, you will send a signal + * with no data or type to each client connected to the session.

    + * + * @param {Function} completionHandler A function that is called when sending the signal + * succeeds or fails. This function takes one parameter — error. + * On success, the completionHandler function is not passed any + * arguments. On error, the function is passed an error object, defined by the + * Error class. The error object has the following + * properties: + * + *
      + *
    • code — (Number) An error code, which can be one of the following: + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      400 One of the signal properties — data, type, or to — + * is invalid.
      404 The client specified by the to property is not connected to + * the session.
      413 The type string exceeds the maximum length (128 bytes), + * or the data string exceeds the maximum size (8 kB).
      500 You are not connected to the OpenTok session.
      + *
    • + *
    • message — (String) A description of the error.
    • + *
    + * + *

    Note that the completionHandler success result (error == null) + * indicates that the options passed into the Session.signal() method are valid + * and the signal was sent. It does not indicate that the signal was successfully + * received by any of the intended recipients. + * + * @method #signal + * @memberOf Session + * @see signal and signal:type events */ this.signal = function(options, completion) { var _options = options, @@ -24220,95 +25016,94 @@ OT.Session = function(apiKey, sessionId) { } _socket.signal(_options, _completion, this.logEvent); - if (options && options.data && (typeof(options.data) !== 'string')) { + if (options && options.data && typeof options.data !== 'string') { OT.warn('Signaling of anything other than Strings is deprecated. ' + 'Please update the data property to be a string.'); } }; -/** -* Forces a remote connection to leave the session. -* -*

    -* The forceDisconnect() method is normally used as a moderation tool -* to remove users from an ongoing session. -*

    -*

    -* When a connection is terminated using the forceDisconnect(), -* sessionDisconnected, connectionDestroyed and -* streamDestroyed events are dispatched in the same way as they -* would be if the connection had terminated itself using the disconnect() -* method. However, the reason property of a {@link ConnectionEvent} or -* {@link StreamEvent} object specifies "forceDisconnected" as the reason -* for the destruction of the connection and stream(s). -*

    -*

    -* While you can use the forceDisconnect() method to terminate your own connection, -* calling the disconnect() method is simpler. -*

    -*

    -* The OT object dispatches an exception event if the user's role -* does not include permissions required to force other users to disconnect. -* You define a user's role when you create the user token using the -* generate_token() method using -* OpenTok -* server-side libraries or the -* Dashboard page. -* See ExceptionEvent and OT.on(). -*

    -*

    -* The application throws an error if the session is not connected. -*

    -* -*
    Events dispatched:
    -* -*

    -* connectionDestroyed (ConnectionEvent) — -* On clients other than which had the connection terminated. -*

    -*

    -* exception (ExceptionEvent) — -* The user's role does not allow forcing other user's to disconnect (event.code = -* 1530), -* or the specified stream is not publishing to the session (event.code = 1535). -*

    -*

    -* sessionDisconnected -* (SessionDisconnectEvent) — -* On the client which has the connection terminated. -*

    -*

    -* streamDestroyed (StreamEvent) — -* If streams are stopped as a result of the connection ending. -*

    -* -* @param {Connection} connection The connection to be disconnected from the session. -* This value can either be a Connection object or a connection -* ID (which can be obtained from the connectionId property of the Connection object). -* -* @param {Function} completionHandler (Optional) A function to be called when the call to the -* forceDiscononnect() method succeeds or fails. This function takes one parameter -* — error. On success, the completionHandler function is -* not passed any arguments. On error, the function is passed an error object -* parameter. The error object, defined by the Error -* class, has two properties: code (an integer) -* and message (a string), which identify the cause of the failure. -* Calling forceDisconnect() fails if the role assigned to your -* token is not "moderator"; in this case error.code is set to 1520. The following -* code adds a completionHandler when calling the forceDisconnect() -* method: -*
    -* session.forceDisconnect(connection, function (error) {
    -*   if (error) {
    -*       console.log(error);
    -*     } else {
    -*       console.log("Connection forced to disconnect: " + connection.id);
    -*     }
    -*   });
    -* 
    -* -* @method #forceDisconnect -* @memberOf Session + /** + * Forces a remote connection to leave the session. + * + *

    + * The forceDisconnect() method is normally used as a moderation tool + * to remove users from an ongoing session. + *

    + *

    + * When a connection is terminated using the forceDisconnect(), + * sessionDisconnected, connectionDestroyed and + * streamDestroyed events are dispatched in the same way as they + * would be if the connection had terminated itself using the disconnect() + * method. However, the reason property of a {@link ConnectionEvent} or + * {@link StreamEvent} object specifies "forceDisconnected" as the reason + * for the destruction of the connection and stream(s). + *

    + *

    + * While you can use the forceDisconnect() method to terminate your own connection, + * calling the disconnect() method is simpler. + *

    + *

    + * The OT object dispatches an exception event if the user's role + * does not include permissions required to force other users to disconnect. + * You define a user's role when you create the user token using the + * generate_token() method using one of the + * OpenTok server-side libraries or + * or the Dashboard page. + * See ExceptionEvent and OT.on(). + *

    + *

    + * The application throws an error if the session is not connected. + *

    + * + *
    Events dispatched:
    + * + *

    + * connectionDestroyed (ConnectionEvent) — + * On clients other than which had the connection terminated. + *

    + *

    + * exception (ExceptionEvent) — + * The user's role does not allow forcing other user's to disconnect (event.code = + * 1530), + * or the specified stream is not publishing to the session (event.code = 1535). + *

    + *

    + * sessionDisconnected + * (SessionDisconnectEvent) — + * On the client which has the connection terminated. + *

    + *

    + * streamDestroyed (StreamEvent) — + * If streams are stopped as a result of the connection ending. + *

    + * + * @param {Connection} connection The connection to be disconnected from the session. + * This value can either be a Connection object or a connection + * ID (which can be obtained from the connectionId property of the Connection object). + * + * @param {Function} completionHandler (Optional) A function to be called when the call to the + * forceDiscononnect() method succeeds or fails. This function takes one parameter + * — error. On success, the completionHandler function is + * not passed any arguments. On error, the function is passed an error object + * parameter. The error object, defined by the Error + * class, has two properties: code (an integer) + * and message (a string), which identify the cause of the failure. + * Calling forceDisconnect() fails if the role assigned to your + * token is not "moderator"; in this case error.code is set to 1520. The following + * code adds a completionHandler when calling the forceDisconnect() + * method: + *
    +  * session.forceDisconnect(connection, function (error) {
    +  *   if (error) {
    +  *       console.log(error);
    +  *     } else {
    +  *       console.log("Connection forced to disconnect: " + connection.id);
    +  *     }
    +  *   });
    +  * 
    + * + * @method #forceDisconnect + * @memberOf Session */ this.forceDisconnect = function(connectionOrConnectionId, completionHandler) { @@ -24319,87 +25114,110 @@ OT.Session = function(apiKey, sessionId) { return; } + var connectionId = ( + typeof connectionOrConnectionId === 'string' ? + connectionOrConnectionId : + connectionOrConnectionId.id + ); + + var invalidParameterErrorMsg = ( + 'Invalid Parameter. Check that you have passed valid parameter values into the method call.' + ); + + if (!connectionId) { + dispatchError( + OT.ExceptionCodes.INVALID_PARAMETER, + invalidParameterErrorMsg, + completionHandler + ); + + return; + } + var notPermittedErrorMsg = 'This token does not allow forceDisconnect. ' + 'The role must be at least `moderator` to enable this functionality'; - if (permittedTo('forceDisconnect')) { - var connectionId = typeof connectionOrConnectionId === 'string' ? - connectionOrConnectionId : connectionOrConnectionId.id; - - _socket.forceDisconnect(connectionId, function(err) { - if (err) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT, - notPermittedErrorMsg, completionHandler); - - } else if (completionHandler && OT.$.isFunction(completionHandler)) { - completionHandler.apply(null, arguments); - } - }); - } else { + if (!permittedTo('forceDisconnect')) { // if this throws an error the handleJsException won't occur - dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT, - notPermittedErrorMsg, completionHandler); + dispatchError( + OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT, + notPermittedErrorMsg, + completionHandler + ); + + return; } + + _socket.forceDisconnect(connectionId, function(err) { + if (err) { + dispatchError( + OT.ExceptionCodes.INVALID_PARAMETER, + invalidParameterErrorMsg, + completionHandler + ); + } else if (completionHandler && OT.$.isFunction(completionHandler)) { + completionHandler.apply(null, arguments); + } + }); }; -/** -* Forces the publisher of the specified stream to stop publishing the stream. -* -*

    -* Calling this method causes the Session object to dispatch a streamDestroyed -* event on all clients that are subscribed to the stream (including the client that is -* publishing the stream). The reason property of the StreamEvent object is -* set to "forceUnpublished". -*

    -*

    -* The OT object dispatches an exception event if the user's role -* does not include permissions required to force other users to unpublish. -* You define a user's role when you create the user token using the generate_token() -* method using the -* OpenTok -* server-side libraries or the Dashboard -* page. -* You pass the token string as a parameter of the connect() method of the Session -* object. See ExceptionEvent and -* OT.on(). -*

    -* -*
    Events dispatched:
    -* -*

    -* exception (ExceptionEvent) — -* The user's role does not allow forcing other users to unpublish. -*

    -*

    -* streamDestroyed (StreamEvent) — -* The stream has been unpublished. The Session object dispatches this on all clients -* subscribed to the stream, as well as on the publisher's client. -*

    -* -* @param {Stream} stream The stream to be unpublished. -* -* @param {Function} completionHandler (Optional) A function to be called when the call to the -* forceUnpublish() method succeeds or fails. This function takes one parameter -* — error. On success, the completionHandler function is -* not passed any arguments. On error, the function is passed an error object -* parameter. The error object, defined by the Error -* class, has two properties: code (an integer) -* and message (a string), which identify the cause of the failure. Calling -* forceUnpublish() fails if the role assigned to your token is not "moderator"; -* in this case error.code is set to 1530. The following code adds a -* completionHandler when calling the forceUnpublish() method: -*
    -* session.forceUnpublish(stream, function (error) {
    -*   if (error) {
    -*       console.log(error);
    -*     } else {
    -*       console.log("Connection forced to disconnect: " + connection.id);
    -*     }
    -*   });
    -* 
    -* -* @method #forceUnpublish -* @memberOf Session + /** + * Forces the publisher of the specified stream to stop publishing the stream. + * + *

    + * Calling this method causes the Session object to dispatch a streamDestroyed + * event on all clients that are subscribed to the stream (including the client that is + * publishing the stream). The reason property of the StreamEvent object is + * set to "forceUnpublished". + *

    + *

    + * The OT object dispatches an exception event if the user's role + * does not include permissions required to force other users to unpublish. + * You define a user's role when you create the user token using the generate_token() + * method using the + * OpenTok server-side libraries or the + * Dashboard page. + * You pass the token string as a parameter of the connect() method of the Session + * object. See ExceptionEvent and + * OT.on(). + *

    + * + *
    Events dispatched:
    + * + *

    + * exception (ExceptionEvent) — + * The user's role does not allow forcing other users to unpublish. + *

    + *

    + * streamDestroyed (StreamEvent) — + * The stream has been unpublished. The Session object dispatches this on all clients + * subscribed to the stream, as well as on the publisher's client. + *

    + * + * @param {Stream} stream The stream to be unpublished. + * + * @param {Function} completionHandler (Optional) A function to be called when the call to the + * forceUnpublish() method succeeds or fails. This function takes one parameter + * — error. On success, the completionHandler function is + * not passed any arguments. On error, the function is passed an error object + * parameter. The error object, defined by the Error + * class, has two properties: code (an integer) + * and message (a string), which identify the cause of the failure. Calling + * forceUnpublish() fails if the role assigned to your token is not "moderator"; + * in this case error.code is set to 1530. The following code adds a + * completionHandler when calling the forceUnpublish() method: + *
    +  * session.forceUnpublish(stream, function (error) {
    +  *   if (error) {
    +  *       console.log(error);
    +  *     } else {
    +  *       console.log("Connection forced to disconnect: " + connection.id);
    +  *     }
    +  *   });
    +  * 
    + * + * @method #forceUnpublish + * @memberOf Session */ this.forceUnpublish = function(streamOrStreamId, completionHandler) { if (this.isNot('connected')) { @@ -24431,223 +25249,219 @@ OT.Session = function(apiKey, sessionId) { } }; - this.getStateManager = function() { - OT.warn('Fixme: Have not implemented session.getStateManager'); - }; - this.isConnected = function() { return this.is('connected'); }; this.capabilities = new OT.Capabilities([]); -/** - * Dispatched when an archive recording of the session starts. - * - * @name archiveStarted - * @event - * @memberof Session - * @see ArchiveEvent - * @see Archiving overview. - */ + /** + * Dispatched when an archive recording of the session starts. + * + * @name archiveStarted + * @event + * @memberof Session + * @see ArchiveEvent + * @see Archiving overview. + */ -/** - * Dispatched when an archive recording of the session stops. - * - * @name archiveStopped - * @event - * @memberof Session - * @see ArchiveEvent - * @see Archiving overview. - */ + /** + * Dispatched when an archive recording of the session stops. + * + * @name archiveStopped + * @event + * @memberof Session + * @see ArchiveEvent + * @see Archiving overview. + */ -/** - * Dispatched when a new client (including your own) has connected to the session, and for - * every client in the session when you first connect. (The Session object also dispatches - * a sessionConnected event when your local client connects.) - * - * @name connectionCreated - * @event - * @memberof Session - * @see ConnectionEvent - * @see OT.initSession() - */ + /** + * Dispatched when a new client (including your own) has connected to the session, and for + * every client in the session when you first connect. (The Session object also dispatches + * a sessionConnected event when your local client connects.) + * + * @name connectionCreated + * @event + * @memberof Session + * @see ConnectionEvent + * @see OT.initSession() + */ -/** - * A client, other than your own, has disconnected from the session. - * @name connectionDestroyed - * @event - * @memberof Session - * @see ConnectionEvent - */ + /** + * A client, other than your own, has disconnected from the session. + * @name connectionDestroyed + * @event + * @memberof Session + * @see ConnectionEvent + */ -/** - * The page has connected to an OpenTok session. This event is dispatched asynchronously - * in response to a successful call to the connect() method of a Session - * object. Before calling the connect() method, initialize the session by - * calling the OT.initSession() method. For a code example and more details, - * see Session.connect(). - * @name sessionConnected - * @event - * @memberof Session - * @see SessionConnectEvent - * @see Session.connect() - * @see OT.initSession() - */ + /** + * The page has connected to an OpenTok session. This event is dispatched asynchronously + * in response to a successful call to the connect() method of a Session + * object. Before calling the connect() method, initialize the session by + * calling the OT.initSession() method. For a code example and more details, + * see Session.connect(). + * @name sessionConnected + * @event + * @memberof Session + * @see SessionConnectEvent + * @see Session.connect() + * @see OT.initSession() + */ -/** - * The client has disconnected from the session. This event may be dispatched asynchronously - * in response to a successful call to the disconnect() method of the Session object. - * The event may also be disptached if a session connection is lost inadvertantly, as in the case - * of a lost network connection. - *

    - * The default behavior is that all Subscriber objects are unsubscribed and removed from the - * HTML DOM. Each Subscriber object dispatches a destroyed event when the element is - * removed from the HTML DOM. If you call the preventDefault() method in the event - * listener for the sessionDisconnect event, the default behavior is prevented, and - * you can, optionally, clean up Subscriber objects using your own code. -* - * @name sessionDisconnected - * @event - * @memberof Session - * @see Session.disconnect() - * @see Session.forceDisconnect() - * @see SessionDisconnectEvent - */ + /** + * The client has disconnected from the session. This event may be dispatched asynchronously + * in response to a successful call to the disconnect() method of the Session object. + * The event may also be disptached if a session connection is lost inadvertantly, as in the case + * of a lost network connection. + *

    + * The default behavior is that all Subscriber objects are unsubscribed and removed from the + * HTML DOM. Each Subscriber object dispatches a destroyed event when the element is + * removed from the HTML DOM. If you call the preventDefault() method in the event + * listener for the sessionDisconnect event, the default behavior is prevented, and + * you can, optionally, clean up Subscriber objects using your own code. + * + * @name sessionDisconnected + * @event + * @memberof Session + * @see Session.disconnect() + * @see Session.forceDisconnect() + * @see SessionDisconnectEvent + */ -/** - * A new stream, published by another client, has been created on this session. For streams - * published by your own client, the Publisher object dispatches a streamCreated - * event. For a code example and more details, see {@link StreamEvent}. - * @name streamCreated - * @event - * @memberof Session - * @see StreamEvent - * @see Session.publish() - */ + /** + * A new stream, published by another client, has been created on this session. For streams + * published by your own client, the Publisher object dispatches a streamCreated + * event. For a code example and more details, see {@link StreamEvent}. + * @name streamCreated + * @event + * @memberof Session + * @see StreamEvent + * @see Session.publish() + */ -/** - * A stream from another client has stopped publishing to the session. - *

    - * The default behavior is that all Subscriber objects that are subscribed to the stream are - * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a - * destroyed event when the element is removed from the HTML DOM. If you call the - * preventDefault() method in the event listener for the - * streamDestroyed event, the default behavior is prevented and you can clean up - * Subscriber objects using your own code. See - * Session.getSubscribersForStream(). - *

    - * For streams published by your own client, the Publisher object dispatches a - * streamDestroyed event. - *

    - * For a code example and more details, see {@link StreamEvent}. - * @name streamDestroyed - * @event - * @memberof Session - * @see StreamEvent - */ + /** + * A stream from another client has stopped publishing to the session. + *

    + * The default behavior is that all Subscriber objects that are subscribed to the stream are + * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a + * destroyed event when the element is removed from the HTML DOM. If you call the + * preventDefault() method in the event listener for the + * streamDestroyed event, the default behavior is prevented and you can clean up + * Subscriber objects using your own code. See + * Session.getSubscribersForStream(). + *

    + * For streams published by your own client, the Publisher object dispatches a + * streamDestroyed event. + *

    + * For a code example and more details, see {@link StreamEvent}. + * @name streamDestroyed + * @event + * @memberof Session + * @see StreamEvent + */ -/** - * Defines an event dispatched when property of a stream has changed. This can happen in - * in the following conditions: - *

    - *

      - *
    • A stream has started or stopped publishing audio or video (see - * Publisher.publishAudio() and - * Publisher.publishVideo()). Note - * that a subscriber's video can be disabled or enabled for reasons other than - * the publisher disabling or enabling it. A Subscriber object dispatches - * videoDisabled and videoEnabled events in all - * conditions that cause the subscriber's stream to be disabled or enabled. - *
    • - *
    • The videoDimensions property of the Stream object has - * changed (see Stream.videoDimensions). - *
    • - *
    • The videoType property of the Stream object has changed. - * This can happen in a stream published by a mobile device. (See - * Stream.videoType.) - *
    • - *
    - * - * @name streamPropertyChanged - * @event - * @memberof Session - * @see StreamPropertyChangedEvent - * @see Publisher.publishAudio() - * @see Publisher.publishVideo() - * @see Stream.hasAudio - * @see Stream.hasVideo - * @see Stream.videoDimensions - * @see Subscriber videoDisabled event - * @see Subscriber videoEnabled event - */ + /** + * Defines an event dispatched when property of a stream has changed. This can happen in + * in the following conditions: + *

    + *

      + *
    • A stream has started or stopped publishing audio or video (see + * Publisher.publishAudio() and + * Publisher.publishVideo()). Note + * that a subscriber's video can be disabled or enabled for reasons other than + * the publisher disabling or enabling it. A Subscriber object dispatches + * videoDisabled and videoEnabled events in all + * conditions that cause the subscriber's stream to be disabled or enabled. + *
    • + *
    • The videoDimensions property of the Stream object has + * changed (see Stream.videoDimensions). + *
    • + *
    • The videoType property of the Stream object has changed. + * This can happen in a stream published by a mobile device. (See + * Stream.videoType.) + *
    • + *
    + * + * @name streamPropertyChanged + * @event + * @memberof Session + * @see StreamPropertyChangedEvent + * @see Publisher.publishAudio() + * @see Publisher.publishVideo() + * @see Stream.hasAudio + * @see Stream.hasVideo + * @see Stream.videoDimensions + * @see Subscriber videoDisabled event + * @see Subscriber videoEnabled event + */ -/** - * A signal was received from the session. The SignalEvent - * class defines this event object. It includes the following properties: - *
      - *
    • data — (String) The data string sent with the signal (if there - * is one).
    • - *
    • from — (Connection) The Connection - * corresponding to the client that sent with the signal.
    • - *
    • type — (String) The type assigned to the signal (if there is - * one).
    • - *
    - *

    - * You can register to receive all signals sent in the session, by adding an event handler - * for the signal event. For example, the following code adds an event handler - * to process all signals sent in the session: - *

    - * session.on("signal", function(event) {
    - *   console.log("Signal sent from connection: " + event.from.id);
    - *   console.log("Signal data: " + event.data);
    - * });
    - * 
    - *

    You can register for signals of a specfied type by adding an event handler for the - * signal:type event (replacing type with the actual type string - * to filter on). - * - * @name signal - * @event - * @memberof Session - * @see Session.signal() - * @see SignalEvent - * @see signal:type event - */ + /** + * A signal was received from the session. The SignalEvent + * class defines this event object. It includes the following properties: + *

      + *
    • data — (String) The data string sent with the signal (if there + * is one).
    • + *
    • from — (Connection) The Connection + * corresponding to the client that sent with the signal.
    • + *
    • type — (String) The type assigned to the signal (if there is + * one).
    • + *
    + *

    + * You can register to receive all signals sent in the session, by adding an event handler + * for the signal event. For example, the following code adds an event handler + * to process all signals sent in the session: + *

    +   * session.on("signal", function(event) {
    +   *   console.log("Signal sent from connection: " + event.from.id);
    +   *   console.log("Signal data: " + event.data);
    +   * });
    +   * 
    + *

    You can register for signals of a specfied type by adding an event handler for the + * signal:type event (replacing type with the actual type string + * to filter on). + * + * @name signal + * @event + * @memberof Session + * @see Session.signal() + * @see SignalEvent + * @see signal:type event + */ -/** - * A signal of the specified type was received from the session. The - * SignalEvent class defines this event object. - * It includes the following properties: - *

      - *
    • data — (String) The data string sent with the signal.
    • - *
    • from — (Connection) The Connection - * corresponding to the client that sent with the signal.
    • - *
    • type — (String) The type assigned to the signal (if there is one). - *
    • - *
    - *

    - * You can register for signals of a specfied type by adding an event handler for the - * signal:type event (replacing type with the actual type string - * to filter on). For example, the following code adds an event handler for signals of - * type "foo": - *

    - * session.on("signal:foo", function(event) {
    - *   console.log("foo signal sent from connection " + event.from.id);
    - *   console.log("Signal data: " + event.data);
    - * });
    - * 
    - *

    - * You can register to receive all signals sent in the session, by adding an event - * handler for the signal event. - * - * @name signal:type - * @event - * @memberof Session - * @see Session.signal() - * @see SignalEvent - * @see signal event - */ + /** + * A signal of the specified type was received from the session. The + * SignalEvent class defines this event object. + * It includes the following properties: + *

      + *
    • data — (String) The data string sent with the signal.
    • + *
    • from — (Connection) The Connection + * corresponding to the client that sent with the signal.
    • + *
    • type — (String) The type assigned to the signal (if there is one). + *
    • + *
    + *

    + * You can register for signals of a specfied type by adding an event handler for the + * signal:type event (replacing type with the actual type string + * to filter on). For example, the following code adds an event handler for signals of + * type "foo": + *

    +   * session.on("signal:foo", function(event) {
    +   *   console.log("foo signal sent from connection " + event.from.id);
    +   *   console.log("Signal data: " + event.data);
    +   * });
    +   * 
    + *

    + * You can register to receive all signals sent in the session, by adding an event + * handler for the signal event. + * + * @name signal:type + * @event + * @memberof Session + * @see Session.signal() + * @see SignalEvent + * @see signal event + */ }; // tb_require('../helpers/helpers.js') @@ -24751,7 +25565,7 @@ OT.Publisher = function(options) { _audioLevelMeter, _properties, _validResolutions, - _validFrameRates = [ 1, 7, 15, 30 ], + _validFrameRates = [1, 7, 15, 30], _prevStats, _state, _iceServers, @@ -24765,6 +25579,7 @@ OT.Publisher = function(options) { options.videoSource === 'application' ), _connectivityAttemptPinger, + _enableSimulcast, _publisher = this; _properties = OT.$.defaults(options || {}, { @@ -24783,21 +25598,13 @@ OT.Publisher = function(options) { '1280x720': {width: 1280, height: 720} }; - if (_isScreenSharing) { - if (window.location.protocol !== 'https:') { - OT.warn('Screen Sharing typically requires pages to be loadever over HTTPS - unless this ' + - 'browser is configured locally to allow non-SSL pages, permission will be denied ' + - 'without user input.'); - } - } - _prevStats = { - 'timeStamp' : OT.$.now() + timeStamp: OT.$.now() }; OT.$.eventing(this); - if(!_isScreenSharing && _audioLevelCapable) { + if (!_isScreenSharing && _audioLevelCapable) { _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext()); var audioLevelRunner = new OT.IntervalRunner(function() { @@ -24829,10 +25636,10 @@ OT.Publisher = function(options) { action: action, variation: variation, payload: payload, - 'sessionId': _session ? _session.sessionId : null, - 'connectionId': _session && + sessionId: _session ? _session.sessionId : null, + connectionId: _session && _session.isConnected() ? _session.connection.connectionId : null, - 'partnerId': _session ? _session.apiKey : OT.APIKEY, + partnerId: _session ? _session.apiKey : OT.APIKEY, streamId: _streamId }, throttle); }, @@ -24841,10 +25648,10 @@ OT.Publisher = function(options) { if (variation === 'Attempt' || !_connectivityAttemptPinger) { _connectivityAttemptPinger = new OT.ConnectivityAttemptPinger({ action: 'Publish', - 'sessionId': _session ? _session.sessionId : null, - 'connectionId': _session && + sessionId: _session ? _session.sessionId : null, + connectionId: _session && _session.isConnected() ? _session.connection.connectionId : null, - 'partnerId': _session ? _session.apiKey : OT.APIKEY, + partnerId: _session ? _session.apiKey : OT.APIKEY, streamId: _streamId }); } @@ -24872,12 +25679,41 @@ OT.Publisher = function(options) { mediaServerName: _session ? _session.sessionInfo.messagingServer : null, p2pFlag: _session ? _session.sessionInfo.p2pEnabled : false, duration: _publishStartTime ? new Date().getTime() - _publishStartTime.getTime() : 0, - remoteConnectionId: connection.id + remoteConnectionId: connection.id, + scalableVideo: !!_enableSimulcast }; - OT.analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) ); + OT.analytics.logQOS(OT.$.extend(QoSBlob, parsedStats)); this.trigger('qos', parsedStats); }, this), + // Returns the video dimensions. Which could either be the ones that + // the developer specific in the videoDimensions property, or just + // whatever the video element reports. + // + // If all else fails then we'll just default to 640x480 + // + getVideoDimensions = function() { + var streamWidth; + var streamHeight; + + // We set the streamWidth and streamHeight to be the minimum of the requested + // resolution and the actual resolution. + if (_properties.videoDimensions) { + streamWidth = Math.min(_properties.videoDimensions.width, + _targetElement.videoWidth() || 640); + streamHeight = Math.min(_properties.videoDimensions.height, + _targetElement.videoHeight() || 480); + } else { + streamWidth = _targetElement.videoWidth() || 640; + streamHeight = _targetElement.videoHeight() || 480; + } + + return { + width: streamWidth, + height: streamHeight + }; + }, + /// Private Events stateChangeFailed = function(changeFailed) { @@ -24939,7 +25775,6 @@ OT.Publisher = function(options) { this.publishVideo(_properties.publishVideo && _webRTCStream.getVideoTracks().length > 0); - this.accessAllowed = true; this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_ALLOWED, false)); @@ -24959,7 +25794,7 @@ OT.Publisher = function(options) { onLoaded(); }); - if(_audioLevelSampler && _webRTCStream.getAudioTracks().length > 0) { + if (_audioLevelSampler && _webRTCStream.getAudioTracks().length > 0) { _audioLevelSampler.webRTCStream(_webRTCStream); } @@ -25005,6 +25840,17 @@ OT.Publisher = function(options) { // The user has clicked the 'deny' button the the allow access dialog // (or it's set to always deny) onAccessDenied = OT.$.bind(function(error) { + if (_isScreenSharing) { + if (window.location.protocol !== 'https:') { + /** + * in http:// the browser will deny permission without asking the + * user. There is also no way to tell if it was denied by the + * user, or prevented from the browser. + */ + error.message += ' Note: https:// is required for screen sharing.'; + } + } + OT.error('OT.Publisher.onStreamAvailableError Permission Denied'); _state.set('Failed'); @@ -25032,7 +25878,7 @@ OT.Publisher = function(options) { onAccessDialogClosed = OT.$.bind(function() { logAnalyticsEvent('accessDialog', 'Closed'); - this.dispatchEvent( new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false)); + this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false)); }, this), onVideoError = OT.$.bind(function(errorCode, errorReason) { @@ -25119,7 +25965,7 @@ OT.Publisher = function(options) { this.dispatchEvent(new OT.StreamEvent('streamCreated', stream, null, false)); var payload = { - streamType: 'WebRTC', + streamType: 'WebRTC' }; logConnectivityEvent('Success', payload); }, this), @@ -25129,12 +25975,22 @@ OT.Publisher = function(options) { if (_webRTCStream) { // Stop revokes our access cam and mic access for this instance // of localMediaStream. - _webRTCStream.stop(); + if (_webRTCStream.stop) { + // Older spec + _webRTCStream.stop(); + } else { + // Newer spec + var tracks = _webRTCStream.getAudioTracks().concat(_webRTCStream.getVideoTracks()); + tracks.forEach(function(track) { + track.stop(); + }); + } + _webRTCStream = null; } }, - createPeerConnectionForRemote = OT.$.bind(function(remoteConnection) { + createPeerConnectionForRemote = OT.$.bind(function(remoteConnection, completion) { var peerConnection = _peerConnections[remoteConnection.id]; if (!peerConnection) { @@ -25146,12 +26002,42 @@ OT.Publisher = function(options) { remoteConnection.on('destroyed', OT.$.bind(this.cleanupSubscriber, this, remoteConnection.id)); + // Calculate the number of streams to use. 1 for normal, >1 for Simulcast + var numberOfSimulcastStreams = 1; + + _enableSimulcast = false; + if (OT.$.env.name === 'Chrome' && !_isScreenSharing && + !_session.sessionInfo.p2pEnabled && + _properties.constraints.video) { + // We only support simulcast on Chrome, and when not using + // screensharing. + _enableSimulcast = _session.sessionInfo.simulcast; + + if (_enableSimulcast === void 0) { + // If there is no session wide preference then allow the + // developer to choose. + _enableSimulcast = options && options._enableSimulcast; + } + } + + if (_enableSimulcast) { + var streamDimensions = getVideoDimensions(); + // HD and above gets three streams. Otherwise they get 2. + if (streamDimensions.width > 640 && + streamDimensions.height > 480) { + numberOfSimulcastStreams = 3; + } else { + numberOfSimulcastStreams = 2; + } + } + peerConnection = _peerConnections[remoteConnection.id] = new OT.PublisherPeerConnection( remoteConnection, _session, _streamId, _webRTCStream, - _properties.channels + _properties.channels, + numberOfSimulcastStreams ); peerConnection.on({ @@ -25167,10 +26053,12 @@ OT.Publisher = function(options) { qos: recordQOS }, this); - peerConnection.init(_iceServers); + peerConnection.init(_iceServers, completion); + } else { + OT.$.callAsync(function() { + completion(undefined, peerConnection); + }); } - - return peerConnection; }, this), /// Chrome @@ -25193,14 +26081,14 @@ OT.Publisher = function(options) { updateChromeForStyleChange = function(key, value) { if (!_chrome) return; - switch(key) { + switch (key) { case 'nameDisplayMode': _chrome.name.setDisplayMode(value); _chrome.backingBar.setNameMode(value); break; case 'showArchiveStatus': - logAnalyticsEvent('showArchiveStatus', 'styleChange', {mode: value ? 'on': 'off'}); + logAnalyticsEvent('showArchiveStatus', 'styleChange', {mode: value ? 'on' : 'off'}); _chrome.archive.setShowArchiveStatus(value); break; @@ -25220,7 +26108,7 @@ OT.Publisher = function(options) { createChrome = function() { - if(!this.getStyle('showArchiveStatus')) { + if (!this.getStyle('showArchiveStatus')) { logAnalyticsEvent('showArchiveStatus', 'createChrome', {mode: 'off'}); } @@ -25273,9 +26161,7 @@ OT.Publisher = function(options) { unmuted: OT.$.bind(this.publishAudio, this, true) }); - if(_audioLevelMeter && this.getStyle('audioLevelDisplayMode') === 'auto') { - _audioLevelMeter[_widgetView.audioOnly() ? 'show' : 'hide'](); - } + updateAudioLevelMeterDisplay(); }, reset = OT.$.bind(function() { @@ -25319,25 +26205,35 @@ OT.Publisher = function(options) { buttonDisplayMode: 'auto', audioLevelDisplayMode: _isScreenSharing ? 'off' : 'auto', backgroundImageURI: null - }, _properties.showControls, function (payload) { + }, _properties.showControls, function(payload) { logAnalyticsEvent('SetStyle', 'Publisher', payload, 0.1); }); + var updateAudioLevelMeterDisplay = function() { + if (_audioLevelMeter && _publisher.getStyle('audioLevelDisplayMode') === 'auto') { + if (!_properties.publishVideo && _properties.publishAudio) { + _audioLevelMeter.show(); + } else { + _audioLevelMeter.hide(); + } + } + }; + var setAudioOnly = function(audioOnly) { if (_widgetView) { _widgetView.audioOnly(audioOnly); _widgetView.showPoster(audioOnly); } - if (_audioLevelMeter && _publisher.getStyle('audioLevelDisplayMode') === 'auto') { - _audioLevelMeter[audioOnly ? 'show' : 'hide'](); - } + updateAudioLevelMeterDisplay(); }; this.publish = function(targetElement) { OT.debug('OT.Publisher: publish'); - if ( _state.isAttemptingToPublish() || _state.isPublishing() ) reset(); + if (_state.isAttemptingToPublish() || _state.isPublishing()) { + reset(); + } _state.set('GetUserMedia'); if (!_properties.constraints) { @@ -25351,12 +26247,12 @@ OT.Publisher = function(options) { _properties.audioSource = null; } - if(_properties.audioSource === null || _properties.audioSource === false) { + if (_properties.audioSource === null || _properties.audioSource === false) { _properties.constraints.audio = false; _properties.publishAudio = false; } else { - if(typeof _properties.audioSource === 'object') { - if(_properties.audioSource.deviceId != null) { + if (typeof _properties.audioSource === 'object') { + if (_properties.audioSource.deviceId != null) { _properties.audioSource = _properties.audioSource.deviceId; } else { OT.warn('Invalid audioSource passed to Publisher. Expected either a device ID'); @@ -25378,12 +26274,12 @@ OT.Publisher = function(options) { } } - if(_properties.videoSource === null || _properties.videoSource === false) { + if (_properties.videoSource === null || _properties.videoSource === false) { _properties.constraints.video = false; _properties.publishVideo = false; } else { - if(typeof _properties.videoSource === 'object' && + if (typeof _properties.videoSource === 'object' && _properties.videoSource.deviceId == null) { OT.warn('Invalid videoSource passed to Publisher. Expected either a device ' + 'ID or device.'); @@ -25407,12 +26303,13 @@ OT.Publisher = function(options) { var mandatory = _properties.constraints.video.mandatory; - if(_isScreenSharing) { - // this is handled by the extension helpers - } else if(_properties.videoSource.deviceId != null) { - mandatory.sourceId = _properties.videoSource.deviceId; - } else { - mandatory.sourceId = _properties.videoSource; + // _isScreenSharing is handled by the extension helpers + if (!_isScreenSharing) { + if (_properties.videoSource.deviceId != null) { + mandatory.sourceId = _properties.videoSource.deviceId; + } else { + mandatory.sourceId = _properties.videoSource; + } } } @@ -25432,9 +26329,8 @@ OT.Publisher = function(options) { {minHeight: _properties.videoDimensions.height}, {maxHeight: _properties.videoDimensions.height} ]); - } else { - // This is not supported } + // We do not support this in Firefox yet } } @@ -25459,9 +26355,8 @@ OT.Publisher = function(options) { _properties.videoDimensions.width; _properties.constraints.video.mandatory.maxHeight = _properties.videoDimensions.height; - } else { - // This is not suppoted } + // We do not support this in Firefox yet } if (_properties.frameRate !== void 0 && @@ -25514,7 +26409,7 @@ OT.Publisher = function(options) { var event = new OT.MediaStoppedEvent(_publisher); _publisher.dispatchEvent(event, function() { - if(!event.isDefaultPrevented()) { + if (!event.isDefaultPrevented()) { if (_session) { _publisher._.unpublishFromSession(_session, 'mediaStopped'); } else { @@ -25534,12 +26429,12 @@ OT.Publisher = function(options) { ); } else if (response.extensionRegistered === false) { onScreenSharingError( - new Error('Screen Sharing suppor in this browser requires an extension, but ' + + new Error('Screen Sharing support in this browser requires an extension, but ' + 'one has not been registered.') ); } else if (response.extensionInstalled === false) { onScreenSharingError( - new Error('Screen Sharing suppor in this browser requires an extension, but ' + + new Error('Screen Sharing support in this browser requires an extension, but ' + 'the extension is not installed.') ); } else { @@ -25570,11 +26465,11 @@ OT.Publisher = function(options) { }); } else { OT.$.shouldAskForDevices(function(devices) { - if(!devices.video) { + if (!devices.video) { OT.warn('Setting video constraint to false, there are no video sources'); _properties.constraints.video = false; } - if(!devices.audio) { + if (!devices.audio) { OT.warn('Setting audio constraint to false, there are no audio sources'); _properties.constraints.audio = false; } @@ -25606,19 +26501,19 @@ OT.Publisher = function(options) { return this; }; -/** -* Starts publishing audio (if it is currently not being published) -* when the value is true; stops publishing audio -* (if it is currently being published) when the value is false. -* -* @param {Boolean} value Whether to start publishing audio (true) -* or not (false). -* -* @see OT.initPublisher() -* @see Stream.hasAudio -* @see StreamPropertyChangedEvent -* @method #publishAudio -* @memberOf Publisher + /** + * Starts publishing audio (if it is currently not being published) + * when the value is true; stops publishing audio + * (if it is currently being published) when the value is false. + * + * @param {Boolean} value Whether to start publishing audio (true) + * or not (false). + * + * @see OT.initPublisher() + * @see Stream.hasAudio + * @see StreamPropertyChangedEvent + * @method #publishAudio + * @memberOf Publisher */ this.publishAudio = function(value) { _properties.publishAudio = value; @@ -25635,23 +26530,24 @@ OT.Publisher = function(options) { _stream.setChannelActiveState('audio', value); } + updateAudioLevelMeterDisplay(); + return this; }; - -/** -* Starts publishing video (if it is currently not being published) -* when the value is true; stops publishing video -* (if it is currently being published) when the value is false. -* -* @param {Boolean} value Whether to start publishing video (true) -* or not (false). -* -* @see OT.initPublisher() -* @see Stream.hasVideo -* @see StreamPropertyChangedEvent -* @method #publishVideo -* @memberOf Publisher + /** + * Starts publishing video (if it is currently not being published) + * when the value is true; stops publishing video + * (if it is currently being published) when the value is false. + * + * @param {Boolean} value Whether to start publishing video (true) + * or not (false). + * + * @see OT.initPublisher() + * @see Stream.hasVideo + * @see StreamPropertyChangedEvent + * @method #publishVideo + * @memberOf Publisher */ this.publishVideo = function(value) { var oldValue = _properties.publishVideo; @@ -25666,7 +26562,7 @@ OT.Publisher = function(options) { // the value of publishVideo at this point. This will be tidied up shortly. if (_webRTCStream) { var videoTracks = _webRTCStream.getVideoTracks(); - for (var i=0, num=videoTracks.length; iaccessAllowed property which indicates whether the user -* has granted access to the camera and microphone. -* @see Event -* @name accessAllowed -* @event -* @memberof Publisher + /** + * Dispatched when the user has clicked the Allow button, granting the + * app access to the camera and microphone. The Publisher object has an + * accessAllowed property which indicates whether the user + * has granted access to the camera and microphone. + * @see Event + * @name accessAllowed + * @event + * @memberof Publisher */ -/** -* Dispatched when the user has clicked the Deny button, preventing the -* app from having access to the camera and microphone. -* @see Event -* @name accessDenied -* @event -* @memberof Publisher + /** + * Dispatched when the user has clicked the Deny button, preventing the + * app from having access to the camera and microphone. + * @see Event + * @name accessDenied + * @event + * @memberof Publisher */ -/** -* Dispatched when the Allow/Deny dialog box is opened. (This is the dialog box in which -* the user can grant the app access to the camera and microphone.) -* @see Event -* @name accessDialogOpened -* @event -* @memberof Publisher + /** + * Dispatched when the Allow/Deny dialog box is opened. (This is the dialog box in which + * the user can grant the app access to the camera and microphone.) + * @see Event + * @name accessDialogOpened + * @event + * @memberof Publisher */ -/** -* Dispatched when the Allow/Deny box is closed. (This is the dialog box in which the -* user can grant the app access to the camera and microphone.) -* @see Event -* @name accessDialogClosed -* @event -* @memberof Publisher + /** + * Dispatched when the Allow/Deny box is closed. (This is the dialog box in which the + * user can grant the app access to the camera and microphone.) + * @see Event + * @name accessDialogClosed + * @event + * @memberof Publisher */ /** @@ -26197,90 +27095,58 @@ OT.Publisher = function(options) { * @see AudioLevelUpdatedEvent */ -/** - * The publisher has started streaming to the session. - * @name streamCreated - * @event - * @memberof Publisher - * @see StreamEvent - * @see Session.publish() - */ + /** + * The publisher has started streaming to the session. + * @name streamCreated + * @event + * @memberof Publisher + * @see StreamEvent + * @see Session.publish() + */ -/** - * The publisher has stopped streaming to the session. The default behavior is that - * the Publisher object is removed from the HTML DOM). The Publisher object dispatches a - * destroyed event when the element is removed from the HTML DOM. If you call the - * preventDefault() method of the event object in the event listener, the default - * behavior is prevented, and you can, optionally, retain the Publisher for reuse or clean it up - * using your own code. - * @name streamDestroyed - * @event - * @memberof Publisher - * @see StreamEvent - */ + /** + * The publisher has stopped streaming to the session. The default behavior is that + * the Publisher object is removed from the HTML DOM). The Publisher object dispatches a + * destroyed event when the element is removed from the HTML DOM. If you call the + * preventDefault() method of the event object in the event listener, the default + * behavior is prevented, and you can, optionally, retain the Publisher for reuse or clean it up + * using your own code. + * @name streamDestroyed + * @event + * @memberof Publisher + * @see StreamEvent + */ -/** -* Dispatched when the Publisher element is removed from the HTML DOM. When this event -* is dispatched, you may choose to adjust or remove HTML DOM elements related to the publisher. -* @name destroyed -* @event -* @memberof Publisher + /** + * Dispatched when the Publisher element is removed from the HTML DOM. When this event + * is dispatched, you may choose to adjust or remove HTML DOM elements related to the publisher. + * @name destroyed + * @event + * @memberof Publisher */ -/** -* Dispatched when the video dimensions of the video change. This can only occur in when the -* stream.videoType property is set to "screen" (for a screen-sharing -* video stream), and the user resizes the window being captured. -* @name videoDimensionsChanged -* @event -* @memberof Publisher + /** + * Dispatched when the video dimensions of the video change. This can only occur in when the + * stream.videoType property is set to "screen" (for a screen-sharing + * video stream), and the user resizes the window being captured. + * @name videoDimensionsChanged + * @event + * @memberof Publisher */ -/** - * The user has stopped screen-sharing for the published stream. This event is only dispatched - * for screen-sharing video streams. - * @name mediaStopped - * @event - * @memberof Publisher - * @see StreamEvent - */ + /** + * The user has stopped screen-sharing for the published stream. This event is only dispatched + * for screen-sharing video streams. + * @name mediaStopped + * @event + * @memberof Publisher + * @see StreamEvent + */ }; // Helper function to generate unique publisher ids OT.Publisher.nextId = OT.$.uuid; -// tb_require('./helpers/lib/css_loader.js') -// tb_require('./ot/system_requirements.js') -// tb_require('./ot/session.js') -// tb_require('./ot/publisher.js') -// tb_require('./ot/subscriber.js') -// tb_require('./ot/archive.js') -// tb_require('./ot/connection.js') -// tb_require('./ot/stream.js') -// We want this to be included at the end, just before footer.js - -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global loadCSS, define */ - -// Tidy up everything on unload -OT.onUnload(function() { - OT.publishers.destroy(); - OT.subscribers.destroy(); - OT.sessions.destroy('unloaded'); -}); - -loadCSS(OT.properties.cssURL); - -// Register as a named AMD module, since TokBox could be concatenated with other -// files that may use define, but not via a proper concatenation script that -// understands anonymous AMD modules. A named AMD is safest and most robust -// way to register. Uppercase TB is used because AMD module names are -// derived from file names, and OpenTok is normally delivered in an uppercase -// file name. -if (typeof define === 'function' && define.amd) { - define( 'TB', [], function () { return TB; } ); -} // tb_require('../../conf/properties.js') // tb_require('../ot.js') // tb_require('./session.js') @@ -26290,7 +27156,6 @@ if (typeof define === 'function' && define.amd) { trailing: true, browser: true, smarttabs:true */ /* global OT */ - /** * The first step in using the OpenTok API is to call the OT.initSession() * method. Other methods of the OT object check for system requirements and set up error logging. @@ -26319,13 +27184,14 @@ if (typeof define === 'function' && define.amd) { * @param {String} apiKey Your OpenTok API key (see the * OpenTok dashboard). * @param {String} sessionId The session ID identifying the OpenTok session. For more -* information, see Session creation. +* information, see Session +* creation. * @returns {Session} The session object through which all further interactions with * the session will occur. */ OT.initSession = function(apiKey, sessionId) { - if(sessionId == null) { + if (sessionId == null) { sessionId = apiKey; apiKey = null; } @@ -26444,16 +27310,21 @@ OT.initSession = function(apiKey, sessionId) { *

    * *
  • -* height (Number) — The desired height, in pixels, of the -* displayed Publisher video stream (default: 198). Note: Use the -* height and width properties to set the dimensions -* of the publisher video; do not set the height and width of the DOM element -* (using CSS). +* height (Number) — The desired initial height of the displayed Publisher +* video in the HTML page (default: 198 pixels). You can specify the number of pixels as either +* a number (such as 300) or a string ending in "px" (such as "300px"). Or you can specify a +* percentage of the size of the parent element, with a string ending in "%" (such as "100%"). +* Note: To resize the publisher video, adjust the CSS of the publisher's DOM element +* (the element property of the Publisher object) or (if the height is specified as +* a percentage) its parent DOM element (see +* Resizing +* or repositioning a video). *
  • *
  • * insertMode (String) — Specifies how the Publisher object will be * inserted in the HTML DOM. See the targetElement parameter. This string can * have the following values: +*

    *

      *
    • "replace" — The Publisher object replaces contents of the * targetElement. This is the default.
    • @@ -26466,7 +27337,12 @@ OT.initSession = function(apiKey, sessionId) { *
    • "append" — The Publisher object is a new element added as a child * of the targetElement. If there are other child elements, the Publisher is appended as * the last child element of the targetElement.
    • -*
    +*

    +*

    Do not move the publisher element or its parent elements in the DOM +* heirarchy. Use CSS to resize or reposition the publisher video's element +* (the element property of the Publisher object) or its parent element (see +* Resizing +* or repositioning a video.

    *
  • *
  • * maxResolution (Object) — Sets the maximum resoultion to stream. @@ -26550,7 +27426,7 @@ OT.initSession = function(apiKey, sessionId) { * video is disabled), "off" (the indicator is not displayed), and * "on" (the indicator is always displayed).
  • * -*
  • backgroundImageURI (String) — A URI for an image to display as +*

  • backgroundImageURI (String) — A URI for an image to display as * the background image when a video is not displayed. (A video may not be displayed if * you call publishVideo(false) on the Publisher object). You can pass an http * or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the @@ -26560,14 +27436,7 @@ OT.initSession = function(apiKey, sessionId) { * you could set the property to "data:VBORw0KGgoAA...", where the portion of the * string after "data:" is the result of a call to * Publisher.getImgData(). If the URL or the image data is invalid, the property -* is ignored (the attempt to set the image fails silently). -*

    -* Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), -* you cannot set the backgroundImageURI style to a string larger than 32 kB. -* This is due to an IE 8 limitation on the size of URI strings. Due to this limitation, -* you cannot set the backgroundImageURI style to a string obtained with the -* getImgData() method. -*

  • +* is ignored (the attempt to set the image fails silently). * *
  • buttonDisplayMode (String) — How to display the microphone controls * Possible values are: "auto" (controls are displayed when the stream is first @@ -26604,11 +27473,15 @@ OT.initSession = function(apiKey, sessionId) { * subscribers to the stream is "fit". *
  • *
  • -* width (Number) — The desired width, in pixels, of the -* displayed Publisher video stream (default: 264). Note: Use the -* height and width properties to set the dimensions -* of the publisher video; do not set the height and width of the DOM element -* (using CSS). +* width (Number) — The desired initial width of the displayed Publisher +* video in the HTML page (default: 264 pixels). You can specify the number of pixels as either +* a number (such as 400) or a string ending in "px" (such as "400px"). Or you can specify a +* percentage of the size of the parent element, with a string ending in "%" (such as "100%"). +* Note: To resize the publisher video, adjust the CSS of the publisher's DOM element +* (the element property of the Publisher object) or (if the width is specified as +* a percentage) its parent DOM element (see +* Resizing +* or repositioning a video). *
  • * * @param {Function} completionHandler (Optional) A function to be called when the method succeeds @@ -26640,28 +27513,27 @@ OT.initSession = function(apiKey, sessionId) { * @memberof OT */ OT.initPublisher = function(targetElement, properties, completionHandler) { - OT.debug('OT.initPublisher('+targetElement+')'); + OT.debug('OT.initPublisher(' + targetElement + ')'); // To support legacy (apikey, targetElement, properties) users // we check to see if targetElement is actually an apikey. Which we ignore. - if(typeof targetElement === 'string' && !document.getElementById(targetElement)) { + if (typeof targetElement === 'string' && !document.getElementById(targetElement)) { targetElement = properties; properties = completionHandler; completionHandler = arguments[3]; } - if(typeof targetElement === 'function') { + if (typeof targetElement === 'function') { completionHandler = targetElement; properties = undefined; targetElement = undefined; - } - else if (OT.$.isObject(targetElement) && !(OT.$.isElementNode(targetElement))) { + } else if (OT.$.isObject(targetElement) && !(OT.$.isElementNode(targetElement))) { completionHandler = properties; properties = targetElement; targetElement = undefined; } - if(typeof properties === 'function') { + if (typeof properties === 'function') { completionHandler = properties; properties = undefined; } @@ -26669,20 +27541,18 @@ OT.initPublisher = function(targetElement, properties, completionHandler) { var publisher = new OT.Publisher(properties); OT.publishers.add(publisher); - var triggerCallback = function triggerCallback (err) { - if (err) { - OT.dispatchError(err.code, err.message, completionHandler, publisher.session); - } else if (completionHandler && OT.$.isFunction(completionHandler)) { + var triggerCallback = function triggerCallback() { + if (completionHandler && OT.$.isFunction(completionHandler)) { completionHandler.apply(null, arguments); } }, - removeInitSuccessAndCallComplete = function removeInitSuccessAndCallComplete (err) { + removeInitSuccessAndCallComplete = function removeInitSuccessAndCallComplete(err) { publisher.off('publishComplete', removeHandlersAndCallComplete); triggerCallback(err); }, - removeHandlersAndCallComplete = function removeHandlersAndCallComplete (err) { + removeHandlersAndCallComplete = function removeHandlersAndCallComplete(err) { publisher.off('initSuccess', removeInitSuccessAndCallComplete); // We're only handling the error case here as we're just @@ -26690,7 +27560,6 @@ OT.initPublisher = function(targetElement, properties, completionHandler) { if (err) triggerCallback(err); }; - publisher.once('initSuccess', removeInitSuccessAndCallComplete); publisher.once('publishComplete', removeHandlersAndCallComplete); @@ -26699,7 +27568,6 @@ OT.initPublisher = function(targetElement, properties, completionHandler) { return publisher; }; - /** * Enumerates the audio input devices (such as microphones) and video input devices * (cameras) available to the browser. @@ -26707,8 +27575,8 @@ OT.initPublisher = function(targetElement, properties, completionHandler) { * The array of devices is passed in as the devices parameter of * the callback function passed into the method. *

    - * This method is only available in Chrome. In other browsers, the callback function is - * passed an error object. + * This method is only available in Chrome and Internet Explorer. In Firefox, the callback function + * is passed an error object. * * @param callback {Function} The callback function invoked when the list of devices * devices is available. This function takes two parameters: @@ -26746,15 +27614,65 @@ OT.getDevices = function(callback) { OT.$.getMediaDevices(callback); }; +/** +* Report that your app experienced an issue. You can use the issue ID with +* Inspector or when discussing +* an issue with the TokBox support team. +* +* @param completionHandler {Function} A function that is called when the call to this method +* succeeds or fails. This function has two parameters. The first parameter is an +* Error object that is set when the call to the reportIssue() +* method fails (for example, if the client is not connected to the network) or null +* when the call to the reportIssue() method succeeds. The second parameter is set to +* the report ID (a unique string) when the call succeeds. +* +* @method OT.reportIssue +* @memberof OT +*/ +OT.reportIssue = function(completionHandler) { + var reportIssueId = OT.$.uuid(); + var sessionCount = OT.sessions.length(); + var completedLogEventCount = 0; + var errorReported = false; + function logEventCompletionHandler(error) { + if (error) { + if (completionHandler && !errorReported) { + var reportIssueError = new OT.Error(OT.ExceptionCodes.REPORT_ISSUE_ERROR, + 'Error calling OT.reportIssue(). Check the client\'s network connection.'); + completionHandler(reportIssueError); + errorReported = true; + } + } else { + completedLogEventCount++; + if (completedLogEventCount >= sessionCount && completionHandler && !errorReported) { + completionHandler(null, reportIssueId); + } + } + } -OT.reportIssue = function(){ - OT.warn('ToDo: haven\'t yet implemented OT.reportIssue'); + var eventOptions = { + action: 'ReportIssue', + payload: { + reportIssueId: reportIssueId + } + }; + + if (sessionCount === 0) { + OT.analytics.logEvent(eventOptions, null, logEventCompletionHandler); + } else { + OT.sessions.forEach(function(session) { + var individualSessionEventOptions = OT.$.extend({ + sessionId: session.sessionId, + partnerId: session.isConnected() ? session.sessionInfo.partnerId : null + }, eventOptions); + OT.analytics.logEvent(individualSessionEventOptions, null, logEventCompletionHandler); + }); + } }; OT.components = {}; - /** * This method is deprecated. Use on() or once() instead. * @@ -26813,7 +27731,6 @@ OT.components = {}; * @method removeEventListener */ - /** * Adds an event handler function for one or more events. * @@ -26924,7 +27841,6 @@ OT.components = {}; * @see Events */ - /** * Removes an event handler. * @@ -26979,6 +27895,38 @@ OT.components = {}; * @see ExceptionEvent */ +// tb_require('./helpers/lib/css_loader.js') +// tb_require('./ot/system_requirements.js') +// tb_require('./ot/session.js') +// tb_require('./ot/publisher.js') +// tb_require('./ot/subscriber.js') +// tb_require('./ot/archive.js') +// tb_require('./ot/connection.js') +// tb_require('./ot/stream.js') +// We want this to be included at the end, just before footer.js + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global loadCSS, define */ + +// Tidy up everything on unload +OT.onUnload(function() { + OT.publishers.destroy(); + OT.subscribers.destroy(); + OT.sessions.destroy('unloaded'); +}); + +loadCSS(OT.properties.cssURL); + +// Register as a named AMD module, since TokBox could be concatenated with other +// files that may use define, but not via a proper concatenation script that +// understands anonymous AMD modules. A named AMD is safest and most robust +// way to register. Uppercase TB is used because AMD module names are +// derived from file names, and OpenTok is normally delivered in an uppercase +// file name. +if (typeof define === 'function' && define.amd) { + define('TB', [], function() { return TB; }); +} // tb_require('./postscript.js') From 974f3affa34777465f61fe4ab1d9e7498873e517 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Tue, 10 Nov 2015 08:58:10 +0000 Subject: [PATCH 03/71] Bug 1210865 - Change how Loop's data channels are setup to cope with the newer SDK that doesn't allow setting them up until subscription is complete. r=dmose --- .../loop/content/shared/js/otSdkDriver.js | 97 +++--- .../test/functional/test_1_browser_call.py | 47 +++ .../loop/test/shared/otSdkDriver_test.js | 328 ++++++++++-------- 3 files changed, 284 insertions(+), 188 deletions(-) diff --git a/browser/components/loop/content/shared/js/otSdkDriver.js b/browser/components/loop/content/shared/js/otSdkDriver.js index 6555b157e22..4fdb1d7bd03 100644 --- a/browser/components/loop/content/shared/js/otSdkDriver.js +++ b/browser/components/loop/content/shared/js/otSdkDriver.js @@ -620,7 +620,7 @@ loop.OTSdkDriver = (function() { this.dispatcher.dispatch(new sharedActions.MediaConnected()); } - this._setupDataChannelIfNeeded(sdkSubscriberObject.stream.connection); + this._setupDataChannelIfNeeded(sdkSubscriberObject); }, /** @@ -655,61 +655,24 @@ loop.OTSdkDriver = (function() { * channel set-up routines. A data channel cannot be requested before this * time as the peer connection is not set up. * - * @param {OT.Connection} connection The OT connection class object.paul - * sched + * @param {OT.Subscriber} sdkSubscriberObject The subscriber object for the stream. * */ - _setupDataChannelIfNeeded: function(connection) { - if (this._useDataChannels) { - this.session.signal({ - type: "readyForDataChannel", - to: connection - }, function(signalError) { - if (signalError) { - console.error(signalError); - } - }); - } - }, - - /** - * Handles receiving the signal that the other end of the connection - * has subscribed to the stream and we're ready to setup the data channel. - * - * We get data channels for both the publisher and subscriber on reception - * of the signal, as it means that a) the remote client is setup for data - * channels, and b) that subscribing of streams has definitely completed - * for both clients. - * - * @param {OT.SignalEvent} event Details of the signal received. - */ - _onReadyForDataChannel: function(event) { - // If we don't want data channels, just ignore the message. We haven't - // send the other side a message, so it won't display anything. + _setupDataChannelIfNeeded: function(sdkSubscriberObject) { if (!this._useDataChannels) { return; } - // This won't work until a subscriber exists for this publisher - this.publisher._.getDataChannel("text", {}, function(err, channel) { - if (err) { - console.error(err); - return; + this.session.signal({ + type: "readyForDataChannel", + to: sdkSubscriberObject.stream.connection + }, function(signalError) { + if (signalError) { + console.error(signalError); } + }); - this._publisherChannel = channel; - - channel.on({ - close: function(e) { - // XXX We probably want to dispatch and handle this somehow. - console.log("Published data channel closed!"); - } - }); - - this._checkDataChannelsAvailable(); - }.bind(this)); - - this.subscriber._.getDataChannel("text", {}, function(err, channel) { + sdkSubscriberObject._.getDataChannel("text", {}, function(err, channel) { // Sends will queue until the channel is fully open. if (err) { console.error(err); @@ -741,6 +704,44 @@ loop.OTSdkDriver = (function() { }.bind(this)); }, + /** + * Handles receiving the signal that the other end of the connection + * has subscribed to the stream and we're ready to setup the data channel. + * + * We create the publisher data channel when we get the signal as it means + * that the remote client is setup for data + * channels. Getting the data channel for the subscriber is handled + * separately when the subscription completes. + * + * @param {OT.SignalEvent} event Details of the signal received. + */ + _onReadyForDataChannel: function(event) { + // If we don't want data channels, just ignore the message. We haven't + // send the other side a message, so it won't display anything. + if (!this._useDataChannels) { + return; + } + + // This won't work until a subscriber exists for this publisher + this.publisher._.getDataChannel("text", {}, function(err, channel) { + if (err) { + console.error(err); + return; + } + + this._publisherChannel = channel; + + channel.on({ + close: function(e) { + // XXX We probably want to dispatch and handle this somehow. + console.log("Published data channel closed!"); + } + }); + + this._checkDataChannelsAvailable(); + }.bind(this)); + }, + /** * Checks to see if all channels have been obtained, and if so it dispatches * a notification to the stores to inform them. diff --git a/browser/components/loop/test/functional/test_1_browser_call.py b/browser/components/loop/test/functional/test_1_browser_call.py index 8238716136d..de09997d175 100644 --- a/browser/components/loop/test/functional/test_1_browser_call.py +++ b/browser/components/loop/test/functional/test_1_browser_call.py @@ -142,6 +142,50 @@ class Test1BrowserCall(MarionetteTestCase): self.switch_to_chatbox() self.check_video(".remote-video") + def send_chat_message(self, text): + """ + Sends a chat message using the current context. + + :param text: The text to send. + """ + chatbox = self.wait_for_element_displayed(By.CSS_SELECTOR, + ".text-chat-box > form > input") + + chatbox.send_keys(text + "\n") + + def check_received_message(self, expectedText): + """ + Checks a chat message has been received in the current context. The + test assumes only one chat message will be received during the tests. + + :param expectedText: The expected text of the chat message. + """ + text_entry = self.wait_for_element_displayed(By.CSS_SELECTOR, + ".text-chat-entry.received > p > span") + + self.assertEqual(text_entry.text, expectedText, + "should have received the correct message") + + def check_text_messaging(self): + """ + Checks text messaging between the generator and clicker in a bi-directional + fashion. + """ + # Send a message using the link generator. + self.switch_to_chatbox() + self.send_chat_message("test1") + + # Now check the result on the link clicker. + self.switch_to_standalone() + self.check_received_message("test1") + + # Then send a message using the standalone. + self.send_chat_message("test2") + + # Finally check the link generator got it. + self.switch_to_chatbox() + self.check_received_message("test2") + def local_enable_screenshare(self): self.switch_to_chatbox() button = self.marionette.find_element(By.CLASS_NAME, "btn-screen-share") @@ -242,6 +286,9 @@ class Test1BrowserCall(MarionetteTestCase): self.standalone_check_remote_video() self.local_check_remote_video() + # Check text messaging + self.check_text_messaging() + # since bi-directional media is connected, make sure we've set # the start time self.local_check_media_start_time_initialized() diff --git a/browser/components/loop/test/shared/otSdkDriver_test.js b/browser/components/loop/test/shared/otSdkDriver_test.js index 0993bd10ed8..d9bc3b8510f 100644 --- a/browser/components/loop/test/shared/otSdkDriver_test.js +++ b/browser/components/loop/test/shared/otSdkDriver_test.js @@ -752,6 +752,9 @@ describe("loop.OTSdkDriver", function() { }; fakeSubscriberObject = _.extend({ + "_": { + getDataChannel: sinon.stub() + }, session: { connection: fakeConnection }, stream: fakeStream }, Backbone.Events); @@ -996,131 +999,196 @@ describe("loop.OTSdkDriver", function() { })); }); - it("should subscribe to a camera stream", function() { - session.trigger("streamCreated", { stream: fakeStream }); + describe("Audio/Video streams", function() { + beforeEach(function() { + session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, + videoElement).returns(this.fakeSubscriberObject); + }); - sinon.assert.calledOnce(session.subscribe); - sinon.assert.calledWithExactly(session.subscribe, - fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig, - sinon.match.func); - }); + it("should subscribe to a camera stream", function() { + session.trigger("streamCreated", { stream: fakeStream }); - it("should dispatch MediaStreamCreated after subscribe is complete", function() { - session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, - videoElement).returns(this.fakeSubscriberObject); - driver.session = session; - fakeStream.connection = fakeConnection; - fakeStream.hasVideo = true; + sinon.assert.calledOnce(session.subscribe); + sinon.assert.calledWithExactly(session.subscribe, + fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig, + sinon.match.func); + }); - session.trigger("streamCreated", { stream: fakeStream }); + it("should dispatch MediaStreamCreated after streamCreated is triggered on the session", function() { + driver.session = session; + fakeStream.connection = fakeConnection; + fakeStream.hasVideo = true; - sinon.assert.called(dispatcher.dispatch); - sinon.assert.calledWithExactly(dispatcher.dispatch, - new sharedActions.MediaStreamCreated({ - hasVideo: true, - isLocal: false, - srcMediaElement: videoElement - })); - }); + session.trigger("streamCreated", { stream: fakeStream }); - it("should dispatch MediaStreamCreated after subscribe with audio-only indication if hasVideo=false", function() { - session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, - videoElement); - fakeStream.connection = fakeConnection; - fakeStream.hasVideo = false; + sinon.assert.called(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.MediaStreamCreated({ + hasVideo: true, + isLocal: false, + srcMediaElement: videoElement + })); + }); - session.trigger("streamCreated", { stream: fakeStream }); + it("should dispatch MediaStreamCreated after streamCreated with audio-only indication if hasVideo=false", function() { + fakeStream.connection = fakeConnection; + fakeStream.hasVideo = false; - sinon.assert.called(dispatcher.dispatch); - sinon.assert.calledWithExactly(dispatcher.dispatch, - new sharedActions.MediaStreamCreated({ - hasVideo: false, - isLocal: false, - srcMediaElement: videoElement - })); - }); + session.trigger("streamCreated", { stream: fakeStream }); - it("should trigger a readyForDataChannel signal after subscribe is complete", function() { - session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, - document.createElement("video")); - driver._useDataChannels = true; - fakeStream.connection = fakeConnection; + sinon.assert.called(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.MediaStreamCreated({ + hasVideo: false, + isLocal: false, + srcMediaElement: videoElement + })); + }); - session.trigger("streamCreated", { stream: fakeStream }); + it("should dispatch a mediaConnected action if both streams are up", function() { + driver._publishedLocalStream = true; - sinon.assert.calledOnce(session.signal); - sinon.assert.calledWith(session.signal, { - type: "readyForDataChannel", - to: fakeConnection + session.trigger("streamCreated", { stream: fakeStream }); + + // Called twice due to the VideoDimensionsChanged above. + sinon.assert.called(dispatcher.dispatch); + sinon.assert.calledWithMatch(dispatcher.dispatch, + new sharedActions.MediaConnected({})); + }); + + it("should store the start time when both streams are up and" + + " driver._sendTwoWayMediaTelemetry is true", function() { + driver._sendTwoWayMediaTelemetry = true; + driver._publishedLocalStream = true; + var startTime = 1; + sandbox.stub(performance, "now").returns(startTime); + + session.trigger("streamCreated", { stream: fakeStream }); + + expect(driver._getTwoWayMediaStartTime()).to.eql(startTime); + }); + + it("should not store the start time when both streams are up and" + + " driver._isDesktop is false", function() { + driver._isDesktop = false; + driver._publishedLocalStream = true; + var startTime = 73; + sandbox.stub(performance, "now").returns(startTime); + + session.trigger("streamCreated", { stream: fakeStream }); + + expect(driver._getTwoWayMediaStartTime()).to.not.eql(startTime); + }); + + describe("Data channel setup", function() { + var fakeChannel; + + beforeEach(function() { + fakeChannel = _.extend({}, Backbone.Events); + fakeStream.connection = fakeConnection; + driver._useDataChannels = true; + }); + + it("should trigger a readyForDataChannel signal after subscribe is complete", function() { + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.calledOnce(session.signal); + sinon.assert.calledWith(session.signal, { + type: "readyForDataChannel", + to: fakeConnection + }); + }); + + it("should not trigger readyForDataChannel signal if data channels are not wanted", function() { + driver._useDataChannels = false; + + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.notCalled(session.signal); + }); + + it("should get the data channel after subscribe is complete", function() { + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.calledOnce(fakeSubscriberObject._.getDataChannel); + sinon.assert.calledWith(fakeSubscriberObject._.getDataChannel, "text", {}); + }); + + it("should not get the data channel if data channels are not wanted", function() { + driver._useDataChannels = false; + + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.notCalled(fakeSubscriberObject._.getDataChannel); + }); + + it("should dispatch `DataChannelsAvailable` if the publisher channel is setup", function() { + // Fake a publisher channel. + driver._publisherChannel = {}; + + fakeSubscriberObject._.getDataChannel.callsArgWith(2, null, fakeChannel); + + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.called(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.DataChannelsAvailable({ + available: true + })); + }); + + it("should not dispatch `DataChannelsAvailable` if the publisher channel isn't setup", function() { + fakeSubscriberObject._.getDataChannel.callsArgWith(2, null, fakeChannel); + + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.neverCalledWith(dispatcher.dispatch, + new sharedActions.DataChannelsAvailable({ + available: true + })); + }); + + it("should dispatch `ReceivedTextChatMessage` when a text message is received", function() { + var data = '{"contentType":"' + CHAT_CONTENT_TYPES.TEXT + + '","message":"Are you there?","receivedTimestamp": "2015-06-25T00:29:14.197Z"}'; + var clock = sinon.useFakeTimers(); + + fakeSubscriberObject._.getDataChannel.callsArgWith(2, null, fakeChannel); + + session.trigger("streamCreated", { stream: fakeStream }); + + // Now send the message. + fakeChannel.trigger("message", { + data: data + }); + + sinon.assert.called(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.ReceivedTextChatMessage({ + contentType: CHAT_CONTENT_TYPES.TEXT, + message: "Are you there?", + receivedTimestamp: "1970-01-01T00:00:00.000Z" + })); + + /* Restore the time. */ + clock.restore(); + }); }); }); - it("should not trigger readyForDataChannel signal if data channels are not wanted", function() { - session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, - document.createElement("video")); - driver._useDataChannels = false; - fakeStream.connection = fakeConnection; + describe("screen sharing streams", function() { + it("should subscribe to a screen sharing stream", function() { + fakeStream.videoType = "screen"; - session.trigger("streamCreated", { stream: fakeStream }); + session.trigger("streamCreated", { stream: fakeStream }); - sinon.assert.notCalled(session.signal); - }); + sinon.assert.calledOnce(session.subscribe); + sinon.assert.calledWithExactly(session.subscribe, + fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig, + sinon.match.func); + }); - it("should subscribe to a screen sharing stream", function() { - fakeStream.videoType = "screen"; - - session.trigger("streamCreated", { stream: fakeStream }); - - sinon.assert.calledOnce(session.subscribe); - sinon.assert.calledWithExactly(session.subscribe, - fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig, - sinon.match.func); - }); - - it("should dispatch a mediaConnected action if both streams are up", function() { - session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, - videoElement); - driver._publishedLocalStream = true; - - session.trigger("streamCreated", { stream: fakeStream }); - - // Called twice due to the VideoDimensionsChanged above. - sinon.assert.called(dispatcher.dispatch); - sinon.assert.calledWithMatch(dispatcher.dispatch, - new sharedActions.MediaConnected({})); - }); - - it("should store the start time when both streams are up and" + - " driver._sendTwoWayMediaTelemetry is true", function() { - session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, - videoElement); - driver._sendTwoWayMediaTelemetry = true; - driver._publishedLocalStream = true; - var startTime = 1; - sandbox.stub(performance, "now").returns(startTime); - - session.trigger("streamCreated", { stream: fakeStream }); - - expect(driver._getTwoWayMediaStartTime()).to.eql(startTime); - }); - - it("should not store the start time when both streams are up and" + - " driver._isDesktop is false", function() { - session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, - videoElement); - driver._isDesktop = false; - driver._publishedLocalStream = true; - var startTime = 73; - sandbox.stub(performance, "now").returns(startTime); - - session.trigger("streamCreated", { stream: fakeStream }); - - expect(driver._getTwoWayMediaStartTime()).to.not.eql(startTime); - }); - - - it("should not dispatch a mediaConnected action for screen sharing streams", - function() { + it("should not dispatch a mediaConnected action for screen sharing streams", function() { driver._publishedLocalStream = true; fakeStream.videoType = "screen"; @@ -1130,16 +1198,14 @@ describe("loop.OTSdkDriver", function() { sinon.match.hasOwn("name", "mediaConnected")); }); - it("should not dispatch a ReceivingScreenShare action for camera streams", - function() { + it("should not dispatch a ReceivingScreenShare action for camera streams", function() { session.trigger("streamCreated", { stream: fakeStream }); sinon.assert.neverCalledWithMatch(dispatcher.dispatch, new sharedActions.ReceivingScreenShare({ receiving: true })); }); - it("should dispatch a ReceivingScreenShare action for screen" + - " sharing streams", function() { + it("should dispatch a ReceivingScreenShare action for screen sharing streams", function() { fakeStream.videoType = "screen"; session.trigger("streamCreated", { stream: fakeStream }); @@ -1149,6 +1215,7 @@ describe("loop.OTSdkDriver", function() { sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.ReceivingScreenShare({ receiving: true })); }); + }); }); describe("streamDestroyed: publisher/local", function() { @@ -1499,16 +1566,11 @@ describe("loop.OTSdkDriver", function() { sinon.assert.calledOnce(publisher._.getDataChannel); }); - it("should get the data channel for the subscriber", function() { - session.trigger("signal:readyForDataChannel"); - - sinon.assert.calledOnce(subscriber._.getDataChannel); - }); - - it("should dispatch `DataChannelsAvailable` once both data channels have been obtained", function() { + it("should dispatch `DataChannelsAvailable` if the subscriber channel is setup", function() { var fakeChannel = _.extend({}, Backbone.Events); - subscriber._.getDataChannel.callsArgWith(2, null, fakeChannel); + driver._subscriberChannel = fakeChannel; + publisher._.getDataChannel.callsArgWith(2, null, fakeChannel); session.trigger("signal:readyForDataChannel"); @@ -1520,31 +1582,17 @@ describe("loop.OTSdkDriver", function() { })); }); - it("should dispatch `ReceivedTextChatMessage` when a text message is received", function() { + it("should not dispatch `DataChannelsAvailable` if the subscriber channel isn't setup", function() { var fakeChannel = _.extend({}, Backbone.Events); - var data = '{"contentType":"' + CHAT_CONTENT_TYPES.TEXT + - '","message":"Are you there?","receivedTimestamp": "2015-06-25T00:29:14.197Z"}'; - var clock = sinon.useFakeTimers(); - subscriber._.getDataChannel.callsArgWith(2, null, fakeChannel); + publisher._.getDataChannel.callsArgWith(2, null, fakeChannel); session.trigger("signal:readyForDataChannel"); - // Now send the message. - fakeChannel.trigger("message", { - data: data - }); - - sinon.assert.calledOnce(dispatcher.dispatch); - sinon.assert.calledWithExactly(dispatcher.dispatch, - new sharedActions.ReceivedTextChatMessage({ - contentType: CHAT_CONTENT_TYPES.TEXT, - message: "Are you there?", - receivedTimestamp: "1970-01-01T00:00:00.000Z" + sinon.assert.neverCalledWith(dispatcher.dispatch, + new sharedActions.DataChannelsAvailable({ + available: true })); - - /* Restore the time. */ - clock.restore(); }); }); From 3ecdeaefe5c28a1f8b812e0296b0083ec399875d Mon Sep 17 00:00:00 2001 From: Sami Jaktholm Date: Sat, 7 Nov 2015 08:28:38 +0200 Subject: [PATCH 04/71] Bug 1199180 - Revert rev f508744adc9f as it did not help with the intermittent failures. r=pbrosset --- devtools/client/markupview/test/browser.ini | 1 - .../test/browser_markupview_keybindings_04.js | 39 +++------------- .../markupview/test/frame-script-utils.js | 46 ------------------- devtools/client/markupview/test/head.js | 2 - 4 files changed, 7 insertions(+), 81 deletions(-) delete mode 100644 devtools/client/markupview/test/frame-script-utils.js diff --git a/devtools/client/markupview/test/browser.ini b/devtools/client/markupview/test/browser.ini index 4bde459bf21..169e90ea506 100644 --- a/devtools/client/markupview/test/browser.ini +++ b/devtools/client/markupview/test/browser.ini @@ -23,7 +23,6 @@ support-files = doc_markup_toggle.html doc_markup_tooltip.png doc_markup_xul.xul - frame-script-utils.js head.js helper_attributes_test_runner.js helper_events_test_runner.js diff --git a/devtools/client/markupview/test/browser_markupview_keybindings_04.js b/devtools/client/markupview/test/browser_markupview_keybindings_04.js index 3bf81cbe876..c36c271e4b1 100644 --- a/devtools/client/markupview/test/browser_markupview_keybindings_04.js +++ b/devtools/client/markupview/test/browser_markupview_keybindings_04.js @@ -45,28 +45,10 @@ function assertNodeSelected(inspector, tagName) { } function* selectWithBrowserMenu(inspector) { - // This test can't use BrowserTestUtils.synthesizeMouseAtCenter() - // method (see below) since it causes intermittent test failures. - // So, we are introducing a new "Test:MarkupView:SynthesizeMouse" event - // that is handled in the content scope. The main difference between - // this new event and BrowserTestUtils library is EventUtils library. - // While BrowserTestUtils is using: - // chrome://mochikit/content/tests/SimpleTest/EventUtils.js - // (see: AsyncUtilsContent.js) - // ... this test requires: - // chrome://marionette/content/EventUtils.js - // (see markupview/test/frame-script-utils.js) - // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1199180 - yield executeInContent("Test:MarkupView:SynthesizeMouse", { - center: true, - selector: "div", - options: {type: "contextmenu", button: 2} - }); - - //yield BrowserTestUtils.synthesizeMouseAtCenter("div", { - // type: "contextmenu", - // button: 2 - //}, gBrowser.selectedBrowser); + yield BrowserTestUtils.synthesizeMouseAtCenter("div", { + type: "contextmenu", + button: 2 + }, gBrowser.selectedBrowser); // nsContextMenu also requires the popupNode to be set, but we can't set it to // node under e10s as it's a CPOW, not a DOM node. But under e10s, @@ -89,16 +71,9 @@ function* selectWithBrowserMenu(inspector) { function* selectWithElementPicker(inspector) { yield inspector.toolbox.highlighterUtils.startPicker(); - yield executeInContent("Test:MarkupView:SynthesizeMouse", { - center: true, - selector: "div", - options: {type: "mousemove"} - }); - - // Read comment in selectWithBrowserMenu() method. - //yield BrowserTestUtils.synthesizeMouseAtCenter("div", { - // type: "mousemove", - //}, gBrowser.selectedBrowser); + yield BrowserTestUtils.synthesizeMouseAtCenter("div", { + type: "mousemove", + }, gBrowser.selectedBrowser); executeInContent("Test:SynthesizeKey", { key: "VK_RETURN", diff --git a/devtools/client/markupview/test/frame-script-utils.js b/devtools/client/markupview/test/frame-script-utils.js deleted file mode 100644 index b004e38d4d9..00000000000 --- a/devtools/client/markupview/test/frame-script-utils.js +++ /dev/null @@ -1,46 +0,0 @@ -/* 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/. */ - -"use strict"; - -var {classes: Cc, interfaces: Ci} = Components; -const subScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Ci.mozIJSSubScriptLoader); -var EventUtils = {}; -subScriptLoader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils); - -/** - * Synthesize a mouse event on an element. This handler doesn't send a message - * back. Consumers should listen to specific events on the inspector/highlighter - * to know when the event got synthesized. - * @param {Object} msg The msg.data part expects the following properties: - * - {Number} x - * - {Number} y - * - {Boolean} center If set to true, x/y will be ignored and - * synthesizeMouseAtCenter will be used instead - * - {Object} options Other event options - * - {String} selector An optional selector that will be used to find the node to - * synthesize the event on, if msg.objects doesn't contain the CPOW. - * The msg.objects part should be the element. - * @param {Object} data Event detail properties: - */ -addMessageListener("Test:MarkupView:SynthesizeMouse", function(msg) { - let {x, y, center, options, selector} = msg.data; - let {node} = msg.objects; - - if (!node && selector) { - node = content.document.querySelector(selector); - } - - if (center) { - EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView); - } else { - EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView); - } - - // Most consumers won't need to listen to this message, unless they want to - // wait for the mouse event to be synthesized and don't have another event - // to listen to instead. - sendAsyncMessage("Test:MarkupView:SynthesizeMouse"); -}); diff --git a/devtools/client/markupview/test/head.js b/devtools/client/markupview/test/head.js index d01f95f6e8e..ffe09abad97 100644 --- a/devtools/client/markupview/test/head.js +++ b/devtools/client/markupview/test/head.js @@ -51,7 +51,6 @@ registerCleanupFunction(function*() { const TEST_URL_ROOT = "http://mochi.test:8888/browser/devtools/client/markupview/test/"; const CHROME_BASE = "chrome://mochitests/content/browser/devtools/client/markupview/test/"; const COMMON_FRAME_SCRIPT_URL = "chrome://devtools/content/shared/frame-script-utils.js"; -const MARKUPVIEW_FRAME_SCRIPT_URL = CHROME_BASE + "frame-script-utils.js"; /** * Add a new test tab in the browser and load the given url. @@ -72,7 +71,6 @@ function addTab(url) { info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL); linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false); - linkedBrowser.messageManager.loadFrameScript(MARKUPVIEW_FRAME_SCRIPT_URL, false); linkedBrowser.addEventListener("load", function onload() { linkedBrowser.removeEventListener("load", onload, true); From 21041eaa5a4d6571f03eefa1126c2ffc0385f1b8 Mon Sep 17 00:00:00 2001 From: Sami Jaktholm Date: Sat, 7 Nov 2015 08:27:16 +0200 Subject: [PATCH 05/71] Bug 1199180 - Wait for the inspector-updated event after selecting nodes with UP key. r=pbrosset The likely steps that lead to intermittent failures in browser_markupview_keybindings_04.js are: 1) UP key is pressed, the test waits for child updates and node-highlight event. 2) Once those have finished, the selection has changed to BUT the inspector-updated event for the change has not been emitted (child node update finishes before the event is emitted). 3) The test calls selectWithElementPicker() presses ENTER to pick the

    element and starts to wait for an inspector-updated event. 4) The inspector-updated event from (1) is finally emitted and the test continues BUT the selection change at (3) has not yet completed. This means is still selected and the assertion fails. Since a new selection will always cause the inspector-updated event to be emitted it makes sense to wait for it after pressing UP. --- .../test/browser_markupview_keybindings_04.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/devtools/client/markupview/test/browser_markupview_keybindings_04.js b/devtools/client/markupview/test/browser_markupview_keybindings_04.js index c36c271e4b1..2529641b3e8 100644 --- a/devtools/client/markupview/test/browser_markupview_keybindings_04.js +++ b/devtools/client/markupview/test/browser_markupview_keybindings_04.js @@ -20,10 +20,7 @@ add_task(function*() { info("Press arrowUp to focus " + "(which works if the node was focused properly)"); - let onNodeHighlighted = toolbox.once("node-highlight"); - EventUtils.synthesizeKey("VK_UP", {}); - yield waitForChildrenUpdated(inspector); - yield onNodeHighlighted; + yield selectPreviousNodeWithArrowUp(inspector); assertNodeSelected(inspector, "body"); info("Select the test node with the element picker"); @@ -32,10 +29,7 @@ add_task(function*() { info("Press arrowUp to focus " + "(which works if the node was focused properly)"); - onNodeHighlighted = toolbox.once("node-highlight"); - EventUtils.synthesizeKey("VK_UP", {}); - yield waitForChildrenUpdated(inspector); - yield onNodeHighlighted; + yield selectPreviousNodeWithArrowUp(inspector); assertNodeSelected(inspector, "body"); }); @@ -44,6 +38,13 @@ function assertNodeSelected(inspector, tagName) { `The <${tagName}> node is selected`); } +function selectPreviousNodeWithArrowUp(inspector) { + let onNodeHighlighted = inspector.toolbox.once("node-highlight"); + let onUpdated = inspector.once("inspector-updated"); + EventUtils.synthesizeKey("VK_UP", {}); + return Promise.all([onUpdated, onNodeHighlighted]); +} + function* selectWithBrowserMenu(inspector) { yield BrowserTestUtils.synthesizeMouseAtCenter("div", { type: "contextmenu", From 2ec37425331bf6c1073fe5c2e24ec3b963bde221 Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Mon, 9 Nov 2015 18:15:38 -0500 Subject: [PATCH 06/71] Bug 1205436 - Add extra logging to PanelUI show/hide and customizing enter/exit. r=Gijs --- browser/components/customizableui/CustomizeMode.jsm | 6 ++++-- browser/components/customizableui/content/panelUI.js | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm index f9d0b9f94e0..b585b6e047a 100644 --- a/browser/components/customizableui/CustomizeMode.jsm +++ b/browser/components/customizableui/CustomizeMode.jsm @@ -309,10 +309,12 @@ CustomizeMode.prototype = { this.exit(); } }.bind(this)).then(null, function(e) { - ERROR(e); + ERROR("Error entering customize mode", e); // We should ensure this has been called, and calling it again doesn't hurt: window.PanelUI.endBatchUpdate(); this._handler.isEnteringCustomizeMode = false; + // Exit customize mode to ensure proper clean-up when entering failed. + this.exit(); }.bind(this)); }, @@ -482,7 +484,7 @@ CustomizeMode.prototype = { this.enter(); } }.bind(this)).then(null, function(e) { - ERROR(e); + ERROR("Error exiting customize mode", e); // We should ensure this has been called, and calling it again doesn't hurt: window.PanelUI.endBatchUpdate(); this._handler.isExitingCustomizeMode = false; diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js index b03a44aca7a..bc31707257a 100644 --- a/browser/components/customizableui/content/panelUI.js +++ b/browser/components/customizableui/content/panelUI.js @@ -166,6 +166,8 @@ const PanelUI = { document.getAnonymousElementByAttribute(anchor, "class", "toolbarbutton-icon"); this.panel.openPopup(iconAnchor || anchor); + }, (reason) => { + console.error("Error showing the PanelUI menu", reason); }); return deferred.promise; From d79c059e05f3db91e4d0935f55c778f9b25e04cd Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Mon, 9 Nov 2015 07:01:00 +0100 Subject: [PATCH 07/71] Bug 1216234 - add inIDOMUtils.getCSSPseudoElementNames; r=heycam,pbrosset --- devtools/client/styleinspector/rule-view.js | 7 ++- .../browser_ruleview_user-agent-styles.js | 8 +++ devtools/server/actors/styles.js | 21 ++----- .../tests/mochitest/test_styles-applied.html | 2 +- devtools/shared/styleinspector/css-logic.js | 42 -------------- layout/inspector/inDOMUtils.cpp | 23 ++++++++ layout/inspector/inIDOMUtils.idl | 13 ++++- layout/inspector/tests/mochitest.ini | 1 + .../tests/test_getCSSPseudoElementNames.html | 57 +++++++++++++++++++ 9 files changed, 111 insertions(+), 63 deletions(-) create mode 100644 layout/inspector/tests/test_getCSSPseudoElementNames.html diff --git a/devtools/client/styleinspector/rule-view.js b/devtools/client/styleinspector/rule-view.js index 88948d2fe2b..b89e4dbce22 100644 --- a/devtools/client/styleinspector/rule-view.js +++ b/devtools/client/styleinspector/rule-view.js @@ -13,8 +13,7 @@ const {setTimeout, clearTimeout} = const {CssLogic} = require("devtools/shared/styleinspector/css-logic"); const {InplaceEditor, editableField, editableItem} = require("devtools/client/shared/inplace-editor"); -const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = - require("devtools/server/actors/styles"); +const {ELEMENT_STYLE} = require("devtools/server/actors/styles"); const {OutputParser} = require("devtools/shared/output-parser"); const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils"); const { @@ -4059,3 +4058,7 @@ XPCOMUtils.defineLazyGetter(this, "domUtils", function() { loader.lazyGetter(this, "AutocompletePopup", function() { return require("devtools/client/shared/autocomplete-popup").AutocompletePopup; }); + +loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => { + return domUtils.getCSSPseudoElementNames(); +}); diff --git a/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js b/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js index da3fef8ddd0..43f1f329aa4 100644 --- a/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js +++ b/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js @@ -160,6 +160,14 @@ function* compareAppliedStylesWithUI(inspector, view, filter) { filter: filter }); + // We may see multiple entries that map to a given rule; filter the + // duplicates here to match what the UI does. + let entryMap = new Map(); + for (let entry of entries) { + entryMap.set(entry.rule, entry); + } + entries = [...entryMap.values()]; + let elementStyle = view._elementStyle; is(elementStyle.rules.length, entries.length, "Should have correct number of rules (" + entries.length + ")"); diff --git a/devtools/server/actors/styles.js b/devtools/server/actors/styles.js index f5190a9546a..88b264db444 100644 --- a/devtools/server/actors/styles.js +++ b/devtools/server/actors/styles.js @@ -12,7 +12,6 @@ const {Arg, Option, method, RetVal, types} = protocol; const events = require("sdk/event/core"); const {Class} = require("sdk/core/heritage"); const {LongStringActor} = require("devtools/server/actors/string"); -const {PSEUDO_ELEMENT_SET} = require("devtools/shared/styleinspector/css-logic"); // This will also add the "stylesheet" actor type for protocol.js to recognize const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = @@ -35,24 +34,12 @@ loader.lazyGetter(this, "RuleRewriter", () => { const ELEMENT_STYLE = 100; exports.ELEMENT_STYLE = ELEMENT_STYLE; -// Not included since these are uneditable by the user. -// See https://hg.mozilla.org/mozilla-central/file/696a4ad5d011/layout/style/nsCSSPseudoElementList.h#l74 -PSEUDO_ELEMENT_SET.delete(":-moz-meter-bar"); -PSEUDO_ELEMENT_SET.delete(":-moz-list-bullet"); -PSEUDO_ELEMENT_SET.delete(":-moz-list-number"); -PSEUDO_ELEMENT_SET.delete(":-moz-focus-inner"); -PSEUDO_ELEMENT_SET.delete(":-moz-focus-outer"); -PSEUDO_ELEMENT_SET.delete(":-moz-math-anonymous"); -PSEUDO_ELEMENT_SET.delete(":-moz-math-stretchy"); - -const PSEUDO_ELEMENTS = Array.from(PSEUDO_ELEMENT_SET); - -exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENTS; - // When gathering rules to read for pseudo elements, we will skip // :before and :after, which are handled as a special case. -const PSEUDO_ELEMENTS_TO_READ = PSEUDO_ELEMENTS.filter(pseudo => { - return pseudo !== ":before" && pseudo !== ":after"; +loader.lazyGetter(this, "PSEUDO_ELEMENTS_TO_READ", () => { + return DOMUtils.getCSSPseudoElementNames().filter(pseudo => { + return pseudo !== ":before" && pseudo !== ":after"; + }); }); const XHTML_NS = "http://www.w3.org/1999/xhtml"; diff --git a/devtools/server/tests/mochitest/test_styles-applied.html b/devtools/server/tests/mochitest/test_styles-applied.html index fc9f04f6ee6..5afa1c30995 100644 --- a/devtools/server/tests/mochitest/test_styles-applied.html +++ b/devtools/server/tests/mochitest/test_styles-applied.html @@ -85,7 +85,7 @@ addTest(function inheritedSystemStyles() { ok(!applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style"); is(applied[1].rule.type, 1, "Entry 1 should be a rule style"); - is(applied.length, 8, "Should have 8 rules."); + is(applied.length, 11, "Should have 11 rules."); }).then(runNextTest)); }); diff --git a/devtools/shared/styleinspector/css-logic.js b/devtools/shared/styleinspector/css-logic.js index 80f1feec647..c0b28d01f02 100644 --- a/devtools/shared/styleinspector/css-logic.js +++ b/devtools/shared/styleinspector/css-logic.js @@ -43,36 +43,6 @@ const Services = require("Services"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { getRootBindingParent } = require("devtools/shared/layout/utils"); -var pseudos = new Set([ - ":after", - ":before", - ":first-letter", - ":first-line", - ":selection", - ":-moz-color-swatch", - ":-moz-focus-inner", - ":-moz-focus-outer", - ":-moz-list-bullet", - ":-moz-list-number", - ":-moz-math-anonymous", - ":-moz-math-stretchy", - ":-moz-meter-bar", - ":-moz-number-spin-box", - ":-moz-number-spin-down", - ":-moz-number-spin-up", - ":-moz-number-text", - ":-moz-number-wrapper", - ":-moz-placeholder", - ":-moz-progress-bar", - ":-moz-range-progress", - ":-moz-range-thumb", - ":-moz-range-track", - ":-moz-selection" -]); - -const PSEUDO_ELEMENT_SET = pseudos; -exports.PSEUDO_ELEMENT_SET = PSEUDO_ELEMENT_SET; - // This should be ok because none of the functions that use this should be used // on the worker thread, where Cu is not available. if (Cu) { @@ -1678,18 +1648,6 @@ CssSelector.prototype = { return this.cssRule.line; }, - /** - * Retrieve the pseudo-elements that we support. This list should match the - * elements specified in layout/style/nsCSSPseudoElementList.h - */ - get pseudoElements() - { - if (!CssSelector._pseudoElements) { - CssSelector._pseudoElements = PSEUDO_ELEMENT_SET; - } - return CssSelector._pseudoElements; - }, - /** * Retrieve specificity information for the current selector. * diff --git a/layout/inspector/inDOMUtils.cpp b/layout/inspector/inDOMUtils.cpp index dccd7ccef02..f176168806b 100644 --- a/layout/inspector/inDOMUtils.cpp +++ b/layout/inspector/inDOMUtils.cpp @@ -1191,6 +1191,29 @@ GetStatesForPseudoClass(const nsAString& aStatePseudo) return sPseudoClassStates[nsCSSPseudoClasses::GetPseudoType(atom)]; } +NS_IMETHODIMP +inDOMUtils::GetCSSPseudoElementNames(uint32_t* aLength, char16_t*** aNames) +{ + nsTArray array; + + for (int i = 0; i < nsCSSPseudoElements::ePseudo_PseudoElementCount; ++i) { + nsCSSPseudoElements::Type type = static_cast(i); + if (!nsCSSPseudoElements::PseudoElementIsUASheetOnly(type)) { + nsIAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type); + array.AppendElement(atom); + } + } + + *aLength = array.Length(); + char16_t** ret = + static_cast(moz_xmalloc(*aLength * sizeof(char16_t*))); + for (uint32_t i = 0; i < *aLength; ++i) { + ret[i] = ToNewUnicode(nsDependentAtomString(array[i])); + } + *aNames = ret; + return NS_OK; +} + NS_IMETHODIMP inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement, const nsAString &aPseudoClass) diff --git a/layout/inspector/inIDOMUtils.idl b/layout/inspector/inIDOMUtils.idl index daaff3a98eb..ddc79710ade 100644 --- a/layout/inspector/inIDOMUtils.idl +++ b/layout/inspector/inIDOMUtils.idl @@ -17,7 +17,7 @@ interface nsIDOMFontFaceList; interface nsIDOMRange; interface nsIDOMCSSStyleSheet; -[scriptable, uuid(d67c0463-592e-4d7c-b67e-923ee3f6c643)] +[scriptable, uuid(ec3dc3d5-41d1-4d08-ace5-7e944de6614d)] interface inIDOMUtils : nsISupports { // CSS utilities @@ -162,6 +162,17 @@ interface inIDOMUtils : nsISupports nsIDOMFontFaceList getUsedFontFaces(in nsIDOMRange aRange); + /** + * Get the names of all the supported pseudo-elements. + * Pseudo-elements which are only accepted in UA style sheets are + * not included. + * + * @param {unsigned long} aCount the number of items returned + * @param {wstring[]} aNames the names + */ + void getCSSPseudoElementNames([optional] out unsigned long aCount, + [retval, array, size_is(aCount)] out wstring aNames); + // pseudo-class style locking methods. aPseudoClass must be a valid pseudo-class // selector string, e.g. ":hover". ":-moz-any-link" and non-event-state // pseudo-classes are ignored. diff --git a/layout/inspector/tests/mochitest.ini b/layout/inspector/tests/mochitest.ini index 4dd9acdd3a9..25209f57a67 100644 --- a/layout/inspector/tests/mochitest.ini +++ b/layout/inspector/tests/mochitest.ini @@ -19,6 +19,7 @@ support-files = [test_color_to_rgba.html] [test_css_property_is_shorthand.html] [test_css_property_is_valid.html] +[test_getCSSPseudoElementNames.html] [test_getRelativeRuleLine.html] [test_get_all_style_sheets.html] [test_is_valid_css_color.html] diff --git a/layout/inspector/tests/test_getCSSPseudoElementNames.html b/layout/inspector/tests/test_getCSSPseudoElementNames.html new file mode 100644 index 00000000000..6085a018c9a --- /dev/null +++ b/layout/inspector/tests/test_getCSSPseudoElementNames.html @@ -0,0 +1,57 @@ + + + + + Test inDOMUtils::getCSSPseudoElementNames + + + + + +

    Test inDOMUtils::getCSSPseudoElementNames

    +

    + +
    +
    + + From a236a44a79de48576957af6520b687afe9c3fe88 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Fri, 6 Nov 2015 20:40:00 +0100 Subject: [PATCH 08/71] Bug 1216542 - disable browser_wa_properties-view-media-nodes.js on OSX, due to failures on 10.10.5. r=vp --- devtools/client/webaudioeditor/test/browser.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/client/webaudioeditor/test/browser.ini b/devtools/client/webaudioeditor/test/browser.ini index 073f378b47d..cad17a5305a 100644 --- a/devtools/client/webaudioeditor/test/browser.ini +++ b/devtools/client/webaudioeditor/test/browser.ini @@ -68,6 +68,7 @@ skip-if = true # bug 1010423 [browser_wa_properties-view-edit-02.js] skip-if = true # bug 1010423 [browser_wa_properties-view-media-nodes.js] +skip-if = os == 'mac' # bug 1216542 [browser_wa_properties-view-params.js] [browser_wa_properties-view-params-objects.js] [browser_wa_reset-01.js] From bd123b8860650bc22b453abd502d9ffdb97de4c2 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Fri, 6 Nov 2015 10:43:00 +0100 Subject: [PATCH 09/71] Bug 1222461 - fix how rewriteDeclarations reports unrelated changes. r=pbrosset --- devtools/client/shared/css-parsing-utils.js | 36 ++++++++++---- .../test/unit/test_rewriteDeclarations.js | 48 +++++++++++++------ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/devtools/client/shared/css-parsing-utils.js b/devtools/client/shared/css-parsing-utils.js index 6b44a7c06b7..d4782b934be 100644 --- a/devtools/client/shared/css-parsing-utils.js +++ b/devtools/client/shared/css-parsing-utils.js @@ -567,7 +567,11 @@ RuleRewriter.prototype = { * declarations. * * @param {String} text The input text. This should include the trailing ";". - * @return {String} Text that has been rewritten to be "lexically safe". + * @return {Array} An array of the form [anySanitized, text], where + * |anySanitized| is a boolean that indicates + * whether anything substantive has changed; and + * where |text| is the text that has been rewritten + * to be "lexically safe". */ sanitizePropertyValue: function(text) { let lexer = DOMUtils.getCSSLexer(text); @@ -575,6 +579,7 @@ RuleRewriter.prototype = { let result = ""; let previousOffset = 0; let braceDepth = 0; + let anySanitized = false; while (true) { let token = lexer.nextToken(); if (!token) { @@ -605,6 +610,7 @@ RuleRewriter.prototype = { // Quote the offending symbol. result += "\\" + token.text; previousOffset = token.endOffset; + anySanitized = true; } break; } @@ -612,9 +618,13 @@ RuleRewriter.prototype = { } // Copy out any remaining text, then any needed terminators. - result += text.substring(previousOffset, text.length) + - lexer.performEOFFixup("", true); - return result; + result += text.substring(previousOffset, text.length); + let eofFixup = lexer.performEOFFixup("", true); + if (eofFixup) { + anySanitized = true; + result += eofFixup; + } + return [anySanitized, result]; }, /** @@ -662,10 +672,16 @@ RuleRewriter.prototype = { // before any trailing whitespace. this.result = this.result.substring(0, endIndex) + termDecl.terminator + trailingText; - // The terminator includes the ";", but we don't want it in - // the changed value. - this.changedDeclarations[index] = - termDecl.value + termDecl.terminator.slice(0, -1); + // In a couple of cases, we may have had to add something to + // terminate the declaration, but the termination did not + // actually affect the property's value -- and at this spot, we + // only care about reporting value changes. In particular, we + // might have added a plain ";", or we might have terminated a + // comment with "*/;". Neither of these affect the value. + if (termDecl.terminator !== ";" && termDecl.terminator !== "*/;") { + this.changedDeclarations[index] = + termDecl.value + termDecl.terminator.slice(0, -1); + } } // If the rule generally has newlines, but this particular // declaration doesn't have a trailing newline, insert one now. @@ -685,8 +701,8 @@ RuleRewriter.prototype = { * @return {String} The sanitized text. */ sanitizeText: function(text, index) { - let sanitizedText = this.sanitizePropertyValue(text); - if (sanitizedText !== text) { + let [anySanitized, sanitizedText] = this.sanitizePropertyValue(text); + if (anySanitized) { this.changedDeclarations[index] = sanitizedText; } return sanitizedText; diff --git a/devtools/client/shared/test/unit/test_rewriteDeclarations.js b/devtools/client/shared/test/unit/test_rewriteDeclarations.js index 1668ba03bfc..936b38d0ecd 100644 --- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js +++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js @@ -269,7 +269,8 @@ const TEST_DATA = [ desc: "enable single quote termination", input: "/* content: 'hi */ color: red;", instruction: {type: "enable", name: "content", value: true, index: 0}, - expected: "content: 'hi'; color: red;" + expected: "content: 'hi'; color: red;", + changed: {0: "'hi'"} }, // Termination insertion corner case. { @@ -277,7 +278,8 @@ const TEST_DATA = [ input: "content: 'hi", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "content: 'hi';color: red;" + expected: "content: 'hi';color: red;", + changed: {0: "'hi'"} }, // Termination insertion corner case. @@ -285,7 +287,8 @@ const TEST_DATA = [ desc: "enable double quote termination", input: "/* content: \"hi */ color: red;", instruction: {type: "enable", name: "content", value: true, index: 0}, - expected: "content: \"hi\"; color: red;" + expected: "content: \"hi\"; color: red;", + changed: {0: "\"hi\""} }, // Termination insertion corner case. { @@ -293,7 +296,8 @@ const TEST_DATA = [ input: "content: \"hi", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "content: \"hi\";color: red;" + expected: "content: \"hi\";color: red;", + changed: {0: "\"hi\""} }, // Termination insertion corner case. @@ -302,7 +306,8 @@ const TEST_DATA = [ input: "/* background-image: url(something.jpg */ color: red;", instruction: {type: "enable", name: "background-image", value: true, index: 0}, - expected: "background-image: url(something.jpg); color: red;" + expected: "background-image: url(something.jpg); color: red;", + changed: {0: "url(something.jpg)"} }, // Termination insertion corner case. { @@ -310,7 +315,8 @@ const TEST_DATA = [ input: "background-image: url(something.jpg", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "background-image: url(something.jpg);color: red;" + expected: "background-image: url(something.jpg);color: red;", + changed: {0: "url(something.jpg)"} }, // Termination insertion corner case. @@ -319,7 +325,8 @@ const TEST_DATA = [ input: "/* background-image: url('something.jpg */ color: red;", instruction: {type: "enable", name: "background-image", value: true, index: 0}, - expected: "background-image: url('something.jpg'); color: red;" + expected: "background-image: url('something.jpg'); color: red;", + changed: {0: "url('something.jpg')"} }, // Termination insertion corner case. { @@ -327,7 +334,8 @@ const TEST_DATA = [ input: "background-image: url('something.jpg", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "background-image: url('something.jpg');color: red;" + expected: "background-image: url('something.jpg');color: red;", + changed: {0: "url('something.jpg')"} }, // Termination insertion corner case. @@ -336,7 +344,8 @@ const TEST_DATA = [ input: "/* background-image: url(\"something.jpg */ color: red;", instruction: {type: "enable", name: "background-image", value: true, index: 0}, - expected: "background-image: url(\"something.jpg\"); color: red;" + expected: "background-image: url(\"something.jpg\"); color: red;", + changed: {0: "url(\"something.jpg\")"} }, // Termination insertion corner case. { @@ -344,7 +353,8 @@ const TEST_DATA = [ input: "background-image: url(\"something.jpg", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "background-image: url(\"something.jpg\");color: red;" + expected: "background-image: url(\"something.jpg\");color: red;", + changed: {0: "url(\"something.jpg\")"} }, // Termination insertion corner case. @@ -353,7 +363,10 @@ const TEST_DATA = [ input: "something: \\", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "something: \\\\;color: red;" + expected: "something: \\\\;color: red;", + // The lexer rewrites the token before we see it. However this is + // so obscure as to be inconsequential. + changed: {0: "\uFFFD\\"} }, // Termination insertion corner case. @@ -362,14 +375,16 @@ const TEST_DATA = [ input: "something: '\\", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "something: '\\\\';color: red;" + expected: "something: '\\\\';color: red;", + changed: {0: "'\\\\'"} }, { desc: "enable backslash double quote termination", input: "something: \"\\", instruction: {type: "create", name: "color", value: "red", priority: "", index: 1}, - expected: "something: \"\\\\\";color: red;" + expected: "something: \"\\\\\";color: red;", + changed: {0: "\"\\\\\""} }, // Termination insertion corner case. @@ -471,8 +486,13 @@ function run_test() { let {changed, text} = rewriteDeclarations(test.input, test.instruction, "\t"); equal(text, test.expected, "output for " + test.desc); + + let expectChanged; if ("changed" in test) { - deepEqual(changed, test.changed, "changed result for " + test.desc); + expectChanged = test.changed; + } else { + expectChanged = {}; } + deepEqual(changed, expectChanged, "changed result for " + test.desc); } } From d91bdfe63620bd3e1ee091bea06403f20d7cc51b Mon Sep 17 00:00:00 2001 From: rthyberg Date: Sun, 8 Nov 2015 16:25:00 +0100 Subject: [PATCH 10/71] Bug 1164039 - moved TelemetryTimestamps.jsm and tests to toolkit/componets/telemetry/. r=dexter --- .../{modules => components/telemetry}/TelemetryTimestamps.jsm | 0 toolkit/components/telemetry/moz.build | 1 + .../telemetry/tests/unit}/test_TelemetryTimestamps.js | 2 +- toolkit/components/telemetry/tests/unit/xpcshell.ini | 2 ++ toolkit/modules/moz.build | 1 - toolkit/modules/tests/xpcshell/xpcshell.ini | 2 -- 6 files changed, 4 insertions(+), 4 deletions(-) rename toolkit/{modules => components/telemetry}/TelemetryTimestamps.jsm (100%) rename toolkit/{modules/tests/xpcshell => components/telemetry/tests/unit}/test_TelemetryTimestamps.js (98%) diff --git a/toolkit/modules/TelemetryTimestamps.jsm b/toolkit/components/telemetry/TelemetryTimestamps.jsm similarity index 100% rename from toolkit/modules/TelemetryTimestamps.jsm rename to toolkit/components/telemetry/TelemetryTimestamps.jsm diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build index b3ff140170f..ace0a44bbe4 100644 --- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -40,6 +40,7 @@ EXTRA_JS_MODULES += [ 'TelemetrySession.jsm', 'TelemetryStopwatch.jsm', 'TelemetryStorage.jsm', + 'TelemetryTimestamps.jsm', 'TelemetryUtils.jsm', 'ThirdPartyCookieProbe.jsm', 'UITelemetry.jsm', diff --git a/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js b/toolkit/components/telemetry/tests/unit/test_TelemetryTimestamps.js similarity index 98% rename from toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js rename to toolkit/components/telemetry/tests/unit/test_TelemetryTimestamps.js index 915ce188e56..f8630579dd5 100644 --- a/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryTimestamps.js @@ -24,7 +24,7 @@ var gGlobalScope = this; function loadAddonManager() { let ns = {}; Cu.import("resource://gre/modules/Services.jsm", ns); - let head = "../../../mozapps/extensions/test/xpcshell/head_addons.js"; + let head = "../../../../mozapps/extensions/test/xpcshell/head_addons.js"; let file = do_get_file(head); let uri = ns.Services.io.newFileURI(file); ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index a0965a3f995..5ec31ced526 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -63,3 +63,5 @@ skip-if = os == "android" tags = addons [test_TelemetryReportingPolicy.js] tags = addons +[test_TelemetryTimestamps.js] +skip-if = toolkit == 'android' diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index 5e6b75228b0..691eb3e8633 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -73,7 +73,6 @@ EXTRA_JS_MODULES += [ 'SpatialNavigation.jsm', 'Sqlite.jsm', 'Task.jsm', - 'TelemetryTimestamps.jsm', 'Timer.jsm', 'Troubleshoot.jsm', 'UpdateUtils.jsm', diff --git a/toolkit/modules/tests/xpcshell/xpcshell.ini b/toolkit/modules/tests/xpcshell/xpcshell.ini index 39e94d98bd5..f4bf5d872ce 100644 --- a/toolkit/modules/tests/xpcshell/xpcshell.ini +++ b/toolkit/modules/tests/xpcshell/xpcshell.ini @@ -53,8 +53,6 @@ skip-if = toolkit == 'android' skip-if = toolkit == 'android' [test_task.js] skip-if = toolkit == 'android' -[test_TelemetryTimestamps.js] -skip-if = toolkit == 'android' [test_timer.js] skip-if = toolkit == 'android' [test_UpdateUtils_url.js] From d1a634bff74c271a1c3ffbf2b6a85a859ccbcc00 Mon Sep 17 00:00:00 2001 From: simplyblue Date: Fri, 6 Nov 2015 19:27:53 +0530 Subject: [PATCH 11/71] Bug 1175519 - Expose raw ping json data on about:telemetry. r=gfritzsche --- toolkit/content/aboutTelemetry.css | 22 +++++++++++++++++++ toolkit/content/aboutTelemetry.js | 15 ++++++++++++- toolkit/content/aboutTelemetry.xhtml | 6 +++++ .../en-US/chrome/global/aboutTelemetry.dtd | 4 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/toolkit/content/aboutTelemetry.css b/toolkit/content/aboutTelemetry.css index 43876480970..30797ad5e10 100644 --- a/toolkit/content/aboutTelemetry.css +++ b/toolkit/content/aboutTelemetry.css @@ -227,3 +227,25 @@ body[dir="rtl"] .copy-node { .filter-blocked { display: none; } + +#raw-ping-data-section { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + background-color:-moz-Dialog; +} + +#raw-ping-data { + background-color:white; + margin: 0px; +} + +#hide-raw-ping { + float: right; + cursor: pointer; + font-size: 20px; + background-color:#d8d8d8; + padding: 5px 10px; +} diff --git a/toolkit/content/aboutTelemetry.js b/toolkit/content/aboutTelemetry.js index fea5d79d804..dc5878c5b97 100644 --- a/toolkit/content/aboutTelemetry.js +++ b/toolkit/content/aboutTelemetry.js @@ -298,7 +298,10 @@ var PingPicker = { .addEventListener("click", () => this._movePingIndex(-1), false); document.getElementById("older-ping") .addEventListener("click", () => this._movePingIndex(1), false); - + document.getElementById("show-raw-ping") + .addEventListener("click", () => this._showRawPingData(), false); + document.getElementById("hide-raw-ping") + .addEventListener("click", () => this._hideRawPingData(), false); document.getElementById("choose-payload") .addEventListener("change", () => displayPingData(gPingData), false); }, @@ -448,6 +451,16 @@ var PingPicker = { this._renderPingList(ping.id); this._updateArchivedPingData(); }, + + _showRawPingData: function() { + let pre = document.getElementById("raw-ping-data"); + pre.textContent = JSON.stringify(gPingData, null, 2); + document.getElementById("raw-ping-data-section").classList.remove("hidden"); + }, + + _hideRawPingData: function() { + document.getElementById("raw-ping-data-section").classList.add("hidden"); + }, }; var GeneralData = { diff --git a/toolkit/content/aboutTelemetry.xhtml b/toolkit/content/aboutTelemetry.xhtml index e6e2ab4a906..3754dcdf775 100644 --- a/toolkit/content/aboutTelemetry.xhtml +++ b/toolkit/content/aboutTelemetry.xhtml @@ -58,6 +58,7 @@ &aboutTelemetry.showArchivedPingData;
    +
    &aboutTelemetry.showSubsessionData;
    @@ -96,6 +97,11 @@ + +

    &aboutTelemetry.generalDataSection;

    diff --git a/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd b/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd index 58771cca3d3..c68e9042016 100644 --- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd +++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd @@ -52,6 +52,10 @@ Choose ping: Older ping >> "> + + From 202aba9a45ea4fb518219b6675b4d7b168cfd583 Mon Sep 17 00:00:00 2001 From: rthyberg Date: Fri, 6 Nov 2015 03:54:00 +0100 Subject: [PATCH 12/71] Bug 1190801 - Moved loadSessionData and split saveSessionData from TelemetrySession to TelemetryStorage. r=gfritzsche --- .../components/telemetry/TelemetrySession.jsm | 154 ++---------------- .../components/telemetry/TelemetryStorage.jsm | 76 ++++++++- 2 files changed, 86 insertions(+), 144 deletions(-) diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index c7751af36e5..c07c76736fd 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -63,8 +63,6 @@ const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload"; const DATAREPORTING_DIRECTORY = "datareporting"; const ABORTED_SESSION_FILE_NAME = "aborted-session-ping"; -const SESSION_STATE_FILE_NAME = "session-state.json"; - // Whether the FHR/Telemetry unification features are enabled. // Changing this pref requires a restart. const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false); @@ -142,8 +140,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment", "resource://gre/modules/TelemetryEnvironment.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", - "resource://services-common/utils.js"); function generateUUID() { let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); @@ -263,97 +259,6 @@ var processInfo = { } }; -/** - * This object allows the serialisation of asynchronous tasks. This is particularly - * useful to serialise write access to the disk in order to prevent race conditions - * to corrupt the data being written. - * We are using this to synchronize saving to the file that TelemetrySession persists - * its state in. - */ -function SaveSerializer() { - this._queuedOperations = []; - this._queuedInProgress = false; - this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX); -} - -SaveSerializer.prototype = { - /** - * Enqueues an operation to a list to serialise their execution in order to prevent race - * conditions. Useful to serialise access to disk. - * - * @param {Function} aFunction The task function to enqueue. It must return a promise. - * @return {Promise} A promise resolved when the enqueued task completes. - */ - enqueueTask: function (aFunction) { - let promise = new Promise((resolve, reject) => - this._queuedOperations.push([aFunction, resolve, reject])); - - if (this._queuedOperations.length == 1) { - this._popAndPerformQueuedOperation(); - } - return promise; - }, - - /** - * Make sure to flush all the pending operations. - * @return {Promise} A promise resolved when all the pending operations have completed. - */ - flushTasks: function () { - let dummyTask = () => new Promise(resolve => resolve()); - return this.enqueueTask(dummyTask); - }, - - /** - * Pop a task from the queue, executes it and continue to the next one. - * This function recursively pops all the tasks. - */ - _popAndPerformQueuedOperation: function () { - if (!this._queuedOperations.length || this._queuedInProgress) { - return; - } - - this._log.trace("_popAndPerformQueuedOperation - Performing queued operation."); - let [func, resolve, reject] = this._queuedOperations.shift(); - let promise; - - try { - this._queuedInProgress = true; - promise = func(); - } catch (ex) { - this._log.warn("_popAndPerformQueuedOperation - Queued operation threw during execution. ", - ex); - this._queuedInProgress = false; - reject(ex); - this._popAndPerformQueuedOperation(); - return; - } - - if (!promise || typeof(promise.then) != "function") { - let msg = "Queued operation did not return a promise: " + func; - this._log.warn("_popAndPerformQueuedOperation - " + msg); - - this._queuedInProgress = false; - reject(new Error(msg)); - this._popAndPerformQueuedOperation(); - return; - } - - promise.then(result => { - this._log.trace("_popAndPerformQueuedOperation - Queued operation completed."); - this._queuedInProgress = false; - resolve(result); - this._popAndPerformQueuedOperation(); - }, - error => { - this._log.warn("_popAndPerformQueuedOperation - Failure when performing queued operation.", - error); - this._queuedInProgress = false; - reject(error); - this._popAndPerformQueuedOperation(); - }); - }, -}; - /** * TelemetryScheduler contains a single timer driving all regularly-scheduled * Telemetry related jobs. Having a single place with this logic simplifies @@ -762,8 +667,6 @@ var Impl = { _delayedInitTask: null, // The deferred promise resolved when the initialization task completes. _delayedInitTaskDeferred: null, - // Used to serialize session state writes to disk. - _stateSaveSerializer: new SaveSerializer(), get _log() { if (!this._logger) { @@ -1332,7 +1235,7 @@ var Impl = { this.startNewSubsession(); // Persist session data to disk (don't wait until it completes). let sessionData = this._getSessionDataObject(); - this._stateSaveSerializer.enqueueTask(() => this._saveSessionData(sessionData)); + TelemetryStorage.saveSessionData(sessionData); } return payload; @@ -1446,7 +1349,7 @@ var Impl = { yield this._loadSessionData(); // Update the session data to keep track of new subsessions created before // the initialization. - yield this._saveSessionData(this._getSessionDataObject()); + yield TelemetryStorage.saveSessionData(this._getSessionDataObject()); this.attachObservers(); this.gatherMemory(); @@ -1796,7 +1699,6 @@ var Impl = { if (Telemetry.isOfficialTelemetry || testing) { return Task.spawn(function*() { yield this.saveShutdownPings(); - yield this._stateSaveSerializer.flushTasks(); if (IS_UNIFIED_TELEMETRY) { yield TelemetryController.removeAbortedSessionPing(); @@ -1857,40 +1759,23 @@ var Impl = { return promise; }, - /** - * Loads session data from the session data file. - * @return {Promise} A promise which is resolved with a true argument when - * loading has completed, with false otherwise. + /** Loads session data from the session data file. + * @return {Promise} A promise which is resolved with an object when + * loading has completed, with null otherwise. */ _loadSessionData: Task.async(function* () { - const dataFile = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIRECTORY, - SESSION_STATE_FILE_NAME); + let data = yield TelemetryStorage.loadSessionData(); - let content; - try { - content = yield OS.File.read(dataFile, { encoding: "utf-8" }); - } catch (ex) { - this._log.info("_loadSessionData - can not load session data file", ex); - Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_LOAD").add(1); - return false; + if (!data) { + return null; } - let data; - try { - data = JSON.parse(content); - } catch (ex) { - this._log.error("_loadSessionData - failed to parse session data", ex); - Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_PARSE").add(1); - return false; - } - - if (!data || - !("profileSubsessionCounter" in data) || + if (!("profileSubsessionCounter" in data) || !(typeof(data.profileSubsessionCounter) == "number") || !("subsessionId" in data) || !("sessionId" in data)) { this._log.error("_loadSessionData - session data is invalid"); Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_VALIDATION").add(1); - return false; + return null; } this._previousSessionId = data.sessionId; @@ -1900,8 +1785,7 @@ var Impl = { // 1 - the current subsessions. this._profileSubsessionCounter = data.profileSubsessionCounter + this._subsessionCounter; - - return true; + return data; }), /** @@ -1915,22 +1799,6 @@ var Impl = { }; }, - /** - * Saves session data to disk. - */ - _saveSessionData: Task.async(function* (sessionData) { - let dataDir = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIRECTORY); - yield OS.File.makeDir(dataDir); - - let filePath = OS.Path.join(dataDir, SESSION_STATE_FILE_NAME); - try { - yield CommonUtils.writeJSON(sessionData, filePath); - } catch(e) { - this._log.error("_saveSessionData - Failed to write session data to " + filePath, e); - Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_SAVE").add(1); - } - }), - _onEnvironmentChange: function(reason, oldEnvironment) { this._log.trace("_onEnvironmentChange", reason); let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true); diff --git a/toolkit/components/telemetry/TelemetryStorage.jsm b/toolkit/components/telemetry/TelemetryStorage.jsm index 4be8e58013a..b4d56c79bd6 100644 --- a/toolkit/components/telemetry/TelemetryStorage.jsm +++ b/toolkit/components/telemetry/TelemetryStorage.jsm @@ -32,6 +32,7 @@ const DATAREPORTING_DIR = "datareporting"; const PINGS_ARCHIVE_DIR = "archived"; const ABORTED_SESSION_FILE_NAME = "aborted-session-ping"; const DELETION_PING_FILE_NAME = "pending-deletion-ping"; +const SESSION_STATE_FILE_NAME = "session-state.json"; XPCOMUtils.defineLazyGetter(this, "gDataReportingDir", function() { return OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIR); @@ -45,7 +46,8 @@ XPCOMUtils.defineLazyGetter(this, "gAbortedSessionFilePath", function() { XPCOMUtils.defineLazyGetter(this, "gDeletionPingFilePath", function() { return OS.Path.join(gDataReportingDir, DELETION_PING_FILE_NAME); }); - +XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", + "resource://services-common/utils.js"); // Maxmimum time, in milliseconds, archive pings should be retained. const MAX_ARCHIVED_PINGS_RETENTION_MS = 180 * 24 * 60 * 60 * 1000; // 180 days @@ -228,6 +230,23 @@ this.TelemetryStorage = { return TelemetryStorageImpl.savePendingPing(ping); }, + /** + * Saves session data to disk. + * @param {Object} sessionData The session data. + * @return {Promise} Resolved when the data was saved. + */ + saveSessionData: function(sessionData) { + return TelemetryStorageImpl.saveSessionData(sessionData); + }, + + /** + * Loads session data from a session data file. + * @return {Promise} Resolved with the session data in object form. + */ + loadSessionData: function() { + return TelemetryStorageImpl.loadSessionData(); + }, + /** * Load a pending ping from disk by id. * @@ -516,6 +535,8 @@ var TelemetryStorageImpl = { _abortedSessionSerializer: new SaveSerializer(), // Used to serialize deletion ping writes to disk. _deletionPingSerializer: new SaveSerializer(), + // Used to serialize session state writes to disk. + _stateSaveSerializer: new SaveSerializer(), // Tracks the archived pings in a Map of (id -> {timestampCreated, type}). // We use this to cache info on archived pings to avoid scanning the disk more than once. @@ -679,6 +700,59 @@ var TelemetryStorageImpl = { } }), + /** + * Saves session data to disk. + */ + saveSessionData: function(sessionData) { + return this._stateSaveSerializer.enqueueTask(() => this._saveSessionData(sessionData)); + }, + + _saveSessionData: Task.async(function* (sessionData) { + let dataDir = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIR); + yield OS.File.makeDir(dataDir); + + let filePath = OS.Path.join(gDataReportingDir, SESSION_STATE_FILE_NAME); + try { + yield CommonUtils.writeJSON(sessionData, filePath); + } catch(e) { + this._log.error("_saveSessionData - Failed to write session data to " + filePath, e); + Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_SAVE").add(1); + } + }), + + /** + * Loads session data from the session data file. + * @return {Promise} A promise resolved with an object on success, + * with null otherwise. + */ + loadSessionData: function() { + return this._stateSaveSerializer.enqueueTask(() => this._loadSessionData()); + }, + + _loadSessionData: Task.async(function* () { + const dataFile = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIR, + SESSION_STATE_FILE_NAME); + let content; + try { + content = yield OS.File.read(dataFile, { encoding: "utf-8" }); + } catch (ex) { + this._log.info("_loadSessionData - can not load session data file", ex); + Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_LOAD").add(1); + return null; + } + + let data; + try { + data = JSON.parse(content); + } catch (ex) { + this._log.error("_loadSessionData - failed to parse session data", ex); + Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_PARSE").add(1); + return null; + } + + return data; + }), + /** * Remove an archived ping from disk. * From 374292883246bcb9cb25ed79c8a17455f76c2b35 Mon Sep 17 00:00:00 2001 From: Edgar Chen Date: Tue, 15 Sep 2015 14:06:51 +0800 Subject: [PATCH 13/71] Bug 1205221 - Fix chrome.tabs.onUpdate event doesn't be fired correctly when tab's attribute is changed. r=billm --- browser/components/extensions/ext-tabs.js | 6 +- browser/components/extensions/ext-utils.js | 16 ++-- .../browser/browser_ext_tabs_onUpdated.js | 90 +++++++++++++++++++ 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/browser/components/extensions/ext-tabs.js b/browser/components/extensions/ext-tabs.js index b159c4e6ab1..1d778e38834 100644 --- a/browser/components/extensions/ext-tabs.js +++ b/browser/components/extensions/ext-tabs.js @@ -127,7 +127,7 @@ extensions.registerAPI((extension, context) => { } }; - WindowListManager.addOpenListener(windowListener, false); + WindowListManager.addOpenListener(windowListener); AllWindowEvents.addListener("TabOpen", listener); return () => { WindowListManager.removeOpenListener(windowListener); @@ -195,7 +195,9 @@ extensions.registerAPI((extension, context) => { let tab = gBrowser.getTabForBrowser(browser); let tabId = TabManager.getId(tab); let [needed, changeInfo] = sanitize(extension, {status}); - fire(tabId, changeInfo, TabManager.convert(extension, tab)); + if (needed) { + fire(tabId, changeInfo, TabManager.convert(extension, tab)); + } }, onLocationChange(browser, webProgress, request, locationURI, flags) { diff --git a/browser/components/extensions/ext-utils.js b/browser/components/extensions/ext-utils.js index a57126eb121..6d4c95074b9 100644 --- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -424,7 +424,7 @@ global.WindowListManager = { } }, - addOpenListener(listener, fireOnExisting = true) { + addOpenListener(listener) { if (this._openListeners.length == 0 && this._closeListeners.length == 0) { Services.ww.registerNotification(this); } @@ -433,8 +433,6 @@ global.WindowListManager = { for (let window of this.browserWindows(true)) { if (window.document.readyState != "complete") { window.addEventListener("load", this); - } else if (fireOnExisting) { - listener(window); } } }, @@ -462,7 +460,7 @@ global.WindowListManager = { handleEvent(event) { let window = event.target.defaultView; - window.removeEventListener("load", this.loadListener); + window.removeEventListener("load", this); if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") { return; } @@ -504,7 +502,9 @@ global.AllWindowEvents = { return WindowListManager.addCloseListener(listener); } - let needOpenListener = this._listeners.size == 0; + if (this._listeners.size == 0) { + WindowListManager.addOpenListener(this.openListener); + } if (!this._listeners.has(type)) { this._listeners.set(type, new Set()); @@ -512,10 +512,7 @@ global.AllWindowEvents = { let list = this._listeners.get(type); list.add(listener); - if (needOpenListener) { - WindowListManager.addOpenListener(this.openListener, false); - } - + // Register listener on all existing windows. for (let window of WindowListManager.browserWindows()) { this.addWindowListener(window, type, listener); } @@ -537,6 +534,7 @@ global.AllWindowEvents = { } } + // Unregister listener from all existing windows. for (let window of WindowListManager.browserWindows()) { if (type == "progress") { window.gBrowser.removeTabsProgressListener(listener); diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js index 9fe6db6852b..b364902242e 100644 --- a/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js +++ b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js @@ -76,3 +76,93 @@ add_task(function* () { yield BrowserTestUtils.closeWindow(win1); }); + +function* do_test_update(background) { + let win1 = yield BrowserTestUtils.openNewBrowserWindow(); + + yield focusWindow(win1); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "permissions": ["tabs"] + }, + + background: background, + }); + + yield Promise.all([ + yield extension.startup(), + yield extension.awaitFinish("finish") + ]); + + yield extension.unload(); + + yield BrowserTestUtils.closeWindow(win1); +} + +add_task(function* test_pinned() { + yield do_test_update(function background() { + // Create a new tab for testing update. + browser.tabs.create(null, function(tab) { + browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) { + // Check callback + browser.test.assertEq(tabId, tab.id, "Check tab id"); + browser.test.log("onUpdate: " + JSON.stringify(changeInfo)); + if ("pinned" in changeInfo) { + browser.test.assertTrue(changeInfo.pinned, "Check changeInfo.pinned"); + browser.tabs.onUpdated.removeListener(onUpdated); + // Remove created tab. + browser.tabs.remove(tabId); + browser.test.notifyPass("finish"); + return; + } + }); + browser.tabs.update(tab.id, {pinned: true}); + }); + }); +}); + +add_task(function* test_unpinned() { + yield do_test_update(function background() { + // Create a new tab for testing update. + browser.tabs.create({pinned: true}, function(tab) { + browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) { + // Check callback + browser.test.assertEq(tabId, tab.id, "Check tab id"); + browser.test.log("onUpdate: " + JSON.stringify(changeInfo)); + if ("pinned" in changeInfo) { + browser.test.assertFalse(changeInfo.pinned, "Check changeInfo.pinned"); + browser.tabs.onUpdated.removeListener(onUpdated); + // Remove created tab. + browser.tabs.remove(tabId); + browser.test.notifyPass("finish"); + return; + } + }); + browser.tabs.update(tab.id, {pinned: false}); + }); + }); +}); + +add_task(function* test_url() { + yield do_test_update(function background() { + // Create a new tab for testing update. + browser.tabs.create(null, function(tab) { + browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) { + // Check callback + browser.test.assertEq(tabId, tab.id, "Check tab id"); + browser.test.log("onUpdate: " + JSON.stringify(changeInfo)); + if ("url" in changeInfo) { + browser.test.assertEq("about:preferences", changeInfo.url, + "Check changeInfo.url"); + browser.tabs.onUpdated.removeListener(onUpdated); + // Remove created tab. + browser.tabs.remove(tabId); + browser.test.notifyPass("finish"); + return; + } + }); + browser.tabs.update(tab.id, {url: "about:preferences"}); + }); + }); +}); From 6c09e5db5a3a3b4c6754db20e4bfa7ce31e815f3 Mon Sep 17 00:00:00 2001 From: Johann Hofmann Date: Wed, 4 Nov 2015 22:29:31 +0100 Subject: [PATCH 14/71] Bug 1217886 - Implement chrome.storage.local.clear. r=wmccloskey --- .../extensions/ExtensionStorage.jsm | 35 +++++++++++++------ toolkit/components/extensions/ext-storage.js | 7 ++++ .../test/mochitest/test_ext_storage.html | 18 ++++++++++ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/toolkit/components/extensions/ExtensionStorage.jsm b/toolkit/components/extensions/ExtensionStorage.jsm index 02e496a56ad..c09e5896540 100644 --- a/toolkit/components/extensions/ExtensionStorage.jsm +++ b/toolkit/components/extensions/ExtensionStorage.jsm @@ -81,12 +81,7 @@ this.ExtensionStorage = { extData[prop] = items[prop]; } - let listeners = this.listeners.get(extensionId); - if (listeners) { - for (let listener of listeners) { - listener(changes); - } - } + this.notifyListeners(extensionId, changes); return this.write(extensionId); }); @@ -106,13 +101,24 @@ this.ExtensionStorage = { delete extData[prop]; } - let listeners = this.listeners.get(extensionId); - if (listeners) { - for (let listener of listeners) { - listener(changes); + this.notifyListeners(extensionId, changes); + + return this.write(extensionId); + }); + }, + + clear(extensionId) { + return this.read(extensionId).then(extData => { + let changes = {}; + if (extData) { + for (let prop of Object.keys(extData)) { + changes[prop] = {oldValue: extData[prop]}; + delete extData[prop]; } } + this.notifyListeners(extensionId, changes); + return this.write(extensionId); }); }, @@ -158,6 +164,15 @@ this.ExtensionStorage = { listeners.delete(listener); }, + notifyListeners(extensionId, changes) { + let listeners = this.listeners.get(extensionId); + if (listeners) { + for (let listener of listeners) { + listener(changes); + } + } + }, + init() { Services.obs.addObserver(this, "extension-invalidate-storage-cache", false); Services.obs.addObserver(this, "xpcom-shutdown", false); diff --git a/toolkit/components/extensions/ext-storage.js b/toolkit/components/extensions/ext-storage.js index ae59b87f50e..ed91dbd88ff 100644 --- a/toolkit/components/extensions/ext-storage.js +++ b/toolkit/components/extensions/ext-storage.js @@ -33,6 +33,13 @@ extensions.registerPrivilegedAPI("storage", (extension, context) => { } }); }, + clear: function(callback) { + ExtensionStorage.clear(extension.id).then(() => { + if (callback) { + runSafe(context, callback); + } + }); + }, }, onChanged: new EventManager(context, "storage.local.onChanged", fire => { diff --git a/toolkit/components/extensions/test/mochitest/test_ext_storage.html b/toolkit/components/extensions/test/mochitest/test_ext_storage.html index 18075848977..4b982da7442 100644 --- a/toolkit/components/extensions/test/mochitest/test_ext_storage.html +++ b/toolkit/components/extensions/test/mochitest/test_ext_storage.html @@ -31,6 +31,12 @@ function backgroundScript() { }); } + function clear(items) { + return new Promise(resolve => { + browser.storage.local.clear(resolve); + }); + } + function check(prop, value) { return get(null).then(data => { browser.test.assertEq(data[prop], value, "null getter worked for " + prop); @@ -110,6 +116,18 @@ function backgroundScript() { browser.test.assertFalse("test-prop1" in data, "prop1 absent"); browser.test.assertFalse("test-prop2" in data, "prop2 absent"); + // test storage.clear + }).then(() => { + return set({"test-prop1": "value1", "test-prop2": "value2"}); + }).then(() => { + return clear(); + }).then(() => { + checkChanges({"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}); + return get(["test-prop1", "test-prop2"]); + }).then(data => { + browser.test.assertFalse("test-prop1" in data, "prop1 absent"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent"); + // Test cache invalidation. }).then(() => { return set({"test-prop1": "value1", "test-prop2": "value2"}); From 5cc3390a26604fff280d510df652f2678479566d Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Sun, 1 Nov 2015 15:34:00 +0100 Subject: [PATCH 15/71] Bug 1218364 - windowless browser windows should not crash on Troubleshoot. r=billm, r=jrmuizel --- gfx/layers/client/ClientLayerManager.cpp | 1 + gfx/tests/browser/browser.ini | 4 ++ .../browser_windowless_troubleshoot_crash.js | 45 +++++++++++++++++++ gfx/tests/moz.build | 1 + toolkit/modules/Troubleshoot.jsm | 6 ++- 5 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 gfx/tests/browser/browser.ini create mode 100644 gfx/tests/browser/browser_windowless_troubleshoot_crash.js diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp index 2141347e951..c67b4a421d1 100644 --- a/gfx/layers/client/ClientLayerManager.cpp +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -760,6 +760,7 @@ void ClientLayerManager::GetBackendName(nsAString& aName) { switch (mForwarder->GetCompositorBackendType()) { + case LayersBackend::LAYERS_NONE: aName.AssignLiteral("None"); return; case LayersBackend::LAYERS_BASIC: aName.AssignLiteral("Basic"); return; case LayersBackend::LAYERS_OPENGL: aName.AssignLiteral("OpenGL"); return; case LayersBackend::LAYERS_D3D9: aName.AssignLiteral("Direct3D 9"); return; diff --git a/gfx/tests/browser/browser.ini b/gfx/tests/browser/browser.ini new file mode 100644 index 00000000000..0a1902f0eab --- /dev/null +++ b/gfx/tests/browser/browser.ini @@ -0,0 +1,4 @@ +[DEFAULT] +support-files = + +[browser_windowless_troubleshoot_crash.js] diff --git a/gfx/tests/browser/browser_windowless_troubleshoot_crash.js b/gfx/tests/browser/browser_windowless_troubleshoot_crash.js new file mode 100644 index 00000000000..ef5c39d5e5c --- /dev/null +++ b/gfx/tests/browser/browser_windowless_troubleshoot_crash.js @@ -0,0 +1,45 @@ +let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); + +add_task(function* test_windowlessBrowserTroubleshootCrash() { + let webNav = Services.appShell.createWindowlessBrowser(false); + + let onLoaded = new Promise((resolve, reject) => { + let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell); + let listener = { + observe(contentWindow, topic, data) { + let observedDocShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .sameTypeRootTreeItem + .QueryInterface(Ci.nsIDocShell); + if (docShell === observedDocShell) { + Services.obs.removeObserver(listener, "content-document-global-created", false); + resolve(); + } + } + } + Services.obs.addObserver(listener, "content-document-global-created", false); + }); + webNav.loadURI("about:blank", 0, null, null, null); + + yield onLoaded; + + let winUtils = webNav.document.defaultView. + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + is(winUtils.layerManagerType, "None", "windowless browser's layerManagerType should be 'None'"); + + ok(true, "not crashed"); + + var Troubleshoot = Cu.import("resource://gre/modules/Troubleshoot.jsm", {}).Troubleshoot; + var data = yield new Promise((resolve, reject) => { + Troubleshoot.snapshot((data) => { + resolve(data); + }); + }); + + is(data.graphics.numTotalWindows, 2, "windowless browser window should not be counted as windows in the troubleshoot graphics report"); + is(data.graphics.numAcceleratedWindows, 0, "windowless browser window should not be counted as an accelerated window"); + is(data.graphics.windowLayerManagerType, "Basic", "windowless browser window should not set windowLayerManagerType to 'None'"); +}); diff --git a/gfx/tests/moz.build b/gfx/tests/moz.build index b985130de0c..696ca9a9b08 100644 --- a/gfx/tests/moz.build +++ b/gfx/tests/moz.build @@ -6,3 +6,4 @@ XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini'] +BROWSER_CHROME_MANIFESTS += ['browser/browser.ini'] diff --git a/toolkit/modules/Troubleshoot.jsm b/toolkit/modules/Troubleshoot.jsm index 159bc66b9b7..bf7f872db88 100644 --- a/toolkit/modules/Troubleshoot.jsm +++ b/toolkit/modules/Troubleshoot.jsm @@ -307,11 +307,15 @@ var dataProviders = { data.numAcceleratedWindows = 0; let winEnumer = Services.ww.getWindowEnumerator(); while (winEnumer.hasMoreElements()) { - data.numTotalWindows++; let winUtils = winEnumer.getNext(). QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDOMWindowUtils); try { + // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report + if (winUtils.layerManagerType == "None") { + continue; + } + data.numTotalWindows++; data.windowLayerManagerType = winUtils.layerManagerType; data.windowLayerManagerRemote = winUtils.layerManagerRemote; data.supportsHardwareH264 = winUtils.supportsHardwareH264Decoding; From 3c49b8b50e76c586b7f6f729f40db2fe0b3a26c1 Mon Sep 17 00:00:00 2001 From: Boris Kudryavtsev Date: Sun, 8 Nov 2015 00:30:00 +0100 Subject: [PATCH 16/71] Bug 1219861 - Remove not-allowed cursor on disabled buttons on in-content pages. r=dao --- browser/themes/shared/aboutCertError.css | 7 ------- toolkit/themes/shared/in-content/common.inc.css | 1 - 2 files changed, 8 deletions(-) diff --git a/browser/themes/shared/aboutCertError.css b/browser/themes/shared/aboutCertError.css index e406eec1f3f..6cfe41cdbc4 100644 --- a/browser/themes/shared/aboutCertError.css +++ b/browser/themes/shared/aboutCertError.css @@ -53,13 +53,6 @@ body { flex: 1; } -/* Pressing the retry button will cause the cursor to flicker from a pointer to - * not-allowed. Override the disabled cursor behaviour since we will never show - * the button disabled as the initial state. Remove this in Bug 1219861. */ -button:disabled { - cursor: pointer; -} - #returnButton { background-color: var(--in-content-primary-button-background); border: none; diff --git a/toolkit/themes/shared/in-content/common.inc.css b/toolkit/themes/shared/in-content/common.inc.css index 38731ca4e2b..1f37ce2cf23 100644 --- a/toolkit/themes/shared/in-content/common.inc.css +++ b/toolkit/themes/shared/in-content/common.inc.css @@ -210,7 +210,6 @@ html|button:disabled, xul|button[disabled="true"], xul|colorpicker[type="button"][disabled="true"], xul|menulist[disabled="true"] { - cursor: not-allowed; opacity: 0.5; } From 27afcdbe81f7358e72f143288368957b5c061125 Mon Sep 17 00:00:00 2001 From: "Carsten \"Tomcat\" Book" Date: Tue, 10 Nov 2015 14:27:12 +0100 Subject: [PATCH 17/71] Backed out changeset 55b6a42400b0 (bug 1218364) for browser_windowless_troubleshoot_crash.js test failures --- gfx/layers/client/ClientLayerManager.cpp | 1 - gfx/tests/browser/browser.ini | 4 -- .../browser_windowless_troubleshoot_crash.js | 45 ------------------- gfx/tests/moz.build | 1 - toolkit/modules/Troubleshoot.jsm | 6 +-- 5 files changed, 1 insertion(+), 56 deletions(-) delete mode 100644 gfx/tests/browser/browser.ini delete mode 100644 gfx/tests/browser/browser_windowless_troubleshoot_crash.js diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp index c67b4a421d1..2141347e951 100644 --- a/gfx/layers/client/ClientLayerManager.cpp +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -760,7 +760,6 @@ void ClientLayerManager::GetBackendName(nsAString& aName) { switch (mForwarder->GetCompositorBackendType()) { - case LayersBackend::LAYERS_NONE: aName.AssignLiteral("None"); return; case LayersBackend::LAYERS_BASIC: aName.AssignLiteral("Basic"); return; case LayersBackend::LAYERS_OPENGL: aName.AssignLiteral("OpenGL"); return; case LayersBackend::LAYERS_D3D9: aName.AssignLiteral("Direct3D 9"); return; diff --git a/gfx/tests/browser/browser.ini b/gfx/tests/browser/browser.ini deleted file mode 100644 index 0a1902f0eab..00000000000 --- a/gfx/tests/browser/browser.ini +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -support-files = - -[browser_windowless_troubleshoot_crash.js] diff --git a/gfx/tests/browser/browser_windowless_troubleshoot_crash.js b/gfx/tests/browser/browser_windowless_troubleshoot_crash.js deleted file mode 100644 index ef5c39d5e5c..00000000000 --- a/gfx/tests/browser/browser_windowless_troubleshoot_crash.js +++ /dev/null @@ -1,45 +0,0 @@ -let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); - -add_task(function* test_windowlessBrowserTroubleshootCrash() { - let webNav = Services.appShell.createWindowlessBrowser(false); - - let onLoaded = new Promise((resolve, reject) => { - let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); - let listener = { - observe(contentWindow, topic, data) { - let observedDocShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .sameTypeRootTreeItem - .QueryInterface(Ci.nsIDocShell); - if (docShell === observedDocShell) { - Services.obs.removeObserver(listener, "content-document-global-created", false); - resolve(); - } - } - } - Services.obs.addObserver(listener, "content-document-global-created", false); - }); - webNav.loadURI("about:blank", 0, null, null, null); - - yield onLoaded; - - let winUtils = webNav.document.defaultView. - QueryInterface(Ci.nsIInterfaceRequestor). - getInterface(Ci.nsIDOMWindowUtils); - is(winUtils.layerManagerType, "None", "windowless browser's layerManagerType should be 'None'"); - - ok(true, "not crashed"); - - var Troubleshoot = Cu.import("resource://gre/modules/Troubleshoot.jsm", {}).Troubleshoot; - var data = yield new Promise((resolve, reject) => { - Troubleshoot.snapshot((data) => { - resolve(data); - }); - }); - - is(data.graphics.numTotalWindows, 2, "windowless browser window should not be counted as windows in the troubleshoot graphics report"); - is(data.graphics.numAcceleratedWindows, 0, "windowless browser window should not be counted as an accelerated window"); - is(data.graphics.windowLayerManagerType, "Basic", "windowless browser window should not set windowLayerManagerType to 'None'"); -}); diff --git a/gfx/tests/moz.build b/gfx/tests/moz.build index 696ca9a9b08..b985130de0c 100644 --- a/gfx/tests/moz.build +++ b/gfx/tests/moz.build @@ -6,4 +6,3 @@ XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini'] -BROWSER_CHROME_MANIFESTS += ['browser/browser.ini'] diff --git a/toolkit/modules/Troubleshoot.jsm b/toolkit/modules/Troubleshoot.jsm index bf7f872db88..159bc66b9b7 100644 --- a/toolkit/modules/Troubleshoot.jsm +++ b/toolkit/modules/Troubleshoot.jsm @@ -307,15 +307,11 @@ var dataProviders = { data.numAcceleratedWindows = 0; let winEnumer = Services.ww.getWindowEnumerator(); while (winEnumer.hasMoreElements()) { + data.numTotalWindows++; let winUtils = winEnumer.getNext(). QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDOMWindowUtils); try { - // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report - if (winUtils.layerManagerType == "None") { - continue; - } - data.numTotalWindows++; data.windowLayerManagerType = winUtils.layerManagerType; data.windowLayerManagerRemote = winUtils.layerManagerRemote; data.supportsHardwareH264 = winUtils.supportsHardwareH264Decoding; From 8c779918ba285ceacafc2b25557e630316009fe4 Mon Sep 17 00:00:00 2001 From: "Carsten \"Tomcat\" Book" Date: Tue, 10 Nov 2015 14:30:39 +0100 Subject: [PATCH 18/71] Backed out changeset f1d90a17ebcc (bug 1216234) for dt6 test failures --- devtools/client/styleinspector/rule-view.js | 7 +-- .../browser_ruleview_user-agent-styles.js | 8 --- devtools/server/actors/styles.js | 21 +++++-- .../tests/mochitest/test_styles-applied.html | 2 +- devtools/shared/styleinspector/css-logic.js | 42 ++++++++++++++ layout/inspector/inDOMUtils.cpp | 23 -------- layout/inspector/inIDOMUtils.idl | 13 +---- layout/inspector/tests/mochitest.ini | 1 - .../tests/test_getCSSPseudoElementNames.html | 57 ------------------- 9 files changed, 63 insertions(+), 111 deletions(-) delete mode 100644 layout/inspector/tests/test_getCSSPseudoElementNames.html diff --git a/devtools/client/styleinspector/rule-view.js b/devtools/client/styleinspector/rule-view.js index b89e4dbce22..88948d2fe2b 100644 --- a/devtools/client/styleinspector/rule-view.js +++ b/devtools/client/styleinspector/rule-view.js @@ -13,7 +13,8 @@ const {setTimeout, clearTimeout} = const {CssLogic} = require("devtools/shared/styleinspector/css-logic"); const {InplaceEditor, editableField, editableItem} = require("devtools/client/shared/inplace-editor"); -const {ELEMENT_STYLE} = require("devtools/server/actors/styles"); +const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = + require("devtools/server/actors/styles"); const {OutputParser} = require("devtools/shared/output-parser"); const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils"); const { @@ -4058,7 +4059,3 @@ XPCOMUtils.defineLazyGetter(this, "domUtils", function() { loader.lazyGetter(this, "AutocompletePopup", function() { return require("devtools/client/shared/autocomplete-popup").AutocompletePopup; }); - -loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => { - return domUtils.getCSSPseudoElementNames(); -}); diff --git a/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js b/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js index 43f1f329aa4..da3fef8ddd0 100644 --- a/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js +++ b/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js @@ -160,14 +160,6 @@ function* compareAppliedStylesWithUI(inspector, view, filter) { filter: filter }); - // We may see multiple entries that map to a given rule; filter the - // duplicates here to match what the UI does. - let entryMap = new Map(); - for (let entry of entries) { - entryMap.set(entry.rule, entry); - } - entries = [...entryMap.values()]; - let elementStyle = view._elementStyle; is(elementStyle.rules.length, entries.length, "Should have correct number of rules (" + entries.length + ")"); diff --git a/devtools/server/actors/styles.js b/devtools/server/actors/styles.js index 88b264db444..f5190a9546a 100644 --- a/devtools/server/actors/styles.js +++ b/devtools/server/actors/styles.js @@ -12,6 +12,7 @@ const {Arg, Option, method, RetVal, types} = protocol; const events = require("sdk/event/core"); const {Class} = require("sdk/core/heritage"); const {LongStringActor} = require("devtools/server/actors/string"); +const {PSEUDO_ELEMENT_SET} = require("devtools/shared/styleinspector/css-logic"); // This will also add the "stylesheet" actor type for protocol.js to recognize const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = @@ -34,12 +35,24 @@ loader.lazyGetter(this, "RuleRewriter", () => { const ELEMENT_STYLE = 100; exports.ELEMENT_STYLE = ELEMENT_STYLE; +// Not included since these are uneditable by the user. +// See https://hg.mozilla.org/mozilla-central/file/696a4ad5d011/layout/style/nsCSSPseudoElementList.h#l74 +PSEUDO_ELEMENT_SET.delete(":-moz-meter-bar"); +PSEUDO_ELEMENT_SET.delete(":-moz-list-bullet"); +PSEUDO_ELEMENT_SET.delete(":-moz-list-number"); +PSEUDO_ELEMENT_SET.delete(":-moz-focus-inner"); +PSEUDO_ELEMENT_SET.delete(":-moz-focus-outer"); +PSEUDO_ELEMENT_SET.delete(":-moz-math-anonymous"); +PSEUDO_ELEMENT_SET.delete(":-moz-math-stretchy"); + +const PSEUDO_ELEMENTS = Array.from(PSEUDO_ELEMENT_SET); + +exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENTS; + // When gathering rules to read for pseudo elements, we will skip // :before and :after, which are handled as a special case. -loader.lazyGetter(this, "PSEUDO_ELEMENTS_TO_READ", () => { - return DOMUtils.getCSSPseudoElementNames().filter(pseudo => { - return pseudo !== ":before" && pseudo !== ":after"; - }); +const PSEUDO_ELEMENTS_TO_READ = PSEUDO_ELEMENTS.filter(pseudo => { + return pseudo !== ":before" && pseudo !== ":after"; }); const XHTML_NS = "http://www.w3.org/1999/xhtml"; diff --git a/devtools/server/tests/mochitest/test_styles-applied.html b/devtools/server/tests/mochitest/test_styles-applied.html index 5afa1c30995..fc9f04f6ee6 100644 --- a/devtools/server/tests/mochitest/test_styles-applied.html +++ b/devtools/server/tests/mochitest/test_styles-applied.html @@ -85,7 +85,7 @@ addTest(function inheritedSystemStyles() { ok(!applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style"); is(applied[1].rule.type, 1, "Entry 1 should be a rule style"); - is(applied.length, 11, "Should have 11 rules."); + is(applied.length, 8, "Should have 8 rules."); }).then(runNextTest)); }); diff --git a/devtools/shared/styleinspector/css-logic.js b/devtools/shared/styleinspector/css-logic.js index c0b28d01f02..80f1feec647 100644 --- a/devtools/shared/styleinspector/css-logic.js +++ b/devtools/shared/styleinspector/css-logic.js @@ -43,6 +43,36 @@ const Services = require("Services"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { getRootBindingParent } = require("devtools/shared/layout/utils"); +var pseudos = new Set([ + ":after", + ":before", + ":first-letter", + ":first-line", + ":selection", + ":-moz-color-swatch", + ":-moz-focus-inner", + ":-moz-focus-outer", + ":-moz-list-bullet", + ":-moz-list-number", + ":-moz-math-anonymous", + ":-moz-math-stretchy", + ":-moz-meter-bar", + ":-moz-number-spin-box", + ":-moz-number-spin-down", + ":-moz-number-spin-up", + ":-moz-number-text", + ":-moz-number-wrapper", + ":-moz-placeholder", + ":-moz-progress-bar", + ":-moz-range-progress", + ":-moz-range-thumb", + ":-moz-range-track", + ":-moz-selection" +]); + +const PSEUDO_ELEMENT_SET = pseudos; +exports.PSEUDO_ELEMENT_SET = PSEUDO_ELEMENT_SET; + // This should be ok because none of the functions that use this should be used // on the worker thread, where Cu is not available. if (Cu) { @@ -1648,6 +1678,18 @@ CssSelector.prototype = { return this.cssRule.line; }, + /** + * Retrieve the pseudo-elements that we support. This list should match the + * elements specified in layout/style/nsCSSPseudoElementList.h + */ + get pseudoElements() + { + if (!CssSelector._pseudoElements) { + CssSelector._pseudoElements = PSEUDO_ELEMENT_SET; + } + return CssSelector._pseudoElements; + }, + /** * Retrieve specificity information for the current selector. * diff --git a/layout/inspector/inDOMUtils.cpp b/layout/inspector/inDOMUtils.cpp index f176168806b..dccd7ccef02 100644 --- a/layout/inspector/inDOMUtils.cpp +++ b/layout/inspector/inDOMUtils.cpp @@ -1191,29 +1191,6 @@ GetStatesForPseudoClass(const nsAString& aStatePseudo) return sPseudoClassStates[nsCSSPseudoClasses::GetPseudoType(atom)]; } -NS_IMETHODIMP -inDOMUtils::GetCSSPseudoElementNames(uint32_t* aLength, char16_t*** aNames) -{ - nsTArray array; - - for (int i = 0; i < nsCSSPseudoElements::ePseudo_PseudoElementCount; ++i) { - nsCSSPseudoElements::Type type = static_cast(i); - if (!nsCSSPseudoElements::PseudoElementIsUASheetOnly(type)) { - nsIAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type); - array.AppendElement(atom); - } - } - - *aLength = array.Length(); - char16_t** ret = - static_cast(moz_xmalloc(*aLength * sizeof(char16_t*))); - for (uint32_t i = 0; i < *aLength; ++i) { - ret[i] = ToNewUnicode(nsDependentAtomString(array[i])); - } - *aNames = ret; - return NS_OK; -} - NS_IMETHODIMP inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement, const nsAString &aPseudoClass) diff --git a/layout/inspector/inIDOMUtils.idl b/layout/inspector/inIDOMUtils.idl index ddc79710ade..daaff3a98eb 100644 --- a/layout/inspector/inIDOMUtils.idl +++ b/layout/inspector/inIDOMUtils.idl @@ -17,7 +17,7 @@ interface nsIDOMFontFaceList; interface nsIDOMRange; interface nsIDOMCSSStyleSheet; -[scriptable, uuid(ec3dc3d5-41d1-4d08-ace5-7e944de6614d)] +[scriptable, uuid(d67c0463-592e-4d7c-b67e-923ee3f6c643)] interface inIDOMUtils : nsISupports { // CSS utilities @@ -162,17 +162,6 @@ interface inIDOMUtils : nsISupports nsIDOMFontFaceList getUsedFontFaces(in nsIDOMRange aRange); - /** - * Get the names of all the supported pseudo-elements. - * Pseudo-elements which are only accepted in UA style sheets are - * not included. - * - * @param {unsigned long} aCount the number of items returned - * @param {wstring[]} aNames the names - */ - void getCSSPseudoElementNames([optional] out unsigned long aCount, - [retval, array, size_is(aCount)] out wstring aNames); - // pseudo-class style locking methods. aPseudoClass must be a valid pseudo-class // selector string, e.g. ":hover". ":-moz-any-link" and non-event-state // pseudo-classes are ignored. diff --git a/layout/inspector/tests/mochitest.ini b/layout/inspector/tests/mochitest.ini index 25209f57a67..4dd9acdd3a9 100644 --- a/layout/inspector/tests/mochitest.ini +++ b/layout/inspector/tests/mochitest.ini @@ -19,7 +19,6 @@ support-files = [test_color_to_rgba.html] [test_css_property_is_shorthand.html] [test_css_property_is_valid.html] -[test_getCSSPseudoElementNames.html] [test_getRelativeRuleLine.html] [test_get_all_style_sheets.html] [test_is_valid_css_color.html] diff --git a/layout/inspector/tests/test_getCSSPseudoElementNames.html b/layout/inspector/tests/test_getCSSPseudoElementNames.html deleted file mode 100644 index 6085a018c9a..00000000000 --- a/layout/inspector/tests/test_getCSSPseudoElementNames.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Test inDOMUtils::getCSSPseudoElementNames - - - - - -

    Test inDOMUtils::getCSSPseudoElementNames

    -

    - -
    -
    - - From 3624f5c89c82a4449a44d13cb3349988622cdc98 Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Thu, 5 Nov 2015 17:27:32 -0500 Subject: [PATCH 19/71] Bug 1221679 - Properly update settings header on locale change. r=liuche --- .../base/preferences/GeckoPreferences.java | 42 ++----------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/mobile/android/base/preferences/GeckoPreferences.java b/mobile/android/base/preferences/GeckoPreferences.java index 258744a6de2..fe69a9670eb 100644 --- a/mobile/android/base/preferences/GeckoPreferences.java +++ b/mobile/android/base/preferences/GeckoPreferences.java @@ -201,44 +201,10 @@ OnSharedPreferenceChangeListener } } - private void updateTitle(String newTitle) { - if (newTitle != null) { - Log.v(LOGTAG, "Setting activity title to " + newTitle); - setTitle(newTitle); - } - } - - private void updateTitle(int title) { - updateTitle(getString(title)); - } - /** - * This updates the title shown above the prefs fragment in - * a multi-pane view. + * We only call this method for pre-HC versions of Android. */ - private void updateBreadcrumbTitle(int title) { - final String newTitle = getString(title); - showBreadCrumbs(newTitle, newTitle); - } - private void updateTitleForPrefsResource(int res) { - // If we're a multi-pane view, the activity title is really - // the header bar above the fragment. - // Find out which fragment we're showing, and use that. - if (Versions.feature11Plus && isMultiPane()) { - int title = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, -1); - if (res == R.xml.preferences) { - // This should only occur when res == R.xml.preferences, - // but showing "Settings" is better than crashing or showing - // "Fennec". - updateActionBarTitle(R.string.settings_title); - } - - updateTitle(title); - updateBreadcrumbTitle(title); - return; - } - // At present we only need to do this for non-leaf prefs views // and the locale switcher itself. int title = -1; @@ -254,7 +220,7 @@ OnSharedPreferenceChangeListener title = R.string.pref_category_search; } if (title != -1) { - updateTitle(title); + setTitle(title); } } @@ -291,7 +257,7 @@ OnSharedPreferenceChangeListener } // Update the title to for the preference pane that we're currently showing. - updateTitle(R.string.pref_header_general); + setTitle(R.string.pref_category_language); // Don't finish the activity -- we just reloaded all of the // individual parts! -- but when it returns, make sure that the @@ -345,7 +311,7 @@ OnSharedPreferenceChangeListener // This is the default header, because it's the first one. // I know, this is an affront to all human decency. And yet. - updateTitle(getString(R.string.pref_header_general)); + setTitle(R.string.pref_header_general); } if (onIsMultiPane()) { From bd583af77c759befb8274f2b2112bcab064a1022 Mon Sep 17 00:00:00 2001 From: Ben Tian Date: Tue, 10 Nov 2015 11:34:11 +0800 Subject: [PATCH 20/71] Bug 1168298 - Support OBEX authentication procedure, r=shuang, r=mrbkap --- dom/base/nsGkAtomList.h | 1 + dom/bindings/Bindings.conf | 4 + .../bluedroid/BluetoothPbapManager.cpp | 132 +++++++++++++++++- .../bluedroid/BluetoothPbapManager.h | 62 +++++--- .../bluedroid/BluetoothServiceBluedroid.cpp | 38 +++++ .../bluedroid/BluetoothServiceBluedroid.h | 7 + dom/bluetooth/bluez/BluetoothDBusService.cpp | 11 ++ dom/bluetooth/bluez/BluetoothDBusService.h | 7 + dom/bluetooth/common/BluetoothCommon.h | 6 + dom/bluetooth/common/BluetoothService.h | 7 + dom/bluetooth/common/ObexBase.cpp | 8 ++ dom/bluetooth/common/ObexBase.h | 29 ++++ .../common/webapi/BluetoothAdapter.cpp | 22 ++- .../common/webapi/BluetoothAdapter.h | 24 +++- .../common/webapi/BluetoothObexAuthHandle.cpp | 96 +++++++++++++ .../common/webapi/BluetoothObexAuthHandle.h | 57 ++++++++ dom/bluetooth/ipc/BluetoothParent.cpp | 26 ++++ dom/bluetooth/ipc/BluetoothParent.h | 6 + .../ipc/BluetoothServiceChildProcess.cpp | 14 ++ .../ipc/BluetoothServiceChildProcess.h | 7 + dom/bluetooth/ipc/PBluetooth.ipdl | 11 ++ dom/bluetooth/moz.build | 2 + .../test/test_all_synthetic_events.html | 4 + .../mochitest/general/test_interfaces.html | 4 + dom/webidl/BluetoothAdapter.webidl | 3 + dom/webidl/BluetoothObexAuthEvent.webidl | 20 +++ dom/webidl/BluetoothObexAuthHandle.webidl | 20 +++ dom/webidl/moz.build | 2 + 28 files changed, 602 insertions(+), 28 deletions(-) create mode 100644 dom/bluetooth/common/webapi/BluetoothObexAuthHandle.cpp create mode 100644 dom/bluetooth/common/webapi/BluetoothObexAuthHandle.h create mode 100644 dom/webidl/BluetoothObexAuthEvent.webidl create mode 100644 dom/webidl/BluetoothObexAuthHandle.webidl diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 5ebbdc26ec7..f5c9e162344 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -843,6 +843,7 @@ GK_ATOM(onmapmessageupdatereq, "onmapmessageupdatereq") GK_ATOM(onnewrdsgroup, "onnewrdsgroup") GK_ATOM(onnotificationclick, "onnotificationclick") GK_ATOM(onnoupdate, "onnoupdate") +GK_ATOM(onobexpasswordreq, "onobexpasswordreq") GK_ATOM(onobsolete, "onobsolete") GK_ATOM(ononline, "ononline") GK_ATOM(onoffline, "onoffline") diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index c2ae1d1ae16..ea4e1c7f404 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -194,6 +194,10 @@ DOMInterfaces = { 'nativeType': 'mozilla::dom::bluetooth::BluetoothManager', }, +'BluetoothObexAuthHandle': { + 'nativeType': 'mozilla::dom::bluetooth::BluetoothObexAuthHandle', +}, + 'BluetoothPairingHandle': { 'nativeType': 'mozilla::dom::bluetooth::BluetoothPairingHandle', }, diff --git a/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp b/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp index 74bb7fab904..413eedccc16 100644 --- a/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothPbapManager.cpp @@ -23,6 +23,7 @@ #include "nsIInputStream.h" #include "nsIObserver.h" #include "nsIObserverService.h" +#include "nsNetCID.h" USING_BLUETOOTH_NAMESPACE using namespace mozilla; @@ -241,6 +242,18 @@ BluetoothPbapManager::ReceiveSocketData(BluetoothSocket* aSocket, return; } + // Section 3.5 "Authentication Procedure", IrOBEX 1.2 + // An user input password is required to reply to authentication + // challenge. The OBEX success response will be sent after gaia + // replies correct password. + if (pktHeaders.Has(ObexHeaderId::AuthChallenge)) { + ObexResponseCode response = NotifyPasswordRequest(pktHeaders); + if (response != ObexResponseCode::Success) { + ReplyError(response); + } + return; + } + // Save the max packet length from remote information mRemoteMaxPacketLength = BigEndian::readUint16(&data[5]); @@ -499,6 +512,51 @@ BluetoothPbapManager::NotifyPbapRequest(const ObexHeaderSet& aHeader) return ObexResponseCode::Success; } +ObexResponseCode +BluetoothPbapManager::NotifyPasswordRequest(const ObexHeaderSet& aHeader) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aHeader.Has(ObexHeaderId::AuthChallenge)); + + // Get authentication challenge data + int dataLength; + nsAutoArrayPtr dataPtr; + aHeader.GetAuthChallenge(dataPtr, &dataLength); + + // Get nonce from authentication challenge + // Section 3.5.1 "Digest Challenge", IrOBEX spec 1.2 + // The tag-length-value triplet of nonce is + // [tagId:1][length:1][nonce:16] + uint8_t offset = 0; + do { + uint8_t tagId = dataPtr[offset++]; + uint8_t length = dataPtr[offset++]; + + BT_LOGR("AuthChallenge header includes tagId %d", tagId); + if (tagId == ObexDigestChallenge::Nonce) { + memcpy(mRemoteNonce, &dataPtr[offset], DIGEST_LENGTH); + } + + offset += length; + } while (offset < dataLength); + + // Ensure bluetooth service is available + BluetoothService* bs = BluetoothService::Get(); + if (!bs) { + return ObexResponseCode::PreconditionFailed; + } + + // Notify gaia of authentiation challenge + // TODO: Append realm if 1) gaia needs to display it and + // 2) it's in authenticate challenge header + InfallibleTArray props; + bs->DistributeSignal(NS_LITERAL_STRING(OBEX_PASSWORD_REQ_ID), + NS_LITERAL_STRING(KEY_ADAPTER), + props); + + return ObexResponseCode::Success; +} + void BluetoothPbapManager::AppendNamedValueByTagId( const ObexHeaderSet& aHeader, @@ -660,7 +718,7 @@ BluetoothPbapManager::GetAddress(BluetoothAddress& aDeviceAddress) } void -BluetoothPbapManager::ReplyToConnect() +BluetoothPbapManager::ReplyToConnect(const nsAString& aPassword) { if (mConnected) { return; @@ -683,9 +741,69 @@ BluetoothPbapManager::ReplyToConnect() kPbapObexTarget.mUuid, sizeof(BluetoothUuid)); index += AppendHeaderConnectionId(&res[index], 0x01); + // Authentication response + if (!aPassword.IsEmpty()) { + // Section 3.5.2.1 "Request-digest", PBAP 1.2 + // The request-digest is required and calculated as follows: + // H(nonce ":" password) + uint32_t hashStringLength = DIGEST_LENGTH + aPassword.Length() + 1; + nsAutoArrayPtr hashString(new char[hashStringLength]); + + memcpy(hashString, mRemoteNonce, DIGEST_LENGTH); + hashString[DIGEST_LENGTH] = ':'; + memcpy(&hashString[DIGEST_LENGTH + 1], + NS_ConvertUTF16toUTF8(aPassword).get(), + aPassword.Length()); + MD5Hash(hashString, hashStringLength); + + // 2 tag-length-value triplets: + uint8_t digestResponse[(DIGEST_LENGTH + 2) * 2]; + int offset = AppendAppParameter(digestResponse, sizeof(digestResponse), + ObexDigestResponse::ReqDigest, + mHashRes, DIGEST_LENGTH); + offset += AppendAppParameter(&digestResponse[offset], + sizeof(digestResponse) - offset, + ObexDigestResponse::NonceChallenged, + mRemoteNonce, DIGEST_LENGTH); + + index += AppendAuthResponse(&res[index], kObexLeastMaxSize - index, + digestResponse, offset); + } + SendObexData(res, ObexResponseCode::Success, index); } +nsresult +BluetoothPbapManager::MD5Hash(char *buf, uint32_t len) +{ + nsresult rv; + + // Cache a reference to the nsICryptoHash instance since we'll be calling + // this function frequently. + if (!mVerifier) { + mVerifier = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + BT_LOGR("MD5Hash: no crypto hash!"); + return rv; + } + } + + rv = mVerifier->Init(nsICryptoHash::MD5); + if (NS_FAILED(rv)) return rv; + + rv = mVerifier->Update((unsigned char*)buf, len); + if (NS_FAILED(rv)) return rv; + + nsAutoCString hashString; + rv = mVerifier->Finish(false, hashString); + if (NS_FAILED(rv)) return rv; + + NS_ENSURE_STATE(hashString.Length() == sizeof(mHashRes)); + memcpy(mHashRes, hashString.get(), hashString.Length()); + + return rv; +} + void BluetoothPbapManager::ReplyToDisconnectOrAbort() { @@ -742,6 +860,18 @@ BluetoothPbapManager::PackPropertiesMask(uint8_t* aData, int aSize) return propSelector; } +void +BluetoothPbapManager::ReplyToAuthChallenge(const nsAString& aPassword) +{ + // Cancel authentication + if (aPassword.IsEmpty()) { + ReplyError(ObexResponseCode::Unauthorized); + } + + ReplyToConnect(aPassword); + AfterPbapConnected(); +} + bool BluetoothPbapManager::ReplyToPullPhonebook(BlobParent* aActor, uint16_t aPhonebookSize) diff --git a/dom/bluetooth/bluedroid/BluetoothPbapManager.h b/dom/bluetooth/bluedroid/BluetoothPbapManager.h index 58a64eb7ea0..736cc446614 100644 --- a/dom/bluetooth/bluedroid/BluetoothPbapManager.h +++ b/dom/bluetooth/bluedroid/BluetoothPbapManager.h @@ -12,8 +12,10 @@ #include "BluetoothSocketObserver.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/ipc/SocketBase.h" +#include "nsICryptoHash.h" #include "ObexBase.h" +class nsICryptoHash; class nsIInputStream; namespace mozilla { namespace dom { @@ -62,26 +64,36 @@ public: } static const int MAX_PACKET_LENGTH = 0xFFFE; + static const int DIGEST_LENGTH = 16; static BluetoothPbapManager* Get(); bool Listen(); /** - * Reply vCard object to the *IPC* 'pullphonebook' request. + * Reply to OBEX authenticate challenge with password. * - * @param aActor [in] a blob actor containing the vCard objects - * @param aPhonebookSize [in] the number of vCard indexes in the blob + * @param aPassword [in] the password known by only client and server. + * + * @return true if the response packet has been packed correctly and started + * to be sent to the remote device; false otherwise. + */ + void ReplyToAuthChallenge(const nsAString& aPassword); + + /** + * Reply vCard objects to IPC 'pullphonebook' request. + * + * @param aActor [in] blob actor of the vCard objects + * @param aPhonebookSize [in] number of vCard indexes in the blob * * @return true if the response packet has been packed correctly and started * to be sent to the remote device; false otherwise. */ bool ReplyToPullPhonebook(BlobParent* aActor, uint16_t aPhonebookSize); - /** - * Reply vCard object to the *in-process* 'pullphonebook' request. + * Reply vCard objects to in-process 'pullphonebook' request. * - * @param aBlob [in] a blob contained the vCard objects - * @param aPhonebookSize [in] the number of vCard indexes in the blob + * @param aBlob [in] blob of the vCard objects + * @param aPhonebookSize [in] number of vCard indexes in the blob * * @return true if the response packet has been packed correctly and started * to be sent to the remote device; false otherwise. @@ -89,21 +101,20 @@ public: bool ReplyToPullPhonebook(Blob* aBlob, uint16_t aPhonebookSize); /** - * Reply vCard object to the *IPC* 'pullvcardlisting' request. + * Reply vCard objects to IPC 'pullvcardlisting' request. * - * @param aActor [in] a blob actor containing the vCard objects - * @param aPhonebookSize [in] the number of vCard indexes in the blob + * @param aActor [in] blob actor of the vCard objects + * @param aPhonebookSize [in] number of vCard indexes in the blob * * @return true if the response packet has been packed correctly and started * to be sent to the remote device; false otherwise. */ bool ReplyToPullvCardListing(BlobParent* aActor, uint16_t aPhonebookSize); - /** - * Reply vCard object to the *in-process* 'pullvcardlisting' request. + * Reply vCard objects to in-process 'pullvcardlisting' request. * - * @param aBlob [in] a blob contained the vCard objects - * @param aPhonebookSize [in] the number of vCard indexes in the blob + * @param aBlob [in] blob of the vCard objects + * @param aPhonebookSize [in] number of vCard indexes in the blob * * @return true if the response packet has been packed correctly and started * to be sent to the remote device; false otherwise. @@ -111,19 +122,18 @@ public: bool ReplyToPullvCardListing(Blob* aBlob, uint16_t aPhonebookSize); /** - * Reply vCard object to the *IPC* 'pullvcardentry' request. + * Reply vCard object to IPC 'pullvcardentry' request. * - * @param aActor [in] a blob actor containing the vCard objects + * @param aActor [in] blob actor of the vCard object * * @return true if the response packet has been packed correctly and started * to be sent to the remote device; false otherwise. */ bool ReplyToPullvCardEntry(BlobParent* aActor); - /** - * Reply vCard object to the *in-process* 'pullvcardentry' request. + * Reply vCard object to in-process 'pullvcardentry' request. * - * @param aBlob [in] a blob contained the vCard objects + * @param aBlob [in] blob of the vCard object * * @return true if the response packet has been packed correctly and started * to be sent to the remote device; false otherwise. @@ -138,7 +148,7 @@ private: bool Init(); void HandleShutdown(); - void ReplyToConnect(); + void ReplyToConnect(const nsAString& aPassword = EmptyString()); void ReplyToDisconnectOrAbort(); void ReplyToSetPath(); bool ReplyToGet(uint16_t aPhonebookSize = 0); @@ -148,6 +158,7 @@ private: ObexResponseCode SetPhoneBookPath(const ObexHeaderSet& aHeader, uint8_t flags); ObexResponseCode NotifyPbapRequest(const ObexHeaderSet& aHeader); + ObexResponseCode NotifyPasswordRequest(const ObexHeaderSet& aHeader); void AppendNamedValueByTagId(const ObexHeaderSet& aHeader, InfallibleTArray& aValues, const AppParameterTag aTagId); @@ -159,9 +170,18 @@ private: bool GetInputStreamFromBlob(Blob* aBlob); void AfterPbapConnected(); void AfterPbapDisconnected(); + nsresult MD5Hash(char *buf, uint32_t len); // mHashRes stores the result /** - * Whether 'PhonebookSize' is required for the OBEX response + * The nonce for OBEX authentication procedure. + * Its value shall differ each time remote OBEX client sends it + */ + uint8_t mRemoteNonce[DIGEST_LENGTH]; + uint8_t mHashRes[DIGEST_LENGTH]; + nsCOMPtr mVerifier; + + /** + * Whether phonebook size is required for OBEX response */ bool mPhonebookSizeRequired; diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp index 10839ccd35b..1565892f381 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp @@ -1723,6 +1723,44 @@ BluetoothServiceBluedroid::IsScoConnected(BluetoothReplyRunnable* aRunnable) DispatchReplySuccess(aRunnable, BluetoothValue(hfp->IsScoConnected())); } +void +BluetoothServiceBluedroid::SetObexPassword(const nsAString& aPassword, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); + + BluetoothPbapManager* pbap = BluetoothPbapManager::Get(); + if (!pbap) { + DispatchReplyError(aRunnable, + NS_LITERAL_STRING("Failed to set OBEX password")); + return; + } + + pbap->ReplyToAuthChallenge(aPassword); + DispatchReplySuccess(aRunnable); +} + +void +BluetoothServiceBluedroid::RejectObexAuth( + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); + + BluetoothPbapManager* pbap = BluetoothPbapManager::Get(); + if (!pbap) { + DispatchReplyError(aRunnable, + NS_LITERAL_STRING("Failed to reject OBEX authentication request")); + return; + } + + pbap->ReplyToAuthChallenge(EmptyString()); + DispatchReplySuccess(aRunnable); +} + void BluetoothServiceBluedroid::ReplyTovCardPulling( BlobParent* aBlobParent, diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h index 281d658a00e..2dc848aba67 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h @@ -147,6 +147,13 @@ public: virtual void IsScoConnected(BluetoothReplyRunnable* aRunnable); + virtual void + SetObexPassword(const nsAString& aPassword, + BluetoothReplyRunnable* aRunnable); + + virtual void + RejectObexAuth(BluetoothReplyRunnable* aRunnable); + virtual void ReplyTovCardPulling(BlobParent* aBlobParent, BlobChild* aBlobChild, diff --git a/dom/bluetooth/bluez/BluetoothDBusService.cpp b/dom/bluetooth/bluez/BluetoothDBusService.cpp index 5182861f0d3..b32e0ac219f 100644 --- a/dom/bluetooth/bluez/BluetoothDBusService.cpp +++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp @@ -4449,6 +4449,17 @@ BluetoothDBusService::GattClientWriteDescriptorValueInternal( { } +void +BluetoothDBusService::SetObexPassword(const nsAString& aPassword, + BluetoothReplyRunnable* aRunnable) +{ +} + +void +BluetoothDBusService::RejectObexAuth(BluetoothReplyRunnable* aRunnable) +{ +} + void BluetoothDBusService::ReplyTovCardPulling( BlobParent* aBlobParent, diff --git a/dom/bluetooth/bluez/BluetoothDBusService.h b/dom/bluetooth/bluez/BluetoothDBusService.h index 8c47fbb1179..930eccfb615 100644 --- a/dom/bluetooth/bluez/BluetoothDBusService.h +++ b/dom/bluetooth/bluez/BluetoothDBusService.h @@ -157,6 +157,13 @@ public: virtual void IsScoConnected(BluetoothReplyRunnable* aRunnable) override; + virtual void + SetObexPassword(const nsAString& aPassword, + BluetoothReplyRunnable* aRunnable) override; + + virtual void + RejectObexAuth(BluetoothReplyRunnable* aRunnable) override; + virtual void ReplyTovCardPulling(BlobParent* aBlobParent, BlobChild* aBlobChild, diff --git a/dom/bluetooth/common/BluetoothCommon.h b/dom/bluetooth/common/BluetoothCommon.h index 635dce8d156..14561e74701 100644 --- a/dom/bluetooth/common/BluetoothCommon.h +++ b/dom/bluetooth/common/BluetoothCommon.h @@ -243,6 +243,12 @@ extern bool gBluetoothDebugFlag; */ #define REQUEST_MEDIA_PLAYSTATUS_ID "requestmediaplaystatus" +/** + * When receiving an OBEX authenticate challenge request from a remote device, + * we'll dispatch an event. + */ +#define OBEX_PASSWORD_REQ_ID "obexpasswordreq" + /** * When receiving a PBAP request from a remote device, we'll dispatch an event. */ diff --git a/dom/bluetooth/common/BluetoothService.h b/dom/bluetooth/common/BluetoothService.h index 620d9a31864..d5af6393356 100644 --- a/dom/bluetooth/common/BluetoothService.h +++ b/dom/bluetooth/common/BluetoothService.h @@ -310,6 +310,13 @@ public: virtual void IsScoConnected(BluetoothReplyRunnable* aRunnable) = 0; + virtual void + SetObexPassword(const nsAString& aPassword, + BluetoothReplyRunnable* aRunnable) = 0; + + virtual void + RejectObexAuth(BluetoothReplyRunnable* aRunnable) = 0; + virtual void ReplyTovCardPulling(BlobParent* aBlobParent, BlobChild* aBlobChild, diff --git a/dom/bluetooth/common/ObexBase.cpp b/dom/bluetooth/common/ObexBase.cpp index 40efc44f5f8..a219ac97e05 100644 --- a/dom/bluetooth/common/ObexBase.cpp +++ b/dom/bluetooth/common/ObexBase.cpp @@ -77,6 +77,14 @@ AppendHeaderWho(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aWho, aWho, aLength); } +int +AppendAuthResponse(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aDigest, + int aLength) +{ + return AppendHeader(ObexHeaderId::AuthResponse, aRetBuf, aBufferSize, + aDigest, aLength); +} + int AppendHeaderAppParameters(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aAppParameters, int aLength) diff --git a/dom/bluetooth/common/ObexBase.h b/dom/bluetooth/common/ObexBase.h index 81970237eba..1de52501652 100644 --- a/dom/bluetooth/common/ObexBase.h +++ b/dom/bluetooth/common/ObexBase.h @@ -124,6 +124,18 @@ enum ObexResponseCode { DatabaseLocked = 0xE1, }; +enum ObexDigestChallenge { + Nonce = 0x00, + Options = 0x01, + Realm = 0x02 +}; + +enum ObexDigestResponse { + ReqDigest = 0x00, + UserId = 0x01, + NonceChallenged = 0x02 +}; + class ObexHeader { public: @@ -253,6 +265,21 @@ public: } } + void GetAuthChallenge(nsAutoArrayPtr& aRetData, + int* aRetDataLength) const + { + *aRetDataLength = 0; + + for (uint8_t i = 0; i < mHeaders.Length(); ++i) { + if (mHeaders[i]->mId == ObexHeaderId::AuthChallenge) { + aRetData = new uint8_t[mHeaders[i]->mDataLength]; + memcpy(aRetData, mHeaders[i]->mData, mHeaders[i]->mDataLength); + *aRetDataLength = mHeaders[i]->mDataLength; + return; + } + } + } + uint32_t GetConnectionId() const { int length = mHeaders.Length(); @@ -345,6 +372,8 @@ int AppendHeaderTarget(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aTarget int aLength); int AppendHeaderWho(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aWho, int aLength); +int AppendAuthResponse(uint8_t* aRetBuf, int aBufferSize, + const uint8_t* aDigest, int aLength); int AppendHeaderAppParameters(uint8_t* aRetBuf, int aBufferSize, const uint8_t* aAppParameters, int aLength); int AppendAppParameter(uint8_t* aRetBuf, int aBufferSize, const uint8_t aTagId, diff --git a/dom/bluetooth/common/webapi/BluetoothAdapter.cpp b/dom/bluetooth/common/webapi/BluetoothAdapter.cpp index 71c95bd2ca7..a124d678060 100644 --- a/dom/bluetooth/common/webapi/BluetoothAdapter.cpp +++ b/dom/bluetooth/common/webapi/BluetoothAdapter.cpp @@ -22,7 +22,7 @@ #include "mozilla/dom/BluetoothMapMessageUpdateEvent.h" #include "mozilla/dom/BluetoothMapSetMessageStatusEvent.h" #include "mozilla/dom/BluetoothMapSendMessageEvent.h" - +#include "mozilla/dom/BluetoothObexAuthEvent.h" #include "mozilla/dom/BluetoothPhonebookPullingEvent.h" #include "mozilla/dom/BluetoothStatusChangedEvent.h" #include "mozilla/dom/BluetoothVCardListingEvent.h" @@ -36,6 +36,7 @@ #include "mozilla/dom/bluetooth/BluetoothDevice.h" #include "mozilla/dom/bluetooth/BluetoothDiscoveryHandle.h" #include "mozilla/dom/bluetooth/BluetoothGattServer.h" +#include "mozilla/dom/bluetooth/BluetoothObexAuthHandle.h" #include "mozilla/dom/bluetooth/BluetoothPairingListener.h" #include "mozilla/dom/bluetooth/BluetoothPbapRequestHandle.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" @@ -535,6 +536,8 @@ BluetoothAdapter::Notify(const BluetoothSignal& aData) } else if (aData.name().EqualsLiteral(PAIRING_ABORTED_ID) || aData.name().EqualsLiteral(REQUEST_MEDIA_PLAYSTATUS_ID)) { DispatchEmptyEvent(aData.name()); + } else if (aData.name().EqualsLiteral(OBEX_PASSWORD_REQ_ID)) { + HandleObexPasswordReq(aData.value()); } else if (aData.name().EqualsLiteral(PULL_PHONEBOOK_REQ_ID)) { HandlePullPhonebookReq(aData.value()); } else if (aData.name().EqualsLiteral(PULL_VCARD_ENTRY_REQ_ID)) { @@ -1219,6 +1222,23 @@ BluetoothAdapter::HandleDeviceUnpaired(const BluetoothValue& aValue) DispatchDeviceEvent(NS_LITERAL_STRING(DEVICE_UNPAIRED_ID), init); } +void +BluetoothAdapter::HandleObexPasswordReq(const BluetoothValue& aValue) +{ + MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue); + MOZ_ASSERT(aValue.get_ArrayOfBluetoothNamedValue().Length() <= 1); + + BluetoothObexAuthEventInit init; + init.mHandle = BluetoothObexAuthHandle::Create(GetOwner()); + + // TODO: Retrieve optional userId from aValue and assign into event + + RefPtr event = + BluetoothObexAuthEvent::Constructor(this, + NS_LITERAL_STRING(OBEX_PASSWORD_REQ_ID), init); + DispatchTrustedEvent(event); +} + void BluetoothAdapter::HandlePullPhonebookReq(const BluetoothValue& aValue) { diff --git a/dom/bluetooth/common/webapi/BluetoothAdapter.h b/dom/bluetooth/common/webapi/BluetoothAdapter.h index 53d1c3735ff..137f8d616f1 100644 --- a/dom/bluetooth/common/webapi/BluetoothAdapter.h +++ b/dom/bluetooth/common/webapi/BluetoothAdapter.h @@ -86,16 +86,21 @@ public: * Event Handlers ***************************************************************************/ IMPL_EVENT_HANDLER(attributechanged); + // PAIRING IMPL_EVENT_HANDLER(devicepaired); IMPL_EVENT_HANDLER(deviceunpaired); IMPL_EVENT_HANDLER(pairingaborted); + // HFP/A2DP/AVRCP IMPL_EVENT_HANDLER(a2dpstatuschanged); IMPL_EVENT_HANDLER(hfpstatuschanged); + IMPL_EVENT_HANDLER(scostatuschanged); + IMPL_EVENT_HANDLER(requestmediaplaystatus); + // PBAP + IMPL_EVENT_HANDLER(obexpasswordreq); IMPL_EVENT_HANDLER(pullphonebookreq); IMPL_EVENT_HANDLER(pullvcardentryreq); IMPL_EVENT_HANDLER(pullvcardlistingreq); - IMPL_EVENT_HANDLER(requestmediaplaystatus); - IMPL_EVENT_HANDLER(scostatuschanged); + // MAP IMPL_EVENT_HANDLER(mapfolderlistingreq); IMPL_EVENT_HANDLER(mapmessageslistingreq); IMPL_EVENT_HANDLER(mapgetmessagereq); @@ -308,7 +313,7 @@ private: * - uint32_t 'maxListCount' * - uint32_t 'listStartOffset' * - uint32_t[] 'vCardSelector_AND' - * - uint32_t[] 'vCardSelector_AND' + * - uint32_t[] 'vCardSelector_OR' */ void HandlePullPhonebookReq(const BluetoothValue& aValue); @@ -335,10 +340,19 @@ private: * - uint32_t 'maxListCount' * - uint32_t 'listStartOffset' * - uint32_t[] 'vCardSelector_AND' - * - uint32_t[] 'vCardSelector_AND' + * - uint32_t[] 'vCardSelector_OR' */ void HandlePullVCardListingReq(const BluetoothValue& aValue); + /** + * Handle OBEX_PASSWORD_REQ_ID bluetooth signal. + * + * @param aValue [in] Properties array of the PBAP request. + * The array may contain the property: + * - nsString 'userId' + */ + void HandleObexPasswordReq(const BluetoothValue& aValue); + /** * Get a Sequence of vCard properies from a BluetoothValue. The name of * BluetoothValue must be propSelector, vCardSelector_OR or vCardSelector_AND. @@ -542,7 +556,7 @@ private: nsTArray > mLeScanHandleArray; /** - * nsRefPtr array of BluetoothDevices created by this adapter. The array is + * RefPtr array of BluetoothDevices created by this adapter. The array is * empty when adapter state is Disabled. * * Devices will be appended when diff --git a/dom/bluetooth/common/webapi/BluetoothObexAuthHandle.cpp b/dom/bluetooth/common/webapi/BluetoothObexAuthHandle.cpp new file mode 100644 index 00000000000..49a4537f7b6 --- /dev/null +++ b/dom/bluetooth/common/webapi/BluetoothObexAuthHandle.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "BluetoothCommon.h" +#include "BluetoothDevice.h" +#include "BluetoothObexAuthHandle.h" +#include "BluetoothReplyRunnable.h" +#include "BluetoothService.h" + +#include "mozilla/dom/BluetoothObexAuthHandleBinding.h" +#include "mozilla/dom/Promise.h" + +using namespace mozilla; +using namespace dom; + +USING_BLUETOOTH_NAMESPACE + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BluetoothObexAuthHandle, mOwner) +NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothObexAuthHandle) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothObexAuthHandle) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothObexAuthHandle) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +BluetoothObexAuthHandle::BluetoothObexAuthHandle(nsPIDOMWindow* aOwner) + : mOwner(aOwner) +{ + MOZ_ASSERT(aOwner); +} + +BluetoothObexAuthHandle::~BluetoothObexAuthHandle() +{ +} + +already_AddRefed +BluetoothObexAuthHandle::Create(nsPIDOMWindow* aOwner) +{ + MOZ_ASSERT(aOwner); + + RefPtr handle = + new BluetoothObexAuthHandle(aOwner); + + return handle.forget(); +} + +already_AddRefed +BluetoothObexAuthHandle::SetPassword(const nsAString& aPassword, ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(GetParentObject()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr promise = Promise::Create(global, aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + + BluetoothService* bs = BluetoothService::Get(); + BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE); + + bs->SetObexPassword(aPassword, + new BluetoothVoidReplyRunnable(nullptr, promise)); + + return promise.forget(); +} + +already_AddRefed +BluetoothObexAuthHandle::Reject(ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(GetParentObject()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr promise = Promise::Create(global, aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + + BluetoothService* bs = BluetoothService::Get(); + BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE); + + bs->RejectObexAuth(new BluetoothVoidReplyRunnable(nullptr, promise)); + + return promise.forget(); +} + +JSObject* +BluetoothObexAuthHandle::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) +{ + return BluetoothObexAuthHandleBinding::Wrap(aCx, this, aGivenProto); +} diff --git a/dom/bluetooth/common/webapi/BluetoothObexAuthHandle.h b/dom/bluetooth/common/webapi/BluetoothObexAuthHandle.h new file mode 100644 index 00000000000..210e5517823 --- /dev/null +++ b/dom/bluetooth/common/webapi/BluetoothObexAuthHandle.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_bluetooth_BluetoothObexAuthHandle_h +#define mozilla_dom_bluetooth_BluetoothObexAuthHandle_h + +#include "BluetoothCommon.h" +#include "nsPIDOMWindow.h" +#include "nsWrapperCache.h" + +namespace mozilla { +class ErrorResult; +namespace dom { +class Promise; +} +} + +BEGIN_BLUETOOTH_NAMESPACE + +class BluetoothObexAuthHandle final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothObexAuthHandle) + + static already_AddRefed + Create(nsPIDOMWindow* aOwner); + + nsPIDOMWindow* GetParentObject() const + { + return mOwner; + } + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // Set password to the OBEX authentication request + already_AddRefed + SetPassword(const nsAString& aPassword, ErrorResult& aRv); + + // Reject the OBEX authentication request + already_AddRefed Reject(ErrorResult& aRv); + +private: + BluetoothObexAuthHandle(nsPIDOMWindow* aOwner); + ~BluetoothObexAuthHandle(); + + nsCOMPtr mOwner; +}; + +END_BLUETOOTH_NAMESPACE + +#endif // mozilla_dom_bluetooth_BluetoothObexAuthHandle_h diff --git a/dom/bluetooth/ipc/BluetoothParent.cpp b/dom/bluetooth/ipc/BluetoothParent.cpp index 9307f6044ce..6576a0bf82a 100644 --- a/dom/bluetooth/ipc/BluetoothParent.cpp +++ b/dom/bluetooth/ipc/BluetoothParent.cpp @@ -246,6 +246,10 @@ BluetoothParent::RecvPBluetoothRequestConstructor( return actor->DoRequest(aRequest.get_DisconnectScoRequest()); case Request::TIsScoConnectedRequest: return actor->DoRequest(aRequest.get_IsScoConnectedRequest()); + case Request::TSetObexPasswordRequest: + return actor->DoRequest(aRequest.get_SetObexPasswordRequest()); + case Request::TRejectObexAuthRequest: + return actor->DoRequest(aRequest.get_RejectObexAuthRequest()); case Request::TReplyTovCardPullingRequest: return actor->DoRequest(aRequest.get_ReplyTovCardPullingRequest()); case Request::TReplyToPhonebookPullingRequest: @@ -750,6 +754,28 @@ BluetoothRequestParent::DoRequest(const IsScoConnectedRequest& aRequest) return true; } +bool +BluetoothRequestParent::DoRequest(const SetObexPasswordRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == Request::TSetObexPasswordRequest); + + mService->SetObexPassword(aRequest.password(), mReplyRunnable.get()); + + return true; +} + +bool +BluetoothRequestParent::DoRequest(const RejectObexAuthRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == Request::TRejectObexAuthRequest); + + mService->RejectObexAuth(mReplyRunnable.get()); + + return true; +} + bool BluetoothRequestParent::DoRequest(const ReplyTovCardPullingRequest& aRequest) { diff --git a/dom/bluetooth/ipc/BluetoothParent.h b/dom/bluetooth/ipc/BluetoothParent.h index c4c00e60e99..96d9bb3a2ad 100644 --- a/dom/bluetooth/ipc/BluetoothParent.h +++ b/dom/bluetooth/ipc/BluetoothParent.h @@ -212,6 +212,12 @@ protected: bool DoRequest(const IsScoConnectedRequest& aRequest); + bool + DoRequest(const SetObexPasswordRequest& aRequest); + + bool + DoRequest(const RejectObexAuthRequest& aRequest); + bool DoRequest(const ReplyTovCardPullingRequest& aRequest); diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp index fb70435694a..08cd25c2f25 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp @@ -366,6 +366,20 @@ BluetoothServiceChildProcess::IsScoConnected(BluetoothReplyRunnable* aRunnable) SendRequest(aRunnable, IsScoConnectedRequest()); } +void +BluetoothServiceChildProcess::SetObexPassword( + const nsAString& aPassword, + BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, SetObexPasswordRequest(nsString(aPassword))); +} + +void +BluetoothServiceChildProcess::RejectObexAuth(BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, RejectObexAuthRequest()); +} + void BluetoothServiceChildProcess::ReplyTovCardPulling( BlobParent* aBlobParent, diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h index 2404549f96a..14910180cdf 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h @@ -158,6 +158,13 @@ public: virtual void IsScoConnected(BluetoothReplyRunnable* aRunnable) override; + virtual void + SetObexPassword(const nsAString& aPassword, + BluetoothReplyRunnable* aRunnable) override; + + virtual void + RejectObexAuth(BluetoothReplyRunnable* aRunnable) override; + virtual void ReplyTovCardPulling(BlobParent* aBlobParent, BlobChild* aBlobChild, diff --git a/dom/bluetooth/ipc/PBluetooth.ipdl b/dom/bluetooth/ipc/PBluetooth.ipdl index 3592c786d97..b2a6127f81d 100644 --- a/dom/bluetooth/ipc/PBluetooth.ipdl +++ b/dom/bluetooth/ipc/PBluetooth.ipdl @@ -171,6 +171,15 @@ struct IsScoConnectedRequest { }; +struct SetObexPasswordRequest +{ + nsString password; +}; + +struct RejectObexAuthRequest +{ +}; + struct ReplyTovCardPullingRequest { PBlob blob; @@ -447,6 +456,8 @@ union Request ConnectScoRequest; DisconnectScoRequest; IsScoConnectedRequest; + SetObexPasswordRequest; + RejectObexAuthRequest; ReplyTovCardPullingRequest; ReplyToPhonebookPullingRequest; ReplyTovCardListingRequest; diff --git a/dom/bluetooth/moz.build b/dom/bluetooth/moz.build index 79260fd4d91..fddf9d0b1e8 100644 --- a/dom/bluetooth/moz.build +++ b/dom/bluetooth/moz.build @@ -38,6 +38,7 @@ if CONFIG['MOZ_B2G_BT']: 'common/webapi/BluetoothLeDeviceEvent.cpp', 'common/webapi/BluetoothManager.cpp', 'common/webapi/BluetoothMapRequestHandle.cpp', + 'common/webapi/BluetoothObexAuthHandle.cpp', 'common/webapi/BluetoothPairingHandle.cpp', 'common/webapi/BluetoothPairingListener.cpp', 'common/webapi/BluetoothPbapRequestHandle.cpp', @@ -154,6 +155,7 @@ EXPORTS.mozilla.dom.bluetooth += [ 'common/webapi/BluetoothLeDeviceEvent.h', 'common/webapi/BluetoothManager.h', 'common/webapi/BluetoothMapRequestHandle.h', + 'common/webapi/BluetoothObexAuthHandle.h', 'common/webapi/BluetoothPairingHandle.h', 'common/webapi/BluetoothPairingListener.h', 'common/webapi/BluetoothPbapRequestHandle.h' diff --git a/dom/events/test/test_all_synthetic_events.html b/dom/events/test/test_all_synthetic_events.html index 147a54c2a34..94d4d4afcdc 100644 --- a/dom/events/test/test_all_synthetic_events.html +++ b/dom/events/test/test_all_synthetic_events.html @@ -76,6 +76,10 @@ const kEventConstructors = { return new BluetoothLeDeviceEvent(aName, aProps); }, }, + BluetoothObexAuthEvent: { create: function (aName, aProps) { + return new BluetoothObexAuthEvent(aName, aProps); + }, + }, BluetoothPairingEvent: { create: function (aName, aProps) { return new BluetoothPairingEvent(aName, aProps); }, diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 2ad213b0ecf..b231bd8bcb3 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -233,6 +233,10 @@ var interfaceNamesInGlobalScope = {name: "BluetoothMapSendMessageEvent", b2g: true, permission: ["bluetooth"]}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "BluetoothMapSetMessageStatusEvent", b2g: true, permission: ["bluetooth"]}, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "BluetoothObexAuthEvent", b2g: true, permission: ["bluetooth"]}, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "BluetoothObexAuthHandle", b2g: true, permission: ["bluetooth"]}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "BluetoothPbapRequestHandle", b2g: true, permission: ["bluetooth"]}, // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/BluetoothAdapter.webidl b/dom/webidl/BluetoothAdapter.webidl index 22579775b73..34ccac37461 100644 --- a/dom/webidl/BluetoothAdapter.webidl +++ b/dom/webidl/BluetoothAdapter.webidl @@ -69,6 +69,9 @@ interface BluetoothAdapter : EventTarget { // Fired when remote devices query current media play status attribute EventHandler onrequestmediaplaystatus; + // Fired when remote devices request password for OBEX authentication + attribute EventHandler onobexpasswordreq; + // Fired when PBAP manager requests for 'pullphonebook' attribute EventHandler onpullphonebookreq; diff --git a/dom/webidl/BluetoothObexAuthEvent.webidl b/dom/webidl/BluetoothObexAuthEvent.webidl new file mode 100644 index 00000000000..cb490375cf2 --- /dev/null +++ b/dom/webidl/BluetoothObexAuthEvent.webidl @@ -0,0 +1,20 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + */ + +[CheckAnyPermissions="bluetooth", + Constructor(DOMString type, + optional BluetoothObexAuthEventInit eventInitDict)] +interface BluetoothObexAuthEvent : Event +{ + readonly attribute DOMString? userId; + readonly attribute BluetoothObexAuthHandle? handle; +}; + +dictionary BluetoothObexAuthEventInit : EventInit +{ + DOMString? userId = ""; + BluetoothObexAuthHandle? handle = null; +}; diff --git a/dom/webidl/BluetoothObexAuthHandle.webidl b/dom/webidl/BluetoothObexAuthHandle.webidl new file mode 100644 index 00000000000..576c64acef3 --- /dev/null +++ b/dom/webidl/BluetoothObexAuthHandle.webidl @@ -0,0 +1,20 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +[CheckAnyPermissions="bluetooth"] +interface BluetoothObexAuthHandle +{ + /** + * Reply password for obexpasswordreq. The promise will be rejected if the + * operation fails. + */ + [NewObject] + Promise setPassword(DOMString aPassword); + + // Reject the OBEX authentication request. The promise will be rejected if + // operation fails. + [NewObject] + Promise reject(); +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index d3fd39bb26e..d6bb258f522 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -701,6 +701,7 @@ if CONFIG['MOZ_B2G_BT']: 'BluetoothManager.webidl', 'BluetoothMapParameters.webidl', 'BluetoothMapRequestHandle.webidl', + 'BluetoothObexAuthHandle.webidl', 'BluetoothPairingHandle.webidl', 'BluetoothPairingListener.webidl', 'BluetoothPbapParameters.webidl', @@ -859,6 +860,7 @@ if CONFIG['MOZ_B2G_BT']: 'BluetoothMapMessageUpdateEvent.webidl', 'BluetoothMapSendMessageEvent.webidl', 'BluetoothMapSetMessageStatusEvent.webidl', + 'BluetoothObexAuthEvent.webidl', 'BluetoothPairingEvent.webidl', 'BluetoothPhonebookPullingEvent.webidl', 'BluetoothStatusChangedEvent.webidl', From c4303fb3ed8626eafaf94b8eff527cb6894a5d21 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Mon, 9 Nov 2015 20:10:32 -0800 Subject: [PATCH 21/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/4dbcf88f0d2b Author: Yi-Fan Liao Desc: Merge pull request #33035 from begeeben/1220078_please_change_the_color Bug 1220078 - [TV Browser] Please change the color for "Terms of Serv… ======== https://hg.mozilla.org/integration/gaia-central/rev/5b16f4cab8a9 Author: yifan Desc: Bug 1220078 - [TV Browser] Please change the color for "Terms of Service" and "Privacy Notice" --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 9992ab9ccbc..3b7688e10a4 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "c0482775b1526add626b170dd53a72d10bcaf07c", + "git_revision": "3180bbe2f2e94809c1fcef0e92a01da99cdfb530", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "da79a53a3e7b627176e3a933387f0eaa7ff379fa", + "revision": "4dbcf88f0d2bf230bdc08cd5246b6a285ae1938e", "repo_path": "integration/gaia-central" } From 1b8823e2afa41f3922425e6904b79cf0c982282b Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Mon, 9 Nov 2015 20:12:19 -0800 Subject: [PATCH 22/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 2737fee4338..d223c752918 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 0a8ff886478..ebaa5e27e55 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index f762c0fc02c..5d3b9c71938 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 6cab54c9222..7c9587e89e4 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 3d8acb6b22a..07e9293fd39 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 5bd23033266..5f1d01c3811 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index f762c0fc02c..5d3b9c71938 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 2cc5124649f..f6c339c9f78 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index a63955afcaa..4e771ec3614 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 55c9d93db07..50c32c0978f 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index b8750048265..4adf5189b19 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 6da7cca9a194affe6c437690af8261bb900fb3f5 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Mon, 9 Nov 2015 23:34:46 -0800 Subject: [PATCH 23/71] Bumping manifests a=b2g-bump --- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 5f1d01c3811..975c1608d6a 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -122,7 +122,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 4adf5189b19..43216b0be9f 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -123,7 +123,7 @@ - + From 93d4c37235374acb5d1ecf5d4f598effc246a1d8 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Mon, 9 Nov 2015 23:40:36 -0800 Subject: [PATCH 24/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index d223c752918..fd431941910 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -106,7 +106,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index ebaa5e27e55..cd3bf11b89d 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -112,7 +112,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 07e9293fd39..0bdff15f21e 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -110,7 +110,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index f6c339c9f78..41aebe46114 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -105,7 +105,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 4e771ec3614..2e00e07f9e1 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -112,7 +112,7 @@ - + From fca26353353db65c14aa09c5a9e02757d2ab89da Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 00:25:16 -0800 Subject: [PATCH 25/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/90ce9220ceee Author: Martijn Desc: Merge pull request #33060 from mwargers/1222512 Bug 1222512 - Almost all settings related tests failing in this Jenkins run with TimeoutException ======== https://hg.mozilla.org/integration/gaia-central/rev/15382886f66b Author: Martijn Wargers Desc: Bug 1222512 - Almost all settings related tests failing in this Jenkins run with TimeoutException --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 3b7688e10a4..5d4b88f2a12 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "3180bbe2f2e94809c1fcef0e92a01da99cdfb530", + "git_revision": "3940f9c71ad3db6f725b892e825b9fe2961c427d", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "4dbcf88f0d2bf230bdc08cd5246b6a285ae1938e", + "revision": "90ce9220ceee8b6fea3d5bc09671c6e7b567a70f", "repo_path": "integration/gaia-central" } From 071a8ca3733e8a98c720f05c6e08e7e2a2683553 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 00:26:39 -0800 Subject: [PATCH 26/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index fd431941910..84f71883df1 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index cd3bf11b89d..7a8345805bf 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 5d3b9c71938..15d6fd9b53c 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 7c9587e89e4..7308c51ded0 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 0bdff15f21e..b04a48a10be 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 975c1608d6a..a3197266262 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 5d3b9c71938..15d6fd9b53c 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 41aebe46114..1a9015373e2 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 2e00e07f9e1..a23e992a3e3 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 50c32c0978f..3ce2674524d 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 43216b0be9f..8d3f6a98c14 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From bd7a4b34d81e80df2ab045543d5a17c672e07f28 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 01:45:35 -0800 Subject: [PATCH 27/71] Bumping gaia.json for 6 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/fcb9eb6b0b10 Author: Michael Henretty Desc: Merge pull request #33072 from mikehenrty/bug-1192249-wait-for-lockscreen-power-menu-test Bug 1192249 - Wait for lockscreen to disappear before holding sleep ======== https://hg.mozilla.org/integration/gaia-central/rev/f3972b1d7d66 Author: Michael Henretty Desc: Bug 1192249 - Wait for lockscreen to disappear before holding sleep ======== https://hg.mozilla.org/integration/gaia-central/rev/0e75d405495e Author: Michael Henretty Desc: Merge pull request #33070 from mikehenrty/bug-1216273-wait-for-element-media-playback Bug 1216273 - Use waitForElement instead of findElement + displayed i… ======== https://hg.mozilla.org/integration/gaia-central/rev/aeef06e850ea Author: Michael Henretty Desc: Bug 1216273 - Use waitForElement instead of findElement + displayed in media_playback_actions.js ======== https://hg.mozilla.org/integration/gaia-central/rev/d343092c2daa Author: isabelrios Desc: Merge pull request #32979 from isabelrios/delete_app_homescreen Bug 1220736 - Fixing test_homescreen_delete_app.py test case ======== https://hg.mozilla.org/integration/gaia-central/rev/aec6daf1d32d Author: Isabel Rios Escobar Desc: Bug 1220736 - Fixing test_homescreen_delete_app.py test case small changes small changes --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 5d4b88f2a12..59064cd168a 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "3940f9c71ad3db6f725b892e825b9fe2961c427d", + "git_revision": "f10062acefdf0a0fb67253917ceeb13ebcaa70a7", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "90ce9220ceee8b6fea3d5bc09671c6e7b567a70f", + "revision": "fcb9eb6b0b10c02400042c87e776707dcf5c0ee0", "repo_path": "integration/gaia-central" } From 0480effc50fc958fbfdb9e83bfd5c7cef1ca28dc Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 01:46:59 -0800 Subject: [PATCH 28/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 84f71883df1..32ab306b5d2 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 7a8345805bf..2b5de14e868 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 15d6fd9b53c..bf017e4bcb6 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 7308c51ded0..b4ce00c42bd 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index b04a48a10be..298be03d550 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index a3197266262..2098637615c 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 15d6fd9b53c..bf017e4bcb6 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 1a9015373e2..787aa7dbb0b 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index a23e992a3e3..d036f12a467 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 3ce2674524d..d6a4510346a 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 8d3f6a98c14..a0840a3640f 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From e1192844076431352b96b98feed6d9edec6cc4c5 Mon Sep 17 00:00:00 2001 From: Naoki Hirata Date: Thu, 5 Nov 2015 17:43:54 -0800 Subject: [PATCH 29/71] Bug 1218452 - Remove the spark distro from the Aries Engineering builds. r=wcosta --- .../configs/b2g/taskcluster-phone-eng.py | 33 +++++++++++++++++++ .../taskcluster/tasks/branches/base_jobs.yml | 2 +- ..._aries_spark_eng.yml => b2g_aries_eng.yml} | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 testing/mozharness/configs/b2g/taskcluster-phone-eng.py rename testing/taskcluster/tasks/builds/{b2g_aries_spark_eng.yml => b2g_aries_eng.yml} (93%) diff --git a/testing/mozharness/configs/b2g/taskcluster-phone-eng.py b/testing/mozharness/configs/b2g/taskcluster-phone-eng.py new file mode 100644 index 00000000000..38e9dafcf67 --- /dev/null +++ b/testing/mozharness/configs/b2g/taskcluster-phone-eng.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +config = { + "default_vcs": "tc-vcs", + "default_actions": [ + 'checkout-sources', + 'build', + 'build-symbols', + 'prep-upload' + ], + "upload": { + "default": { + "upload_dep_target_exclusions": [] + } + }, + "env": { + "GAIA_OPTIMIZE": "1", + "WGET_OPTS": "-c -q", + "B2G_PATH": "%(work_dir)s", + "BOWER_FLAGS": "--allow-root", + "WGET_OPTS": "-c -q", + }, + "is_automation": True, + "repo_remote_mappings": { + 'https://android.googlesource.com/': 'https://git.mozilla.org/external/aosp', + 'git://codeaurora.org/': 'https://git.mozilla.org/external/caf', + 'https://git.mozilla.org/b2g': 'https://git.mozilla.org/b2g', + 'git://github.com/mozilla-b2g/': 'https://git.mozilla.org/b2g', + 'git://github.com/mozilla/': 'https://git.mozilla.org/b2g', + 'https://git.mozilla.org/releases': 'https://git.mozilla.org/releases', + 'http://android.git.linaro.org/git-ro/': 'https://git.mozilla.org/external/linaro', + 'git://github.com/apitrace/': 'https://git.mozilla.org/external/apitrace', + }, +} diff --git a/testing/taskcluster/tasks/branches/base_jobs.yml b/testing/taskcluster/tasks/branches/base_jobs.yml index a632de844ce..9ac4947c948 100644 --- a/testing/taskcluster/tasks/branches/base_jobs.yml +++ b/testing/taskcluster/tasks/branches/base_jobs.yml @@ -73,7 +73,7 @@ builds: - b2g types: opt: - task: tasks/builds/b2g_aries_spark_eng.yml + task: tasks/builds/b2g_aries_eng.yml flame-kk: platforms: - b2g diff --git a/testing/taskcluster/tasks/builds/b2g_aries_spark_eng.yml b/testing/taskcluster/tasks/builds/b2g_aries_eng.yml similarity index 93% rename from testing/taskcluster/tasks/builds/b2g_aries_spark_eng.yml rename to testing/taskcluster/tasks/builds/b2g_aries_eng.yml index 7c3e71fc05c..0a06abf5a84 100644 --- a/testing/taskcluster/tasks/builds/b2g_aries_spark_eng.yml +++ b/testing/taskcluster/tasks/builds/b2g_aries_eng.yml @@ -16,7 +16,7 @@ task: build-aries-eng-objdir-gecko-{{project}}: /home/worker/objdir-gecko env: TARGET: 'aries' - MOZHARNESS_CONFIG: b2g/taskcluster-spark.py + MOZHARNESS_CONFIG: b2g/taskcluster-phone-eng.py extra: treeherderEnv: - production From 2b592e152eaaa85b1f2ca672fffee99db58567f5 Mon Sep 17 00:00:00 2001 From: Alastor Wu Date: Tue, 10 Nov 2015 14:43:59 +0800 Subject: [PATCH 30/71] Bug 1222902 - Create log system for the AudioChannel. r=baku --- dom/audiochannel/AudioChannelAgent.cpp | 16 +++++++++++++ dom/audiochannel/AudioChannelService.cpp | 23 +++++++++++++++++++ dom/audiochannel/AudioChannelService.h | 3 +++ .../BrowserElementAudioChannel.cpp | 8 +++++++ dom/html/nsBrowserElement.cpp | 3 +++ dom/ipc/TabChild.cpp | 3 +++ 6 files changed, 56 insertions(+) diff --git a/dom/audiochannel/AudioChannelAgent.cpp b/dom/audiochannel/AudioChannelAgent.cpp index 27af43e4a41..01014f153b2 100644 --- a/dom/audiochannel/AudioChannelAgent.cpp +++ b/dom/audiochannel/AudioChannelAgent.cpp @@ -130,6 +130,11 @@ AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType, mCallback = aCallback; } + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelAgent, InitInternal, this = %p, type = %d, " + "owner = %p, hasCallback = %d\n", this, mAudioChannelType, + mWindow.get(), (!!mCallback || !!mWeakCallback))); + return NS_OK; } @@ -158,6 +163,10 @@ NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(uint32_t aNotifyPlayback, service->GetState(mWindow, mAudioChannelType, aVolume, aMuted); + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelAgent, NotifyStartedPlaying, this = %p, mute = %d, " + "volume = %f\n", this, *aMuted, *aVolume)); + mNotifyPlayback = aNotifyPlayback; mIsRegToService = true; return NS_OK; @@ -170,6 +179,9 @@ NS_IMETHODIMP AudioChannelAgent::NotifyStoppedPlaying() return NS_ERROR_FAILURE; } + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelAgent, NotifyStoppedPlaying, this = %p\n", this)); + RefPtr service = AudioChannelService::GetOrCreate(); service->UnregisterAudioChannelAgent(this, mNotifyPlayback); mIsRegToService = false; @@ -200,6 +212,10 @@ AudioChannelAgent::WindowVolumeChanged() RefPtr service = AudioChannelService::GetOrCreate(); service->GetState(mWindow, mAudioChannelType, &volume, &muted); + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %d, " + "volume = %f\n", this, muted, volume)); + callback->WindowVolumeChanged(volume, muted); } diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp index b3ef5b8ae6e..18239805a02 100644 --- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -78,6 +78,11 @@ public: mActive ? MOZ_UTF16("active") : MOZ_UTF16("inactive")); + + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("NotifyChannelActiveRunnable, type = %d, active = %d\n", + mAudioChannel, mActive)); + return NS_OK; } @@ -178,6 +183,16 @@ AudioChannelService::GetOrCreate() return service.forget(); } +/* static */ PRLogModuleInfo* +AudioChannelService::GetAudioChannelLog() +{ + static PRLogModuleInfo *gAudioChannelLog; + if (!gAudioChannelLog) { + gAudioChannelLog = PR_NewLogModule("AudioChannel"); + } + return gAudioChannelLog; +} + void AudioChannelService::Shutdown() { @@ -719,6 +734,10 @@ AudioChannelService::SetAudioChannelVolume(nsPIDOMWindow* aWindow, MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsOuterWindow()); + MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelService, SetAudioChannelVolume, window = %p, type = %d, " + "volume = %d\n", aWindow, aAudioChannel, aVolume)); + AudioChannelWindow* winData = GetOrCreateWindowData(aWindow); winData->mChannels[(uint32_t)aAudioChannel].mVolume = aVolume; RefreshAgentsVolume(aWindow); @@ -773,6 +792,10 @@ AudioChannelService::SetAudioChannelMuted(nsPIDOMWindow* aWindow, MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsOuterWindow()); + MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug, + ("AudioChannelService, SetAudioChannelMuted, window = %p, type = %d, " + "mute = %d\n", aWindow, aAudioChannel, aMuted)); + if (aAudioChannel == AudioChannel::System) { // Workaround for bug1183033, system channel type can always playback. return; diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h index 7a5a4728a88..40100c42caf 100644 --- a/dom/audiochannel/AudioChannelService.h +++ b/dom/audiochannel/AudioChannelService.h @@ -19,6 +19,7 @@ class nsIRunnable; class nsPIDOMWindow; +class PRLogModuleInfo; namespace mozilla { namespace dom { @@ -45,6 +46,8 @@ public: static bool IsAudioChannelMutedByDefault(); + static PRLogModuleInfo* GetAudioChannelLog(); + /** * Any audio channel agent that starts playing should register itself to * this service, sharing the AudioChannel. diff --git a/dom/browser-element/BrowserElementAudioChannel.cpp b/dom/browser-element/BrowserElementAudioChannel.cpp index c522b0a10a6..854ad81c50d 100644 --- a/dom/browser-element/BrowserElementAudioChannel.cpp +++ b/dom/browser-element/BrowserElementAudioChannel.cpp @@ -68,6 +68,10 @@ BrowserElementAudioChannel::Create(nsPIDOMWindow* aWindow, return nullptr; } + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("BrowserElementAudioChannel, Create, channel = %p, type = %d\n", + ac.get(), aAudioChannel)); + return ac.forget(); } @@ -608,6 +612,10 @@ BrowserElementAudioChannel::Observe(nsISupports* aSubject, const char* aTopic, void BrowserElementAudioChannel::ProcessStateChanged(const char16_t* aData) { + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("BrowserElementAudioChannel, ProcessStateChanged, this = %p, " + "type = %d\n", this, mAudioChannel)); + nsAutoString value(aData); mState = value.EqualsASCII("active") ? eStateActive : eStateInactive; DispatchTrustedEvent(NS_LITERAL_STRING("activestatechanged")); diff --git a/dom/html/nsBrowserElement.cpp b/dom/html/nsBrowserElement.cpp index 2b268c564c0..f7485c5df34 100644 --- a/dom/html/nsBrowserElement.cpp +++ b/dom/html/nsBrowserElement.cpp @@ -563,6 +563,9 @@ nsBrowserElement::GetAllowedAudioChannels( return; } + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("nsBrowserElement, GetAllowedAudioChannels, this = %p\n", this)); + GenerateAllowedAudioChannels(window, frameLoader, mBrowserElementAPI, manifestURL, mBrowserElementAudioChannels, aRv); diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index a8a44a98be6..32a164d7d8f 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -693,6 +693,9 @@ TabChild::Observe(nsISupports *aSubject, // In theory a tabChild should contain just 1 top window, but let's double // check it comparing the windowID. if (window->WindowID() != windowID) { + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("TabChild, Observe, different windowID, owner ID = %lld, " + "ID from wrapper = %lld", window->WindowID(), windowID)); return NS_OK; } From e56872b557120294c289f3130416bdadc93f43d3 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Tue, 10 Nov 2015 09:42:38 +0000 Subject: [PATCH 31/71] Bug 1221275 - override background image on all non-menubar/tabs toolbars, r=jaws --- browser/themes/shared/devedition.inc.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/browser/themes/shared/devedition.inc.css b/browser/themes/shared/devedition.inc.css index 993a5ed82d7..b207704ee8b 100644 --- a/browser/themes/shared/devedition.inc.css +++ b/browser/themes/shared/devedition.inc.css @@ -164,13 +164,10 @@ .browserContainer > findbar, #browser-bottombox { background-color: var(--chrome-secondary-background-color) !important; + background-image: none !important; color: var(--chrome-color); } -.browserContainer > findbar { - background-image: none; -} - .browserContainer > .findbar-textbox { background-color: var(--url-and-searchbar-background-color) !important; color: var(--url-and-searchbar-color); @@ -245,7 +242,6 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper { border-bottom: none !important; border-radius: 0 !important; box-shadow: 0 -1px var(--chrome-nav-bar-separator-color) !important; - background-image: none !important; } /* No extra vertical padding for nav bar */ From d767a47df1c879874c5cf98367df8a93f7a0c406 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 02:20:38 -0800 Subject: [PATCH 32/71] Bumping gaia.json for 4 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/d0110b8070c3 Author: Michael Henretty Desc: Merge pull request #32941 from mikehenrty/bug-1020699-remove-header-mutation-observer Bug 1020699 - Remove header mutation obserber from font_size_utils.js… ======== https://hg.mozilla.org/integration/gaia-central/rev/dc86aab8e80b Author: Michael Henretty Desc: Bug 1020699 - Remove header mutation obserber from font_size_utils.js now that we have gaia-header ======== https://hg.mozilla.org/integration/gaia-central/rev/4d4c0e2646ce Author: Chris Lord Desc: Merge pull request #33050 from Cwiiis/bug1216523-task-manager-transitions Bug 1216523 - Fix task manager enter/exit transitions. r=etienne ======== https://hg.mozilla.org/integration/gaia-central/rev/31c62c9ca1f5 Author: Chris Lord Desc: Bug 1216523 - Fix task manager enter/exit transitions. r=etienne --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 59064cd168a..90f2598372e 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "f10062acefdf0a0fb67253917ceeb13ebcaa70a7", + "git_revision": "93ee67c1b886aab107999307cc1fd9177cc1f83c", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "fcb9eb6b0b10c02400042c87e776707dcf5c0ee0", + "revision": "d0110b8070c38fdf444c9e2ca2f0b6ef379f8ab1", "repo_path": "integration/gaia-central" } From 1ba30079d8378905bea45ebf1c0266ab875c69b2 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 02:22:01 -0800 Subject: [PATCH 33/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 32ab306b5d2..3030a7a00b8 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 2b5de14e868..f9cae726dd5 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index bf017e4bcb6..ff1d7a22a27 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index b4ce00c42bd..fad3d817cf0 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 298be03d550..a8eef626b21 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 2098637615c..a7e09618f31 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index bf017e4bcb6..ff1d7a22a27 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 787aa7dbb0b..1e7998c5082 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index d036f12a467..302499b32f8 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index d6a4510346a..d44c1f445e8 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index a0840a3640f..115a4c9d7a6 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From cab9c2a1bf8d4089be4c69da99c805d11677aabb Mon Sep 17 00:00:00 2001 From: "Carsten \"Tomcat\" Book" Date: Tue, 10 Nov 2015 11:23:06 +0100 Subject: [PATCH 34/71] Backed out changeset 9b2c15970aa0 (bug 1222902) for bustage --- dom/audiochannel/AudioChannelAgent.cpp | 16 ------------- dom/audiochannel/AudioChannelService.cpp | 23 ------------------- dom/audiochannel/AudioChannelService.h | 3 --- .../BrowserElementAudioChannel.cpp | 8 ------- dom/html/nsBrowserElement.cpp | 3 --- dom/ipc/TabChild.cpp | 3 --- 6 files changed, 56 deletions(-) diff --git a/dom/audiochannel/AudioChannelAgent.cpp b/dom/audiochannel/AudioChannelAgent.cpp index 01014f153b2..27af43e4a41 100644 --- a/dom/audiochannel/AudioChannelAgent.cpp +++ b/dom/audiochannel/AudioChannelAgent.cpp @@ -130,11 +130,6 @@ AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType, mCallback = aCallback; } - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("AudioChannelAgent, InitInternal, this = %p, type = %d, " - "owner = %p, hasCallback = %d\n", this, mAudioChannelType, - mWindow.get(), (!!mCallback || !!mWeakCallback))); - return NS_OK; } @@ -163,10 +158,6 @@ NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(uint32_t aNotifyPlayback, service->GetState(mWindow, mAudioChannelType, aVolume, aMuted); - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("AudioChannelAgent, NotifyStartedPlaying, this = %p, mute = %d, " - "volume = %f\n", this, *aMuted, *aVolume)); - mNotifyPlayback = aNotifyPlayback; mIsRegToService = true; return NS_OK; @@ -179,9 +170,6 @@ NS_IMETHODIMP AudioChannelAgent::NotifyStoppedPlaying() return NS_ERROR_FAILURE; } - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("AudioChannelAgent, NotifyStoppedPlaying, this = %p\n", this)); - RefPtr service = AudioChannelService::GetOrCreate(); service->UnregisterAudioChannelAgent(this, mNotifyPlayback); mIsRegToService = false; @@ -212,10 +200,6 @@ AudioChannelAgent::WindowVolumeChanged() RefPtr service = AudioChannelService::GetOrCreate(); service->GetState(mWindow, mAudioChannelType, &volume, &muted); - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("AudioChannelAgent, WindowVolumeChanged, this = %p, mute = %d, " - "volume = %f\n", this, muted, volume)); - callback->WindowVolumeChanged(volume, muted); } diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp index 18239805a02..b3ef5b8ae6e 100644 --- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -78,11 +78,6 @@ public: mActive ? MOZ_UTF16("active") : MOZ_UTF16("inactive")); - - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("NotifyChannelActiveRunnable, type = %d, active = %d\n", - mAudioChannel, mActive)); - return NS_OK; } @@ -183,16 +178,6 @@ AudioChannelService::GetOrCreate() return service.forget(); } -/* static */ PRLogModuleInfo* -AudioChannelService::GetAudioChannelLog() -{ - static PRLogModuleInfo *gAudioChannelLog; - if (!gAudioChannelLog) { - gAudioChannelLog = PR_NewLogModule("AudioChannel"); - } - return gAudioChannelLog; -} - void AudioChannelService::Shutdown() { @@ -734,10 +719,6 @@ AudioChannelService::SetAudioChannelVolume(nsPIDOMWindow* aWindow, MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsOuterWindow()); - MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug, - ("AudioChannelService, SetAudioChannelVolume, window = %p, type = %d, " - "volume = %d\n", aWindow, aAudioChannel, aVolume)); - AudioChannelWindow* winData = GetOrCreateWindowData(aWindow); winData->mChannels[(uint32_t)aAudioChannel].mVolume = aVolume; RefreshAgentsVolume(aWindow); @@ -792,10 +773,6 @@ AudioChannelService::SetAudioChannelMuted(nsPIDOMWindow* aWindow, MOZ_ASSERT(aWindow); MOZ_ASSERT(aWindow->IsOuterWindow()); - MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug, - ("AudioChannelService, SetAudioChannelMuted, window = %p, type = %d, " - "mute = %d\n", aWindow, aAudioChannel, aMuted)); - if (aAudioChannel == AudioChannel::System) { // Workaround for bug1183033, system channel type can always playback. return; diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h index 40100c42caf..7a5a4728a88 100644 --- a/dom/audiochannel/AudioChannelService.h +++ b/dom/audiochannel/AudioChannelService.h @@ -19,7 +19,6 @@ class nsIRunnable; class nsPIDOMWindow; -class PRLogModuleInfo; namespace mozilla { namespace dom { @@ -46,8 +45,6 @@ public: static bool IsAudioChannelMutedByDefault(); - static PRLogModuleInfo* GetAudioChannelLog(); - /** * Any audio channel agent that starts playing should register itself to * this service, sharing the AudioChannel. diff --git a/dom/browser-element/BrowserElementAudioChannel.cpp b/dom/browser-element/BrowserElementAudioChannel.cpp index 854ad81c50d..c522b0a10a6 100644 --- a/dom/browser-element/BrowserElementAudioChannel.cpp +++ b/dom/browser-element/BrowserElementAudioChannel.cpp @@ -68,10 +68,6 @@ BrowserElementAudioChannel::Create(nsPIDOMWindow* aWindow, return nullptr; } - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("BrowserElementAudioChannel, Create, channel = %p, type = %d\n", - ac.get(), aAudioChannel)); - return ac.forget(); } @@ -612,10 +608,6 @@ BrowserElementAudioChannel::Observe(nsISupports* aSubject, const char* aTopic, void BrowserElementAudioChannel::ProcessStateChanged(const char16_t* aData) { - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("BrowserElementAudioChannel, ProcessStateChanged, this = %p, " - "type = %d\n", this, mAudioChannel)); - nsAutoString value(aData); mState = value.EqualsASCII("active") ? eStateActive : eStateInactive; DispatchTrustedEvent(NS_LITERAL_STRING("activestatechanged")); diff --git a/dom/html/nsBrowserElement.cpp b/dom/html/nsBrowserElement.cpp index f7485c5df34..2b268c564c0 100644 --- a/dom/html/nsBrowserElement.cpp +++ b/dom/html/nsBrowserElement.cpp @@ -563,9 +563,6 @@ nsBrowserElement::GetAllowedAudioChannels( return; } - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("nsBrowserElement, GetAllowedAudioChannels, this = %p\n", this)); - GenerateAllowedAudioChannels(window, frameLoader, mBrowserElementAPI, manifestURL, mBrowserElementAudioChannels, aRv); diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 32a164d7d8f..a8a44a98be6 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -693,9 +693,6 @@ TabChild::Observe(nsISupports *aSubject, // In theory a tabChild should contain just 1 top window, but let's double // check it comparing the windowID. if (window->WindowID() != windowID) { - MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, - ("TabChild, Observe, different windowID, owner ID = %lld, " - "ID from wrapper = %lld", window->WindowID(), windowID)); return NS_OK; } From 5eeda2154ac20f7c1600b3b6d9767f226816210a Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 02:55:16 -0800 Subject: [PATCH 35/71] Bumping gaia.json for 6 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/40e7c7a1ef52 Author: albertopq Desc: Merge pull request #33020 from albertopq/1221377-attachment-sms Bug 1221377 - Preventing keyboard when an actionmenu is being shown r=etienne ======== https://hg.mozilla.org/integration/gaia-central/rev/b5c6d85aa994 Author: albertopq Desc: Bug 1221377 - Preventing keyboard when an actionmenu is being shown ======== https://hg.mozilla.org/integration/gaia-central/rev/25bad02eff88 Author: Carsten Book Desc: Merge pull request #33074 from bzbarsky/fix-promise-abuses Bug 1223004 - Fix misuse of Promise static methods in Gaia unit tests. r=timdream ======== https://hg.mozilla.org/integration/gaia-central/rev/27aea09d6912 Author: Boris Zbarsky Desc: Bug 1223004 - Fix misuse of Promise static methods in Gaia unit tests. ======== https://hg.mozilla.org/integration/gaia-central/rev/099535e34201 Author: Carsten Book Desc: Merge pull request #33031 from schien/bug1221943 Bug 1221943 - Include settings app for engineering build. r=rexboy. ======== https://hg.mozilla.org/integration/gaia-central/rev/ecbc3d65e72d Author: Shih-Chiang Chien Desc: Bug 1221943 - Include settings app for engineering build. r=rexboy. --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 90f2598372e..e7b0bb43340 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "93ee67c1b886aab107999307cc1fd9177cc1f83c", + "git_revision": "ad79017bbab3229a02c49cd4cd0c8401e72fc640", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "d0110b8070c38fdf444c9e2ca2f0b6ef379f8ab1", + "revision": "40e7c7a1ef524230c0c4a9b30b37ec263ee9291a", "repo_path": "integration/gaia-central" } From 562df7c4c5240b2e9105747c0038efc454ba06bc Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 02:56:39 -0800 Subject: [PATCH 36/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 3030a7a00b8..65dd38e074f 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index f9cae726dd5..c27fc055762 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index ff1d7a22a27..f550c2d09ea 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index fad3d817cf0..414357a0e3e 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index a8eef626b21..aec4c85f979 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index a7e09618f31..9b7a0b9e5e1 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index ff1d7a22a27..f550c2d09ea 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 1e7998c5082..6de2a96ed79 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 302499b32f8..f10db2b7a31 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index d44c1f445e8..d9d1234c498 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 115a4c9d7a6..60ca8f0fd90 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From afb9fb093dcee8923f3420744ecab43b7e3bf4bc Mon Sep 17 00:00:00 2001 From: Shawn Huang Date: Tue, 10 Nov 2015 19:04:12 +0800 Subject: [PATCH 37/71] Bug 1211769 - [MAP] Pack MAP replies to OBEX response packets, r=btain, sr=mrbkap --- .../bluedroid/BluetoothMapSmsManager.cpp | 412 +++++++++++++++--- .../bluedroid/BluetoothMapSmsManager.h | 28 +- .../bluedroid/BluetoothServiceBluedroid.cpp | 3 +- .../bluedroid/BluetoothServiceBluedroid.h | 6 +- dom/bluetooth/bluez/BluetoothDBusService.cpp | 4 +- dom/bluetooth/bluez/BluetoothDBusService.h | 1 + dom/bluetooth/common/BluetoothService.h | 3 +- .../webapi/BluetoothMapRequestHandle.cpp | 3 +- .../common/webapi/BluetoothMapRequestHandle.h | 9 +- dom/bluetooth/ipc/BluetoothParent.cpp | 1 + .../ipc/BluetoothServiceChildProcess.cpp | 3 +- .../ipc/BluetoothServiceChildProcess.h | 6 +- dom/bluetooth/ipc/PBluetooth.ipdl | 1 + dom/webidl/BluetoothMapRequestHandle.webidl | 2 +- 14 files changed, 406 insertions(+), 76 deletions(-) diff --git a/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp b/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp index f14601bcdad..a78f1f3a8eb 100644 --- a/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothMapSmsManager.cpp @@ -87,9 +87,12 @@ BluetoothMapSmsManager::HandleShutdown() sMapSmsManager = nullptr; } -BluetoothMapSmsManager::BluetoothMapSmsManager() : mMasConnected(false), - mMnsConnected(false), - mNtfRequired(false) +BluetoothMapSmsManager::BluetoothMapSmsManager() + : mBodyRequired(false) + , mFractionDeliverRequired(false) + , mMasConnected(false) + , mMnsConnected(false) + , mNtfRequired(false) { BuildDefaultFolderStructure(); } @@ -241,7 +244,7 @@ BluetoothMapSmsManager::MasDataHandler(UnixSocketBuffer* aMessage) */ int receivedLength = aMessage->GetSize(); if (receivedLength < 1 || receivedLength > MAX_PACKET_LENGTH) { - ReplyError(ObexResponseCode::BadRequest); + SendReply(ObexResponseCode::BadRequest); return; } @@ -256,23 +259,23 @@ BluetoothMapSmsManager::MasDataHandler(UnixSocketBuffer* aMessage) // [Headers:var] if (receivedLength < 7 || !ParseHeaders(&data[7], receivedLength - 7, &pktHeaders)) { - ReplyError(ObexResponseCode::BadRequest); + SendReply(ObexResponseCode::BadRequest); return; } // "Establishing an OBEX Session" // The OBEX header target shall equal to MAS obex target UUID. if (!CompareHeaderTarget(pktHeaders)) { - ReplyError(ObexResponseCode::BadRequest); + SendReply(ObexResponseCode::BadRequest); return; } mRemoteMaxPacketLength = BigEndian::readUint16(&data[5]); - if (mRemoteMaxPacketLength < 255) { + if (mRemoteMaxPacketLength < kObexLeastMaxSize) { BT_LOGR("Remote maximum packet length %d", mRemoteMaxPacketLength); mRemoteMaxPacketLength = 0; - ReplyError(ObexResponseCode::BadRequest); + SendReply(ObexResponseCode::BadRequest); return; } @@ -286,7 +289,7 @@ BluetoothMapSmsManager::MasDataHandler(UnixSocketBuffer* aMessage) // [opcode:1][length:2][Headers:var] if (receivedLength < 3 || !ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) { - ReplyError(ObexResponseCode::BadRequest); + SendReply(ObexResponseCode::BadRequest); return; } @@ -298,13 +301,13 @@ BluetoothMapSmsManager::MasDataHandler(UnixSocketBuffer* aMessage) // [opcode:1][length:2][flags:1][contants:1][Headers:var] if (receivedLength < 5 || !ParseHeaders(&data[5], receivedLength - 5, &pktHeaders)) { - ReplyError(ObexResponseCode::BadRequest); + SendReply(ObexResponseCode::BadRequest); return; } uint8_t response = SetPath(data[3], pktHeaders); if (response != ObexResponseCode::Success) { - ReplyError(response); + SendReply(response); return; } @@ -317,7 +320,7 @@ BluetoothMapSmsManager::MasDataHandler(UnixSocketBuffer* aMessage) // [opcode:1][length:2][Headers:var] if (receivedLength < 3 || !ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) { - ReplyError(ObexResponseCode::BadRequest); + SendReply(ObexResponseCode::BadRequest); return; } @@ -339,27 +342,42 @@ BluetoothMapSmsManager::MasDataHandler(UnixSocketBuffer* aMessage) break; case ObexRequestCode::Get: case ObexRequestCode::GetFinal: { - // [opcode:1][length:2][Headers:var] - if (receivedLength < 3 || + /* When |mDataStream| requires multiple response packets to complete, + * the client should continue to issue GET requests until the final body + * information (i.e., End-of-Body header) arrives, along with + * ObexResponseCode::Success + */ + if (mDataStream) { + nsAutoArrayPtr res(new uint8_t[mRemoteMaxPacketLength]); + if (!ReplyToGetWithHeaderBody(res.get(), kObexRespHeaderSize)) { + BT_LOGR("Failed to reply to MAP GET request."); + SendReply(ObexResponseCode::InternalServerError); + } + return; + } + + // [opcode:1][length:2][Headers:var] + if (receivedLength < 3 || !ParseHeaders(&data[3], receivedLength - 3, &pktHeaders)) { - ReplyError(ObexResponseCode::BadRequest); - return; - } - pktHeaders.GetContentType(type); - if (type.EqualsLiteral("x-obex/folder-listing")) { - HandleSmsMmsFolderListing(pktHeaders); - } else if (type.EqualsLiteral("x-bt/MAP-msg-listing")) { - HandleSmsMmsMsgListing(pktHeaders); - } else if (type.EqualsLiteral("x-bt/message")) { - HandleSmsMmsGetMessage(pktHeaders); - } else { - BT_LOGR("Unknown MAP request type: %s", - NS_ConvertUTF16toUTF8(type).get()); - } + SendReply(ObexResponseCode::BadRequest); + return; + } + + pktHeaders.GetContentType(type); + if (type.EqualsLiteral("x-obex/folder-listing")) { + HandleSmsMmsFolderListing(pktHeaders); + } else if (type.EqualsLiteral("x-bt/MAP-msg-listing")) { + HandleSmsMmsMsgListing(pktHeaders); + } else if (type.EqualsLiteral("x-bt/message")) { + HandleSmsMmsGetMessage(pktHeaders); + } else { + BT_LOGR("Unknown MAP request type: %s", + NS_ConvertUTF16toUTF8(type).get()); } break; + } default: - ReplyError(ObexResponseCode::NotImplemented); + SendReply(ObexResponseCode::NotImplemented); BT_LOGR("Unrecognized ObexRequestCode %x", opCode); break; } @@ -471,6 +489,9 @@ void BluetoothMapSmsManager::AfterMapSmsDisconnected() { mMasConnected = false; + mBodyRequired = false; + mFractionDeliverRequired = false; + // To ensure we close MNS connection DestroyMnsObexConnection(); } @@ -543,6 +564,81 @@ BluetoothMapSmsManager::ReplyToSetPath() SendMasObexData(req, ObexResponseCode::Success, index); } +bool +BluetoothMapSmsManager::ReplyToGetWithHeaderBody(uint8_t* aResponse, + unsigned int aIndex) +{ + if (!mMasConnected) { + return false; + } + + /** + * This response consists of following parts: + * - Part 1: [response code:1][length:2] + * - Part 2a: [headerId:1][length:2][EndOfBody:0] + * or + * - Part 2b: [headerId:1][length:2][Body:var] + */ + // ---- Part 1: [response code:1][length:2] ---- // + // [response code:1][length:2] will be set in |SendObexData|. + // Reserve index for them here + uint64_t bytesAvailable = 0; + nsresult rv = mDataStream->Available(&bytesAvailable); + if (NS_FAILED(rv)) { + BT_LOGR("Failed to get available bytes from input stream. rv=0x%x", + static_cast(rv)); + return false; + } + + /* In practice, some platforms can only handle zero length End-of-Body + * header separately with Body header. + * Thus, append End-of-Body only if the data stream had been sent out, + * otherwise, send 'Continue' to request for next GET request. + */ + unsigned int opcode; + if (!bytesAvailable) { + // ---- Part 2a: [headerId:1][length:2][EndOfBody:0] ---- // + aIndex += AppendHeaderEndOfBody(&aResponse[aIndex]); + + // Close input stream + mDataStream->Close(); + mDataStream = nullptr; + + opcode = ObexResponseCode::Success; + } else { + // ---- Part 2b: [headerId:1][length:2][Body:var] ---- // + MOZ_ASSERT(mDataStream); + + // Compute remaining packet size to append Body, excluding Body's header + uint32_t remainingPacketSize = + mRemoteMaxPacketLength - kObexBodyHeaderSize - aIndex; + + // Read blob data from input stream + uint32_t numRead = 0; + nsAutoArrayPtr buf(new char[remainingPacketSize]); + nsresult rv = mDataStream->Read(buf, remainingPacketSize, &numRead); + if (NS_FAILED(rv)) { + BT_LOGR("Failed to read from input stream. rv=0x%x", + static_cast(rv)); + return false; + } + + // |numRead| must be non-zero + MOZ_ASSERT(numRead); + + aIndex += AppendHeaderBody(&aResponse[aIndex], + remainingPacketSize, + reinterpret_cast(buf.get()), + numRead); + + opcode = ObexResponseCode::Continue; + } + + SendMasObexData(aResponse, opcode, aIndex); + + return true; +} + void BluetoothMapSmsManager::ReplyToPut() { @@ -568,10 +664,10 @@ BluetoothMapSmsManager::ReplyToFolderListing(long aMasId, bool BluetoothMapSmsManager::ReplyToMessagesListing(BlobParent* aActor, - long aMasId, - bool aNewMessage, - const nsAString& aTimestamp, - int aSize) + long aMasId, + bool aNewMessage, + const nsAString& aTimestamp, + int aSize) { RefPtr impl = aActor->GetBlobImpl(); RefPtr blob = Blob::Create(nullptr, impl); @@ -586,8 +682,74 @@ BluetoothMapSmsManager::ReplyToMessagesListing(Blob* aBlob, long aMasId, const nsAString& aTimestamp, int aSize) { - // TODO: Implement in Bug 1211769 - return false; + /* If the response code is 0x90 or 0xA0, response consists of following parts: + * - Part 1: [response code:1][length:2] + * - Part 2: [headerId:1][length:2][appParam:var] + * where [appParam:var] includes: + * [NewMessage:3] = [tagId:1][length:1][value:1] + * [MseTime:var] = [tagId:1][length:1][value:var] + * [MessageListingSize:4] = [tagId:1][length:1][value:2] + * If mBodyRequired is true, + * - Part 3: [headerId:1][length:2][Body:var] + */ + // ---- Part 1: [response code:1][length:2] ---- // + // [response code:1][length:2] will be set in |SendObexData|. + // Reserve index here + nsAutoArrayPtr res(new uint8_t[mRemoteMaxPacketLength]); + unsigned int index = kObexRespHeaderSize; + + // ---- Part 2: headerId:1][length:2][appParam:var] ---- // + // MSETime - String with the current time basis and UTC-offset of the MSE + nsCString timestampStr = NS_ConvertUTF16toUTF8(aTimestamp); + const uint8_t* str = reinterpret_cast(timestampStr.get()); + uint8_t len = timestampStr.Length(); + + // Total length: [NewMessage:3] + [MseTime:var] + [MessageListingSize:4] + nsAutoArrayPtr appParameters(new uint8_t[len + 9]); + uint8_t newMessage = aNewMessage ? 1 : 0; + + AppendAppParameter(appParameters, + 3, + (uint8_t) Map::AppParametersTagId::NewMessage, + &newMessage, + sizeof(newMessage)); + + AppendAppParameter(appParameters + 3, + len + 2, + (uint8_t) Map::AppParametersTagId::MSETime, + str, + len); + + uint8_t msgListingSize[2]; + BigEndian::writeUint16(&msgListingSize[0], aSize); + + AppendAppParameter(appParameters + 5 + len, + 4, + (uint8_t) Map::AppParametersTagId::MessagesListingSize, + msgListingSize, + sizeof(msgListingSize)); + + index += AppendHeaderAppParameters(res + index, + mRemoteMaxPacketLength, + appParameters, + len + 9); + + if (mBodyRequired) { + // Open input stream only if |mBodyRequired| is true + if (!GetInputStreamFromBlob(aBlob)) { + SendReply(ObexResponseCode::InternalServerError); + return false; + } + + // ---- Part 3: [headerId:1][length:2][Body:var] ---- // + ReplyToGetWithHeaderBody(res, index); + // Reset flag + mBodyRequired = false; + } else { + SendMasObexData(res, ObexResponseCode::Success, index); + } + + return true; } bool @@ -602,29 +764,103 @@ BluetoothMapSmsManager::ReplyToGetMessage(BlobParent* aActor, long aMasId) bool BluetoothMapSmsManager::ReplyToGetMessage(Blob* aBlob, long aMasId) { - // TODO: Implement in Bug 1211769 - return false; + if (!GetInputStreamFromBlob(aBlob)) { + SendReply(ObexResponseCode::InternalServerError); + return false; + } + + /* + * If the response code is 0x90 or 0xA0, response consists of following parts: + * - Part 1: [response code:1][length:2] + * If mFractionDeliverRequired is true, + * - Part 2: [headerId:1][length:2][appParameters:3] + * - Part 3: [headerId:1][length:2][Body:var] + * where [appParameters] includes: + * [FractionDeliver:3] = [tagId:1][length:1][value: 1] + * otherwise, + * - Part 2: [headerId:1][length:2][appParameters:3] + */ + // ---- Part 1: [response code:1][length:2] ---- // + // [response code:1][length:2] will be set in |SendObexData|. + // Reserve index here + nsAutoArrayPtr res (new uint8_t[mRemoteMaxPacketLength]); + unsigned int index = kObexRespHeaderSize; + + if (mFractionDeliverRequired) { + // ---- Part 2: [headerId:1][length:2][appParam:3] ---- // + uint8_t appParameters[3]; + // TODO: Support FractionDeliver, reply "1(last)" now. + uint8_t fractionDeliver = 1; + AppendAppParameter(appParameters, + sizeof(appParameters), + (uint8_t) Map::AppParametersTagId::FractionDeliver, + &fractionDeliver, + sizeof(fractionDeliver)); + + index += AppendHeaderAppParameters(res + index, + mRemoteMaxPacketLength, + appParameters, + sizeof(appParameters)); + } + + // TODO: Support bMessage encoding in bug 1166652. + // ---- Part 3: [headerId:1][length:2][Body:var] ---- // + ReplyToGetWithHeaderBody(res.get(), index); + mFractionDeliverRequired = false; + + return true; +} + +bool +BluetoothMapSmsManager::ReplyToSendMessage( + long aMasId, const nsAString& aHandleId, bool aStatus) +{ + if (!aStatus) { + SendReply(ObexResponseCode::InternalServerError); + return true; + } + + /* Handle is mandatory if the response code is success (0x90 or 0xA0). + * The Name header shall be used to contain the handle that was assigned by + * the MSE device to the message that was pushed by the MCE device. + * The handle shall be represented by a null-terminated Unicode text strings + * with 16 hexadecimal digits. + */ + int len = aHandleId.Length(); + nsAutoArrayPtr handleId(new uint8_t[(len + 1) * 2]); + const char16_t* handleIdPtr = aHandleId.BeginReading(); + + for (int i = 0; i < len; i++) { + *(handleId + (i * 2)) = (uint8_t)(handleIdPtr[i] >> 8); + *(handleId + (i * 2 + 1)) = (uint8_t)handleIdPtr[i]; + } + + *(handleId + (len * 2)) = 0x00; + *(handleId + (len * 2 + 1)) = 0x00; + + nsAutoArrayPtr res(new uint8_t[mRemoteMaxPacketLength]); + int index = kObexRespHeaderSize; + index += AppendHeaderName(res + index, mRemoteMaxPacketLength - index, + handleId, (len + 1) * 2); + SendMasObexData(res.get(), ObexResponseCode::Success, index); + + return true; } bool BluetoothMapSmsManager::ReplyToSetMessageStatus(long aMasId, bool aStatus) { - // TODO: Implement in Bug 1211769 - return false; -} - -bool -BluetoothMapSmsManager::ReplyToSendMessage(long aMasId, bool aStatus) -{ - // TODO: Implement in Bug 1211769 - return false; + SendReply(aStatus ? ObexResponseCode::Success : + ObexResponseCode::InternalServerError); + return true; } bool BluetoothMapSmsManager::ReplyToMessageUpdate(long aMasId, bool aStatus) { - // TODO: Implement in Bug 1211769 - return false; + SendReply(aStatus ? ObexResponseCode::Success : + ObexResponseCode::InternalServerError); + return true; } void @@ -768,6 +1004,12 @@ BluetoothMapSmsManager::AppendBtNamedValueByTagId( switch (aTagId) { case Map::AppParametersTagId::MaxListCount: { uint16_t maxListCount = BigEndian::readUint16(buf); + /* MAP specification 5.4.3.1/5.5.4.1 + * If MaxListCount = 0, the response shall not contain the Body header. + * The MSE shall ignore the request-parameters "ListStartOffset", + * "SubjectLength" and "ParameterMask". + */ + mBodyRequired = (maxListCount != 0); BT_LOGR("max list count: %d", maxListCount); AppendNamedValue(aValues, "maxListCount", static_cast(maxListCount)); @@ -787,12 +1029,8 @@ BluetoothMapSmsManager::AppendBtNamedValueByTagId( break; } case Map::AppParametersTagId::ParameterMask: { - /* Table 6.5, MAP 6.3.1. ParameterMask is Bit 16-31 Reserved for future - * use. The reserved bits shall be set to 0 by MCE and discarded by MSE. - */ - uint32_t parameterMask = BigEndian::readUint32(buf); - BT_LOGR("msg parameterMask : %d", parameterMask); - AppendNamedValue(aValues, "parameterMask", parameterMask); + InfallibleTArray parameterMask = PackParameterMask(buf, 64); + AppendNamedValue(aValues, "parameterMask", BluetoothValue(parameterMask)); break; } case Map::AppParametersTagId::FilterMessageType: { @@ -890,6 +1128,11 @@ BluetoothMapSmsManager::AppendBtNamedValueByTagId( AppendNamedValue(aValues, "charset", filterCharset); break; } + case Map::AppParametersTagId::FractionRequest: { + mFractionDeliverRequired = true; + AppendNamedValue(aValues, "fractionRequest", (buf[0] != 0)); + break; + } case Map::AppParametersTagId::StatusIndicator: { using namespace mozilla::dom::StatusIndicatorsValues; uint32_t filterStatusIndicator = @@ -925,6 +1168,30 @@ BluetoothMapSmsManager::AppendBtNamedValueByTagId( } } +InfallibleTArray +BluetoothMapSmsManager::PackParameterMask(uint8_t* aData, int aSize) +{ + InfallibleTArray parameterMask; + + /* Table 6.5, MAP 6.3.1. ParameterMask is Bit 16-31 Reserved for future + * use. The reserved bits shall be set to 0 by MCE and discarded by MSE. + * convert big endian to little endian + */ + uint32_t x = BigEndian::readUint32(aData); + + uint32_t count = 0; + while (x) { + if (x & 1) { + parameterMask.AppendElement(count); + } + + ++count; + x >>= 1; + } + + return parameterMask; +} + void BluetoothMapSmsManager::HandleSmsMmsMsgListing(const ObexHeaderSet& aHeader) { @@ -974,6 +1241,8 @@ BluetoothMapSmsManager::HandleSmsMmsGetMessage(const ObexHeaderSet& aHeader) Map::AppParametersTagId::Attachment); AppendBtNamedValueByTagId(aHeader, data, Map::AppParametersTagId::Charset); + AppendBtNamedValueByTagId(aHeader, data, + Map::AppParametersTagId::FractionRequest); bs->DistributeSignal(NS_LITERAL_STRING(MAP_GET_MESSAGE_REQ_ID), NS_LITERAL_STRING(KEY_ADAPTER), @@ -1147,17 +1416,35 @@ BluetoothMapSmsManager::HandleSmsMmsPushMessage(const ObexHeaderSet& aHeader) NS_LITERAL_STRING(KEY_ADAPTER), data); } -void -BluetoothMapSmsManager::ReplyError(uint8_t aError) +bool +BluetoothMapSmsManager::GetInputStreamFromBlob(Blob* aBlob) { - BT_LOGR("[0x%x]", aError); + if (mDataStream) { + mDataStream->Close(); + mDataStream = nullptr; + } + + ErrorResult rv; + aBlob->GetInternalStream(getter_AddRefs(mDataStream), rv); + if (rv.Failed()) { + BT_LOGR("Failed to get internal stream from blob. rv=0x%x", + rv.ErrorCodeAsInt()); + return false; + } + + return true; +} + +void +BluetoothMapSmsManager::SendReply(uint8_t aResponseCode) +{ + BT_LOGR("[0x%x]", aResponseCode); // Section 3.2 "Response Format", IrOBEX 1.2 // [opcode:1][length:2][Headers:var] - uint8_t req[255]; - int index = 3; + uint8_t req[3]; - SendMasObexData(req, aError, index); + SendMasObexData(req, aResponseCode, 3); } void @@ -1217,6 +1504,11 @@ BluetoothMapSmsManager::OnSocketDisconnect(BluetoothSocket* aSocket) { MOZ_ASSERT(aSocket); + if (mDataStream) { + mDataStream->Close(); + mDataStream = nullptr; + } + // MNS socket is disconnected if (aSocket == mMnsSocket) { mMnsConnected = false; diff --git a/dom/bluetooth/bluedroid/BluetoothMapSmsManager.h b/dom/bluetooth/bluedroid/BluetoothMapSmsManager.h index cbd49f56a18..9977bf23e42 100644 --- a/dom/bluetooth/bluedroid/BluetoothMapSmsManager.h +++ b/dom/bluetooth/bluedroid/BluetoothMapSmsManager.h @@ -168,12 +168,14 @@ public: * Reply to the *in-process* 'sendmessage' request. * * @param aMasId [in] MAS id + * @param aHandleId [in] Handle id * @param aStatus [in] success or failure * * @return true if the response packet has been packed correctly and started * to be sent to the remote device; false otherwise. */ - bool ReplyToSendMessage(long aMasId, bool aStatus); + bool ReplyToSendMessage( + long aMasId, const nsAString& aHandleId , bool aStatus); /** * Reply to the *in-process* 'messageupdate' request. @@ -196,9 +198,17 @@ private: void ReplyToConnect(); void ReplyToDisconnectOrAbort(); + + /* + * This function replies to Get request with Header Body, in case of a GET + * operation returning an object that is too big to fit in one response + * packet. If the operation requires multiple response packets to complete + * after the Final bit is set in the request. + */ + bool ReplyToGetWithHeaderBody(uint8_t* aResponse, unsigned int aIndex); void ReplyToSetPath(); void ReplyToPut(); - void ReplyError(uint8_t aError); + void SendReply(uint8_t aResponse); void HandleNotificationRegistration(const ObexHeaderSet& aHeader); void HandleEventReport(const ObexHeaderSet& aHeader); @@ -213,6 +223,7 @@ private: const Map::AppParametersTagId aTagId); void SendMasObexData(uint8_t* aData, uint8_t aOpcode, int aSize); void SendMnsObexData(uint8_t* aData, uint8_t aOpcode, int aSize); + bool StatusResponse(bool aStatus); uint8_t SetPath(uint8_t flags, const ObexHeaderSet& aHeader); bool CompareHeaderTarget(const ObexHeaderSet& aHeader); @@ -224,6 +235,9 @@ private: void SendMnsDisconnectRequest(); void MnsDataHandler(mozilla::ipc::UnixSocketBuffer* aMessage); void MasDataHandler(mozilla::ipc::UnixSocketBuffer* aMessage); + bool GetInputStreamFromBlob(Blob* aBlob); + InfallibleTArray PackParameterMask(uint8_t* aData, int aSize); + /* * Build mandatory folders */ @@ -238,11 +252,16 @@ private: * Record the last command */ int mLastCommand; + // Whether header body is required for the current MessagesListing response. + bool mBodyRequired; + // Whether FractionDeliver is required for the current GetMessage response + bool mFractionDeliverRequired; // MAS OBEX session status. Set when MAS OBEX session is established. bool mMasConnected; // MNS OBEX session status. Set when MNS OBEX session is established. bool mMnsConnected; bool mNtfRequired; + BluetoothAddress mDeviceAddress; unsigned int mRemoteMaxPacketLength; @@ -261,6 +280,11 @@ private: int mBodySegmentLength; nsAutoArrayPtr mBodySegment; + + /** + * The bMessage/message-listing data stream for current processing response + */ + nsCOMPtr mDataStream; }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp index 1565892f381..8b591ac6495 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp @@ -1970,6 +1970,7 @@ BluetoothServiceBluedroid:: ReplyToMapSetMessageStatus( void BluetoothServiceBluedroid:: ReplyToMapSendMessage( long aMasId, + const nsAString& aHandleId, bool aStatus, BluetoothReplyRunnable* aRunnable) { @@ -1980,7 +1981,7 @@ BluetoothServiceBluedroid:: ReplyToMapSendMessage( return; } - map->ReplyToSendMessage(aMasId, aStatus); + map->ReplyToSendMessage(aMasId, aHandleId, aStatus); DispatchReplySuccess(aRunnable); } diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h index 2dc848aba67..23611c48330 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h @@ -224,8 +224,10 @@ public: BluetoothReplyRunnable* aRunnable); virtual void - ReplyToMapSendMessage( - long aMasId, bool aStatus, BluetoothReplyRunnable* aRunnable); + ReplyToMapSendMessage(long aMasId, + const nsAString& aHandleId, + bool aStatus, + BluetoothReplyRunnable* aRunnable); virtual void ReplyToMapMessageUpdate( diff --git a/dom/bluetooth/bluez/BluetoothDBusService.cpp b/dom/bluetooth/bluez/BluetoothDBusService.cpp index b32e0ac219f..3e7e14649d2 100644 --- a/dom/bluetooth/bluez/BluetoothDBusService.cpp +++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp @@ -4560,7 +4560,9 @@ BluetoothDBusService::ReplyToMapSetMessageStatus(long aMasId, } void -BluetoothDBusService::ReplyToMapSendMessage(long aMasId, bool aStatus, +BluetoothDBusService::ReplyToMapSendMessage(long aMasId, + const nsAString& aHandleId, + bool aStatus, BluetoothReplyRunnable* aRunnable) { } diff --git a/dom/bluetooth/bluez/BluetoothDBusService.h b/dom/bluetooth/bluez/BluetoothDBusService.h index 930eccfb615..19560eae3af 100644 --- a/dom/bluetooth/bluez/BluetoothDBusService.h +++ b/dom/bluetooth/bluez/BluetoothDBusService.h @@ -233,6 +233,7 @@ public: virtual void ReplyToMapSendMessage(long aMasId, + const nsAString& aHandleId, bool aStatus, BluetoothReplyRunnable* aRunnable) override; diff --git a/dom/bluetooth/common/BluetoothService.h b/dom/bluetooth/common/BluetoothService.h index d5af6393356..876ff1982ac 100644 --- a/dom/bluetooth/common/BluetoothService.h +++ b/dom/bluetooth/common/BluetoothService.h @@ -388,7 +388,8 @@ public: virtual void ReplyToMapSendMessage( - long aMasId, bool aStatus, BluetoothReplyRunnable* aRunnable) = 0; + long aMasId, const nsAString& aHandleId, bool aStatus, + BluetoothReplyRunnable* aRunnable) = 0; virtual void ReplyToMapMessageUpdate( diff --git a/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.cpp b/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.cpp index 1407e1840ac..697af0fc256 100644 --- a/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.cpp +++ b/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.cpp @@ -202,6 +202,7 @@ BluetoothMapRequestHandle::ReplyToSetMessageStatus(long aMasId, already_AddRefed BluetoothMapRequestHandle::ReplyToSendMessage(long aMasId, + const nsAString& aHandleId, bool aStatus, ErrorResult& aRv) { @@ -220,7 +221,7 @@ BluetoothMapRequestHandle::ReplyToSendMessage(long aMasId, return nullptr; } - bs->ReplyToMapSendMessage(aMasId, aStatus, + bs->ReplyToMapSendMessage(aMasId, aHandleId, aStatus, new BluetoothVoidReplyRunnable(nullptr, promise)); return promise.forget(); diff --git a/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.h b/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.h index 6347aaa00d6..1b3f5e9fd84 100644 --- a/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.h +++ b/dom/bluetooth/common/webapi/BluetoothMapRequestHandle.h @@ -76,7 +76,7 @@ public: ErrorResult& aRv); /** - * Reply to get-message request + * Reply to set-message request * * @param aMasId [in] MAS ID. * @param aStatus [in] MAP set message result. @@ -86,14 +86,15 @@ public: ErrorResult& aRv); /** - * Reply to get-message request + * Reply to send-message request * * @param aMasId [in] MAS ID. + * @param aHandleId [in] Handle ID. * @param aStatus [in] MAP send message result. * @param aRv [out] Error result to set in case of error. */ - already_AddRefed ReplyToSendMessage(long aMasId, bool aStatus, - ErrorResult& aRv); + already_AddRefed ReplyToSendMessage( + long aMasId, const nsAString& aHandleId, bool aStatus, ErrorResult& aRv); /** * Reply to message update request diff --git a/dom/bluetooth/ipc/BluetoothParent.cpp b/dom/bluetooth/ipc/BluetoothParent.cpp index 6576a0bf82a..4fe124b392f 100644 --- a/dom/bluetooth/ipc/BluetoothParent.cpp +++ b/dom/bluetooth/ipc/BluetoothParent.cpp @@ -874,6 +874,7 @@ BluetoothRequestParent::DoRequest(const ReplyToSendMessageRequest& aRequest) MOZ_ASSERT(mRequestType == Request::TReplyToSendMessageRequest); mService->ReplyToMapSendMessage(aRequest.masId(), + aRequest.handleId(), aRequest.messageStatus(), mReplyRunnable.get()); return true; diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp index 08cd25c2f25..5c847cff2b1 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp @@ -506,11 +506,12 @@ BluetoothServiceChildProcess::ReplyToMapSetMessageStatus(long aMasId, void BluetoothServiceChildProcess::ReplyToMapSendMessage(long aMasId, + const nsAString& aHandleId, bool aStatus, BluetoothReplyRunnable* aRunnable) { SendRequest(aRunnable, - ReplyToSendMessageRequest(aMasId, aStatus)); + ReplyToSendMessageRequest(aMasId, nsString(aHandleId), aStatus)); } void diff --git a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h index 14910180cdf..adc9efc5f64 100644 --- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h +++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h @@ -235,8 +235,10 @@ public: BluetoothReplyRunnable* aRunnable) override; virtual void - ReplyToMapSendMessage( - long aMasId, bool aStatus, BluetoothReplyRunnable* aRunnable) override; + ReplyToMapSendMessage(long aMasId, + const nsAString& aHandleId, + bool aStatus, + BluetoothReplyRunnable* aRunnable) override; virtual void ReplyToMapMessageUpdate( diff --git a/dom/bluetooth/ipc/PBluetooth.ipdl b/dom/bluetooth/ipc/PBluetooth.ipdl index b2a6127f81d..5ada1ca7340 100644 --- a/dom/bluetooth/ipc/PBluetooth.ipdl +++ b/dom/bluetooth/ipc/PBluetooth.ipdl @@ -227,6 +227,7 @@ struct ReplyToSetMessageStatusRequest struct ReplyToSendMessageRequest { uint16_t masId; + nsString handleId; bool messageStatus; }; diff --git a/dom/webidl/BluetoothMapRequestHandle.webidl b/dom/webidl/BluetoothMapRequestHandle.webidl index 666487ad294..d5fa6fba2cf 100644 --- a/dom/webidl/BluetoothMapRequestHandle.webidl +++ b/dom/webidl/BluetoothMapRequestHandle.webidl @@ -44,7 +44,7 @@ interface BluetoothMapRequestHandle * if the MAP request operation fails. */ [NewObject, Throws, AvailableIn=CertifiedApps] - Promise replyToSendMessage(long masId, boolean status); + Promise replyToSendMessage(long masId, DOMString handleId, boolean status); /** * Reply Message-Update object to the MAP request. The Promise will be From b7f4a3dc0bd7f5e94209a510b7b8a7c792d4e506 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 03:20:17 -0800 Subject: [PATCH 38/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/fc51e60ec160 Author: Fernando Jiménez Moreno Desc: Merge pull request #32620 from michielbdejong/1200539-sync-app-integration-tests-2 Bug 1200539 - Sync app integration tests, r=ferjm ======== https://hg.mozilla.org/integration/gaia-central/rev/d7072a982bbc Author: Michiel de Jong Desc: Bug 1200539 - Integration tests for Sync app, r=ferjm --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index e7b0bb43340..e4aae8d0a87 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "ad79017bbab3229a02c49cd4cd0c8401e72fc640", + "git_revision": "b2991b4c68d2f5339443ebe85dafa5946240bbc1", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "40e7c7a1ef524230c0c4a9b30b37ec263ee9291a", + "revision": "fc51e60ec160446cc3930c883377bc70a94d89f4", "repo_path": "integration/gaia-central" } From dba6fae1fea1edfe0279c3060697fa0fd37b65a6 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 03:21:39 -0800 Subject: [PATCH 39/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 65dd38e074f..c5b1f0b4c66 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index c27fc055762..5c93a7ebe7c 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index f550c2d09ea..25ee3057698 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 414357a0e3e..41f0d7d37f6 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index aec4c85f979..e1c970b8a76 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 9b7a0b9e5e1..e798f93a767 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index f550c2d09ea..25ee3057698 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 6de2a96ed79..da019a42b17 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index f10db2b7a31..f571659cd77 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index d9d1234c498..12685471935 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 60ca8f0fd90..1a918c8c5fd 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 9346be0b97b0d12d4d5196ac9cbf1cfa1a4907b0 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 05:35:15 -0800 Subject: [PATCH 40/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/8bfc3fed5e09 Author: Aleh Zasypkin Desc: Merge pull request #33071 from azasypkin/bug-1200583-test-storage Bug 1200583 - [Messages][Tests] It is difficult to debug when we forget to set the mock data in an integration test. Storage code is rewritten so that data can be set _before_ app is started. r=julien ======== https://hg.mozilla.org/integration/gaia-central/rev/00b18175be30 Author: Aleh Zasypkin Desc: Bug 1200583 - [Messages][Tests] It is difficult to debug when we forget to set the mock data in an integration test. Storage code is rewritten so that data can be set _before_ app is started. r=julien --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index e4aae8d0a87..acf86ff24af 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "b2991b4c68d2f5339443ebe85dafa5946240bbc1", + "git_revision": "4b7779a91f7bd3c57c7ff1ada1f03a6a8b0bc5ec", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "fc51e60ec160446cc3930c883377bc70a94d89f4", + "revision": "8bfc3fed5e09dc426b7b418b4d3802ff63279e83", "repo_path": "integration/gaia-central" } From 04dd2c1fb59dbaef3e12e54e5c23e36ebc877c40 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 05:36:38 -0800 Subject: [PATCH 41/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index c5b1f0b4c66..85efe94a0b8 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 5c93a7ebe7c..00766945ed3 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 25ee3057698..e16661224aa 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 41f0d7d37f6..aa2ceeaf249 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index e1c970b8a76..98bfa3ef2b1 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index e798f93a767..71ed262b483 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 25ee3057698..e16661224aa 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index da019a42b17..489fabffed0 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index f571659cd77..beb2691b518 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 12685471935..dcf902ce2b6 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 1a918c8c5fd..1ec228e3cd9 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 3ebdf47a13036d7fbfb2392621d96fac4fb95a0d Mon Sep 17 00:00:00 2001 From: Wander Lairson Costa Date: Tue, 10 Nov 2015 12:23:41 -0200 Subject: [PATCH 42/71] Bug 1223259 part 1: Provide nexus 4 and 5 ota builds for v2.5 branch. r=pmoore --- b2g/config/nexus-4-kk/config.json | 6 ++- b2g/config/nexus-5-l/config.json | 6 ++- .../tasks/branches/base_job_flags.yml | 2 + .../branches/mozilla-b2g44_v2_5/job_flags.yml | 12 +++++ .../tasks/builds/b2g_nexus_4_kk_ota_debug.yml | 44 +++++++++++++++++++ .../tasks/builds/b2g_nexus_5l_ota_debug.yml | 44 +++++++++++++++++++ 6 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 testing/taskcluster/tasks/builds/b2g_nexus_4_kk_ota_debug.yml create mode 100644 testing/taskcluster/tasks/builds/b2g_nexus_5l_ota_debug.yml diff --git a/b2g/config/nexus-4-kk/config.json b/b2g/config/nexus-4-kk/config.json index aaaf402790a..acbf6cc6a5a 100644 --- a/b2g/config/nexus-4-kk/config.json +++ b/b2g/config/nexus-4-kk/config.json @@ -11,14 +11,16 @@ "upload_files": [ "{objdir}/dist/b2g-*.crashreporter-symbols.zip", "{objdir}/dist/b2g-*.tar.gz", - "{workdir}/sources.xml" + "{workdir}/sources.xml", + "{workdir}/out/target/product/nexus-4-kk/fota-*-update-*.mar" ], "public_upload_files": [ "{objdir}/dist/b2g-*.crashreporter-symbols.zip", "{objdir}/dist/b2g-*.tar.gz", "{workdir}/sources.xml", "{objdir}/dist/b2g-update/*.mar", - "{workdir}/mako.zip" + "{workdir}/mako.zip", + "{workdir}/out/target/product/nexus-4-kk/fota-*-update.mar" ], "zip_files": [ ["{workdir}/out/target/product/mako/*.img", "out/target/product/mako/"], diff --git a/b2g/config/nexus-5-l/config.json b/b2g/config/nexus-5-l/config.json index 3de1ca340cd..00b8d36e91b 100644 --- a/b2g/config/nexus-5-l/config.json +++ b/b2g/config/nexus-5-l/config.json @@ -11,14 +11,16 @@ "upload_files": [ "{objdir}/dist/b2g-*.crashreporter-symbols.zip", "{objdir}/dist/b2g-*.tar.gz", - "{workdir}/sources.xml" + "{workdir}/sources.xml", + "{workdir}/out/target/product/nexus-5-l/fota-*-update-*.mar" ], "public_upload_files": [ "{objdir}/dist/b2g-*.crashreporter-symbols.zip", "{objdir}/dist/b2g-*.tar.gz", "{workdir}/sources.xml", "{objdir}/dist/b2g-update/*.mar", - "{workdir}/hammerhead.zip" + "{workdir}/hammerhead.zip", + "{workdir}/out/target/product/nexus-5-l/fota-*-update.mar" ], "zip_files": [ ["{workdir}/out/target/product/hammerhead/*.img", "out/target/product/hammerhead/"], diff --git a/testing/taskcluster/tasks/branches/base_job_flags.yml b/testing/taskcluster/tasks/branches/base_job_flags.yml index 20c094f8916..02c17ebf151 100644 --- a/testing/taskcluster/tasks/branches/base_job_flags.yml +++ b/testing/taskcluster/tasks/branches/base_job_flags.yml @@ -65,8 +65,10 @@ flags: - nexus-4 - nexus-4-eng - nexus-4-kk + - nexus-4-kk-ota - nexus-4-kk-eng - nexus-5l + - nexus-5l-ota - nexus-5l-eng - dolphin - dolphin-eng diff --git a/testing/taskcluster/tasks/branches/mozilla-b2g44_v2_5/job_flags.yml b/testing/taskcluster/tasks/branches/mozilla-b2g44_v2_5/job_flags.yml index b44d14ea8a2..594d9cfaf97 100644 --- a/testing/taskcluster/tasks/branches/mozilla-b2g44_v2_5/job_flags.yml +++ b/testing/taskcluster/tasks/branches/mozilla-b2g44_v2_5/job_flags.yml @@ -18,6 +18,18 @@ builds: types: debug: task: tasks/builds/b2g_flame_kk_ota_debug.yml + nexus-4-kk-ota: + platforms: + - b2g + types: + debug: + task: tasks/builds/b2g_nexus_4_kk_ota_debug.yml + nexus-5l-ota: + platforms: + - b2g + types: + debug: + task: tasks/builds/b2g_nexus_5l_ota_debug.yml # Just needed for parser sake tests: diff --git a/testing/taskcluster/tasks/builds/b2g_nexus_4_kk_ota_debug.yml b/testing/taskcluster/tasks/builds/b2g_nexus_4_kk_ota_debug.yml new file mode 100644 index 00000000000..3ddc2100bf1 --- /dev/null +++ b/testing/taskcluster/tasks/builds/b2g_nexus_4_kk_ota_debug.yml @@ -0,0 +1,44 @@ +$inherits: + from: 'tasks/builds/b2g_phone_base.yml' + variables: + build_name: 'nexus-4-kk-ota' + build_type: 'debug' +task: + workerType: balrog + metadata: + name: '[TC] B2G Nexus 4 KK OTA (userdebug)' + scopes: + - 'docker-worker:cache:build-nexus-4-kk-ota-debug' + - 'docker-worker:cache:build-nexus-4-kk-ota-debug-objdir-gecko-{{project}}' + - 'docker-worker:feature:balrogVPNProxy' + + payload: + features: + balrogVPNProxy: true + cache: + build-nexus-4-kk-ota-debug: /home/worker/workspace + build-nexus-4-kk-ota-debug-objdir-gecko-{{project}}: /home/worker/objdir-gecko + env: + VARIANT: userdebug + B2G_DEBUG: 0 + TARGET: 'nexus-4-kk' + DEBUG: 0 + DOGFOOD: 1 + command: + - > + checkout-gecko workspace && + cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder && + buildbot_step 'Build' ./build-phone-ota.sh $HOME/workspace + extra: + locations: + img: 'private/build/nexus-4-kk.zip' + treeherderEnv: + - staging + treeherder: + symbol: B + groupSymbol: Nexus-4-KK-OTA + groupName: Nexus 4 KitKat Device Image + machine: + platform: b2g-device-image + collection: + debug: true diff --git a/testing/taskcluster/tasks/builds/b2g_nexus_5l_ota_debug.yml b/testing/taskcluster/tasks/builds/b2g_nexus_5l_ota_debug.yml new file mode 100644 index 00000000000..f22862bd1af --- /dev/null +++ b/testing/taskcluster/tasks/builds/b2g_nexus_5l_ota_debug.yml @@ -0,0 +1,44 @@ +$inherits: + from: 'tasks/builds/b2g_phone_base.yml' + variables: + build_name: 'nexus-5-l-ota' + build_type: 'debug' +task: + workerType: balrog + metadata: + name: '[TC] B2G Nexus 5L OTA (userdebug)' + scopes: + - 'docker-worker:cache:build-nexus-5l-ota-debug' + - 'docker-worker:cache:build-nexus-5l-ota-debug-objdir-gecko-{{project}}' + - 'docker-worker:feature:balrogVPNProxy' + + payload: + features: + balrogVPNProxy: true + cache: + build-nexus-5l-ota-debug: /home/worker/workspace + build-nexus-5l-ota-debug-objdir-gecko-{{project}}: /home/worker/objdir-gecko + env: + VARIANT: userdebug + B2G_DEBUG: 0 + TARGET: 'nexus-5-l' + DEBUG: 0 + DOGFOOD: 1 + command: + - > + checkout-gecko workspace && + cd ./workspace/gecko/testing/taskcluster/scripts/phone-builder && + buildbot_step 'Build' ./build-phone-ota.sh $HOME/workspace + extra: + locations: + img: 'private/build/nexus-5-l.zip' + treeherderEnv: + - staging + treeherder: + symbol: B + groupSymbol: Nexus-5L-OTA + groupName: Nexus 5 Lollipop Device Image + machine: + platform: b2g-device-image + collection: + debug: true From da6c69f14c274cc5d997b47c20ef6c85f31abecd Mon Sep 17 00:00:00 2001 From: Wander Lairson Costa Date: Tue, 10 Nov 2015 12:23:42 -0200 Subject: [PATCH 43/71] Bug 1223259 part 2: Disable balrog publishing for OTA builds. r=pmoore OTA builds is publishing FOTA updates (and I don't have a context why) so let's disable automatic balrog publshing and do it manually. --- testing/mozharness/configs/b2g/taskcluster-phone-ota.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/mozharness/configs/b2g/taskcluster-phone-ota.py b/testing/mozharness/configs/b2g/taskcluster-phone-ota.py index 339a241289e..f3667e831c1 100644 --- a/testing/mozharness/configs/b2g/taskcluster-phone-ota.py +++ b/testing/mozharness/configs/b2g/taskcluster-phone-ota.py @@ -8,7 +8,7 @@ config = { 'build-symbols', 'make-updates', 'prep-upload', - 'submit-to-balrog' + #'submit-to-balrog' ], "balrog_credentials_file": "balrog_credentials", "nightly_build": True, From a3c88eba0e2c431010a73c6c160b3e778dea2b8c Mon Sep 17 00:00:00 2001 From: Wander Lairson Costa Date: Tue, 10 Nov 2015 12:23:42 -0200 Subject: [PATCH 44/71] Bug 1223259 part 3: Disable fota builds. r=pmoore Nexus 4 does not support fota builds, so let's disable fota to get v2.5 released. --- testing/mozharness/configs/b2g/taskcluster-phone-ota.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/mozharness/configs/b2g/taskcluster-phone-ota.py b/testing/mozharness/configs/b2g/taskcluster-phone-ota.py index f3667e831c1..324e5e0b679 100644 --- a/testing/mozharness/configs/b2g/taskcluster-phone-ota.py +++ b/testing/mozharness/configs/b2g/taskcluster-phone-ota.py @@ -38,7 +38,7 @@ config = { "GAIA_OPTIMIZE": "1", "WGET_OPTS": "-c -q" }, - "update_types": [ "ota", "fota" ], + "update_types": [ "ota" ], "is_automation": True, "repo_remote_mappings": { 'https://android.googlesource.com/': 'https://git.mozilla.org/external/aosp', From 8da45ec92773c8b1d194c8961895783d8db762b1 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 06:26:27 -0800 Subject: [PATCH 45/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 85efe94a0b8..1359d8c4624 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 00766945ed3..a35dc05fbd5 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index aa2ceeaf249..245c1fc48b9 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -20,7 +20,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 98bfa3ef2b1..61ba0afdac6 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 71ed262b483..e86b9d759fa 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 489fabffed0..c7756042a82 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index beb2691b518..4d82d5ecfa5 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index dcf902ce2b6..ecfecef1266 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -21,7 +21,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 1ec228e3cd9..81f8150d2cf 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -24,7 +24,7 @@ - + From 4c2b84476d7f5e2089881d324c78ee2b91cfa89d Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 07:00:14 -0800 Subject: [PATCH 46/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/0bb38ad31747 Author: Aleh Zasypkin Desc: Merge pull request #32821 from azasypkin/bug-1219264-activity-shim-to-iframe Bug 1219264 - [Messages][NG] Move ActivityShim to shim host iframe. r=schung ======== https://hg.mozilla.org/integration/gaia-central/rev/4b27970b2fd0 Author: Aleh Zasypkin Desc: Bug 1219264 - [Messages][NG] Move ActivityShim to shim host iframe. r=schung --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index acf86ff24af..195d31cfac7 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "4b7779a91f7bd3c57c7ff1ada1f03a6a8b0bc5ec", + "git_revision": "2469862c035b76b995cdb3a26737270c65327a41", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "8bfc3fed5e09dc426b7b418b4d3802ff63279e83", + "revision": "0bb38ad317478cb2e98be2899e3c6ee4616f3eeb", "repo_path": "integration/gaia-central" } From f8cf7d9dc2c3b25b2eb17fa97bdce3fe3762c67d Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 07:01:37 -0800 Subject: [PATCH 47/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 1359d8c4624..eb45031f3d1 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index a35dc05fbd5..5af6ce88aae 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index e16661224aa..da7e312cacb 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 245c1fc48b9..2b63913702a 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 61ba0afdac6..40470a14f53 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index e86b9d759fa..330f7a6a2cd 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index e16661224aa..da7e312cacb 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index c7756042a82..8afb873409c 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 4d82d5ecfa5..4f352cad095 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index ecfecef1266..ccbca2d2d3c 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 81f8150d2cf..40bfb4f2281 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 062582a947192e0ac08d86d0d2f9d3103a408121 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 07:25:12 -0800 Subject: [PATCH 48/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/07276754bcbf Author: gasolin Desc: Merge pull request #33096 from gasolin/issue-1211357 Bug 1211357 - Remove bluetooth APIv1 code from System, r=timdream ======== https://hg.mozilla.org/integration/gaia-central/rev/7c265dc61929 Author: gasolin Desc: Bug 1211357 - Remove bluetooth APIv1 code from System, r=timdream --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 195d31cfac7..517eb707c30 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "2469862c035b76b995cdb3a26737270c65327a41", + "git_revision": "94a7fe2bdc344cd51429dfdbbcd2a59f4e43cd26", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "0bb38ad317478cb2e98be2899e3c6ee4616f3eeb", + "revision": "07276754bcbfe48885d0beafb3cd26adb2aa2203", "repo_path": "integration/gaia-central" } From 67bf8e80a763115ea0e5c3f5d9fc4bb8a5ab62fb Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 07:26:35 -0800 Subject: [PATCH 49/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index eb45031f3d1..e9e9427bb19 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 5af6ce88aae..8b4538e700d 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index da7e312cacb..6708dac160a 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 2b63913702a..aa51e0a75a9 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 40470a14f53..5335ce0f9bd 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 330f7a6a2cd..3662751a707 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index da7e312cacb..6708dac160a 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 8afb873409c..125f4af786d 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 4f352cad095..ebcd1f435fc 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index ccbca2d2d3c..db57ef5cf8a 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 40bfb4f2281..088df103a4e 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 33dba5fffc652c023a23954483712586b8bbe87b Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 08:23:13 -0800 Subject: [PATCH 50/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/92f4f7705543 Author: gasolin Desc: Merge pull request #32228 from gasolin/issue-1211342 Bug 1211342 - Remove bluetooth APIv1 code from Bluetooth, r=timdream ======== https://hg.mozilla.org/integration/gaia-central/rev/0d566255ee07 Author: gasolin Desc: Bug 1211342 - Remove bluetooth APIv1 code from Bluetooth, r=timdream --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 517eb707c30..df4ac2a22e7 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "94a7fe2bdc344cd51429dfdbbcd2a59f4e43cd26", + "git_revision": "f8357f6fea5cbdf1d98cee582905ce63dbf3f378", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "07276754bcbfe48885d0beafb3cd26adb2aa2203", + "revision": "92f4f7705543f0ae060433032589598bb94de9d1", "repo_path": "integration/gaia-central" } From 5b42d9280dc6dfa007614c1c6ac4f9ba8ca74111 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 08:24:37 -0800 Subject: [PATCH 51/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index e9e9427bb19..92d4f8728cf 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 8b4538e700d..54de8ac965c 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 6708dac160a..a64d7116ae2 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index aa51e0a75a9..2c54187954a 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 5335ce0f9bd..41a13cb00fb 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 3662751a707..71cfdce6283 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 6708dac160a..a64d7116ae2 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 125f4af786d..17a11c99ebb 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index ebcd1f435fc..824b8f53e94 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index db57ef5cf8a..996e1038632 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 088df103a4e..751bbb454bb 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 9fd2fe6515966905de194fb1fbe1c799e91c2a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacobo=20Aragunde=20P=C3=A9rez?= Date: Tue, 10 Nov 2015 09:15:10 -0800 Subject: [PATCH 52/71] Bug 1219712: Fix syntax error in test. r=kwierso --- addon-sdk/source/test/test-simple-storage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon-sdk/source/test/test-simple-storage.js b/addon-sdk/source/test/test-simple-storage.js index 8273c86f319..2eb449bb6db 100644 --- a/addon-sdk/source/test/test-simple-storage.js +++ b/addon-sdk/source/test/test-simple-storage.js @@ -286,7 +286,7 @@ exports.testSetNoSetRead = function (assert, done) { function setGetRoot(assert, done, val, compare) { - compare = compare || (a, b) => a === b; + compare = compare || ((a, b) => a === b); // Load the module once, set a value. let loader = Loader(module); From 120de5d9cc6e6f3f7992462855b15ce04aafcc88 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 09:23:13 -0800 Subject: [PATCH 53/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/1bf95335fc99 Author: Michael Henretty Desc: Merge pull request #33113 from mikehenrty/bug-1223374-bookmark-favicon-to-pinning Bug 1223374 - Migrate bookmark_invalid_favicon_test.js to pinning the… ======== https://hg.mozilla.org/integration/gaia-central/rev/a3cdd0e315c9 Author: Michael Henretty Desc: Bug 1223374 - Migrate bookmark_invalid_favicon_test.js to pinning the web --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index df4ac2a22e7..a71ea657f33 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "f8357f6fea5cbdf1d98cee582905ce63dbf3f378", + "git_revision": "c8c55fa52206e0b19e43beba34aa34ffbdf06120", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "92f4f7705543f0ae060433032589598bb94de9d1", + "revision": "1bf95335fc99e1e82730cd50eb16c23a38f6d66e", "repo_path": "integration/gaia-central" } From 65157dd02ab58a9769a2ad6b98b24d878cb08bbd Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 09:24:36 -0800 Subject: [PATCH 54/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 92d4f8728cf..c206f230922 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 54de8ac965c..2cc357804f0 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index a64d7116ae2..92db81d614c 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 2c54187954a..bbbcee830c5 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 41a13cb00fb..b6a18ac1c2a 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 71cfdce6283..818fc7d4078 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index a64d7116ae2..92db81d614c 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 17a11c99ebb..37fe302509b 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 824b8f53e94..274d83b782e 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 996e1038632..ca569c33a36 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 751bbb454bb..b3632629930 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 225634fee80511ce1d089f1d0e4788c8f0ca9a6b Mon Sep 17 00:00:00 2001 From: Kit Cambridge Date: Tue, 10 Nov 2015 09:29:47 -0800 Subject: [PATCH 55/71] Bug 1220531 - Link to the Push notifications article on SUMO. r=MattN --- browser/app/profile/firefox.js | 2 -- .../alerts/browser_notification_permission_migration.js | 4 +++- browser/components/nsBrowserGlue.js | 6 ++++-- browser/components/preferences/in-content/content.js | 3 ++- testing/profiles/prefs_general.js | 3 +-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index ae33a8e60b4..ea0987e58ea 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -995,8 +995,6 @@ pref("urlclassifier.downloadAllowTable", "goog-downloadwhite-digest256"); #endif pref("browser.geolocation.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/geolocation/"); -pref("browser.push.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/push/"); -pref("browser.push.warning.migrationURL", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/push#w_upgraded-notifications"); pref("browser.EULA.version", 3); pref("browser.rights.version", 3); diff --git a/browser/base/content/test/alerts/browser_notification_permission_migration.js b/browser/base/content/test/alerts/browser_notification_permission_migration.js index 688af8aaf55..b015e59a7d6 100644 --- a/browser/base/content/test/alerts/browser_notification_permission_migration.js +++ b/browser/base/content/test/alerts/browser_notification_permission_migration.js @@ -24,7 +24,9 @@ add_task(function* test_permissionMigration() { let alertWindow = yield alertWindowPromise; info("Clicking on notification"); - let url = Services.urlFormatter.formatURLPref("browser.push.warning.migrationURL"); + let url = + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "push#w_upgraded-notifications"; let closePromise = promiseWindowClosed(alertWindow); let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url); EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow); diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 3a74a55c7b5..71ce7738b26 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -2287,7 +2287,8 @@ BrowserGlue.prototype = { "chrome://branding/content/about-logo.png"; let title = gBrowserBundle.GetStringFromName("webNotifications.upgradeTitle"); let text = gBrowserBundle.GetStringFromName("webNotifications.upgradeBody"); - let url = Services.urlFormatter.formatURLPref("browser.push.warning.migrationURL"); + let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + + "push#w_upgraded-notifications"; AlertsService.showAlertNotification(imageURL, title, text, true, url, clickCallback); @@ -2765,7 +2766,8 @@ ContentPermissionPrompt.prototype = { } var options = { - learnMoreURL: Services.urlFormatter.formatURLPref("browser.push.warning.infoURL"), + learnMoreURL: + Services.urlFormatter.formatURLPref("app.support.baseURL") + "push", }; this._showPrompt(aRequest, message, "desktop-notification", actions, diff --git a/browser/components/preferences/in-content/content.js b/browser/components/preferences/in-content/content.js index 5ce0c331157..673684219b6 100644 --- a/browser/components/preferences/in-content/content.js +++ b/browser/components/preferences/in-content/content.js @@ -74,7 +74,8 @@ var gContentPane = { setEventListener("notificationsDoNotDisturb", "command", gContentPane.toggleDoNotDisturbNotifications); - let notificationInfoURL = Services.urlFormatter.formatURLPref("browser.push.warning.infoURL"); + let notificationInfoURL = + Services.urlFormatter.formatURLPref("app.support.baseURL") + "push"; document.getElementById("notificationsPolicyLearnMore").setAttribute("href", notificationInfoURL); diff --git a/testing/profiles/prefs_general.js b/testing/profiles/prefs_general.js index 3cae435a341..7e1ef0adfab 100644 --- a/testing/profiles/prefs_general.js +++ b/testing/profiles/prefs_general.js @@ -116,8 +116,7 @@ user_pref("network.sntp.pools", "%(server)s"); user_pref("network.sntp.maxRetryCount", 1); // Make sure the notification permission migration test doesn't hit the network. -user_pref("browser.push.warning.infoURL", "http://%(server)s/alerts-dummy/infoURL"); -user_pref("browser.push.warning.migrationURL", "http://%(server)s/alerts-dummy/migrationURL"); +user_pref("app.support.baseURL", "http://%(server)s/support-dummy/"); // Existing tests don't wait for the notification button security delay user_pref("security.notification_enable_delay", 0); From affd07d4c297afe4854eaaaa8b2d47c129ec6e61 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 10:15:33 -0800 Subject: [PATCH 56/71] Bumping gaia.json for 4 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/1cf2d98d4c57 Author: David Flanagan Desc: Merge pull request #33025 from russnicoletti/bug-1147871 Bug 1147871 - Intermittent video/test/unit/thumbnail_list_test.js fail r=djf ======== https://hg.mozilla.org/integration/gaia-central/rev/21966b721421 Author: Russ Nicoletti Desc: Bug 1147871 - Intermittent video/test/unit/thumbnail_list_test.js fail ======== https://hg.mozilla.org/integration/gaia-central/rev/0f80a2036585 Author: Etienne Segonzac Desc: Merge pull request #33108 from etiennesegonzac/bug-1219635 Bug 1219635 - Implement *test_cards_view_kill_apps_with_two_apps.py* as an integration test in JavaScript r=sfoster ======== https://hg.mozilla.org/integration/gaia-central/rev/73229be5e716 Author: Etienne Segonzac Desc: Bug 1219635 - Implement *test_cards_view_kill_apps_with_two_apps.py* as an integration test in JavaScript r=sfoster --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index a71ea657f33..6acdf8e783b 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "c8c55fa52206e0b19e43beba34aa34ffbdf06120", + "git_revision": "55da38674559526e595ceacac69765f0a2a314a4", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "1bf95335fc99e1e82730cd50eb16c23a38f6d66e", + "revision": "1cf2d98d4c5702147dcb818752280e9be1ee6f3d", "repo_path": "integration/gaia-central" } From fec057ec58bb0a878a7f5dcea53639329c285122 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 10:16:57 -0800 Subject: [PATCH 57/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index c206f230922..d2a57701ae6 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 2cc357804f0..8030bf834b3 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 92db81d614c..b313cf6be85 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index bbbcee830c5..ff1ca26897a 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index b6a18ac1c2a..3799d755410 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 818fc7d4078..68e6d175488 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 92db81d614c..b313cf6be85 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 37fe302509b..b28a6615998 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 274d83b782e..13555024f5f 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index ca569c33a36..121c3150dcb 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index b3632629930..5bd97843925 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 0d779f06490747d36851242c4b5113cd262f4a97 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 11:15:17 -0800 Subject: [PATCH 58/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/218023b067fd Author: Fernando Jiménez Moreno Desc: Merge pull request #33114 from michielbdejong/1218278-dataadapter-code-comments Bug 1218278 - Improve DataAdapters code comments, r=ferjm ======== https://hg.mozilla.org/integration/gaia-central/rev/3a2737f80bff Author: Michiel de Jong Desc: Bug 1218278 - Improve DataAdapters code comments, r=ferjm --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 6acdf8e783b..3e995864691 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "55da38674559526e595ceacac69765f0a2a314a4", + "git_revision": "276f24a27efbee485c3edb489b5dd289f29c85c6", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "1cf2d98d4c5702147dcb818752280e9be1ee6f3d", + "revision": "218023b067fd0971441d034926ccccd467f92e46", "repo_path": "integration/gaia-central" } From d122fbd20c539d5c7f34403e9d4820f4ee13f9fa Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 11:16:41 -0800 Subject: [PATCH 59/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index d2a57701ae6..55a5991ce72 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 8030bf834b3..51af954d66b 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index b313cf6be85..5c3af4b8038 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index ff1ca26897a..7941f99b3f9 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 3799d755410..05b269fd57b 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 68e6d175488..5e316f6af41 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index b313cf6be85..5c3af4b8038 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index b28a6615998..90ee4e4a4f7 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 13555024f5f..6042e65e55b 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 121c3150dcb..8cef2dccb2c 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 5bd97843925..5c76721b46e 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 065dceb3ac0e59f8e25ec819e111c075b06755ab Mon Sep 17 00:00:00 2001 From: Marco Bonardo Date: Tue, 10 Nov 2015 20:18:24 +0100 Subject: [PATCH 60/71] Bug 720589 - mMatchCounts may be accessed with a nonexisting index. r=neil --- .../autocomplete/nsAutoCompleteController.cpp | 96 ++++++++++------ .../autocomplete/nsAutoCompleteController.h | 4 +- .../nsAutoCompleteSimpleResult.cpp | 104 ++++++++++++++++-- .../autocomplete/nsAutoCompleteSimpleResult.h | 2 + .../nsIAutoCompleteSimpleResult.idl | 13 ++- .../autocomplete/tests/unit/test_393191.js | 3 +- .../autocomplete/tests/unit/test_660156.js | 34 +++--- .../components/satchel/nsFormAutoComplete.js | 2 +- .../satchel/nsFormAutoCompleteResult.jsm | 2 +- .../satchel/nsFormFillController.cpp | 7 +- .../satchel/nsInputListAutoComplete.js | 10 +- 11 files changed, 201 insertions(+), 76 deletions(-) diff --git a/toolkit/components/autocomplete/nsAutoCompleteController.cpp b/toolkit/components/autocomplete/nsAutoCompleteController.cpp index 8d38043b0b2..929b2e040e9 100644 --- a/toolkit/components/autocomplete/nsAutoCompleteController.cpp +++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp @@ -132,7 +132,6 @@ nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput) aInput->GetSearchCount(&searchCount); mResults.SetCapacity(searchCount); mSearches.SetCapacity(searchCount); - mMatchCounts.SetLength(searchCount); mImmediateSearchesCount = 0; const char *searchCID = kAutoCompleteSearchCID; @@ -629,7 +628,7 @@ nsAutoCompleteController::HandleDelete(bool *_retval) RowIndexToSearch(index, &searchIndex, &rowIndex); NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE); - nsIAutoCompleteResult *result = mResults[searchIndex]; + nsIAutoCompleteResult *result = mResults.SafeObjectAt(searchIndex); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); nsAutoString search; @@ -691,7 +690,7 @@ nsAutoCompleteController::GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aR RowIndexToSearch(aIndex, &searchIndex, aRowIndex); NS_ENSURE_TRUE(searchIndex >= 0 && *aRowIndex >= 0, NS_ERROR_FAILURE); - *aResult = mResults[searchIndex]; + *aResult = mResults.SafeObjectAt(searchIndex); NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE); return NS_OK; } @@ -1522,39 +1521,50 @@ nsresult nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult) { NS_ENSURE_STATE(mInput); + MOZ_ASSERT(aResult, "ProcessResult should always receive a result"); + NS_ENSURE_ARG(aResult); nsCOMPtr input(mInput); - uint16_t result = 0; - if (aResult) - aResult->GetSearchResult(&result); + uint16_t searchResult = 0; + aResult->GetSearchResult(&searchResult); - uint32_t oldMatchCount = 0; - uint32_t matchCount = 0; - if (aResult) - aResult->GetMatchCount(&matchCount); - - int32_t resultIndex = mResults.IndexOf(aResult); - if (resultIndex == -1) { - // cache the result - mResults.AppendObject(aResult); - mMatchCounts.AppendElement(matchCount); - resultIndex = mResults.Count() - 1; - } - else { - oldMatchCount = mMatchCounts[aSearchIndex]; - mMatchCounts[resultIndex] = matchCount; + // The following code supports incremental updating results in 2 ways: + // * The search may reuse the same result, just by adding entries to it. + // * The search may send a new result every time. In this case we merge + // the results and proceed on the same code path as before. + // This way both mSearches and mResults can be indexed by the search index, + // cause we'll always have only one result per search. + if (mResults.IndexOf(aResult) == -1) { + nsIAutoCompleteResult* oldResult = mResults.SafeObjectAt(aSearchIndex); + if (oldResult) { + MOZ_ASSERT(false, "Passing new matches to OnSearchResult with a new " + "nsIAutoCompleteResult every time is deprecated, please " + "update the same result until the search is done"); + // Build a new nsIAutocompleteSimpleResult and merge results into it. + RefPtr mergedResult = + new nsAutoCompleteSimpleResult(); + mergedResult->AppendResult(oldResult); + mergedResult->AppendResult(aResult); + mResults.ReplaceObjectAt(mergedResult, aSearchIndex); + } else { + // This inserts and grows the array if needed. + mResults.ReplaceObjectAt(aResult, aSearchIndex); + } } + // When found the result should have the same index as the search. + MOZ_ASSERT_IF(mResults.IndexOf(aResult) != -1, + mResults.IndexOf(aResult) == aSearchIndex); + MOZ_ASSERT(mResults.Count() >= aSearchIndex + 1, + "aSearchIndex should always be valid for mResults"); bool isTypeAheadResult = false; - if (aResult) { - aResult->GetTypeAheadResult(&isTypeAheadResult); - } + aResult->GetTypeAheadResult(&isTypeAheadResult); if (!isTypeAheadResult) { uint32_t oldRowCount = mRowCount; // If the search failed, increase the match count to include the error // description. - if (result == nsIAutoCompleteResult::RESULT_FAILURE) { + if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) { nsAutoString error; aResult->GetErrorDescription(error); if (!error.IsEmpty()) { @@ -1563,13 +1573,28 @@ nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteRes mTree->RowCountChanged(oldRowCount, 1); } } - } else if (result == nsIAutoCompleteResult::RESULT_SUCCESS || - result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) { + } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS || + searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) { // Increase the match count for all matches in this result. - mRowCount += matchCount - oldMatchCount; + uint32_t totalMatchCount = 0; + for (uint32_t i = 0; i < mResults.Length(); i++) { + nsIAutoCompleteResult* result = mResults.SafeObjectAt(i); + if (result) { + // not all results implement this, so it can likely fail. + bool typeAhead = false; + result->GetTypeAheadResult(&typeAhead); + if (!typeAhead) { + uint32_t matchCount = 0; + result->GetMatchCount(&matchCount); + totalMatchCount += matchCount; + } + } + } + uint32_t delta = totalMatchCount - oldRowCount; + mRowCount += delta; if (mTree) { - mTree->RowCountChanged(oldRowCount, matchCount - oldMatchCount); + mTree->RowCountChanged(oldRowCount, delta); } } @@ -1592,10 +1617,10 @@ nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteRes } } - if (result == nsIAutoCompleteResult::RESULT_SUCCESS || - result == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) { + if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS || + searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) { // Try to autocomplete the default index for this search. - CompleteDefaultIndex(resultIndex); + CompleteDefaultIndex(aSearchIndex); } return NS_OK; @@ -1633,7 +1658,6 @@ nsAutoCompleteController::ClearResults() int32_t oldRowCount = mRowCount; mRowCount = 0; mResults.Clear(); - mMatchCounts.Clear(); if (oldRowCount != 0) { if (mTree) mTree->RowCountChanged(0, -oldRowCount); @@ -1701,14 +1725,16 @@ nsAutoCompleteController::GetDefaultCompleteResult(int32_t aResultIndex, // If a result index was not provided, find the first defaultIndex result. for (int32_t i = 0; resultIndex < 0 && i < mResults.Count(); ++i) { - nsIAutoCompleteResult *result = mResults[i]; + nsIAutoCompleteResult *result = mResults.SafeObjectAt(i); if (result && NS_SUCCEEDED(result->GetDefaultIndex(_defaultIndex)) && *_defaultIndex >= 0) { resultIndex = i; } } - NS_ENSURE_TRUE(resultIndex >= 0, NS_ERROR_FAILURE); + if (resultIndex < 0) { + return NS_ERROR_FAILURE; + } *_result = mResults.SafeObjectAt(resultIndex); NS_ENSURE_TRUE(*_result, NS_ERROR_FAILURE); diff --git a/toolkit/components/autocomplete/nsAutoCompleteController.h b/toolkit/components/autocomplete/nsAutoCompleteController.h index 7558fc0b314..7ecd9914bd7 100644 --- a/toolkit/components/autocomplete/nsAutoCompleteController.h +++ b/toolkit/components/autocomplete/nsAutoCompleteController.h @@ -123,10 +123,8 @@ protected: nsCOMPtr mInput; nsCOMArray mSearches; + // This is used as a sparse array, always use SafeObjectAt to access it. nsCOMArray mResults; - // Caches the match counts for the current ongoing results to allow - // incremental results to keep the rowcount up to date. - nsTArray mMatchCounts; // Temporarily keeps the results alive while invoking startSearch() for each // search. This is needed to allow the searches to reuse the previous result, // since otherwise the first search clears mResults. diff --git a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp index 247d516840a..683ac462a48 100644 --- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp +++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp @@ -22,12 +22,14 @@ struct AutoCompleteSimpleResultMatch const nsAString& aComment, const nsAString& aImage, const nsAString& aStyle, - const nsAString& aFinalCompleteValue) + const nsAString& aFinalCompleteValue, + const nsAString& aLabel) : mValue(aValue) , mComment(aComment) , mImage(aImage) , mStyle(aStyle) , mFinalCompleteValue(aFinalCompleteValue) + , mLabel(aLabel) { } @@ -36,6 +38,7 @@ struct AutoCompleteSimpleResultMatch nsString mImage; nsString mStyle; nsString mFinalCompleteValue; + nsString mLabel; }; nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() : @@ -45,6 +48,74 @@ nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() : { } +nsresult +nsAutoCompleteSimpleResult::AppendResult(nsIAutoCompleteResult* aResult) +{ + nsAutoString searchString; + nsresult rv = aResult->GetSearchString(searchString); + NS_ENSURE_SUCCESS(rv, rv); + mSearchString = searchString; + + uint16_t searchResult; + rv = aResult->GetSearchResult(&searchResult); + NS_ENSURE_SUCCESS(rv, rv); + mSearchResult = searchResult; + + nsAutoString errorDescription; + if (NS_SUCCEEDED(aResult->GetErrorDescription(errorDescription)) && + !errorDescription.IsEmpty()) { + mErrorDescription = errorDescription; + } + + bool typeAheadResult = false; + if (NS_SUCCEEDED(aResult->GetTypeAheadResult(&typeAheadResult)) && + typeAheadResult) { + mTypeAheadResult = typeAheadResult; + } + + int32_t defaultIndex = -1; + if (NS_SUCCEEDED(aResult->GetDefaultIndex(&defaultIndex)) && + defaultIndex >= 0) { + mDefaultIndex = defaultIndex; + } + + nsCOMPtr simpleResult = + do_QueryInterface(aResult); + if (simpleResult) { + nsCOMPtr listener; + if (NS_SUCCEEDED(simpleResult->GetListener(getter_AddRefs(listener))) && + listener) { + listener.swap(mListener); + } + } + + // Copy matches. + uint32_t matchCount = 0; + rv = aResult->GetMatchCount(&matchCount); + NS_ENSURE_SUCCESS(rv, rv); + for (size_t i = 0; i < matchCount; ++i) { + nsAutoString value, comment, image, style, finalCompleteValue, label; + + rv = aResult->GetValueAt(i, value); + NS_ENSURE_SUCCESS(rv, rv); + rv = aResult->GetCommentAt(i, comment); + NS_ENSURE_SUCCESS(rv, rv); + rv = aResult->GetImageAt(i, image); + NS_ENSURE_SUCCESS(rv, rv); + rv = aResult->GetStyleAt(i, style); + NS_ENSURE_SUCCESS(rv, rv); + rv = aResult->GetFinalCompleteValueAt(i, finalCompleteValue); + NS_ENSURE_SUCCESS(rv, rv); + rv = aResult->GetLabelAt(i, label); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AppendMatch(value, comment, image, style, finalCompleteValue, label); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + // searchString NS_IMETHODIMP nsAutoCompleteSimpleResult::GetSearchString(nsAString &aSearchString) @@ -122,11 +193,12 @@ nsAutoCompleteSimpleResult::InsertMatchAt(int32_t aIndex, const nsAString& aComment, const nsAString& aImage, const nsAString& aStyle, - const nsAString& aFinalCompleteValue) + const nsAString& aFinalCompleteValue, + const nsAString& aLabel) { CHECK_MATCH_INDEX(aIndex, true); - AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue); + AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue, aLabel); if (!mMatches.InsertElementAt(aIndex, match)) { return NS_ERROR_OUT_OF_MEMORY; @@ -140,10 +212,11 @@ nsAutoCompleteSimpleResult::AppendMatch(const nsAString& aValue, const nsAString& aComment, const nsAString& aImage, const nsAString& aStyle, - const nsAString& aFinalCompleteValue) + const nsAString& aFinalCompleteValue, + const nsAString& aLabel) { return InsertMatchAt(mMatches.Length(), aValue, aComment, aImage, aStyle, - aFinalCompleteValue); + aFinalCompleteValue, aLabel); } NS_IMETHODIMP @@ -164,7 +237,12 @@ nsAutoCompleteSimpleResult::GetValueAt(int32_t aIndex, nsAString& _retval) NS_IMETHODIMP nsAutoCompleteSimpleResult::GetLabelAt(int32_t aIndex, nsAString& _retval) { - return GetValueAt(aIndex, _retval); + CHECK_MATCH_INDEX(aIndex, false); + _retval = mMatches[aIndex].mLabel; + if (_retval.IsEmpty()) { + _retval = mMatches[aIndex].mValue; + } + return NS_OK; } NS_IMETHODIMP @@ -197,8 +275,9 @@ nsAutoCompleteSimpleResult::GetFinalCompleteValueAt(int32_t aIndex, { CHECK_MATCH_INDEX(aIndex, false); _retval = mMatches[aIndex].mFinalCompleteValue; - if (_retval.Length() == 0) + if (_retval.IsEmpty()) { _retval = mMatches[aIndex].mValue; + } return NS_OK; } @@ -209,6 +288,14 @@ nsAutoCompleteSimpleResult::SetListener(nsIAutoCompleteSimpleResultListener* aLi return NS_OK; } +NS_IMETHODIMP +nsAutoCompleteSimpleResult::GetListener(nsIAutoCompleteSimpleResultListener** aListener) +{ + nsCOMPtr listener(mListener); + listener.forget(aListener); + return NS_OK; +} + NS_IMETHODIMP nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex, bool aRemoveFromDb) @@ -218,8 +305,9 @@ nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex, nsString value = mMatches[aRowIndex].mValue; mMatches.RemoveElementAt(aRowIndex); - if (mListener) + if (mListener) { mListener->OnValueRemoved(this, value, aRemoveFromDb); + } return NS_OK; } diff --git a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h index ed1abe24cf8..61ee542e47b 100644 --- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h +++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h @@ -24,6 +24,8 @@ public: NS_DECL_NSIAUTOCOMPLETERESULT NS_DECL_NSIAUTOCOMPLETESIMPLERESULT + nsresult AppendResult(nsIAutoCompleteResult* aResult); + private: ~nsAutoCompleteSimpleResult() {} diff --git a/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl b/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl index 15f286044fc..6a8827ab84c 100644 --- a/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl +++ b/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl @@ -14,7 +14,7 @@ interface nsIAutoCompleteSimpleResultListener; * an array. */ -[scriptable, uuid(457ce8da-9631-45c5-b3b0-293ab0928df1)] +[scriptable, uuid(23de9c96-becb-4d0d-a9bb-1d131ce361b5)] interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult { /** @@ -69,7 +69,8 @@ interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult in AString aComment, [optional] in AString aImage, [optional] in AString aStyle, - [optional] in AString aFinalCompleteValue); + [optional] in AString aFinalCompleteValue, + [optional] in AString aLabel); /** * Appends a match consisting of the given value, comment, image, style and @@ -90,7 +91,13 @@ interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult in AString aComment, [optional] in AString aImage, [optional] in AString aStyle, - [optional] in AString aFinalCompleteValue); + [optional] in AString aFinalCompleteValue, + [optional] in AString aLabel); + + /** + * Gets the listener for changes in the result. + */ + nsIAutoCompleteSimpleResultListener getListener(); /** * Sets a listener for changes in the result. diff --git a/toolkit/components/autocomplete/tests/unit/test_393191.js b/toolkit/components/autocomplete/tests/unit/test_393191.js index b73ec9c93ee..30c782e7177 100644 --- a/toolkit/components/autocomplete/tests/unit/test_393191.js +++ b/toolkit/components/autocomplete/tests/unit/test_393191.js @@ -146,6 +146,7 @@ AutoCompleteResult.prototype = { */ function AutoCompleteSearch(aName, aResult) { this.name = aName; + this._result = aResult; } AutoCompleteSearch.prototype = { constructor: AutoCompleteSearch, @@ -154,7 +155,7 @@ AutoCompleteSearch.prototype = { name: null, // AutoCompleteResult - _result:null, + _result: null, /** diff --git a/toolkit/components/autocomplete/tests/unit/test_660156.js b/toolkit/components/autocomplete/tests/unit/test_660156.js index 112616ed4c1..98acb243e07 100644 --- a/toolkit/components/autocomplete/tests/unit/test_660156.js +++ b/toolkit/components/autocomplete/tests/unit/test_660156.js @@ -1,6 +1,3 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - /** * Search object that returns results at different times. * First, the search that returns results asynchronously. @@ -10,11 +7,16 @@ function AutoCompleteAsyncSearch(aName, aResult) { this._result = aResult; } AutoCompleteAsyncSearch.prototype = Object.create(AutoCompleteSearchBase.prototype); -AutoCompleteAsyncSearch.prototype.startSearch = function(aSearchString, - aSearchParam, - aPreviousResult, +AutoCompleteAsyncSearch.prototype.startSearch = function(aSearchString, + aSearchParam, + aPreviousResult, aListener) { - setTimeout(this._returnResults.bind(this), 500, aListener); + this._result.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH_ONGOING; + aListener.onSearchResult(this, this._result); + + do_timeout(500, () => { + this._returnResults(aListener); + }); }; AutoCompleteAsyncSearch.prototype._returnResults = function(aListener) { @@ -32,9 +34,9 @@ function AutoCompleteSyncSearch(aName, aResult) { this._result = aResult; } AutoCompleteSyncSearch.prototype = Object.create(AutoCompleteAsyncSearch.prototype); -AutoCompleteSyncSearch.prototype.startSearch = function(aSearchString, - aSearchParam, - aPreviousResult, +AutoCompleteSyncSearch.prototype.startSearch = function(aSearchString, + aSearchParam, + aPreviousResult, aListener) { this._returnResults(aListener); }; @@ -49,7 +51,7 @@ function AutoCompleteResult(aValues, aDefaultIndex) { AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype); -/** +/** * Test AutoComplete with multiple AutoCompleteSearch sources, with one of them * (index != 0) returning before the rest. */ @@ -60,19 +62,19 @@ function run_test() { var inputStr = "moz"; // Async search - var asyncSearch = new AutoCompleteAsyncSearch("Async", + var asyncSearch = new AutoCompleteAsyncSearch("Async", new AutoCompleteResult(results, -1)); // Sync search var syncSearch = new AutoCompleteSyncSearch("Sync", new AutoCompleteResult(results, 0)); - + // Register searches so AutoCompleteController can find them registerAutoCompleteSearch(asyncSearch); registerAutoCompleteSearch(syncSearch); - + var controller = Cc["@mozilla.org/autocomplete/controller;1"]. - getService(Ci.nsIAutoCompleteController); - + getService(Ci.nsIAutoCompleteController); + // Make an AutoCompleteInput that uses our searches // and confirms results on search complete. // Async search MUST be FIRST to trigger the bug this tests. diff --git a/toolkit/components/satchel/nsFormAutoComplete.js b/toolkit/components/satchel/nsFormAutoComplete.js index 387eb2e7caf..f4d8b9580b1 100644 --- a/toolkit/components/satchel/nsFormAutoComplete.js +++ b/toolkit/components/satchel/nsFormAutoComplete.js @@ -285,7 +285,7 @@ FormAutoComplete.prototype = { result.entries = aEntries; } - if (aDatalistResult) { + if (aDatalistResult && aDatalistResult.matchCount > 0) { result = this.mergeResults(result, aDatalistResult); } diff --git a/toolkit/components/satchel/nsFormAutoCompleteResult.jsm b/toolkit/components/satchel/nsFormAutoCompleteResult.jsm index 17b52adbc3d..c9fd25bb0c3 100644 --- a/toolkit/components/satchel/nsFormAutoCompleteResult.jsm +++ b/toolkit/components/satchel/nsFormAutoCompleteResult.jsm @@ -110,7 +110,7 @@ FormAutoCompleteResult.prototype = { getLabelAt: function(index) { this._checkIndexBounds(index); - return this._labels[index]; + return this._labels[index] || this._values[index]; }, /** diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp index bda4b07d507..1d6e89752e3 100644 --- a/toolkit/components/satchel/nsFormFillController.cpp +++ b/toolkit/components/satchel/nsFormFillController.cpp @@ -727,11 +727,12 @@ public: : mObserver(aObserver) , mSearch(aSearch) , mResult(aResult) - {} + { + MOZ_ASSERT(mResult, "Should have a valid result"); + MOZ_ASSERT(mObserver, "You shouldn't call this runnable with a null observer!"); + } NS_IMETHOD Run() { - NS_ASSERTION(mObserver, "You shouldn't call this runnable with a null observer!"); - mObserver->OnUpdateSearchResult(mSearch, mResult); return NS_OK; } diff --git a/toolkit/components/satchel/nsInputListAutoComplete.js b/toolkit/components/satchel/nsInputListAutoComplete.js index f529b3ae015..f42427862c9 100644 --- a/toolkit/components/satchel/nsInputListAutoComplete.js +++ b/toolkit/components/satchel/nsInputListAutoComplete.js @@ -16,12 +16,12 @@ InputListAutoComplete.prototype = { autoCompleteSearch : function (aUntrimmedSearchString, aField) { let [values, labels] = this.getListSuggestions(aField); - if (values.length === 0) - return null; + let searchResult = values.length > 0 ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS + : Ci.nsIAutoCompleteResult.RESULT_NOMATCH; + let defaultIndex = values.length > 0 ? 0 : -1; return new FormAutoCompleteResult(aUntrimmedSearchString, - Ci.nsIAutoCompleteResult.RESULT_SUCCESS, - 0, "", values, labels, - [], null); + searchResult, defaultIndex, "", + values, labels, [], null); }, getListSuggestions : function (aField) { From 1334e5ad6096c8cad58671e15aea155884d3072d Mon Sep 17 00:00:00 2001 From: Kit Cambridge Date: Tue, 10 Nov 2015 11:23:13 -0800 Subject: [PATCH 61/71] Bug 1220337 - Don't show alternate notification actions on OS X 10.8. r=MattN --- widget/cocoa/OSXNotificationCenter.mm | 36 +++++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm index c52047a7438..1c79834269d 100644 --- a/widget/cocoa/OSXNotificationCenter.mm +++ b/widget/cocoa/OSXNotificationCenter.mm @@ -104,10 +104,14 @@ enum { - (void)userNotificationCenter:(id)center didActivateNotification:(id)notification { - NSNumber *alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"]; + unsigned long long additionalActionIndex = ULLONG_MAX; + if ([notification respondsToSelector:@selector(get_alternateActionIndex:)]) { + NSNumber *alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"]; + additionalActionIndex = [alternateActionIndex unsignedLongLongValue]; + } mOSXNC->OnActivate([[notification userInfo] valueForKey:@"name"], notification.activationType, - [alternateActionIndex unsignedLongLongValue]); + additionalActionIndex); } - (BOOL)userNotificationCenter:(id)center @@ -281,16 +285,26 @@ OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const bundle->GetStringFromName(MOZ_UTF16("webActions.settings.label"), getter_Copies(settingsButtonTitle)); - notification.hasActionButton = YES; notification.otherButtonTitle = nsCocoaUtils::ToNSString(closeButtonTitle); - notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle); - [(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"]; - [(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"]; - [(NSObject*)notification setValue:@[ - nsCocoaUtils::ToNSString(disableButtonTitle), - nsCocoaUtils::ToNSString(settingsButtonTitle) - ] - forKey:@"_alternateActionButtonTitles"]; + + // OS X 10.8 only shows action buttons if the "Alerts" style is set in + // Notification Center preferences, and doesn't support the alternate + // action menu. + if ([notification respondsToSelector:@selector(set_showsButtons:)] && + [notification respondsToSelector:@selector(set_alwaysShowAlternateActionMenu:)] && + [notification respondsToSelector:@selector(set_alternateActionButtonTitles:)]) { + + notification.hasActionButton = YES; + notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle); + + [(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"]; + [(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"]; + [(NSObject*)notification setValue:@[ + nsCocoaUtils::ToNSString(disableButtonTitle), + nsCocoaUtils::ToNSString(settingsButtonTitle) + ] + forKey:@"_alternateActionButtonTitles"]; + } } } NSString *alertName = nsCocoaUtils::ToNSString(aAlertName); From 57b222f23d43d511d4a7bbf7349a6c3aa4cf7131 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Tue, 10 Nov 2015 11:48:51 -0800 Subject: [PATCH 62/71] Bug 1181852 - Use shared-head.js for devtools/client/shared;r=jryans --- .../client/shared/test/browser_css_color.js | 2 +- .../shared/test/browser_cubic-bezier-01.js | 2 +- .../shared/test/browser_cubic-bezier-02.js | 2 +- .../shared/test/browser_cubic-bezier-03.js | 2 +- .../shared/test/browser_cubic-bezier-04.js | 2 +- .../shared/test/browser_cubic-bezier-05.js | 2 +- .../shared/test/browser_cubic-bezier-06.js | 2 +- .../shared/test/browser_filter-editor-01.js | 2 +- .../shared/test/browser_filter-editor-02.js | 2 +- .../shared/test/browser_filter-editor-03.js | 2 +- .../shared/test/browser_filter-editor-04.js | 2 +- .../shared/test/browser_filter-editor-05.js | 2 +- .../shared/test/browser_filter-editor-06.js | 3 +- .../shared/test/browser_filter-editor-07.js | 3 +- .../shared/test/browser_filter-editor-08.js | 2 +- .../shared/test/browser_filter-editor-09.js | 2 +- .../shared/test/browser_filter-editor-10.js | 2 +- .../shared/test/browser_filter-presets-01.js | 2 +- .../shared/test/browser_filter-presets-02.js | 2 +- .../shared/test/browser_filter-presets-03.js | 2 +- .../shared/test/browser_flame-graph-01.js | 2 +- .../shared/test/browser_flame-graph-02.js | 2 +- .../shared/test/browser_flame-graph-03a.js | 2 +- .../shared/test/browser_flame-graph-03b.js | 2 +- .../shared/test/browser_flame-graph-03c.js | 2 +- .../shared/test/browser_flame-graph-04.js | 2 +- .../test/browser_flame-graph-utils-01.js | 2 +- .../test/browser_flame-graph-utils-02.js | 2 +- .../test/browser_flame-graph-utils-03.js | 2 +- .../test/browser_flame-graph-utils-04.js | 2 +- .../test/browser_flame-graph-utils-05.js | 2 +- .../test/browser_flame-graph-utils-06.js | 2 +- .../client/shared/test/browser_graphs-01.js | 2 +- .../client/shared/test/browser_graphs-02.js | 2 +- .../client/shared/test/browser_graphs-03.js | 2 +- .../client/shared/test/browser_graphs-04.js | 2 +- .../client/shared/test/browser_graphs-05.js | 2 +- .../client/shared/test/browser_graphs-06.js | 2 +- .../client/shared/test/browser_graphs-07a.js | 2 +- .../client/shared/test/browser_graphs-07b.js | 2 +- .../client/shared/test/browser_graphs-07c.js | 2 +- .../client/shared/test/browser_graphs-07d.js | 2 +- .../client/shared/test/browser_graphs-07e.js | 2 +- .../client/shared/test/browser_graphs-08.js | 2 +- .../client/shared/test/browser_graphs-09a.js | 2 +- .../client/shared/test/browser_graphs-09b.js | 2 +- .../client/shared/test/browser_graphs-09c.js | 2 +- .../client/shared/test/browser_graphs-09d.js | 2 +- .../client/shared/test/browser_graphs-09e.js | 2 +- .../client/shared/test/browser_graphs-09f.js | 2 +- .../client/shared/test/browser_graphs-10a.js | 2 +- .../client/shared/test/browser_graphs-10b.js | 2 +- .../client/shared/test/browser_graphs-10c.js | 2 +- .../client/shared/test/browser_graphs-11a.js | 2 +- .../client/shared/test/browser_graphs-11b.js | 2 +- .../client/shared/test/browser_graphs-12.js | 2 +- .../client/shared/test/browser_graphs-13.js | 2 +- .../client/shared/test/browser_graphs-14.js | 2 +- .../client/shared/test/browser_graphs-15.js | 2 +- .../client/shared/test/browser_graphs-16.js | 2 +- .../shared/test/browser_inplace-editor-01.js | 3 +- .../shared/test/browser_inplace-editor-02.js | 3 +- .../test/browser_layoutHelpers-getBoxQuads.js | 35 ++++---- .../client/shared/test/browser_mdn-docs-01.js | 3 +- .../client/shared/test/browser_mdn-docs-02.js | 2 +- .../shared/test/browser_options-view-01.js | 3 +- .../shared/test/browser_outputparser.js | 3 +- .../client/shared/test/browser_spectrum.js | 2 +- .../browser_telemetry_button_eyedropper.js | 2 +- .../browser_telemetry_button_paintflashing.js | 2 +- .../browser_telemetry_button_responsive.js | 2 +- .../browser_telemetry_button_scratchpad.js | 2 +- .../test/browser_telemetry_button_tilt.js | 2 +- .../shared/test/browser_telemetry_sidebar.js | 2 +- .../shared/test/browser_telemetry_toolbox.js | 2 +- ...er_telemetry_toolboxtabs_canvasdebugger.js | 2 +- ...browser_telemetry_toolboxtabs_inspector.js | 2 +- ...rowser_telemetry_toolboxtabs_jsdebugger.js | 2 +- ...rowser_telemetry_toolboxtabs_jsprofiler.js | 2 +- ...rowser_telemetry_toolboxtabs_netmonitor.js | 2 +- .../browser_telemetry_toolboxtabs_options.js | 2 +- ...wser_telemetry_toolboxtabs_shadereditor.js | 2 +- .../browser_telemetry_toolboxtabs_storage.js | 2 +- ...owser_telemetry_toolboxtabs_styleeditor.js | 2 +- ...er_telemetry_toolboxtabs_webaudioeditor.js | 2 +- ...rowser_telemetry_toolboxtabs_webconsole.js | 2 +- .../shared/test/browser_templater_basic.js | 2 +- devtools/client/shared/test/browser_theme.js | 4 +- .../shared/test/browser_toolbar_basic.js | 82 ++++++++----------- .../shared/test/browser_toolbar_tooltip.js | 2 +- ...browser_toolbar_webconsole_errors_count.js | 8 +- .../shared/test/browser_treeWidget_basic.js | 2 +- ...browser_treeWidget_keyboard_interaction.js | 2 +- .../browser_treeWidget_mouse_interaction.js | 2 +- devtools/client/shared/test/head.js | 65 ++++----------- 95 files changed, 160 insertions(+), 221 deletions(-) diff --git a/devtools/client/shared/test/browser_css_color.js b/devtools/client/shared/test/browser_css_color.js index 4974190e9d2..6bfd6e2f793 100644 --- a/devtools/client/shared/test/browser_css_color.js +++ b/devtools/client/shared/test/browser_css_color.js @@ -6,7 +6,7 @@ var {colorUtils} = require("devtools/shared/css-color"); var origColorUnit; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); info("Creating a test canvas element to test colors"); diff --git a/devtools/client/shared/test/browser_cubic-bezier-01.js b/devtools/client/shared/test/browser_cubic-bezier-01.js index dd1dc0bd642..6fc64757f5f 100644 --- a/devtools/client/shared/test/browser_cubic-bezier-01.js +++ b/devtools/client/shared/test/browser_cubic-bezier-01.js @@ -11,7 +11,7 @@ const {CubicBezierWidget} = require("devtools/client/shared/widgets/CubicBezierWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); info("Checking that the graph markup is created in the parent"); diff --git a/devtools/client/shared/test/browser_cubic-bezier-02.js b/devtools/client/shared/test/browser_cubic-bezier-02.js index 4966b8ff411..099eb4ac0f2 100644 --- a/devtools/client/shared/test/browser_cubic-bezier-02.js +++ b/devtools/client/shared/test/browser_cubic-bezier-02.js @@ -12,7 +12,7 @@ const {CubicBezierWidget} = const {PREDEFINED} = require("devtools/client/shared/widgets/CubicBezierPresets"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); // Required or widget will be clipped inside of 'bottom' diff --git a/devtools/client/shared/test/browser_cubic-bezier-03.js b/devtools/client/shared/test/browser_cubic-bezier-03.js index 4bf891338bc..d787749a644 100644 --- a/devtools/client/shared/test/browser_cubic-bezier-03.js +++ b/devtools/client/shared/test/browser_cubic-bezier-03.js @@ -12,7 +12,7 @@ const {CubicBezierWidget} = const {PREDEFINED} = require("devtools/client/shared/widgets/CubicBezierPresets"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); let container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_cubic-bezier-04.js b/devtools/client/shared/test/browser_cubic-bezier-04.js index bc96dbbd47f..fe9a4311ca0 100644 --- a/devtools/client/shared/test/browser_cubic-bezier-04.js +++ b/devtools/client/shared/test/browser_cubic-bezier-04.js @@ -12,7 +12,7 @@ const {CubicBezierPresetWidget} = const {PRESETS} = require("devtools/client/shared/widgets/CubicBezierPresets"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); let container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_cubic-bezier-05.js b/devtools/client/shared/test/browser_cubic-bezier-05.js index e0bc3233041..4817598c081 100644 --- a/devtools/client/shared/test/browser_cubic-bezier-05.js +++ b/devtools/client/shared/test/browser_cubic-bezier-05.js @@ -13,7 +13,7 @@ const {PREDEFINED, PRESETS, DEFAULT_PRESET_CATEGORY} = require("devtools/client/shared/widgets/CubicBezierPresets"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); let container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_cubic-bezier-06.js b/devtools/client/shared/test/browser_cubic-bezier-06.js index 1b93238375b..a4857097cba 100644 --- a/devtools/client/shared/test/browser_cubic-bezier-06.js +++ b/devtools/client/shared/test/browser_cubic-bezier-06.js @@ -13,7 +13,7 @@ const {CubicBezierWidget} = const {PRESETS} = require("devtools/client/shared/widgets/CubicBezierPresets"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); let container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-01.js b/devtools/client/shared/test/browser_filter-editor-01.js index 8737f7b6c6f..6bed8ba8ef5 100644 --- a/devtools/client/shared/test/browser_filter-editor-01.js +++ b/devtools/client/shared/test/browser_filter-editor-01.js @@ -9,7 +9,7 @@ const TEST_URI = "chrome://devtools/content/shared/widgets/filter-frame.xhtml"; const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget"); add_task(function *() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-02.js b/devtools/client/shared/test/browser_filter-editor-02.js index 857e7fe1f59..2422cde64a7 100644 --- a/devtools/client/shared/test/browser_filter-editor-02.js +++ b/devtools/client/shared/test/browser_filter-editor-02.js @@ -13,7 +13,7 @@ const STRINGS_URI = "chrome://devtools/locale/filterwidget.properties"; const L10N = new ViewHelpers.L10N(STRINGS_URI); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const TEST_DATA = [ diff --git a/devtools/client/shared/test/browser_filter-editor-03.js b/devtools/client/shared/test/browser_filter-editor-03.js index 225e2faf2e3..7a51c4a905b 100644 --- a/devtools/client/shared/test/browser_filter-editor-03.js +++ b/devtools/client/shared/test/browser_filter-editor-03.js @@ -12,7 +12,7 @@ const GRAYSCALE_MAX = 100; const INVERT_MIN = 0; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-04.js b/devtools/client/shared/test/browser_filter-editor-04.js index 8231f9db3f2..68b8ed54616 100644 --- a/devtools/client/shared/test/browser_filter-editor-04.js +++ b/devtools/client/shared/test/browser_filter-editor-04.js @@ -10,7 +10,7 @@ const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWi const LIST_ITEM_HEIGHT = 32; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-05.js b/devtools/client/shared/test/browser_filter-editor-05.js index a3a048b71e3..b12132b5e4e 100644 --- a/devtools/client/shared/test/browser_filter-editor-05.js +++ b/devtools/client/shared/test/browser_filter-editor-05.js @@ -16,7 +16,7 @@ const GRAYSCALE_MAX = 100, GRAYSCALE_MIN = 0; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-06.js b/devtools/client/shared/test/browser_filter-editor-06.js index 07972ac39f7..200f87b64e6 100644 --- a/devtools/client/shared/test/browser_filter-editor-06.js +++ b/devtools/client/shared/test/browser_filter-editor-06.js @@ -7,7 +7,6 @@ const TEST_URI = "chrome://devtools/content/shared/widgets/filter-frame.xhtml"; -const { Cu } = require("chrome"); const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget"); const { ViewHelpers } = Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm", {}); @@ -15,7 +14,7 @@ const STRINGS_URI = "chrome://devtools/locale/filterwidget.properties"; const L10N = new ViewHelpers.L10N(STRINGS_URI); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-07.js b/devtools/client/shared/test/browser_filter-editor-07.js index 7bd53b0b711..f8bdd12e1bc 100644 --- a/devtools/client/shared/test/browser_filter-editor-07.js +++ b/devtools/client/shared/test/browser_filter-editor-07.js @@ -7,7 +7,6 @@ const TEST_URI = "chrome://devtools/content/shared/widgets/filter-frame.xhtml"; -const { Cu } = require("chrome"); const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget"); const { ViewHelpers } = Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm", {}); @@ -15,7 +14,7 @@ const STRINGS_URI = "chrome://devtools/locale/filterwidget.properties"; const L10N = new ViewHelpers.L10N(STRINGS_URI); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-08.js b/devtools/client/shared/test/browser_filter-editor-08.js index 5bf3ab2c383..bd9514daf63 100644 --- a/devtools/client/shared/test/browser_filter-editor-08.js +++ b/devtools/client/shared/test/browser_filter-editor-08.js @@ -14,7 +14,7 @@ const SLOW_VALUE_MULTIPLIER = 0.1; const DEFAULT_VALUE_MULTIPLIER = 1; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-09.js b/devtools/client/shared/test/browser_filter-editor-09.js index 34e35795a86..504d8ccee5f 100644 --- a/devtools/client/shared/test/browser_filter-editor-09.js +++ b/devtools/client/shared/test/browser_filter-editor-09.js @@ -14,7 +14,7 @@ const SLOW_VALUE_MULTIPLIER = 0.1; const DEFAULT_VALUE_MULTIPLIER = 1; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-editor-10.js b/devtools/client/shared/test/browser_filter-editor-10.js index 45900bf30a0..b7242be1e22 100644 --- a/devtools/client/shared/test/browser_filter-editor-10.js +++ b/devtools/client/shared/test/browser_filter-editor-10.js @@ -14,7 +14,7 @@ const SLOW_VALUE_MULTIPLIER = 0.1; const DEFAULT_VALUE_MULTIPLIER = 1; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); const container = doc.querySelector("#container"); diff --git a/devtools/client/shared/test/browser_filter-presets-01.js b/devtools/client/shared/test/browser_filter-presets-01.js index 674fad16429..abb9f77e104 100644 --- a/devtools/client/shared/test/browser_filter-presets-01.js +++ b/devtools/client/shared/test/browser_filter-presets-01.js @@ -9,7 +9,7 @@ const TEST_URI = "chrome://devtools/content/shared/widgets/filter-frame.xhtml"; const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget"); add_task(function* () { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); diff --git a/devtools/client/shared/test/browser_filter-presets-02.js b/devtools/client/shared/test/browser_filter-presets-02.js index 9f8a964ed9a..08db71f20e2 100644 --- a/devtools/client/shared/test/browser_filter-presets-02.js +++ b/devtools/client/shared/test/browser_filter-presets-02.js @@ -9,7 +9,7 @@ const TEST_URI = "chrome://devtools/content/shared/widgets/filter-frame.xhtml"; const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget"); add_task(function* () { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); diff --git a/devtools/client/shared/test/browser_filter-presets-03.js b/devtools/client/shared/test/browser_filter-presets-03.js index bb1aa94fe1a..1f5be3befc9 100644 --- a/devtools/client/shared/test/browser_filter-presets-03.js +++ b/devtools/client/shared/test/browser_filter-presets-03.js @@ -9,7 +9,7 @@ const TEST_URI = "chrome://devtools/content/shared/widgets/filter-frame.xhtml"; const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget"); add_task(function* () { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); diff --git a/devtools/client/shared/test/browser_flame-graph-01.js b/devtools/client/shared/test/browser_flame-graph-01.js index f9cccbdda29..cf6e372c077 100644 --- a/devtools/client/shared/test/browser_flame-graph-01.js +++ b/devtools/client/shared/test/browser_flame-graph-01.js @@ -6,7 +6,7 @@ var {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-02.js b/devtools/client/shared/test/browser_flame-graph-02.js index f88f11a3eab..f3f1fe91a21 100644 --- a/devtools/client/shared/test/browser_flame-graph-02.js +++ b/devtools/client/shared/test/browser_flame-graph-02.js @@ -6,7 +6,7 @@ var {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-03a.js b/devtools/client/shared/test/browser_flame-graph-03a.js index 2984c578633..3b75b08996a 100644 --- a/devtools/client/shared/test/browser_flame-graph-03a.js +++ b/devtools/client/shared/test/browser_flame-graph-03a.js @@ -11,7 +11,7 @@ var TEST_HEIGHT = 100; var {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-03b.js b/devtools/client/shared/test/browser_flame-graph-03b.js index 6a8c99428cb..551a7ec466e 100644 --- a/devtools/client/shared/test/browser_flame-graph-03b.js +++ b/devtools/client/shared/test/browser_flame-graph-03b.js @@ -12,7 +12,7 @@ var TEST_DPI_DENSITIY = 2; var {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-03c.js b/devtools/client/shared/test/browser_flame-graph-03c.js index 03997d0eea9..578040c6005 100644 --- a/devtools/client/shared/test/browser_flame-graph-03c.js +++ b/devtools/client/shared/test/browser_flame-graph-03c.js @@ -12,7 +12,7 @@ var TEST_DPI_DENSITIY = 2; var {FlameGraph} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-04.js b/devtools/client/shared/test/browser_flame-graph-04.js index 267f4fe4aeb..41438421d2e 100644 --- a/devtools/client/shared/test/browser_flame-graph-04.js +++ b/devtools/client/shared/test/browser_flame-graph-04.js @@ -12,7 +12,7 @@ var {FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY} = require("devtools/client/shared/widge var L10N = new ViewHelpers.L10N(); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-utils-01.js b/devtools/client/shared/test/browser_flame-graph-utils-01.js index 51a8a683d60..3413a307def 100644 --- a/devtools/client/shared/test/browser_flame-graph-utils-01.js +++ b/devtools/client/shared/test/browser_flame-graph-utils-01.js @@ -8,7 +8,7 @@ var {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph"); var {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-utils-02.js b/devtools/client/shared/test/browser_flame-graph-utils-02.js index 2b776072132..a52fa437f96 100644 --- a/devtools/client/shared/test/browser_flame-graph-utils-02.js +++ b/devtools/client/shared/test/browser_flame-graph-utils-02.js @@ -7,7 +7,7 @@ var {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph"); var {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-utils-03.js b/devtools/client/shared/test/browser_flame-graph-utils-03.js index 7bf8f566810..5ad7f140487 100644 --- a/devtools/client/shared/test/browser_flame-graph-utils-03.js +++ b/devtools/client/shared/test/browser_flame-graph-utils-03.js @@ -8,7 +8,7 @@ var {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph"); var {FrameNode} = require("devtools/client/performance/modules/logic/tree-model"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-utils-04.js b/devtools/client/shared/test/browser_flame-graph-utils-04.js index 65de9eb0443..5569e175d94 100644 --- a/devtools/client/shared/test/browser_flame-graph-utils-04.js +++ b/devtools/client/shared/test/browser_flame-graph-utils-04.js @@ -8,7 +8,7 @@ var {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph"); var {FrameNode} = require("devtools/client/performance/modules/logic/tree-model"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-utils-05.js b/devtools/client/shared/test/browser_flame-graph-utils-05.js index efa0d93fce6..d7981b2f820 100644 --- a/devtools/client/shared/test/browser_flame-graph-utils-05.js +++ b/devtools/client/shared/test/browser_flame-graph-utils-05.js @@ -6,7 +6,7 @@ var {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_flame-graph-utils-06.js b/devtools/client/shared/test/browser_flame-graph-utils-06.js index 21bc38cc38f..b7b83fafb11 100644 --- a/devtools/client/shared/test/browser_flame-graph-utils-06.js +++ b/devtools/client/shared/test/browser_flame-graph-utils-06.js @@ -8,7 +8,7 @@ var {FlameGraphUtils} = require("devtools/client/shared/widgets/FlameGraph"); var {PALLETTE_SIZE} = require("devtools/client/shared/widgets/FlameGraph"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-01.js b/devtools/client/shared/test/browser_graphs-01.js index 8e9c07633f1..7963517f5b2 100644 --- a/devtools/client/shared/test/browser_graphs-01.js +++ b/devtools/client/shared/test/browser_graphs-01.js @@ -6,7 +6,7 @@ var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); finish(); diff --git a/devtools/client/shared/test/browser_graphs-02.js b/devtools/client/shared/test/browser_graphs-02.js index aeaafd9eee4..694a5239a71 100644 --- a/devtools/client/shared/test/browser_graphs-02.js +++ b/devtools/client/shared/test/browser_graphs-02.js @@ -8,7 +8,7 @@ const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }]; var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-03.js b/devtools/client/shared/test/browser_graphs-03.js index 3dae205d60d..d358451c2c2 100644 --- a/devtools/client/shared/test/browser_graphs-03.js +++ b/devtools/client/shared/test/browser_graphs-03.js @@ -7,7 +7,7 @@ var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-04.js b/devtools/client/shared/test/browser_graphs-04.js index fc0c96862c0..5e45fa5e368 100644 --- a/devtools/client/shared/test/browser_graphs-04.js +++ b/devtools/client/shared/test/browser_graphs-04.js @@ -6,7 +6,7 @@ var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-05.js b/devtools/client/shared/test/browser_graphs-05.js index 2a48121c811..579805cd550 100644 --- a/devtools/client/shared/test/browser_graphs-05.js +++ b/devtools/client/shared/test/browser_graphs-05.js @@ -8,7 +8,7 @@ const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }]; var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-06.js b/devtools/client/shared/test/browser_graphs-06.js index 329cb254ea4..bc571bf6593 100644 --- a/devtools/client/shared/test/browser_graphs-06.js +++ b/devtools/client/shared/test/browser_graphs-06.js @@ -8,7 +8,7 @@ const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }]; var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-07a.js b/devtools/client/shared/test/browser_graphs-07a.js index 00ac5ff379b..2b5ded25fd0 100644 --- a/devtools/client/shared/test/browser_graphs-07a.js +++ b/devtools/client/shared/test/browser_graphs-07a.js @@ -7,7 +7,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-07b.js b/devtools/client/shared/test/browser_graphs-07b.js index 7cad9ee071e..810922b89fb 100644 --- a/devtools/client/shared/test/browser_graphs-07b.js +++ b/devtools/client/shared/test/browser_graphs-07b.js @@ -7,7 +7,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-07c.js b/devtools/client/shared/test/browser_graphs-07c.js index 55a1f69ce03..22e5117baf6 100644 --- a/devtools/client/shared/test/browser_graphs-07c.js +++ b/devtools/client/shared/test/browser_graphs-07c.js @@ -9,7 +9,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-07d.js b/devtools/client/shared/test/browser_graphs-07d.js index 7b863b1f8d7..95d42dc2dba 100644 --- a/devtools/client/shared/test/browser_graphs-07d.js +++ b/devtools/client/shared/test/browser_graphs-07d.js @@ -8,7 +8,7 @@ const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }]; var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-07e.js b/devtools/client/shared/test/browser_graphs-07e.js index 2c78337cf7d..a5d9134fbd3 100644 --- a/devtools/client/shared/test/browser_graphs-07e.js +++ b/devtools/client/shared/test/browser_graphs-07e.js @@ -8,7 +8,7 @@ var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); var CURRENT_ZOOM = 1; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-08.js b/devtools/client/shared/test/browser_graphs-08.js index c36878f1941..a8208de6011 100644 --- a/devtools/client/shared/test/browser_graphs-08.js +++ b/devtools/client/shared/test/browser_graphs-08.js @@ -7,7 +7,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-09a.js b/devtools/client/shared/test/browser_graphs-09a.js index ed909979bd9..e507e9eab25 100644 --- a/devtools/client/shared/test/browser_graphs-09a.js +++ b/devtools/client/shared/test/browser_graphs-09a.js @@ -7,7 +7,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-09b.js b/devtools/client/shared/test/browser_graphs-09b.js index 1774f06323b..9ccd5468417 100644 --- a/devtools/client/shared/test/browser_graphs-09b.js +++ b/devtools/client/shared/test/browser_graphs-09b.js @@ -7,7 +7,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-09c.js b/devtools/client/shared/test/browser_graphs-09c.js index b9da4379db8..85d4f8985ec 100644 --- a/devtools/client/shared/test/browser_graphs-09c.js +++ b/devtools/client/shared/test/browser_graphs-09c.js @@ -7,7 +7,7 @@ const TEST_DATA = []; var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-09d.js b/devtools/client/shared/test/browser_graphs-09d.js index d78f3fe141f..faa0b24f1f2 100644 --- a/devtools/client/shared/test/browser_graphs-09d.js +++ b/devtools/client/shared/test/browser_graphs-09d.js @@ -8,7 +8,7 @@ const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 59.9 }]; var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-09e.js b/devtools/client/shared/test/browser_graphs-09e.js index bbf620b7989..95a2c4388ed 100644 --- a/devtools/client/shared/test/browser_graphs-09e.js +++ b/devtools/client/shared/test/browser_graphs-09e.js @@ -10,7 +10,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-09f.js b/devtools/client/shared/test/browser_graphs-09f.js index 5b2d2b49016..393bd1fe80c 100644 --- a/devtools/client/shared/test/browser_graphs-09f.js +++ b/devtools/client/shared/test/browser_graphs-09f.js @@ -8,7 +8,7 @@ const TEST_DATA = [{ delta: 100, value: 60 }, { delta: 200, value: 1 }]; var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-10a.js b/devtools/client/shared/test/browser_graphs-10a.js index 31e5af925d9..d84d4e2fa3d 100644 --- a/devtools/client/shared/test/browser_graphs-10a.js +++ b/devtools/client/shared/test/browser_graphs-10a.js @@ -7,7 +7,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-10b.js b/devtools/client/shared/test/browser_graphs-10b.js index e055913112d..43e501f7a9e 100644 --- a/devtools/client/shared/test/browser_graphs-10b.js +++ b/devtools/client/shared/test/browser_graphs-10b.js @@ -8,7 +8,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-10c.js b/devtools/client/shared/test/browser_graphs-10c.js index 4e09d75a55a..2617e70936a 100644 --- a/devtools/client/shared/test/browser_graphs-10c.js +++ b/devtools/client/shared/test/browser_graphs-10c.js @@ -5,7 +5,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-11a.js b/devtools/client/shared/test/browser_graphs-11a.js index 48318b33b79..acc0594679b 100644 --- a/devtools/client/shared/test/browser_graphs-11a.js +++ b/devtools/client/shared/test/browser_graphs-11a.js @@ -12,7 +12,7 @@ const CATEGORIES = [ ]; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-11b.js b/devtools/client/shared/test/browser_graphs-11b.js index 56441709f95..2d09a3c0006 100644 --- a/devtools/client/shared/test/browser_graphs-11b.js +++ b/devtools/client/shared/test/browser_graphs-11b.js @@ -12,7 +12,7 @@ const CATEGORIES = [ ]; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-12.js b/devtools/client/shared/test/browser_graphs-12.js index 4bf3dba6840..65e5f338fc9 100644 --- a/devtools/client/shared/test/browser_graphs-12.js +++ b/devtools/client/shared/test/browser_graphs-12.js @@ -8,7 +8,7 @@ var BarGraphWidget = require("devtools/client/shared/widgets/BarGraphWidget"); var {CanvasGraphUtils} = require("devtools/client/shared/widgets/Graphs"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-13.js b/devtools/client/shared/test/browser_graphs-13.js index 7cee71d8301..d1ac0f4e58a 100644 --- a/devtools/client/shared/test/browser_graphs-13.js +++ b/devtools/client/shared/test/browser_graphs-13.js @@ -6,7 +6,7 @@ var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-14.js b/devtools/client/shared/test/browser_graphs-14.js index 072e819b83f..f8341c198a4 100644 --- a/devtools/client/shared/test/browser_graphs-14.js +++ b/devtools/client/shared/test/browser_graphs-14.js @@ -7,7 +7,7 @@ const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-15.js b/devtools/client/shared/test/browser_graphs-15.js index 43a5a179535..e752cbf7491 100644 --- a/devtools/client/shared/test/browser_graphs-15.js +++ b/devtools/client/shared/test/browser_graphs-15.js @@ -23,7 +23,7 @@ for (let frameRate of FRAMES) { var LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_graphs-16.js b/devtools/client/shared/test/browser_graphs-16.js index 01b872a45e1..3c06ed27a78 100644 --- a/devtools/client/shared/test/browser_graphs-16.js +++ b/devtools/client/shared/test/browser_graphs-16.js @@ -20,7 +20,7 @@ const SECTIONS = [ ]; add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_inplace-editor-01.js b/devtools/client/shared/test/browser_inplace-editor-01.js index f71cc6e8d50..5c0865c180c 100644 --- a/devtools/client/shared/test/browser_inplace-editor-01.js +++ b/devtools/client/shared/test/browser_inplace-editor-01.js @@ -4,13 +4,12 @@ "use strict"; -var promise = require("promise"); var {editableField, getInplaceEditorForSpan: inplaceEditor} = require("devtools/client/shared/inplace-editor"); // Test the inplace-editor behavior. add_task(function*() { - yield promiseTab("data:text/html;charset=utf-8,inline editor tests"); + yield addTab("data:text/html;charset=utf-8,inline editor tests"); let [host, win, doc] = yield createHost(); yield testMultipleInitialization(doc); diff --git a/devtools/client/shared/test/browser_inplace-editor-02.js b/devtools/client/shared/test/browser_inplace-editor-02.js index 774f245fe08..7707906496f 100644 --- a/devtools/client/shared/test/browser_inplace-editor-02.js +++ b/devtools/client/shared/test/browser_inplace-editor-02.js @@ -5,12 +5,11 @@ "use strict"; var {editableField, getInplaceEditorForSpan: inplaceEditor} = require("devtools/client/shared/inplace-editor"); -var promise = require("promise"); // Test that the trimOutput option for the inplace editor works correctly. add_task(function*() { - yield promiseTab("data:text/html;charset=utf-8,inline editor tests"); + yield addTab("data:text/html;charset=utf-8,inline editor tests"); let [host, win, doc] = yield createHost(); yield testNonTrimmed(doc); diff --git a/devtools/client/shared/test/browser_layoutHelpers-getBoxQuads.js b/devtools/client/shared/test/browser_layoutHelpers-getBoxQuads.js index b42dc07bb42..b6598bdf41f 100644 --- a/devtools/client/shared/test/browser_layoutHelpers-getBoxQuads.js +++ b/devtools/client/shared/test/browser_layoutHelpers-getBoxQuads.js @@ -9,28 +9,27 @@ var {getAdjustedQuads} = require("devtools/shared/layout/utils"); const TEST_URI = TEST_URI_ROOT + "browser_layoutHelpers-getBoxQuads.html"; -function test() { - addTab(TEST_URI, function(browser, tab) { - let doc = browser.contentDocument; +add_task(function* () { - ok(typeof getAdjustedQuads === "function", "getAdjustedQuads is defined"); + let tab = yield addTab(TEST_URI); + let doc = tab.linkedBrowser.contentDocument; - info("Running tests"); + ok(typeof getAdjustedQuads === "function", "getAdjustedQuads is defined"); - returnsTheRightDataStructure(doc); - isEmptyForMissingNode(doc); - isEmptyForHiddenNodes(doc); - defaultsToBorderBoxIfNoneProvided(doc); - returnsLikeGetBoxQuadsInSimpleCase(doc); - takesIframesOffsetsIntoAccount(doc); - takesScrollingIntoAccount(doc); - takesZoomIntoAccount(doc); - returnsMultipleItemsForWrappingInlineElements(doc); + info("Running tests"); - gBrowser.removeCurrentTab(); - finish(); - }); -} + returnsTheRightDataStructure(doc); + isEmptyForMissingNode(doc); + isEmptyForHiddenNodes(doc); + defaultsToBorderBoxIfNoneProvided(doc); + returnsLikeGetBoxQuadsInSimpleCase(doc); + takesIframesOffsetsIntoAccount(doc); + takesScrollingIntoAccount(doc); + takesZoomIntoAccount(doc); + returnsMultipleItemsForWrappingInlineElements(doc); + + gBrowser.removeCurrentTab(); +}); function returnsTheRightDataStructure(doc) { info("Checks that the returned data contains bounds and 4 points"); diff --git a/devtools/client/shared/test/browser_mdn-docs-01.js b/devtools/client/shared/test/browser_mdn-docs-01.js index 7e426ec5545..0915736349b 100644 --- a/devtools/client/shared/test/browser_mdn-docs-01.js +++ b/devtools/client/shared/test/browser_mdn-docs-01.js @@ -22,7 +22,6 @@ const {CssDocsTooltip} = require("devtools/client/shared/widgets/Tooltip"); const {setBaseCssDocsUrl, MdnDocsWidget} = require("devtools/client/shared/widgets/MdnDocsWidget"); -const promise = require("promise"); // frame to load the tooltip into const MDN_DOCS_TOOLTIP_FRAME = "chrome://devtools/content/shared/widgets/mdn-docs-frame.xhtml"; @@ -51,7 +50,7 @@ const URI_PARAMS = "?utm_source=mozilla&utm_medium=firefox-inspector&utm_campaig add_task(function*() { setBaseCssDocsUrl(TEST_URI_ROOT); - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", MDN_DOCS_TOOLTIP_FRAME); let widget = new MdnDocsWidget(win.document); diff --git a/devtools/client/shared/test/browser_mdn-docs-02.js b/devtools/client/shared/test/browser_mdn-docs-02.js index 5500673051f..7d15e73f1df 100644 --- a/devtools/client/shared/test/browser_mdn-docs-02.js +++ b/devtools/client/shared/test/browser_mdn-docs-02.js @@ -99,7 +99,7 @@ const TEST_DATA = [{ add_task(function*() { setBaseCssDocsUrl(TEST_URI_ROOT); - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", MDN_DOCS_TOOLTIP_FRAME); let widget = new MdnDocsWidget(win.document); diff --git a/devtools/client/shared/test/browser_options-view-01.js b/devtools/client/shared/test/browser_options-view-01.js index a87ed8afe20..919e463b1a1 100644 --- a/devtools/client/shared/test/browser_options-view-01.js +++ b/devtools/client/shared/test/browser_options-view-01.js @@ -4,7 +4,6 @@ // Tests that options-view OptionsView responds to events correctly. const {OptionsView} = require("devtools/client/shared/options-view"); -const {Services} = require("resource://gre/modules/Services.jsm"); const BRANCH = "devtools.debugger."; const BLACK_BOX_PREF = "auto-black-box"; @@ -19,7 +18,7 @@ add_task(function*() { Services.prefs.setBoolPref(BRANCH + PRETTY_PRINT_PREF, true); info("Opening a test tab and a toolbox host to create the options view in"); - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", OPTIONS_VIEW_URL); yield testOptionsView(win); diff --git a/devtools/client/shared/test/browser_outputparser.js b/devtools/client/shared/test/browser_outputparser.js index d404f22fbfb..2cd2c45e8ba 100644 --- a/devtools/client/shared/test/browser_outputparser.js +++ b/devtools/client/shared/test/browser_outputparser.js @@ -3,13 +3,12 @@ "use strict"; -var {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); var {Loader} = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}); var {OutputParser} = require("devtools/shared/output-parser"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_spectrum.js b/devtools/client/shared/test/browser_spectrum.js index a20e348654b..22f7d08652c 100644 --- a/devtools/client/shared/test/browser_spectrum.js +++ b/devtools/client/shared/test/browser_spectrum.js @@ -8,7 +8,7 @@ const TEST_URI = "chrome://devtools/content/shared/widgets/spectrum-frame.xhtml" const {Spectrum} = require("devtools/client/shared/widgets/Spectrum"); add_task(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); yield performTest(); gBrowser.removeCurrentTab(); }); diff --git a/devtools/client/shared/test/browser_telemetry_button_eyedropper.js b/devtools/client/shared/test/browser_telemetry_button_eyedropper.js index bf40b6c49f1..0ff8260b520 100644 --- a/devtools/client/shared/test/browser_telemetry_button_eyedropper.js +++ b/devtools/client/shared/test/browser_telemetry_button_eyedropper.js @@ -7,7 +7,7 @@ const TEST_URI = "data:text/html;charset=utf-8," + var {EyedropperManager} = require("devtools/client/eyedropper/eyedropper"); add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); let target = TargetFactory.forTab(gBrowser.selectedTab); diff --git a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js index 00e09e8dc69..e402029a30f 100644 --- a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js +++ b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js @@ -11,7 +11,7 @@ const TEST_URI = "data:text/html;charset=utf-8," + const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); let target = TargetFactory.forTab(gBrowser.selectedTab); diff --git a/devtools/client/shared/test/browser_telemetry_button_responsive.js b/devtools/client/shared/test/browser_telemetry_button_responsive.js index 5f1e5cde2fa..e749cf09e08 100644 --- a/devtools/client/shared/test/browser_telemetry_button_responsive.js +++ b/devtools/client/shared/test/browser_telemetry_button_responsive.js @@ -9,7 +9,7 @@ const TEST_URI = "data:text/html;charset=utf-8," + const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); let target = TargetFactory.forTab(gBrowser.selectedTab); diff --git a/devtools/client/shared/test/browser_telemetry_button_scratchpad.js b/devtools/client/shared/test/browser_telemetry_button_scratchpad.js index ff874270971..39431b9112a 100644 --- a/devtools/client/shared/test/browser_telemetry_button_scratchpad.js +++ b/devtools/client/shared/test/browser_telemetry_button_scratchpad.js @@ -9,7 +9,7 @@ const TEST_URI = "data:text/html;charset=utf-8," + const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); let target = TargetFactory.forTab(gBrowser.selectedTab); diff --git a/devtools/client/shared/test/browser_telemetry_button_tilt.js b/devtools/client/shared/test/browser_telemetry_button_tilt.js index 0bba156fe03..386cef5e8b6 100644 --- a/devtools/client/shared/test/browser_telemetry_button_tilt.js +++ b/devtools/client/shared/test/browser_telemetry_button_tilt.js @@ -9,7 +9,7 @@ const TEST_URI = "data:text/html;charset=utf-8," + const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); let target = TargetFactory.forTab(gBrowser.selectedTab); diff --git a/devtools/client/shared/test/browser_telemetry_sidebar.js b/devtools/client/shared/test/browser_telemetry_sidebar.js index b80930e0e6c..c088837ae45 100644 --- a/devtools/client/shared/test/browser_telemetry_sidebar.js +++ b/devtools/client/shared/test/browser_telemetry_sidebar.js @@ -8,7 +8,7 @@ const TEST_URI = "data:text/html;charset=utf-8,

    browser_telemetry_sidebar.jsbrowser_telemetry_toolboxtabs_ const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "performance"); diff --git a/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js b/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js index e7554e5492a..caca6aaa889 100644 --- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js +++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js @@ -8,7 +8,7 @@ const TEST_URI = "data:text/html;charset=utf-8,

    browser_telemetry_toolboxtabs_ const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "netmonitor"); diff --git a/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js b/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js index 14256814e49..44f15ab06d7 100644 --- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js +++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js @@ -8,7 +8,7 @@ const TEST_URI = "data:text/html;charset=utf-8,

    browser_telemetry_toolboxtabs_ const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "options"); diff --git a/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js b/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js index 476fbd320d9..f0cee95f69a 100644 --- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js +++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js @@ -19,7 +19,7 @@ add_task(function*() { let originalPref = Services.prefs.getBoolPref("devtools.shadereditor.enabled"); Services.prefs.setBoolPref("devtools.shadereditor.enabled", true); - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "shadereditor"); diff --git a/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js b/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js index 93348b96df7..58965249223 100644 --- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js +++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js @@ -11,7 +11,7 @@ add_task(function*() { info("Activating the storage inspector"); Services.prefs.setBoolPref("devtools.storage.enabled", true); - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "storage"); diff --git a/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js b/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js index 15c4a9c08ac..762f107946f 100644 --- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js +++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js @@ -8,7 +8,7 @@ const TEST_URI = "data:text/html;charset=utf-8,

    browser_telemetry_toolboxtabs_ const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "styleeditor"); diff --git a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js index 033791a7235..a57325aa516 100644 --- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js +++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js @@ -12,7 +12,7 @@ add_task(function*() { let originalPref = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled"); Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true); - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "webaudioeditor"); diff --git a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js index b989a14269a..a84686f8101 100644 --- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js +++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js @@ -8,7 +8,7 @@ const TEST_URI = "data:text/html;charset=utf-8,

    browser_telemetry_toolboxtabs_ const TOOL_DELAY = 200; add_task(function*() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); let Telemetry = loadTelemetryAndRecordLogs(); yield openAndCloseToolbox(2, TOOL_DELAY, "webconsole"); diff --git a/devtools/client/shared/test/browser_templater_basic.js b/devtools/client/shared/test/browser_templater_basic.js index 70acb70ee09..b37cf1e6bcc 100644 --- a/devtools/client/shared/test/browser_templater_basic.js +++ b/devtools/client/shared/test/browser_templater_basic.js @@ -14,7 +14,7 @@ const template = Cu.import("resource://devtools/shared/gcli/Templater.jsm", {}). const TEST_URI = TEST_URI_ROOT + "browser_templater_basic.html"; var test = Task.async(function*() { - yield promiseTab("about:blank"); + yield addTab("about:blank"); let [host, win, doc] = yield createHost("bottom", TEST_URI); info("Starting DOM Templater Tests"); diff --git a/devtools/client/shared/test/browser_theme.js b/devtools/client/shared/test/browser_theme.js index 110d8a28df0..7e704ddc792 100644 --- a/devtools/client/shared/test/browser_theme.js +++ b/devtools/client/shared/test/browser_theme.js @@ -6,12 +6,12 @@ var {getColor, getTheme, setTheme} = require("devtools/client/shared/theme"); -function test() { +add_task(function* () { testGetTheme(); testSetTheme(); testGetColor(); testColorExistence(); -} +}); function testGetTheme () { let originalTheme = getTheme(); diff --git a/devtools/client/shared/test/browser_toolbar_basic.js b/devtools/client/shared/test/browser_toolbar_basic.js index cfeb7f95801..3605e92dc12 100644 --- a/devtools/client/shared/test/browser_toolbar_basic.js +++ b/devtools/client/shared/test/browser_toolbar_basic.js @@ -5,26 +5,17 @@ const TEST_URI = TEST_URI_ROOT + "browser_toolbar_basic.html"; -function test() { - addTab(TEST_URI, function(browser, tab) { - info("Starting browser_toolbar_basic.js"); - runTest(); - }); -} +add_task(function*() { + info("Starting browser_toolbar_basic.js"); + yield addTab(TEST_URI); -function runTest() { - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in runTest"); + ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in to start"); - oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW, catchFail(checkOpen)); + let shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW); document.getElementById("Tools:DevToolbar").doCommand(); -} - -function isChecked(b) { - return b.getAttribute("checked") == "true"; -} - -function checkOpen() { + yield shown; ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in checkOpen"); + let close = document.getElementById("developer-toolbar-closebutton"); ok(close, "Close button exists"); @@ -33,42 +24,35 @@ function checkOpen() { ok(!isChecked(toggleToolbox), "toggle toolbox button is not checked"); let target = TargetFactory.forTab(gBrowser.selectedTab); - gDevTools.showToolbox(target, "inspector").then(function(toolbox) { - ok(isChecked(toggleToolbox), "toggle toolbox button is checked"); - - addTab("about:blank", function(browser, tab) { - info("Opened a new tab"); - - ok(!isChecked(toggleToolbox), "toggle toolbox button is not checked"); - - gBrowser.removeCurrentTab(); - - oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE, catchFail(checkClosed)); - document.getElementById("Tools:DevToolbar").doCommand(); - }); - }); -} - -function checkClosed() { - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in checkClosed"); - - oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW, catchFail(checkReOpen)); - document.getElementById("Tools:DevToolbar").doCommand(); -} - -function checkReOpen() { - ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in checkReOpen"); - - let toggleToolbox = - document.getElementById("devtoolsMenuBroadcaster_DevToolbox"); + let toolbox = yield gDevTools.showToolbox(target, "inspector"); ok(isChecked(toggleToolbox), "toggle toolbox button is checked"); - oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE, catchFail(checkReClosed)); + yield addTab("about:blank"); + info("Opened a new tab"); + + ok(!isChecked(toggleToolbox), "toggle toolbox button is not checked"); + + gBrowser.removeCurrentTab(); + + let hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE); + document.getElementById("Tools:DevToolbar").doCommand(); + yield hidden; + ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in hidden"); + + shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW); + document.getElementById("Tools:DevToolbar").doCommand(); + yield shown; + ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in after open"); + + ok(isChecked(toggleToolbox), "toggle toolbox button is checked"); + + hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE); document.getElementById("developer-toolbar-closebutton").doCommand(); -} + yield hidden; -function checkReClosed() { - ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in checkReClosed"); + ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible after re-close"); +}); - finish(); +function isChecked(b) { + return b.getAttribute("checked") == "true"; } diff --git a/devtools/client/shared/test/browser_toolbar_tooltip.js b/devtools/client/shared/test/browser_toolbar_tooltip.js index a042f2bf270..7e979fb0b73 100644 --- a/devtools/client/shared/test/browser_toolbar_tooltip.js +++ b/devtools/client/shared/test/browser_toolbar_tooltip.js @@ -19,7 +19,7 @@ registerCleanupFunction(() => { }); add_task(function* showToolbar() { - yield promiseTab(TEST_URI); + yield addTab(TEST_URI); info("Starting browser_toolbar_tooltip.js"); diff --git a/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js b/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js index 4b259de0866..3bde1598e24 100644 --- a/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js +++ b/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js @@ -19,9 +19,9 @@ function test() { }); ignoreAllUncaughtExceptions(); - addTab(TEST_URI, openToolbar); + addTab(TEST_URI).then(openToolbar); - function openToolbar(browser, tab) { + function openToolbar(tab) { tab1 = tab; ignoreAllUncaughtExceptions(false); @@ -62,12 +62,12 @@ function test() { warnings: 1, callback: () => { ignoreAllUncaughtExceptions(); - addTab(TEST_URI, onOpenSecondTab); + addTab(TEST_URI).then(onOpenSecondTab); }, }); } - function onOpenSecondTab(browser, tab) { + function onOpenSecondTab(tab) { tab2 = tab; ignoreAllUncaughtExceptions(false); diff --git a/devtools/client/shared/test/browser_treeWidget_basic.js b/devtools/client/shared/test/browser_treeWidget_basic.js index cf58e4926fd..8aa3bd18307 100644 --- a/devtools/client/shared/test/browser_treeWidget_basic.js +++ b/devtools/client/shared/test/browser_treeWidget_basic.js @@ -11,7 +11,7 @@ const TEST_URI = "data:text/html;charset=utf-8, { - DevToolsUtils.testing = false; -}); const TEST_URI_ROOT = "http://example.com/browser/devtools/client/shared/test/"; const OPTIONS_VIEW_URL = TEST_URI_ROOT + "doc_options-view.xul"; -/** - * Open a new tab at a URL and call a callback on load - */ -function addTab(aURL, aCallback) -{ - waitForExplicitFinish(); - - gBrowser.selectedTab = gBrowser.addTab(); - let tab = gBrowser.selectedTab; - let browser = gBrowser.getBrowserForTab(tab); - - let url = encodeURI(aURL); - - BrowserTestUtils.browserLoaded(browser, false, url).then(() => { - aCallback(browser, tab, browser.contentDocument); - }); - - browser.loadURI(url); -} - -function promiseTab(aURL) { - return new Promise(resolve => - addTab(aURL, resolve)); -} - -registerCleanupFunction(function* tearDown() { - let target = TargetFactory.forTab(gBrowser.selectedTab); - yield gDevTools.closeToolbox(target); - - while (gBrowser.tabs.length > 1) { - gBrowser.removeCurrentTab(); - } - - console = undefined; -}); - function catchFail(func) { return function() { try { @@ -136,11 +93,17 @@ function waitForValue(aOptions) } function oneTimeObserve(name, callback) { - var func = function() { - Services.obs.removeObserver(func, name); - callback(); - }; - Services.obs.addObserver(func, name, false); + return new Promise((resolve) => { + + var func = function() { + Services.obs.removeObserver(func, name); + if (callback) { + callback(); + } + resolve(); + }; + Services.obs.addObserver(func, name, false); + }); } var createHost = Task.async(function*(type = "bottom", src = "data:text/html;charset=utf-8,") { From c4f729fcfbd85aa03d0d69baca93bce2ca7e66d1 Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Thu, 5 Nov 2015 17:06:14 -0800 Subject: [PATCH 63/71] Bug 1215954 - Add feature to save a heap snapshot from memory tool to disk. r=fitzgen,vp --- .../client/locales/en-US/memory.properties | 12 +++++ devtools/client/memory/actions/io.js | 45 +++++++++++++++++++ devtools/client/memory/actions/moz.build | 1 + devtools/client/memory/app.js | 4 +- devtools/client/memory/components/list.js | 4 +- .../memory/components/snapshot-list-item.js | 15 +++++-- devtools/client/memory/constants.js | 6 +++ devtools/client/memory/store.js | 18 +++++++- devtools/client/memory/test/unit/head.js | 21 +++++++++ .../test/unit/test_action-export-snapshot.js | 43 ++++++++++++++++++ .../client/memory/test/unit/test_utils.js | 2 +- devtools/client/memory/test/unit/xpcshell.ini | 1 + devtools/client/memory/utils.js | 39 +++++++++++++++- devtools/client/shared/redux/create-store.js | 13 ++++-- .../client/shared/redux/middleware/history.js | 23 ++++++++++ .../client/shared/redux/middleware/moz.build | 1 + devtools/client/themes/memory.css | 12 ++++- 17 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 devtools/client/memory/actions/io.js create mode 100644 devtools/client/memory/test/unit/test_action-export-snapshot.js create mode 100644 devtools/client/shared/redux/middleware/history.js diff --git a/devtools/client/locales/en-US/memory.properties b/devtools/client/locales/en-US/memory.properties index e4dcde4751a..61f6a45b905 100644 --- a/devtools/client/locales/en-US/memory.properties +++ b/devtools/client/locales/en-US/memory.properties @@ -24,6 +24,18 @@ memory.panelLabel=Memory Panel # displayed inside the developer tools window. memory.tooltip=Memory +# LOCALIZATION NOTE (snapshot.io.save): The label for the link that saves a snapshot +# to disk. +snapshot.io.save=Save + +# LOCALIZATION NOTE (snapshot.io.save.window): The title for the window displayed when +# saving a snapshot to disk. +snapshot.io.save.window=Save Heap Snapshot + +# LOCALIZATION NOTE (snapshot.io.filter): The title for the filter used to +# filter file types (*.fxsnapshot) +snapshot.io.filter=Firefox Heap Snapshots + # LOCALIZATION NOTE (aggregate.mb): The label annotating the number of bytes (in megabytes) # in a snapshot. %S represents the value, rounded to 2 decimal points. aggregate.mb=%S MB diff --git a/devtools/client/memory/actions/io.js b/devtools/client/memory/actions/io.js new file mode 100644 index 00000000000..89bea8ccffb --- /dev/null +++ b/devtools/client/memory/actions/io.js @@ -0,0 +1,45 @@ +/* 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/. */ +"use strict"; + +const { assert } = require("devtools/shared/DevToolsUtils"); +const { snapshotState: states, actions } = require("../constants"); +const { L10N, openFilePicker } = require("../utils"); +const { OS } = require("resource://gre/modules/osfile.jsm"); +const VALID_EXPORT_STATES = [states.SAVED, states.READ, states.SAVING_CENSUS, states.SAVED_CENSUS]; + +exports.pickFileAndExportSnapshot = function (snapshot) { + return function* (dispatch, getState) { + let outputFile = yield openFilePicker({ + title: L10N.getFormatStr("snapshot.io.save.window"), + defaultName: OS.Path.basename(snapshot.path), + filters: [[L10N.getFormatStr("snapshot.io.filter"), "*.fxsnapshot"]] + }); + + if (!outputFile) { + return; + } + + yield dispatch(exportSnapshot(snapshot, outputFile.path)); + }; +}; + +const exportSnapshot = exports.exportSnapshot = function (snapshot, dest) { + return function* (dispatch, getState) { + + dispatch({ type: actions.EXPORT_SNAPSHOT_START, snapshot }); + + assert(VALID_EXPORT_STATES.includes(snapshot.state), + `Snapshot is in invalid state for exporting: ${snapshot.state}`); + + try { + yield OS.File.copy(snapshot.path, dest); + } catch (error) { + reportException("exportSnapshot", error); + dispatch({ type: actions.EXPORT_SNAPSHOT_ERROR, snapshot, error }); + } + + dispatch({ type: actions.EXPORT_SNAPSHOT_END, snapshot }); + }; +}; diff --git a/devtools/client/memory/actions/moz.build b/devtools/client/memory/actions/moz.build index 1fb9f9503b4..83ca635febd 100644 --- a/devtools/client/memory/actions/moz.build +++ b/devtools/client/memory/actions/moz.build @@ -8,5 +8,6 @@ DevToolsModules( 'breakdown.js', 'filter.js', 'inverted.js', + 'io.js', 'snapshot.js', ) diff --git a/devtools/client/memory/app.js b/devtools/client/memory/app.js index 454153dbda3..87816414609 100644 --- a/devtools/client/memory/app.js +++ b/devtools/client/memory/app.js @@ -9,6 +9,7 @@ const { toggleRecordingAllocationStacks } = require("./actions/allocations"); const { setBreakdownAndRefresh } = require("./actions/breakdown"); const { toggleInvertedAndRefresh } = require("./actions/inverted"); const { setFilterStringAndRefresh } = require("./actions/filter"); +const { pickFileAndExportSnapshot } = require("./actions/io"); const { selectSnapshotAndRefresh, takeSnapshotAndCensus } = require("./actions/snapshot"); const { breakdownNameToSpec, getBreakdownDisplayData } = require("./utils"); const Toolbar = createFactory(require("./components/toolbar")); @@ -78,7 +79,8 @@ const App = createClass({ List({ itemComponent: SnapshotListItem, items: snapshots, - onClick: snapshot => dispatch(selectSnapshotAndRefresh(heapWorker, snapshot)) + onClick: snapshot => dispatch(selectSnapshotAndRefresh(heapWorker, snapshot)), + onSave: snapshot => dispatch(pickFileAndExportSnapshot(snapshot)) }), HeapView({ diff --git a/devtools/client/memory/components/list.js b/devtools/client/memory/components/list.js index ee78f70fa2b..cbb2f0c2ba0 100644 --- a/devtools/client/memory/components/list.js +++ b/devtools/client/memory/components/list.js @@ -23,9 +23,9 @@ const List = module.exports = createClass({ return ( dom.ul({ className: "list" }, ...items.map((item, index) => { - return Item({ + return Item(Object.assign({}, this.props, { key: index, item, index, onClick: () => onClick(item), - }); + })); })) ); } diff --git a/devtools/client/memory/components/snapshot-list-item.js b/devtools/client/memory/components/snapshot-list-item.js index f5552274e58..2152b944ccc 100644 --- a/devtools/client/memory/components/snapshot-list-item.js +++ b/devtools/client/memory/components/snapshot-list-item.js @@ -11,13 +11,14 @@ const SnapshotListItem = module.exports = createClass({ displayName: "snapshot-list-item", propTypes: { - onClick: PropTypes.func, + onClick: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, item: snapshotModel.isRequired, index: PropTypes.number.isRequired, }, render() { - let { index, item: snapshot, onClick } = this.props; + let { index, item: snapshot, onClick, onSave } = this.props; let className = `snapshot-list-item ${snapshot.selected ? " selected" : ""}`; let statusText = getSnapshotStatusText(snapshot); let title = getSnapshotTitle(snapshot); @@ -34,12 +35,20 @@ const SnapshotListItem = module.exports = createClass({ details = dom.span({ className: "snapshot-state" }, statusText); } + let saveLink = !snapshot.path ? void 0 : dom.a({ + onClick: () => onSave(snapshot), + className: "save", + }, L10N.getFormatStr("snapshot.io.save")); + return ( dom.li({ className, onClick }, dom.span({ className: `snapshot-title ${statusText ? " devtools-throbber" : ""}` }, title), - details + dom.div({ className: "snapshot-info" }, + details, + saveLink + ) ) ); } diff --git a/devtools/client/memory/constants.js b/devtools/client/memory/constants.js index c6f90a1ab56..3e23301c23e 100644 --- a/devtools/client/memory/constants.js +++ b/devtools/client/memory/constants.js @@ -23,6 +23,12 @@ actions.TAKE_CENSUS_END = "take-census-end"; actions.TOGGLE_RECORD_ALLOCATION_STACKS_START = "toggle-record-allocation-stacks-start"; actions.TOGGLE_RECORD_ALLOCATION_STACKS_END = "toggle-record-allocation-stacks-end"; +// When a heap snapshot is being saved to a user-specified +// location on disk. +actions.EXPORT_SNAPSHOT_START = "export-snapshot-start"; +actions.EXPORT_SNAPSHOT_END = "export-snapshot-end"; +actions.EXPORT_SNAPSHOT_ERROR = "export-snapshot-error"; + // Fired by UI to select a snapshot to view. actions.SELECT_SNAPSHOT = "select-snapshot"; diff --git a/devtools/client/memory/store.js b/devtools/client/memory/store.js index 485266f136d..2bcd2ee56cd 100644 --- a/devtools/client/memory/store.js +++ b/devtools/client/memory/store.js @@ -8,6 +8,20 @@ const reducers = require("./reducers"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); module.exports = function () { - let shouldLog = DevToolsUtils.testing; - return createStore({ log: shouldLog })(combineReducers(reducers), {}); + let shouldLog = false; + let history; + + // If testing, store the action history in an array + // we'll later attach to the store + if (DevToolsUtils.testing) { + history = []; + } + + let store = createStore({ log: shouldLog, history })(combineReducers(reducers), {}); + + if (history) { + store.history = history; + } + + return store; }; diff --git a/devtools/client/memory/test/unit/head.js b/devtools/client/memory/test/unit/head.js index 523425959fa..c33f4720927 100644 --- a/devtools/client/memory/test/unit/head.js +++ b/devtools/client/memory/test/unit/head.js @@ -8,6 +8,8 @@ var { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {}); var { console } = Cu.import("resource://gre/modules/Console.jsm", {}); var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +var { OS } = require("resource://gre/modules/osfile.jsm"); +var { FileUtils } = require("resource://gre/modules/FileUtils.jsm"); var { TargetFactory } = require("devtools/client/framework/target"); var DevToolsUtils = require("devtools/shared/DevToolsUtils"); var promise = require("promise"); @@ -70,6 +72,25 @@ function waitUntilState (store, predicate) { return deferred.promise; } +function waitUntilAction (store, actionType) { + let deferred = promise.defer(); + let unsubscribe = store.subscribe(check); + let history = store.history; + let index = history.length; + + do_print(`Waiting for action "${actionType}"`); + function check () { + let action = history[index++]; + if (action && action.type === actionType) { + do_print(`Found action "${actionType}"`); + unsubscribe(); + deferred.resolve(store.getState()); + } + } + + return deferred.promise; +} + function waitUntilSnapshotState (store, expected) { let predicate = () => { let snapshots = store.getState().snapshots; diff --git a/devtools/client/memory/test/unit/test_action-export-snapshot.js b/devtools/client/memory/test/unit/test_action-export-snapshot.js new file mode 100644 index 00000000000..22b19b29f0b --- /dev/null +++ b/devtools/client/memory/test/unit/test_action-export-snapshot.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test exporting a snapshot to a user specified location on disk. + +let { exportSnapshot } = require("devtools/client/memory/actions/io"); +let { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot"); +let { snapshotState: states, actions } = require("devtools/client/memory/constants"); + +function run_test() { + run_next_test(); +} + +add_task(function *() { + let front = new StubbedMemoryFront(); + let heapWorker = new HeapAnalysesClient(); + yield front.attach(); + let store = Store(); + const { getState, dispatch } = store; + + let file = FileUtils.getFile("TmpD", ["tmp.fxsnapshot"]); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + let destPath = file.path; + let stat = yield OS.File.stat(destPath); + ok(stat.size === 0, "new file is 0 bytes at start"); + + dispatch(takeSnapshotAndCensus(front, heapWorker)); + yield waitUntilSnapshotState(store, [states.SAVED_CENSUS]); + + let exportEvents = Promise.all([ + waitUntilAction(store, actions.EXPORT_SNAPSHOT_START), + waitUntilAction(store, actions.EXPORT_SNAPSHOT_END) + ]); + dispatch(exportSnapshot(getState().snapshots[0], destPath)); + yield exportEvents; + + stat = yield OS.File.stat(destPath); + do_print(stat.size); + ok(stat.size > 0, "destination file is more than 0 bytes"); + + heapWorker.destroy(); + yield front.detach(); +}); diff --git a/devtools/client/memory/test/unit/test_utils.js b/devtools/client/memory/test/unit/test_utils.js index 7ec94813c6b..519a4327a27 100644 --- a/devtools/client/memory/test/unit/test_utils.js +++ b/devtools/client/memory/test/unit/test_utils.js @@ -35,7 +35,7 @@ add_task(function *() { let s1 = utils.createSnapshot(); let s2 = utils.createSnapshot(); - ok(s1.state, states.SAVING, "utils.createSnapshot() creates snapshot in saving state"); + equal(s1.state, states.SAVING, "utils.createSnapshot() creates snapshot in saving state"); ok(s1.id !== s2.id, "utils.createSnapshot() creates snapshot with unique ids"); ok(utils.breakdownEquals(utils.breakdownNameToSpec("coarseType"), breakdowns.coarseType.breakdown), diff --git a/devtools/client/memory/test/unit/xpcshell.ini b/devtools/client/memory/test/unit/xpcshell.ini index d580f7f3b3d..897db9754b6 100644 --- a/devtools/client/memory/test/unit/xpcshell.ini +++ b/devtools/client/memory/test/unit/xpcshell.ini @@ -5,6 +5,7 @@ tail = firefox-appdir = browser skip-if = toolkit == 'android' || toolkit == 'gonk' +[test_action-export-snapshot.js] [test_action-filter-01.js] [test_action-filter-02.js] [test_action-filter-03.js] diff --git a/devtools/client/memory/utils.js b/devtools/client/memory/utils.js index ac2a0d5d18f..f95e4c38b24 100644 --- a/devtools/client/memory/utils.js +++ b/devtools/client/memory/utils.js @@ -2,7 +2,7 @@ * 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/. */ -const { Cu } = require("chrome"); +const { Cu, Cc, Ci } = require("chrome"); Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm"); const STRINGS_URI = "chrome://devtools/locale/memory.properties" @@ -314,3 +314,40 @@ exports.parseSource = function (source) { return { short, long, host }; }; + +/** + * Takes some configurations and opens up a file picker and returns + * a promise to the chosen file if successful. + * + * @param {String} .title + * The title displayed in the file picker window. + * @param {Array>} .filters + * An array of filters to display in the file picker. Each filter in the array + * is a duple of two strings, one a name for the filter, and one the filter itself + * (like "*.json"). + * @param {String} .defaultName + * The default name chosen by the file picker window. + * @return {Promise} + * The file selected by the user, or null, if cancelled. + */ +exports.openFilePicker = function({ title, filters, defaultName }) { + let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + fp.init(window, title, Ci.nsIFilePicker.modeSave); + + for (let filter of (filters || [])) { + fp.appendFilter(filter[0], filter[1]); + } + fp.defaultString = defaultName; + + return new Promise(resolve => { + fp.open({ + done: result => { + if (result === Ci.nsIFilePicker.returnCancel) { + resolve(null); + return; + } + resolve(fp.file); + } + }); + }); +}; diff --git a/devtools/client/shared/redux/create-store.js b/devtools/client/shared/redux/create-store.js index 22aed294052..cca0f643f07 100644 --- a/devtools/client/shared/redux/create-store.js +++ b/devtools/client/shared/redux/create-store.js @@ -9,14 +9,17 @@ const { waitUntilService } = require("./middleware/wait-service"); const { task } = require("./middleware/task"); const { log } = require("./middleware/log"); const { promise } = require("./middleware/promise"); +const { history } = require("./middleware/history"); /** * This creates a dispatcher with all the standard middleware in place * that all code requires. It can also be optionally configured in * various ways, such as logging and recording. * - * @param {object} opts - boolean configuration flags + * @param {object} opts: * - log: log all dispatched actions to console + * - history: an array to store every action in. Should only be + * used in tests. * - middleware: array of middleware to be included in the redux store */ module.exports = (opts={}) => { @@ -27,13 +30,17 @@ module.exports = (opts={}) => { promise, ]; - if (opts.log) { - middleware.push(log); + if (opts.history) { + middleware.push(history(opts.history)); } if (opts.middleware) { opts.middleware.forEach(fn => middleware.push(fn)); } + if (opts.log) { + middleware.push(log); + } + return applyMiddleware(...middleware)(createStore); } diff --git a/devtools/client/shared/redux/middleware/history.js b/devtools/client/shared/redux/middleware/history.js new file mode 100644 index 00000000000..b76ade01024 --- /dev/null +++ b/devtools/client/shared/redux/middleware/history.js @@ -0,0 +1,23 @@ +/* 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/. */ +"use strict"; + +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); + +/** + * A middleware that stores every action coming through the store in the passed + * in logging object. Should only be used for tests, as it collects all + * action information, which will cause memory bloat. + */ +exports.history = (log=[]) => ({ dispatch, getState }) => { + if (!DevToolsUtils.testing) { + console.warn(`Using history middleware stores all actions in state for testing\ + and devtools is not currently running in test mode. Be sure this is\ + intentional.`); + } + return next => action => { + log.push(action); + next(action); + }; +}; diff --git a/devtools/client/shared/redux/middleware/moz.build b/devtools/client/shared/redux/middleware/moz.build index d4024da4058..33fa9f57db5 100644 --- a/devtools/client/shared/redux/middleware/moz.build +++ b/devtools/client/shared/redux/middleware/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'history.js', 'log.js', 'promise.js', 'task.js', diff --git a/devtools/client/themes/memory.css b/devtools/client/themes/memory.css index 86568f07801..19f0ba6e209 100644 --- a/devtools/client/themes/memory.css +++ b/devtools/client/themes/memory.css @@ -153,7 +153,6 @@ html, body, #app, #memory-tool { color: var(--theme-body-color); border-bottom: 1px solid rgba(128,128,128,0.15); padding: 8px; - cursor: pointer; } .snapshot-list-item.selected { @@ -161,6 +160,17 @@ html, body, #app, #memory-tool { color: var(--theme-selection-color); } +.snapshot-list-item .snapshot-info { + display: flex; + justify-content: space-between; + font-size: 90%; +} + +.snapshot-list-item .save { + text-decoration: underline; + cursor: pointer; +} + .snapshot-list-item > .snapshot-title { margin-bottom: 14px; } From d8d5b02185c1227ee7db08e62eb4f330497d266d Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 12:29:31 -0800 Subject: [PATCH 64/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index 55a5991ce72..d63db2da186 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 51af954d66b..3b00865b929 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 7941f99b3f9..4048e68eea8 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -20,7 +20,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 05b269fd57b..eac5828f64b 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 5e316f6af41..764e95da59e 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -23,7 +23,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 90ee4e4a4f7..208b50b3874 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 6042e65e55b..b9f544e18ec 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -24,7 +24,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 8cef2dccb2c..176f69d53f6 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -21,7 +21,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 5c76721b46e..e2d129e8a70 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -24,7 +24,7 @@ - + From 3f3631e25d3e2b55f8cf02a971ed05734858d058 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Tue, 10 Nov 2015 21:25:51 +0000 Subject: [PATCH 65/71] Bug 1223351 - Store a metrics event on the loop server if the data channel setup fails. r=Mardak --- .../loop/content/shared/js/otSdkDriver.js | 6 +- .../loop/test/shared/otSdkDriver_test.js | 57 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/browser/components/loop/content/shared/js/otSdkDriver.js b/browser/components/loop/content/shared/js/otSdkDriver.js index 4fdb1d7bd03..4e0f8af9507 100644 --- a/browser/components/loop/content/shared/js/otSdkDriver.js +++ b/browser/components/loop/content/shared/js/otSdkDriver.js @@ -507,7 +507,9 @@ loop.OTSdkDriver = (function() { case "Session.forceDisconnected": break; default: - if (eventName.indexOf("sdk.exception") === -1) { + // We don't want unexpected events being sent to the server, so + // filter out the unexpected, and let the known ones through. + if (!/^sdk\.(exception|datachannel)/.test(eventName)) { console.error("Unexpected event name", eventName); return; } @@ -676,6 +678,7 @@ loop.OTSdkDriver = (function() { // Sends will queue until the channel is fully open. if (err) { console.error(err); + this._notifyMetricsEvent("sdk.datachannel.sub." + err.message); return; } @@ -726,6 +729,7 @@ loop.OTSdkDriver = (function() { this.publisher._.getDataChannel("text", {}, function(err, channel) { if (err) { console.error(err); + this._notifyMetricsEvent("sdk.datachannel.pub." + err.message); return; } diff --git a/browser/components/loop/test/shared/otSdkDriver_test.js b/browser/components/loop/test/shared/otSdkDriver_test.js index d9bc3b8510f..976b80811f1 100644 --- a/browser/components/loop/test/shared/otSdkDriver_test.js +++ b/browser/components/loop/test/shared/otSdkDriver_test.js @@ -1087,6 +1087,8 @@ describe("loop.OTSdkDriver", function() { fakeChannel = _.extend({}, Backbone.Events); fakeStream.connection = fakeConnection; driver._useDataChannels = true; + + sandbox.stub(console, "error"); }); it("should trigger a readyForDataChannel signal after subscribe is complete", function() { @@ -1122,6 +1124,33 @@ describe("loop.OTSdkDriver", function() { sinon.assert.notCalled(fakeSubscriberObject._.getDataChannel); }); + it("should log an error if the data channel couldn't be obtained", function() { + var err = new Error("fakeError"); + + fakeSubscriberObject._.getDataChannel.callsArgWith(2, err); + + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.calledOnce(console.error); + sinon.assert.calledWithMatch(console.error, err); + }); + + it("should dispatch ConnectionStatus if the data channel couldn't be obtained", function() { + fakeSubscriberObject._.getDataChannel.callsArgWith(2, new Error("fakeError")); + + session.trigger("streamCreated", { stream: fakeStream }); + + sinon.assert.called(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.ConnectionStatus({ + connections: 0, + event: "sdk.datachannel.sub.fakeError", + sendStreams: 0, + state: "receiving", + recvStreams: 1 + })); + }); + it("should dispatch `DataChannelsAvailable` if the publisher channel is setup", function() { // Fake a publisher channel. driver._publisherChannel = {}; @@ -1549,6 +1578,7 @@ describe("loop.OTSdkDriver", function() { beforeEach(function() { driver.subscriber = subscriber; driver._useDataChannels = true; + sandbox.stub(console, "error"); }); it("should not do anything if data channels are not wanted", function() { @@ -1566,6 +1596,33 @@ describe("loop.OTSdkDriver", function() { sinon.assert.calledOnce(publisher._.getDataChannel); }); + it("should log an error if the data channel couldn't be obtained", function() { + var err = new Error("fakeError"); + + publisher._.getDataChannel.callsArgWith(2, err); + + session.trigger("signal:readyForDataChannel"); + + sinon.assert.calledOnce(console.error); + sinon.assert.calledWithMatch(console.error, err); + }); + + it("should dispatch ConnectionStatus if the data channel couldn't be obtained", function() { + publisher._.getDataChannel.callsArgWith(2, new Error("fakeError")); + + session.trigger("signal:readyForDataChannel"); + + sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledWithExactly(dispatcher.dispatch, + new sharedActions.ConnectionStatus({ + connections: 0, + event: "sdk.datachannel.pub.fakeError", + sendStreams: 0, + state: "starting", + recvStreams: 0 + })); + }); + it("should dispatch `DataChannelsAvailable` if the subscriber channel is setup", function() { var fakeChannel = _.extend({}, Backbone.Events); From 03e9ba0cca30525c8f5d1ea0a4ac76353ea17f67 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 14:53:18 -0800 Subject: [PATCH 66/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/98f2ffabcc4d Author: punamdahiya Desc: Merge pull request #33000 from punamdahiya/Bug1221650 Bug 1221650 - [Gallery] LazyLoad gaia-header.js ======== https://hg.mozilla.org/integration/gaia-central/rev/6d1b39dd6921 Author: Punam Dahiya Desc: Bug 1221650 - [Gallery] LazyLoad gaia-header.js --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 3e995864691..52705251434 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "276f24a27efbee485c3edb489b5dd289f29c85c6", + "git_revision": "7eb0ea62cdc713eb3109d4701ff959f1bba1ef92", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "218023b067fd0971441d034926ccccd467f92e46", + "revision": "98f2ffabcc4d2e6c1a54874f6f096ad25f4ad172", "repo_path": "integration/gaia-central" } From 550b271b52e02af846d49794bc3bb326c1acf9d4 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 14:54:44 -0800 Subject: [PATCH 67/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index d63db2da186..c611f969f29 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 3b00865b929..02a212de1b9 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 5c3af4b8038..fad2e575fa6 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 4048e68eea8..2c440d61abc 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index eac5828f64b..ac2090ec276 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 764e95da59e..e5c92a39633 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 5c3af4b8038..fad2e575fa6 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 208b50b3874..da51e945e61 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index b9f544e18ec..3692f60eb8d 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 176f69d53f6..08d5733a40f 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index e2d129e8a70..816050e2697 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 8daae9f22020dcde3aaf5dc71e3537b83a63f4ce Mon Sep 17 00:00:00 2001 From: Gregory Szorc Date: Tue, 10 Nov 2015 15:41:08 -0800 Subject: [PATCH 68/71] Bug 1223149 - Add basic usage documentation for `mach build`; r=glandium Support for displaying docstrings in `mach help` was added relatively recently. `mach build` was never documented. Let's document it. There are a gazillion things we could put in the documentation. For now, mainly focus on targets. --- python/mozbuild/mozbuild/mach_commands.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py index 1cec5575ac8..486ea0a282f 100644 --- a/python/mozbuild/mozbuild/mach_commands.py +++ b/python/mozbuild/mozbuild/mach_commands.py @@ -274,6 +274,26 @@ class Build(MachCommandBase): help='Verbose output for what commands the build is running.') def build(self, what=None, disable_extra_make_dependencies=None, jobs=0, directory=None, verbose=False): + """Build the source tree. + + With no arguments, this will perform a full build. + + Positional arguments define targets to build. These can be make targets + or patterns like "

    /" to indicate a make target within a + directory. + + There are a few special targets that can be used to perform a partial + build faster than what `mach build` would perform: + + * binaries - compiles and links all C/C++ sources and produces shared + libraries and executables (binaries). + + * faster - builds JavaScript, XUL, CSS, etc files. + + "binaries" and "faster" almost fully complement each other. However, + there are build actions not captured by either. If things don't appear to + be rebuilding, perform a vanilla `mach build` to rebuild the world. + """ import which from mozbuild.controller.building import BuildMonitor from mozbuild.util import resolve_target_to_make From d7560653b323d6f18522229c656473fc24b6f939 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 16:15:33 -0800 Subject: [PATCH 69/71] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump ======== https://hg.mozilla.org/integration/gaia-central/rev/fbb1b70ce9fd Author: Sam Foster Desc: Merge pull request #33090 from sfoster/ftu-statusbar-gij-bug-1219655 Bug 1219655 - Statusbar tests for FTU + helpers. r=mhenretty ======== https://hg.mozilla.org/integration/gaia-central/rev/6a3c4adbf7d2 Author: Sam Foster Desc: Bug 1219655 - Statusbar tests for FTU + helpers --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 52705251434..438da5b51f4 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "7eb0ea62cdc713eb3109d4701ff959f1bba1ef92", + "git_revision": "22f8023b112dfae83531b0a075ab9eb9a5444dfa", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "98f2ffabcc4d2e6c1a54874f6f096ad25f4ad172", + "revision": "fbb1b70ce9fdf934ff09885898d9ce5fa9f87b57", "repo_path": "integration/gaia-central" } From 5adf8f32dc68e41dbf80d7e120982801cb86f249 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Tue, 10 Nov 2015 16:16:56 -0800 Subject: [PATCH 70/71] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index c611f969f29..960559e5fd6 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 02a212de1b9..d548df0847b 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index fad2e575fa6..48725a8bae7 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 2c440d61abc..d795a05ad69 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index ac2090ec276..26333c608da 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index e5c92a39633..44ea96c155f 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index fad2e575fa6..48725a8bae7 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index da51e945e61..b032376fd8a 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4-kk/sources.xml b/b2g/config/nexus-4-kk/sources.xml index 3692f60eb8d..b526a16b2be 100644 --- a/b2g/config/nexus-4-kk/sources.xml +++ b/b2g/config/nexus-4-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 08d5733a40f..0042364cd3b 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 816050e2697..c000b7bdc5f 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 1c03aa1b9b3c691a1aaed3480eabe5df5e2639a1 Mon Sep 17 00:00:00 2001 From: Allison Naaktgeboren Date: Tue, 10 Nov 2015 19:02:23 -0800 Subject: [PATCH 71/71] Bug 1215475 - Search suggestions are duplicated after one of it is previously selected.r=mcomella --- mobile/android/base/home/BrowserSearch.java | 13 +++- mobile/android/base/home/SearchEngineRow.java | 75 ++++++++++++------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/mobile/android/base/home/BrowserSearch.java b/mobile/android/base/home/BrowserSearch.java index 8f0e7bda301..00f90389137 100644 --- a/mobile/android/base/home/BrowserSearch.java +++ b/mobile/android/base/home/BrowserSearch.java @@ -101,8 +101,9 @@ public class BrowserSearch extends HomeFragment // Timeout for the suggestion client to respond private static final int SUGGESTION_TIMEOUT = 3000; - // Maximum number of results returned by the suggestion client - private static final int SUGGESTION_MAX = 3; + // Maximum number of suggestions from the search engine's suggestion client. This impacts network traffic and device + // data consumption whereas R.integer.max_saved_suggestions controls how many suggestion to show in the UI. + private static final int NETWORK_SUGGESTION_MAX = 3; // The maximum number of rows deep in a search we'll dig // for an autocomplete result @@ -670,7 +671,7 @@ public class BrowserSearch extends HomeFragment return; } - mSuggestClient = sSuggestClientFactory.getSuggestClient(getActivity(), suggestTemplate, SUGGESTION_TIMEOUT, SUGGESTION_MAX); + mSuggestClient = sSuggestClientFactory.getSuggestClient(getActivity(), suggestTemplate, SUGGESTION_TIMEOUT, NETWORK_SUGGESTION_MAX); } private void showSuggestionsOptIn() { @@ -903,7 +904,11 @@ public class BrowserSearch extends HomeFragment String actualQuery = BrowserContract.SearchHistory.QUERY + " LIKE ?"; String[] queryArgs = new String[] { '%' + mSearchTerm + '%' }; - final int maxSavedSuggestions = getContext().getResources().getInteger(R.integer.max_saved_suggestions); + // For deduplication, the worst case is that all the first NETWORK_SUGGESTION_MAX history suggestions are duplicates + // of search engine suggestions, and the there is a duplicate for the search term itself. A duplicate of the + // search term can occur if the user has previously searched for the same thing. + final int maxSavedSuggestions = NETWORK_SUGGESTION_MAX + 1 + getContext().getResources().getInteger(R.integer.max_saved_suggestions); + final String sortOrderAndLimit = BrowserContract.SearchHistory.DATE +" DESC LIMIT " + maxSavedSuggestions; final Cursor result = cr.query(BrowserContract.SearchHistory.CONTENT_URI, columns, actualQuery, queryArgs, sortOrderAndLimit); diff --git a/mobile/android/base/home/SearchEngineRow.java b/mobile/android/base/home/SearchEngineRow.java index 81d35e2d394..6f92a2c71d6 100644 --- a/mobile/android/base/home/SearchEngineRow.java +++ b/mobile/android/base/home/SearchEngineRow.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.graphics.Typeface; +import android.support.annotation.Nullable; import android.text.style.StyleSpan; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -36,6 +37,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -261,24 +263,28 @@ class SearchEngineRow extends AnimatedHeightLayout { /** * Displays search suggestions from previous searches. * - * @param savedSuggestions The List to iterate over for saved search suggestions to display - * @param suggestionCounter global index of where to start adding suggestion "buttons" in the search engine row + * @param savedSuggestions The List to iterate over for saved search suggestions to display. This function does not + * enforce a ui maximum or filter. It will show all the suggestions in this list. + * @param suggestionStartIndex global index of where to start adding suggestion "buttons" in the search engine row. Also + * acts as a counter for total number of suggestions visible. * @param animate whether or not to animate suggestions for visual polish * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls */ - private void updateFromSavedSearches(List savedSuggestions, boolean animate, int suggestionCounter, int recycledSuggestionCount) { + private void updateFromSavedSearches(List savedSuggestions, boolean animate, int suggestionStartIndex, int recycledSuggestionCount) { if (savedSuggestions == null || savedSuggestions.isEmpty()) { return; } - final int historyStartIndex = suggestionCounter; - for (String suggestion : savedSuggestions) { - String telemetryTag = "history." + (suggestionCounter - historyStartIndex); - bindSuggestionView(suggestion, animate, recycledSuggestionCount, suggestionCounter, true, telemetryTag); - ++suggestionCounter; + final int numSavedSearches = savedSuggestions.size(); + int indexOfPreviousSuggestion = 0; + for (int i = 0; i < numSavedSearches; i++) { + String telemetryTag = "history." + i; + final String suggestion = savedSuggestions.get(i); + indexOfPreviousSuggestion = suggestionStartIndex + i; + bindSuggestionView(suggestion, animate, recycledSuggestionCount, indexOfPreviousSuggestion, true, telemetryTag); } - hideRecycledSuggestions(suggestionCounter, recycledSuggestionCount); + hideRecycledSuggestions(indexOfPreviousSuggestion + 1, recycledSuggestionCount); } /** @@ -289,33 +295,34 @@ class SearchEngineRow extends AnimatedHeightLayout { * @param savedSuggestionCount how many saved searches this searchTerm has * @return the global count of how many suggestions have been bound/shown in the search engine row */ - private int updateFromSearchEngine(boolean animate, int recycledSuggestionCount, int savedSuggestionCount) { + private int updateFromSearchEngine(boolean animate, List searchEngineSuggestions, int recycledSuggestionCount, int savedSuggestionCount) { int maxSuggestions = mMaxSearchSuggestions; // If there are less than max saved searches on phones, fill the space with more search engine suggestions if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) { maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount; } - int suggestionCounter = 0; - for (String suggestion : mSearchEngine.getSuggestions()) { - if (suggestionCounter == maxSuggestions) { + final int numSearchEngineSuggestions = searchEngineSuggestions.size(); + int relativeIndex; + for (relativeIndex = 0; relativeIndex < numSearchEngineSuggestions; relativeIndex++) { + if (relativeIndex == maxSuggestions) { break; } - // Since the search engine suggestions are listed first, we can use suggestionCounter to get their relative positions for telemetry - String telemetryTag = "engine." + suggestionCounter; - bindSuggestionView(suggestion, animate, recycledSuggestionCount, suggestionCounter, false, telemetryTag); - ++suggestionCounter; + // Since the search engine suggestions are listed first, their relative index is their global index + String telemetryTag = "engine." + relativeIndex; + final String suggestion = searchEngineSuggestions.get(relativeIndex); + bindSuggestionView(suggestion, animate, recycledSuggestionCount, relativeIndex, false, telemetryTag); } - hideRecycledSuggestions(suggestionCounter, recycledSuggestionCount); + hideRecycledSuggestions(relativeIndex + 1, recycledSuggestionCount); // Make sure mSelectedView is still valid. if (mSelectedView >= mSuggestionView.getChildCount()) { mSelectedView = mSuggestionView.getChildCount() - 1; } - return suggestionCounter; + return relativeIndex; } /** @@ -327,10 +334,10 @@ class SearchEngineRow extends AnimatedHeightLayout { * * @param searchSuggestionsEnabled whether or not suggestions from the default search engine are enabled * @param searchEngine the search engine to use throughout the SearchEngineRow class - * @param searchHistorySuggestions search history suggestions + * @param rawSearchHistorySuggestions search history suggestions * @param animate whether or not to use animations **/ - public void updateSuggestions(boolean searchSuggestionsEnabled, SearchEngine searchEngine, List searchHistorySuggestions, boolean animate) { + public void updateSuggestions(boolean searchSuggestionsEnabled, SearchEngine searchEngine, @Nullable List rawSearchHistorySuggestions, boolean animate) { mSearchEngine = searchEngine; // Set the search engine icon (e.g., Google) for the row. mIconView.updateAndScaleImage(mSearchEngine.getIcon(), mSearchEngine.getEngineIdentifier()); @@ -341,15 +348,31 @@ class SearchEngineRow extends AnimatedHeightLayout { final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext()); final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true); - if (searchSuggestionsEnabled && savedSearchesEnabled) { - final int savedSearchCount = (searchHistorySuggestions != null) ? searchHistorySuggestions.size() : 0; - final int suggestionViewCount = updateFromSearchEngine(animate, recycledSuggestionCount, savedSearchCount); - updateFromSavedSearches(searchHistorySuggestions, animate, suggestionViewCount, recycledSuggestionCount); + // Remove duplicates of search engine suggestions from saved searches. + List searchHistorySuggestions = (rawSearchHistorySuggestions != null) ? rawSearchHistorySuggestions : new ArrayList(); + List searchEngineSuggestions = new ArrayList(); + for (String suggestion : searchEngine.getSuggestions()) { + searchHistorySuggestions.remove(suggestion); + searchEngineSuggestions.add(suggestion); + } + // Make sure the search term itself isn't duplicated. This is more important on phones than tablets where screen + // space is more precious. + searchHistorySuggestions.remove(getSuggestionTextFromView(mUserEnteredView)); + // Trim the history suggestions down to the maximum allowed. + if (searchHistorySuggestions.size() >= mMaxSavedSuggestions) { + // The second index to subList() is exclusive, so this looks like an off by one error but it is not. + searchHistorySuggestions = searchHistorySuggestions.subList(0, mMaxSavedSuggestions); + } + final int searchHistoryCount = searchHistorySuggestions.size(); + + if (searchSuggestionsEnabled && savedSearchesEnabled) { + final int suggestionViewCount = updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, searchHistoryCount); + updateFromSavedSearches(searchHistorySuggestions, animate, suggestionViewCount, recycledSuggestionCount); } else if (savedSearchesEnabled) { updateFromSavedSearches(searchHistorySuggestions, animate, 0, recycledSuggestionCount); } else if (searchSuggestionsEnabled) { - updateFromSearchEngine(animate, recycledSuggestionCount, 0); + updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, 0); } }