Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* jshint moz:true, browser:true */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 "use strict";
8 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
10 Cu.import("resource://gre/modules/Services.jsm");
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12 XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
13 "resource://gre/modules/media/PeerConnectionIdp.jsm");
15 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
16 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
17 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
18 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
19 const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
20 const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
21 const PC_IDENTITY_CONTRACT = "@mozilla.org/dom/rtcidentityassertion;1";
23 const PC_CID = Components.ID("{00e0e20d-1494-4776-8e0e-0f0acbea3c79}");
24 const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
25 const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
26 const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
27 const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
28 const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
29 const PC_IDENTITY_CID = Components.ID("{1abc7499-3c54-43e0-bd60-686e2703f072}");
31 // Global list of PeerConnection objects, so they can be cleaned up when
32 // a page is torn down. (Maps inner window ID to an array of PC objects).
33 function GlobalPCList() {
34 this._list = {};
35 this._networkdown = false; // XXX Need to query current state somehow
36 Services.obs.addObserver(this, "inner-window-destroyed", true);
37 Services.obs.addObserver(this, "profile-change-net-teardown", true);
38 Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
39 Services.obs.addObserver(this, "network:offline-status-changed", true);
40 }
41 GlobalPCList.prototype = {
42 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
43 Ci.nsISupportsWeakReference,
44 Ci.IPeerConnectionManager]),
45 classID: PC_MANAGER_CID,
46 _xpcom_factory: {
47 createInstance: function(outer, iid) {
48 if (outer) {
49 throw Cr.NS_ERROR_NO_AGGREGATION;
50 }
51 return _globalPCList.QueryInterface(iid);
52 }
53 },
55 addPC: function(pc) {
56 let winID = pc._winID;
57 if (this._list[winID]) {
58 this._list[winID].push(Cu.getWeakReference(pc));
59 } else {
60 this._list[winID] = [Cu.getWeakReference(pc)];
61 }
62 this.removeNullRefs(winID);
63 },
65 removeNullRefs: function(winID) {
66 if (this._list[winID] === undefined) {
67 return;
68 }
69 this._list[winID] = this._list[winID].filter(
70 function (e,i,a) { return e.get() !== null; });
72 if (this._list[winID].length === 0) {
73 delete this._list[winID];
74 }
75 },
77 hasActivePeerConnection: function(winID) {
78 this.removeNullRefs(winID);
79 return this._list[winID] ? true : false;
80 },
82 observe: function(subject, topic, data) {
83 let cleanupPcRef = function(pcref) {
84 let pc = pcref.get();
85 if (pc) {
86 pc._pc.close();
87 delete pc._observer;
88 pc._pc = null;
89 }
90 };
92 let cleanupWinId = function(list, winID) {
93 if (list.hasOwnProperty(winID)) {
94 list[winID].forEach(cleanupPcRef);
95 delete list[winID];
96 }
97 };
99 if (topic == "inner-window-destroyed") {
100 cleanupWinId(this._list, subject.QueryInterface(Ci.nsISupportsPRUint64).data);
101 } else if (topic == "profile-change-net-teardown" ||
102 topic == "network:offline-about-to-go-offline") {
103 // Delete all peerconnections on shutdown - mostly synchronously (we
104 // need them to be done deleting transports and streams before we
105 // return)! All socket operations must be queued to STS thread
106 // before we return to here.
107 // Also kill them if "Work Offline" is selected - more can be created
108 // while offline, but attempts to connect them should fail.
109 for (let winId in this._list) {
110 cleanupWinId(this._list, winId);
111 }
112 this._networkdown = true;
113 }
114 else if (topic == "network:offline-status-changed") {
115 if (data == "offline") {
116 // this._list shold be empty here
117 this._networkdown = true;
118 } else if (data == "online") {
119 this._networkdown = false;
120 }
121 }
122 },
124 };
125 let _globalPCList = new GlobalPCList();
127 function RTCIceCandidate() {
128 this.candidate = this.sdpMid = this.sdpMLineIndex = null;
129 }
130 RTCIceCandidate.prototype = {
131 classDescription: "mozRTCIceCandidate",
132 classID: PC_ICE_CID,
133 contractID: PC_ICE_CONTRACT,
134 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
135 Ci.nsIDOMGlobalPropertyInitializer]),
137 init: function(win) { this._win = win; },
139 __init: function(dict) {
140 this.candidate = dict.candidate;
141 this.sdpMid = dict.sdpMid;
142 this.sdpMLineIndex = ("sdpMLineIndex" in dict)? dict.sdpMLineIndex : null;
143 }
144 };
146 function RTCSessionDescription() {
147 this.type = this.sdp = null;
148 }
149 RTCSessionDescription.prototype = {
150 classDescription: "mozRTCSessionDescription",
151 classID: PC_SESSION_CID,
152 contractID: PC_SESSION_CONTRACT,
153 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
154 Ci.nsIDOMGlobalPropertyInitializer]),
156 init: function(win) { this._win = win; },
158 __init: function(dict) {
159 this.type = dict.type;
160 this.sdp = dict.sdp;
161 }
162 };
164 function RTCStatsReport(win, dict) {
165 function appendStats(stats, report) {
166 stats.forEach(function(stat) {
167 report[stat.id] = stat;
168 });
169 }
171 this._win = win;
172 this._pcid = dict.pcid;
173 this._report = {};
174 appendStats(dict.inboundRTPStreamStats, this._report);
175 appendStats(dict.outboundRTPStreamStats, this._report);
176 appendStats(dict.mediaStreamTrackStats, this._report);
177 appendStats(dict.mediaStreamStats, this._report);
178 appendStats(dict.transportStats, this._report);
179 appendStats(dict.iceComponentStats, this._report);
180 appendStats(dict.iceCandidatePairStats, this._report);
181 appendStats(dict.iceCandidateStats, this._report);
182 appendStats(dict.codecStats, this._report);
183 }
184 RTCStatsReport.prototype = {
185 classDescription: "RTCStatsReport",
186 classID: PC_STATS_CID,
187 contractID: PC_STATS_CONTRACT,
188 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
190 // TODO: Change to use webidl getters once available (Bug 952122)
191 //
192 // Since webidl getters are not available, we make the stats available as
193 // enumerable read-only properties directly on our content-facing object.
194 // Must be called after our webidl sandwich is made.
196 makeStatsPublic: function() {
197 let props = {};
198 this.forEach(function(stat) {
199 props[stat.id] = { enumerable: true, configurable: false,
200 writable: false, value: stat };
201 });
202 Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, props);
203 },
205 forEach: function(cb, thisArg) {
206 for (var key in this._report) {
207 cb.call(thisArg || this._report, this.get(key), key, this._report);
208 }
209 },
211 get: function(key) {
212 function publifyReadonly(win, obj) {
213 let props = {};
214 for (let k in obj) {
215 props[k] = {enumerable:true, configurable:false, writable:false, value:obj[k]};
216 }
217 let pubobj = Cu.createObjectIn(win);
218 Object.defineProperties(pubobj, props);
219 return pubobj;
220 }
222 // Return a content object rather than a wrapped chrome one.
223 return publifyReadonly(this._win, this._report[key]);
224 },
226 has: function(key) {
227 return this._report[key] !== undefined;
228 },
230 get mozPcid() { return this._pcid; }
231 };
233 function RTCIdentityAssertion() {}
234 RTCIdentityAssertion.prototype = {
235 classDescription: "RTCIdentityAssertion",
236 classID: PC_IDENTITY_CID,
237 contractID: PC_IDENTITY_CONTRACT,
238 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
239 Ci.nsIDOMGlobalPropertyInitializer]),
241 init: function(win) { this._win = win; },
243 __init: function(idp, name) {
244 this.idp = idp;
245 this.name = name;
246 }
247 };
249 function RTCPeerConnection() {
250 this._queue = [];
252 this._pc = null;
253 this._observer = null;
254 this._closed = false;
256 this._onCreateOfferSuccess = null;
257 this._onCreateOfferFailure = null;
258 this._onCreateAnswerSuccess = null;
259 this._onCreateAnswerFailure = null;
260 this._onGetStatsSuccess = null;
261 this._onGetStatsFailure = null;
263 this._pendingType = null;
264 this._localType = null;
265 this._remoteType = null;
266 this._trickleIce = false;
267 this._peerIdentity = null;
269 /**
270 * Everytime we get a request from content, we put it in the queue. If there
271 * are no pending operations though, we will execute it immediately. In
272 * PeerConnectionObserver, whenever we are notified that an operation has
273 * finished, we will check the queue for the next operation and execute if
274 * neccesary. The _pending flag indicates whether an operation is currently in
275 * progress.
276 */
277 this._pending = false;
279 // States
280 this._iceGatheringState = this._iceConnectionState = "new";
281 }
282 RTCPeerConnection.prototype = {
283 classDescription: "mozRTCPeerConnection",
284 classID: PC_CID,
285 contractID: PC_CONTRACT,
286 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
287 Ci.nsIDOMGlobalPropertyInitializer]),
288 init: function(win) { this._win = win; },
290 __init: function(rtcConfig) {
291 this._trickleIce = Services.prefs.getBoolPref("media.peerconnection.trickle_ice");
292 if (!rtcConfig.iceServers ||
293 !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
294 rtcConfig = {iceServers:
295 JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"))};
296 }
297 this._mustValidateRTCConfiguration(rtcConfig,
298 "RTCPeerConnection constructor passed invalid RTCConfiguration");
299 if (_globalPCList._networkdown) {
300 throw new this._win.DOMError("",
301 "Can't create RTCPeerConnections when the network is down");
302 }
304 this.makeGetterSetterEH("onaddstream");
305 this.makeGetterSetterEH("onicecandidate");
306 this.makeGetterSetterEH("onnegotiationneeded");
307 this.makeGetterSetterEH("onsignalingstatechange");
308 this.makeGetterSetterEH("onremovestream");
309 this.makeGetterSetterEH("ondatachannel");
310 this.makeGetterSetterEH("onconnection");
311 this.makeGetterSetterEH("onclosedconnection");
312 this.makeGetterSetterEH("oniceconnectionstatechange");
313 this.makeGetterSetterEH("onidentityresult");
314 this.makeGetterSetterEH("onpeeridentity");
315 this.makeGetterSetterEH("onidpassertionerror");
316 this.makeGetterSetterEH("onidpvalidationerror");
318 this._pc = new this._win.PeerConnectionImpl();
320 this.__DOM_IMPL__._innerObject = this;
321 this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
323 // Add a reference to the PeerConnection to global list (before init).
324 this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
325 .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
326 _globalPCList.addPC(this);
328 this._queueOrRun({
329 func: this._initialize,
330 args: [rtcConfig],
331 // If not trickling, suppress start.
332 wait: !this._trickleIce
333 });
334 },
336 _initialize: function(rtcConfig) {
337 this._impl.initialize(this._observer, this._win, rtcConfig,
338 Services.tm.currentThread);
339 this._initIdp();
340 },
342 get _impl() {
343 if (!this._pc) {
344 throw new this._win.DOMError("",
345 "RTCPeerConnection is gone (did you enter Offline mode?)");
346 }
347 return this._pc;
348 },
350 _initIdp: function() {
351 let prefName = "media.peerconnection.identity.timeout";
352 let idpTimeout = Services.prefs.getIntPref(prefName);
353 let warningFunc = this.logWarning.bind(this);
354 this._localIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc,
355 this.dispatchEvent.bind(this));
356 this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc,
357 this.dispatchEvent.bind(this));
358 },
360 /**
361 * Add a function to the queue or run it immediately if the queue is empty.
362 * Argument is an object with the func, args and wait properties; wait should
363 * be set to true if the function has a success/error callback that will call
364 * _executeNext, false if it doesn't have a callback.
365 */
366 _queueOrRun: function(obj) {
367 this._checkClosed();
368 if (!this._pending) {
369 if (obj.type !== undefined) {
370 this._pendingType = obj.type;
371 }
372 obj.func.apply(this, obj.args);
373 if (obj.wait) {
374 this._pending = true;
375 }
376 } else {
377 this._queue.push(obj);
378 }
379 },
381 // Pick the next item from the queue and run it.
382 _executeNext: function() {
383 if (this._queue.length) {
384 let obj = this._queue.shift();
385 if (obj.type !== undefined) {
386 this._pendingType = obj.type;
387 }
388 obj.func.apply(this, obj.args);
389 if (!obj.wait) {
390 this._executeNext();
391 }
392 } else {
393 this._pending = false;
394 }
395 },
397 /**
398 * An RTCConfiguration looks like this:
399 *
400 * { "iceServers": [ { url:"stun:stun.example.org" },
401 * { url:"turn:turn.example.org",
402 * username:"jib", credential:"mypass"} ] }
403 *
404 * WebIDL normalizes structure for us, so we test well-formed stun/turn urls,
405 * but not validity of servers themselves, before passing along to C++.
406 * ErrorMsg is passed in to detail which array-entry failed, if any.
407 */
408 _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) {
409 var errorCtor = this._win.DOMError;
410 function nicerNewURI(uriStr, errorMsg) {
411 let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
412 try {
413 return ios.newURI(uriStr, null, null);
414 } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
415 throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr);
416 }
417 }
418 function mustValidateServer(server) {
419 if (!server.url) {
420 throw new errorCtor("", errorMsg + " - missing url");
421 }
422 let url = nicerNewURI(server.url, errorMsg);
423 if (url.scheme in { turn:1, turns:1 }) {
424 if (!server.username) {
425 throw new errorCtor("", errorMsg + " - missing username: " + server.url);
426 }
427 if (!server.credential) {
428 throw new errorCtor("", errorMsg + " - missing credential: " +
429 server.url);
430 }
431 }
432 else if (!(url.scheme in { stun:1, stuns:1 })) {
433 throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme);
434 }
435 }
436 if (rtcConfig.iceServers) {
437 let len = rtcConfig.iceServers.length;
438 for (let i=0; i < len; i++) {
439 mustValidateServer (rtcConfig.iceServers[i], errorMsg);
440 }
441 }
442 },
444 /**
445 * MediaConstraints look like this:
446 *
447 * {
448 * mandatory: {"OfferToReceiveAudio": true, "OfferToReceiveVideo": true },
449 * optional: [{"VoiceActivityDetection": true}, {"FooBar": 10}]
450 * }
451 *
452 * WebIDL normalizes the top structure for us, but the mandatory constraints
453 * member comes in as a raw object so we can detect unknown constraints. We
454 * compare its members against ones we support, and fail if not found.
455 */
456 _mustValidateConstraints: function(constraints, errorMsg) {
457 if (constraints.mandatory) {
458 let supported;
459 try {
460 // Passing the raw constraints.mandatory here validates its structure
461 supported = this._observer.getSupportedConstraints(constraints.mandatory);
462 } catch (e) {
463 throw new this._win.DOMError("", errorMsg + " - " + e.message);
464 }
466 for (let constraint of Object.keys(constraints.mandatory)) {
467 if (!(constraint in supported)) {
468 throw new this._win.DOMError("",
469 errorMsg + " - unknown mandatory constraint: " + constraint);
470 }
471 }
472 }
473 if (constraints.optional) {
474 let len = constraints.optional.length;
475 for (let i = 0; i < len; i++) {
476 let constraints_per_entry = 0;
477 for (let constraint in Object.keys(constraints.optional[i])) {
478 if (constraints_per_entry) {
479 throw new this._win.DOMError("", errorMsg +
480 " - optional constraint must be single key/value pair");
481 }
482 constraints_per_entry += 1;
483 }
484 }
485 }
486 },
488 // Ideally, this should be of the form _checkState(state),
489 // where the state is taken from an enumeration containing
490 // the valid peer connection states defined in the WebRTC
491 // spec. See Bug 831756.
492 _checkClosed: function() {
493 if (this._closed) {
494 throw new this._win.DOMError("", "Peer connection is closed");
495 }
496 },
498 dispatchEvent: function(event) {
499 this.__DOM_IMPL__.dispatchEvent(event);
500 },
502 // Log error message to web console and window.onerror, if present.
503 logErrorAndCallOnError: function(msg, file, line) {
504 this.logMsg(msg, file, line, Ci.nsIScriptError.exceptionFlag);
506 // Safely call onerror directly if present (necessary for testing)
507 try {
508 if (typeof this._win.onerror === "function") {
509 this._win.onerror(msg, file, line);
510 }
511 } catch(e) {
512 // If onerror itself throws, service it.
513 try {
514 this.logError(e.message, e.fileName, e.lineNumber);
515 } catch(e) {}
516 }
517 },
519 logError: function(msg, file, line) {
520 this.logMsg(msg, file, line, Ci.nsIScriptError.errorFlag);
521 },
523 logWarning: function(msg, file, line) {
524 this.logMsg(msg, file, line, Ci.nsIScriptError.warningFlag);
525 },
527 logMsg: function(msg, file, line, flag) {
528 let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
529 let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
530 scriptError.initWithWindowID(msg, file, null, line, 0, flag,
531 "content javascript", this._winID);
532 let console = Cc["@mozilla.org/consoleservice;1"].
533 getService(Ci.nsIConsoleService);
534 console.logMessage(scriptError);
535 },
537 getEH: function(type) {
538 return this.__DOM_IMPL__.getEventHandler(type);
539 },
541 setEH: function(type, handler) {
542 this.__DOM_IMPL__.setEventHandler(type, handler);
543 },
545 makeGetterSetterEH: function(name) {
546 Object.defineProperty(this, name,
547 {
548 get:function() { return this.getEH(name); },
549 set:function(h) { return this.setEH(name, h); }
550 });
551 },
553 createOffer: function(onSuccess, onError, constraints) {
554 if (!constraints) {
555 constraints = {};
556 }
557 this._mustValidateConstraints(constraints, "createOffer passed invalid constraints");
559 this._queueOrRun({
560 func: this._createOffer,
561 args: [onSuccess, onError, constraints],
562 wait: true
563 });
564 },
566 _createOffer: function(onSuccess, onError, constraints) {
567 this._onCreateOfferSuccess = onSuccess;
568 this._onCreateOfferFailure = onError;
569 this._impl.createOffer(constraints);
570 },
572 _createAnswer: function(onSuccess, onError, constraints, provisional) {
573 this._onCreateAnswerSuccess = onSuccess;
574 this._onCreateAnswerFailure = onError;
576 if (!this.remoteDescription) {
578 this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState,
579 "setRemoteDescription not called");
580 return;
581 }
583 if (this.remoteDescription.type != "offer") {
585 this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState,
586 "No outstanding offer");
587 return;
588 }
590 // TODO: Implement provisional answer.
592 this._impl.createAnswer(constraints);
593 },
595 createAnswer: function(onSuccess, onError, constraints, provisional) {
596 if (!constraints) {
597 constraints = {};
598 }
600 this._mustValidateConstraints(constraints, "createAnswer passed invalid constraints");
602 if (!provisional) {
603 provisional = false;
604 }
606 this._queueOrRun({
607 func: this._createAnswer,
608 args: [onSuccess, onError, constraints, provisional],
609 wait: true
610 });
611 },
613 setLocalDescription: function(desc, onSuccess, onError) {
614 let type;
615 switch (desc.type) {
616 case "offer":
617 type = Ci.IPeerConnection.kActionOffer;
618 break;
619 case "answer":
620 type = Ci.IPeerConnection.kActionAnswer;
621 break;
622 case "pranswer":
623 throw new this._win.DOMError("", "pranswer not yet implemented");
624 default:
625 throw new this._win.DOMError("",
626 "Invalid type " + desc.type + " provided to setLocalDescription");
627 }
629 this._queueOrRun({
630 func: this._setLocalDescription,
631 args: [type, desc.sdp, onSuccess, onError],
632 wait: true,
633 type: desc.type
634 });
635 },
637 _setLocalDescription: function(type, sdp, onSuccess, onError) {
638 this._onSetLocalDescriptionSuccess = onSuccess;
639 this._onSetLocalDescriptionFailure = onError;
640 this._impl.setLocalDescription(type, sdp);
641 },
643 setRemoteDescription: function(desc, onSuccess, onError) {
644 let type;
645 switch (desc.type) {
646 case "offer":
647 type = Ci.IPeerConnection.kActionOffer;
648 break;
649 case "answer":
650 type = Ci.IPeerConnection.kActionAnswer;
651 break;
652 case "pranswer":
653 throw new this._win.DOMError("", "pranswer not yet implemented");
654 default:
655 throw new this._win.DOMError("",
656 "Invalid type " + desc.type + " provided to setRemoteDescription");
657 }
659 try {
660 let processIdentity = this._processIdentity.bind(this);
661 this._remoteIdp.verifyIdentityFromSDP(desc.sdp, processIdentity);
662 } catch (e) {
663 this.logWarning(e.message, e.fileName, e.lineNumber);
664 // only happens if processing the SDP for identity doesn't work
665 // let _setRemoteDescription do the error reporting
666 }
668 this._queueOrRun({
669 func: this._setRemoteDescription,
670 args: [type, desc.sdp, onSuccess, onError],
671 wait: true,
672 type: desc.type
673 });
674 },
676 _processIdentity: function(message) {
677 if (message) {
678 this._peerIdentity = new this._win.RTCIdentityAssertion(
679 this._remoteIdp.provider, message.identity);
681 let args = { peerIdentity: this._peerIdentity };
682 this.dispatchEvent(new this._win.Event("peeridentity"));
683 }
684 },
686 _setRemoteDescription: function(type, sdp, onSuccess, onError) {
687 this._onSetRemoteDescriptionSuccess = onSuccess;
688 this._onSetRemoteDescriptionFailure = onError;
689 this._impl.setRemoteDescription(type, sdp);
690 },
692 setIdentityProvider: function(provider, protocol, username) {
693 this._checkClosed();
694 this._localIdp.setIdentityProvider(provider, protocol, username);
695 },
697 _gotIdentityAssertion: function(assertion){
698 let args = { assertion: assertion };
699 let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult", args);
700 this.dispatchEvent(ev);
701 },
703 getIdentityAssertion: function() {
704 this._checkClosed();
706 function gotAssertion(assertion) {
707 if (assertion) {
708 this._gotIdentityAssertion(assertion);
709 }
710 }
712 this._localIdp.getIdentityAssertion(this._impl.fingerprint,
713 gotAssertion.bind(this));
714 },
716 updateIce: function(config, constraints) {
717 throw new this._win.DOMError("", "updateIce not yet implemented");
718 },
720 addIceCandidate: function(cand, onSuccess, onError) {
721 if (!cand.candidate && !cand.sdpMLineIndex) {
722 throw new this._win.DOMError("",
723 "Invalid candidate passed to addIceCandidate!");
724 }
725 this._onAddIceCandidateSuccess = onSuccess || null;
726 this._onAddIceCandidateError = onError || null;
728 this._queueOrRun({ func: this._addIceCandidate, args: [cand], wait: true });
729 },
731 _addIceCandidate: function(cand) {
732 this._impl.addIceCandidate(cand.candidate, cand.sdpMid || "",
733 (cand.sdpMLineIndex === null) ? 0 :
734 cand.sdpMLineIndex + 1);
735 },
737 addStream: function(stream, constraints) {
738 if (!constraints) {
739 constraints = {};
740 }
741 this._mustValidateConstraints(constraints,
742 "addStream passed invalid constraints");
743 if (stream.currentTime === undefined) {
744 throw new this._win.DOMError("", "Invalid stream passed to addStream!");
745 }
746 this._queueOrRun({ func: this._addStream,
747 args: [stream, constraints],
748 wait: false });
749 },
751 _addStream: function(stream, constraints) {
752 this._impl.addStream(stream, constraints);
753 },
755 removeStream: function(stream) {
756 // Bug 844295: Not implementing this functionality.
757 throw new this._win.DOMError("", "removeStream not yet implemented");
758 },
760 getStreamById: function(id) {
761 throw new this._win.DOMError("", "getStreamById not yet implemented");
762 },
764 close: function() {
765 if (this._closed) {
766 return;
767 }
768 this._queueOrRun({ func: this._close, args: [false], wait: false });
769 this._closed = true;
770 this.changeIceConnectionState("closed");
771 },
773 _close: function() {
774 this._localIdp.close();
775 this._remoteIdp.close();
776 this._impl.close();
777 },
779 getLocalStreams: function() {
780 this._checkClosed();
781 return this._impl.getLocalStreams();
782 },
784 getRemoteStreams: function() {
785 this._checkClosed();
786 return this._impl.getRemoteStreams();
787 },
789 get localDescription() {
790 this._checkClosed();
791 let sdp = this._impl.localDescription;
792 if (sdp.length == 0) {
793 return null;
794 }
796 sdp = this._localIdp.wrapSdp(sdp);
797 return new this._win.mozRTCSessionDescription({ type: this._localType,
798 sdp: sdp });
799 },
801 get remoteDescription() {
802 this._checkClosed();
803 let sdp = this._impl.remoteDescription;
804 if (sdp.length == 0) {
805 return null;
806 }
807 return new this._win.mozRTCSessionDescription({ type: this._remoteType,
808 sdp: sdp });
809 },
811 get peerIdentity() { return this._peerIdentity; },
812 get iceGatheringState() { return this._iceGatheringState; },
813 get iceConnectionState() { return this._iceConnectionState; },
815 get signalingState() {
816 // checking for our local pc closed indication
817 // before invoking the pc methods.
818 if (this._closed) {
819 return "closed";
820 }
821 return {
822 "SignalingInvalid": "",
823 "SignalingStable": "stable",
824 "SignalingHaveLocalOffer": "have-local-offer",
825 "SignalingHaveRemoteOffer": "have-remote-offer",
826 "SignalingHaveLocalPranswer": "have-local-pranswer",
827 "SignalingHaveRemotePranswer": "have-remote-pranswer",
828 "SignalingClosed": "closed"
829 }[this._impl.signalingState];
830 },
832 changeIceGatheringState: function(state) {
833 this._iceGatheringState = state;
834 },
836 changeIceConnectionState: function(state) {
837 this._iceConnectionState = state;
838 this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
839 },
841 getStats: function(selector, onSuccess, onError) {
842 this._queueOrRun({
843 func: this._getStats,
844 args: [selector, onSuccess, onError],
845 wait: true
846 });
847 },
849 _getStats: function(selector, onSuccess, onError) {
850 this._onGetStatsSuccess = onSuccess;
851 this._onGetStatsFailure = onError;
853 this._impl.getStats(selector);
854 },
856 createDataChannel: function(label, dict) {
857 this._checkClosed();
858 if (dict == undefined) {
859 dict = {};
860 }
861 if (dict.maxRetransmitNum != undefined) {
862 dict.maxRetransmits = dict.maxRetransmitNum;
863 this.logWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!", null, 0);
864 }
865 if (dict.outOfOrderAllowed != undefined) {
866 dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with
867 // the name change
868 this.logWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!", null, 0);
869 }
870 if (dict.preset != undefined) {
871 dict.negotiated = dict.preset;
872 this.logWarning("Deprecated RTCDataChannelInit dictionary entry preset used!", null, 0);
873 }
874 if (dict.stream != undefined) {
875 dict.id = dict.stream;
876 this.logWarning("Deprecated RTCDataChannelInit dictionary entry stream used!", null, 0);
877 }
879 if (dict.maxRetransmitTime != undefined &&
880 dict.maxRetransmits != undefined) {
881 throw new this._win.DOMError("",
882 "Both maxRetransmitTime and maxRetransmits cannot be provided");
883 }
884 let protocol;
885 if (dict.protocol == undefined) {
886 protocol = "";
887 } else {
888 protocol = dict.protocol;
889 }
891 // Must determine the type where we still know if entries are undefined.
892 let type;
893 if (dict.maxRetransmitTime != undefined) {
894 type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
895 } else if (dict.maxRetransmits != undefined) {
896 type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
897 } else {
898 type = Ci.IPeerConnection.kDataChannelReliable;
899 }
901 // Synchronous since it doesn't block.
902 let channel = this._impl.createDataChannel(
903 label, protocol, type, !dict.ordered, dict.maxRetransmitTime,
904 dict.maxRetransmits, dict.negotiated ? true : false,
905 dict.id != undefined ? dict.id : 0xFFFF
906 );
907 return channel;
908 },
910 connectDataConnection: function(localport, remoteport, numstreams) {
911 if (numstreams == undefined || numstreams <= 0) {
912 numstreams = 16;
913 }
914 this._queueOrRun({
915 func: this._connectDataConnection,
916 args: [localport, remoteport, numstreams],
917 wait: false
918 });
919 },
921 _connectDataConnection: function(localport, remoteport, numstreams) {
922 this._impl.connectDataConnection(localport, remoteport, numstreams);
923 }
924 };
926 function RTCError(code, message) {
927 this.name = this.reasonName[Math.min(code, this.reasonName.length - 1)];
928 this.message = (typeof message === "string")? message : this.name;
929 this.__exposedProps__ = { name: "rw", message: "rw" };
930 }
931 RTCError.prototype = {
932 // These strings must match those defined in the WebRTC spec.
933 reasonName: [
934 "NO_ERROR", // Should never happen -- only used for testing
935 "INVALID_CONSTRAINTS_TYPE",
936 "INVALID_CANDIDATE_TYPE",
937 "INVALID_MEDIASTREAM_TRACK",
938 "INVALID_STATE",
939 "INVALID_SESSION_DESCRIPTION",
940 "INCOMPATIBLE_SESSION_DESCRIPTION",
941 "INCOMPATIBLE_CONSTRAINTS",
942 "INCOMPATIBLE_MEDIASTREAMTRACK",
943 "INTERNAL_ERROR"
944 ]
945 };
947 // This is a separate object because we don't want to expose it to DOM.
948 function PeerConnectionObserver() {
949 this._dompc = null;
950 }
951 PeerConnectionObserver.prototype = {
952 classDescription: "PeerConnectionObserver",
953 classID: PC_OBS_CID,
954 contractID: PC_OBS_CONTRACT,
955 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
956 Ci.nsIDOMGlobalPropertyInitializer]),
957 init: function(win) { this._win = win; },
959 __init: function(dompc) {
960 this._dompc = dompc._innerObject;
961 },
963 dispatchEvent: function(event) {
964 this._dompc.dispatchEvent(event);
965 },
967 callCB: function(callback, arg) {
968 if (callback) {
969 try {
970 callback(arg);
971 } catch(e) {
972 // A content script (user-provided) callback threw an error. We don't
973 // want this to take down peerconnection, but we still want the user
974 // to see it, so we catch it, report it, and move on.
975 this._dompc.logErrorAndCallOnError(e.message,
976 e.fileName,
977 e.lineNumber);
978 }
979 }
980 },
982 onCreateOfferSuccess: function(sdp) {
983 let pc = this._dompc;
984 let fp = pc._impl.fingerprint;
985 pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
986 if (assertion) {
987 pc._gotIdentityAssertion(assertion);
988 }
989 this.callCB(pc._onCreateOfferSuccess,
990 new pc._win.mozRTCSessionDescription({ type: "offer",
991 sdp: sdp }));
992 pc._executeNext();
993 }.bind(this));
994 },
996 onCreateOfferError: function(code, message) {
997 this.callCB(this._dompc._onCreateOfferFailure, new RTCError(code, message));
998 this._dompc._executeNext();
999 },
1001 onCreateAnswerSuccess: function(sdp) {
1002 let pc = this._dompc;
1003 let fp = pc._impl.fingerprint;
1004 pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
1005 if (assertion) {
1006 pc._gotIdentityAssertion(assertion);
1007 }
1008 this.callCB (pc._onCreateAnswerSuccess,
1009 new pc._win.mozRTCSessionDescription({ type: "answer",
1010 sdp: sdp }));
1011 pc._executeNext();
1012 }.bind(this));
1013 },
1015 onCreateAnswerError: function(code, message) {
1016 this.callCB(this._dompc._onCreateAnswerFailure, new RTCError(code, message));
1017 this._dompc._executeNext();
1018 },
1020 onSetLocalDescriptionSuccess: function() {
1021 this._dompc._localType = this._dompc._pendingType;
1022 this._dompc._pendingType = null;
1023 this.callCB(this._dompc._onSetLocalDescriptionSuccess);
1025 if (this._dompc._iceGatheringState == "complete") {
1026 // If we are not trickling or we completed gathering prior
1027 // to setLocal, then trigger a call of onicecandidate here.
1028 this.foundIceCandidate(null);
1029 }
1031 this._dompc._executeNext();
1032 },
1034 onSetRemoteDescriptionSuccess: function() {
1035 this._dompc._remoteType = this._dompc._pendingType;
1036 this._dompc._pendingType = null;
1037 this.callCB(this._dompc._onSetRemoteDescriptionSuccess);
1038 this._dompc._executeNext();
1039 },
1041 onSetLocalDescriptionError: function(code, message) {
1042 this._dompc._pendingType = null;
1043 this.callCB(this._dompc._onSetLocalDescriptionFailure,
1044 new RTCError(code, message));
1045 this._dompc._executeNext();
1046 },
1048 onSetRemoteDescriptionError: function(code, message) {
1049 this._dompc._pendingType = null;
1050 this.callCB(this._dompc._onSetRemoteDescriptionFailure,
1051 new RTCError(code, message));
1052 this._dompc._executeNext();
1053 },
1055 onAddIceCandidateSuccess: function() {
1056 this._dompc._pendingType = null;
1057 this.callCB(this._dompc._onAddIceCandidateSuccess);
1058 this._dompc._executeNext();
1059 },
1061 onAddIceCandidateError: function(code, message) {
1062 this._dompc._pendingType = null;
1063 this.callCB(this._dompc._onAddIceCandidateError, new RTCError(code, message));
1064 this._dompc._executeNext();
1065 },
1067 onIceCandidate: function(level, mid, candidate) {
1068 this.foundIceCandidate(new this._dompc._win.mozRTCIceCandidate(
1069 {
1070 candidate: candidate,
1071 sdpMid: mid,
1072 sdpMLineIndex: level - 1
1073 }
1074 ));
1075 },
1078 // This method is primarily responsible for updating iceConnectionState.
1079 // This state is defined in the WebRTC specification as follows:
1080 //
1081 // iceConnectionState:
1082 // -------------------
1083 // new The ICE Agent is gathering addresses and/or waiting for
1084 // remote candidates to be supplied.
1085 //
1086 // checking The ICE Agent has received remote candidates on at least
1087 // one component, and is checking candidate pairs but has not
1088 // yet found a connection. In addition to checking, it may
1089 // also still be gathering.
1090 //
1091 // connected The ICE Agent has found a usable connection for all
1092 // components but is still checking other candidate pairs to
1093 // see if there is a better connection. It may also still be
1094 // gathering.
1095 //
1096 // completed The ICE Agent has finished gathering and checking and found
1097 // a connection for all components. Open issue: it is not
1098 // clear how the non controlling ICE side knows it is in the
1099 // state.
1100 //
1101 // failed The ICE Agent is finished checking all candidate pairs and
1102 // failed to find a connection for at least one component.
1103 // Connections may have been found for some components.
1104 //
1105 // disconnected Liveness checks have failed for one or more components.
1106 // This is more aggressive than failed, and may trigger
1107 // intermittently (and resolve itself without action) on a
1108 // flaky network.
1109 //
1110 // closed The ICE Agent has shut down and is no longer responding to
1111 // STUN requests.
1113 handleIceConnectionStateChange: function(iceConnectionState) {
1114 var histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE");
1116 if (iceConnectionState === 'failed') {
1117 histogram.add(false);
1118 this._dompc.logError("ICE failed, see about:webrtc for more details", null, 0);
1119 }
1120 if (this._dompc.iceConnectionState === 'checking' &&
1121 (iceConnectionState === 'completed' ||
1122 iceConnectionState === 'connected')) {
1123 histogram.add(true);
1124 }
1125 this._dompc.changeIceConnectionState(iceConnectionState);
1126 },
1128 // This method is responsible for updating iceGatheringState. This
1129 // state is defined in the WebRTC specification as follows:
1130 //
1131 // iceGatheringState:
1132 // ------------------
1133 // new The object was just created, and no networking has occurred
1134 // yet.
1135 //
1136 // gathering The ICE engine is in the process of gathering candidates for
1137 // this RTCPeerConnection.
1138 //
1139 // complete The ICE engine has completed gathering. Events such as adding
1140 // a new interface or a new TURN server will cause the state to
1141 // go back to gathering.
1142 //
1143 handleIceGatheringStateChange: function(gatheringState) {
1144 this._dompc.changeIceGatheringState(gatheringState);
1146 if (gatheringState === "complete") {
1147 if (!this._dompc._trickleIce) {
1148 // If we are not trickling, then the queue is in a pending state
1149 // waiting for ICE gathering and executeNext frees it
1150 this._dompc._executeNext();
1151 }
1152 else if (this._dompc.localDescription) {
1153 // If we are trickling but we have already done setLocal,
1154 // then we need to send a final foundIceCandidate(null) to indicate
1155 // that we are done gathering.
1156 this.foundIceCandidate(null);
1157 }
1158 }
1159 },
1161 onStateChange: function(state) {
1162 switch (state) {
1163 case "SignalingState":
1164 this.callCB(this._dompc.onsignalingstatechange,
1165 this._dompc.signalingState);
1166 break;
1168 case "IceConnectionState":
1169 this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
1170 break;
1172 case "IceGatheringState":
1173 this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState);
1174 break;
1176 case "SdpState":
1177 // No-op
1178 break;
1180 case "ReadyState":
1181 // No-op
1182 break;
1184 case "SipccState":
1185 // No-op
1186 break;
1188 default:
1189 this._dompc.logWarning("Unhandled state type: " + state, null, 0);
1190 break;
1191 }
1192 },
1194 onGetStatsSuccess: function(dict) {
1195 let chromeobj = new RTCStatsReport(this._dompc._win, dict);
1196 let webidlobj = this._dompc._win.RTCStatsReport._create(this._dompc._win,
1197 chromeobj);
1198 chromeobj.makeStatsPublic();
1199 this.callCB(this._dompc._onGetStatsSuccess, webidlobj);
1200 this._dompc._executeNext();
1201 },
1203 onGetStatsError: function(code, message) {
1204 this.callCB(this._dompc._onGetStatsFailure, new RTCError(code, message));
1205 this._dompc._executeNext();
1206 },
1208 onAddStream: function(stream) {
1209 this.dispatchEvent(new this._dompc._win.MediaStreamEvent("addstream",
1210 { stream: stream }));
1211 },
1213 onRemoveStream: function(stream, type) {
1214 this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
1215 { stream: stream }));
1216 },
1218 foundIceCandidate: function(cand) {
1219 this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate",
1220 { candidate: cand } ));
1221 },
1223 notifyDataChannel: function(channel) {
1224 this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
1225 { channel: channel }));
1226 },
1228 notifyConnection: function() {
1229 this.dispatchEvent(new this._dompc._win.Event("connection"));
1230 },
1232 notifyClosedConnection: function() {
1233 this.dispatchEvent(new this._dompc._win.Event("closedconnection"));
1234 },
1236 getSupportedConstraints: function(dict) {
1237 return dict;
1238 },
1239 };
1241 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
1242 [GlobalPCList,
1243 RTCIceCandidate,
1244 RTCSessionDescription,
1245 RTCPeerConnection,
1246 RTCStatsReport,
1247 RTCIdentityAssertion,
1248 PeerConnectionObserver]
1249 );