merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 01 Mar 2016 11:58:50 +0100
changeset 322386 8ef94be995a453f5c464278c53478ba8c8554f81
parent 322336 e153836569007d614aa6b2260f30f0e8994e69e4 (current diff)
parent 322385 aefae03959a33e8e5761e448e7ff6aad0610402e (diff)
child 322542 5cafa6f3019b57c43312a75e7d7d58aeb032f1bc
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
first release with
nightly linux32
8ef94be995a4 / 47.0a1 / 20160301030237 / files
nightly linux64
8ef94be995a4 / 47.0a1 / 20160301030237 / files
nightly mac
8ef94be995a4 / 47.0a1 / 20160301030237 / files
nightly win32
8ef94be995a4 / 47.0a1 / 20160301030237 / files
nightly win64
8ef94be995a4 / 47.0a1 / 20160301030237 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
browser/components/customizableui/test/browser_946320_tabs_from_other_computers.js
browser/components/downloads/content/allDownloadsViewOverlay.css
browser/components/downloads/content/download.css
devtools/client/themes/images/debugger-collapse.png
devtools/client/themes/images/debugger-collapse@2x.png
devtools/client/themes/images/debugger-expand.png
devtools/client/themes/images/debugger-expand@2x.png
mobile/android/base/java/org/mozilla/gecko/db/TopSitesCursorWrapper.java
mobile/android/tests/browser/junit3/src/org/mozilla/tests/browser/junit3/TestTopSitesCursorWrapper.java
toolkit/components/addoncompat/tests/Makefile.in
toolkit/mozapps/extensions/test/browser/Makefile.in
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1323,22 +1323,16 @@ pref("services.sync.prefs.sync.security.
 pref("services.sync.prefs.sync.security.default_personal_cert", true);
 pref("services.sync.prefs.sync.security.tls.version.min", true);
 pref("services.sync.prefs.sync.security.tls.version.max", true);
 pref("services.sync.prefs.sync.services.sync.syncedTabs.showRemoteIcons", true);
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
 pref("services.sync.prefs.sync.spellchecker.dictionary", true);
 pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
 
-#ifdef NIGHTLY_BUILD
-pref("services.sync.syncedTabsUIRefresh", true);
-#else
-pref("services.sync.syncedTabsUIRefresh", false);
-#endif
-
 // A preference that controls whether we should show the icon for a remote tab.
 // This pref has no UI but exists because some people may be concerned that
 // fetching these icons to show remote tabs may leak information about that
 // user's tabs and bookmarks. Note this pref is also synced.
 pref("services.sync.syncedTabs.showRemoteIcons", true);
 
 // Developer edition preferences
 #ifdef MOZ_DEV_EDITION
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -185,17 +185,16 @@
     <broadcaster id="sync-status"/>
     <!-- broadcasters of the "hidden" attribute to reflect setup state for
          menus -->
     <broadcaster id="sync-setup-state"/>
     <broadcaster id="sync-syncnow-state" hidden="true"/>
     <broadcaster id="sync-reauth-state" hidden="true"/>
     <broadcaster id="viewTabsSidebar" autoCheck="false" sidebartitle="&syncedTabs.sidebar.label;"
                  type="checkbox" group="sidebar"
-                 hidden="true"
                  sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
                  oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
     <broadcaster id="workOfflineMenuitemState"/>
     <broadcaster id="socialSidebarBroadcaster" hidden="true"/>
 
     <!-- DevTools broadcasters -->
     <broadcaster id="devtoolsMenuBroadcaster_DevToolbox"
                  label="&devToolboxMenuItem.label;"
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -76,22 +76,16 @@ var gSyncUI = {
     this._obs.forEach(function(topic) {
       Services.obs.addObserver(this, topic, true);
     }, this);
 
     // initial label for the sync buttons.
     let broadcaster = document.getElementById("sync-status");
     broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
 
-    // Initialize the Synced Tabs Sidebar
-    if (Services.prefs.getBoolPref("services.sync.syncedTabsUIRefresh")) {
-      let sidebarBroadcaster = document.getElementById("viewTabsSidebar");
-      sidebarBroadcaster.removeAttribute("hidden");
-    }
-
     this.maybeMoveSyncedTabsButton();
 
     this.updateUI();
   },
 
 
   // Returns a promise that resolves with true if Sync needs to be configured,
   // false otherwise.
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6447,21 +6447,17 @@ function checkEmptyPageOrigin(browser = 
   }
   // ... so for those that don't have them, enforce that the page has the
   // system principal (this matches e.g. on about:newtab).
   let ssm = Services.scriptSecurityManager;
   return ssm.isSystemPrincipal(contentPrincipal);
 }
 
 function BrowserOpenSyncTabs() {
-  if (Services.prefs.getBoolPref("services.sync.syncedTabsUIRefresh")) {
-    gSyncUI.openSyncedTabsPanel();
-  } else {
-    switchToTabHavingURI("about:sync-tabs", true);
-  }
+  gSyncUI.openSyncedTabsPanel();
 }
 
 /**
  * Format a URL
  * eg:
  * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
  * > https://addons.mozilla.org/en-US/firefox/3.0a1/
  *
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -64,16 +64,18 @@
 # All JS files which are not content (only) dependent that browser.xul
 # wishes to include *must* go into the global-scripts.inc file
 # so that they can be shared by macBrowserOverlay.xul.
 #include global-scripts.inc
 <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
 
 <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
 
+<script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
 <script type="application/javascript" src="chrome://browser/content/places/editBookmarkOverlay.js"/>
 
 # All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
 # browser-sets.inc file for sharing with hiddenWindow.xul.
 #define FULL_BROWSER_WINDOW
 #include browser-sets.inc
 #undef FULL_BROWSER_WINDOW
 
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -2,18 +2,16 @@
 # 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/.
 
 <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
 <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
 <script type="application/javascript" src="chrome://browser/content/places/browserPlacesViews.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser.js"/>
-<script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
-<script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
 <script type="application/javascript" src="chrome://browser/content/customizableui/panelUI.js"/>
 <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
 <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
 
 <script type="application/javascript" src="chrome://browser/content/browser-addons.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-ctrlTab.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-customization.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-devedition.js"/>
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -290,17 +290,21 @@ Site.prototype = {
   },
 
   /**
    * Speculatively opens a connection to the current site.
    */
   _speculativeConnect: function Site_speculativeConnect() {
     let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
     let uri = Services.io.newURI(this.url, null, null);
-    sc.speculativeConnect(uri, null);
+    try {
+      // This can throw for certain internal URLs, when they wind up in
+      // about:newtab. Be sure not to propagate the error.
+      sc.speculativeConnect(uri, null);
+    } catch (e) {}
   },
 
   /**
    * Record interaction with site using telemetry.
    */
   _recordSiteClicked: function Site_recordSiteClicked(aIndex) {
     if (Services.prefs.prefHasUserValue("browser.newtabpage.rows") ||
         Services.prefs.prefHasUserValue("browser.newtabpage.columns") ||
--- a/browser/base/content/social-content.js
+++ b/browser/base/content/social-content.js
@@ -60,21 +60,16 @@ SocialErrorListener = {
     addMessageListener("WaitForDOMContentLoaded", this);
     let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                               .getInterface(Components.interfaces.nsIWebProgress);
     webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
                                           Ci.nsIWebProgress.NOTIFY_LOCATION);
   },
 
   receiveMessage(message) {
-    if (!content) {
-      Cu.reportError("Message received whilst `content` is null: " + message.name);
-      return;
-    }
-
     let document = content.document;
 
     switch (message.name) {
       case "Loop:GetAllWebrtcStats":
         content.WebrtcGlobalInformation.getAllStats(allStats => {
           content.WebrtcGlobalInformation.getLogging("", logs => {
             sendAsyncMessage("Loop:GetAllWebrtcStats", {
               allStats: allStats,
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -513,27 +513,25 @@ skip-if = e10s # Bug 1094240 - has findb
 [browser_registerProtocolHandler_notification.js]
 [browser_no_mcb_on_http_site.js]
 tags = mcb
 [browser_bug1104165-switchtab-decodeuri.js]
 [browser_bug1003461-switchtab-override.js]
 [browser_bug1024133-switchtab-override-keynav.js]
 [browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
 [browser_addCertException.js]
-skip-if = e10s # Bug 1100687 - test directly manipulates content (content.document.getElementById)
 [browser_bug1045809.js]
 tags = mcb
 [browser_bug1225194-remotetab.js]
 [browser_e10s_switchbrowser.js]
 [browser_e10s_about_process.js]
 [browser_e10s_chrome_process.js]
 [browser_e10s_javascript.js]
 [browser_blockHPKP.js]
 tags = psm
-skip-if = e10s # bug 1100687 - test directly manipulates content (content.document.getElementById)
 [browser_mcb_redirect.js]
 tags = mcb
 [browser_windowactivation.js]
 [browser_contextmenu_childprocess.js]
 [browser_bug963945.js]
 [browser_readerMode.js]
 support-files =
   readerModeArticle.html
--- a/browser/base/content/test/general/browser_addCertException.js
+++ b/browser/base/content/test/general/browser_addCertException.js
@@ -11,83 +11,60 @@
 // the site, including showing the right identity box and control center icons.
 function test() {
   waitForExplicitFinish();
   whenNewTabLoaded(window, loadBadCertPage);
 }
 
 // Attempt to load https://expired.example.com (which has an expired cert).
 function loadBadCertPage() {
-  gBrowser.addProgressListener(certErrorProgressListener);
-  gBrowser.selectedBrowser.loadURI("https://expired.example.com");
+  Services.obs.addObserver(certExceptionDialogObserver,
+                           "cert-exception-ui-ready", false);
+  let startedLoad = BrowserTestUtils.loadURI(gBrowser.selectedBrowser,
+                                             "https://expired.example.com");
+  startedLoad.then(() => promiseErrorPageLoaded(gBrowser.selectedBrowser)).then(function() {
+    ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+      content.document.getElementById("exceptionDialogButton").click();
+    });
+  });
 }
 
-// The browser should load about:certerror. When This happens, click the
-// button to open the certificate exception dialog.
-var certErrorProgressListener = {
-  buttonClicked: false,
-
-  onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
-    if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
-      let self = this;
-      // Can't directly call button.click() in onStateChange
-      executeSoon(function() {
-        let button = content.document.getElementById("exceptionDialogButton");
-        // If about:certerror hasn't fully loaded, the button won't be present.
-        // It will eventually be there, however.
-        if (button && !self.buttonClicked) {
-          gBrowser.removeProgressListener(self);
-          Services.obs.addObserver(certExceptionDialogObserver,
-                                   "cert-exception-ui-ready", false);
-          button.click();
-        }
-      });
-    }
-  }
-};
-
 // When the certificate exception dialog has opened, click the button to add
 // an exception.
 const EXCEPTION_DIALOG_URI = "chrome://pippki/content/exceptionDialog.xul";
 var certExceptionDialogObserver = {
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "cert-exception-ui-ready") {
       Services.obs.removeObserver(this, "cert-exception-ui-ready");
       let certExceptionDialog = getDialog(EXCEPTION_DIALOG_URI);
       ok(certExceptionDialog, "found exception dialog");
       executeSoon(function() {
-        gBrowser.selectedBrowser.addEventListener("load",
-                                                  successfulLoadListener,
-                                                  true);
+        BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(realPageLoaded);
         certExceptionDialog.documentElement.getButton("extra1").click();
       });
     }
   }
 };
 
 // Finally, we should successfully load https://expired.example.com.
-var successfulLoadListener = {
-  handleEvent: function() {
-    gBrowser.selectedBrowser.removeEventListener("load", this, true);
-    checkControlPanelIcons();
-    let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
-                                .getService(Ci.nsICertOverrideService);
-    certOverrideService.clearValidityOverride("expired.example.com", -1);
-    gBrowser.removeTab(gBrowser.selectedTab);
-    finish();
-  }
+function realPageLoaded() {
+  checkControlPanelIcons();
+  let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+                              .getService(Ci.nsICertOverrideService);
+  certOverrideService.clearValidityOverride("expired.example.com", -1);
+  BrowserTestUtils.removeTab(gBrowser.selectedTab).then(finish);
 };
 
 // Check for the correct icons in the identity box and control center.
 function checkControlPanelIcons() {
   let { gIdentityHandler } = gBrowser.ownerGlobal;
   gIdentityHandler._identityBox.click();
   document.getElementById("identity-popup-security-expander").click();
 
-  is_element_visible(document.getElementById("connection-icon"));
+  is_element_visible(document.getElementById("connection-icon"), "Should see connection icon");
   let connectionIconImage = gBrowser.ownerGlobal
         .getComputedStyle(document.getElementById("connection-icon"), "")
         .getPropertyValue("list-style-image");
   let securityViewBG = gBrowser.ownerGlobal
         .getComputedStyle(document.getElementById("identity-popup-securityView"), "")
         .getPropertyValue("background-image");
   let securityContentBG = gBrowser.ownerGlobal
         .getComputedStyle(document.getElementById("identity-popup-security-content"), "")
--- a/browser/base/content/test/general/browser_blockHPKP.js
+++ b/browser/base/content/test/general/browser_blockHPKP.js
@@ -44,66 +44,58 @@ function test() {
     let uri = gIOService.newURI("https://" + kPinningDomain, null, null);
     gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
   });
   whenNewTabLoaded(window, loadPinningPage);
 }
 
 // Start by making a successful connection to a domain that will pin a site
 function loadPinningPage() {
-  gBrowser.selectedBrowser.addEventListener("load",
-                                             successfulPinningPageListener,
-                                             true);
 
-  gBrowser.selectedBrowser.loadURI("https://" + kPinningDomain + kURLPath + "valid");
+  BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "valid").then(function() {
+    gBrowser.selectedBrowser.addEventListener("load",
+                                               successfulPinningPageListener,
+                                               true);
+  });
 }
 
 // After the site is pinned try to load with a subdomain site that should
 // fail to validate
 var successfulPinningPageListener = {
   handleEvent: function() {
     gBrowser.selectedBrowser.removeEventListener("load", this, true);
-    gBrowser.addProgressListener(certErrorProgressListener);
-    gBrowser.selectedBrowser.loadURI("https://" + kBadPinningDomain);
+    BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() {
+      return promiseErrorPageLoaded(gBrowser.selectedBrowser);
+    }).then(errorPageLoaded);
   }
 };
 
 // The browser should load about:neterror, when this happens, proceed
 // to load the pinning domain again, this time removing the pinning information
-var certErrorProgressListener = {
-  onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
-    if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
-      let textElement = content.document.getElementById("errorShortDescText");
-      let text = textElement.innerHTML;
-      ok(text.indexOf("MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE") > 0,
-         "Got a pinning error page");
-      gBrowser.removeProgressListener(this);
-      gBrowser.selectedBrowser.addEventListener("load",
-                                                successfulPinningRemovalPageListener,
-                                                true);
-      gBrowser.selectedBrowser.loadURI("https://" + kPinningDomain + kURLPath + "zeromaxagevalid");
-    }
-  }
+function errorPageLoaded() {
+  ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+    let textElement = content.document.getElementById("errorShortDescText");
+    let text = textElement.innerHTML;
+    ok(text.indexOf("MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE") > 0,
+       "Got a pinning error page");
+  }).then(function() {
+    BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "zeromaxagevalid").then(function() {
+      return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+    }).then(pinningRemovalLoaded);
+  });
 };
 
 // After the pinning information has been removed (successful load) proceed
 // to load again with the invalid pin domain.
-var successfulPinningRemovalPageListener = {
-  handleEvent: function() {
-    gBrowser.selectedBrowser.removeEventListener("load", this, true);
-    gBrowser.selectedBrowser.addEventListener("load",
-                                              successfulLoadListener,
-                                              true);
-
-    gBrowser.selectedBrowser.loadURI("https://" + kBadPinningDomain);
-  }
+function pinningRemovalLoaded() {
+  BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() {
+    return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+  }).then(badPinningPageLoaded);
 };
 
 // Finally, we should successfully load
 // https://bad.include-subdomains.pinning-dynamic.example.com.
-var successfulLoadListener = {
-  handleEvent: function() {
-    gBrowser.selectedBrowser.removeEventListener("load", this, true);
-    gBrowser.removeTab(gBrowser.selectedTab);
+function badPinningPageLoaded() {
+  BrowserTestUtils.removeTab(gBrowser.selectedTab).then(function() {
     ok(true, "load complete");
     finish();
-  }
+  });
 };
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -4,16 +4,18 @@
 
 const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
 const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PROGRESS_NOTIFICATION = "addon-progress";
 
+const { REQUIRE_SIGNING } = Cu.import("resource://gre/modules/addons/AddonConstants.jsm", {});
+
 var rootDir = getRootDirectory(gTestPath);
 var path = rootDir.split('/');
 var chromeName = path[0] + '//' + path[2];
 var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
 var jar = getJar(croot);
 if (jar) {
   var tmpdir = extractJarToTmp(jar);
   croot = 'file://' + tmpdir.path + '/';
@@ -200,17 +202,17 @@ function test_disabled_install() {
       });
     });
 
     // Click on Enable
     EventUtils.synthesizeMouseAtCenter(notification.button, {});
   });
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "unsigned.xpi"
+    "XPI": "amosigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_blocked_install() {
   // Wait for the blocked notification
   wait_for_notification("addon-install-blocked", function(aPanel) {
@@ -249,17 +251,17 @@ function test_blocked_install() {
 
     // Notification should have changed to progress notification
     ok(PopupNotifications.isPanelOpen, "Notification should still be open");
     notification = aPanel.childNodes[0];
     is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
   });
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "unsigned.xpi"
+    "XPI": "amosigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_whitelisted_install() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
@@ -291,17 +293,17 @@ function test_whitelisted_install() {
       accept_install_dialog();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "unsigned.xpi"
+    "XPI": "amosigned.xpi"
   }));
   let originalTab = gBrowser.selectedTab;
   let tab = gBrowser.addTab();
   gBrowser.selectedTab = tab;
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_failed_download() {
@@ -453,17 +455,17 @@ function test_multiple() {
       accept_install_dialog();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "Unsigned XPI": "unsigned.xpi",
+    "Unsigned XPI": "amosigned.xpi",
     "Restartless XPI": "restartless.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_sequential() {
   // This test is only relevant if using the new doorhanger UI
@@ -542,17 +544,18 @@ function test_sequential() {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_someunverified() {
   // This test is only relevant if using the new doorhanger UI and allowing
   // unsigned add-ons
   if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
-      Preferences.get("xpinstall.signatures.required", true)) {
+      Preferences.get("xpinstall.signatures.required", true) ||
+      REQUIRE_SIGNING) {
     runNextTest();
     return;
   }
 
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
     wait_for_install_dialog(function() {
@@ -588,28 +591,29 @@ function test_someunverified() {
       accept_install_dialog();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "Extension XPI": "restartless.xpi",
+    "Extension XPI": "restartless-unsigned.xpi",
     "Theme XPI": "theme.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_allunverified() {
   // This test is only relevant if using the new doorhanger UI and allowing
   // unsigned add-ons
   if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
-      Preferences.get("xpinstall.signatures.required", true)) {
+      Preferences.get("xpinstall.signatures.required", true) ||
+      REQUIRE_SIGNING) {
     runNextTest();
     return;
   }
 
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
     // Wait for the install confirmation dialog
     wait_for_install_dialog(function() {
@@ -636,17 +640,17 @@ function test_allunverified() {
       accept_install_dialog();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "Extension XPI": "restartless.xpi"
+    "Extension XPI": "restartless-unsigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_url() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
@@ -670,17 +674,17 @@ function test_url() {
       });
 
       accept_install_dialog();
     });
   });
 
   gBrowser.selectedTab = gBrowser.addTab("about:blank");
   BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
-    gBrowser.loadURI(TESTROOT + "unsigned.xpi");
+    gBrowser.loadURI(TESTROOT + "amosigned.xpi");
   });
 },
 
 function test_localfile() {
   // Wait for the install to fail
   Services.obs.addObserver(function() {
     Services.obs.removeObserver(arguments.callee, "addon-install-failed");
 
@@ -732,17 +736,17 @@ function test_tabclose() {
 
         gBrowser.removeTab(gBrowser.selectedTab);
       });
     });
   });
 
   gBrowser.selectedTab = gBrowser.addTab("about:blank");
   BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
-    gBrowser.loadURI(TESTROOT + "unsigned.xpi");
+    gBrowser.loadURI(TESTROOT + "amosigned.xpi");
   });
 },
 
 // Add-ons should be cancelled and the install notification destroyed when
 // navigating to a new origin
 function test_tabnavigate() {
   if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
     runNextTest();
@@ -769,17 +773,17 @@ function test_tabnavigate() {
       gBrowser.loadURI("about:blank");
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "Extension XPI": "unsigned.xpi"
+    "Extension XPI": "amosigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_urlbar() {
   wait_for_notification("addon-install-origin-blocked", function(aPanel) {
     let notification = aPanel.childNodes[0];
@@ -790,17 +794,17 @@ function test_urlbar() {
       runNextTest();
     });
 
     gBrowser.removeCurrentTab();
   });
 
   gBrowser.selectedTab = gBrowser.addTab("about:blank");
   BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
-    gURLBar.value = TESTROOT + "unsigned.xpi";
+    gURLBar.value = TESTROOT + "amosigned.xpi";
     gURLBar.focus();
     EventUtils.synthesizeKey("VK_RETURN", {});
   });
 },
 
 function test_wronghost() {
   gBrowser.selectedTab = gBrowser.addTab();
 
@@ -864,17 +868,17 @@ function test_reload() {
       accept_install_dialog();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "Unsigned XPI": "unsigned.xpi"
+    "Unsigned XPI": "amosigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_theme() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
@@ -946,17 +950,17 @@ function test_renotify_blocked() {
       });
     });
 
     // hide the panel (this simulates the user dismissing it)
     aPanel.hidePopup();
   });
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "unsigned.xpi"
+    "XPI": "amosigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_renotify_installed() {
   // Wait for the progress notification
   wait_for_progress_notification(function(aPanel) {
@@ -1001,17 +1005,17 @@ function test_renotify_installed() {
       accept_install_dialog();
     });
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "unsigned.xpi"
+    "XPI": "amosigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_cancel() {
   function complete_install(callback) {
     let url = TESTROOT + "slowinstall.sjs?continue=true"
@@ -1059,27 +1063,27 @@ function test_cancel() {
     // Cancel the download
     EventUtils.synthesizeMouseAtCenter(button, {});
   });
 
   var pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
 
   var triggers = encodeURIComponent(JSON.stringify({
-    "XPI": "slowinstall.sjs?file=unsigned.xpi"
+    "XPI": "slowinstall.sjs?file=amosigned.xpi"
   }));
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 },
 
 function test_failed_security() {
   Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
 
   setup_redirect({
-    "Location": TESTROOT + "unsigned.xpi"
+    "Location": TESTROOT + "amosigned.xpi"
   });
 
   // Wait for the blocked notification
   wait_for_notification("addon-install-blocked", function(aPanel) {
     let notification = aPanel.childNodes[0];
 
     // Click on Allow
     EventUtils.synthesizeMouse(notification.button, 20, 10, {});
--- a/browser/base/content/test/general/browser_ssl_error_reports.js
+++ b/browser/base/content/test/general/browser_ssl_error_reports.js
@@ -160,24 +160,15 @@ function createReportResponseStatusPromi
         Services.obs.removeObserver(observer, "http-on-examine-response");
         resolve(subject.responseStatus);
       }
     };
     Services.obs.addObserver(observer, "http-on-examine-response", false);
   });
 }
 
-function promiseErrorPageLoaded(browser) {
-  return new Promise(resolve => {
-    browser.addEventListener("DOMContentLoaded", function onLoad() {
-      browser.removeEventListener("DOMContentLoaded", onLoad, false, true);
-      resolve();
-    }, false, true);
-  });
-}
-
 function checkErrorPage(browser, suffix) {
   return ContentTask.spawn(browser, null, function* () {
     return content.document.documentURI;
   }).then(uri => {
     ok(uri.startsWith(`about:${suffix}`), "correct error page loaded");
   });
 }
--- a/browser/base/content/test/general/browser_urlbarCopying.js
+++ b/browser/base/content/test/general/browser_urlbarCopying.js
@@ -72,26 +72,26 @@ var tests = [
   {
     loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass",
     expectedURL: "mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass",
     copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass"
   },
 
   // Test escaping
   {
-    loadURL: "http://example.com/()%C3%A9",
-    expectedURL: "example.com/()\xe9",
-    copyExpected: "http://example.com/%28%29%C3%A9"
+    loadURL: "http://example.com/()%28%29%C3%A9",
+    expectedURL: "example.com/()()\xe9",
+    copyExpected: "http://example.com/()%28%29%C3%A9"
   },
   {
-    copyVal: "<example.com/(>)\xe9",
+    copyVal: "<example.com/(>)()\xe9",
     copyExpected: "http://example.com/("
   },
   {
-    copyVal: "e<xample.com/(>)\xe9",
+    copyVal: "e<xample.com/(>)()\xe9",
     copyExpected: "xample.com/("
   },
 
   {
     loadURL: "http://example.com/%C3%A9%C3%A9",
     expectedURL: "example.com/\xe9\xe9",
     copyExpected: "http://example.com/%C3%A9%C3%A9"
   },
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -953,22 +953,22 @@ function is_visible(element) {
   if (element.parentNode != element.ownerDocument)
     return is_visible(element.parentNode);
 
   return true;
 }
 
 function is_element_visible(element, msg) {
   isnot(element, null, "Element should not be null, when checking visibility");
-  ok(is_visible(element), msg);
+  ok(is_visible(element), msg || "Element should be visible");
 }
 
 function is_element_hidden(element, msg) {
   isnot(element, null, "Element should not be null, when checking visibility");
-  ok(is_hidden(element), msg);
+  ok(is_hidden(element), msg || "Element should be hidden");
 }
 
 function promisePopupEvent(popup, eventSuffix) {
   let endState = {shown: "open", hidden: "closed"}[eventSuffix];
 
   if (popup.state == endState)
     return Promise.resolve();
 
@@ -1210,8 +1210,17 @@ function promiseCrashReport(expectedExtr
         } else {
           is(value, expectedExtra[key],
              `Crash report had the right extra value for ${key}`);
         }
       }
     }
   });
 }
+
+function promiseErrorPageLoaded(browser) {
+  return new Promise(resolve => {
+    browser.addEventListener("DOMContentLoaded", function onLoad() {
+      browser.removeEventListener("DOMContentLoaded", onLoad, false, true);
+      resolve();
+    }, false, true);
+  });
+}
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -596,34 +596,38 @@ file, You can obtain one at http://mozil
             let remainder = inputVal.replace(selectedVal, "");
             if (remainder != "" && remainder[0] != "/")
               return selectedVal;
           }
 
           let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
 
           let uri;
-          try {
-            uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
-          } catch (e) {}
-          if (!uri)
-            return selectedVal;
+          if (this.getAttribute("pageproxystate") == "valid") {
+            uri = gBrowser.currentURI;
+          } else {
+            // We're dealing with an autocompleted value, create a new URI from that.
+            try {
+              uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
+            } catch (e) {}
+            if (!uri)
+              return selectedVal;
+          }
 
           // Only copy exposable URIs
           try {
             uri = uriFixup.createExposableURI(uri);
           } catch (ex) {}
 
           // If the entire URL is selected, just use the actual loaded URI.
           if (inputVal == selectedVal) {
             // ... but only if  isn't a javascript: or data: URI, since those
             // are hard to read when encoded
             if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
-              // Parentheses are known to confuse third-party applications (bug 458565).
-              selectedVal = uri.spec.replace(/[()]/g, c => escape(c));
+              selectedVal = uri.spec;
             }
 
             return selectedVal;
           }
 
           // Just the beginning of the URL is selected, check for a trimmed
           // value
           let spec = uri.spec;
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -26,15 +26,11 @@ pref("app.update.url.details", "https://
 // without checking for an update.  This assumes that
 // app.update.checkInstallTime is true.
 pref("app.update.checkInstallTime.days", 2);
 
 // Give the user x seconds to reboot before showing a badge on the hamburger
 // button. default=4 days
 pref("app.update.badgeWaitTime", 345600);
 
-// code usage depends on contracts, please contact the Firefox module owner if you have questions
-pref("browser.search.param.yahoo-fr", "moz35");
-pref("browser.search.param.yahoo-fr-ja", "mozff");
-
 // Number of usages of the web console or scratchpad.
 // If this is less than 5, then pasting code into the web console or scratchpad is disabled
 pref("devtools.selfxss.count", 5);
--- a/browser/branding/nightly/pref/firefox-branding.js
+++ b/browser/branding/nightly/pref/firefox-branding.js
@@ -24,15 +24,11 @@ pref("app.update.url.details", "https://
 // without checking for an update.  This assumes that
 // app.update.checkInstallTime is true.
 pref("app.update.checkInstallTime.days", 2);
 
 // Give the user x seconds to reboot before showing a badge on the hamburger
 // button. default=immediately
 pref("app.update.badgeWaitTime", 0);
 
-// code usage depends on contracts, please contact the Firefox module owner if you have questions
-pref("browser.search.param.yahoo-fr", "moz35");
-pref("browser.search.param.yahoo-fr-ja", "mozff");
-
 // Number of usages of the web console or scratchpad.
 // If this is less than 5, then pasting code into the web console or scratchpad is disabled
 pref("devtools.selfxss.count", 5);
--- a/browser/branding/official/pref/firefox-branding.js
+++ b/browser/branding/official/pref/firefox-branding.js
@@ -23,15 +23,11 @@ pref("app.update.url.details", "https://
 // without checking for an update.  This assumes that
 // app.update.checkInstallTime is true.
 pref("app.update.checkInstallTime.days", 63);
 
 // Give the user x seconds to reboot before showing a badge on the hamburger
 // button. default=immediately
 pref("app.update.badgeWaitTime", 0);
 
-// code usage depends on contracts, please contact the Firefox module owner if you have questions
-pref("browser.search.param.yahoo-fr", "moz35");
-pref("browser.search.param.yahoo-fr-ja", "mozff");
-
 // Number of usages of the web console or scratchpad.
 // If this is less than 5, then pasting code into the web console or scratchpad is disabled
 pref("devtools.selfxss.count", 0);
--- a/browser/branding/unofficial/pref/firefox-branding.js
+++ b/browser/branding/unofficial/pref/firefox-branding.js
@@ -23,15 +23,11 @@ pref("app.update.url.details", "https://
 // without checking for an update.  This assumes that
 // app.update.checkInstallTime is true.
 pref("app.update.checkInstallTime.days", 2);
 
 // Give the user x seconds to reboot before showing a badge on the hamburger
 // button. default=immediately
 pref("app.update.badgeWaitTime", 0);
 
-// code usage depends on contracts, please contact the Firefox module owner if you have questions
-pref("browser.search.param.yahoo-fr", "moz35");
-pref("browser.search.param.yahoo-fr-ja", "mozff");
-
 // Number of usages of the web console or scratchpad.
 // If this is less than 5, then pasting code into the web console or scratchpad is disabled
 pref("devtools.selfxss.count", 0);
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -241,23 +241,16 @@ const CustomizableWidgets = [
         recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild);
       }
 
       let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
       while (recentlyClosedWindows.firstChild) {
         recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild);
       }
 
-      let tabsFromOtherComputers = doc.getElementById("sync-tabs-menuitem2");
-      if (PlacesUIUtils.shouldShowTabsFromOtherComputersMenuitem()) {
-        tabsFromOtherComputers.removeAttribute("hidden");
-      } else {
-        tabsFromOtherComputers.setAttribute("hidden", true);
-      }
-
       let utils = RecentlyClosedTabsAndWindowsMenuUtils;
       let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true,
                                                "menuRestoreAllTabsSubview.label");
       let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
       let elementCount = tabsFragment.childElementCount;
       separator.hidden = !elementCount;
       while (--elementCount >= 0) {
         tabsFragment.children[elementCount].classList.add("subviewbutton");
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -79,21 +79,16 @@
                        key="key_gotoHistory"
                        oncommand="SidebarUI.toggle('viewHistorySidebar'); PanelUI.hide();">
           <observes element="viewHistorySidebar" attribute="checked"/>
         </toolbarbutton>
         <toolbarbutton id="appMenuClearRecentHistory"
                        label="&appMenuHistory.clearRecent.label;"
                        class="subviewbutton"
                        command="Tools:Sanitize"/>
-        <toolbarbutton id="sync-tabs-menuitem2"
-                       class="syncTabsMenuItem subviewbutton"
-                       label="&syncTabsMenu3.label;"
-                       oncommand="BrowserOpenSyncTabs();"
-                       hidden="true"/>
         <toolbarbutton id="appMenuRestoreLastSession"
                        label="&appMenuHistory.restoreSession.label;"
                        class="subviewbutton"
                        command="Browser:RestoreLastSession"/>
         <menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
         <vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
         <menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
         <vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -46,19 +46,16 @@ skip-if = os == "linux"
 
 # Bug 1163231 - Causes failures on Developer Edition on Windows 7.
 # [browser_932928_show_notice_when_palette_empty.js]
 
 [browser_934113_menubar_removable.js]
 # Because this test is about the menubar, it can't be run on mac
 skip-if = os == "mac"
 
-[browser_946320_tabs_from_other_computers.js]
-skip-if = os == "linux"
-
 [browser_934951_zoom_in_toolbar.js]
 [browser_938980_navbar_collapsed.js]
 [browser_938995_indefaultstate_nonremovable.js]
 [browser_940013_registerToolbarNode_calls_registerArea.js]
 [browser_940307_panel_click_closure_handling.js]
 [browser_940946_removable_from_navbar_customizemode.js]
 [browser_941083_invalidate_wrapper_cache_createWidget.js]
 [browser_942581_unregisterArea_keeps_placements.js]
deleted file mode 100644
--- a/browser/components/customizableui/test/browser_946320_tabs_from_other_computers.js
+++ /dev/null
@@ -1,144 +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 Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
-
-const {FxAccounts, AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
-
-// FxA logs can be gotten at via this pref which helps debugging.
-Preferences.set("services.sync.log.appender.dump", "Debug");
-
-add_task(function*() {
-  yield PanelUI.show({type: "command"});
-
-  let historyButton = document.getElementById("history-panelmenu");
-  let historySubview = document.getElementById("PanelUI-history");
-  let subviewShownPromise = subviewShown(historySubview);
-  historyButton.click();
-  yield subviewShownPromise;
-
-  let tabsFromOtherComputers = document.getElementById("sync-tabs-menuitem2");
-  is(tabsFromOtherComputers.hidden, true, "The Tabs From Other Computers menuitem should be hidden when sync isn't enabled.");
-
-  let hiddenPanelPromise = promisePanelHidden(window);
-  PanelUI.hide();
-  yield hiddenPanelPromise;
-
-  // Part 2 - When Sync is enabled the menuitem should be shown.
-  yield configureIdentity();
-  yield PanelUI.show({type: "command"});
-
-  subviewShownPromise = subviewShown(historySubview);
-  historyButton.click();
-  yield subviewShownPromise;
-
-  is(tabsFromOtherComputers.hidden, false, "The Tabs From Other Computers menuitem should be shown when sync is enabled.");
-
-  let syncPrefBranch = new Preferences("services.sync.");
-  syncPrefBranch.resetBranch("");
-  Services.logins.removeAllLogins();
-
-  hiddenPanelPromise = promisePanelHidden(window);
-  PanelUI.toggle({type: "command"});
-  yield hiddenPanelPromise;
-
-  yield fxAccounts.signOut(/*localOnly = */true);
-});
-
-function configureIdentity() {
-  // do the FxAccounts thang and wait for Sync to initialize the identity.
-  configureFxAccountIdentity();
-  return Weave.Service.identity.initializeWithCurrentIdentity().then(() => {
-    // need to wait until this identity manager is readyToAuthenticate.
-    return Weave.Service.identity.whenReadyToAuthenticate.promise;
-  });
-}
-
-// Configure an instance of an FxAccount identity provider.
-function configureFxAccountIdentity() {
-  // A mock "storage manager" for FxAccounts that doesn't actually write anywhere.
-  function MockFxaStorageManager() {
-  }
-
-  MockFxaStorageManager.prototype = {
-    promiseInitialized: Promise.resolve(),
-
-    initialize(accountData) {
-      this.accountData = accountData;
-    },
-
-    finalize() {
-      return Promise.resolve();
-    },
-
-    getAccountData() {
-      return Promise.resolve(this.accountData);
-    },
-
-    updateAccountData(updatedFields) {
-      for (let [name, value] of Iterator(updatedFields)) {
-        if (value == null) {
-          delete this.accountData[name];
-        } else {
-          this.accountData[name] = value;
-        }
-      }
-      return Promise.resolve();
-    },
-
-    deleteAccountData() {
-      this.accountData = null;
-      return Promise.resolve();
-    }
-  }
-
-  let user = {
-    assertion: "assertion",
-    email: "email",
-    kA: "kA",
-    kB: "kB",
-    sessionToken: "sessionToken",
-    uid: "user_uid",
-    verified: true,
-  };
-
-  let token = {
-    endpoint: null,
-    duration: 300,
-    id: "id",
-    key: "key",
-    // uid will be set to the username.
-  };
-
-  let MockInternal = {
-    newAccountState(credentials) {
-      isnot(credentials, "not expecting credentials");
-      let storageManager = new MockFxaStorageManager();
-      // and init storage with our user.
-      storageManager.initialize(user);
-      return new AccountState(storageManager);
-    },
-    _getAssertion(audience) {
-      return Promise.resolve("assertion");
-    },
-    getCertificateSigned() {
-      return Promise.resolve();
-    },
-  };
-  let mockTSC = { // TokenServerClient
-    getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
-      token.uid = "username";
-      cb(null, token);
-    },
-  };
-
-  let fxa = new FxAccounts(MockInternal);
-  Weave.Service.identity._fxaService = fxa;
-  Weave.Service.identity._tokenServerClient = mockTSC;
-  // Set the "account" of the browserId manager to be the "email" of the
-  // logged in user of the mockFXA service.
-  Weave.Service.identity._account = user.email;
-}
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -346,17 +346,17 @@ DistributionCustomizer.prototype = {
     } catch (e) {
       /* ignore bad prefs due to bug 895473 and move on */
       Cu.reportError(e);
     }
 
     if (sections["Preferences"]) {
       for (let key of enumerate(this._ini.getKeys("Preferences"))) {
         try {
-          let value = eval(this._ini.getString("Preferences", key));
+          let value = parseValue(this._ini.getString("Preferences", key));
           switch (typeof value) {
           case "boolean":
             defaults.setBoolPref(key, value);
             break;
           case "number":
             defaults.setIntPref(key, value);
             break;
           case "string":
@@ -377,49 +377,49 @@ DistributionCustomizer.prototype = {
     let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"].
       createInstance(Ci.nsIPrefLocalizedString);
 
     var usedLocalizablePreferences = [];
 
     if (sections["LocalizablePreferences-" + this._locale]) {
       for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) {
         try {
-          let value = eval(this._ini.getString("LocalizablePreferences-" + this._locale, key));
+          let value = parseValue(this._ini.getString("LocalizablePreferences-" + this._locale, key));
           if (value !== undefined) {
             localizedStr.data = "data:text/plain," + key + "=" + value;
             defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
           }
           usedLocalizablePreferences.push(key);
         } catch (e) { /* ignore bad prefs and move on */ }
       }
     }
 
     if (sections["LocalizablePreferences-" + this._language]) {
       for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._language))) {
         if (usedLocalizablePreferences.indexOf(key) > -1) {
           continue;
         }
         try {
-          let value = eval(this._ini.getString("LocalizablePreferences-" + this._language, key));
+          let value = parseValue(this._ini.getString("LocalizablePreferences-" + this._language, key));
           if (value !== undefined) {
             localizedStr.data = "data:text/plain," + key + "=" + value;
             defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
           }
           usedLocalizablePreferences.push(key);
         } catch (e) { /* ignore bad prefs and move on */ }
       }
     }
 
     if (sections["LocalizablePreferences"]) {
       for (let key of enumerate(this._ini.getKeys("LocalizablePreferences"))) {
         if (usedLocalizablePreferences.indexOf(key) > -1) {
           continue;
         }
         try {
-          let value = eval(this._ini.getString("LocalizablePreferences", key));
+          let value = parseValue(this._ini.getString("LocalizablePreferences", key));
           if (value !== undefined) {
             value = value.replace(/%LOCALE%/g, this._locale);
             value = value.replace(/%LANGUAGE%/g, this._language);
             localizedStr.data = "data:text/plain," + key + "=" + value;
             defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
           }
         } catch (e) { /* ignore bad prefs and move on */ }
       }
@@ -453,16 +453,29 @@ DistributionCustomizer.prototype = {
         prefDefaultsApplied) {
       let os = Cc["@mozilla.org/observer-service;1"].
                getService(Ci.nsIObserverService);
       os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null);
     }
   }
 };
 
+function parseValue(value) {
+  try {
+    value = JSON.parse(value);
+  } catch (e) {
+    // JSON.parse catches numbers and booleans.
+    // Anything else, we assume is a string.
+    // Remove the quotes that aren't needed anymore.
+    value = value.replace(/^"/, "");
+    value = value.replace(/"$/, "");
+  }
+  return value;
+}
+
 function* enumerate(UTF8Enumerator) {
   while (UTF8Enumerator.hasMore())
     yield UTF8Enumerator.getNext();
 }
 
 function enumToObject(UTF8Enumerator) {
   let ret = {};
   for (let i of enumerate(UTF8Enumerator))
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -19,17 +19,25 @@ Cu.import("resource://gre/modules/XPCOMU
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
                                   "resource://gre/modules/DownloadUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
                                   "resource:///modules/DownloadsCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 
-this.DownloadsViewUI = {};
+this.DownloadsViewUI = {
+  /**
+   * Returns true if the given string is the name of a command that can be
+   * handled by the Downloads user interface, including standard commands.
+   */
+  isCommandName(name) {
+    return name.startsWith("cmd_") || name.startsWith("downloadsCmd_");
+  },
+};
 
 /**
  * A download element shell is responsible for handling the commands and the
  * displayed data for a single element that uses the "download.xml" binding.
  *
  * The information to display is obtained through the associated Download object
  * from the JavaScript API for downloads, and commands are executed using a
  * combination of Download methods and DownloadsCommon.jsm helper functions.
@@ -154,17 +162,16 @@ this.DownloadsViewUI.DownloadElementShel
   get statusTextAndTip() {
     return this.rawStatusTextAndTip;
   },
 
   /**
    * Derived objects may call this to get the status text.
    */
   get rawStatusTextAndTip() {
-    const nsIDM = Ci.nsIDownloadManager;
     let s = DownloadsCommon.strings;
 
     let text = "";
     let tip = "";
 
     if (!this.download.stopped) {
       let totalBytes = this.download.hasProgress ? this.download.totalBytes
                                                  : -1;
@@ -226,9 +233,78 @@ this.DownloadsViewUI.DownloadElementShel
 
       let firstPart = s.statusSeparator(stateLabel, displayHost);
       text = s.statusSeparator(firstPart, displayDate);
       tip = s.statusSeparator(fullHost, fullDate);
     }
 
     return { text, tip: tip || text };
   },
+
+  /**
+   * Returns the name of the default command to use for the current state of the
+   * download, when there is a double click or another default interaction. If
+   * there is no default command for the current state, returns an empty string.
+   * The commands are implemented as functions on this object or derived ones.
+   */
+  get currentDefaultCommandName() {
+    switch (DownloadsCommon.stateOfDownload(this.download)) {
+      case Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+        return "downloadsCmd_cancel";
+      case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
+      case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+        return "downloadsCmd_retry";
+      case Ci.nsIDownloadManager.DOWNLOAD_PAUSED:
+        return "downloadsCmd_pauseResume";
+      case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
+        return "downloadsCmd_open";
+      case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+      case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+        return "downloadsCmd_openReferrer";
+    }
+    return "";
+  },
+
+  /**
+   * Returns true if the specified command can be invoked on the current item.
+   * The commands are implemented as functions on this object or derived ones.
+   *
+   * @param aCommand
+   *        Name of the command to check, for example "downloadsCmd_retry".
+   */
+  isCommandEnabled(aCommand) {
+    switch (aCommand) {
+      case "downloadsCmd_retry":
+        return this.download.canceled || this.download.error;
+      case "downloadsCmd_pauseResume":
+        return this.download.hasPartialData && !this.download.error;
+      case "downloadsCmd_openReferrer":
+        return !!this.download.source.referrer;
+      case "downloadsCmd_confirmBlock":
+      case "downloadsCmd_unblock":
+        return this.download.hasBlockedData;
+    }
+    return false;
+  },
+
+  downloadsCmd_cancel() {
+    // This is the correct way to avoid race conditions when cancelling.
+    this.download.cancel().catch(() => {});
+    this.download.removePartialData().catch(Cu.reportError);
+  },
+
+  downloadsCmd_retry() {
+    // Errors when retrying are already reported as download failures.
+    this.download.start().catch(() => {});
+  },
+
+  downloadsCmd_pauseResume() {
+    if (this.download.stopped) {
+      this.download.start();
+    } else {
+      this.download.cancel();
+    }
+  },
+
+  downloadsCmd_confirmBlock() {
+    this.download.confirmBlock().catch(Cu.reportError);
+  },
 };
deleted file mode 100644
--- a/browser/components/downloads/content/allDownloadsViewOverlay.css
+++ /dev/null
@@ -1,53 +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/. */
-
-/**
- * The downloads richlistbox may list thousands of items, and it turns out
- * XBL binding attachment, and even more so detachment, is a performance hog.
- * This hack makes sure we don't apply any binding to inactive items (inactive
- * items are history downloads that haven't been in the visible area).
- * We can do this because the richlistbox implementation does not interact
- * much with the richlistitem binding.  However, this may turn out to have
- * some side effects (see bug 828111 for the details).
- *
- * We might be able to do away with this workaround once bug 653881 is fixed.
- */
-richlistitem.download {
-  -moz-binding: none;
-}
-
-richlistitem.download[active] {
-  -moz-binding: url('chrome://browser/content/downloads/download.xml#download-full-ui');
-}
-
-.download-state:not(          [state="0"]  /* Downloading        */)
-                                           .downloadPauseMenuItem,
-.download-state:not(          [state="4"]  /* Paused             */)
-                                           .downloadResumeMenuItem,
-.download-state:not(:-moz-any([state="2"], /* Failed             */
-                              [state="4"]) /* Paused             */)
-                                           .downloadCancelMenuItem,
-/* Blocked (dirty) downloads that have not been confirmed and
-   have temporary data. */
-.download-state:not(          [state="8"]  /* Blocked (dirty)    */)
-                                           .downloadUnblockMenuItem,
-.download-state[state="8"]:not(.temporary-block)
-                                           .downloadUnblockMenuItem,
-.download-state[state]:not(:-moz-any([state="1"], /* Finished           */
-                                     [state="2"], /* Failed             */
-                                     [state="3"], /* Canceled           */
-                                     [state="6"], /* Blocked (parental) */
-                                     [state="8"], /* Blocked (dirty)    */
-                                     [state="9"]) /* Blocked (policy)   */)
-                                           .downloadRemoveFromHistoryMenuItem,
-.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
-                              [state="0"], /* Downloading        */
-                              [state="1"], /* Finished           */
-                              [state="4"], /* Paused             */
-                              [state="5"]) /* Starting (queued)  */)
-                                           .downloadShowMenuItem,
-.download-state[state="7"]                 .downloadCommandsSeparator
-{
-  display: none;
-}
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -29,22 +29,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 const nsIDM = Ci.nsIDownloadManager;
 
 const DESTINATION_FILE_URI_ANNO  = "downloads/destinationFileURI";
 const DOWNLOAD_META_DATA_ANNO    = "downloads/metaData";
 
-const DOWNLOAD_VIEW_SUPPORTED_COMMANDS =
- ["cmd_delete", "cmd_copy", "cmd_paste", "cmd_selectAll",
-  "downloadsCmd_pauseResume", "downloadsCmd_cancel", "downloadsCmd_unblock",
-  "downloadsCmd_confirmBlock", "downloadsCmd_open", "downloadsCmd_show",
-  "downloadsCmd_retry", "downloadsCmd_openReferrer", "downloadsCmd_clearDownloads"];
-
 /**
  * Represents a download from the browser history. It implements part of the
  * interface of the Download object.
  *
  * @param aPlacesNode
  *        The Places node from which the history download should be initialized.
  */
 function HistoryDownload(aPlacesNode) {
@@ -312,17 +306,16 @@ HistoryDownloadElementShell.prototype = 
     // This cannot be placed within onStateChanged because
     // when a download goes from hasBlockedData to !hasBlockedData
     // it will still remain in the same state.
     this.element.classList.toggle("temporary-block",
                                   !!this.download.hasBlockedData);
     this._updateProgress();
   },
 
-  /* nsIController */
   isCommandEnabled(aCommand) {
     // The only valid command for inactive elements is cmd_delete.
     if (!this.active && aCommand != "cmd_delete") {
       return false;
     }
     switch (aCommand) {
       case "downloadsCmd_open":
         // This property is false if the download did not succeed.
@@ -333,94 +326,63 @@ HistoryDownloadElementShell.prototype = 
           let partFile = new FileUtils.File(this.download.target.partFilePath);
           if (partFile.exists()) {
             return true;
           }
         }
 
         // This property is false if the download did not succeed.
         return this.download.target.exists;
-      case "downloadsCmd_pauseResume":
-        return this.download.hasPartialData && !this.download.error;
-      case "downloadsCmd_retry":
-        return this.download.canceled || this.download.error;
-      case "downloadsCmd_openReferrer":
-        return !!this.download.source.referrer;
       case "cmd_delete":
         // We don't want in-progress downloads to be removed accidentally.
         return this.download.stopped;
       case "downloadsCmd_cancel":
         return !!this._sessionDownload;
-      case "downloadsCmd_confirmBlock":
-      case "downloadsCmd_unblock":
-        return this.download.hasBlockedData;
+    }
+    return DownloadsViewUI.DownloadElementShell.prototype
+                          .isCommandEnabled.call(this, aCommand);
+  },
+
+  doCommand(aCommand) {
+    if (DownloadsViewUI.isCommandName(aCommand)) {
+      this[aCommand]();
     }
-    return false;
+  },
+
+  downloadsCmd_open() {
+    let file = new FileUtils.File(this.download.target.path);
+    DownloadsCommon.openDownloadedFile(file, null, window);
+  },
+
+  downloadsCmd_show() {
+    let file = new FileUtils.File(this.download.target.path);
+    DownloadsCommon.showDownloadedFile(file);
   },
 
-  /* nsIController */
-  doCommand(aCommand) {
-    switch (aCommand) {
-      case "downloadsCmd_open": {
-        let file = new FileUtils.File(this.download.target.path);
-        DownloadsCommon.openDownloadedFile(file, null, window);
-        break;
-      }
-      case "downloadsCmd_show": {
-        let file = new FileUtils.File(this.download.target.path);
-        DownloadsCommon.showDownloadedFile(file);
-        break;
-      }
-      case "downloadsCmd_openReferrer": {
-        openURL(this.download.source.referrer);
-        break;
-      }
-      case "downloadsCmd_cancel": {
-        this.download.cancel().catch(() => {});
-        this.download.removePartialData().catch(Cu.reportError);
-        break;
+  downloadsCmd_openReferrer() {
+    openURL(this.download.source.referrer);
+  },
+
+  cmd_delete() {
+    if (this._sessionDownload) {
+      DownloadsCommon.removeAndFinalizeDownload(this.download);
+    }
+    if (this._historyDownload) {
+      let uri = NetUtil.newURI(this.download.source.url);
+      PlacesUtils.bhistory.removePage(uri);
+    }
+  },
+
+  downloadsCmd_unblock() {
+    DownloadsCommon.confirmUnblockDownload(DownloadsCommon.BLOCK_VERDICT_MALWARE,
+                                           window).then((confirmed) => {
+      if (confirmed) {
+        return this.download.unblock();
       }
-      case "cmd_delete": {
-        if (this._sessionDownload) {
-          DownloadsCommon.removeAndFinalizeDownload(this.download);
-        }
-        if (this._historyDownload) {
-          let uri = NetUtil.newURI(this.download.source.url);
-          PlacesUtils.bhistory.removePage(uri);
-        }
-        break;
-      }
-      case "downloadsCmd_retry": {
-        // Errors when retrying are already reported as download failures.
-        this.download.start().catch(() => {});
-        break;
-      }
-      case "downloadsCmd_pauseResume": {
-        // This command is only enabled for session downloads.
-        if (this.download.stopped) {
-          this.download.start();
-        } else {
-          this.download.cancel();
-        }
-        break;
-      }
-      case "downloadsCmd_unblock": {
-        DownloadsCommon.confirmUnblockDownload(DownloadsCommon.BLOCK_VERDICT_MALWARE,
-                                               window).then((confirmed) => {
-          if (confirmed) {
-            return this.download.unblock();
-          }
-        }).catch(Cu.reportError);
-        break;
-      }
-      case "downloadsCmd_confirmBlock": {
-        this.download.confirmBlock().catch(Cu.reportError);
-        break;
-      }
-    }
+    }).catch(Cu.reportError);
   },
 
   // Returns whether or not the download handled by this shell should
   // show up in the search results for the given term.  Both the display
   // name for the download and the url are searched.
   matchesSearchTerm(aTerm) {
     if (!aTerm) {
       return true;
@@ -428,39 +390,17 @@ HistoryDownloadElementShell.prototype = 
     aTerm = aTerm.toLowerCase();
     return this.displayName.toLowerCase().includes(aTerm) ||
            this.download.source.url.toLowerCase().includes(aTerm);
   },
 
   // Handles return keypress on the element (the keypress listener is
   // set in the DownloadsPlacesView object).
   doDefaultCommand() {
-    function getDefaultCommandForState(aState) {
-      switch (aState) {
-        case nsIDM.DOWNLOAD_FINISHED:
-          return "downloadsCmd_open";
-        case nsIDM.DOWNLOAD_PAUSED:
-          return "downloadsCmd_pauseResume";
-        case nsIDM.DOWNLOAD_NOTSTARTED:
-        case nsIDM.DOWNLOAD_QUEUED:
-          return "downloadsCmd_cancel";
-        case nsIDM.DOWNLOAD_FAILED:
-        case nsIDM.DOWNLOAD_CANCELED:
-          return "downloadsCmd_retry";
-        case nsIDM.DOWNLOAD_SCANNING:
-          return "downloadsCmd_show";
-        case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
-        case nsIDM.DOWNLOAD_DIRTY:
-        case nsIDM.DOWNLOAD_BLOCKED_POLICY:
-          return "downloadsCmd_openReferrer";
-      }
-      return "";
-    }
-    let state = DownloadsCommon.stateOfDownload(this.download);
-    let command = getDefaultCommandForState(state);
+    let command = this.currentDefaultCommandName;
     if (command && this.isCommandEnabled(command)) {
       this.doCommand(command);
     }
   },
 
   /**
    * This method is called by the outer download view, after the controller
    * commands have already been updated. In case we did not check for the
@@ -500,16 +440,27 @@ HistoryDownloadElementShell.prototype = 
 
     // Ensure the interface has been updated based on the new values. We need to
     // do this because history downloads can't trigger update notifications.
     this._updateProgress();
   }),
 };
 
 /**
+ * Relays commands from the download.xml binding to the selected items.
+ */
+const DownloadsView = {
+  onDownloadCommand(event, command) {
+    goDoCommand(command);
+  },
+
+  onDownloadClick() {},
+};
+
+/**
  * A Downloads Places View is a places view designed to show a places query
  * for history downloads alongside the session downloads.
  *
  * As we don't use the places controller, some methods implemented by other
  * places views are not implemented by this view.
  *
  * A richlistitem in this view can represent either a past download or a session
  * download, or both. Session downloads are shown first in the view, and as long
@@ -1170,34 +1121,38 @@ DownloadsPlacesView.prototype = {
   onDownloadChanged(download) {
     this._viewItemsForDownloads.get(download).onChanged();
   },
 
   onDownloadRemoved(download) {
     this._removeSessionDownloadFromView(download);
   },
 
+  // nsIController
   supportsCommand(aCommand) {
-    if (DOWNLOAD_VIEW_SUPPORTED_COMMANDS.indexOf(aCommand) != -1) {
-      // The clear-downloads command may be performed by the toolbar-button,
-      // which can be focused on OS X.  Thus enable this command even if the
-      // richlistbox is not focused.
-      // For other commands, be prudent and disable them unless the richlistview
-      // is focused. It's important to make the decision here rather than in
-      // isCommandEnabled.  Otherwise our controller may "steal" commands from
-      // other controls in the window (see goUpdateCommand &
-      // getControllerForCommand).
-      if (document.activeElement == this._richlistbox ||
-          aCommand == "downloadsCmd_clearDownloads") {
-        return true;
-      }
+    // Firstly, determine if this is a command that we can handle.
+    if (!aCommand.startsWith("cmd_") &&
+        !aCommand.startsWith("downloadsCmd_")) {
+      return false;
+    }
+    if (!(aCommand in this) &&
+        !(aCommand in HistoryDownloadElementShell.prototype)) {
+      return false;
     }
-    return false;
+    // If this function returns true, other controllers won't get a chance to
+    // process the command even if isCommandEnabled returns false, so it's
+    // important to check if the list is focused here to handle common commands
+    // like copy and paste correctly. The clear downloads command, instead, is
+    // specific to the downloads list but can be invoked from the toolbar, so we
+    // can just return true unconditionally.
+    return aCommand == "downloadsCmd_clearDownloads" ||
+           document.activeElement == this._richlistbox;
   },
 
+  // nsIController
   isCommandEnabled(aCommand) {
     switch (aCommand) {
       case "cmd_copy":
         return this._richlistbox.selectedItems.length > 0;
       case "cmd_selectAll":
         return true;
       case "cmd_paste":
         return this._canDownloadClipboardURL();
@@ -1264,53 +1219,61 @@ DownloadsPlacesView.prototype = {
 
   _downloadURLFromClipboard() {
     let [url, name] = this._getURLFromClipboardData();
     let browserWin = RecentWindow.getMostRecentBrowserWindow();
     let initiatingDoc = browserWin ? browserWin.document : document;
     DownloadURL(url, name, initiatingDoc);
   },
 
+  // nsIController
   doCommand(aCommand) {
-    switch (aCommand) {
-      case "cmd_copy":
-        this._copySelectedDownloadsToClipboard();
-        break;
-      case "cmd_selectAll":
-        this._richlistbox.selectAll();
-        break;
-      case "cmd_paste":
-        this._downloadURLFromClipboard();
-        break;
-      case "downloadsCmd_clearDownloads":
-        this._downloadsData.removeFinished();
-        if (this.result) {
-          Cc["@mozilla.org/browser/download-history;1"]
-            .getService(Ci.nsIDownloadHistory)
-            .removeAllDownloads();
-        }
-        // There may be no selection or focus change as a result
-        // of these change, and we want the command updated immediately.
-        goUpdateCommand("downloadsCmd_clearDownloads");
-        break;
-      default: {
-        // Cloning the nodelist into an array to get a frozen list of selected items.
-        // Otherwise, the selectedItems nodelist is live and doCommand may alter the
-        // selection while we are trying to do one particular action, like removing
-        // items from history.
-        let selectedElements = [... this._richlistbox.selectedItems];
-        for (let element of selectedElements) {
-          element._shell.doCommand(aCommand);
-        }
-      }
+    // If this command is not selection-specific, execute it.
+    if (aCommand in this) {
+      this[aCommand]();
+      return;
+    }
+
+    // Cloning the nodelist into an array to get a frozen list of selected items.
+    // Otherwise, the selectedItems nodelist is live and doCommand may alter the
+    // selection while we are trying to do one particular action, like removing
+    // items from history.
+    let selectedElements = [...this._richlistbox.selectedItems];
+    for (let element of selectedElements) {
+      element._shell.doCommand(aCommand);
     }
   },
 
+  // nsIController
   onEvent() {},
 
+  cmd_copy() {
+    this._copySelectedDownloadsToClipboard();
+  },
+
+  cmd_selectAll() {
+    this._richlistbox.selectAll();
+  },
+
+  cmd_paste() {
+    this._downloadURLFromClipboard();
+  },
+
+  downloadsCmd_clearDownloads() {
+    this._downloadsData.removeFinished();
+    if (this.result) {
+      Cc["@mozilla.org/browser/download-history;1"]
+        .getService(Ci.nsIDownloadHistory)
+        .removeAllDownloads();
+    }
+    // There may be no selection or focus change as a result
+    // of these change, and we want the command updated immediately.
+    goUpdateCommand("downloadsCmd_clearDownloads");
+  },
+
   onContextMenu(aEvent) {
     let element = this._richlistbox.selectedItem;
     if (!element || !element._shell) {
       return false;
     }
 
     // Set the state attribute so that only the appropriate items are displayed.
     let contextMenu = document.getElementById("downloadsContextMenu");
@@ -1441,12 +1404,18 @@ DownloadsPlacesView.prototype = {
 for (let methodName of ["load", "applyFilter", "selectNode", "selectItems"]) {
   DownloadsPlacesView.prototype[methodName] = function () {
     throw new Error("|" + methodName +
                     "| is not implemented by the downloads view.");
   }
 }
 
 function goUpdateDownloadCommands() {
-  for (let command of DOWNLOAD_VIEW_SUPPORTED_COMMANDS) {
-    goUpdateCommand(command);
+  function updateCommandsForObject(object) {
+    for (let name in object) {
+      if (name.startsWith("cmd_") || name.startsWith("downloadsCmd_")) {
+        goUpdateCommand(name);
+      }
+    }
   }
+  updateCommandsForObject(this);
+  updateCommandsForObject(HistoryDownloadElementShell.prototype);
 }
--- a/browser/components/downloads/content/allDownloadsViewOverlay.xul
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.xul
@@ -1,15 +1,15 @@
 <?xml version="1.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/.
 
-<?xml-stylesheet href="chrome://browser/content/downloads/allDownloadsViewOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
 <?xml-stylesheet href="chrome://browser/skin/downloads/allDownloadsViewOverlay.css"?>
 
 <!DOCTYPE overlay [
 <!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
 %downloadsDTD;
 ]>
 
 <!-- This overlay provides a downloads view that lists both session downloads,
deleted file mode 100644
--- a/browser/components/downloads/content/download.css
+++ /dev/null
@@ -1,52 +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/. */
-
-richlistitem.download button {
-  /* These buttons should never get focus, as that would "disable"
-     the downloads view controller (it's only used when the richlistbox
-     is focused). */
-  -moz-user-focus: none;
-}
-
-/*** Visibility of controls inside download items ***/
-
-.download-state:-moz-any(     [state="6"], /* Blocked (parental) */
-                              [state="8"], /* Blocked (dirty)    */
-                              [state="9"]) /* Blocked (policy)   */
-                                           > .downloadTypeIcon:not(.blockedIcon),
-
-.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
-                              [state="8"], /* Blocked (dirty)    */
-                              [state="9"]) /* Blocked (policy)   */)
-                                           > .downloadTypeIcon.blockedIcon,
-
-.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
-                              [state="5"], /* Starting (queued)  */
-                              [state="0"], /* Downloading        */
-                              [state="4"], /* Paused             */
-                              [state="7"]) /* Scanning           */)
-                                           > vbox > .downloadProgress,
-
-.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
-                              [state="5"], /* Starting (queued)  */
-                              [state="0"], /* Downloading        */
-                              [state="4"]) /* Paused             */)
-                                           > .downloadCancel,
-
-/* Blocked (dirty) downloads that have not been confirmed and
-   have temporary data. */
-.download-state:not(          [state="8"])
-                                           > .downloadConfirmBlock,
-.download-state[state="8"]:not(.temporary-block)
-                                           > .downloadConfirmBlock,
-
-.download-state[state]:not(:-moz-any([state="2"], /* Failed             */
-                                     [state="3"]) /* Canceled           */)
-                                                  > .downloadRetry,
-
-.download-state:not(          [state="1"]  /* Finished           */)
-                                           > .downloadShow
-{
-  display: none;
-}
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -62,61 +62,16 @@
                     oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
         <xul:button class="downloadButton downloadConfirmBlock"
                     tooltiptext="&cmd.removeFile.label;"
                     oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_confirmBlock');"/>
       </xul:stack>
     </content>
   </binding>
 
-  <binding id="download-full-ui"
-           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <resources>
-      <stylesheet src="chrome://browser/content/downloads/download.css"/>
-    </resources>
-
-    <content orient="horizontal" align="center">
-      <xul:image class="downloadTypeIcon"
-                 validate="always"
-                 xbl:inherits="src=image"/>
-      <xul:image class="downloadTypeIcon blockedIcon"/>
-      <xul:vbox pack="center" flex="1">
-        <xul:description class="downloadTarget"
-                         crop="center"
-                         xbl:inherits="value=displayName,tooltiptext=displayName"/>
-        <xul:progressmeter anonid="progressmeter"
-                           class="downloadProgress"
-                           min="0"
-                           max="100"
-                           xbl:inherits="mode=progressmode,value=progress"/>
-        <xul:description class="downloadDetails"
-                         style="width: &downloadDetails.width;"
-                         crop="end"
-                         xbl:inherits="value=status,tooltiptext=statusTip"/>
-      </xul:vbox>
-
-      <xul:button class="downloadButton downloadCancel"
-                  tooltiptext="&cmd.cancel.label;"
-                  oncommand="goDoCommand('downloadsCmd_cancel')"/>
-      <xul:button class="downloadButton downloadRetry"
-                  tooltiptext="&cmd.retry.label;"
-                  oncommand="goDoCommand('downloadsCmd_retry')"/>
-      <xul:button class="downloadButton downloadShow"
-#ifdef XP_MACOSX
-                  tooltiptext="&cmd.showMac.label;"
-#else
-                  tooltiptext="&cmd.show.label;"
-#endif
-                  oncommand="goDoCommand('downloadsCmd_show')"/>
-      <xul:button class="downloadButton downloadConfirmBlock"
-                  tooltiptext="&cmd.removeFile.label;"
-                  oncommand="goDoCommand('downloadsCmd_confirmBlock');"/>
-    </content>
-  </binding>
-
   <binding id="download-toolbarbutton"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children />
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,wrap"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
--- a/browser/components/downloads/content/downloads.css
+++ b/browser/components/downloads/content/downloads.css
@@ -1,23 +1,62 @@
 /* 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/. */
 
-/*** Download items ***/
+/*** Downloads Panel ***/
 
 richlistitem[type="download"] {
   -moz-binding: url('chrome://browser/content/downloads/download.xml#download');
 }
 
 richlistitem[type="download"]:not([selected]) button {
   /* Only focus buttons in the selected item. */
   -moz-user-focus: none;
 }
 
+richlistitem[type="download"].download-state[state="1"]:not([exists]) .downloadShow {
+  display: none;
+}
+
+#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
+#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
+#downloadsFooter[showingsummary] > #downloadsHistory,
+#downloadsFooter:not([showingsummary]) > #downloadsSummary {
+  display: none;
+}
+
+/*** Downloads View ***/
+
+/**
+ * The downloads richlistbox may list thousands of items, and it turns out
+ * XBL binding attachment, and even more so detachment, is a performance hog.
+ * This hack makes sure we don't apply any binding to inactive items (inactive
+ * items are history downloads that haven't been in the visible area).
+ * We can do this because the richlistbox implementation does not interact
+ * much with the richlistitem binding.  However, this may turn out to have
+ * some side effects (see bug 828111 for the details).
+ *
+ * We might be able to do away with this workaround once bug 653881 is fixed.
+ */
+richlistitem.download {
+  -moz-binding: none;
+}
+
+richlistitem.download[active] {
+  -moz-binding: url("chrome://browser/content/downloads/download.xml#download");
+}
+
+richlistitem.download button {
+  /* These buttons should never get focus, as that would "disable"
+     the downloads view controller (it's only used when the richlistbox
+     is focused). */
+  -moz-user-focus: none;
+}
+
 /*** Visibility of controls inside download items ***/
 
 .download-state:-moz-any(     [state="6"], /* Blocked (parental) */
                               [state="8"], /* Blocked (dirty)    */
                               [state="9"]) /* Blocked (policy)   */
                                            .downloadTypeIcon:not(.blockedIcon),
 
 .download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
@@ -90,21 +129,8 @@ richlistitem[type="download"]:not([selec
                                            .downloadRetry,
 
 .download-state:not(          [state="1"]  /* Finished           */)
                                            .downloadShow
 
 {
   visibility: hidden;
 }
-
-.download-state[state="1"]:not([exists]) .downloadShow
-{
-  display: none;
-}
-
-#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
-#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
-#downloadsFooter[showingsummary] > #downloadsHistory,
-#downloadsFooter:not([showingsummary]) > #downloadsSummary
-{
-  display: none;
-}
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -18,26 +18,23 @@
  *
  * DownloadsView
  * Builds and updates the downloads list widget, responding to changes in the
  * download state and real-time data.  In addition, handles part of the user
  * interaction events raised by the downloads list widget.
  *
  * DownloadsViewItem
  * Builds and updates a single item in the downloads list widget, responding to
- * changes in the download state and real-time data.
+ * changes in the download state and real-time data, and handles the user
+ * interaction events related to a single item in the downloads list widgets.
  *
  * DownloadsViewController
  * Handles part of the user interaction events raised by the downloads list
  * widget, in particular the "commands" that apply to multiple items, and
  * dispatches the commands that apply to individual items.
- *
- * DownloadsViewItemController
- * Handles all the user interaction events, in particular the "commands",
- * related to a single item in the downloads list widgets.
  */
 
 /**
  * A few words on focus and focusrings
  *
  * We do quite a few hacks in the Downloads Panel for focusrings. In fact, we
  * basically suppress most if not all XUL-level focusrings, and style/draw
  * them ourselves (using :focus instead of -moz-focusring). There are a few
@@ -841,38 +838,37 @@ const DownloadsView = {
       }
     }
 
     this._itemCountChanged();
   },
 
   /**
    * Associates each richlistitem for a download with its corresponding
-   * DownloadsViewItemController object.
+   * DownloadsViewItem object.
    */
-  _controllersForElements: new Map(),
+  _itemsForElements: new Map(),
 
-  controllerForElement(element) {
-    return this._controllersForElements.get(element);
+  itemForElement(element) {
+    return this._itemsForElements.get(element);
   },
 
   /**
    * Creates a new view item associated with the specified data item, and adds
    * it to the top or the bottom of the list.
    */
   _addViewItem(download, aNewest)
   {
     DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
                         "aNewest =", aNewest);
 
     let element = document.createElement("richlistitem");
     let viewItem = new DownloadsViewItem(download, element);
     this._visibleViewItems.set(download, viewItem);
-    let viewItemController = new DownloadsViewItemController(download);
-    this._controllersForElements.set(element, viewItemController);
+    this._itemsForElements.set(element, viewItem);
     if (aNewest) {
       this.richListBox.insertBefore(element, this.richListBox.firstChild);
     } else {
       this.richListBox.appendChild(element);
     }
   },
 
   /**
@@ -883,17 +879,17 @@ const DownloadsView = {
     let element = this._visibleViewItems.get(download).element;
     let previousSelectedIndex = this.richListBox.selectedIndex;
     this.richListBox.removeChild(element);
     if (previousSelectedIndex != -1) {
       this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
                                                 this.richListBox.itemCount - 1);
     }
     this._visibleViewItems.delete(download);
-    this._controllersForElements.delete(element);
+    this._itemsForElements.delete(element);
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// User interface event functions
 
   /**
    * Helper function to do commands on a specific download item.
    *
@@ -904,17 +900,17 @@ const DownloadsView = {
    * @param aCommand
    *        The command to be performed.
    */
   onDownloadCommand(aEvent, aCommand) {
     let target = aEvent.target;
     while (target.nodeName != "richlistitem") {
       target = target.parentNode;
     }
-    DownloadsView.controllerForElement(target).doCommand(aCommand);
+    DownloadsView.itemForElement(target).doCommand(aCommand);
   },
 
   onDownloadClick(aEvent) {
     // Handle primary clicks only, and exclude the action button.
     if (aEvent.button == 0 &&
         !aEvent.originalTarget.hasAttribute("oncommand")) {
       goDoCommand("downloadsCmd_open");
     }
@@ -981,17 +977,17 @@ const DownloadsView = {
 
   onDownloadDragStart(aEvent) {
     let element = this.richListBox.selectedItem;
     if (!element) {
       return;
     }
 
     // We must check for existence synchronously because this is a DOM event.
-    let file = new FileUtils.File(DownloadsView.controllerForElement(element)
+    let file = new FileUtils.File(DownloadsView.itemForElement(element)
                                                .download.target.path);
     if (!file.exists()) {
       return;
     }
 
     let dataTransfer = aEvent.dataTransfer;
     dataTransfer.mozSetDataAt("application/x-moz-file", file, 0);
     dataTransfer.effectAllowed = "copyMove";
@@ -1006,17 +1002,18 @@ const DownloadsView = {
 
 XPCOMUtils.defineConstant(this, "DownloadsView", DownloadsView);
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsViewItem
 
 /**
  * Builds and updates a single item in the downloads list widget, responding to
- * changes in the download state and real-time data.
+ * changes in the download state and real-time data, and handles the user
+ * interaction events related to a single item in the downloads list widgets.
  *
  * @param download
  *        Download object to be associated with the view item.
  * @param aElement
  *        XUL element corresponding to the single download item in the view.
  */
 function DownloadsViewItem(download, aElement) {
   this.download = download;
@@ -1046,16 +1043,114 @@ DownloadsViewItem.prototype = {
   onChanged() {
     // This cannot be placed within onStateChanged because
     // when a download goes from hasBlockedData to !hasBlockedData
     // it will still remain in the same state.
     this.element.classList.toggle("temporary-block",
                                   !!this.download.hasBlockedData);
     this._updateProgress();
   },
+
+  isCommandEnabled(aCommand) {
+    switch (aCommand) {
+      case "downloadsCmd_open": {
+        if (!this.download.succeeded) {
+          return false;
+        }
+
+        let file = new FileUtils.File(this.download.target.path);
+        return file.exists();
+      }
+      case "downloadsCmd_show": {
+        let file = new FileUtils.File(this.download.target.path);
+        if (file.exists()) {
+          return true;
+        }
+
+        if (!this.download.target.partFilePath) {
+          return false;
+        }
+
+        let partFile = new FileUtils.File(this.download.target.partFilePath);
+        return partFile.exists();
+      }
+      case "cmd_delete":
+      case "downloadsCmd_cancel":
+      case "downloadsCmd_copyLocation":
+      case "downloadsCmd_doDefault":
+        return true;
+    }
+    return DownloadsViewUI.DownloadElementShell.prototype
+                          .isCommandEnabled.call(this, aCommand);
+  },
+
+  doCommand(aCommand) {
+    if (this.isCommandEnabled(aCommand)) {
+      this[aCommand]();
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Item commands
+
+  cmd_delete() {
+    DownloadsCommon.removeAndFinalizeDownload(this.download);
+    PlacesUtils.bhistory.removePage(
+                           NetUtil.newURI(this.download.source.url));
+  },
+
+  downloadsCmd_unblock() {
+    DownloadsPanel.hidePanel();
+    DownloadsCommon.confirmUnblockDownload(DownloadsCommon.BLOCK_VERDICT_MALWARE,
+                                           window).then((confirmed) => {
+      if (confirmed) {
+        return this.download.unblock();
+      }
+    }).catch(Cu.reportError);
+  },
+
+  downloadsCmd_open() {
+    this.download.launch().catch(Cu.reportError);
+
+    // We explicitly close the panel here to give the user the feedback that
+    // their click has been received, and we're handling the action.
+    // Otherwise, we'd have to wait for the file-type handler to execute
+    // before the panel would close. This also helps to prevent the user from
+    // accidentally opening a file several times.
+    DownloadsPanel.hidePanel();
+  },
+
+  downloadsCmd_show() {
+    let file = new FileUtils.File(this.download.target.path);
+    DownloadsCommon.showDownloadedFile(file);
+
+    // We explicitly close the panel here to give the user the feedback that
+    // their click has been received, and we're handling the action.
+    // Otherwise, we'd have to wait for the operating system file manager
+    // window to open before the panel closed. This also helps to prevent the
+    // user from opening the containing folder several times.
+    DownloadsPanel.hidePanel();
+  },
+
+  downloadsCmd_openReferrer() {
+    openURL(this.download.source.referrer);
+  },
+
+  downloadsCmd_copyLocation() {
+    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
+                    .getService(Ci.nsIClipboardHelper);
+    clipboard.copyString(this.download.source.url);
+  },
+
+  downloadsCmd_doDefault() {
+    let defaultCommand = this.currentDefaultCommandName;
+    if (defaultCommand && this.isCommandEnabled(defaultCommand)) {
+      this.doCommand(defaultCommand);
+    }
+  },
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsViewController
 
 /**
  * Handles part of the user interaction events raised by the downloads list
  * widget, in particular the "commands" that apply to multiple items, and
@@ -1073,18 +1168,21 @@ const DownloadsViewController = {
     window.controllers.removeController(this);
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// nsIController
 
   supportsCommand(aCommand) {
     // Firstly, determine if this is a command that we can handle.
-    if (!(aCommand in this.commands) &&
-        !(aCommand in DownloadsViewItemController.prototype.commands)) {
+    if (!DownloadsViewUI.isCommandName(aCommand)) {
+      return false;
+    }
+    if (!(aCommand in this) &&
+        !(aCommand in DownloadsViewItem.prototype)) {
       return false;
     }
     // Secondly, determine if focus is on a control in the downloads list.
     let element = document.commandDispatcher.focusedElement;
     while (element && element != DownloadsView.richListBox) {
       element = element.parentNode;
     }
     // We should handle the command only if the downloads list is among the
@@ -1095,228 +1193,63 @@ const DownloadsViewController = {
   isCommandEnabled(aCommand) {
     // Handle commands that are not selection-specific.
     if (aCommand == "downloadsCmd_clearList") {
       return DownloadsCommon.getData(window).canRemoveFinished;
     }
 
     // Other commands are selection-specific.
     let element = DownloadsView.richListBox.selectedItem;
-    return element && DownloadsView.controllerForElement(element)
+    return element && DownloadsView.itemForElement(element)
                                    .isCommandEnabled(aCommand);
   },
 
   doCommand(aCommand) {
     // If this command is not selection-specific, execute it.
-    if (aCommand in this.commands) {
-      this.commands[aCommand].apply(this);
+    if (aCommand in this) {
+      this[aCommand]();
       return;
     }
 
     // Other commands are selection-specific.
     let element = DownloadsView.richListBox.selectedItem;
     if (element) {
       // The doCommand function also checks if the command is enabled.
-      DownloadsView.controllerForElement(element).doCommand(aCommand);
+      DownloadsView.itemForElement(element).doCommand(aCommand);
     }
   },
 
   onEvent() {},
 
   //////////////////////////////////////////////////////////////////////////////
   //// Other functions
 
   updateCommands() {
-    Object.keys(this.commands).forEach(goUpdateCommand);
-    Object.keys(DownloadsViewItemController.prototype.commands)
-          .forEach(goUpdateCommand);
+    function updateCommandsForObject(object) {
+      for (let name in object) {
+        if (DownloadsViewUI.isCommandName(name)) {
+          goUpdateCommand(name);
+        }
+      }
+    }
+    updateCommandsForObject(this);
+    updateCommandsForObject(DownloadsViewItem.prototype);
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Selection-independent commands
 
-  /**
-   * This object contains one key for each command that operates regardless of
-   * the currently selected item in the list.
-   */
-  commands: {
-    downloadsCmd_clearList() {
-      DownloadsCommon.getData(window).removeFinished();
-    }
-  }
+  downloadsCmd_clearList() {
+    DownloadsCommon.getData(window).removeFinished();
+  },
 };
 
 XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewController);
 
 ////////////////////////////////////////////////////////////////////////////////
-//// DownloadsViewItemController
-
-/**
- * Handles all the user interaction events, in particular the "commands",
- * related to a single item in the downloads list widgets.
- */
-function DownloadsViewItemController(download) {
-  this.download = download;
-}
-
-DownloadsViewItemController.prototype = {
-  isCommandEnabled(aCommand) {
-    switch (aCommand) {
-      case "downloadsCmd_open": {
-        if (!this.download.succeeded) {
-          return false;
-        }
-
-        let file = new FileUtils.File(this.download.target.path);
-        return file.exists();
-      }
-      case "downloadsCmd_show": {
-        let file = new FileUtils.File(this.download.target.path);
-        if (file.exists()) {
-          return true;
-        }
-
-        if (!this.download.target.partFilePath) {
-          return false;
-        }
-
-        let partFile = new FileUtils.File(this.download.target.partFilePath);
-        return partFile.exists();
-      }
-      case "downloadsCmd_pauseResume":
-        return this.download.hasPartialData && !this.download.error;
-      case "downloadsCmd_retry":
-        return this.download.canceled || this.download.error;
-      case "downloadsCmd_openReferrer":
-        return !!this.download.source.referrer;
-      case "cmd_delete":
-      case "downloadsCmd_cancel":
-      case "downloadsCmd_copyLocation":
-      case "downloadsCmd_doDefault":
-        return true;
-      case "downloadsCmd_unblock":
-      case "downloadsCmd_confirmBlock":
-        return this.download.hasBlockedData;
-    }
-    return false;
-  },
-
-  doCommand(aCommand) {
-    if (this.isCommandEnabled(aCommand)) {
-      this.commands[aCommand].apply(this);
-    }
-  },
-
-  //////////////////////////////////////////////////////////////////////////////
-  //// Item commands
-
-  /**
-   * This object contains one key for each command that operates on this item.
-   *
-   * In commands, the "this" identifier points to the controller item.
-   */
-  commands: {
-    cmd_delete() {
-      DownloadsCommon.removeAndFinalizeDownload(this.download);
-      PlacesUtils.bhistory.removePage(
-                             NetUtil.newURI(this.download.source.url));
-    },
-
-    downloadsCmd_cancel() {
-      this.download.cancel().catch(() => {});
-      this.download.removePartialData().catch(Cu.reportError);
-    },
-
-    downloadsCmd_unblock() {
-      DownloadsPanel.hidePanel();
-      DownloadsCommon.confirmUnblockDownload(DownloadsCommon.BLOCK_VERDICT_MALWARE,
-                                             window).then((confirmed) => {
-        if (confirmed) {
-          return this.download.unblock();
-        }
-      }).catch(Cu.reportError);
-    },
-
-    downloadsCmd_confirmBlock() {
-      this.download.confirmBlock().catch(Cu.reportError);
-    },
-
-    downloadsCmd_open() {
-      this.download.launch().catch(Cu.reportError);
-
-      // We explicitly close the panel here to give the user the feedback that
-      // their click has been received, and we're handling the action.
-      // Otherwise, we'd have to wait for the file-type handler to execute
-      // before the panel would close. This also helps to prevent the user from
-      // accidentally opening a file several times.
-      DownloadsPanel.hidePanel();
-    },
-
-    downloadsCmd_show() {
-      let file = new FileUtils.File(this.download.target.path);
-      DownloadsCommon.showDownloadedFile(file);
-
-      // We explicitly close the panel here to give the user the feedback that
-      // their click has been received, and we're handling the action.
-      // Otherwise, we'd have to wait for the operating system file manager
-      // window to open before the panel closed. This also helps to prevent the
-      // user from opening the containing folder several times.
-      DownloadsPanel.hidePanel();
-    },
-
-    downloadsCmd_pauseResume() {
-      if (this.download.stopped) {
-        this.download.start();
-      } else {
-        this.download.cancel();
-      }
-    },
-
-    downloadsCmd_retry() {
-      this.download.start().catch(() => {});
-    },
-
-    downloadsCmd_openReferrer() {
-      openURL(this.download.source.referrer);
-    },
-
-    downloadsCmd_copyLocation() {
-      let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
-                      .getService(Ci.nsIClipboardHelper);
-      clipboard.copyString(this.download.source.url);
-    },
-
-    downloadsCmd_doDefault() {
-      const nsIDM = Ci.nsIDownloadManager;
-
-      // Determine the default command for the current item.
-      let defaultCommand = function () {
-        switch (DownloadsCommon.stateOfDownload(this.download)) {
-          case nsIDM.DOWNLOAD_NOTSTARTED:       return "downloadsCmd_cancel";
-          case nsIDM.DOWNLOAD_FINISHED:         return "downloadsCmd_open";
-          case nsIDM.DOWNLOAD_FAILED:           return "downloadsCmd_retry";
-          case nsIDM.DOWNLOAD_CANCELED:         return "downloadsCmd_retry";
-          case nsIDM.DOWNLOAD_PAUSED:           return "downloadsCmd_pauseResume";
-          case nsIDM.DOWNLOAD_QUEUED:           return "downloadsCmd_cancel";
-          case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: return "downloadsCmd_openReferrer";
-          case nsIDM.DOWNLOAD_SCANNING:         return "downloadsCmd_show";
-          case nsIDM.DOWNLOAD_DIRTY:            return "downloadsCmd_openReferrer";
-          case nsIDM.DOWNLOAD_BLOCKED_POLICY:   return "downloadsCmd_openReferrer";
-        }
-        return "";
-      }.apply(this);
-      if (defaultCommand && this.isCommandEnabled(defaultCommand)) {
-        this.doCommand(defaultCommand);
-      }
-    },
-  },
-};
-
-
-////////////////////////////////////////////////////////////////////////////////
 //// DownloadsSummary
 
 /**
  * Manages the summary at the bottom of the downloads panel list if the number
  * of items in the list exceeds the panels limit.
  */
 const DownloadsSummary = {
 
--- a/browser/components/downloads/jar.mn
+++ b/browser/components/downloads/jar.mn
@@ -1,18 +1,16 @@
 # 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/.
 
 browser.jar:
 *       content/browser/downloads/download.xml           (content/download.xml)
-        content/browser/downloads/download.css           (content/download.css)
         content/browser/downloads/downloads.css          (content/downloads.css)
         content/browser/downloads/downloads.js           (content/downloads.js)
 *       content/browser/downloads/downloadsOverlay.xul   (content/downloadsOverlay.xul)
         content/browser/downloads/indicator.js           (content/indicator.js)
         content/browser/downloads/indicatorOverlay.xul   (content/indicatorOverlay.xul)
 *       content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul)
         content/browser/downloads/allDownloadsViewOverlay.js  (content/allDownloadsViewOverlay.js)
-        content/browser/downloads/allDownloadsViewOverlay.css (content/allDownloadsViewOverlay.css)
 *       content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul)
         content/browser/downloads/contentAreaDownloadsView.js  (content/contentAreaDownloadsView.js)
         content/browser/downloads/contentAreaDownloadsView.css (content/contentAreaDownloadsView.css)
--- a/browser/components/downloads/test/browser/browser_basic_functionality.js
+++ b/browser/components/downloads/test/browser/browser_basic_functionality.js
@@ -44,13 +44,13 @@ add_task(function* test_basic_functional
   let richlistbox = document.getElementById("downloadsListBox");
   /* disabled for failing intermittently (bug 767828)
     is(richlistbox.children.length, DownloadData.length,
        "There is the correct number of richlistitems");
   */
   let itemCount = richlistbox.children.length;
   for (let i = 0; i < itemCount; i++) {
     let element = richlistbox.children[itemCount - i - 1];
-    let download = DownloadsView.controllerForElement(element).download;
+    let download = DownloadsView.itemForElement(element).download;
     is(DownloadsCommon.stateOfDownload(download), DownloadData[i].state,
        "Download states match up");
   }
 });
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -149,16 +149,29 @@ extensions.registerSchemaAPI("tabs", nul
         WindowListManager.addOpenListener(windowListener);
         AllWindowEvents.addListener("TabOpen", listener);
         return () => {
           WindowListManager.removeOpenListener(windowListener);
           AllWindowEvents.removeListener("TabOpen", listener);
         };
       }).api(),
 
+      /**
+       * Since multiple tabs currently can't be highlighted, onHighlighted
+       * essentially acts an alias for self.tabs.onActivated but returns
+       * the tabId in an array to match the API.
+       * @see  https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
+      */
+      onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
+        let tab = event.originalTarget;
+        let tabIds = [TabManager.getId(tab)];
+        let windowId = WindowManager.getId(tab.ownerDocument.defaultView);
+        fire({tabIds, windowId});
+      }).api(),
+
       onAttached: new EventManager(context, "tabs.onAttached", fire => {
         let fireForTab = tab => {
           let newWindowId = WindowManager.getId(tab.ownerDocument.defaultView);
           fire(TabManager.getId(tab), {newWindowId, newPosition: tab._tPos});
         };
 
         let listener = event => {
           if (event.detail.adoptedTab) {
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -39,13 +39,14 @@ support-files =
 [browser_ext_tabs_create_invalid_url.js]
 [browser_ext_tabs_duplicate.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_tabs_onUpdated.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_move.js]
 [browser_ext_tabs_move_window.js]
+[browser_ext_tabs_onHighlighted.js]
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows_update.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_tab_runtimeConnect.js]
 [browser_ext_webNavigation_getFrames.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onHighlighted.js
@@ -0,0 +1,118 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testTabEvents() {
+  function background() {
+    /** The list of active tab ID's */
+    let tabIds = [];
+
+    /**
+     * Stores the events that fire for each tab.
+     *
+     * events {
+     *   tabId1: [event1, event2, ...],
+     *   tabId2: [event1, event2, ...],
+     * }
+     */
+    let events = {};
+
+    browser.tabs.onActivated.addListener((info) => {
+      if (info.tabId in events) {
+        events[info.tabId].push("onActivated");
+      } else {
+        events[info.tabId] = ["onActivated"];
+      }
+    });
+
+    browser.tabs.onHighlighted.addListener((info) => {
+      if (info.tabIds[0] in events) {
+        events[info.tabIds[0]].push("onHighlighted");
+      } else {
+        events[info.tabIds[0]] = ["onHighlighted"];
+      }
+    });
+
+    /**
+     * Asserts that the expected events are fired for the tab with id = tabId.
+     * The events associated to the specified tab are removed after this check is made.
+     */
+    function expectEvents(tabId, expectedEvents) {
+      browser.test.log(`Expecting events: ${expectedEvents.join(", ")}`);
+
+      return new Promise(resolve => {
+        setTimeout(resolve, 0);
+      }).then(() => {
+        browser.test.assertEq(expectedEvents.length, events[tabId].length,
+         `Got expected number of events for ${tabId}`);
+        for (let [i, name] of expectedEvents.entries()) {
+          browser.test.assertEq(name, i in events[tabId] && events[tabId][i],
+                                `Got expected ${name} event`);
+        }
+        delete events[tabId];
+      });
+    }
+
+    /**
+     * Opens a new tab and asserts that the correct events are fired.
+     */
+    function openTab(windowId) {
+      return browser.tabs.create({windowId}).then(tab => {
+        tabIds.push(tab.id);
+        browser.test.log(`Opened tab ${tab.id}`);
+        return expectEvents(tab.id, [
+          "onActivated",
+          "onHighlighted",
+        ]);
+      });
+    }
+
+    /**
+     * Highlights an existing tab and asserts that the correct events are fired.
+     */
+    function highlightTab(tabId) {
+      browser.test.log(`Highlighting tab ${tabId}`);
+      return browser.tabs.update(tabId, {active: true}).then(tab => {
+        browser.test.assertEq(tab.id, tabId, `Tab ${tab.id} highlighted`);
+        return expectEvents(tab.id, [
+          "onActivated",
+          "onHighlighted",
+        ]);
+      });
+    }
+
+    /**
+     * The main entry point to the tests.
+     */
+    browser.tabs.query({active: true, currentWindow: true}, tabs => {
+      let activeWindow = tabs[0].windowId;
+      Promise.all([
+        openTab(activeWindow),
+        openTab(activeWindow),
+        openTab(activeWindow),
+      ]).then(() => {
+        return Promise.all([
+          highlightTab(tabIds[0]),
+          highlightTab(tabIds[1]),
+          highlightTab(tabIds[2]),
+        ]);
+      }).then(() => {
+        return Promise.all(tabIds.map(id => browser.tabs.remove(id)));
+      }).then(() => {
+        browser.test.notifyPass("tabs.highlight");
+      });
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+
+    background,
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("tabs.highlight");
+  yield extension.unload();
+});
--- a/browser/components/safebrowsing/content/test/browser.ini
+++ b/browser/components/safebrowsing/content/test/browser.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
 support-files = head.js
 
 [browser_forbidden.js]
 [browser_bug400731.js]
-skip-if = e10s
 [browser_bug415846.js]
 skip-if = os == "mac" || e10s
 # Disabled on Mac because of its bizarre special-and-unique
 # snowflake of a help menu.
--- a/browser/components/safebrowsing/content/test/browser_bug400731.js
+++ b/browser/components/safebrowsing/content/test/browser_bug400731.js
@@ -1,77 +1,58 @@
-/* Check for the intended visibility of the "Ignore this warning" text*/
+/* Check presence of the "Ignore this warning" button */
+
+function onDOMContentLoaded(callback) {
+  function complete({ data }) {
+    mm.removeMessageListener("Test:DOMContentLoaded", complete);
+    callback(data);
+  }
+
+  let mm = gBrowser.selectedBrowser.messageManager;
+  mm.addMessageListener("Test:DOMContentLoaded", complete);
+
+  function contentScript() {
+    let listener = function () {
+      removeEventListener("DOMContentLoaded", listener);
+
+      let button = content.document.getElementById("ignoreWarningButton");
+
+      sendAsyncMessage("Test:DOMContentLoaded", { buttonPresent: !!button });
+    };
+    addEventListener("DOMContentLoaded", listener);
+  }
+  mm.loadFrameScript("data:,(" + contentScript.toString() + ")();", true);
+}
 
 function test() {
   waitForExplicitFinish();
 
-  gBrowser.selectedTab = gBrowser.addTab();
-
-  // Navigate to malware site.  Can't use an onload listener here since
-  // error pages don't fire onload.  Also can't register the DOMContentLoaded
-  // handler here because registering it too soon would mean that we might
-  // get it for about:blank, and not about:blocked.
-  gBrowser.addTabsProgressListener({
-    onLocationChange: function(aTab, aWebProgress, aRequest, aLocation, aFlags) {
-      if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
-        gBrowser.removeTabsProgressListener(this);
-        window.addEventListener("DOMContentLoaded", testMalware, true);
-      }
-    }
-  });
-  content.location = "http://www.itisatrap.org/firefox/its-an-attack.html";
+  gBrowser.selectedTab = gBrowser.addTab("http://www.itisatrap.org/firefox/its-an-attack.html");
+  onDOMContentLoaded(testMalware);
 }
 
-function testMalware(event) {
-  if (event.target != gBrowser.selectedBrowser.contentDocument) {
-    return;
-  }
-
-  window.removeEventListener("DOMContentLoaded", testMalware, true);
-
-  // Confirm that "Ignore this warning" is visible - bug 422410
-  var el = content.document.getElementById("ignoreWarningButton");
-  ok(el, "Ignore warning button should be present for malware");
-
-  var style = content.getComputedStyle(el, null);
-  is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for malware");
+function testMalware(data) {
+  ok(data.buttonPresent, "Ignore warning button should be present for malware");
 
   Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", false);
 
   // Now launch the unwanted software test
-  window.addEventListener("DOMContentLoaded", testUnwanted, true);
-  content.location = "http://www.itisatrap.org/firefox/unwanted.html";
+  onDOMContentLoaded(testUnwanted);
+  gBrowser.loadURI("http://www.itisatrap.org/firefox/unwanted.html");
 }
 
-function testUnwanted(event) {
-  if (event.target != gBrowser.selectedBrowser.contentDocument) {
-    return;
-  }
-
-  window.removeEventListener("DOMContentLoaded", testUnwanted, true);
-
+function testUnwanted(data) {
   // Confirm that "Ignore this warning" is visible - bug 422410
-  var el = content.document.getElementById("ignoreWarningButton");
-  ok(!el, "Ignore warning button should be missing for unwanted software");
+  ok(!data.buttonPresent, "Ignore warning button should be missing for unwanted software");
 
   Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", true);
 
   // Now launch the phishing test
-  window.addEventListener("DOMContentLoaded", testPhishing, true);
-  content.location = "http://www.itisatrap.org/firefox/its-a-trap.html";
+  onDOMContentLoaded(testPhishing);
+  gBrowser.loadURI("http://www.itisatrap.org/firefox/its-a-trap.html");
 }
 
-function testPhishing(event) {
-  if (event.target != gBrowser.selectedBrowser.contentDocument) {
-    return;
-  }
-
-  window.removeEventListener("DOMContentLoaded", testPhishing, true);
-
-  var el = content.document.getElementById("ignoreWarningButton");
-  ok(el, "Ignore warning button should be present for phishing");
-
-  var style = content.getComputedStyle(el, null);
-  is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for phishing");
+function testPhishing(data) {
+  ok(data.buttonPresent, "Ignore warning button should be present for phishing");
 
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/components/syncedtabs/SyncedTabsListStore.js
+++ b/browser/components/syncedtabs/SyncedTabsListStore.js
@@ -183,22 +183,26 @@ Object.assign(SyncedTabsListStore.protot
   },
 
   openBranch(id) {
     this._toggleBranch(id, false);
   },
 
   focusInput() {
     this.inputFocused = true;
-    this._change("update");
+    // A change type of "all" updates rather than rebuilds, which is what we
+    // want here - only the selection/focus has changed.
+    this._change("all");
   },
 
   blurInput() {
     this.inputFocused = false;
-    this._change("update");
+    // A change type of "all" updates rather than rebuilds, which is what we
+    // want here - only the selection/focus has changed.
+    this._change("all");
   },
 
   clearFilter() {
     this.filter = "";
     this._selectedRow = [-1, -1];
     return this.getData();
   },
 
--- a/browser/components/tests/unit/test_distribution.js
+++ b/browser/components/tests/unit/test_distribution.js
@@ -58,17 +58,17 @@ do_register_cleanup(function () {
 });
 
 add_task(function* () {
   // Force distribution.
   let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
   glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
 
   Assert.equal(Services.prefs.getCharPref("distribution.test.string"), "Test String");
-  Assert.throws(() => Services.prefs.getCharPref("distribution.test.string.noquotes"));
+  Assert.equal(Services.prefs.getCharPref("distribution.test.string.noquotes"), "Test String");
   Assert.equal(Services.prefs.getIntPref("distribution.test.int"), 777);
   Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.true"), true);
   Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.false"), false);
   Assert.equal(Services.prefs.getComplexValue("distribution.test.locale", Ci.nsIPrefLocalizedString).data, "en-US");
   Assert.equal(Services.prefs.getComplexValue("distribution.test.language.en", Ci.nsIPrefLocalizedString).data, "en");
   Assert.equal(Services.prefs.getComplexValue("distribution.test.locale.en-US", Ci.nsIPrefLocalizedString).data, "en-US");
   Assert.throws(() => Services.prefs.getComplexValue("distribution.test.locale.de", Ci.nsIPrefLocalizedString));
   // This value was never set because of the empty language specific pref
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -588,17 +588,17 @@ var WindowListener = {
        *
        *  @param cursorData Object with the correct position for the cursor
        *                    {
        *                      ratioX: position on the X axis (percentage value)
        *                      ratioY: position on the Y axis (percentage value)
        *                    }
        */
       addRemoteCursor: function(cursorData) {
-        if (!this._listeningToTabSelect) {
+        if (this._browserSharePaused || !this._listeningToTabSelect) {
           return;
         }
 
         let browser = gBrowser.selectedBrowser;
 
         let cursor = document.getElementById("loop-remote-cursor");
         if (!cursor) {
           cursor = document.createElement("image");
@@ -644,63 +644,67 @@ var WindowListener = {
       },
 
       /**
        * Shows an infobar notification at the top of the browser window that warns
        * the user that their browser tabs are being broadcasted through the current
        * conversation.
        */
       _maybeShowBrowserSharingInfoBar: function() {
-        this._hideBrowserSharingInfoBar();
-
-        let box = gBrowser.getNotificationBox();
         // Pre-load strings
         let pausedStrings = {
           label: this._getString("infobar_button_restart_label2"),
           accesskey: this._getString("infobar_button_restart_accesskey"),
           message: this._getString("infobar_screenshare_stop_sharing_message")
         };
         let unpausedStrings = {
           label: this._getString("infobar_button_stop_label2"),
           accesskey: this._getString("infobar_button_stop_accesskey"),
           message: this._getString("infobar_screenshare_browser_message2")
         };
-        let initStrings = this._browserSharePaused ? pausedStrings : unpausedStrings;
+        let initStrings =
+          this._browserSharePaused ? pausedStrings : unpausedStrings;
+
+        this._hideBrowserSharingInfoBar();
+        let box = gBrowser.getNotificationBox();
         let bar = box.appendNotification(
-          initStrings.message,
-          kBrowserSharingNotificationId,
-          // Icon is defined in browser theme CSS.
-          null,
-          box.PRIORITY_WARNING_LOW,
-          [{
+          initStrings.message,            // label
+          kBrowserSharingNotificationId,  // value
+          // Icon defined in browser theme CSS.
+          null,                           // image
+          box.PRIORITY_WARNING_LOW,       // priority
+          [{                              // buttons (Pause, Stop)
             label: initStrings.label,
             accessKey: initStrings.accessKey,
             isDefault: false,
             callback: (event, buttonInfo, buttonNode) => {
               this._browserSharePaused = !this._browserSharePaused;
               let stringObj = this._browserSharePaused ? pausedStrings : unpausedStrings;
               bar.label = stringObj.message;
               bar.classList.toggle("paused", this._browserSharePaused);
               buttonNode.label = stringObj.label;
               buttonNode.accessKey = stringObj.accesskey;
               LoopUI.MozLoopService.toggleBrowserSharing(this._browserSharePaused);
               if (this._browserSharePaused) {
                 this._pauseButtonClicked = true;
+                // if paused we stop sharing remote cursors
+                this.removeRemoteCursor();
               } else {
                 this._resumeButtonClicked = true;
               }
               return true;
             },
             type: "pause"
           },
           {
             label: this._getString("infobar_button_disconnect_label"),
             accessKey: this._getString("infobar_button_disconnect_accesskey"),
             isDefault: true,
             callback: () => {
+              this.removeRemoteCursor();
               this._hideBrowserSharingInfoBar();
               LoopUI.MozLoopService.hangupAllChatWindows();
             },
             type: "stop"
           }]
         );
 
         // Sets 'paused' class if needed.
@@ -775,18 +779,18 @@ var WindowListener = {
       },
 
       /**
        * Handles mousemove events from gBrowser and send a broadcast message
        * with all the data needed for sending link generator cursor position
        * through the sdk.
        */
       handleMousemove: function(event) {
-        // We want to stop sending events if sharing is paused.
-        if (this._browserSharePaused) {
+        // Won't send events if not sharing (paused or not started).
+        if (this._browserSharePaused || !this._listeningToTabSelect) {
           return;
         }
 
         // Only update every so often.
         let now = Date.now();
         if (now - this.lastCursorTime < MIN_CURSOR_INTERVAL) {
           return;
         }
--- a/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
@@ -978,17 +978,23 @@ var MozLoopServiceInternal = {
           let listeners = {};
 
           let messageName = "Social:CustomEvent";
           mm.addMessageListener(messageName, listeners[messageName] = message => {
             let eventName = message.data.name;
             if (kEventNamesMap[eventName]) {
               eventName = kEventNamesMap[eventName];
 
-              UITour.clearAvailableTargetsCache();
+              // `clearAvailableTargetsCache` is new in Firefox 46. The else branch
+              // supports Firefox 45.
+              if ("clearAvailableTargetsCache" in UITour) {
+                UITour.clearAvailableTargetsCache();
+              } else {
+                UITour.availableTargetsCache.clear();
+              }
               UITour.notify(eventName);
             } else {
               // When the chat box or messages are shown, resize the panel or window
               // to be slightly higher to accomodate them.
               let customSize = kSizeMap[eventName];
               let currSize = chatbox.getAttribute("customSize");
               // If the size is already at the requested one or at the maximum size
               // already, don't do anything. Especially don't make it shrink.
@@ -1966,16 +1972,17 @@ this.MozLoopService = {
         log.error(ex);
       }
 
       this.setLoopPref("gettingStarted.latestFTUVersion", this.FTU_VERSION);
 
       // Notify the UI, which has the side effect of re-enabling panel opening
       // and updating the toolbar.
       xulWin.LoopUI.isSlideshowOpen = false;
+      xulWin.LoopUI.openPanel();
 
       xulWin.removeEventListener("CloseSlideshow", removeSlideshow);
 
       log.info("slideshow removed");
     }.bind(this);
 
     function xulLoadListener() {
       xulBrowser.contentWindow.addEventListener("CloseSlideshow",
--- a/browser/extensions/loop/chrome/content/panels/js/panel.js
+++ b/browser/extensions/loop/chrome/content/panels/js/panel.js
@@ -462,23 +462,20 @@ loop.panel = function (_, mozL10n) {
     handleClickEntry: function (event) {
       event.preventDefault();
 
       this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
         roomToken: this.props.room.roomToken
       }));
 
       // Open url if needed.
-      loop.requestMulti(
-        ["getSelectedTabMetadata"],
-        ["GettingStartedURL", null, {}]
-      ).then(function(results) {
+      loop.requestMulti(["getSelectedTabMetadata"], ["GettingStartedURL", null, {}]).then(function (results) {
         var contextURL = this.props.room.decryptedContext.urls && this.props.room.decryptedContext.urls[0].location;
 
-        contextURL = contextURL || (results[1] + "?noopenpanel=1");
+        contextURL = contextURL || results[1] + "?noopenpanel=1";
 
         if (results[0].url !== contextURL) {
           loop.request("OpenURL", contextURL);
         }
         this.closeWindow();
       }.bind(this));
     },
 
--- a/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
@@ -136,16 +136,17 @@ loop.store.ActiveRoomStore = (function(m
         // Tracks if the room has been used during this
         // session. 'Used' means at least one call has been placed
         // with it. Entering and leaving the room without seeing
         // anyone is not considered as 'used'
         used: false,
         localVideoDimensions: {},
         remoteVideoDimensions: {},
         screenSharingState: SCREEN_SHARE_STATES.INACTIVE,
+        sharingPaused: false,
         receivingScreenShare: false,
         // Any urls (aka context) associated with the room.
         roomContextUrls: null,
         // The description for a room as stored in the context data.
         roomDescription: null,
         // Room information failed to be obtained for a reason. See ROOM_INFO_FAILURES.
         roomInfoFailure: null,
         // The name of the room.
@@ -259,16 +260,17 @@ loop.store.ActiveRoomStore = (function(m
         "leaveRoom",
         "feedbackComplete",
         "mediaStreamCreated",
         "mediaStreamDestroyed",
         "remoteVideoStatus",
         "videoDimensionsChanged",
         "startBrowserShare",
         "endScreenShare",
+        "toggleBrowserSharing",
         "updateSocialShareInfo",
         "connectionStatus",
         "mediaConnected"
       ];
       // Register actions that are only used on Desktop.
       if (this._isDesktop) {
         // 'receivedTextChatMessage' and  'sendTextChatMessage' actions are only
         // registered for Telemetry. Once measured, they're unregistered.
@@ -919,20 +921,29 @@ loop.store.ActiveRoomStore = (function(m
         this._sdkDriver.startScreenShare(options);
       } else if (screenSharingState === SCREEN_SHARE_STATES.ACTIVE) {
         // Just update the current share.
         this._sdkDriver.switchAcquiredWindow(windowId);
       } else {
         console.error("Unexpectedly received windowId for browser sharing when pending");
       }
 
-      // The browser being shared changed, so update to the new context
+      // Only update context if sharing is not paused and there's somebody.
+      if (!this.getStoreState().sharingPaused && this._hasParticipants()) {
+        this._checkTabContext();
+      }
+    },
+
+    /**
+     * Get the current tab context to update the room context.
+     */
+    _checkTabContext: function() {
       loop.request("GetSelectedTabMetadata").then(function(meta) {
-        // Avoid sending the event if there is no data nor participants nor url
-        if (!meta || !meta.url || !this._hasParticipants()) {
+        // Avoid sending the event if there is no data nor url.
+        if (!meta || !meta.url) {
           return;
         }
 
         if (updateContextTimer) {
           clearTimeout(updateContextTimer);
         }
 
         updateContextTimer = setTimeout(function() {
@@ -985,16 +996,32 @@ loop.store.ActiveRoomStore = (function(m
       if (this._sdkDriver.endScreenShare()) {
         this.dispatchAction(new sharedActions.ScreenSharingState({
           state: SCREEN_SHARE_STATES.INACTIVE
         }));
       }
     },
 
     /**
+     * Handle browser sharing being enabled or disabled.
+     *
+     * @param {sharedActions.ToggleBrowserSharing} actionData
+     */
+    toggleBrowserSharing: function(actionData) {
+      this.setStoreState({
+        sharingPaused: !actionData.enabled
+      });
+
+      // If unpausing, check the context as it might have changed.
+      if (actionData.enabled) {
+        this._checkTabContext();
+      }
+    },
+
+    /**
      * Handles recording when a remote peer has connected to the servers.
      */
     remotePeerConnected: function() {
       this.setStoreState({
         roomState: ROOM_STATES.HAS_PARTICIPANTS,
         used: true
       });
     },
--- a/browser/extensions/loop/chrome/content/shared/js/views.js
+++ b/browser/extensions/loop/chrome/content/shared/js/views.js
@@ -730,18 +730,22 @@ loop.shared.views = function (_, mozL10n
       var now = Date.now();
       if (now - this.lastCursorTime < this.MIN_CURSOR_INTERVAL) {
         return;
       }
       this.lastCursorTime = now;
 
       var storeState = this.props.cursorStore.getStoreState();
 
-      var deltaX = event.clientX - storeState.videoLetterboxing.left;
-      var deltaY = event.clientY - storeState.videoLetterboxing.top;
+      // video is not at the top, so we need to calculate the offset
+      var video = this.getDOMNode().querySelector("video");
+      var offset = video.getBoundingClientRect();
+
+      var deltaX = event.clientX - storeState.videoLetterboxing.left - offset.left;
+      var deltaY = event.clientY - storeState.videoLetterboxing.top - offset.top;
 
       // Skip the update if cursor is out of bounds
       if (deltaX < 0 || deltaX > storeState.streamVideoWidth || deltaY < 0 || deltaY > storeState.streamVideoHeight ||
       // or the cursor didn't move the minimum.
       Math.abs(deltaX - this.lastCursorX) < this.MIN_CURSOR_DELTA && Math.abs(deltaY - this.lastCursorY) < this.MIN_CURSOR_DELTA) {
         return;
       }
 
--- a/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
@@ -1595,16 +1595,17 @@ describe("loop.store.ActiveRoomStore", f
           scrollWithPage: true
         }
       });
     });
 
     it("should request the new metadata when the browser being shared change", function() {
       store.startBrowserShare(new sharedActions.StartBrowserShare());
       clock.tick(500);
+
       sinon.assert.calledOnce(getSelectedTabMetadataStub);
       sinon.assert.calledTwice(dispatcher.dispatch);
       sinon.assert.calledWith(dispatcher.dispatch.getCall(1),
         new sharedActions.UpdateRoomContext({
           newRoomDescription: "fakeTitle",
           newRoomThumbnail: "fakeFavicon",
           newRoomURL: "http://www.fakeurl.com",
           roomToken: store.getStoreState().roomToken
@@ -1625,45 +1626,52 @@ describe("loop.store.ActiveRoomStore", f
           newRoomDescription: "fakeTitle",
           newRoomThumbnail: "fakeFavicon",
           newRoomURL: "http://www.fakeurl.com",
           roomToken: store.getStoreState().roomToken
       }));
     });
 
     it("should not process a request without url", function() {
-      clock.tick(500);
       getSelectedTabMetadataStub.returns({
         title: "fakeTitle",
         favicon: "fakeFavicon"
       });
 
       store.startBrowserShare(new sharedActions.StartBrowserShare());
+      clock.tick(500);
+
       sinon.assert.calledOnce(getSelectedTabMetadataStub);
       sinon.assert.calledOnce(dispatcher.dispatch);
     });
 
+    it("should not process a request if sharing is paused", function() {
+      store.setStoreState({
+        sharingPaused: true
+      });
+
+      store.startBrowserShare(new sharedActions.StartBrowserShare());
+      clock.tick(500);
+
+      sinon.assert.notCalled(getSelectedTabMetadataStub);
+      sinon.assert.calledOnce(dispatcher.dispatch);
+    });
+
     it("should not process a request if no-one is in the room", function() {
       store.setStoreState({
-        roomState: ROOM_STATES.JOINED,
-        roomToken: "fakeToken",
-        sessionToken: "1627384950",
         participants: [{
           displayName: "Owner",
           owner: true
         }]
       });
-      clock.tick(500);
-      getSelectedTabMetadataStub.returns({
-        title: "fakeTitle",
-        favicon: "fakeFavicon"
-      });
 
       store.startBrowserShare(new sharedActions.StartBrowserShare());
-      sinon.assert.calledOnce(getSelectedTabMetadataStub);
+      clock.tick(500);
+
+      sinon.assert.notCalled(getSelectedTabMetadataStub);
       sinon.assert.calledOnce(dispatcher.dispatch);
     });
   });
 
   describe("Screen share Events", function() {
     beforeEach(function() {
       store.startBrowserShare(new sharedActions.StartBrowserShare());
 
@@ -1726,16 +1734,64 @@ describe("loop.store.ActiveRoomStore", f
 
       // Now stop the screen share.
       store.endScreenShare();
 
       sinon.assert.calledOnce(requestStubs.RemoveBrowserSharingListener);
     });
   });
 
+  describe("#toggleBrowserSharing", function() {
+    it("should set paused to false when enabled", function() {
+      store.toggleBrowserSharing(new sharedActions.ToggleBrowserSharing({
+        enabled: true
+      }));
+
+      expect(store.getStoreState().sharingPaused).eql(false);
+    });
+
+    it("should set paused to true when not enabled", function() {
+      store.toggleBrowserSharing(new sharedActions.ToggleBrowserSharing({
+        enabled: false
+      }));
+
+      expect(store.getStoreState().sharingPaused).eql(true);
+    });
+
+    it("should update context when enabled", function() {
+      var getSelectedTabMetadataStub = sinon.stub();
+      LoopMochaUtils.stubLoopRequest({
+        GetSelectedTabMetadata: getSelectedTabMetadataStub.returns({
+          title: "fakeTitle",
+          favicon: "fakeFavicon",
+          url: "http://www.fakeurl.com"
+        })
+      });
+      store.setStoreState({
+        roomState: ROOM_STATES.JOINED,
+        roomToken: "fakeToken"
+      });
+
+      store.toggleBrowserSharing(new sharedActions.ToggleBrowserSharing({
+        enabled: true
+      }));
+      clock.tick(500);
+
+      sinon.assert.calledOnce(getSelectedTabMetadataStub);
+      sinon.assert.calledOnce(dispatcher.dispatch);
+      sinon.assert.calledWith(dispatcher.dispatch,
+        new sharedActions.UpdateRoomContext({
+          newRoomDescription: "fakeTitle",
+          newRoomThumbnail: "fakeFavicon",
+          newRoomURL: "http://www.fakeurl.com",
+          roomToken: "fakeToken"
+      }));
+    });
+  });
+
   describe("#remotePeerConnected", function() {
     it("should set the state to `HAS_PARTICIPANTS`", function() {
       store.remotePeerConnected();
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.HAS_PARTICIPANTS);
     });
   });
 
--- a/browser/extensions/loop/chrome/content/shared/vendor/sdk.js
+++ b/browser/extensions/loop/chrome/content/shared/vendor/sdk.js
@@ -1,26 +1,26 @@
 /**
- * @license OpenTok.js v2.7.2 6a37d02 HEAD
+ * @license OpenTok.js v2.7.3 c273b40 HEAD
  *
  * Copyright (c) 2010-2015 TokBox, Inc.
  * Released under the MIT license
  * http://opensource.org/licenses/MIT
  *
- * Date: December 16 12:20:36 2015
+ * Date: February 03 09:39:18 2016
  */
 
 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
 module.exports = Array;
 
 },{}],2:[function(require,module,exports){
-arguments[4][110][0].apply(exports,arguments)
-},{"dup":110}],3:[function(require,module,exports){
-arguments[4][111][0].apply(exports,arguments)
-},{"./buffer":1,"./rng":2,"dup":111}],4:[function(require,module,exports){
+arguments[4][108][0].apply(exports,arguments)
+},{"dup":108}],3:[function(require,module,exports){
+arguments[4][109][0].apply(exports,arguments)
+},{"./buffer":1,"./rng":2,"dup":109}],4:[function(require,module,exports){
 (function (global){
 'use strict';
 
 // The top-level namespace, also performs basic DOMElement selecting.
 //
 // @example Get the DOM element with the id of 'domId'
 //   OTHelpers('#domId')
 //
@@ -108,28 +108,17 @@ global.OTHelpers = OTHelpers;
 
 // A guard to detect when IE has performed cleans on unload
 global.___othelpers = true;
 
 module.exports = OTHelpers;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../vendor/async":37,"./ajax":5,"./analytics":8,"./behaviours/eventing":9,"./behaviours/eventing/event":11,"./behaviours/statable":13,"./callbacks":14,"./capabilities":15,"./casting":16,"./collection":17,"./cookies":18,"./domExtras":19,"./domLoad":20,"./elementCollection/index":25,"./elementCollection/shorthandSelector":26,"./env":27,"./error":28,"./isWebSocketSupported":29,"./logging":31,"./logging/mixin":32,"./modal":34,"./requestAnimationFrame":35,"./util":36}],5:[function(require,module,exports){
-'use strict';
-
-var env = require('./env');
-
-module.exports = (
-  env.name === 'Node' ?
-  require('./ajax/node') :
-  require('./ajax/browser')
-);
-
-},{"./ajax/browser":6,"./ajax/node":7,"./env":27}],6:[function(require,module,exports){
+},{"../vendor/async":35,"./ajax":5,"./analytics":6,"./behaviours/eventing":7,"./behaviours/eventing/event":9,"./behaviours/statable":11,"./callbacks":12,"./capabilities":13,"./casting":14,"./collection":15,"./cookies":16,"./domExtras":17,"./domLoad":18,"./elementCollection/index":23,"./elementCollection/shorthandSelector":24,"./env":25,"./error":26,"./isWebSocketSupported":27,"./logging":29,"./logging/mixin":30,"./modal":32,"./requestAnimationFrame":33,"./util":34}],5:[function(require,module,exports){
 'use strict';
 
 /* jshint browser:true, smarttabs:true */
 
 var $ = require('../elementCollection/shorthandSelector');
 var env = require('../env');
 var makeEverythingAttachToOTHelpers = require('../makeEverythingAttachToOTHelpers');
 var util = require('../util');
@@ -305,92 +294,22 @@ if (env.name !== 'Node') {
       }), done);
     }
 
   };
 
   makeEverythingAttachToOTHelpers(browserAjax);
 }
 
-},{"../elementCollection/shorthandSelector":26,"../env":27,"../makeEverythingAttachToOTHelpers":33,"../util":36}],7:[function(require,module,exports){
-'use strict';
-
-/* jshint browser:false, smarttabs:true */
-
-var env = require('../env');
-var makeEverythingAttachToOTHelpers = require('../makeEverythingAttachToOTHelpers');
-var util = require('../util');
-
-var nodeAjax = {};
-module.exports = nodeAjax;
-
-if (env.name === 'Node') {
-  var request = require('request' + ''); // make browserify happy with + ''
-
-  nodeAjax.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);
-    }
-  };
-
-  nodeAjax.get = function(url, options, callback) {
-    var _options = util.extend(options || {}, {
-      method: 'GET'
-    });
-    nodeAjax.request(url, _options, callback);
-  };
-
-  nodeAjax.post = function(url, options, callback) {
-    var _options = util.extend(options || {}, {
-      method: 'POST'
-    });
-
-    nodeAjax.request(url, _options, callback);
-  };
-
-  nodeAjax.getJSON = function(url, options, callback) {
-    var extendedHeaders = require('underscore' + '').extend( // make browserify happy with + ''
-      {
-        'Accept': 'application/json'
-      },
-      options.headers || {}
-    );
-
-    request.get({
-      url: url,
-      headers: extendedHeaders,
-      json: true
-    }, function(err, response) {
-      callback(err, response && response.body);
-    });
-  };
-
-  makeEverythingAttachToOTHelpers(nodeAjax);
-}
-},{"../env":27,"../makeEverythingAttachToOTHelpers":33,"../util":36}],8:[function(require,module,exports){
+},{"../elementCollection/shorthandSelector":24,"../env":25,"../makeEverythingAttachToOTHelpers":31,"../util":34}],6:[function(require,module,exports){
 'use strict';
 
 var ajax = require('./ajax');
 var env = require('./env');
+var logging = require('./logging');
 var util = require('./util');
 var uuid = require('uuid');
 
 // Singleton interval
 var logQueue = [],
     queueRunning = false;
 
 module.exports = function Analytics(loggingUrl, debugFn) {
@@ -536,19 +455,22 @@ module.exports = function Analytics(logg
   //                 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;
-      }
-    }
+        logging.debug('skipping sending analytics due to throttle:', data);
+        return;
+      }
+    }
+
+    logging.debug('sending analytics:', data);
 
     // remove properties that have null values:
     for (var key in data) {
       if (data.hasOwnProperty(key) && data[key] === null) {
         delete data[key];
       }
     }
 
@@ -573,17 +495,17 @@ module.exports = function Analytics(logg
   // @option options [String] section ...
   // @option options [String] clientVersion ...
   //
   this.logQOS = function(options) {
     this.logEvent(options, true);
   };
 };
 
-},{"./ajax":5,"./env":27,"./util":36,"uuid":3}],9:[function(require,module,exports){
+},{"./ajax":5,"./env":25,"./logging":29,"./util":34,"uuid":3}],7:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var browserEventing = require('./eventing/browser');
 var logging = require('../logging');
 var nodeEventing = require('./eventing/node');
 var util = require('../util');
 
@@ -902,21 +824,17 @@ module.exports = function eventing(self,
   //    The name of this event.
   //
   // @param [Array] arguments
   //    Any additional arguments beyond +eventName+ will be passed to the handlers.
   //
   // @return this
   //
   self.trigger = function(/* eventName [, arg0, arg1, ..., argN ] */) {
-    var args = Array.prototype.slice.call(arguments);
-
-    // Shifting to remove the eventName from the other args
-    _.trigger(args.shift(), args);
-
+    _.trigger.apply(_, arguments);
     return this;
   };
 
   // Alias of trigger for easier node compatibility
   self.emit = self.trigger;
 
 
   /**
@@ -1015,17 +933,17 @@ module.exports = function eventing(self,
   };
   /* end-test-code */
 
   return self;
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../logging":31,"../util":36,"./eventing/browser":10,"./eventing/node":12}],10:[function(require,module,exports){
+},{"../logging":29,"../util":34,"./eventing/browser":8,"./eventing/node":10}],8:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var env = require('../../env');
 var callbacks = require('../../callbacks');
 
 if(env.name !== 'Node') {
   module.exports = function browserEventing(self, syncronous) {
@@ -1182,17 +1100,20 @@ if(env.name !== 'Node') {
       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) {
+    api.trigger = function(/* eventName [, arg1, arg2, ...argN] */) {
+      var args = Array.prototype.slice.call(arguments);
+      var eventName = args.shift();
+
       if (!api.events[eventName] || api.events[eventName].length === 0) {
         return;
       }
 
       executeListeners(eventName, args);
     };
 
 
@@ -1216,17 +1137,17 @@ if(env.name !== 'Node') {
     /* end-test-code */
 
     return api;
   };
 }
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../callbacks":14,"../../env":27}],11:[function(require,module,exports){
+},{"../../callbacks":12,"../../env":25}],9:[function(require,module,exports){
 'use strict';
 
 var logging = require('../../logging');
 
 module.exports = function Event() {
   return function (type, cancelable) {
     this.type = type;
     this.cancelable = cancelable !== undefined ? cancelable : true;
@@ -1243,17 +1164,17 @@ module.exports = function Event() {
     };
 
     this.isDefaultPrevented = function() {
       return _defaultPrevented;
     };
   };
 };
 
-},{"../../logging":31}],12:[function(require,module,exports){
+},{"../../logging":29}],10:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var env = require('../../env');
 
 if (env.name === 'Node') {
   var EventEmitter = require('events').EventEmitter,
       nodeUtil = require('util');
@@ -1357,17 +1278,17 @@ if (env.name === 'Node') {
   };
 }
 else {
   module.exports = undefined;
 }
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../env":27,"events":78,"util":83}],13:[function(require,module,exports){
+},{"../../env":25,"events":76,"util":81}],11:[function(require,module,exports){
 'use strict';
 
 var util = require('../util');
 
 module.exports = function statable(
   self,
   possibleStates,
   initialState,
@@ -1414,17 +1335,17 @@ module.exports = function statable(
   //
   self.isNot = function (/* state0:String, state1:String, ..., stateN:String */) {
     return Array.prototype.indexOf.call(arguments, currentState) === -1;
   };
 
   return setState;
 };
 
-},{"../util":36}],14:[function(require,module,exports){
+},{"../util":34}],12:[function(require,module,exports){
 'use strict';
 
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 
 var callbacks = {};
 module.exports = callbacks;
 
 // Calls the function +fn+ asynchronously with the current execution.
@@ -1461,17 +1382,17 @@ callbacks.createAsyncHandler = function(
     callbacks.callAsync(function() {
       handler.apply(null, args);
     });
   };
 };
 
 makeEverythingAttachToOTHelpers(callbacks);
 
-},{"./makeEverythingAttachToOTHelpers":33}],15:[function(require,module,exports){
+},{"./makeEverythingAttachToOTHelpers":31}],13:[function(require,module,exports){
 'use strict';
 
 var logging = require('./logging');
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 var util = require('./util');
 
 var capabilities = {};
 module.exports = capabilities;
@@ -1591,17 +1512,17 @@ capabilities.hasCapabilities = function(
     }
   }
 
   return true;
 };
 
 makeEverythingAttachToOTHelpers(capabilities);
 
-},{"./logging":31,"./makeEverythingAttachToOTHelpers":33,"./util":36}],16:[function(require,module,exports){
+},{"./logging":29,"./makeEverythingAttachToOTHelpers":31,"./util":34}],14:[function(require,module,exports){
 'use strict';
 
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 
 var casting = {};
 module.exports = casting;
 
 casting.castToBoolean = function(value, defaultValue) {
@@ -1613,17 +1534,17 @@ casting.castToBoolean = function(value, 
 };
 
 casting.roundFloat = function(value, places) {
   return Number(value.toFixed(places));
 };
 
 makeEverythingAttachToOTHelpers(casting);
 
-},{"./makeEverythingAttachToOTHelpers":33}],17:[function(require,module,exports){
+},{"./makeEverythingAttachToOTHelpers":31}],15:[function(require,module,exports){
 'use strict';
 
 var eventing = require('./behaviours/eventing');
 var logging = require('./logging');
 var util = require('./util');
 
 module.exports = function Collection(idField) {
   var _models = [],
@@ -1796,17 +1717,17 @@ module.exports = function Collection(idF
     }, this);
   };
 
   this.length = function() {
     return _models.length;
   };
 };
 
-},{"./behaviours/eventing":9,"./logging":31,"./util":36}],18:[function(require,module,exports){
+},{"./behaviours/eventing":7,"./logging":29,"./util":34}],16:[function(require,module,exports){
 'use strict';
 
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 
 var cookies = {};
 module.exports = cookies;
 
 cookies.setCookie = function(key, value) {
@@ -1846,17 +1767,17 @@ cookies.getCookie = function(key) {
     }
   }
 
   return null;
 };
 
 makeEverythingAttachToOTHelpers(cookies);
 
-},{"./makeEverythingAttachToOTHelpers":33}],19:[function(require,module,exports){
+},{"./makeEverythingAttachToOTHelpers":31}],17:[function(require,module,exports){
 'use strict';
 
 var $ = require('./elementCollection/shorthandSelector');
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 
 var domExtras = {};
 module.exports = domExtras;
 
@@ -1916,17 +1837,17 @@ domExtras.createButton = function(innerH
     button._boundEvents = events;
   }
 
   return button;
 };
 
 makeEverythingAttachToOTHelpers(domExtras);
 
-},{"./elementCollection/shorthandSelector":26,"./makeEverythingAttachToOTHelpers":33}],20:[function(require,module,exports){
+},{"./elementCollection/shorthandSelector":24,"./makeEverythingAttachToOTHelpers":31}],18:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 
 var domLoad = {};
 module.exports = domLoad;
 
@@ -2012,17 +1933,17 @@ if (_domReady) {
   // fallback
   global.addEventListener('load', onDomReady, false );
 }
 
 makeEverythingAttachToOTHelpers(domLoad);
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./makeEverythingAttachToOTHelpers":33}],21:[function(require,module,exports){
+},{"./makeEverythingAttachToOTHelpers":31}],19:[function(require,module,exports){
 'use strict';
 
 var util = require('../../util');
 
 var specialDomProperties = {
   'for': 'htmlFor',
   'class': 'className'
 };
@@ -2163,17 +2084,17 @@ module.exports = function(ElementCollect
 
   // @remove
   ElementCollection._attachToOTHelpers.height = function(element, newHeight) {
     var ret = new ElementCollection(element).height(newHeight);
     return newHeight ? ElementCollection._attachToOTHelpers : ret;
   };
 };
 
-},{"../../util":36}],22:[function(require,module,exports){
+},{"../../util":34}],20:[function(require,module,exports){
 'use strict';
 
 var capabilities = require('../../capabilities');
 
 function isClassListSupported() {
   return (typeof document !== 'undefined') && ('classList' in document.createElement('a'));
 }
 
@@ -2332,17 +2253,17 @@ module.exports = function(ElementCollect
 
   ElementCollection.prototype.hasClass = function (value) {
     return this.some(function(element) {
       return hasClass(element, value);
     });
   };
 };
 
-},{"../../capabilities":15}],23:[function(require,module,exports){
+},{"../../capabilities":13}],21:[function(require,module,exports){
 (function (global){
 'use strict';
 var env = require('../../env');
 var getCompStyle = global.getComputedStyle;
 
 var VENDOR_PREFIXES =  {
   Firefox: 'moz',
   Opera: 'O',
@@ -2544,17 +2465,17 @@ module.exports = function(ElementCollect
     });
 
     return results;
   };
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../env":27}],24:[function(require,module,exports){
+},{"../../env":25}],22:[function(require,module,exports){
 'use strict';
 
 var callbacks = require('../../callbacks');
 var util = require('../../util');
 
 module.exports = function(ElementCollection) {
   var observeStyleChanges = function observeStyleChanges(element, stylesToObserve, onChange) {
     var oldStyles = {};
@@ -2778,17 +2699,17 @@ module.exports = function(ElementCollect
   ElementCollection._attachToOTHelpers.observeNodeOrChildNodeRemoval = function(
     element,
     onChange
   ) {
     return new ElementCollection(element).observeNodeOrChildNodeRemoval(onChange)[0];
   };
 };
 
-},{"../../callbacks":14,"../../util":36}],25:[function(require,module,exports){
+},{"../../callbacks":12,"../../util":34}],23:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var extensions = {
   attributes: require('./extensions/attributes'),
   css: require('./extensions/css'),
   classNames: require('./extensions/classNames'),
   observers: require('./extensions/observers')
@@ -3135,26 +3056,26 @@ extensions.observers(ElementCollection);
     var collection = new ElementCollection(element);
 
     return ElementCollection.prototype[methodName].apply(collection, args);
   };
 });
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../env":27,"../util":36,"./extensions/attributes":21,"./extensions/classNames":22,"./extensions/css":23,"./extensions/observers":24}],26:[function(require,module,exports){
+},{"../env":25,"../util":34,"./extensions/attributes":19,"./extensions/classNames":20,"./extensions/css":21,"./extensions/observers":22}],24:[function(require,module,exports){
 'use strict';
 
 var ElementCollection = require('./index');
 
 module.exports = function(selector, context) {
   return new ElementCollection(selector, context);
 };
 
-},{"./index":25}],27:[function(require,module,exports){
+},{"./index":23}],25:[function(require,module,exports){
 (function (process,global){
 'use strict';
 
 // OTHelpers.env
 //
 // Contains information about the current environment.
 // * **env.name** The name of the Environment (Chrome, FF, Node, etc)
 // * **env.version** Usually a Float, except in Node which uses a String
@@ -3294,17 +3215,17 @@ env._attachToOTHelpers.browser = functio
 };
 
 env._attachToOTHelpers.browserVersion = function() {
   return env;
 };
 
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"_process":81}],28:[function(require,module,exports){
+},{"_process":79}],26:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var env = require('./env');
 var util = require('./util');
 
 var getErrorLocation;
 
@@ -3546,34 +3467,34 @@ getErrorLocation = function getErrorLoca
   }
 
   if (err.message) info.message = err.message;
   return info;
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./env":27,"./util":36}],29:[function(require,module,exports){
+},{"./env":25,"./util":34}],27:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var capabilities = require('./capabilities');
 
 function isWebSocketSupported() {
   return 'WebSocket' in global && global.WebSocket !== void 0;
 }
 
 module.exports = isWebSocketSupported;
 
 // Indicates if the client supports WebSockets.
 capabilities.registerCapability('websockets', isWebSocketSupported);
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./capabilities":15}],30:[function(require,module,exports){
+},{"./capabilities":13}],28:[function(require,module,exports){
 'use strict';
 var util = require('../util');
 
 function toJson(object) {
   try {
     return JSON.stringify(object);
   } catch (e) {
     return object.toString();
@@ -3617,28 +3538,28 @@ module.exports = function toDebugStringI
   }
   else {
     components.push(object.toString());
   }
 
   return components.join(', ');
 };
 
-},{"../util":36}],31:[function(require,module,exports){
+},{"../util":34}],29:[function(require,module,exports){
 'use strict';
 
 var useLogHelpers = require('./mixin');
 var makeEverythingAttachToOTHelpers = require('../makeEverythingAttachToOTHelpers');
 
 var logging = exports;
 useLogHelpers(logging);
 
 makeEverythingAttachToOTHelpers(logging);
 
-},{"../makeEverythingAttachToOTHelpers":33,"./mixin":32}],32:[function(require,module,exports){
+},{"../makeEverythingAttachToOTHelpers":31,"./mixin":30}],30:[function(require,module,exports){
 'use strict';
 
 /* jshint browser:true, smarttabs:true */
 
 var env = require('../env');
 var toDebugStringIE9 = require('./debug_helper_ie9');
 
 var LEVELS_DESCRIPTORS = [
@@ -3732,30 +3653,30 @@ module.exports = function applyLoggerMix
     logging.debug('Setting the debug level to ' + level);
 
     return level;
   };
 
   logging.setLogLevel(logging.ERROR);
 };
 
-},{"../env":27,"./debug_helper_ie9":30}],33:[function(require,module,exports){
+},{"../env":25,"./debug_helper_ie9":28}],31:[function(require,module,exports){
 'use strict';
 
 module.exports = function(mod) {
   var attachments = {};
 
   for (var key in mod) {
     attachments[key] = mod[key];
   }
 
   mod._attachToOTHelpers = attachments;
 };
 
-},{}],34:[function(require,module,exports){
+},{}],32:[function(require,module,exports){
 'use strict';
 
 var $ = require('./elementCollection/shorthandSelector');
 var env = require('./env');
 var eventing = require('./behaviours/eventing');
 var util = require('./util');
 var uuid = require('uuid');
 
@@ -3850,17 +3771,17 @@ module.exports = function Modal(options)
     this.trigger('closed');
     this.element = domElement = null;
     return this;
   };
 
   this.element = domElement;
 };
 
-},{"./behaviours/eventing":9,"./elementCollection/shorthandSelector":26,"./env":27,"./util":36,"uuid":3}],35:[function(require,module,exports){
+},{"./behaviours/eventing":7,"./elementCollection/shorthandSelector":24,"./env":25,"./util":34,"uuid":3}],33:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var util = require('./util');
 
 var reqAnimationFrame = global.requestAnimationFrame ||
                         global.mozRequestAnimationFrame ||
                         global.webkitRequestAnimationFrame ||
@@ -3880,32 +3801,32 @@ if (reqAnimationFrame) {
     return id;
   };
 }
 
 module.exports = reqAnimationFrame;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./util":36}],36:[function(require,module,exports){
+},{"./util":34}],34:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var Bluebird = require('bluebird');
 var makeEverythingAttachToOTHelpers = require('./makeEverythingAttachToOTHelpers');
 
 var util = exports;
 
 /**
- * @license  Common JS Helpers on OpenTok v2.7.2 6a37d02 HEAD
+ * @license  Common JS Helpers on OpenTok v2.7.3 c273b40 HEAD
  * http://www.tokbox.com/
  *
- * Copyright (c) 2015 TokBox, Inc.
- *
- * Date: December 16 12:20:56 2015
+ * Copyright (c) 2016 TokBox, Inc.
+ *
+ * Date: February 03 09:39:35 2016
  *
  */
 
 
 // OT Helper Methods
 //
 // helpers.js                           <- the root file
 // helpers/lib/{helper topic}.js        <- specialised helpers for specific tasks/topics
@@ -4149,17 +4070,17 @@ util.makeSimplePromise = function(err) {
     }
   });
 };
 
 makeEverythingAttachToOTHelpers(util);
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./makeEverythingAttachToOTHelpers":33,"bluebird":72}],37:[function(require,module,exports){
+},{"./makeEverythingAttachToOTHelpers":31,"bluebird":70}],35:[function(require,module,exports){
 (function (process,global){
 'use strict';
 
 // TODO: We (tokbox) have modified this file. Is this still necessary?
 
 var makeEverythingAttachToOTHelpers = require('../lib/makeEverythingAttachToOTHelpers');
 
 var async = {};
@@ -4253,64 +4174,57 @@ async.waterfall = function(array, done) 
 
   next(async.iterator(array))();
 };
 
 makeEverythingAttachToOTHelpers(async);
 
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../lib/makeEverythingAttachToOTHelpers":33,"_process":81}],38:[function(require,module,exports){
+},{"../lib/makeEverythingAttachToOTHelpers":31,"_process":79}],36:[function(require,module,exports){
 (function (__dirname){
 var path = require('path');
 var pkg = require('./package.json');
 
 exports.version = pkg.publicVersion;
 exports.installer = path.join(__dirname, 'OpenTokPluginMain.msi');
 
 }).call(this,"/node_modules/@opentok/otplugin.js/node_modules/@opentok/opentok-plugin")
 
-},{"./package.json":39,"path":80}],39:[function(require,module,exports){
+},{"./package.json":37,"path":78}],37:[function(require,module,exports){
 module.exports={
   "name": "@opentok/opentok-plugin",
-  "version": "1.0.0-alpha.238",
-  "publicVersion": "1.0.0.238",
-  "_id": "@opentok/opentok-plugin@1.0.0-alpha.238",
+  "version": "1.0.0-alpha.240",
+  "publicVersion": "1.0.0.240",
+  "_id": "@opentok/opentok-plugin@1.0.0-alpha.240",
   "scripts": {},
-  "_shasum": "579a56a26fef20b5e76954d8f89cfd610f4365de",
+  "_shasum": "2ff5d8bc0fe8ddf4b38d0b8277fbb47d92a4628f",
   "_from": "@opentok/opentok-plugin@>=1.0.0-alpha.229 <2.0.0",
-  "_npmVersion": "2.12.1",
-  "_nodeVersion": "0.12.7",
+  "_npmVersion": "2.14.7",
+  "_nodeVersion": "4.2.2",
   "_npmUser": {
     "name": "opentok",
     "email": "adam@tokbox.com"
   },
   "dist": {
-    "shasum": "579a56a26fef20b5e76954d8f89cfd610f4365de",
-    "tarball": "http://registry.npmjs.org/@opentok/opentok-plugin/-/opentok-plugin-1.0.0-alpha.238.tgz"
+    "shasum": "2ff5d8bc0fe8ddf4b38d0b8277fbb47d92a4628f",
+    "tarball": "http://registry.npmjs.org/@opentok/opentok-plugin/-/opentok-plugin-1.0.0-alpha.240.tgz"
   },
   "maintainers": [
     {
       "name": "opentok",
       "email": "adam@tokbox.com"
     }
   ],
   "directories": {},
-  "_resolved": "https://registry.npmjs.org/@opentok/opentok-plugin/-/opentok-plugin-1.0.0-alpha.238.tgz"
-}
-
-},{}],40:[function(require,module,exports){
-/**
- * lodash 3.0.4 (Custom Build) <https://lodash.com/>
- * Build: `lodash modern modularize exports="npm" -o ./`
- * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
- * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
- * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
- * Available under MIT license <https://lodash.com/license>
- */
+  "_resolved": "https://registry.npmjs.org/@opentok/opentok-plugin/-/opentok-plugin-1.0.0-alpha.240.tgz"
+}
+
+},{}],38:[function(require,module,exports){
+/** Used as the `TypeError` message for "Functions" methods. */
 
 /** Used as the `TypeError` message for "Functions" methods. */
 var FUNC_ERROR_TEXT = 'Expected a function';
 
 /** Used for native method references. */
 var objectProto = Object.prototype;
 
 /** Used to check objects for own properties. */
@@ -4389,17 +4303,17 @@ function mapSet(key, value) {
  * provided it determines the cache key for storing the result based on the
  * arguments provided to the memoized function. By default, the first argument
  * provided to the memoized function is coerced to a string and used as the
  * cache key. The `func` is invoked with the `this` binding of the memoized
  * function.
  *
  * **Note:** The cache is exposed as the `cache` property on the memoized
  * function. Its creation may be customized by replacing the `_.memoize.Cache`
- * constructor with one whose instances implement the [`Map`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-properties-of-the-map-prototype-object)
+ * constructor with one whose instances implement the [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
  * method interface of `get`, `has`, and `set`.
  *
  * @static
  * @memberOf _
  * @category Function
  * @param {Function} func The function to have its output memoized.
  * @param {Function} [resolver] The function to resolve the cache key.
  * @returns {Function} Returns the new memoizing function.
@@ -4461,17 +4375,17 @@ MapCache.prototype.get = mapGet;
 MapCache.prototype.has = mapHas;
 MapCache.prototype.set = mapSet;
 
 // Assign cache to `_.memoize`.
 memoize.Cache = MapCache;
 
 module.exports = memoize;
 
-},{}],41:[function(require,module,exports){
+},{}],39:[function(require,module,exports){
 /**
  * lodash 3.0.1 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -4495,17 +4409,17 @@ var before = require('lodash.before');
  * // `initialize` invokes `createApplication` once
  */
 function once(func) {
   return before(2, func);
 }
 
 module.exports = once;
 
-},{"lodash.before":42}],42:[function(require,module,exports){
+},{"lodash.before":40}],40:[function(require,module,exports){
 /**
  * lodash 3.0.3 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -4548,25 +4462,25 @@ function before(n, func) {
       func = undefined;
     }
     return result;
   };
 }
 
 module.exports = before;
 
-},{}],43:[function(require,module,exports){
+},{}],41:[function(require,module,exports){
 // This file exists so users of this module can
 // get the path to the plugin installer contained
 // in the opentok-plugin module, and (likely)
 // copy it as part of a build process.
 
 module.exports = require('@opentok/opentok-plugin');
 
-},{"@opentok/opentok-plugin":38}],44:[function(require,module,exports){
+},{"@opentok/opentok-plugin":36}],42:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 var OTPlugin = {};
 module.exports = OTPlugin;
 global.OTPlugin = OTPlugin;
@@ -4744,17 +4658,17 @@ OTPlugin.RTCSessionDescription = RTCSess
 OTPlugin.RTCIceCandidate = RTCIceCandidate;
 
 OTPlugin.mediaDevices = new MediaDevices();
 
 module.exports = OTPlugin;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./auto_updater.js":45,"./logging.js":48,"./media_constraints.js":49,"./media_devices.js":50,"./media_stream.js":51,"./meta.js":52,"./peer_connection/peer_connection.js":59,"./plugin_proxies.js":61,"./readiness.js":63,"./rtc_ice_candidate.js":65,"./rtc_session_description.js":66,"./rumor_socket.js":68,"@opentok/ot-helpers":4}],45:[function(require,module,exports){
+},{"./auto_updater.js":43,"./logging.js":46,"./media_constraints.js":47,"./media_devices.js":48,"./media_stream.js":49,"./meta.js":50,"./peer_connection/peer_connection.js":57,"./plugin_proxies.js":59,"./readiness.js":61,"./rtc_ice_candidate.js":63,"./rtc_session_description.js":64,"./rumor_socket.js":66,"@opentok/ot-helpers":4}],43:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var curryCallAsync = require('./curry_call_async.js');
 var logging = require('./logging.js');
 var meta = require('./meta.js');
 
 // TODO: fix this super duper evil circular dependency between this project and webrtc-js via global
@@ -4894,46 +4808,46 @@ AutoUpdater.isinstalled = function() {
 AutoUpdater.installedVersion = function() {
   return meta.installedVersion();
 };
 
 module.exports = AutoUpdater;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./curry_call_async.js":46,"./logging.js":48,"./meta.js":52,"./plugin_proxies.js":61,"./version_greater_than.js":70,"@opentok/ot-helpers":4}],46:[function(require,module,exports){
+},{"./curry_call_async.js":44,"./logging.js":46,"./meta.js":50,"./plugin_proxies.js":59,"./version_greater_than.js":68,"@opentok/ot-helpers":4}],44:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function curryCallAsync(fn) {
   return function() {
     var args = Array.prototype.slice.call(arguments);
     args.unshift(fn);
     OTHelpers.callAsync.apply(OTHelpers, args);
   };
 };
 
-},{"@opentok/ot-helpers":4}],47:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],45:[function(require,module,exports){
 'use strict';
 
 // Magic number to avoid plugin crashes through a settimeout call
 module.exports = 3000;
 
-},{}],48:[function(require,module,exports){
+},{}],46:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 var logging = {};
 OTHelpers.useLogHelpers(logging);
 
 module.exports = logging;
 
-},{"@opentok/ot-helpers":4}],49:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],47:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function MediaConstraints(userConstraints) {
   var constraints = OTHelpers.clone(userConstraints);
 
   this.hasVideo = constraints.video !== void 0 && constraints.video !== false;
@@ -4967,17 +4881,17 @@ module.exports = function MediaConstrain
     else delete constraints.audio;
   };
 
   this.toHash = function() {
     return constraints;
   };
 };
 
-},{"@opentok/ot-helpers":4}],50:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],48:[function(require,module,exports){
 'use strict';
 
 var isReady = require('./readiness.js').isReady;
 var PluginProxies = require('./plugin_proxies.js');
 var registerReadyListner = require('./readiness.js').listen;
 
 // Exposes a enumerateDevices method and emits a devicechange event
 //
@@ -5013,17 +4927,17 @@ module.exports = function MediaDevices()
     if (isReady()) {
       PluginProxies.mediaCapturer.off('devicesChanged', fn, context);
     }
   };
 
   return api;
 };
 
-},{"./plugin_proxies.js":61,"./readiness.js":63}],51:[function(require,module,exports){
+},{"./plugin_proxies.js":59,"./readiness.js":61}],49:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var VideoContainer = require('./video_container.js');
 
 var MediaStreamTrack = function(mediaStreamId, options, plugin) {
   var Proto = function MediaStreamTrack() {},
       api = new Proto();
@@ -5131,17 +5045,17 @@ var MediaStream = function(options, plug
 
 MediaStream.fromJson = function(json, plugin) {
   if (!json) return null;
   return new MediaStream(JSON.parse(json), plugin);
 };
 
 module.exports = MediaStream;
 
-},{"./video_container.js":71,"@opentok/ot-helpers":4}],52:[function(require,module,exports){
+},{"./video_container.js":69,"@opentok/ot-helpers":4}],50:[function(require,module,exports){
 'use strict';
 
 /* global ActiveXObject */
 
 var OTHelpers = require('@opentok/ot-helpers');
 var opentokPlugin = require('../../opentok-plugin');
 var versionGreaterThan = require('./version_greater_than.js');
 
@@ -5262,17 +5176,17 @@ meta.pathToInstaller = function(callback
     // Give it to them straight up
     callback(installerPath);
   } else {
   // queue up to give it to them later
     waitingForInstallerPath.push(callback);
   }
 };
 
-},{"../../opentok-plugin":43,"./version_greater_than.js":70,"@opentok/ot-helpers":4}],53:[function(require,module,exports){
+},{"../../opentok-plugin":41,"./version_greater_than.js":68,"@opentok/ot-helpers":4}],51:[function(require,module,exports){
 'use strict';
 
 var createSingleElementContainer = require('./create_single_element_container.js');
 var curryCallAsync = require('../../curry_call_async.js');
 var once = require('lodash.once');
 var Promise = require('bluebird');
 var requestManager = require('./request_manager.js');
 var RTCStatsReport = require('../../rtc_stats_report.js');
@@ -5316,17 +5230,17 @@ module.exports = function(pluginElement,
         error: reject
       });
 
       pluginElement.getStats(id, mediaStreamTrack);
     });
   });
 };
 
-},{"../../curry_call_async.js":46,"../../rtc_stats_report.js":67,"./create_single_element_container.js":55,"./request_manager.js":57,"bluebird":72,"lodash.once":41}],54:[function(require,module,exports){
+},{"../../curry_call_async.js":44,"../../rtc_stats_report.js":65,"./create_single_element_container.js":53,"./request_manager.js":55,"bluebird":70,"lodash.once":39}],52:[function(require,module,exports){
 'use strict';
 
 // A resolver for lodash.memoize (its second argument, see lodash docs) that assigns a consistent
 // number to any object/value passed to it by storing an array of seen objects and looking up the
 // index of the of the current object/value.
 
 module.exports = function() {
   var seenObjects = [];
@@ -5338,17 +5252,17 @@ module.exports = function() {
       seenIndex = seenObjects.length;
       seenObjects.push(obj);
     }
 
     return String(seenIndex);
   };
 };
 
-},{}],55:[function(require,module,exports){
+},{}],53:[function(require,module,exports){
 'use strict';
 
 var assert = require('assert');
 
 // A single element container. Simply models an array, delegating push and pop, and asserting if
 // you try to store more than one element or pop when empty.
 //
 // When this is used, the alternative would be to either forgo the protective assertions or do
@@ -5383,17 +5297,17 @@ module.exports = function() {
   container.pop = function() {
     assert(elements.length === 1);
     return elements.pop();
   };
 
   return container;
 };
 
-},{"assert":73}],56:[function(require,module,exports){
+},{"assert":71}],54:[function(require,module,exports){
 'use strict';
 
 // core modules
 var assert = require('assert');
 
 // Takes a function of the form function() => promise (no args and returns a promise) and returns
 // a function of the same form which prevents concurrent requests by returning the pending result
 // when additional requests are made before the first is fulfilled.
@@ -5411,17 +5325,17 @@ module.exports = function(request) {
     responsePromise = request().finally(function() {
       responsePromise = undefined;
     });
 
     return responsePromise;
   };
 };
 
-},{"assert":73}],57:[function(require,module,exports){
+},{"assert":71}],55:[function(require,module,exports){
 'use strict';
 
 var createLinearResolver = require('./create_linear_resolver.js');
 var memoize = require('lodash.memoize');
 var requestCompressor = require('./request_compressor.js');
 var requestSerializer = require('./request_serializer.js');
 
 // Takes a function of the form function(input?) => promise (one optional argument and returns a
@@ -5455,17 +5369,17 @@ module.exports = function(request) {
   var getCompressedRequest = memoize(rawGetCompressedRequest, createLinearResolver());
 
   return function(input) {
     var compressedRequest = getCompressedRequest(input);
     return compressedRequest();
   };
 };
 
-},{"./create_linear_resolver.js":54,"./request_compressor.js":56,"./request_serializer.js":58,"lodash.memoize":40}],58:[function(require,module,exports){
+},{"./create_linear_resolver.js":52,"./request_compressor.js":54,"./request_serializer.js":56,"lodash.memoize":38}],56:[function(require,module,exports){
 'use strict';
 
 // core modules
 var assert = require('assert');
 
 // community modules
 var Promise = require('bluebird');
 
@@ -5509,17 +5423,17 @@ module.exports = function(request) {
     });
 
     job.resolve(responsePromise);
   };
 
   return serializedRequest;
 };
 
-},{"assert":73,"bluebird":72}],59:[function(require,module,exports){
+},{"assert":71,"bluebird":70}],57:[function(require,module,exports){
 'use strict';
 
 var bluebird = require('bluebird');
 
 var uuid = require('uuid');
 var curryCallAsync = require('../curry_call_async.js');
 var createGetStatsAdaptor = require('./create_get_stats_adaptor/create_get_stats_adaptor.js');
 var empiricDelay = require('../empiric_delay.js');
@@ -5834,17 +5748,17 @@ var PeerConnection = function(iceServers
 };
 
 PeerConnection.create = function(iceServers, options, plugin, ready) {
   new PeerConnection(iceServers, options, plugin, ready);
 };
 
 module.exports = PeerConnection;
 
-},{"../curry_call_async.js":46,"../empiric_delay.js":47,"../logging.js":48,"../media_stream":51,"../rtc_ice_candidate.js":65,"../rtc_session_description.js":66,"./create_get_stats_adaptor/create_get_stats_adaptor.js":53,"@opentok/ot-helpers":4,"bluebird":72,"uuid":111}],60:[function(require,module,exports){
+},{"../curry_call_async.js":44,"../empiric_delay.js":45,"../logging.js":46,"../media_stream":49,"../rtc_ice_candidate.js":63,"../rtc_session_description.js":64,"./create_get_stats_adaptor/create_get_stats_adaptor.js":51,"@opentok/ot-helpers":4,"bluebird":70,"uuid":109}],58:[function(require,module,exports){
 'use strict';
 
 var curryCallAsync = require('./curry_call_async.js');
 var empiricDelay = require('./empiric_delay.js');
 var logging = require('./logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var TaskQueue = require('./task_queue.js');
 
@@ -5958,17 +5872,17 @@ module.exports = function pluginEventing
       }
 
       logging.debug('Plugin ' + api.id + ' is loaded');
       completion(void 0, api);
     });
   };
 };
 
-},{"./curry_call_async.js":46,"./empiric_delay.js":47,"./logging.js":48,"./task_queue.js":69,"@opentok/ot-helpers":4}],61:[function(require,module,exports){
+},{"./curry_call_async.js":44,"./empiric_delay.js":45,"./logging.js":46,"./task_queue.js":67,"@opentok/ot-helpers":4}],59:[function(require,module,exports){
 'use strict';
 
 var meta = require('./meta.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var proxyExtras = require('./proxy_extras.js');
 
 var Proto = function PluginProxies() {},
     api = new Proto(),
@@ -6054,17 +5968,17 @@ api.createMediaCapturer = function creat
     completion.call(void 0, err, api.mediaCapturer);
   });
 
   proxyExtras.makeMediaCapturerProxy(api.mediaCapturer);
 };
 
 module.exports = api;
 
-},{"./meta.js":52,"./proxy_extras.js":62,"@opentok/ot-helpers":4}],62:[function(require,module,exports){
+},{"./meta.js":50,"./proxy_extras.js":60,"@opentok/ot-helpers":4}],60:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var uuid = require('uuid');
 var OTHelpers = require('@opentok/ot-helpers');
 var pluginEventingBehaviour = require('./plugin_eventing_behaviour.js');
 var refCountBehaviour = require('./ref_count_behaviour.js');
 
@@ -6281,17 +6195,17 @@ var makeMediaPeerProxy = function makeMe
 module.exports = {
   createPluginProxy: createPluginProxy,
   makeMediaPeerProxy: makeMediaPeerProxy,
   makeMediaCapturerProxy: makeMediaCapturerProxy
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./plugin_eventing_behaviour.js":60,"./ref_count_behaviour.js":64,"@opentok/ot-helpers":4,"uuid":111}],63:[function(require,module,exports){
+},{"./plugin_eventing_behaviour.js":58,"./ref_count_behaviour.js":62,"@opentok/ot-helpers":4,"uuid":109}],61:[function(require,module,exports){
 'use strict';
 
 var AutoUpdater = require('./auto_updater.js');
 var logging = require('./logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var PluginProxies = require('./plugin_proxies.js');
 
 var isReady = false;
@@ -6362,17 +6276,17 @@ var destroy = function destroy() {
 
 OTHelpers.onDOMLoad(onDomReady);
 
 module.exports = {
   listen: registerReadyListener,
   isReady: function() { return isReady; }
 };
 
-},{"./auto_updater.js":45,"./logging.js":48,"./plugin_proxies.js":61,"@opentok/ot-helpers":4}],64:[function(require,module,exports){
+},{"./auto_updater.js":43,"./logging.js":46,"./plugin_proxies.js":59,"@opentok/ot-helpers":4}],62:[function(require,module,exports){
 'use strict';
 
 module.exports = function refCountBehaviour(api) {
   var _liveObjects = [];
 
   api.addRef = function(ref) {
     _liveObjects.push(ref);
     return api;
@@ -6395,36 +6309,36 @@ module.exports = function refCountBehavi
 
   api.removeAllRefs = function() {
     while (_liveObjects.length) {
       _liveObjects.shift().destroy();
     }
   };
 };
 
-},{}],65:[function(require,module,exports){
+},{}],63:[function(require,module,exports){
 'use strict';
 
 // A RTCIceCandidate like object exposed for native WebRTC compatability
 module.exports = function RTCIceCandidate(options) {
   this.sdpMid = options.sdpMid;
   this.sdpMLineIndex = parseInt(options.sdpMLineIndex, 10);
   this.candidate = options.candidate;
 };
 
-},{}],66:[function(require,module,exports){
+},{}],64:[function(require,module,exports){
 'use strict';
 
 // A RTCSessionDescription like object exposed for native WebRTC compatability
 module.exports = function RTCSessionDescription(options) {
   this.type = options.type;
   this.sdp = options.sdp;
 };
 
-},{}],67:[function(require,module,exports){
+},{}],65:[function(require,module,exports){
 'use strict';
 
 var RTCStatsReport = function RTCStatsReport(reports) {
   for (var id in reports) {
     if (reports.hasOwnProperty(id)) {
       this[id] = reports[id];
     }
   }
@@ -6435,17 +6349,17 @@ RTCStatsReport.prototype.forEach = funct
     if (this.hasOwnProperty(id)) {
       callback.call(context, this[id]);
     }
   }
 };
 
 module.exports = RTCStatsReport;
 
-},{}],68:[function(require,module,exports){
+},{}],66:[function(require,module,exports){
 'use strict';
 
 var curryCallAsync = require('./curry_call_async.js');
 var logging = require('./logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function RumorSocket(plugin, server) {
   var Proto = function RumorSocket() {},
@@ -6517,17 +6431,17 @@ module.exports = function RumorSocket(pl
 
     // We're done. Clean up ourselves
     plugin.removeRef(api);
   }));
 
   return api;
 };
 
-},{"./curry_call_async.js":46,"./logging.js":48,"@opentok/ot-helpers":4}],69:[function(require,module,exports){
+},{"./curry_call_async.js":44,"./logging.js":46,"@opentok/ot-helpers":4}],67:[function(require,module,exports){
 'use strict';
 
 module.exports = function TaskQueue() {
   var Proto = function TaskQueue() {},
       api = new Proto(),
       tasks = [];
 
   api.add = function(fn, context) {
@@ -6543,17 +6457,17 @@ module.exports = function TaskQueue() {
     while ((task = tasks.shift())) {
       task();
     }
   };
 
   return api;
 };
 
-},{}],70:[function(require,module,exports){
+},{}],68:[function(require,module,exports){
 'use strict';
 
 module.exports = function versionGreaterThan(version1, version2) {
   if (version1 === version2) return false;
   if (version1 === -1) return version2;
   if (version2 === -1) return version1;
 
   if (version1.indexOf('.') === -1 && version2.indexOf('.') === -1) {
@@ -6579,17 +6493,17 @@ module.exports = function versionGreater
   // that someone changed versioning systems.
   if (i < v1.length) {
     return true;
   }
 
   return false;
 };
 
-},{}],71:[function(require,module,exports){
+},{}],69:[function(require,module,exports){
 'use strict';
 
 var logging = require('./logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function VideoContainer(plugin, stream) {
   var Proto = function VideoContainer() {},
       api = new Proto(),
@@ -6682,17 +6596,17 @@ module.exports = function VideoContainer
   api.destroy = function() {
     plugin._.setStream(null);
     plugin.removeRef(api);
   };
 
   return api;
 };
 
-},{"./logging.js":48,"@opentok/ot-helpers":4}],72:[function(require,module,exports){
+},{"./logging.js":46,"@opentok/ot-helpers":4}],70:[function(require,module,exports){
 (function (process,global){
 /* @preserve
  * The MIT License (MIT)
  * 
  * Copyright (c) 2013-2015 Petka Antonov
  * 
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -11573,17 +11487,17 @@ if (ret.isNode) ret.toFastProperties(pro
 
 try {throw new Error(); } catch (e) {ret.lastLineError = e;}
 module.exports = ret;
 
 },{"./es5.js":14}]},{},[4])(4)
 });                    ;if (typeof window !== 'undefined' && window !== null) {                               window.P = window.Promise;                                                     } else if (typeof self !== 'undefined' && self !== null) {                             self.P = self.Promise;                                                         }
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"_process":81}],73:[function(require,module,exports){
+},{"_process":79}],71:[function(require,module,exports){
 // http://wiki.commonjs.org/wiki/Unit_Testing/1.0
 //
 // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
 //
 // Originally from narwhal.js (http://narwhaljs.org)
 // Copyright (c) 2009 Thomas Robinson <280north.com>
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -11934,26 +11848,28 @@ assert.ifError = function(err) { if (err
 var objectKeys = Object.keys || function (obj) {
   var keys = [];
   for (var key in obj) {
     if (hasOwn.call(obj, key)) keys.push(key);
   }
   return keys;
 };
 
-},{"util/":83}],74:[function(require,module,exports){
+},{"util/":81}],72:[function(require,module,exports){
 (function (global){
 /*!
  * The buffer module from node.js, for the browser.
  *
  * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
  * @license  MIT
  */
 /* eslint-disable no-proto */
 
+'use strict'
+
 var base64 = require('base64-js')
 var ieee754 = require('ieee754')
 var isArray = require('isarray')
 
 exports.Buffer = Buffer
 exports.SlowBuffer = SlowBuffer
 exports.INSPECT_MAX_BYTES = 50
 Buffer.poolSize = 8192 // not used by this implementation
@@ -12026,18 +11942,20 @@ function kMaxLength () {
  */
 function Buffer (arg) {
   if (!(this instanceof Buffer)) {
     // Avoid going through an ArgumentsAdaptorTrampoline in the common case.
     if (arguments.length > 1) return new Buffer(arg, arguments[1])
     return new Buffer(arg)
   }
 
-  this.length = 0
-  this.parent = undefined
+  if (!Buffer.TYPED_ARRAY_SUPPORT) {
+    this.length = 0
+    this.parent = undefined
+  }
 
   // Common case.
   if (typeof arg === 'number') {
     return fromNumber(this, arg)
   }
 
   // Slightly less common case.
   if (typeof arg === 'string') {
@@ -12158,16 +12076,20 @@ function fromJsonObject (that, object) {
     that[i] = array[i] & 255
   }
   return that
 }
 
 if (Buffer.TYPED_ARRAY_SUPPORT) {
   Buffer.prototype.__proto__ = Uint8Array.prototype
   Buffer.__proto__ = Uint8Array
+} else {
+  // pre-set for values that may exist in the future
+  Buffer.prototype.length = undefined
+  Buffer.prototype.parent = undefined
 }
 
 function allocate (that, length) {
   if (Buffer.TYPED_ARRAY_SUPPORT) {
     // Return an augmented `Uint8Array` instance, for best performance
     that = Buffer._augment(new Uint8Array(length))
     that.__proto__ = Buffer.prototype
   } else {
@@ -12308,20 +12230,16 @@ function byteLength (string, encoding) {
         if (loweredCase) return utf8ToBytes(string).length // assume utf8
         encoding = ('' + encoding).toLowerCase()
         loweredCase = true
     }
   }
 }
 Buffer.byteLength = byteLength
 
-// pre-set for values that may exist in the future
-Buffer.prototype.length = undefined
-Buffer.prototype.parent = undefined
-
 function slowToString (encoding, start, end) {
   var loweredCase = false
 
   start = start | 0
   end = end === undefined || end === Infinity ? this.length : end | 0
 
   if (!encoding) encoding = 'utf8'
   if (start < 0) start = 0
@@ -13483,17 +13401,17 @@ function blitBuffer (src, dst, offset, l
     if ((i + offset >= dst.length) || (i >= src.length)) break
     dst[i + offset] = src[i]
   }
   return i
 }
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"base64-js":75,"ieee754":76,"isarray":77}],75:[function(require,module,exports){
+},{"base64-js":73,"ieee754":74,"isarray":75}],73:[function(require,module,exports){
 var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
 
 ;(function (exports) {
 	'use strict';
 
   var Arr = (typeof Uint8Array !== 'undefined')
     ? Uint8Array
     : Array
@@ -13609,17 +13527,17 @@ var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ
 
 		return output
 	}
 
 	exports.toByteArray = b64ToByteArray
 	exports.fromByteArray = uint8ToBase64
 }(typeof exports === 'undefined' ? (this.base64js = {}) : exports))
 
-},{}],76:[function(require,module,exports){
+},{}],74:[function(require,module,exports){
 exports.read = function (buffer, offset, isLE, mLen, nBytes) {
   var e, m
   var eLen = nBytes * 8 - mLen - 1
   var eMax = (1 << eLen) - 1
   var eBias = eMax >> 1
   var nBits = -7
   var i = isLE ? (nBytes - 1) : 0
   var d = isLE ? -1 : 1
@@ -13695,24 +13613,24 @@ exports.write = function (buffer, value,
 
   e = (e << mLen) | m
   eLen += mLen
   for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
 
   buffer[offset + i - d] |= s * 128
 }
 
-},{}],77:[function(require,module,exports){
+},{}],75:[function(require,module,exports){
 var toString = {}.toString;
 
 module.exports = Array.isArray || function (arr) {
   return toString.call(arr) == '[object Array]';
 };
 
-},{}],78:[function(require,module,exports){
+},{}],76:[function(require,module,exports){
 // Copyright Joyent, Inc. and other Node contributors.
 //
 // 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
@@ -14005,17 +13923,17 @@ function isNumber(arg) {
 function isObject(arg) {
   return typeof arg === 'object' && arg !== null;
 }
 
 function isUndefined(arg) {
   return arg === void 0;
 }
 
-},{}],79:[function(require,module,exports){
+},{}],77:[function(require,module,exports){
 if (typeof Object.create === 'function') {
   // implementation from standard node.js 'util' module
   module.exports = function inherits(ctor, superCtor) {
     ctor.super_ = superCtor
     ctor.prototype = Object.create(superCtor.prototype, {
       constructor: {
         value: ctor,
         enumerable: false,
@@ -14030,17 +13948,17 @@ if (typeof Object.create === 'function')
     ctor.super_ = superCtor
     var TempCtor = function () {}
     TempCtor.prototype = superCtor.prototype
     ctor.prototype = new TempCtor()
     ctor.prototype.constructor = ctor
   }
 }
 
-},{}],80:[function(require,module,exports){
+},{}],78:[function(require,module,exports){
 (function (process){
 // Copyright Joyent, Inc. and other Node contributors.
 //
 // 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
@@ -14259,17 +14177,17 @@ var substr = 'ab'.substr(-1) === 'b'
     : function (str, start, len) {
         if (start < 0) start = str.length + start;
         return str.substr(start, len);
     }
 ;
 
 }).call(this,require('_process'))
 
-},{"_process":81}],81:[function(require,module,exports){
+},{"_process":79}],79:[function(require,module,exports){
 // shim for using process in browser
 
 var process = module.exports = {};
 var queue = [];
 var draining = false;
 var currentQueue;
 var queueIndex = -1;
 
@@ -14352,24 +14270,24 @@ process.binding = function (name) {
 };
 
 process.cwd = function () { return '/' };
 process.chdir = function (dir) {
     throw new Error('process.chdir is not supported');
 };
 process.umask = function() { return 0; };
 
-},{}],82:[function(require,module,exports){
+},{}],80:[function(require,module,exports){
 module.exports = function isBuffer(arg) {
   return arg && typeof arg === 'object'
     && typeof arg.copy === 'function'
     && typeof arg.fill === 'function'
     && typeof arg.readUInt8 === 'function';
 }
-},{}],83:[function(require,module,exports){
+},{}],81:[function(require,module,exports){
 (function (process,global){
 // Copyright Joyent, Inc. and other Node contributors.
 //
 // 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
@@ -14950,17 +14868,17 @@ exports._extend = function(origin, add) 
 };
 
 function hasOwnProperty(obj, prop) {
   return Object.prototype.hasOwnProperty.call(obj, prop);
 }
 
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./support/isBuffer":82,"_process":81,"inherits":79}],84:[function(require,module,exports){
+},{"./support/isBuffer":80,"_process":79,"inherits":77}],82:[function(require,module,exports){
 /**
  * lodash 3.2.0 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15032,17 +14950,17 @@ function assignWith(object, source, cust
 var assign = createAssigner(function(object, source, customizer) {
   return customizer
     ? assignWith(object, source, customizer)
     : baseAssign(object, source);
 });
 
 module.exports = assign;
 
-},{"lodash._baseassign":85,"lodash._createassigner":87,"lodash.keys":91}],85:[function(require,module,exports){
+},{"lodash._baseassign":83,"lodash._createassigner":85,"lodash.keys":89}],83:[function(require,module,exports){
 /**
  * lodash 3.2.0 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15061,17 +14979,17 @@ var baseCopy = require('lodash._basecopy
 function baseAssign(object, source) {
   return source == null
     ? object
     : baseCopy(source, keys(source), object);
 }
 
 module.exports = baseAssign;
 
-},{"lodash._basecopy":86,"lodash.keys":91}],86:[function(require,module,exports){
+},{"lodash._basecopy":84,"lodash.keys":89}],84:[function(require,module,exports){
 /**
  * lodash 3.0.1 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15095,17 +15013,17 @@ function baseCopy(source, props, object)
     var key = props[index];
     object[key] = source[key];
   }
   return object;
 }
 
 module.exports = baseCopy;
 
-},{}],87:[function(require,module,exports){
+},{}],85:[function(require,module,exports){
 /**
  * lodash 3.1.1 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15149,17 +15067,17 @@ function createAssigner(assigner) {
       }
     }
     return object;
   });
 }
 
 module.exports = createAssigner;
 
-},{"lodash._bindcallback":88,"lodash._isiterateecall":89,"lodash.restparam":90}],88:[function(require,module,exports){
+},{"lodash._bindcallback":86,"lodash._isiterateecall":87,"lodash.restparam":88}],86:[function(require,module,exports){
 /**
  * lodash 3.0.1 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15216,17 +15134,17 @@ function bindCallback(func, thisArg, arg
  * // => true
  */
 function identity(value) {
   return value;
 }
 
 module.exports = bindCallback;
 
-},{}],89:[function(require,module,exports){
+},{}],87:[function(require,module,exports){
 /**
  * lodash 3.0.9 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15350,17 +15268,17 @@ function isObject(value) {
   // Avoid a V8 JIT bug in Chrome 19-20.
   // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
   var type = typeof value;
   return !!value && (type == 'object' || type == 'function');
 }
 
 module.exports = isIterateeCall;
 
-},{}],90:[function(require,module,exports){
+},{}],88:[function(require,module,exports){
 /**
  * lodash 3.6.1 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15419,17 +15337,17 @@ function restParam(func, start) {
     }
     otherArgs[start] = rest;
     return func.apply(this, otherArgs);
   };
 }
 
 module.exports = restParam;
 
-},{}],91:[function(require,module,exports){
+},{}],89:[function(require,module,exports){
 /**
  * lodash 3.1.2 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15657,17 +15575,17 @@ function keysIn(object) {
       result.push(key);
     }
   }
   return result;
 }
 
 module.exports = keys;
 
-},{"lodash._getnative":92,"lodash.isarguments":93,"lodash.isarray":94}],92:[function(require,module,exports){
+},{"lodash._getnative":90,"lodash.isarguments":91,"lodash.isarray":92}],90:[function(require,module,exports){
 /**
  * lodash 3.9.1 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -15796,53 +15714,51 @@ function isNative(value) {
   if (isFunction(value)) {
     return reIsNative.test(fnToString.call(value));
   }
   return isObjectLike(value) && reIsHostCtor.test(value);
 }
 
 module.exports = getNative;
 
-},{}],93:[function(require,module,exports){
-/**
- * lodash 3.0.4 (Custom Build) <https://lodash.com/>
- * Build: `lodash modern modularize exports="npm" -o ./`
- * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+},{}],91:[function(require,module,exports){
+(function (global){
+/**
+ * lodash 3.0.5 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize exports="npm" -o ./`
+ * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
- * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
 
-/**
- * Checks if `value` is object-like.
- *
- * @private
- * @param {*} value The value to check.
- * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
- */
-function isObjectLike(value) {
-  return !!value && typeof value == 'object';
-}
-
-/** Used for native method references. */
-var objectProto = Object.prototype;
+/** Used as references for various `Number` constants. */
+var MAX_SAFE_INTEGER = 9007199254740991;
+
+/** `Object#toString` result references. */
+var argsTag = '[object Arguments]',
+    funcTag = '[object Function]',
+    genTag = '[object GeneratorFunction]';
+
+/** Used for built-in method references. */
+var objectProto = global.Object.prototype;
 
 /** Used to check objects for own properties. */
 var hasOwnProperty = objectProto.hasOwnProperty;
 
-/** Native method references. */
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objectToString = objectProto.toString;
+
+/** Built-in value references. */
 var propertyIsEnumerable = objectProto.propertyIsEnumerable;
 
 /**
- * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
- * of an array-like value.
- */
-var MAX_SAFE_INTEGER = 9007199254740991;
-
-/**
  * The base implementation of `_.property` without support for deep paths.
  *
  * @private
  * @param {string} key The key of the property to get.
  * @returns {Function} Returns the new function.
  */
 function baseProperty(key) {
   return function(object) {
@@ -15858,63 +15774,209 @@ function baseProperty(key) {
  *
  * @private
  * @param {Object} object The object to query.
  * @returns {*} Returns the "length" value.
  */
 var getLength = baseProperty('length');
 
 /**
- * Checks if `value` is array-like.
- *
- * @private
- * @param {*} value The value to check.
- * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
- */
-function isArrayLike(value) {
-  return value != null && isLength(getLength(value));
-}
-
-/**
- * Checks if `value` is a valid array-like length.
- *
- * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
- *
- * @private
- * @param {*} value The value to check.
- * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
- */
-function isLength(value) {
-  return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
-}
-
-/**
- * Checks if `value` is classified as an `arguments` object.
+ * Checks if `value` is likely an `arguments` object.
  *
  * @static
  * @memberOf _
  * @category Lang
  * @param {*} value The value to check.
  * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
  * @example
  *
  * _.isArguments(function() { return arguments; }());
  * // => true
  *
  * _.isArguments([1, 2, 3]);
  * // => false
  */
 function isArguments(value) {
-  return isObjectLike(value) && isArrayLike(value) &&
-    hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
+  // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode.
+  return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
+    (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
+}
+
+/**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
+function isArrayLike(value) {
+  return value != null &&
+    !(typeof value == 'function' && isFunction(value)) && isLength(getLength(value));
+}
+
+/**
+ * This method is like `_.isArrayLike` except that it also checks if `value`
+ * is an object.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array-like object, else `false`.
+ * @example
+ *
+ * _.isArrayLikeObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLikeObject(document.body.children);
+ * // => true
+ *
+ * _.isArrayLikeObject('abc');
+ * // => false
+ *
+ * _.isArrayLikeObject(_.noop);
+ * // => false
+ */
+function isArrayLikeObject(value) {
+  return isObjectLike(value) && isArrayLike(value);
+}
+
+/**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+function isFunction(value) {
+  // The use of `Object#toString` avoids issues with the `typeof` operator
+  // in Safari 8 which returns 'object' for typed array constructors, and
+  // PhantomJS 1.9 which returns 'function' for `NodeList` instances.
+  var tag = isObject(value) ? objectToString.call(value) : '';
+  return tag == funcTag || tag == genTag;
+}
+
+/**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This function is loosely based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ * @example
+ *
+ * _.isLength(3);
+ * // => true
+ *
+ * _.isLength(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isLength(Infinity);
+ * // => false
+ *
+ * _.isLength('3');
+ * // => false
+ */
+function isLength(value) {
+  return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+}
+
+/**
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+function isObject(value) {
+  // Avoid a V8 JIT bug in Chrome 19-20.
+  // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
+  var type = typeof value;
+  return !!value && (type == 'object' || type == 'function');
+}
+
+/**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+function isObjectLike(value) {
+  return !!value && typeof value == 'object';
 }
 
 module.exports = isArguments;
 
-},{}],94:[function(require,module,exports){
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],92:[function(require,module,exports){
 /**
  * lodash 3.0.4 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -16086,17 +16148,17 @@ function isNative(value) {
   if (isFunction(value)) {
     return reIsNative.test(fnToString.call(value));
   }
   return isObjectLike(value) && reIsHostCtor.test(value);
 }
 
 module.exports = isArray;
 
-},{}],95:[function(require,module,exports){
+},{}],93:[function(require,module,exports){
 /**
  * lodash 3.1.0 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.2 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -16138,17 +16200,17 @@ var pick = restParam(function(object, pr
   }
   return typeof props[0] == 'function'
     ? pickByCallback(object, bindCallback(props[0], props[1], 3))
     : pickByArray(object, baseFlatten(props));
 });
 
 module.exports = pick;
 
-},{"lodash._baseflatten":96,"lodash._bindcallback":99,"lodash._pickbyarray":100,"lodash._pickbycallback":101,"lodash.restparam":106}],96:[function(require,module,exports){
+},{"lodash._baseflatten":94,"lodash._bindcallback":97,"lodash._pickbyarray":98,"lodash._pickbycallback":99,"lodash.restparam":104}],94:[function(require,module,exports){
 /**
  * lodash 3.1.4 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -16271,23 +16333,273 @@ function isArrayLike(value) {
  * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
  */
 function isLength(value) {
   return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
 }
 
 module.exports = baseFlatten;
 
-},{"lodash.isarguments":97,"lodash.isarray":98}],97:[function(require,module,exports){
-arguments[4][93][0].apply(exports,arguments)
-},{"dup":93}],98:[function(require,module,exports){
-arguments[4][94][0].apply(exports,arguments)
-},{"dup":94}],99:[function(require,module,exports){
-arguments[4][88][0].apply(exports,arguments)
-},{"dup":88}],100:[function(require,module,exports){
+},{"lodash.isarguments":95,"lodash.isarray":96}],95:[function(require,module,exports){
+(function (global){
+/**
+ * lodash 3.0.5 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize exports="npm" -o ./`
+ * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+
+/** Used as references for various `Number` constants. */
+var MAX_SAFE_INTEGER = 9007199254740991;
+
+/** `Object#toString` result references. */
+var argsTag = '[object Arguments]',
+    funcTag = '[object Function]',
+    genTag = '[object GeneratorFunction]';
+
+/** Used for built-in method references. */
+var objectProto = global.Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objectToString = objectProto.toString;
+
+/** Built-in value references. */
+var propertyIsEnumerable = objectProto.propertyIsEnumerable;
+
+/**
+ * The base implementation of `_.property` without support for deep paths.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @returns {Function} Returns the new function.
+ */
+function baseProperty(key) {
+  return function(object) {
+    return object == null ? undefined : object[key];
+  };
+}
+
+/**
+ * Gets the "length" property value of `object`.
+ *
+ * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
+ * that affects Safari on at least iOS 8.1-8.3 ARM64.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {*} Returns the "length" value.
+ */
+var getLength = baseProperty('length');
+
+/**
+ * Checks if `value` is likely an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+function isArguments(value) {
+  // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode.
+  return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
+    (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
+}
+
+/**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
+function isArrayLike(value) {
+  return value != null &&
+    !(typeof value == 'function' && isFunction(value)) && isLength(getLength(value));
+}
+
+/**
+ * This method is like `_.isArrayLike` except that it also checks if `value`
+ * is an object.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array-like object, else `false`.
+ * @example
+ *
+ * _.isArrayLikeObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLikeObject(document.body.children);
+ * // => true
+ *
+ * _.isArrayLikeObject('abc');
+ * // => false
+ *
+ * _.isArrayLikeObject(_.noop);
+ * // => false
+ */
+function isArrayLikeObject(value) {
+  return isObjectLike(value) && isArrayLike(value);
+}
+
+/**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+function isFunction(value) {
+  // The use of `Object#toString` avoids issues with the `typeof` operator
+  // in Safari 8 which returns 'object' for typed array constructors, and
+  // PhantomJS 1.9 which returns 'function' for `NodeList` instances.
+  var tag = isObject(value) ? objectToString.call(value) : '';
+  return tag == funcTag || tag == genTag;
+}
+
+/**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This function is loosely based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ * @example
+ *
+ * _.isLength(3);
+ * // => true
+ *
+ * _.isLength(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isLength(Infinity);
+ * // => false
+ *
+ * _.isLength('3');
+ * // => false
+ */
+function isLength(value) {
+  return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+}
+
+/**
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+function isObject(value) {
+  // Avoid a V8 JIT bug in Chrome 19-20.
+  // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
+  var type = typeof value;
+  return !!value && (type == 'object' || type == 'function');
+}
+
+/**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+function isObjectLike(value) {
+  return !!value && typeof value == 'object';
+}
+
+module.exports = isArguments;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],96:[function(require,module,exports){
+arguments[4][92][0].apply(exports,arguments)
+},{"dup":92}],97:[function(require,module,exports){
+arguments[4][86][0].apply(exports,arguments)
+},{"dup":86}],98:[function(require,module,exports){
 /**
  * lodash 3.0.2 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -16352,17 +16664,17 @@ function isObject(value) {
   // Avoid a V8 JIT bug in Chrome 19-20.
   // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
   var type = typeof value;
   return !!value && (type == 'object' || type == 'function');
 }
 
 module.exports = pickByArray;
 
-},{}],101:[function(require,module,exports){
+},{}],99:[function(require,module,exports){
 /**
  * lodash 3.0.0 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.7.0 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -16398,23 +16710,23 @@ function pickByCallback(object, predicat
       result[key] = value;
     }
   });
   return result;
 }
 
 module.exports = pickByCallback;
 
-},{"lodash._basefor":102,"lodash.keysin":103}],102:[function(require,module,exports){
-/**
- * lodash 3.0.2 (Custom Build) <https://lodash.com/>
- * Build: `lodash modern modularize exports="npm" -o ./`
- * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+},{"lodash._basefor":100,"lodash.keysin":101}],100:[function(require,module,exports){
+/**
+ * lodash 3.0.3 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize exports="npm" -o ./`
+ * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
- * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
 
 /**
  * The base implementation of `baseForIn` and `baseForOwn` which iterates
  * over `object` properties returned by `keysFunc` invoking `iteratee` for
  * each property. Iteratee functions may exit iteration early by explicitly
  * returning `false`.
@@ -16423,80 +16735,42 @@ module.exports = pickByCallback;
  * @param {Object} object The object to iterate over.
  * @param {Function} iteratee The function invoked per iteration.
  * @param {Function} keysFunc The function to get the keys of `object`.
  * @returns {Object} Returns `object`.
  */
 var baseFor = createBaseFor();
 
 /**
- * Creates a base function for `_.forIn` or `_.forInRight`.
+ * Creates a base function for methods like `_.forIn`.
  *
  * @private
  * @param {boolean} [fromRight] Specify iterating from right to left.
  * @returns {Function} Returns the new base function.
  */
 function createBaseFor(fromRight) {
   return function(object, iteratee, keysFunc) {
-    var iterable = toObject(object),
+    var index = -1,
+        iterable = Object(object),
         props = keysFunc(object),
-        length = props.length,
-        index = fromRight ? length : -1;
-
-    while ((fromRight ? index-- : ++index < length)) {
-      var key = props[index];
+        length = props.length;
+
+    while (length--) {
+      var key = props[fromRight ? length : ++index];
       if (iteratee(iterable[key], key, iterable) === false) {
         break;
       }
     }
     return object;
   };
 }
 
-/**
- * Converts `value` to an object if it's not one.
- *
- * @private
- * @param {*} value The value to process.
- * @returns {Object} Returns the object.
- */
-function toObject(value) {
-  return isObject(value) ? value : Object(value);
-}
-
-/**
- * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
- * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
- *
- * @static
- * @memberOf _
- * @category Lang
- * @param {*} value The value to check.
- * @returns {boolean} Returns `true` if `value` is an object, else `false`.
- * @example
- *
- * _.isObject({});
- * // => true
- *
- * _.isObject([1, 2, 3]);
- * // => true
- *
- * _.isObject(1);
- * // => false
- */
-function isObject(value) {
-  // Avoid a V8 JIT bug in Chrome 19-20.
-  // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
-  var type = typeof value;
-  return !!value && (type == 'object' || type == 'function');
-}
-
 module.exports = baseFor;
 
-},{}],103:[function(require,module,exports){
+},{}],101:[function(require,module,exports){
 /**
  * lodash 3.0.8 (Custom Build) <https://lodash.com/>
  * Build: `lodash modern modularize exports="npm" -o ./`
  * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  * Available under MIT license <https://lodash.com/license>
  */
@@ -16620,23 +16894,273 @@ function keysIn(object) {
       result.push(key);
     }
   }
   return result;
 }
 
 module.exports = keysIn;
 
-},{"lodash.isarguments":104,"lodash.isarray":105}],104:[function(require,module,exports){
-arguments[4][93][0].apply(exports,arguments)
-},{"dup":93}],105:[function(require,module,exports){
-arguments[4][94][0].apply(exports,arguments)
-},{"dup":94}],106:[function(require,module,exports){
-arguments[4][90][0].apply(exports,arguments)
-},{"dup":90}],107:[function(require,module,exports){
+},{"lodash.isarguments":102,"lodash.isarray":103}],102:[function(require,module,exports){
+(function (global){
+/**
+ * lodash 3.0.5 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize exports="npm" -o ./`
+ * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+
+/** Used as references for various `Number` constants. */
+var MAX_SAFE_INTEGER = 9007199254740991;
+
+/** `Object#toString` result references. */
+var argsTag = '[object Arguments]',
+    funcTag = '[object Function]',
+    genTag = '[object GeneratorFunction]';
+
+/** Used for built-in method references. */
+var objectProto = global.Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objectToString = objectProto.toString;
+
+/** Built-in value references. */
+var propertyIsEnumerable = objectProto.propertyIsEnumerable;
+
+/**
+ * The base implementation of `_.property` without support for deep paths.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @returns {Function} Returns the new function.
+ */
+function baseProperty(key) {
+  return function(object) {
+    return object == null ? undefined : object[key];
+  };
+}
+
+/**
+ * Gets the "length" property value of `object`.
+ *
+ * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
+ * that affects Safari on at least iOS 8.1-8.3 ARM64.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {*} Returns the "length" value.
+ */
+var getLength = baseProperty('length');
+
+/**
+ * Checks if `value` is likely an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+function isArguments(value) {
+  // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode.
+  return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
+    (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
+}
+
+/**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
+function isArrayLike(value) {
+  return value != null &&
+    !(typeof value == 'function' && isFunction(value)) && isLength(getLength(value));
+}
+
+/**
+ * This method is like `_.isArrayLike` except that it also checks if `value`
+ * is an object.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array-like object, else `false`.
+ * @example
+ *
+ * _.isArrayLikeObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLikeObject(document.body.children);
+ * // => true
+ *
+ * _.isArrayLikeObject('abc');
+ * // => false
+ *
+ * _.isArrayLikeObject(_.noop);
+ * // => false
+ */
+function isArrayLikeObject(value) {
+  return isObjectLike(value) && isArrayLike(value);
+}
+
+/**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+function isFunction(value) {
+  // The use of `Object#toString` avoids issues with the `typeof` operator
+  // in Safari 8 which returns 'object' for typed array constructors, and
+  // PhantomJS 1.9 which returns 'function' for `NodeList` instances.
+  var tag = isObject(value) ? objectToString.call(value) : '';
+  return tag == funcTag || tag == genTag;
+}
+
+/**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This function is loosely based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ * @example
+ *
+ * _.isLength(3);
+ * // => true
+ *
+ * _.isLength(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isLength(Infinity);
+ * // => false
+ *
+ * _.isLength('3');
+ * // => false
+ */
+function isLength(value) {
+  return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+}
+
+/**
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+function isObject(value) {
+  // Avoid a V8 JIT bug in Chrome 19-20.
+  // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
+  var type = typeof value;
+  return !!value && (type == 'object' || type == 'function');
+}
+
+/**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+function isObjectLike(value) {
+  return !!value && typeof value == 'object';
+}
+
+module.exports = isArguments;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],103:[function(require,module,exports){
+arguments[4][92][0].apply(exports,arguments)
+},{"dup":92}],104:[function(require,module,exports){
+arguments[4][88][0].apply(exports,arguments)
+},{"dup":88}],105:[function(require,module,exports){
 // Copyright 2014 Joshua Bell. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
 //     http://www.apache.org/licenses/LICENSE-2.0
 //
@@ -16648,17 +17172,17 @@ arguments[4][90][0].apply(exports,argume
 
 var encoding = require("./lib/encoding.js");
 
 module.exports = {
   TextEncoder: encoding.TextEncoder,
   TextDecoder: encoding.TextDecoder,
 };
 
-},{"./lib/encoding.js":109}],108:[function(require,module,exports){
+},{"./lib/encoding.js":107}],106:[function(require,module,exports){
 // Copyright 2014 Joshua Bell. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
 //     http://www.apache.org/licenses/LICENSE-2.0
 //
@@ -16704,17 +17228,17 @@ module.exports = {
   "windows-1255":[8364,129,8218,402,8222,8230,8224,8225,710,8240,138,8249,140,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,732,8482,154,8250,156,157,158,159,160,161,162,163,8362,165,166,167,168,169,215,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,247,187,188,189,190,191,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,null,1467,1468,1469,1470,1471,1472,1473,1474,1475,1520,1521,1522,1523,1524,null,null,null,null,null,null,null,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,null,null,8206,8207,null],
   "windows-1256":[8364,1662,8218,402,8222,8230,8224,8225,710,8240,1657,8249,338,1670,1688,1672,1711,8216,8217,8220,8221,8226,8211,8212,1705,8482,1681,8250,339,8204,8205,1722,160,1548,162,163,164,165,166,167,168,169,1726,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,1563,187,188,189,190,1567,1729,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,215,1591,1592,1593,1594,1600,1601,1602,1603,224,1604,226,1605,1606,1607,1608,231,232,233,234,235,1609,1610,238,239,1611,1612,1613,1614,244,1615,1616,247,1617,249,1618,251,252,8206,8207,1746],
   "windows-1257":[8364,129,8218,131,8222,8230,8224,8225,136,8240,138,8249,140,168,711,184,144,8216,8217,8220,8221,8226,8211,8212,152,8482,154,8250,156,175,731,159,160,null,162,163,164,null,166,167,216,169,342,171,172,173,174,198,176,177,178,179,180,181,182,183,248,185,343,187,188,189,190,230,260,302,256,262,196,197,280,274,268,201,377,278,290,310,298,315,352,323,325,211,332,213,214,215,370,321,346,362,220,379,381,223,261,303,257,263,228,229,281,275,269,233,378,279,291,311,299,316,353,324,326,243,333,245,246,247,371,322,347,363,252,380,382,729],
   "windows-1258":[8364,129,8218,402,8222,8230,8224,8225,710,8240,138,8249,338,141,142,143,144,8216,8217,8220,8221,8226,8211,8212,732,8482,154,8250,339,157,158,376,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,258,196,197,198,199,200,201,202,203,768,205,206,207,272,209,777,211,212,416,214,215,216,217,218,219,220,431,771,223,224,225,226,259,228,229,230,231,232,233,234,235,769,237,238,239,273,241,803,243,244,417,246,247,248,249,250,251,252,432,8363,255],
   "x-mac-cyrillic":[1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,8224,176,1168,163,167,8226,182,1030,174,169,8482,1026,1106,8800,1027,1107,8734,177,8804,8805,1110,181,1169,1032,1028,1108,1031,1111,1033,1113,1034,1114,1112,1029,172,8730,402,8776,8710,171,187,8230,160,1035,1115,1036,1116,1109,8211,8212,8220,8221,8216,8217,247,8222,1038,1118,1039,1119,8470,1025,1105,1103,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,8364]
 };
 }(this));
 
-},{}],109:[function(require,module,exports){
+},{}],107:[function(require,module,exports){
 // Copyright 2014 Joshua Bell. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
 //
 //     http://www.apache.org/licenses/LICENSE-2.0
 //
@@ -19774,17 +20298,17 @@ if (typeof module !== "undefined" && mod
   };
 
   if (!('TextEncoder' in global))
     global['TextEncoder'] = TextEncoder;
   if (!('TextDecoder' in global))
     global['TextDecoder'] = TextDecoder;
 }(this));
 
-},{"./encoding-indexes.js":108}],110:[function(require,module,exports){
+},{"./encoding-indexes.js":106}],108:[function(require,module,exports){
 (function (global){
 
 var rng;
 
 if (global.crypto && crypto.getRandomValues) {
   // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
   // Moderately fast, high quality
   var _rnds8 = new Uint8Array(16);
@@ -19810,17 +20334,17 @@ if (!rng) {
   };
 }
 
 module.exports = rng;
 
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{}],111:[function(require,module,exports){
+},{}],109:[function(require,module,exports){
 //     uuid.js
 //
 //     Copyright (c) 2010-2012 Robert Kieffer
 //     MIT License - http://opensource.org/licenses/mit-license.php
 
 // Unique ID creation requires a high quality random # generator.  We feature
 // detect to determine the best RNG source, normalizing to a function that
 // returns 128-bits of randomness, since that's what's usually required
@@ -19995,17 +20519,17 @@ function v4(options, buf, offset) {
 var uuid = v4;
 uuid.v1 = v1;
 uuid.v4 = v4;
 uuid.parse = parse;
 uuid.unparse = unparse;
 
 module.exports = uuid;
 
-},{"./rng":110}],112:[function(require,module,exports){
+},{"./rng":108}],110:[function(require,module,exports){
 
 /**
  * Module dependencies.
  */
 
 var global = (function() { return this; })();
 
 /**
@@ -20040,38 +20564,38 @@ function ws(uri, protocols, opts) {
   } else {
     instance = new WebSocket(uri);
   }
   return instance;
 }
 
 if (WebSocket) ws.prototype = WebSocket.prototype;
 
-},{}],113:[function(require,module,exports){
+},{}],111:[function(require,module,exports){
 module.exports = {
-  "version": "v2.7.2",
-  "build": "6a37d02",
-  "buildTime": "December 16 12:20:56 2015",
+  "version": "v2.7.3",
+  "build": "c273b40",
+  "buildTime": "February 03 09:39:35 2016",
   "debug": "false",
   "websiteURL": "http://www.tokbox.com",
   "cdnURL": "http://static.opentok.com",
   "loggingURL": "http://hlg.tokbox.com/prod",
   "apiURL": "http://anvil.opentok.com",
   "messagingProtocol": "wss",
   "messagingPort": 443,
   "supportSSL": "true",
   "cdnURLSSL": "https://static.opentok.com",
   "loggingURLSSL": "https://hlg.tokbox.com/prod",
   "apiURLSSL": "https://anvil.opentok.com",
   "minimumVersion": {
     "firefox": null,
     "chrome": null
   }
 }
-},{}],114:[function(require,module,exports){
+},{}],112:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var guidStorage = require('./guid_storage.js');
 var logging = require('../ot/logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var properties = require('./properties.js');
 
@@ -20152,17 +20676,17 @@ module.exports = function Analytics(logg
 
       _analytics.logQOS(data);
     });
   };
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../ot/logging.js":162,"./guid_storage.js":125,"./properties.js":128,"@opentok/ot-helpers":4}],115:[function(require,module,exports){
+},{"../ot/logging.js":160,"./guid_storage.js":123,"./properties.js":126,"@opentok/ot-helpers":4}],113:[function(require,module,exports){
 (function (global){
 'use strict';
 
 /*
  * Lazy instantiates an audio context and always return the same instance on following calls
  *
  * @returns {AudioContext}
  */
@@ -20173,17 +20697,17 @@ var audioContext = function() {
   context = context || new global.AudioContext();
   return context;
 };
 
 module.exports = audioContext;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{}],116:[function(require,module,exports){
+},{}],114:[function(require,module,exports){
 'use strict';
 
 /*
  * A <code>RTCPeerConnection.getStats</code> based audio level sampler.
  *
  * It uses the the <code>getStats</code> method to get the <code>audioOutputLevel</code>.
  * This implementation expects the single parameter version of the <code>getStats</code> method.
  *
@@ -20214,17 +20738,17 @@ module.exports = function GetstatsAudioO
         }
       }
 
       done(null);
     });
   };
 };
 
-},{}],117:[function(require,module,exports){
+},{}],115:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 /*
  * An <code>AudioContext</code> based audio level sampler. It returns the maximum value in the
  * last 1024 samples.
  *
@@ -20280,17 +20804,17 @@ module.exports = function WebaudioAudioL
       // the getStats' audioOutputLevel
       done(max / 128);
     } else {
       done(null);
     }
   };
 };
 
-},{"@opentok/ot-helpers":4}],118:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],116:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var logging = require('../ot/logging.js');
 var properties = require('./properties.js');
 var OTPlugin = require('@opentok/otplugin.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
@@ -20325,19 +20849,19 @@ OTHelpers.registerCapability('getUserMed
 // * Firefox 20 doesn't interoperate with Chrome.
 //
 OTHelpers.registerCapability('PeerConnection', function() {
   if (OTHelpers.env === 'Node') {
     return false;
   } else if (typeof global.webkitRTCPeerConnection === 'function' &&
                     !!global.webkitRTCPeerConnection.prototype.addStream) {
     return true;
-  } else if (typeof global.mozRTCPeerConnection === 'function' && OTHelpers.env.version > 20.0) {
+  } else if (typeof global.RTCPeerConnection === 'function') {
     return true;
-  } else if (typeof global.RTCPeerConnection === 'function') {
+  } else if (typeof global.mozRTCPeerConnection === 'function' && OTHelpers.env.version > 20.0) {
     return true;
   }
 
   return OTPlugin.isInstalled();
 });
 
 // Indicates whether this client supports WebRTC
 //
@@ -20404,17 +20928,17 @@ OTHelpers.registerCapability('webAudioCa
 });
 
 OTHelpers.registerCapability('webAudio', function() {
   return 'AudioContext' in global;
 });
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../ot/logging.js":162,"./properties.js":128,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],119:[function(require,module,exports){
+},{"../ot/logging.js":160,"./properties.js":126,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],117:[function(require,module,exports){
 'use strict';
 
 // TODO: this is not unit tested
 
 var analytics = require('../ot/analytics.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function ConnectivityAttemptPinger(options) {
@@ -20494,27 +21018,27 @@ module.exports = function ConnectivityAt
     // analytics.logEvent(payload);
   };
 
   this.stop = function() {
     stopAttemptPings();
   };
 };
 
-},{"../ot/analytics.js":141,"@opentok/ot-helpers":4}],120:[function(require,module,exports){
+},{"../ot/analytics.js":139,"@opentok/ot-helpers":4}],118:[function(require,module,exports){
 (function (global){
 'use strict';
 
 require('./web_rtc_polyfills.js');
 
 var OTPlugin = require('@opentok/otplugin.js');
 
 var NativeRTCPeerConnection = (global.webkitRTCPeerConnection ||
-                               global.mozRTCPeerConnection ||
-                               global.RTCPeerConnection);
+                               global.RTCPeerConnection ||
+                               global.mozRTCPeerConnection);
 
 module.exports =  function createPeerConnection(
   config,
   options,
   publishersWebRtcStream,
   completion
 ) {
   if (OTPlugin.isInstalled()) {
@@ -20531,30 +21055,30 @@ module.exports =  function createPeerCon
     }
 
     completion(null, pc);
   }
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./web_rtc_polyfills.js":138,"@opentok/otplugin.js":44}],121:[function(require,module,exports){
+},{"./web_rtc_polyfills.js":136,"@opentok/otplugin.js":42}],119:[function(require,module,exports){
 'use strict';
 
 module.exports = function cssLoader(cssURL) {
   var style = document.createElement('link');
   style.type = 'text/css';
   style.media = 'screen';
   style.rel = 'stylesheet';
   style.href = cssURL;
   var head = document.head || document.getElementsByTagName('head')[0];
   head.appendChild(style);
 };
 
-},{}],122:[function(require,module,exports){
+},{}],120:[function(require,module,exports){
 (function (global){
 'use strict';
 
 // This is here because capabilities are all about global state. TODO: That's why they need to go.
 require('./web_rtc_polyfills.js');
 
 // Web OT Helpers
 var OTPlugin = require('@opentok/otplugin.js');
@@ -20677,17 +21201,17 @@ deviceHelpers.shouldAskForDevices = func
     // TODO: Is memoization worth it here?
     deviceHelpers.shouldAskForDevices = fakeShouldAskForDevices;
     deviceHelpers.shouldAskForDevices(callback);
   }
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./web_rtc_polyfills.js":138,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],123:[function(require,module,exports){
+},{"./web_rtc_polyfills.js":136,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],121:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var properties = require('./properties.js');
 var logging = require('../ot/logging.js');
 
 var Dialogs = {};
 module.exports = Dialogs;
@@ -21020,17 +21544,17 @@ Dialogs.Plugin.updateComplete = function
       document.body.appendChild(root);
     });
 
   });
 
   return modal;
 };
 
-},{"../ot/logging.js":162,"./properties.js":128,"@opentok/ot-helpers":4}],124:[function(require,module,exports){
+},{"../ot/logging.js":160,"./properties.js":126,"@opentok/ot-helpers":4}],122:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var Bluebird = require('bluebird');
 require('./web_rtc_polyfills.js');
 
 // Web OT Helpers
 //
@@ -21270,17 +21794,17 @@ module.exports = function(constraints, s
   } else {
     // wait a second and then trigger accessDialogOpened
     triggerOpenedTimer = setTimeout(triggerOpened, 500);
   }
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../ot/logging.js":162,"./throttle_until_complete.js":130,"./web_rtc_polyfills.js":138,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44,"bluebird":72}],125:[function(require,module,exports){
+},{"../ot/logging.js":160,"./throttle_until_complete.js":128,"./web_rtc_polyfills.js":136,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42,"bluebird":70}],123:[function(require,module,exports){
 'use strict';
 
 var uuid = require('uuid');
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../ot/logging.js');
 
 var guidStorage = {};
 module.exports = guidStorage;
@@ -21409,17 +21933,17 @@ guidStorage.override({
   }
 });
 
 // Test only
 guidStorage.set = function(guid) {
   currentGuid = guid;
 };
 
-},{"../ot/logging.js":162,"@opentok/ot-helpers":4,"uuid":111}],126:[function(require,module,exports){
+},{"../ot/logging.js":160,"@opentok/ot-helpers":4,"uuid":109}],124:[function(require,module,exports){
 (function (global){
 'use strict';
 
 // Returns true if we think the DOM has been unloaded
 // It detects this by looking for a global object, which
 // should always exist until the DOM is cleaned up.
 
 var uuid = require('uuid');
@@ -21429,34 +21953,34 @@ var canary = 'OT_CANARY_' + uuid();
 global[canary] = {};
 
 module.exports = function() {
   return !global[canary];
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"uuid":111}],127:[function(require,module,exports){
+},{"uuid":109}],125:[function(require,module,exports){
 (function (global){
 'use strict';
 
 module.exports = function noConflict() {
   var globalOT = global.OT;
   var globalTB = global.TB;
   return function() {
     var OT = global.OT;
     global.OT = globalOT;
     global.TB = globalTB;
     return OT;
   };
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{}],128:[function(require,module,exports){
+},{}],126:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var assign = require('lodash.assign');
 var pick = require('lodash.pick');
 var OTHelpers = require('@opentok/ot-helpers');
 var OTPlugin = require('@opentok/otplugin.js');
 var incompleteProperties = require('../../conf/current_properties.js');
@@ -21506,17 +22030,17 @@ OTPlugin.setPathToInstaller(properties.a
 if (!properties.hasOwnProperty('cssURL')) {
   properties.cssURL = properties.assetURL + '/css/TB.min.css';
 }
 
 module.exports = properties;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../conf/current_properties.js":113,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44,"lodash.assign":84,"lodash.pick":95}],129:[function(require,module,exports){
+},{"../../conf/current_properties.js":111,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42,"lodash.assign":82,"lodash.pick":93}],127:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 // Returns a String representing the supported WebRTC crypto scheme. The possible
 // values are SDES_SRTP, DTLS_SRTP, and NONE;
 //
 // Broadly:
@@ -21528,17 +22052,17 @@ module.exports = function supportedCrypt
   var chromeBefore25 = (
     OTHelpers.env.name === 'Chrome' &&
     OTHelpers.env.version < 25
   );
 
   return (chromeBefore25 ? 'SDES_SRTP' : 'DTLS_SRTP');
 };
 
-},{"@opentok/ot-helpers":4}],130:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],128:[function(require,module,exports){
 'use strict';
 
 var Bluebird = require('bluebird');
 
 // Ensures that promisified functions run in serial.
 // Perhaps this module should be called "serialize," but that seems too generic.
 //
 // @memberof OTHelpers
@@ -21560,17 +22084,17 @@ module.exports = function(fn) {
       // steps have finished.
       return fn.apply(null, args);
     });
 
     return queue;
   };
 };
 
-},{"bluebird":72}],131:[function(require,module,exports){
+},{"bluebird":70}],129:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function(self, domElement) {
   var width = domElement.videoWidth;
   var height = domElement.videoHeight;
   var stopped = true;
@@ -21601,17 +22125,17 @@ module.exports = function(self, domEleme
     waiter();
   };
 
   self.stopObservingSize = function() {
     stopped = true;
   };
 };
 
-},{"@opentok/ot-helpers":4}],132:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],130:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 var VideoOrientationTransforms = {
   0: 'rotate(0deg)',
   270: 'rotate(90deg)',
   90: 'rotate(-90deg)',
@@ -21685,17 +22209,17 @@ module.exports = function canBeOrientate
         if ('mozAudioChannelType' in this.domElement) {
           this.domElement.mozAudioChannelType = type;
         }
       }
     }
   });
 };
 
-},{"@opentok/ot-helpers":4}],133:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],131:[function(require,module,exports){
 'use strict';
 
 require('../web_rtc_polyfills.js');
 
 var OTPlugin = require('@opentok/otplugin.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 var NativeVideoElementWrapper = require('./native_video_element_wrapper');
@@ -21890,17 +22414,17 @@ module.exports = function VideoElementFa
     // unbind all events so they don't fire after the object is dead
     this.off();
 
     _videoElementWrapper.destroy();
     return void 0;
   };
 };
 
-},{"../web_rtc_polyfills.js":138,"./native_video_element_wrapper":134,"./plugin_video_element_wrapper":135,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],134:[function(require,module,exports){
+},{"../web_rtc_polyfills.js":136,"./native_video_element_wrapper":132,"./plugin_video_element_wrapper":133,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],132:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../../ot/logging.js');
 var Promise = require('bluebird');
 
 var canBeOrientatedMixin = require('./can_be_oriented_mixin.js');
@@ -22203,17 +22727,17 @@ module.exports = function NativeVideoEle
     }
 
     return void 0;
   };
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../ot/logging.js":162,"../audio_context":115,"../audio_level_samplers/webaudio_audio_level_sampler":117,"../video_content_resizes_mixin.js":131,"./can_be_oriented_mixin.js":132,"./video_element_error_code_to_str.js":136,"@opentok/ot-helpers":4,"bluebird":72}],135:[function(require,module,exports){
+},{"../../ot/logging.js":160,"../audio_context":113,"../audio_level_samplers/webaudio_audio_level_sampler":115,"../video_content_resizes_mixin.js":129,"./can_be_oriented_mixin.js":130,"./video_element_error_code_to_str.js":134,"@opentok/ot-helpers":4,"bluebird":70}],133:[function(require,module,exports){
 'use strict';
 
 var Promise = require('bluebird');
 var OTHelpers = require('@opentok/ot-helpers');
 
 var canBeOrientatedMixin = require('./can_be_oriented_mixin.js');
 
 module.exports = function PluginVideoElementWrapper(
@@ -22312,17 +22836,17 @@ module.exports = function PluginVideoEle
 
   this.destroy = function() {
     this.unbindStream();
 
     return void 0;
   };
 };
 
-},{"./can_be_oriented_mixin.js":132,"@opentok/ot-helpers":4,"bluebird":72}],136:[function(require,module,exports){
+},{"./can_be_oriented_mixin.js":130,"@opentok/ot-helpers":4,"bluebird":70}],134:[function(require,module,exports){
 (function (global){
 'use strict';
 
 // See http://www.w3.org/TR/2010/WD-html5-20101019/video.html#error-codes
 var _videoErrorCodes = {};
 
 // Checking for global.MediaError for IE compatibility, just so we don't throw
 // exceptions when the script is included
@@ -22340,27 +22864,27 @@ if (global.MediaError) {
 }
 
 module.exports = function videoElementErrorCodeToStr(errorCode) {
   return _videoErrorCodes[parseInt(errorCode, 10)] || 'An unknown error occurred.';
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{}],137:[function(require,module,exports){
+},{}],135:[function(require,module,exports){
 'use strict';
 
 module.exports = {
   ROTATED_NORMAL: 0,
   ROTATED_LEFT: 270,
   ROTATED_RIGHT: 90,
   ROTATED_UPSIDE_DOWN: 180
 };
 
-},{}],138:[function(require,module,exports){
+},{}],136:[function(require,module,exports){
 (function (global){
 'use strict';
 
 // TODO: We should be exporting fixed versions of these objects rather than messing with them
 // globally. Currently this file is not actually modularized and require('./web_rtc_polyfills.js')
 // is only used as an error-prone inclusion mechanism.
 
 var OTHelpers = require('@opentok/ot-helpers');
@@ -22439,17 +22963,17 @@ if (global && typeof navigator !== 'unde
 
   if (!global.URL && global.webkitURL) {
     global.URL = global.webkitURL;
   }
 }
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"@opentok/ot-helpers":4}],139:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],137:[function(require,module,exports){
 'use strict';
 
 var uuid = require('uuid');
 var OTPlugin = require('@opentok/otplugin.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../ot/logging.js');
 var VideoElementFacade = require('./video_element/index.js');
 
@@ -22811,17 +23335,17 @@ var WidgetView = function(targetElement,
 };
 
 // This is bound here so that it can be mocked in testing. Feels like a smell that's a symptom of
 // larger problems to me, but I'm just maintaining existing behaviour right now.
 WidgetView.VideoElementFacade = VideoElementFacade;
 
 module.exports = WidgetView;
 
-},{"../ot/logging.js":162,"./video_element/index.js":133,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44,"uuid":111}],140:[function(require,module,exports){
+},{"../ot/logging.js":160,"./video_element/index.js":131,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42,"uuid":109}],138:[function(require,module,exports){
 (function (global){
 'use strict';
 
 // We need to do this first because of circular dependency issues with otplugin.js. @TODO: fix this.
 var OT = {};
 global.OT = OT;
 global.TB = OT;
 module.exports = OT;
@@ -23291,25 +23815,25 @@ module.exports = OT;
  * @event
  * @borrows ExceptionEvent#message as this.message
  * @memberof OT
  * @see ExceptionEvent
  */
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"./helpers/analytics.js":114,"./helpers/audio_context.js":115,"./helpers/audio_level_samplers/getstats_audio_output_level_sampler":116,"./helpers/audio_level_samplers/webaudio_audio_level_sampler":117,"./helpers/capabilities":118,"./helpers/connectivity_attempt_pinger.js":119,"./helpers/css_loader.js":121,"./helpers/dialogs.js":123,"./helpers/get_user_media.js":124,"./helpers/guid_storage.js":125,"./helpers/no_conflict.js":127,"./helpers/properties.js":128,"./helpers/video_element/index.js":133,"./helpers/video_orientation.js":137,"./helpers/widget_view.js":139,"./ot/analytics.js":141,"./ot/anvil.js":142,"./ot/api_key.js":143,"./ot/archive.js":144,"./ot/audio_level_transformer":145,"./ot/capabilities.js":146,"./ot/chrome/chrome.js":151,"./ot/connection.js":155,"./ot/environment_loader.js":156,"./ot/events.js":157,"./ot/exception_codes.js":158,"./ot/generate_simple_state_machine.js":159,"./ot/get_devices.js":160,"./ot/interval_runner.js":161,"./ot/logging.js":162,"./ot/messaging/raptor/legacy_structure.js":166,"./ot/messaging/raptor/session_dispatcher.js":172,"./ot/messaging/raptor/signal.js":173,"./ot/messaging/rumor/legacy_structure.js":176,"./ot/ot_error.js":183,"./ot/peer_connection/get_stats_adapter.js":186,"./ot/peer_connection/get_stats_helpers.js":187,"./ot/peer_connection/peer_connection.js":190,"./ot/peer_connection/peer_connections.js":192,"./ot/peer_connection/publisher_peer_connection.js":193,"./ot/peer_connection/qos.js":194,"./ot/peer_connection/subscriber_peer_connection.js":198,"./ot/publisher":199,"./ot/publisher/init.js":200,"./ot/publisher/microphone.js":202,"./ot/publisher/state.js":203,"./ot/qos_testing/http_test.js":204,"./ot/qos_testing/webrtc_test.js":205,"./ot/report_issue.js":206,"./ot/screensharing/screen_sharing.js":210,"./ot/session/handle.js":211,"./ot/session/info.js":212,"./ot/session/init.js":213,"./ot/session/objects.js":214,"./ot/stream.js":216,"./ot/stream_channel.js":217,"./ot/stylable_component.js":218,"./ot/subscriber":220,"./ot/subscriber/state.js":221,"./ot/system_requirements.js":222,"@opentok/ot-helpers":4}],141:[function(require,module,exports){
+},{"./helpers/analytics.js":112,"./helpers/audio_context.js":113,"./helpers/audio_level_samplers/getstats_audio_output_level_sampler":114,"./helpers/audio_level_samplers/webaudio_audio_level_sampler":115,"./helpers/capabilities":116,"./helpers/connectivity_attempt_pinger.js":117,"./helpers/css_loader.js":119,"./helpers/dialogs.js":121,"./helpers/get_user_media.js":122,"./helpers/guid_storage.js":123,"./helpers/no_conflict.js":125,"./helpers/properties.js":126,"./helpers/video_element/index.js":131,"./helpers/video_orientation.js":135,"./helpers/widget_view.js":137,"./ot/analytics.js":139,"./ot/anvil.js":140,"./ot/api_key.js":141,"./ot/archive.js":142,"./ot/audio_level_transformer":143,"./ot/capabilities.js":144,"./ot/chrome/chrome.js":149,"./ot/connection.js":153,"./ot/environment_loader.js":154,"./ot/events.js":155,"./ot/exception_codes.js":156,"./ot/generate_simple_state_machine.js":157,"./ot/get_devices.js":158,"./ot/interval_runner.js":159,"./ot/logging.js":160,"./ot/messaging/raptor/legacy_structure.js":164,"./ot/messaging/raptor/session_dispatcher.js":170,"./ot/messaging/raptor/signal.js":171,"./ot/messaging/rumor/legacy_structure.js":174,"./ot/ot_error.js":181,"./ot/peer_connection/get_stats_adapter.js":184,"./ot/peer_connection/get_stats_helpers.js":185,"./ot/peer_connection/peer_connection.js":188,"./ot/peer_connection/peer_connections.js":190,"./ot/peer_connection/publisher_peer_connection.js":191,"./ot/peer_connection/qos.js":192,"./ot/peer_connection/subscriber_peer_connection.js":196,"./ot/publisher":197,"./ot/publisher/init.js":198,"./ot/publisher/microphone.js":200,"./ot/publisher/state.js":201,"./ot/qos_testing/http_test.js":202,"./ot/qos_testing/webrtc_test.js":203,"./ot/report_issue.js":204,"./ot/screensharing/screen_sharing.js":208,"./ot/session/handle.js":209,"./ot/session/info.js":210,"./ot/session/init.js":211,"./ot/session/objects.js":212,"./ot/stream.js":214,"./ot/stream_channel.js":215,"./ot/stylable_component.js":216,"./ot/subscriber":218,"./ot/subscriber/state.js":219,"./ot/system_requirements.js":220,"@opentok/ot-helpers":4}],139:[function(require,module,exports){
 'use strict';
 
 var Analytics = require('../helpers/analytics.js');
 var properties = require('../helpers/properties.js');
 
 module.exports = new Analytics(properties.loggingURL);
 
-},{"../helpers/analytics.js":114,"../helpers/properties.js":128}],142:[function(require,module,exports){
+},{"../helpers/analytics.js":112,"../helpers/properties.js":126}],140:[function(require,module,exports){
 'use strict';
 
 var uuid = require('uuid');
 var Bluebird = require('bluebird');
 var ExceptionCodes = require('./exception_codes.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var properties = require('../helpers/properties.js');
 
@@ -23457,17 +23981,17 @@ Anvil.get = function getFromAnvil(resour
         return;
       }
 
       resolve(responseJson[0]);
     });
   });
 };
 
-},{"../helpers/properties.js":128,"./exception_codes.js":158,"@opentok/ot-helpers":4,"bluebird":72,"uuid":111}],143:[function(require,module,exports){
+},{"../helpers/properties.js":126,"./exception_codes.js":156,"@opentok/ot-helpers":4,"bluebird":70,"uuid":109}],141:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 if (OTHelpers.env.name === 'Node') {
   module.exports = { value: '' };
 } else {
   // Script embed
@@ -23480,17 +24004,17 @@ if (OTHelpers.env.name === 'Node') {
 
   var m = scriptSrc.match(/[\?\&]apikey=([^&]+)/i);
 
   // TODO: The indirection here is due to the need to set APIKEY in testing. We should find a better
   // solution.
   module.exports = { value: m ? m[1] : '' };
 }
 
-},{"@opentok/ot-helpers":4}],144:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],142:[function(require,module,exports){
 'use strict';
 
 var Events = require('./events.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function Archive(id, name, status) {
   this.id = id;
   this.name = name;
@@ -23512,17 +24036,17 @@ module.exports = function Archive(id, na
       var event = new Events.ArchiveUpdatedEvent(this, key, oldValue, this[key]);
       this.dispatchEvent(event);
     }
   }.bind(this);
 
   this.destroy = function() {};
 };
 
-},{"./events.js":157,"@opentok/ot-helpers":4}],145:[function(require,module,exports){
+},{"./events.js":155,"@opentok/ot-helpers":4}],143:[function(require,module,exports){
 'use strict';
 
 /*
  * Transforms a raw audio level to produce a "smoother" animation when using displaying the
  * audio level. This transformer is state-full because it needs to keep the previous average
  * value of the signal for filtering.
  *
  * It applies a low pass filter to get rid of level jumps and apply a log scale.
@@ -23548,17 +24072,17 @@ module.exports = function AudioLevelTran
 
     // 1.5 scaling to map -30-0 dBm range to [0,1]
     var logScaled = (Math.log(_averageAudioLevel) / Math.LN10) / 1.5 + 1;
 
     return Math.min(Math.max(logScaled, 0), 1);
   };
 };
 
-},{}],146:[function(require,module,exports){
+},{}],144:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 /**
  * A class defining properties of the <code>capabilities</code> property of a
  * Session object. See <a href="Session.html#properties">Session.capabilities</a>.
  * <p>
@@ -23592,17 +24116,17 @@ module.exports = function Capabilities(p
   this.forceDisconnect = permissions.indexOf('forcedisconnect') !== -1 ? 1 : 0;
   this.supportsWebRTC = OTHelpers.hasCapabilities('webrtc') ? 1 : 0;
 
   this.permittedTo = function(action) {
     return this.hasOwnProperty(action) && this[action] === 1;
   };
 };
 
-},{"@opentok/ot-helpers":4}],147:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],145:[function(require,module,exports){
 'use strict';
 
 var Widget = require('./behaviour/widget.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 // Archving Chrome Widget
 //
 // mode (String)
@@ -23700,17 +24224,17 @@ module.exports = function Archiving(opti
     _archiving = status;
     _initialState = false;
     if (self.domElement) {
       renderStage.call(self);
     }
   };
 };
 
-},{"./behaviour/widget.js":150,"@opentok/ot-helpers":4}],148:[function(require,module,exports){
+},{"./behaviour/widget.js":148,"@opentok/ot-helpers":4}],146:[function(require,module,exports){
 'use strict';
 
 var Widget = require('./behaviour/widget.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function AudioLevelMeter(options) {
   var _meterBarElement, _voiceOnlyIconElement, _meterValueElement, _value;
   var widget = this;
@@ -23763,17 +24287,17 @@ module.exports = function AudioLevelMete
   };
 
   widget.setValue = function(value) {
     _value = value;
     updateView();
   };
 };
 
-},{"./behaviour/widget.js":150,"@opentok/ot-helpers":4}],149:[function(require,module,exports){
+},{"./behaviour/widget.js":148,"@opentok/ot-helpers":4}],147:[function(require,module,exports){
 'use strict';
 
 var Widget = require('./behaviour/widget.js');
 
 // BackingBar Chrome Widget
 //
 // nameMode (String)
 // Whether or not the name panel is being displayed
@@ -23824,17 +24348,17 @@ module.exports = function BackingBar(opt
   };
 
   this.setMuteMode = function(muteMode) {
     _muteMode = muteMode;
     this.setDisplayMode(getDisplayMode());
   };
 };
 
-},{"./behaviour/widget.js":150}],150:[function(require,module,exports){
+},{"./behaviour/widget.js":148}],148:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 // A mixin to encapsulate the basic widget behaviour. This needs a better name,
 // it's not actually a widget. It's actually "Behaviour that can be applied to
 // an object to make it support the basic Chrome widget workflow"...but that would
 // probably been too long a name.
@@ -23916,17 +24440,17 @@ module.exports = function Widget(widget,
 
     // add the widget to the parent
     parent.appendChild(this.domElement);
 
     return widget;
   };
 };
 
-},{"@opentok/ot-helpers":4}],151:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],149:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 // Manages N Chrome elements
 module.exports = function Chrome(properties) {
   var _widgets = {};
 
@@ -23998,17 +24522,17 @@ module.exports = function Chrome(propert
           _set.call(this, name, widgetName[name]);
         }
       }
     }
     return this;
   };
 };
 
-},{"@opentok/ot-helpers":4}],152:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],150:[function(require,module,exports){
 'use strict';
 
 var Widget = require('./behaviour/widget.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function MuteButton(options) {
   var _onClickCb;
   var _muted = options.muted || false;
@@ -24065,17 +24589,17 @@ module.exports = function MuteButton(opt
     htmlAttributes: {
       className: classNames
     },
     onCreate: attachEvents.bind(this),
     onDestroy: detachEvents.bind(this)
   });
 };
 
-},{"./behaviour/widget.js":150,"@opentok/ot-helpers":4}],153:[function(require,module,exports){
+},{"./behaviour/widget.js":148,"@opentok/ot-helpers":4}],151:[function(require,module,exports){
 'use strict';
 
 var Widget = require('./behaviour/widget.js');
 
 // NamePanel Chrome Widget
 //
 // mode (String)
 // Whether to display the name. Possible values are: "auto" (the name is displayed
@@ -24107,17 +24631,17 @@ module.exports = function NamePanel(opti
     nodeName: 'h1',
     htmlContent: _name,
     htmlAttributes: {
       className: 'OT_name OT_edge-bar-item'
     }
   });
 };
 
-},{"./behaviour/widget.js":150}],154:[function(require,module,exports){
+},{"./behaviour/widget.js":148}],152:[function(require,module,exports){
 'use strict';
 
 var Widget = require('./behaviour/widget.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function VideoDisabledIndicator(options) {
   var videoDisabled = false;
   var warning = false;
@@ -24164,17 +24688,17 @@ module.exports = function VideoDisabledI
 
   var parentSetDisplayMode = this.setDisplayMode.bind(this);
   this.setDisplayMode = function(mode) {
     parentSetDisplayMode(mode);
     updateClasses(this.domElement);
   };
 };
 
-},{"./behaviour/widget.js":150,"@opentok/ot-helpers":4}],155:[function(require,module,exports){
+},{"./behaviour/widget.js":148,"@opentok/ot-helpers":4}],153:[function(require,module,exports){
 'use strict';
 
 var Capabilities = require('./capabilities.js');
 var Events = require('./events.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 /**
  * The Connection object represents a connection to an OpenTok session. Each client that connects
@@ -24266,17 +24790,17 @@ Connection.Capabilities = function(capab
 
   // Private data
   var _caps = castCapabilities(capabilitiesHash);
   this.supportsWebRTC = _caps.supportsWebRTC;
 };
 
 module.exports = Connection;
 
-},{"./capabilities.js":146,"./events.js":157,"@opentok/ot-helpers":4}],156:[function(require,module,exports){
+},{"./capabilities.js":144,"./events.js":155,"@opentok/ot-helpers":4}],154:[function(require,module,exports){
 'use strict';
 
 var Events = require('./events.js');
 var OTPlugin = require('@opentok/otplugin.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('./logging.js');
 
 // Helper to synchronise several startup tasks and then dispatch a unified
@@ -24354,17 +24878,17 @@ function EnvironmentLoader() {
 
   this.isUnloaded = function() {
     return OTHelpers.isDOMUnloaded();
   };
 }
 
 module.exports = new EnvironmentLoader();
 
-},{"./events.js":157,"./logging.js":162,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],157:[function(require,module,exports){
+},{"./events.js":155,"./logging.js":160,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],155:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('./logging.js');
 
 var Events = {};
 
 /**
@@ -25337,17 +25861,17 @@ Events.AudioLevelUpdatedEvent = function
 
 Events.MediaStoppedEvent = function(target) {
   Events.Event.call(this, Events.Event.names.MEDIA_STOPPED, true);
   this.target = target;
 };
 
 module.exports = Events;
 
-},{"./logging.js":162,"@opentok/ot-helpers":4}],158:[function(require,module,exports){
+},{"./logging.js":160,"@opentok/ot-helpers":4}],156:[function(require,module,exports){
 'use strict';
 
 module.exports = {
   JS_EXCEPTION: 2000,
   AUTHENTICATION_ERROR: 1004,
   INVALID_SESSION_ID: 1005,
   CONNECT_FAILED: 1006,
   CONNECT_REJECTED: 1007,
@@ -25362,17 +25886,17 @@ module.exports = {
   UNABLE_TO_FORCE_DISCONNECT: 1520,
   UNABLE_TO_FORCE_UNPUBLISH: 1530,
   PUBLISHER_ICE_WORKFLOW_FAILED: 1553,
   SUBSCRIBER_ICE_WORKFLOW_FAILED: 1554,
   UNEXPECTED_SERVER_RESPONSE: 2001,
   REPORT_ISSUE_ERROR: 2011
 };
 
-},{}],159:[function(require,module,exports){
+},{}],157:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 // A Factory method for generating simple state machine classes.
 //
 // @usage
 //    var StateMachine = generateSimpleStateMachine('start', ['start', 'middle', 'end', {
@@ -25434,17 +25958,17 @@ module.exports = function generateSimple
     this.set = function(newState) {
       if (!handleInvalidStateChanges(newState)) { return; }
       previousState = currentState;
       this.current = currentState = newState;
     };
   };
 };
 
-},{"@opentok/ot-helpers":4}],160:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],158:[function(require,module,exports){
 'use strict';
 
 var getMediaDevices = require('../helpers/device_helpers.js').getMediaDevices;
 
 /**
  * Enumerates the audio input devices (such as microphones) and video input devices
  * (cameras) available to the browser.
  * <p>
@@ -25485,17 +26009,17 @@ var getMediaDevices = require('../helper
  * @see <a href="#initPublisher">OT.initPublisher()</a>
  * @method OT.getDevices
  * @memberof OT
  */
 module.exports = function getDevices(callback) {
   getMediaDevices(callback);
 };
 
-},{"../helpers/device_helpers.js":122}],161:[function(require,module,exports){
+},{"../helpers/device_helpers.js":120}],159:[function(require,module,exports){
 (function (global){
 'use strict';
 
 /*
  * Executes the provided callback thanks to <code>global.setInterval</code>.
  *
  * @param {function()} callback
  * @param {number} frequency how many times per second we want to execute the callback
@@ -25513,17 +26037,17 @@ module.exports = function IntervalRunner
   this.stop = function() {
     global.clearInterval(_intervalId);
     _intervalId = null;
   };
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{}],162:[function(require,module,exports){
+},{}],160:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var properties = require('../helpers/properties.js');
 
 var logging = {};
 module.exports = logging;
 
@@ -25622,17 +26146,17 @@ logging.setLogLevel(debugTrue ? logging.
   * @param {String} message The string to log.
   *
   * @name OT.log
   * @memberof OT
   * @function
   * @see <a href="#setLogLevel">OT.setLogLevel()</a>
   */
 
-},{"../helpers/properties.js":128,"@opentok/ot-helpers":4}],163:[function(require,module,exports){
+},{"../helpers/properties.js":126,"@opentok/ot-helpers":4}],161:[function(require,module,exports){
 'use strict';
 
 var DelayedEventQueue = function DelayedEventQueue(eventDispatcher) {
   var queue = [];
 
   this.enqueue = function enqueue(/* arg1, arg2, ..., argN */) {
     queue.push(Array.prototype.slice.call(arguments));
   };
@@ -25663,17 +26187,17 @@ var DelayedEventQueue = function Delayed
     while ((event = queue.shift())) {
       eventDispatcher.trigger.apply(eventDispatcher, event);
     }
   };
 };
 
 module.exports = DelayedEventQueue;
 
-},{}],164:[function(require,module,exports){
+},{}],162:[function(require,module,exports){
 'use strict';
 
 // 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.
 //
 // These include:
 // * typeName: A human readable version of the Raptor type. E.g. STREAM instead of 102
 // * actionName: A human readable version of the Raptor action. E.g. CREATE instead of 101
@@ -25709,17 +26233,17 @@ module.exports = function deserializeMes
   } else {
     message.resource = bits[bits.length - 1];
   }
 
   message.signature = message.resource + '#' + message.method;
   return message;
 };
 
-},{}],165:[function(require,module,exports){
+},{}],163:[function(require,module,exports){
 'use strict';
 
 var OTError = require('../../ot_error.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../../logging.js');
 var RumorMessageTypes = require('../rumor/rumor_message_types.js');
 var unboxFromRumorMessage = require('./unbox_from_rumor_message.js');
 
@@ -25977,17 +26501,17 @@ Dispatcher.prototype.dispatchArchive = f
     case 'updated':
       this.emit('archive#updated', message.params.archive, message.content);
       break;
 
     default:
   }
 };
 
-},{"../../logging.js":162,"../../ot_error.js":183,"../rumor/rumor_message_types.js":180,"./unbox_from_rumor_message.js":174,"@opentok/ot-helpers":4}],166:[function(require,module,exports){
+},{"../../logging.js":160,"../../ot_error.js":181,"../rumor/rumor_message_types.js":178,"./unbox_from_rumor_message.js":172,"@opentok/ot-helpers":4}],164:[function(require,module,exports){
 'use strict';
 
 var RaptorConstants = require('./raptor_constants.js');
 
 var Raptor = {};
 
 Raptor.Actions = RaptorConstants.Actions;
 Raptor.Types = RaptorConstants.Types;
@@ -25996,17 +26520,17 @@ Raptor.serializeMessage = require('./ser
 Raptor.deserializeMessage = require('./deserialize_message.js');
 Raptor.unboxFromRumorMessage = require('./unbox_from_rumor_message.js');
 Raptor.parseIceServers = require('./parse_ice_servers.js');
 Raptor.Message = require('./message.js');
 Raptor.Socket = require('./raptor_socket.js');
 
 module.exports = Raptor;
 
-},{"./deserialize_message.js":164,"./dispatcher.js":165,"./message.js":167,"./parse_ice_servers.js":168,"./raptor_constants.js":169,"./raptor_socket.js":170,"./serialize_message.js":171,"./unbox_from_rumor_message.js":174}],167:[function(require,module,exports){
+},{"./deserialize_message.js":162,"./dispatcher.js":163,"./message.js":165,"./parse_ice_servers.js":166,"./raptor_constants.js":167,"./raptor_socket.js":168,"./serialize_message.js":169,"./unbox_from_rumor_message.js":172}],165:[function(require,module,exports){
 'use strict';
 
 var uuid = require('uuid');
 var OTHelpers = require('@opentok/ot-helpers');
 var serializeMessage = require('./serialize_message.js');
 var supportedCryptoScheme = require('../../../helpers/supported_crypto_scheme.js');
 var properties = require('../../../helpers/properties.js');
 
@@ -26237,28 +26761,39 @@ Message.signals.create =
     return serializeMessage({
       method: 'signal',
       uri: '/v2/partner/' + apiKey + '/session/' + sessionId +
         (toAddress !== void 0 ? '/connection/' + toAddress : '') + '/signal/' + uuid(),
       content: content
     });
   };
 
-},{"../../../helpers/properties.js":128,"../../../helpers/supported_crypto_scheme.js":129,"./serialize_message.js":171,"@opentok/ot-helpers":4,"uuid":111}],168:[function(require,module,exports){
+},{"../../../helpers/properties.js":126,"../../../helpers/supported_crypto_scheme.js":127,"./serialize_message.js":169,"@opentok/ot-helpers":4,"uuid":109}],166:[function(require,module,exports){
 'use strict';
 
 module.exports = function parseIceServers(message) {
+  var iceServers;
+
   try {
-    return JSON.parse(message.data).content.iceServers;
+    iceServers = JSON.parse(message.data).content.iceServers;
   } catch (e) {
     return [];
   }
-};
-
-},{}],169:[function(require,module,exports){
+
+  return iceServers.map(function(iceServer) {
+    return {
+      url: iceServer.url, // soon to be deprecated
+      urls: iceServer.url,
+      username: iceServer.username,
+      credential: iceServer.credential
+    };
+  });
+};
+
+},{}],167:[function(require,module,exports){
 'use strict';
 
 // Rumor Messaging for JS
 //
 // https://tbwiki.tokbox.com/index.php/Raptor_Messages_(Sent_as_a_RumorMessage_payload_in_JSON)
 //
 // @todo Raptor {
 //     Look at disconnection cleanup: i.e. subscriber + publisher cleanup
@@ -26345,17 +26880,17 @@ module.exports = {
     SIGNAL: 108,
     SUBSCRIBER: 110,
 
     //JSEP Protocol
     JSEP: 109
   }
 };
 
-},{}],170:[function(require,module,exports){
+},{}],168:[function(require,module,exports){
 'use strict';
 
 var uuid = require('uuid');
 var analytics = require('../../analytics.js');
 var ExceptionCodes = require('../../exception_codes.js');
 var Dispatcher = require('./dispatcher.js');
 var logging = require('../../logging.js');
 var Message = require('./message.js');
@@ -26779,24 +27314,24 @@ var RaptorSocket = function RaptorSocket
   }
   _dispatcher = dispatcher;
 };
 
 RaptorSocket.RumorSocket = RumorSocket;
 
 module.exports = RaptorSocket;
 
-},{"../../analytics.js":141,"../../exception_codes.js":158,"../../logging.js":162,"../../ot_error.js":183,"../rumor/rumor_socket.js":181,"./dispatcher.js":165,"./message.js":167,"./signal.js":173,"@opentok/ot-helpers":4,"uuid":111}],171:[function(require,module,exports){
+},{"../../analytics.js":139,"../../exception_codes.js":156,"../../logging.js":160,"../../ot_error.js":181,"../rumor/rumor_socket.js":179,"./dispatcher.js":163,"./message.js":165,"./signal.js":171,"@opentok/ot-helpers":4,"uuid":109}],169:[function(require,module,exports){
 'use strict';
 
 module.exports = function serializeMessage(message) {
   return JSON.stringify(message);
 };
 
-},{}],172:[function(require,module,exports){
+},{}],170:[function(require,module,exports){
 'use strict';
 
 var Archive = require('../../archive.js');
 var Connection = require('../../connection.js');
 var DelayedEventQueue = require('./delayed_event_queue.js');
 var Dispatcher = require('./dispatcher.js');
 var logging = require('../../logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
@@ -27221,17 +27756,17 @@ module.exports = function SessionDispatc
     }
 
     archive._.update(update);
   });
 
   return dispatcher;
 };
 
-},{"../../archive.js":144,"../../connection.js":155,"../../logging.js":162,"../../session/objects.js":214,"../../stream.js":216,"../../stream_channel.js":217,"./delayed_event_queue.js":163,"./dispatcher.js":165,"@opentok/ot-helpers":4}],173:[function(require,module,exports){
+},{"../../archive.js":142,"../../connection.js":153,"../../logging.js":160,"../../session/objects.js":212,"../../stream.js":214,"../../stream_channel.js":215,"./delayed_event_queue.js":161,"./dispatcher.js":163,"@opentok/ot-helpers":4}],171:[function(require,module,exports){
 'use strict';
 
 var APIKEY = require('../../api_key.js');
 var Connection = require('../../connection.js');
 var Message = require('./message.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var sessionTag = require('../../session/tag.js');
 
@@ -27384,60 +27919,60 @@ module.exports = function Signal(session
       }
       this.retryAfterReconnect = options.retryAfterReconnect;
     }
   }
 
   this.valid = this.error === null;
 };
 
-},{"../../api_key.js":143,"../../connection.js":155,"../../session/tag.js":215,"./message.js":167,"@opentok/ot-helpers":4}],174:[function(require,module,exports){
+},{"../../api_key.js":141,"../../connection.js":153,"../../session/tag.js":213,"./message.js":165,"@opentok/ot-helpers":4}],172:[function(require,module,exports){
 'use strict';
 
 var deserializeMessage = require('./deserialize_message.js');
 
 module.exports = function unboxFromRumorMessage(rumorMessage) {
   var message = deserializeMessage(rumorMessage.data);
   message.transactionId = rumorMessage.transactionId;
   message.fromAddress = rumorMessage.headers['X-TB-FROM-ADDRESS'];
 
   return message;
 };
 
-},{"./deserialize_message.js":164}],175:[function(require,module,exports){
+},{"./deserialize_message.js":162}],173:[function(require,module,exports){
 (function (global){
 'use strict';
 
 // Unfortunately it looks like the text-encoding module always returns its implementations of
 // TextEncoder and TextDecoder, so we wrap it here to expose the global (window) ones, if available.
 
 var TextEncoding = require('text-encoding');
 
 module.exports = {
   TextEncoder: global.TextEncoder || TextEncoding.TextEncoder,
   TextDecoder: global.TextDecoder || TextEncoding.TextDecoder
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"text-encoding":107}],176:[function(require,module,exports){
+},{"text-encoding":105}],174:[function(require,module,exports){
 'use strict';
 
 var Rumor = {};
 
 Rumor.MessageType = require('./rumor_message_types.js');
 Rumor.PluginSocket = require('./plugin_socket.js');
 Rumor.Message = require('./rumor_message.js');
 Rumor.NativeSocket = require('./native_socket.js');
 Rumor.SocketError = require('./socket_error.js');
 Rumor.Socket = require('./rumor_socket.js');
 
 module.exports = Rumor;
 
-},{"./native_socket.js":177,"./plugin_socket.js":178,"./rumor_message.js":179,"./rumor_message_types.js":180,"./rumor_socket.js":181,"./socket_error.js":182}],177:[function(require,module,exports){
+},{"./native_socket.js":175,"./plugin_socket.js":176,"./rumor_message.js":177,"./rumor_message_types.js":178,"./rumor_socket.js":179,"./socket_error.js":180}],175:[function(require,module,exports){
 'use strict';
 
 var Message = require('./rumor_message.js');
 
 var BUFFER_DRAIN_INTERVAL = 100;
 
 // The total number of times to retest the websocket's send buffer
 var BUFFER_DRAIN_MAX_RETRIES = 10;
@@ -27517,17 +28052,17 @@ module.exports = function NativeSocket(T
   };
 
   this.isClosed = function() {
     return webSocket.readyState === 3;
   };
   
 };
 
-},{"./rumor_message.js":179}],178:[function(require,module,exports){
+},{"./rumor_message.js":177}],176:[function(require,module,exports){
 'use strict';
 
 var Message = require('./rumor_message.js');
 var OTPlugin = require('@opentok/otplugin.js');
 
 module.exports = function PluginSocket(messagingURL, events) {
   var webSocket;
   var state = 'initializing';
@@ -27600,17 +28135,17 @@ module.exports = function PluginSocket(m
   };
 
   this.isClosed = function() {
     return state === 'closed';
   };
 
 };
 
-},{"./rumor_message.js":179,"@opentok/otplugin.js":44}],179:[function(require,module,exports){
+},{"./rumor_message.js":177,"@opentok/otplugin.js":42}],177:[function(require,module,exports){
 (function (Buffer){
 'use strict';
 
 var uuid = require('uuid');
 var encoding = require('./encoding.js');
 var RumorMessageTypes = require('./rumor_message_types.js');
 
 //
@@ -27829,17 +28364,17 @@ RumorMessage.Status = function(topics, h
 // client sends a PING to the server, the server will respond with
 // a PONG.
 RumorMessage.Ping = function() {
   return new RumorMessage(RumorMessageTypes.PING, [], {}, '');
 };
 
 }).call(this,require("buffer").Buffer)
 
-},{"./encoding.js":175,"./rumor_message_types.js":180,"buffer":74,"uuid":111}],180:[function(require,module,exports){
+},{"./encoding.js":173,"./rumor_message_types.js":178,"buffer":72,"uuid":109}],178:[function(require,module,exports){
 'use strict';
 
 // Rumor Messaging for JS
 //
 // https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork
 //
 // @todo Rumor {
 //     Add error codes for all the error cases
@@ -27870,17 +28405,17 @@ module.exports = {
   DISCONNECT: 4,
 
   //Enhancements to support Keepalives
   PING: 7,
   PONG: 8,
   STATUS: 9
 };
 
-},{}],181:[function(require,module,exports){
+},{}],179:[function(require,module,exports){
 'use strict';
 
 var uuid = require('uuid');
 var isDOMUnloaded = require('../../../helpers/is_dom_unloaded.js');
 var Message = require('./rumor_message.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../../logging.js');
 var NativeSocket = require('./native_socket.js');
@@ -28449,25 +28984,25 @@ var RumorSocket = function(options) {
 
 // The number of ms to wait for the websocket to connect
 RumorSocket.CONNECT_TIMEOUT = 15000;
 RumorSocket.RECONNECT_TIMEOUT = 60000;
 RumorSocket.RECONNECT_RETRY = 500;
 
 module.exports = RumorSocket;
 
-},{"../../../helpers/is_dom_unloaded.js":126,"../../logging.js":162,"./native_socket.js":177,"./plugin_socket.js":178,"./rumor_message.js":179,"./rumor_message_types.js":180,"./socket_error.js":182,"@opentok/ot-helpers":4,"uuid":111,"ws":112}],182:[function(require,module,exports){
+},{"../../../helpers/is_dom_unloaded.js":124,"../../logging.js":160,"./native_socket.js":175,"./plugin_socket.js":176,"./rumor_message.js":177,"./rumor_message_types.js":178,"./socket_error.js":180,"@opentok/ot-helpers":4,"uuid":109,"ws":110}],180:[function(require,module,exports){
 'use strict';
 
 module.exports = function(code, message) {
   this.code = code;
   this.message = message;
 };
 
-},{}],183:[function(require,module,exports){
+},{}],181:[function(require,module,exports){
 'use strict';
 
 var analytics = require('./analytics.js');
 var APIKEY = require('./api_key.js');
 var Events = require('./events.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('./logging.js');
 
@@ -28870,17 +29405,17 @@ OTError.handleJsException = function(err
     };
 
     if (!options.target) { options.target = null; }
   }
 
   _exceptionHandler(options.target, errorMsg, code, context);
 };
 
-},{"./analytics.js":141,"./api_key.js":143,"./events.js":157,"./logging.js":162,"@opentok/ot-helpers":4}],184:[function(require,module,exports){
+},{"./analytics.js":139,"./api_key.js":141,"./events.js":155,"./logging.js":160,"@opentok/ot-helpers":4}],182:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../logging.js');
 
 // @meta: ping Mike/Eric to let them know that this data is coming and what format it will be
 // @meta: what reports would I like around this, what question am I trying to answer?
 //
@@ -28957,17 +29492,17 @@ module.exports = function connectionStat
       };
 
       // @todo send client event
       logging.debug(payload);
     }
   };
 };
 
-},{"../logging.js":162,"@opentok/ot-helpers":4}],185:[function(require,module,exports){
+},{"../logging.js":160,"@opentok/ot-helpers":4}],183:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../logging.js');
 
 // Wraps up a native RTCDataChannelEvent object for the message event. This is
 // so we never accidentally leak the native DataChannel.
 //
@@ -29124,17 +29659,17 @@ module.exports = function DataChannel(da
   dataChannel.addEventListener('open', onOpen, false);
   dataChannel.addEventListener('close', onClose, false);
   dataChannel.addEventListener('error', onError, false);
   dataChannel.addEventListener('message', onMessage, false);
 
   return api;
 };
 
-},{"../logging.js":162,"@opentok/ot-helpers":4}],186:[function(require,module,exports){
+},{"../logging.js":160,"@opentok/ot-helpers":4}],184:[function(require,module,exports){
 'use strict';
 
 var OTPlugin = require('@opentok/otplugin.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 /**
  *
  * @returns {function(RTCPeerConnection,
@@ -29188,17 +29723,17 @@ module.exports = function getStatsAdapte
 
   if (OTHelpers.browserVersion().name === 'Firefox' || OTPlugin.isInstalled()) {
     return getStatsNewAPI;
   }
 
   return getStatsOldAPI;
 };
 
-},{"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],187:[function(require,module,exports){
+},{"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],185:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 var getStatsHelpers = {};
 module.exports = getStatsHelpers;
 
 getStatsHelpers.isVideoStat = function(stat) {
@@ -29244,27 +29779,27 @@ getStatsHelpers.normalizeTimestamp = fun
     // Chrome as of 39 delivers a "kind of Date" object for timestamps
     // we duck check it and get the timestamp
     return timestamp.getTime();
   }
 
   return timestamp;
 };
 
-},{"@opentok/ot-helpers":4}],188:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],186:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var OTPlugin = require('@opentok/otplugin.js');
 
 // Normalise these
 var NativeRTCIceCandidate;
 
 if (!OTPlugin.isInstalled()) {
-  NativeRTCIceCandidate = (global.mozRTCIceCandidate || global.RTCIceCandidate);
+  NativeRTCIceCandidate = (global.RTCIceCandidate || global.mozRTCIceCandidate);
 } else {
   NativeRTCIceCandidate = OTPlugin.RTCIceCandidate;
 }
 
 // Process incoming Ice Candidates from a remote connection (which have been
 // forwarded via iceCandidateForwarder). The Ice Candidates cannot be processed
 // until a PeerConnection is available. Once a PeerConnection becomes available
 // the pending PeerConnections can be processed by calling processPending.
@@ -29301,17 +29836,17 @@ module.exports = function IceCandidatePr
     while (_pendingIceCandidates.length) {
       _peerConnection.addIceCandidate(_pendingIceCandidates.shift());
     }
   };
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"@opentok/otplugin.js":44}],189:[function(require,module,exports){
+},{"@opentok/otplugin.js":42}],187:[function(require,module,exports){
 'use strict';
 
 var logging = require('../logging.js');
 var SDPHelpers = require('./sdp_helpers.js');
 
 // Attempt to completely process +offer+. This will:
 // * set the offer as the remote description
 // * create an answer and
@@ -29386,17 +29921,17 @@ module.exports = function offerProcessor
     // Success
     createAnswer,
 
     // Failure
     generateErrorCallback('Error while setting RemoteDescription', 'SetRemoteDescription')
   );
 };
 
-},{"../logging.js":162,"./sdp_helpers.js":195}],190:[function(require,module,exports){
+},{"../logging.js":160,"./sdp_helpers.js":193}],188:[function(require,module,exports){
 (function (global){
 'use strict';
 
 require('../../helpers/web_rtc_polyfills.js');
 
 var analytics = require('../analytics.js');
 var connectionStateLogger = require('./connection_state_logger.js');
 var createPeerConnection = require('../../helpers/create_peer_connection.js');
@@ -29410,19 +29945,18 @@ var PeerConnectionChannels = require('./
 var RaptorConstants = require('../messaging/raptor/raptor_constants.js');
 var subscribeProcessor = require('./subscribe_processor.js');
 var Qos = require('./qos.js');
 
 // Normalise these
 var NativeRTCSessionDescription;
 
 if (!OTPlugin.isInstalled()) {
-  // order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless
-  NativeRTCSessionDescription = (global.mozRTCSessionDescription ||
-                                 global.RTCSessionDescription);
+  NativeRTCSessionDescription = (global.RTCSessionDescription ||
+                                 global.mozRTCSessionDescription);
 } else {
   NativeRTCSessionDescription = OTPlugin.RTCSessionDescription;
 }
 
 // Helper function to forward Ice Candidates via +messageDelegate+
 var iceCandidateForwarder = function(messageDelegate) {
   return function(event) {
     if (event.candidate) {
@@ -29905,17 +30439,17 @@ var PeerConnection = function(config) {
 };
 
 PeerConnection.createPeerConnection = createPeerConnection;
 
 module.exports = PeerConnection;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../helpers/create_peer_connection.js":120,"../../helpers/web_rtc_polyfills.js":138,"../analytics.js":141,"../logging.js":162,"../messaging/raptor/raptor_constants.js":169,"./connection_state_logger.js":184,"./get_stats_adapter.js":186,"./ice_candidate_processor":188,"./offer_processor.js":189,"./peer_connection_channels.js":191,"./qos.js":194,"./subscribe_processor.js":197,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],191:[function(require,module,exports){
+},{"../../helpers/create_peer_connection.js":118,"../../helpers/web_rtc_polyfills.js":136,"../analytics.js":139,"../logging.js":160,"../messaging/raptor/raptor_constants.js":167,"./connection_state_logger.js":182,"./get_stats_adapter.js":184,"./ice_candidate_processor":186,"./offer_processor.js":187,"./peer_connection_channels.js":189,"./qos.js":192,"./subscribe_processor.js":195,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],189:[function(require,module,exports){
 'use strict';
 
 var DataChannel = require('./data_channel.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 // Contains a collection of DataChannels for a particular RTCPeerConnection
 //
 // @param [RTCPeerConnection] pc A native peer connection object
@@ -30020,17 +30554,17 @@ module.exports = function PeerConnection
 
   pc.addEventListener('datachannel', function(event) {
     add(event.channel);
   }, false);
 
   return api;
 };
 
-},{"./data_channel.js":185,"@opentok/ot-helpers":4}],192:[function(require,module,exports){
+},{"./data_channel.js":183,"@opentok/ot-helpers":4}],190:[function(require,module,exports){
 'use strict';
 
 var PeerConnection = require('./peer_connection.js');
 
 var connections = {};
 
 var PeerConnections = {};
 module.exports = PeerConnections;
@@ -30061,17 +30595,17 @@ PeerConnections.remove = function(remote
 
     if (ref.count === 0) {
       ref.pc.disconnect();
       delete connections[key];
     }
   }
 };
 
-},{"./peer_connection.js":190}],193:[function(require,module,exports){
+},{"./peer_connection.js":188}],191:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var PeerConnections = require('./peer_connections.js');
 var RaptorConstants = require('../messaging/raptor/raptor_constants.js');
 var setCertificates = require('./set_certificates.js');
 
 /*
@@ -30249,17 +30783,17 @@ module.exports = function PublisherPeerC
     return _peerConnection.createOfferWithIceRestart(subscriberUri);
   };
 
   this.iceConnectionStateIsConnected = function() {
     return _peerConnection.iceConnectionStateIsConnected();
   };
 };
 
-},{"../messaging/raptor/raptor_constants.js":169,"./peer_connections.js":192,"./set_certificates.js":196,"@opentok/ot-helpers":4}],194:[function(require,module,exports){
+},{"../messaging/raptor/raptor_constants.js":167,"./peer_connections.js":190,"./set_certificates.js":194,"@opentok/ot-helpers":4}],192:[function(require,module,exports){
 'use strict';
 
 var OTPlugin = require('@opentok/otplugin.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('../logging.js');
 
 //
 // There are three implementations of stats parsing in this file.
@@ -30599,17 +31133,17 @@ var Qos = function(qosCallback) {
 
 // Send stats after 1 sec
 Qos.INITIAL_INTERVAL = 1000;
 // Recalculate the stats every 30 sec
 Qos.INTERVAL = 30000;
 
 module.exports = Qos;
 
-},{"../logging.js":162,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],195:[function(require,module,exports){
+},{"../logging.js":160,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],193:[function(require,module,exports){
 'use strict';
 
 var logging = require('../logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 var START_MEDIA_SSRC = 10000;
 var START_RTX_SSRC = 20000;
 
@@ -30901,16 +31435,21 @@ SDPHelpers.enableSimulcast = function en
   var sdpLines = sdp.split('\r\n');
   var videoAttrs = SDPHelpers.getAttributesForMediaType(sdpLines, 'video');
 
   if (videoAttrs.filterByName('ssrc-group:SIM').length > 0) {
     logging.debug('Simulcast is already enabled in this SDP, not attempting to enable again.');
     return sdp;
   }
 
+  if (!videoAttrs.msid) {
+    logging.debug('No local stream attached, not enabling simulcast.');
+    return sdp;
+  }
+
   var usingRTX = videoAttrs.isUsingRTX();
   var mediaSSRC = [];
   var rtxSSRC = [];
 
   // 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); }
@@ -30950,17 +31489,17 @@ SDPHelpers.enableSimulcast = function en
 
 // Modifies +sdp+ to work around this bug: http://crbug.com/528089
 // In Chrome if you call createOffer() on a peer that is recvonly, it generates SDP that is sendonly
 // which doesn't work. Perhaps unsurprisingly. This fixes it.
 SDPHelpers.fixChromeBug528089 = function(sdp) {
   return sdp.replace(/sendonly/g, 'recvonly');
 };
 
-},{"../logging.js":162,"@opentok/ot-helpers":4}],196:[function(require,module,exports){
+},{"../logging.js":160,"@opentok/ot-helpers":4}],194:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function(pcConfig, completion) {
   if (
     OTHelpers.env.name === 'Firefox' &&
@@ -30982,17 +31521,17 @@ module.exports = function(pcConfig, comp
     OTHelpers.callAsync(function() {
       completion(undefined, pcConfig);
     });
   }
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"@opentok/ot-helpers":4}],197:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],195:[function(require,module,exports){
 'use strict';
 
 var logging = require('../logging.js');
 var SDPHelpers = require('./sdp_helpers.js');
 
 // Attempt to completely process a subscribe message. This will:
 // * create an Offer
 // * set the new offer as the location description
@@ -31052,17 +31591,17 @@ module.exports = function subscribeProce
 
     // Constraints
     {
       iceRestart: true
     }
   );
 };
 
-},{"../logging.js":162,"./sdp_helpers.js":195}],198:[function(require,module,exports){
+},{"../logging.js":160,"./sdp_helpers.js":193}],196:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var parseIceServers = require('../messaging/raptor/parse_ice_servers.js');
 var PeerConnections = require('./peer_connections.js');
 var RaptorConstants = require('../messaging/raptor/raptor_constants.js');
 var setCertificates = require('./set_certificates.js');
 
@@ -31356,17 +31895,17 @@ module.exports = function SubscriberPeer
         );
 
         completion(undefined, _subscriberPeerConnection);
       }
     });
   };
 };
 
-},{"../messaging/raptor/parse_ice_servers.js":168,"../messaging/raptor/raptor_constants.js":169,"./peer_connections.js":192,"./set_certificates.js":196,"@opentok/ot-helpers":4}],199:[function(require,module,exports){
+},{"../messaging/raptor/parse_ice_servers.js":166,"../messaging/raptor/raptor_constants.js":167,"./peer_connections.js":190,"./set_certificates.js":194,"@opentok/ot-helpers":4}],197:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var analytics = require('../analytics.js');
 var APIKEY = require('../api_key.js');
 var Archiving = require('../chrome/archiving.js');
 var audioContext = require('../../helpers/audio_context.js');
 var AudioLevelMeter = require('../chrome/audio_level_meter.js');
@@ -33197,17 +33736,17 @@ Publisher.getUserMedia = getUserMedia;
 Publisher.Microphone = Microphone;
 Publisher.PublisherPeerConnection = PublisherPeerConnection;
 Publisher.WidgetView = WidgetView;
 
 module.exports = Publisher;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../helpers/audio_context.js":115,"../../helpers/connectivity_attempt_pinger.js":119,"../../helpers/device_helpers.js":122,"../../helpers/get_user_media.js":124,"../../helpers/properties.js":128,"../../helpers/video_orientation.js":137,"../../helpers/widget_view.js":139,"../analytics.js":141,"../api_key.js":143,"../audio_level_transformer":145,"../chrome/archiving.js":147,"../chrome/audio_level_meter.js":148,"../chrome/backing_bar.js":149,"../chrome/chrome.js":151,"../chrome/mute_button.js":152,"../chrome/name_panel.js":153,"../environment_loader.js":156,"../events.js":157,"../exception_codes.js":158,"../interval_runner.js":161,"../logging.js":162,"../messaging/raptor/parse_ice_servers.js":168,"../ot_error.js":183,"../peer_connection/publisher_peer_connection.js":193,"../screensharing/screen_sharing.js":210,"../stream_channel.js":217,"../stylable_component.js":218,"../system_requirements.js":222,"./max_delay.js":201,"./microphone.js":202,"./state.js":203,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44,"bluebird":72,"uuid":111}],200:[function(require,module,exports){
+},{"../../helpers/audio_context.js":113,"../../helpers/connectivity_attempt_pinger.js":117,"../../helpers/device_helpers.js":120,"../../helpers/get_user_media.js":122,"../../helpers/properties.js":126,"../../helpers/video_orientation.js":135,"../../helpers/widget_view.js":137,"../analytics.js":139,"../api_key.js":141,"../audio_level_transformer":143,"../chrome/archiving.js":145,"../chrome/audio_level_meter.js":146,"../chrome/backing_bar.js":147,"../chrome/chrome.js":149,"../chrome/mute_button.js":150,"../chrome/name_panel.js":151,"../environment_loader.js":154,"../events.js":155,"../exception_codes.js":156,"../interval_runner.js":159,"../logging.js":160,"../messaging/raptor/parse_ice_servers.js":166,"../ot_error.js":181,"../peer_connection/publisher_peer_connection.js":191,"../screensharing/screen_sharing.js":208,"../stream_channel.js":215,"../stylable_component.js":216,"../system_requirements.js":220,"./max_delay.js":199,"./microphone.js":200,"./state.js":201,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42,"bluebird":70,"uuid":109}],198:[function(require,module,exports){
 'use strict';
 
 var logging = require('../logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 var Publisher = require('./index.js');
 var sessionObjects = require('../session/objects.js');
 
 /**
@@ -33577,22 +34116,22 @@ module.exports = function initPublisher(
   publisher.once('initSuccess', removeInitSuccessAndCallComplete);
   publisher.once('publishComplete', removeHandlersAndCallComplete);
 
   publisher.publish(targetElement);
 
   return publisher;
 };
 
-},{"../logging.js":162,"../session/objects.js":214,"./index.js":199,"@opentok/ot-helpers":4}],201:[function(require,module,exports){
+},{"../logging.js":160,"../session/objects.js":212,"./index.js":197,"@opentok/ot-helpers":4}],199:[function(require,module,exports){
 'use strict';
 
 module.exports = 15000;
 
-},{}],202:[function(require,module,exports){
+},{}],200:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 /*
  * A Publishers Microphone.
  *
  * TODO
@@ -33627,17 +34166,17 @@ module.exports = function Microphone(web
   } else if (webRTCStream.getAudioTracks().length) {
     this.muted(!webRTCStream.getAudioTracks()[0].enabled);
 
   } else {
     this.muted(false);
   }
 };
 
-},{"@opentok/ot-helpers":4}],203:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],201:[function(require,module,exports){
 'use strict';
 
 var generateSimpleStateMachine = require('../generate_simple_state_machine.js');
 
 // Models a Publisher's publishing State
 //
 // Valid States:
 //    NotPublishing
@@ -33728,17 +34267,17 @@ PublishingState.prototype.isAttemptingTo
     'PublishingToSession'
   ].indexOf(this.current) !== -1;
 };
 
 PublishingState.prototype.isPublishing = function() {
   return this.current === 'Publishing';
 };
 
-},{"../generate_simple_state_machine.js":159}],204:[function(require,module,exports){
+},{"../generate_simple_state_machine.js":157}],202:[function(require,module,exports){
 'use strict';
 
 var Bluebird = require('bluebird');
 
 module.exports = function httpTest(config) {
 
   var _httpConfig = config.httpConfig;
 
@@ -33861,17 +34400,17 @@ module.exports = function httpTest(confi
     .then(function(results) {
       return {
         downloadBandwidth: results[0],
         uploadBandwidth: results[1]
       };
     });
 };
 
-},{"bluebird":72}],205:[function(require,module,exports){
+},{"bluebird":70}],203:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var createPeerConnection = require('../../helpers/create_peer_connection.js');
 var logging = require('../logging.js');
 var getStatsAdpater = require('../peer_connection/get_stats_adapter.js');
 var getStatsHelpers = require('../peer_connection/get_stats_helpers.js');
 var OTHelpers = require('@opentok/ot-helpers');
@@ -33890,17 +34429,17 @@ module.exports = function webrtcTest(con
   // todo copied from peer_connection.js
   // Normalise these
   // var NativeRTCSessionDescription;
   var NativeRTCIceCandidate;
   if (!OTPlugin.isInstalled()) {
     // order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless
     // NativeRTCSessionDescription = (global.mozRTCSessionDescription ||
     //   global.RTCSessionDescription);
-    NativeRTCIceCandidate = (global.mozRTCIceCandidate || global.RTCIceCandidate);
+    NativeRTCIceCandidate = (global.RTCIceCandidate || global.mozRTCIceCandidate);
   } else {
     // NativeRTCSessionDescription = OTPlugin.RTCSessionDescription;
     NativeRTCIceCandidate = OTPlugin.RTCIceCandidate;
   }
 
   function isCandidateRelay(candidate) {
     return candidate.candidate.indexOf('relay') !== -1;
   }
@@ -34187,17 +34726,17 @@ module.exports = function webrtcTest(con
           dispose();
           throw error;
         });
     });
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../../helpers/create_peer_connection.js":120,"../../helpers/video_element/index.js":133,"../logging.js":162,"../peer_connection/get_stats_adapter.js":186,"../peer_connection/get_stats_helpers.js":187,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}],206:[function(require,module,exports){
+},{"../../helpers/create_peer_connection.js":118,"../../helpers/video_element/index.js":131,"../logging.js":160,"../peer_connection/get_stats_adapter.js":184,"../peer_connection/get_stats_helpers.js":185,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}],204:[function(require,module,exports){
 'use strict';
 
 var analytics      = require('./analytics.js');
 var ExceptionCodes = require('./exception_codes.js');
 var OTError        = require('./ot_error.js');
 var OTHelpers      = require('@opentok/ot-helpers');
 var sessionObjects = require('./session/objects.js');
 var uuid           = require('uuid');
@@ -34254,17 +34793,17 @@ module.exports = function reportIssue(co
         sessionId: session.sessionId,
         partnerId: session.isConnected() ? session.sessionInfo.partnerId : null
       }, eventOptions);
       analytics.logEvent(individualSessionEventOptions, null, logEventCompletionHandler);
     });
   }
 };
 
-},{"./analytics.js":141,"./exception_codes.js":158,"./ot_error.js":183,"./session/objects.js":214,"@opentok/ot-helpers":4,"uuid":111}],207:[function(require,module,exports){
+},{"./analytics.js":139,"./exception_codes.js":156,"./ot_error.js":181,"./session/objects.js":212,"@opentok/ot-helpers":4,"uuid":109}],205:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var uuid = require('uuid');
 
 /* global chrome */
 
 module.exports = {
@@ -34470,17 +35009,17 @@ module.exports = {
       getConstraints: getConstraints
     };
 
   }
 };
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"uuid":111}],208:[function(require,module,exports){
+},{"uuid":109}],206:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = {
   isSupportedInThisBrowser: OTHelpers.env.name === 'Firefox',
   autoRegisters: true,
   extensionRequired: false,
@@ -34512,17 +35051,17 @@ module.exports = {
         }
 
         callback(void 0, constraints);
       }
     };
   }
 };
 
-},{"@opentok/ot-helpers":4}],209:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],207:[function(require,module,exports){
 'use strict';
 
 var OTPlugin = require('@opentok/otplugin.js');
 
 module.exports = {
   isSupportedInThisBrowser: OTPlugin.isSupported(),
   autoRegisters: true,
   extensionRequired: true,
@@ -34542,17 +35081,17 @@ module.exports = {
         constraints.video.mandatory.chromeMediaSource = source;
 
         callback(void 0, constraints);
       }
     };
   }
 };
 
-},{"@opentok/otplugin.js":44}],210:[function(require,module,exports){
+},{"@opentok/otplugin.js":42}],208:[function(require,module,exports){
 'use strict';
 
 var chromeExtensionHelper = require('./chrome_extension_helper.js');
 var firefoxExtensionHelper = require('./firefox_extension_helper.js');
 var ieExtensionHelper = require('./ie_extension_helper.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 var screenSharing = {};
@@ -34736,17 +35275,17 @@ screenSharing.checkCapability = function
     callback(response);
   });
 };
 
 screenSharing.registerExtensionHelper('chrome', chromeExtensionHelper);
 screenSharing.registerExtensionHelper('firefox', firefoxExtensionHelper);
 screenSharing.registerExtensionHelper('OpenTokPlugin', ieExtensionHelper);
 
-},{"./chrome_extension_helper.js":207,"./firefox_extension_helper.js":208,"./ie_extension_helper.js":209,"@opentok/ot-helpers":4}],211:[function(require,module,exports){
+},{"./chrome_extension_helper.js":205,"./firefox_extension_helper.js":206,"./ie_extension_helper.js":207,"@opentok/ot-helpers":4}],209:[function(require,module,exports){
 'use strict';
 
 var uuid                      = require('uuid');
 var Bluebird                  = require('bluebird');
 var analytics                 = require('../analytics.js');
 var APIKEY                    = require('../api_key.js');
 var Capabilities              = require('../capabilities.js');
 var ConnectivityAttemptPinger = require('../../helpers/connectivity_attempt_pinger.js');
@@ -37003,17 +37542,17 @@ SessionHandle.Session = function(apiKey,
    * @see <a href="Session.html#signal">Session.signal()</a>
    * @see SignalEvent
    * @see <a href="#event:signal">signal</a> event
    */
 };
 
 SessionHandle.Session.RaptorSocket = RaptorSocket;
 
-},{"../../helpers/connectivity_attempt_pinger.js":119,"../../helpers/properties.js":128,"../analytics.js":141,"../api_key.js":143,"../capabilities.js":146,"../events.js":157,"../exception_codes.js":158,"../logging.js":162,"../messaging/raptor/raptor_socket.js":170,"../messaging/raptor/session_dispatcher.js":172,"../ot_error.js":183,"../publisher":199,"../publisher/init.js":200,"../qos_testing/http_test.js":204,"../qos_testing/webrtc_test.js":205,"../subscriber":220,"../system_requirements.js":222,"./info.js":212,"./objects.js":214,"./tag.js":215,"@opentok/ot-helpers":4,"bluebird":72,"uuid":111}],212:[function(require,module,exports){
+},{"../../helpers/connectivity_attempt_pinger.js":117,"../../helpers/properties.js":126,"../analytics.js":139,"../api_key.js":141,"../capabilities.js":144,"../events.js":155,"../exception_codes.js":156,"../logging.js":160,"../messaging/raptor/raptor_socket.js":168,"../messaging/raptor/session_dispatcher.js":170,"../ot_error.js":181,"../publisher":197,"../publisher/init.js":198,"../qos_testing/http_test.js":202,"../qos_testing/webrtc_test.js":203,"../subscriber":218,"../system_requirements.js":220,"./info.js":210,"./objects.js":212,"./tag.js":213,"@opentok/ot-helpers":4,"bluebird":70,"uuid":109}],210:[function(require,module,exports){
 'use strict';
 
 var Anvil          = require('../anvil.js');
 var Bluebird       = require('bluebird');
 var ExceptionCodes = require('../exception_codes.js');
 var logging        = require('../logging.js');
 
 // This sequence defines the delay before retry. Therefore a 0 indicates
@@ -37094,17 +37633,17 @@ SessionInfo.get = function(id, token) {
 
   return new Bluebird.Promise(function(resolve, reject) {
     attempt(void 0, resolve, reject);
   });
 };
 
 module.exports = SessionInfo;
 
-},{"../anvil.js":142,"../exception_codes.js":158,"../logging.js":162,"bluebird":72}],213:[function(require,module,exports){
+},{"../anvil.js":140,"../exception_codes.js":156,"../logging.js":160,"bluebird":70}],211:[function(require,module,exports){
 'use strict';
 
 var SessionHandle  = require('./handle.js');
 var sessionObjects = require('./objects.js');
 
 /**
 * The first step in using the OpenTok API is to call the <code>OT.initSession()</code>
 * method. Other methods of the OT object check for system requirements and set up error logging.
@@ -37171,17 +37710,17 @@ module.exports = function initSession(ap
   if (!session) {
     session = new SessionHandle.Session(apiKey, sessionId);
     sessionObjects.sessions.add(session);
   }
 
   return session;
 };
 
-},{"./handle.js":211,"./objects.js":214}],214:[function(require,module,exports){
+},{"./handle.js":209,"./objects.js":212}],212:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 
 var sessionObjects = {};
 
 // TODO: Eliminate the need for this module, which is globally tracking these objects.
 
@@ -37190,26 +37729,26 @@ sessionObjects.publishers = new OTHelper
 
 // Subscribers are id'd by their widgetId
 sessionObjects.subscribers = new OTHelpers.Collection('widgetId');
 
 sessionObjects.sessions = new OTHelpers.Collection();
 
 module.exports = sessionObjects;
 
-},{"@opentok/ot-helpers":4}],215:[function(require,module,exports){
+},{"@opentok/ot-helpers":4}],213:[function(require,module,exports){
 'use strict';
 
 // This is used to break the dependency Raptor had on Session. It only needs to be able to know
 // whether an object is an instanceof a Session. The dependency was an issue for node because
 // Session depends on get_user_media.js which doesn't work in node.
 
 module.exports = {};
 
-},{}],216:[function(require,module,exports){
+},{}],214:[function(require,module,exports){
 'use strict';
 
 var Events         = require('./events.js');
 var logging        = require('./logging.js');
 var OTHelpers      = require('@opentok/ot-helpers');
 var sessionObjects = require('./session/objects.js');
 
 var validPropertyNames = ['name', 'archiving'];
@@ -37641,17 +38180,17 @@ module.exports = function Stream(id, nam
     }
   };
 
   this._.updateChannel = function(channelId, attributes) {
     self.getChannel(channelId).update(attributes);
   };
 };
 
-},{"./events.js":157,"./logging.js":162,"./session/objects.js":214,"@opentok/ot-helpers":4}],217:[function(require,module,exports){
+},{"./events.js":155,"./logging.js":160,"./session/objects.js":212,"@opentok/ot-helpers":4}],215:[function(require,module,exports){
 'use strict';
 
 var OTHelpers = require('@opentok/ot-helpers');
 var logging = require('./logging.js');
 var VideoOrientation = require('../helpers/video_orientation.js');
 
 // id: String                           | mandatory | immutable
 // type: String {video/audio/data/...}  | mandatory | immutable
@@ -37745,17 +38284,17 @@ module.exports = function StreamChannel(
       // which is an aggregate of width, height, and orientation changes.
       this.trigger('update', this, 'videoDimensions', oldVideoDimensions, videoDimensions);
     }
 
     return true;
   };
 };
 
-},{"../helpers/video_orientation.js":137,"./logging.js":162,"@opentok/ot-helpers":4}],218:[function(require,module,exports){
+},{"../helpers/video_orientation.js":135,"./logging.js":160,"@opentok/ot-helpers":4}],216:[function(require,module,exports){
 'use strict';
 
 var logging = require('./logging.js');
 var Style = require('./style.js');
 
 /* Stylable Notes
  * Some bits are controlled by multiple flags, i.e. buttonDisplayMode and nameDisplayMode.
  * When there are multiple flags how is the final setting chosen?
@@ -38011,17 +38550,17 @@ module.exports = function StylableCompon
         logPayload[keyOrStyleHash] = value;
       }
       if (logSetStyleWithPayload) { logSetStyleWithPayload(logPayload); }
       return this;
     };
   }
 };
 
-},{"./logging.js":162,"./style.js":219}],219:[function(require,module,exports){
+},{"./logging.js":160,"./style.js":217}],217:[function(require,module,exports){
 'use strict';
 
 var logging = require('./logging.js');
 var OTHelpers = require('@opentok/ot-helpers');
 
 module.exports = function Style(initalStyles, onStyleChange) {
   var _style = {};
 
@@ -38136,17 +38675,17 @@ module.exports = function Style(initalSt
     }
 
     return this;
   };
 
   if (initalStyles) { this.setAll(initalStyles, true); }
 };
 
-},{"./logging.js":162,"@opentok/ot-helpers":4}],220:[function(require,module,exports){
+},{"./logging.js":160,"@opentok/ot-helpers":4}],218:[function(require,module,exports){
 'use strict';
 
 var uuid =                            require('uuid');
 var analytics =                       require('../analytics.js');
 var audioContext =                    require('../../helpers/audio_context.js');
 var GetstatsAudioOutputLevelSampler = require('../../helpers/audio_level_samplers/getstats_audio_output_level_sampler');
 var WebaudioAudioLevelSampler =       require('../../helpers/audio_level_samplers/webaudio_audio_level_sampler');
 var AudioLevelTransformer =           require('../audio_level_transformer');
@@ -39597,17 +40136,17 @@ var Subscriber = function(targetElement,
   */
 };
 
 Subscriber.WidgetView = WidgetView;
 Subscriber.SubscriberPeerConnection = SubscriberPeerConnection;
 
 module.exports = Subscriber;
 
-},{"../../helpers/audio_context.js":115,"../../helpers/audio_level_samplers/getstats_audio_output_level_sampler":116,"../../helpers/audio_level_samplers/webaudio_audio_level_sampler":117,"../../helpers/connectivity_attempt_pinger.js":119,"../../helpers/properties.js":128,"../../helpers/widget_view.js":139,"../analytics.js":141,"../audio_level_transformer":145,"../chrome/audio_level_meter.js":148,"../chrome/backing_bar.js":149,"../chrome/chrome.js":151,"../chrome/mute_button.js":152,"../chrome/name_panel.js":153,"../chrome/video_disabled_indicator.js":154,"../events.js":157,"../exception_codes.js":158,"../interval_runner.js":161,"../logging.js":162,"../messaging/raptor/message.js":167,"../ot_error.js":183,"../peer_connection/get_stats_helpers.js":187,"../peer_connection/subscriber_peer_connection.js":198,"../stylable_component.js":218,"./state.js":221,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44,"uuid":111}],221:[function(require,module,exports){
+},{"../../helpers/audio_context.js":113,"../../helpers/audio_level_samplers/getstats_audio_output_level_sampler":114,"../../helpers/audio_level_samplers/webaudio_audio_level_sampler":115,"../../helpers/connectivity_attempt_pinger.js":117,"../../helpers/properties.js":126,"../../helpers/widget_view.js":137,"../analytics.js":139,"../audio_level_transformer":143,"../chrome/audio_level_meter.js":146,"../chrome/backing_bar.js":147,"../chrome/chrome.js":149,"../chrome/mute_button.js":150,"../chrome/name_panel.js":151,"../chrome/video_disabled_indicator.js":152,"../events.js":155,"../exception_codes.js":156,"../interval_runner.js":159,"../logging.js":160,"../messaging/raptor/message.js":165,"../ot_error.js":181,"../peer_connection/get_stats_helpers.js":185,"../peer_connection/subscriber_peer_connection.js":196,"../stylable_component.js":216,"./state.js":219,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42,"uuid":109}],219:[function(require,module,exports){
 'use strict';
 
 var generateSimpleStateMachine = require('../generate_simple_state_machine.js');
 
 // Models a Subscriber's subscribing State
 //
 // Valid States:
 //     NotSubscribing            (the initial state
@@ -39701,17 +40240,17 @@ SubscribingState.prototype.isAttemptingT
     'Init',
     'ConnectingToPeer',
     'BindingRemoteStream'
   ].indexOf(this.current) !== -1;
 };
 
 module.exports = SubscribingState;
 
-},{"../generate_simple_state_machine.js":159}],222:[function(require,module,exports){
+},{"../generate_simple_state_machine.js":157}],220:[function(require,module,exports){
 (function (global){
 'use strict';
 
 var analytics = require('./analytics.js');
 var APIKEY = require('./api_key.js');
 var Dialogs = require('../helpers/dialogs.js');
 var EnvironmentLoader = require('../ot/environment_loader.js');
 var OTPlugin = require('@opentok/otplugin.js');
@@ -39848,10 +40387,10 @@ systemRequirements.upgrade = function() 
     }, 100);
   });
 };
 
 module.exports = systemRequirements;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
 
-},{"../helpers/dialogs.js":123,"../helpers/properties.js":128,"../ot/environment_loader.js":156,"./analytics.js":141,"./api_key.js":143,"./logging.js":162,"@opentok/ot-helpers":4,"@opentok/otplugin.js":44}]},{},[140])
-
+},{"../helpers/dialogs.js":121,"../helpers/properties.js":126,"../ot/environment_loader.js":154,"./analytics.js":139,"./api_key.js":141,"./logging.js":160,"@opentok/ot-helpers":4,"@opentok/otplugin.js":42}]},{},[138])
+
--- a/browser/extensions/loop/chrome/locale/bn-BD/loop.properties
+++ b/browser/extensions/loop/chrome/locale/bn-BD/loop.properties
@@ -1,15 +1,15 @@
 # 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/.
 
 # Panel Strings
 
-clientSuperShortname=হ্যালো
+clientSuperShortname=Hello
 
 ## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
 ## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
 ## use "..." if \u2026 doesn't suit traditions in your locale.
 loopMenuItem_label=আলাপ শুরু করুন
 loopMenuItem_accesskey=t
 
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
@@ -38,29 +38,34 @@ first_time_experience_subheading_button_above=বন্ধুর সাথে ওয়েব পেজ ব্রাউজ করতে Hello বোতাম চাপুন।
 first_time_experience_content=একসাথে পরিকল্পনা করতে, কাজ করতে, আনন্দ করতে - এটি ব্যবহার করুন।
 first_time_experience_content2=একসাথে পরিকল্পনা করতে, কাজ করতে, আনন্দ করতে - এটি ব্যবহার করুন।
 first_time_experience_button_label2=দেখুন এটা কিভাবে কাজ করে
 
 ## First Time Experience Slides
 fte_slide_1_title=বন্ধুর সাথে পাতাটি ব্রাউজ করুন
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
+fte_slide_1_copy=যখনই আপনি কোন উপহারের জন্য কেনাকাটা বা কোথায় বেড়ানোর পরিকল্পনা করছেন, {{clientShortname2}} আপনাকে দ্রুত সময়ের মধ্যে তাৎক্ষণিক সিদ্ধান্ত নিতে সাহায্য করবে।
 fte_slide_2_title=একই পাতায় পেতে
 fte_slide_2_copy=কোন আইডিয়া শেয়ার করতে, বিকল্পগুলো তুলনা করছে এবং একটি সিধান্তে আসতে বিল্ট-ইন টেক্সট অথবা ভিডিও চ্যাট ব্যবহার করুন।
 fte_slide_3_title=লিঙ্ক শেয়ার করে বন্ধুদের আলাপে আমন্ত্রণ জানান
 ## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
+fte_slide_3_copy={{clientSuperShortname}} প্রায় সকল ডেস্কটপ ব্রাউজারের সাথে কাজ করে। কোন অ্যাকাউন্টের প্রয়োজন নেই এবং সকলেই বিনামুল্যে সংযুক্ত হতে পারেন।
 ## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
+fte_slide_4_title=শুরু করতে {{clientSuperShortname}} আইকন খুঁজে নিন
 ## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
 ## will be replaced by the brand short name.
+fte_slide_4_copy=যখনই আপনি কোন পাতা পাবেন আলোচনা করার জন্যে, লিঙ্ক তৈরি করতে {{brandShortname}} এ ক্লিক করুন। তারপর সেই লিঙ্ক যেকোন ভাবে আপনার বন্ধুর কাছে পাঠিয়ে দিন।
 
 invite_header_text_bold=আপনার সাথে এই পাতা ব্রাউজে যোগদানে কাউকে আমন্ত্রণ জানান!
 invite_header_text_bold2=কাউকে আমন্ত্রণ জানান আপনার সাথে যোগ দিতে ।
 invite_header_text3=ফায়ারফক্স হ্যালো ব্যবহার করতে দুজনের প্রয়োজন হয়, আপনার সাথে ওয়েব ব্রাউজ করতে আপনার বন্ধুকে লিঙ্ক পাঠান।
+invite_header_text4=এই লিঙ্কটি শেয়ার করুন যেন আপনারা একসাথে ওয়েব ব্রাউজ করতে পারেন।
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=লিঙ্ক কপি করুন
 invite_copied_link_button=কপি করা হয়েছে!
 invite_email_link_button=ইমেইল লিঙ্ক
 invite_facebook_button3=ফেসবুক
 invite_your_link=আপনার লিঙ্ক:
@@ -190,17 +195,19 @@ room_name_untitled_page=শিরোনামহীন পাতা
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=আবার দেখা হবে! Hello প্যানেলের মাধ্যমে আপনি এই শেয়ারকৃত সেশনে যেকোন সময় ফিরে আসতে পারবেন।
 door_hanger_prompt_name=সহজভাবে মনে রাখার জন্য কোনো নাম দিতে চান? বর্তমান নাম:
 door_hanger_button=ঠিক আছে
 
 # Infobar strings
 
+infobar_screenshare_no_guest_message=যখনই আপনার বন্ধু যোগ দেবে, তারা আপনি কি ট্যাব ক্লিক করছেন তা দেখতে পাবে।
 infobar_screenshare_browser_message2=আপনি আপনার ট্যাব শেয়ার করছেন। যেকোনো ট্যাব ক্লিক করলে তা আপনার বন্ধু দেখতে পারবে।
+infobar_screenshare_browser_message3=আপনি এখন আপনার ট্যাব শেয়ার করছেন। আপনে যেই ট্যাবেই ক্লিক করুন তা আপনার বন্ধু দেখতে পাবে।
 infobar_screenshare_stop_sharing_message=আপনি আপনার ট্যাব আর শেয়ার করছেন না
 infobar_button_restart_label2=শেয়ার করা পুনরায় শুরু করুন
 infobar_button_restart_accesskey=R
 infobar_button_stop_label2=শেয়ার করা বন্ধ করুন
 infobar_button_stop_accesskey=S
 infobar_button_disconnect_label=বিচ্ছিন্ন
 infobar_button_disconnect_accesskey=D
 
@@ -248,8 +255,10 @@ rooms_room_join_label=আালাপে যোগ দিন
 rooms_room_joined_owner_connected_label2=আপনার বন্ধু এখন সংযুক্ত হয়েছেন এবং আপনার ট্যাব দেখতে পারবেন।
 rooms_room_joined_owner_not_connected_label=আপনার বন্ধু আপনার সাথে {{roomURLHostname}} ব্রাউজ করতে অপেক্ষা করছেন।
 
 self_view_hidden_message=সেলফ-ভিউ লুকানো কিন্তু এখনো ছবি পাঠাবে; দেখাতে উইন্ডোর আকার পরিবর্তন করুন
 
 ## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
 ## as this will be replaced by clientShortname2.
 tos_failure_message={{clientShortname}} আপনার দেশের বিদ্যমান নয়।
+
+display_name_guest=অতিথি
--- a/browser/extensions/loop/chrome/locale/cs/loop.properties
+++ b/browser/extensions/loop/chrome/locale/cs/loop.properties
@@ -31,30 +31,41 @@ panel_disconnect_button=Odpojit
 ## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Klepněte na tlačítko Hello a prohlížejte si web spolu s přítelem.
 first_time_experience_subheading_button_above=Klepněte na tlačítko výše a prohlížejte si web s přítelem.
 
 ## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Použijte jej pro společné plánování, práci i zábavu.
+first_time_experience_content2=Použijte jej pro společné plánování, zábavu i práci.
 first_time_experience_button_label2=Podívejte se, jak to funguje
 
 ## First Time Experience Slides
+fte_slide_1_title=Prohlížejte si web spolu s přítelem
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
+fte_slide_1_copy=Ať už plánujete výlet nebo vybíráte dárek, {{clientShortname2}} vám pomůže urychlit společné rozhodování.
+fte_slide_2_title=Buďte na stejné stránce
+fte_slide_2_copy=Použijte vestavěný chat a video pro sdílení nápadů, srovnání možností a společnému rozhodnutí.
+fte_slide_3_title=Pozvěte přátele posláním odkazu
 ## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
+fte_slide_3_copy={{clientSuperShortname}} funguje ve většině desktopových prohlížečů. Nepotřebujete žádný účet a každý se může připojit zdarma.
 ## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
+fte_slide_4_title=Najděte ikonu {{clientSuperShortname}} a začněte
 ## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
 ## will be replaced by the brand short name.
+fte_slide_4_copy=Jakmile najdete stránku, o které chcete mluvit, klepněte na ikonu {{brandShortname}} a vytvořte odkaz. Ten pošlete svému příteli jakýmkoliv způsobem se vám zrovna hodí!
 
 invite_header_text_bold=Pozvěte někoho, kdo bude prohlížet tuto stránku s vámi!
+invite_header_text_bold2=Pozvěte přítele na hovor!
 invite_header_text3=K používání Firefox Hello jsou potřeba dva, takže pošlete kamarádovi odkaz, aby si prohlížel web s vámi!
+invite_header_text4=Sdílejte tento odkaz a můžete začít prohlížeč web společně.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Kopírovat odkaz
 invite_copied_link_button=Zkopírováno!
 invite_email_link_button=Poslat odkaz e-mailem
 invite_facebook_button3=Facebook
 invite_your_link=Váš odkaz:
@@ -245,8 +256,10 @@ rooms_room_join_label=Připojit ke konverzaci
 rooms_room_joined_owner_connected_label2=Váš přítel je nyní připojen a může tak vidět vaše panely.
 rooms_room_joined_owner_not_connected_label=Váš přítel čeká na prohlížení s vámi v místnosti {{roomURLHostname}}.
 
 self_view_hidden_message=Váš obraz byl skryt, ale je nadále odesílán; pro jeho zobrazení změňte velikost okna
 
 ## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
 ## as this will be replaced by clientShortname2.
 tos_failure_message={{clientShortname}} není ve vaší zemi dostupný.
+
+display_name_guest=Host
--- a/browser/extensions/loop/chrome/locale/da/loop.properties
+++ b/browser/extensions/loop/chrome/locale/da/loop.properties
@@ -23,39 +23,58 @@ sign_in_again_title_line_two2=for at forsætte med at bruge {{clientShortname2}}
 sign_in_again_button=Log ind
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
 sign_in_again_use_as_guest_button2=Brug {{clientSuperShortname}} som gæst
 
 panel_browse_with_friend_button=Besøg siden sammen med en ven
 panel_disconnect_button=Afbryd forbindelse
 
-## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
+## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Klik på Hello-knappen for at bruge nettet sammen med en ven.
+first_time_experience_subheading_button_above=Klik på knappen ovenfor for at surfe på internettet sammen med en ven.
 
-## LOCALIZATION_NOTE(first_time_experience_content): Message describing
+## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Brug det til at planlægge, arbejde og grine sammen.
+first_time_experience_content2=Brug den til at få tingene gjort: planlæg sammen, le sammen, arbejd sammen.
 first_time_experience_button_label2=Se, hvordan det fungerer
 
+## First Time Experience Slides
+fte_slide_1_title=Surf på internettet sammen med en ven
+## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
+## will be replaced by the short name 2.
+fte_slide_1_copy=Med {{clientShortname2}} kan du tage hurtigere beslutninger i realtid, uanset om du planlægger en rejse eller indkøb af en gave.
+fte_slide_2_title=Surf sammen
+fte_slide_2_copy=Brug den indbyggede tekst- eller video-chat til at dele ideér, sammenligne muligheder og blive enige.
+fte_slide_3_title=Invitér en ven ved at sende vedkommende et link
+## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_3_copy={{clientSuperShortname}} virker med de fleste computer-browsere. Det er ikke nødvendigt med en konto og alle forbinder gratis.
+## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_4_title=Find ikonet {{clientSuperShortname}} for at komme i gang
+## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
+## will be replaced by the brand short name.
+fte_slide_4_copy=Når I har fundet en side, I vil diskutere, så klik på ikonet i {{brandShortname}} for at oprette et link. Send så linket til din ven.
+
 invite_header_text_bold=Inviter nogen til at besøge siden sammen med dig.
+invite_header_text_bold2=Invitèr en ven til at være med!
 invite_header_text3=Det kræver selskab at bruge Firefox Hello, så send et link til én af dine venner sådan at I kan bruge nettet sammen. 
+invite_header_text4=Del dette link, så I kan begynde at surfe på internettet sammen.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Kopier link
 invite_copied_link_button=Kopieret!
 invite_email_link_button=Mail link
 invite_facebook_button3=Facebook
 invite_your_link=Dit link:
 
-# Status text
-display_name_guest=Gæst
-
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
 session_expired_error_description=Sessionen udløb. Alle URL'er du tidligere har oprettet og delt vil ikke længere virke.
 could_not_authenticate=Kunne ikke godkendes
 password_changed_question=Har du ændret din adgangskode?
 try_again_later=Prøv igen senere
 could_not_connect=Kunne ikke forbinde til serveren
@@ -143,18 +162,17 @@ network_disconnected=Netværksforbindelsen blev pludselig afbrudt.
 connection_error_see_console_notification=Opkaldet mislykkedes. Se detaljer i konsollen.
 no_media_failure_message=Kamera eller mikrofon blev ikke fundet.
 ice_failure_message=Forbindelse mislykkedes. Din firewall blokerer muligvis opkald.
 
 ## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
 ## parts between {{..}} because these will be replaced with links with the labels
 ## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
 ## by the brand name.
-legal_text_and_links3=Ved at bruge {{clientShortname}} accepterer du {{terms_of_use}} \
-  og {{privacy_notice}}.
+legal_text_and_links3=Ved at bruge {{clientShortname}} accepterer du {{terms_of_use}} og {{privacy_notice}}.
 legal_text_tos=betingelserne for brug
 legal_text_privacy=privatlivspolitikken
 
 ## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
 ## These 2 strings are displayed before and after a 'Telefonica'
 ## logo.
 powered_by_beforeLogo=Powered by
 powered_by_afterLogo=
@@ -177,17 +195,19 @@ room_name_untitled_page=Unavngiven side
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=Vi ses senere! Du kan til enhver tid vende tilbage til denne delte session ved at klikke på Hello-panelet. 
 door_hanger_prompt_name=Vil du give den et navn, der er nemmere at huske? Nuværende navn:
 door_hanger_button=OK
 
 # Infobar strings
 
+infobar_screenshare_no_guest_message=Lige så snart din ven deltager, vil hun kunne se de faneblade, du klikker på.
 infobar_screenshare_browser_message2=Du deler dine faneblade. Når du klikker på et faneblad kan dine venner se det
+infobar_screenshare_browser_message3=Du deler nu dine faneblade. Din ven vil kunne se de faneblade, du klikker på.
 infobar_screenshare_stop_sharing_message=Du deler ikke dine faneblade længere
 infobar_button_restart_label2=Genstart deling
 infobar_button_restart_accesskey=e
 infobar_button_stop_label2=Stop med at dele
 infobar_button_stop_accesskey=S
 infobar_button_disconnect_label=Afbryd
 infobar_button_disconnect_accesskey=A
 
--- a/browser/extensions/loop/chrome/locale/de/loop.properties
+++ b/browser/extensions/loop/chrome/locale/de/loop.properties
@@ -5,17 +5,17 @@
 # Panel Strings
 
 clientSuperShortname=Hello
 
 ## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
 ## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
 ## use "..." if \u2026 doesn't suit traditions in your locale.
 loopMenuItem_label=Gespräch beginnen…
-loopMenuItem_accesskey=t
+loopMenuItem_accesskey=G
 
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
 ## These are displayed together at the top of the panel when a user is needed to
 ## sign-in again. The emphesis is on the first line to get the user to sign-in again,
 ## and this is displayed in slightly larger font. Please arrange as necessary for
 ## your locale.
 ## {{clientShortname2}} will be replaced by the brand name for either string.
 sign_in_again_title_line_one=Bitte melden Sie sich erneut an,
@@ -38,17 +38,17 @@ first_time_experience_subheading_button_above=Klicken Sie auf die obige Schaltfläche, um mit einem Freund zusammen Webseiten anzusehen.
 first_time_experience_content=Verwenden Sie die Funktion, um gemeinsam zu planen, zu arbeiten und zu lachen.
 first_time_experience_content2=Nutzen Sie es für gemeinsame Aktivitäten: gemeinsam planen, gemeinsam lachen, gemeinsam arbeiten.
 first_time_experience_button_label2=Sehen Sie sich an, wie es funktioniert
 
 ## First Time Experience Slides
 fte_slide_1_title=Surfen Sie gemeinsam mit einem Freund
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
-fte_slide_1_copy=Egal ob Sie eine Reise planen oder ein Geschenk einkaufen, mit {{clientShortname2}} können Sie schnellere Entscheidungen in Echtzeit treffen.
+fte_slide_1_copy=Egal, ob Sie eine Reise planen oder ein Geschenk einkaufen, mit {{clientShortname2}} können Sie schnellere Entscheidungen in Echtzeit treffen.
 fte_slide_2_title=Rufen Sie die gleiche Seite auf
 fte_slide_2_copy=Verwenden Sie den integrierten Text- oder Videochat, um Ideen auszutauschen, Optionen zu vergleichen und zu einer Einigung zu kommen.
 fte_slide_3_title=Laden Sie einen Freund ein, indem Sie ihm einen Link senden
 ## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
 fte_slide_3_copy={{clientSuperShortname}} funktioniert mit den meisten Desktop-Browsern. Es sind keine Benutzerkonten notwendig und die Nutzung ist kostenlos.
 ## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
@@ -201,19 +201,19 @@ door_hanger_button=OK
 
 # Infobar strings
 
 infobar_screenshare_no_guest_message=Sobald Ihr Freund dabei ist, kann er jeden Tab sehen, den Sie anklicken.
 infobar_screenshare_browser_message2=Sie geben Ihre Tabs weiter. Jeder von Ihnen angeklickte Tab wird von Ihren Freunden gesehen.
 infobar_screenshare_browser_message3=Sie geben jetzt Ihre Tabs weiter. Ihr Freund kann alle Tabs sehen, die Sie anklicken.
 infobar_screenshare_stop_sharing_message=Sie geben Ihre Tabs nicht mehr weiter.
 infobar_button_restart_label2=Wieder weitergeben
-infobar_button_restart_accesskey=s
+infobar_button_restart_accesskey=W
 infobar_button_stop_label2=Nicht mehr weitergeben
-infobar_button_stop_accesskey=b
+infobar_button_stop_accesskey=N
 infobar_button_disconnect_label=Verbindung trennen
 infobar_button_disconnect_accesskey=t
 
 # E10s not supported strings
 
 e10s_not_supported_button_label=Neues Fenster öffnen
 e10s_not_supported_subheading={{brandShortname}} funktioniert nicht in der Mehr-Prozess-Ausführung.
 # This Source Code Form is subject to the terms of the Mozilla Public
--- a/browser/extensions/loop/chrome/locale/es-ES/loop.properties
+++ b/browser/extensions/loop/chrome/locale/es-ES/loop.properties
@@ -1,128 +1,128 @@
 # 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/.
 
 # Panel Strings
 
-clientSuperShortname=Hola
+clientSuperShortname=Hello
 
 ## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
 ## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
 ## use "..." if \u2026 doesn't suit traditions in your locale.
 loopMenuItem_label=Iniciar una conversación…
-loopMenuItem_accesskey=t
+loopMenuItem_accesskey=I
 
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
 ## These are displayed together at the top of the panel when a user is needed to
 ## sign-in again. The emphesis is on the first line to get the user to sign-in again,
 ## and this is displayed in slightly larger font. Please arrange as necessary for
 ## your locale.
 ## {{clientShortname2}} will be replaced by the brand name for either string.
 sign_in_again_title_line_one=Vuelve a iniciar sesión
 sign_in_again_title_line_two2=para continuar utilizando {{clientShortname2}}
 sign_in_again_button=Iniciar sesión
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
-sign_in_again_use_as_guest_button2=Utiliza {{clientSuperShortname}} como Invitado
+sign_in_again_use_as_guest_button2=Utiliza {{clientSuperShortname}} como invitado
 
 panel_browse_with_friend_button=Navega por la página con un amigo
 panel_disconnect_button=Desconectar
 
 ## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Haz clic en el botón de Hello para navegar por la Web con un amigo.
-first_time_experience_subheading_button_above=Pulse en el botón de arriba para navegar por páginas web con un amigo.
+first_time_experience_subheading_button_above=Haz clic en el botón de arriba para navegar por páginas web con un amigo.
 
 ## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
-first_time_experience_content=Utilízalo para hacer planes, trabajar y reír juntos.
-first_time_experience_content2=Úselo para llevar a cabo tareas; planear juntos, divertirse juntos, trabajar juntos.
+first_time_experience_content=Úsalo para hacer planes juntos, divertirse juntos, trabajar juntos.
+first_time_experience_content2=Úsalo para llevar a cabo tareas; hacer planes juntos, divertirse juntos, trabajar juntos.
 first_time_experience_button_label2=Aprende cómo funciona
 
 ## First Time Experience Slides
-fte_slide_1_title=Navegue por páginas web con un amigo
+fte_slide_1_title=Navega por páginas web con un amigo
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
-fte_slide_1_copy=Tanto si está planeando un viaje o buscando un regalo para comprarlo, {{clientShortname2}} le permite tomar decisiones más rápidas en tiempo real.
-fte_slide_2_title=Reúnanse en la misma página
-fte_slide_2_copy=Use los chats incluidos de texto o vídeo para compartir ideas, comparar opciones o llegar a acuerdos.
-fte_slide_3_title=Invite a un amigo enviando un enlace
+fte_slide_1_copy=Tanto si estás planeando un viaje o buscando un regalo para comprarlo, {{clientShortname2}} te permite tomar decisiones más rápidas en tiempo real.
+fte_slide_2_title=Reuníos en la misma página
+fte_slide_2_copy=Usa los chats incluidos de texto o vídeo para compartir ideas, comparar opciones o llegar a acuerdos.
+fte_slide_3_title=Invita a un amigo enviando un enlace
 ## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
 fte_slide_3_copy={{clientSuperShortnae}} funciona con la mayoría de los navegadores de escritorio. No es necesario tener cuenta y todo el mundo se conecta gratuitamente.
 ## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
-fte_slide_4_title=Encuentre el icono de {{clientSuperShortname}} para comenzar
+fte_slide_4_title=Busca el icono de {{clientSuperShortname}} para comenzar
 ## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
 ## will be replaced by the brand short name.
-fte_slide_4_copy=Cuando encuentre una página sobre la que conversar, pulse el icono en {{brandShortname}} para crear un enlace. ¡Luego envíela a su amigo como mejor le parezca!
+fte_slide_4_copy=Cuando encuentres una página sobre la que conversar, pulsa el icono en {{brandShortname}} para crear un enlace. ¡Luego envíala a tu amigo como mejor te parezca!
 
 invite_header_text_bold=¡Invita a alguien a navegar por la página contigo!
-invite_header_text_bold2=¡Invite a un amigo a unirse a usted!
+invite_header_text_bold2=¡Invita a un amigo a unirse a ti!
 invite_header_text3=Se necesitan dos personas para utilizar Firefox Hello. ¡Envíale el enlace a un amigo y navegad juntos!
-invite_header_text4=Comparta este enlace para que puedan comenzar a navegar juntos por la web.
+invite_header_text4=Comparte este enlace para que podáis comenzar a navegar juntos por la web.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Copiar enlace
 invite_copied_link_button=¡Copiado!
 invite_email_link_button=Enviar enlace
 invite_facebook_button3=Facebook
 invite_your_link=Tu enlace:
 
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
-session_expired_error_description=Ha expirado la sesión. Ya no te funcionarán las URLs que hayas creado o compartido antes.
+session_expired_error_description=Ha caducado la sesión. Ya no te funcionarán las URL que hayas creado o compartido antes.
 could_not_authenticate=No se pudo autenticar
 password_changed_question=¿Has cambiado la contraseña?
 try_again_later=Por favor, vuelve a intentarlo luego
 could_not_connect=No se pudo conectar al servidor
-check_internet_connection=Por favor, comprueba tu conexión a internet
-login_expired=Tu sesión ha expirado
+check_internet_connection=Por favor, comprueba tu conexión a Internet
+login_expired=Tu sesión ha caducado
 service_not_available=El servicio no está disponible en este momento
 problem_accessing_account=Hubo un problema al acceder a tu cuenta
 
 ## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
 ## the appropriate action.
 retry_button=Reintentar
 
 share_email_subject7=Tu invitación a navegar juntos por la Web
 ## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
 ## part between {{..}} and leave the \n\n part alone
 share_email_body7=Un amigo te está esperando en Firefox Hello. Haz clic en el enlace para empezar a navegar juntos por la Web: {{callUrl}}
 ## LOCALIZATION NOTE (share_email_body_context3): In this item, don't translate
 ## the part between {{..}} and leave the \n\n part alone.
 share_email_body_context3=Un amigo te está esperando en Firefox Hello. Haz clic en el enlace para conectarte y navegar juntos por {{title}}: {{callUrl}}
 ## LOCALIZATION NOTE (share_email_footer2): Common footer content for both email types
-share_email_footer2=\n\n____________\n Firefox Hello te permite navegar por la Web con tus amigos. Utilízadlo cuando queráis para hacer planes, trabajar o reír juntos. Obtén más información en http://www.firefox.com/hello
+share_email_footer2=\n\n____________\n Firefox Hello te permite navegar por la Web con tus amigos. Utilizadlo cuando queráis hacer planes, trabajar o reír juntos. Obtén más información en http://www.firefox.com/hello
 ## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
 ## between {{..}}. Please keep the text below 117 characters to make sure it fits
 ## in a tweet.
-share_tweet=¡Únete para que iniciemos una conversación con video en {{clientShortname2}}!
+share_tweet=¡Únete para que iniciemos una conversación con vídeo en {{clientShortname2}}!
 
-share_add_service_button=Agregar un servicio
+share_add_service_button=Añadir un servicio
 
 ## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
 ## These menu items are displayed from a panel's context menu for a conversation.
 copy_link_menuitem=Copiar enlace
 email_link_menuitem=Enviar enlace
 delete_conversation_menuitem2=Eliminar
 
 panel_footer_signin_or_signup_link=Inicia sesión o regístrate
 
 settings_menu_item_account=Cuenta
 settings_menu_item_settings=Configuración
 settings_menu_item_signout=Cerrar sesión
 settings_menu_item_signin=Iniciar sesión
 settings_menu_item_turnnotificationson=Activar notificaciones
 settings_menu_item_turnnotificationsoff=Desactivar notificaciones
-settings_menu_item_feedback=Enviar compentario
+settings_menu_item_feedback=Enviar comentario
 settings_menu_button_tooltip=Configuración
 
 
 # Conversation Window Strings
 
 initiate_call_button_label2=¿Listo para iniciar una conversación?
 incoming_call_title2=Solicitud de conversación
 incoming_call_block_button=Bloquear
@@ -134,17 +134,17 @@ hangup_button_caption2=Salir
 ## when calling a contact. Don't translate the part between {{..}} because
 ## this will be replaced by the contact's name.
 call_with_contact_title=Conversación con {{contactName}}
 
 # Outgoing conversation
 
 outgoing_call_title=¿Iniciar una conversación?
 initiate_audio_video_call_button2=Iniciar
-initiate_audio_video_call_tooltip2=Iniciar una conversación con video
+initiate_audio_video_call_tooltip2=Iniciar una conversación con vídeo
 initiate_audio_call_button2=Conversación de voz
 
 peer_ended_conversation2=La persona a la que llamaste ha finalizado la conversación.
 restart_call=Volver a unirse
 
 ## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
 ## contact is offline.
 contact_offline_title=Esta persona no está conectada
@@ -152,21 +152,21 @@ contact_offline_title=Esta persona no está conectada
 ## when the call didn't go through.
 call_timeout_notification_text=No se pudo realizar la llamada.
 
 ## LOCALIZATION NOTE (cancel_button):
 ## This button is displayed when a call has failed.
 cancel_button=Cancelar
 rejoin_button=Volver a unirse a la conversación
 
-cannot_start_call_session_not_ready=No puedes hacer una llamara, la sesión no está lista.
+cannot_start_call_session_not_ready=No puedes hacer una llamada, la sesión no está lista.
 network_disconnected=La conexión de red se ha interrumpido de repente.
 connection_error_see_console_notification=Llamada fallida; comprueba la consola para más detalles.
-no_media_failure_message=No se encuentran cámara ni micrófono.
-ice_failure_message=Ha fallado la conexión. Puede que el firewall esté bloqueando las llamadas.
+no_media_failure_message=No se encuentra cámara ni micrófono.
+ice_failure_message=Ha fallado la conexión. Puede que el cortafuegos esté bloqueando las llamadas.
 
 ## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
 ## parts between {{..}} because these will be replaced with links with the labels
 ## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
 ## by the brand name.
 legal_text_and_links3=Si utilizas {{clientShortname}}, aceptas los {{terms_of_use}} y el {{privacy_notice}}.
 legal_text_tos=Términos de uso
 legal_text_privacy=Aviso de privacidad
@@ -174,40 +174,40 @@ legal_text_privacy=Aviso de privacidad
 ## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
 ## These 2 strings are displayed before and after a 'Telefonica'
 ## logo.
 powered_by_beforeLogo=Proporcionado por
 powered_by_afterLogo=
 
 ## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
 ## a signed-in to signed-in user call.
-feedback_rejoin_button=Volver a unirse
+feedback_rejoin_button=Volver a unirte
 ## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
 ## an abusive user.
 feedback_report_user_button=Denunciar usuario
 feedback_window_heading=¿Qué tal fue la conversación?
 feedback_request_button=Dejar comentario
 
-tour_label=Tour
+tour_label=Visita guiada
 
 rooms_list_recently_browsed2=Visitadas recientemente
 rooms_list_currently_browsing2=Visitando actualmente
 rooms_signout_alert=Se cerrarán las conversaciones abiertas
 room_name_untitled_page=Página sin título
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=¡Hasta luego! Puedes volver a esta sesión compartida cuando quieras en el panel Hello.
 door_hanger_prompt_name=¿Te gustaría darle un nombre más fácil de recordar? Nombre actual:
 door_hanger_button=Aceptar
 
 # Infobar strings
 
-infobar_screenshare_no_guest_message=Tan pronto se una su amigo, podrá ver cualquier pestaña en la que pulse usted.
-infobar_screenshare_browser_message2=Estás compartiendo tus pestañas. Si haces clic en una de ellas, tus amigos también la verán
-infobar_screenshare_browser_message3=Ahora está compartiendo sus pestañas. Su amigo verá cualquier pestaña en la que pulse usted.
+infobar_screenshare_no_guest_message=Tan pronto se una tu amigo, podrá ver cualquier pestaña en la que pulses tú.
+infobar_screenshare_browser_message2=Estás compartiendo tus pestañas. Cualquier pestaña en la que pulses puede ser vista por tus amigos
+infobar_screenshare_browser_message3=Ahora estás compartiendo tus pestañas. Tu amigo verá cualquier pestaña en la que pulses.
 infobar_screenshare_stop_sharing_message=Ya no compartes tus pestañas
 infobar_button_restart_label2=Volver a compartir
 infobar_button_restart_accesskey=e
 infobar_button_stop_label2=Dejar de compartir
 infobar_button_stop_accesskey=S
 infobar_button_disconnect_label=Desconectar
 infobar_button_disconnect_accesskey=D
 
@@ -232,18 +232,18 @@ conversation_has_ended=La conversación ha terminado.
 generic_failure_message=Estamos teniendo problemas técnicos...
 
 generic_failure_no_reason2=¿Quieres volver a intentarlo?
 
 help_label=Ayuda
 
 mute_local_audio_button_title=Silenciar sonido
 unmute_local_audio_button_title=Activar sonido
-mute_local_video_button_title2=Desactivar video
-unmute_local_video_button_title2=Activar video
+mute_local_video_button_title2=Desactivar vídeo
+unmute_local_video_button_title2=Activar vídeo
 
 ## LOCALIZATION NOTE (retry_call_button):
 ## This button is displayed when a call has failed.
 retry_call_button=Reintentar
 
 rooms_leave_button_label=Abandonar
 
 rooms_panel_title=Elige una conversación o inicia una nueva
--- a/browser/extensions/loop/chrome/locale/es-MX/loop.properties
+++ b/browser/extensions/loop/chrome/locale/es-MX/loop.properties
@@ -13,92 +13,92 @@ loopMenuItem_label=Iniciar una conversación…
 loopMenuItem_accesskey=t
 
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
 ## These are displayed together at the top of the panel when a user is needed to
 ## sign-in again. The emphesis is on the first line to get the user to sign-in again,
 ## and this is displayed in slightly larger font. Please arrange as necessary for
 ## your locale.
 ## {{clientShortname2}} will be replaced by the brand name for either string.
-sign_in_again_title_line_one=Por favor, inicia sesión de nuevo
+sign_in_again_title_line_one=Vuelve a iniciar sesión
 sign_in_again_title_line_two2=para continuar usando {{clientShortname2}}
 sign_in_again_button=Iniciar sesión
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
-sign_in_again_use_as_guest_button2=Usar {{clientSuperShortname}} como un Invitado
+sign_in_again_use_as_guest_button2=Usar {{clientSuperShortname}} como invitado
 
-panel_browse_with_friend_button=Navegar esta página con un amigo
+panel_browse_with_friend_button=Explorar esta página con un amigo
 panel_disconnect_button=Desconectar
 
 ## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
-first_time_experience_subheading2=Haz clic en el botón de Hello para para navegar las páginas Web con un amigo.
-first_time_experience_subheading_button_above=Haz clic en el botón de arriba para navegar la Web con un amigo.
+first_time_experience_subheading2=Haz clic en el botón Hello para explorar las páginas Web con un amigo.
+first_time_experience_subheading_button_above=Haz clic en el botón de arriba para explorar la Web con un amigo.
 
 ## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
-first_time_experience_content=Úsalo para planear cosas juntos, trabajar juntos, reír juntos. 
-first_time_experience_content2=Úsalo para hacer las cosas bien: planear algo juntos, reír juntos, trabajar juntos.
-first_time_experience_button_label2=Ver como trabaja
+first_time_experience_content=Úsalo para hacer planes juntos, trabajar juntos, reír juntos.
+first_time_experience_content2=Úsalo para hacer cosas: planear algo juntos, reír juntos, trabajar juntos.
+first_time_experience_button_label2=Ver cómo funciona
 
 ## First Time Experience Slides
-fte_slide_1_title=Navega páginas Web con un amigo
+fte_slide_1_title=Explora páginas Web con un amigo
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
-fte_slide_1_copy=Ya sea que estés planeando un viaje o comprar un regalo, {{clientShortname2}} te permite tomar decisiones más rápido en tiempo real.
+fte_slide_1_copy=Ya sea que estés planeando un viaje o comprando un regalo, {{clientShortname2}} te permite tomar decisiones más rápido en tiempo real.
 fte_slide_2_title=Estar en la misma página
-fte_slide_2_copy=Usa el texto incorporado o el videochat para compartir ideas, comparar opciones y llegar a acuerdos.
+fte_slide_2_copy=Usa los chats incluidos de texto o video para compartir ideas, comparar opciones y lograr acuerdos.
 fte_slide_3_title=Invita a un amigo enviándole un enlace
 ## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
-fte_slide_3_copy={{clientSuperShortname}} trabaja con la mayoría de los navegadores de escritorio. No se necesita cuentas y todos se pueden conectar gratis.
+fte_slide_3_copy={{clientSuperShortname}} funciona con la mayoría de los navegadores de escritorio. No es necesario tener cuenta y todos se pueden conectar gratis.
 ## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
-fte_slide_4_title=Encuentra el ícono de {{clientSuperShortname}} para comenzar
+fte_slide_4_title=Busca el icono de {{clientSuperShortname}} para comenzar
 ## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
 ## will be replaced by the brand short name.
-fte_slide_4_copy=Una vez que encuentres una página que quieras discutir, haz clic en el ícono de {{brandShortname}} para crear un enlace. ¡Entonces envíalo al amigo que quieras!
+fte_slide_4_copy=Una vez que encuentres una página que quieras discutir, haz clic en el icono de {{brandShortname}} para crear un enlace. ¡Luego envíaselo a tu amigo como mejor te parezca!
 
-invite_header_text_bold=Invita a alguien para navegar esta página contigo.
-invite_header_text_bold2=¡Invita a un amigo a unirse!
-invite_header_text3=Es muy fácil usar Firefox Hello, ¡simplemente envía a tu amigo un enlace para navegar la Web contigo!
-invite_header_text4=Compartir este enlace para que así puedan iniciar a navegar la Web juntos.
+invite_header_text_bold=¡Invita a alguien para explorar esta página contigo!
+invite_header_text_bold2=¡Invita a un amigo a unirse a ti!
+invite_header_text3=Hacen falta dos para usar Firefox Hello, ¡así que envíale un enlace a tu amigo para navegar la Web juntos!
+invite_header_text4=Comparte este enlace para que puedan iniciar a explorar juntos la Web.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Copiar enlace
 invite_copied_link_button=¡Copiado!
 invite_email_link_button=Enviar enlace
 invite_facebook_button3=Facebook
 invite_your_link=Tu enlace:
 
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
-session_expired_error_description=La sesión ha expirado. Todas las URLs que creaste con anterioridad y compartiste no funcionarán más.
-could_not_authenticate=No se puede autenticar
+session_expired_error_description=La sesión ha expirado. Las URLs que creaste o compartiste antes ya no funcionarán.
+could_not_authenticate=No se pudo autenticar
 password_changed_question=¿Cambiaste tu contraseña?
 try_again_later=Por favor, inténtalo de nuevo más tarde
-could_not_connect=No se puede conectar al servidor
+could_not_connect=No se pudo conectar al servidor
 check_internet_connection=Por favor, revisa tu conexión a Internet
 login_expired=Te sesión ha expirado
 service_not_available=El servicio no está disponible por ahora
 problem_accessing_account=Hubo un problema accediendo a tu cuenta
 
 ## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
 ## the appropriate action.
 retry_button=Reintentar
 
-share_email_subject7=Tu invitación para navegar la Web juntos
+share_email_subject7=Tu invitación para explorar juntos la Web
 ## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
 ## part between {{..}} and leave the \n\n part alone
-share_email_body7=Un amigo está esperando por ti en Firefox Hello. Haz clic en el enlace para conectarte y navegar la Web juntos: {{callUrl}}
+share_email_body7=Un amigo está esperándote en Firefox Hello. Haz clic en el enlace para conectarte y explorar juntos la Web: {{callUrl}}
 ## LOCALIZATION NOTE (share_email_body_context3): In this item, don't translate
 ## the part between {{..}} and leave the \n\n part alone.
-share_email_body_context3=Un amigo está esperando por ti en Firefox Hello. Haz clic en el enlace para conectarte y navegar {{title}} juntos: {{callUrl}}
+share_email_body_context3=Un amigo está esperándote en Firefox Hello. Haz clic en el enlace para conectarte y explorar {{title}} juntos: {{callUrl}}
 ## LOCALIZATION NOTE (share_email_footer2): Common footer content for both email types
 share_email_footer2=\n\n___\nFirefox Hello te permite navegar por la Web con tus amigos. Puedes usarlo cada vez que quieras hacer algo: planificar, trabajar juntos, reír juntos. Más información en http://www.firefox.com/hello
 ## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
 ## between {{..}}. Please keep the text below 117 characters to make sure it fits
 ## in a tweet.
 share_tweet=¡Platiquemos por video en {{clientShortname2}}!
 
 share_add_service_button=Agregar un servicio
@@ -202,24 +202,24 @@ door_hanger_button=Aceptar
 
 infobar_screenshare_no_guest_message=Tan pronto como tus amigos se vayan uniendo, serán capaces de ver cualquier pestaña en la que hagas clic.
 infobar_screenshare_browser_message2=Estás compartiendo tus pestañas. Cualquier pestaña en la que des clic puede ser vista por tus amigos
 infobar_screenshare_browser_message3=Estás compartiendo tus pestañas. Tu amigo podrá ver cualquier pestaña en la que hagas clic.
 infobar_screenshare_stop_sharing_message=Ya no estás compartiendo tus pestañas
 infobar_button_restart_label2=Volver a compartir
 infobar_button_restart_accesskey=e
 infobar_button_stop_label2=Dejar de compartir
-infobar_button_stop_accesskey=S
+infobar_button_stop_accesskey=c
 infobar_button_disconnect_label=Desconectar
 infobar_button_disconnect_accesskey=D
 
 # E10s not supported strings
 
-e10s_not_supported_button_label=Ejecutar una nueva ventana
-e10s_not_supported_subheading={{brandShortname}} no funciona en una ventana multiproceso.
+e10s_not_supported_button_label=Abrir una nueva ventana
+e10s_not_supported_subheading={{brandShortname}} no funciona en ventanas multiproceso.
 # 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/.
 
 ## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
 
 # Text chat strings
 chat_textbox_placeholder=Escribe aquí...
--- a/browser/extensions/loop/chrome/locale/et/loop.properties
+++ b/browser/extensions/loop/chrome/locale/et/loop.properties
@@ -14,48 +14,67 @@ loopMenuItem_accesskey=t
 
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
 ## These are displayed together at the top of the panel when a user is needed to
 ## sign-in again. The emphesis is on the first line to get the user to sign-in again,
 ## and this is displayed in slightly larger font. Please arrange as necessary for
 ## your locale.
 ## {{clientShortname2}} will be replaced by the brand name for either string.
 sign_in_again_title_line_one=Palun logi uuesti sisse,
-sign_in_again_title_line_two2=et jätkata {{clientShortname2}}i kasutamist
+sign_in_again_title_line_two2=et jätkata {{clientShortname2}} kasutamist
 sign_in_again_button=Logi sisse
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
-sign_in_again_use_as_guest_button2=Kasuta {{clientSuperShortname}}i külalisena
+sign_in_again_use_as_guest_button2=Kasuta {{clientSuperShortname}}t külalisena
 
 panel_browse_with_friend_button=Lehitse seda lehte koos sõbraga
 panel_disconnect_button=Lõpeta ühendus
 
-## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
+## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Veebilehtede sirvimiseks koos sõbraga klõpsa Hello nupul.
+first_time_experience_subheading_button_above=Veebilehtede sirvimiseks koos sõbraga klõpsa ülal oleval nupul.
 
-## LOCALIZATION_NOTE(first_time_experience_content): Message describing
+## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Kasuta seda ühiseks plaanimiseks, töötamiseks ja naermiseks.
+first_time_experience_content2=Kasuta seda asjade ära tegemiseks: ühiseks plaanimiseks, töötamiseks ja naermiseks.
 first_time_experience_button_label2=Vaata, kuidas see töötab
 
+## First Time Experience Slides
+fte_slide_1_title=Lehitse veebilehti koos sõbraga
+## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
+## will be replaced by the short name 2.
+fte_slide_1_copy=Kui plaanid reisi või otsid kingitust, siis võimaldab {{clientShortname2}} sul kiiremini otsustada.
+fte_slide_2_title=Olge samal veebilehel.
+fte_slide_2_copy=Kasuta sisseehitatud teksti- või videovestlust ideede jagamiseks, valikute võrdlemiseks ning ühisele otsusele jõudmiseks.
+fte_slide_3_title=Kutsu sõber, saates talle lingi.
+## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_3_copy={{clientSuperShortname}} töötab enamikus töölaua veebilehitsejates. Kontot pole vaja ning ühendumine on tasuta.
+## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_4_title=Leia {{clientSuperShortname}} ikoon ning tee algust.
+## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
+## will be replaced by the brand short name.
+fte_slide_4_copy=Kui oled leidnud lehe, mida soovid arutada, siis klõpsa ikoonil {{brandShortname}}s ning loo link. Seejärel saada see sõbrale, kuidas iganes soovid!
+
 invite_header_text_bold=Kutsu keegi teine seda lehte vaatama!
+invite_header_text_bold2=Kutsu sõber endaga ühinema!
 invite_header_text3=Firefox Hello kasutamiseks on vaja kahte kasutajat. Saada sõbrale link!
+invite_header_text4=Jaga seda linki, et saaksite koos veebilehitsemist alustada.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Kopeeri link
 invite_copied_link_button=Kopeeritud!
 invite_email_link_button=Saada link e-postiga
 invite_facebook_button3=Facebook
 invite_your_link=Sinu link:
 
-# Status text
-display_name_guest=Külaline
-
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
 session_expired_error_description=Seanss aegus. Ükski varem loodud ja jagatud URLidest ei tööta enam.
 could_not_authenticate=Autentimine polnud võimalik
 password_changed_question=Kas muutsid oma parooli?
 try_again_later=Palun proovi hiljem uuesti
 could_not_connect=Serveriga polnud võimalik ühenduda
@@ -177,17 +196,19 @@ room_name_untitled_page=Nimeta leht
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=Näeme hiljem! Saad sellesse jagatud sessiooni naasta mis tahes ajal Hello paneeli kaudu.
 door_hanger_prompt_name=Kas soovid sellele nime anda, et oleks kergem meeles pidada? Praegune nimi:
 door_hanger_button=Olgu
 
 # Infobar strings
 
+infobar_screenshare_no_guest_message=Niipea kui su sõber liitub, näeb ta sinu valitud kaarti.
 infobar_screenshare_browser_message2=Jagad enda kaarte. Sinu sõbrad näevad kaarti, mille oled valinud.
+infobar_screenshare_browser_message3=Jagad nüüd enda kaarte. Sinu sõber näeb kaarti, mille oled valinud.
 infobar_screenshare_stop_sharing_message=Sa ei jaga enam kaarte.
 infobar_button_restart_label2=Alusta jagamist uuesti
 infobar_button_restart_accesskey=s
 infobar_button_stop_label2=Lõpeta jagamine
 infobar_button_stop_accesskey=L
 infobar_button_disconnect_label=Lõpeta ühendus
 infobar_button_disconnect_accesskey=L
 
@@ -235,8 +256,10 @@ rooms_room_join_label=Liitu vestlusega
 rooms_room_joined_owner_connected_label2=Sinu sõbraga on ühendus olemas ja ta saab sinu kaarte näha.
 rooms_room_joined_owner_not_connected_label=Sinu sõber ootab, et sinuga {{roomURLHostname}} sirvida.
 
 self_view_hidden_message=Sinu pilti edastatakse, kuid see on praegu peidetud. Pildi kuvamiseks muuda akna suurust.
 
 ## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
 ## as this will be replaced by clientShortname2.
 tos_failure_message={{clientShortname}} pole sinu riigis saadaval.
+
+display_name_guest=Külaline
--- a/browser/extensions/loop/chrome/locale/fa/loop.properties
+++ b/browser/extensions/loop/chrome/locale/fa/loop.properties
@@ -23,39 +23,58 @@ sign_in_again_title_line_two2=جهت ادامه استفاده از {{clientShortname2}}
 sign_in_again_button=ورود
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
 sign_in_again_use_as_guest_button2=استفاده از {{clientSuperShortname}} به عنوان مهمان
 
 panel_browse_with_friend_button=این صفحه را با همراهی یک دوست مرور کنید
 panel_disconnect_button=قطع ارتباط
 
-## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
+## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=بر روی دکمه Hello کلیک کنید تا با همراهی یک دوست صفحه را مرور کنید.
+first_time_experience_subheading_button_above=بر روی دکمه بالا کلی کنید تا وب را با یک دوست مرور کنید.
 
-## LOCALIZATION_NOTE(first_time_experience_content): Message describing
+## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=برای برنامه‌ریزی گروهی، کار کردن گروهی و خندیدن با هم استفاده کنید.
+first_time_experience_content2=برای به سرانجام رساندن کارها از آن استفاده کنید: با هم برنامه‌ریزی کنید، با هم بخندید، با هم کار کنید.
 first_time_experience_button_label2=نحوه کار را یاد بگیرید
 
+## First Time Experience Slides
+fte_slide_1_title=صفحات وب را با یک دوست مرور کنید
+## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
+## will be replaced by the short name 2.
+fte_slide_1_copy=اگر در حال برنامه‌ریزی برای یک سفر هستید یا قصد خرید یک هدیه را دارید، {{clientShortname2}} به شما اجازه می‌دهد تا زمانی کوتاه تصمیمات سریع‌تر بگیرید.
+fte_slide_2_title=با هم هماهنگ باشید
+fte_slide_2_copy=از سیستم گپ ویدئویی یا متنی برای به اشتراک گذاشتن ایده‌ها، مقایسه گزینه‌ها و رسیدن به تفاهم، استفاده کنید.
+fte_slide_3_title=یک دوست را با ارسال یک پیوند دعوت کنید
+## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_3_copy={{clientSuperShortname}} با اکثر مرورگرهای رومیزی کار می‌کند. هیچ حسابی لازم نیست و هر کسی می‌تواند به رایگان متصل شود.
+## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_4_title=شمایل {{clientSuperShortname}} را برای شروع پیدا کنید
+## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
+## will be replaced by the brand short name.
+fte_slide_4_copy=زمانی که صفحه‌ای پیدا کردید تا موردش بحث کنید، بر روی شمایل {{brandShortname}} کلیک کنید تا یک پیوند بسازید. سپس آن را هر طور که دوست دارید برای دوست خود ارسال کنید!
+
 invite_header_text_bold=یک نفر را برای همراهی در مرور اینه صفحه دعوت کنید!
+invite_header_text_bold2=از یک دوست برای پیوستن به شما دعوت کنید!
 invite_header_text3=برای کار کردن با Firefox Hello باید دو نفر باشید، پس برای دوست خود یک پیوند بفرستید تا وب را با هم مرور کنید!
+invite_header_text4=این پیوند را برای شروع مرور وب با هم به اشتراک بگذارید.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=رونوشت از پیوند
 invite_copied_link_button=رونوشت شد!
 invite_email_link_button=پست پیوند
 invite_facebook_button3=فیس‌بوک
 invite_your_link=پیوند شما:
 
-# Status text
-display_name_guest=مهمان
-
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
 session_expired_error_description=نشست منقضی شد. تمام آدرس‌هایی که شما قبلا ساخته و به اشتراک گذاشته‌اید دیگر کار نخواهند کرد.
 could_not_authenticate=تایید هویت ممکن نبود
 password_changed_question=آیا شما گذرواژه خود را تغییر داده‌اید؟
 try_again_later=لطفا بعدا دوباره تلاش کنید
 could_not_connect=امکان اتصال به کارگزار نبود
@@ -176,17 +195,19 @@ room_name_untitled_page=صفحه بی‌نام
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=بعدا می‌بنیمت! شما می‌توانید از طریق قسمت Hello در هر زمانی به این نشست اشتراک گذاشته شده بازگردید.
 door_hanger_prompt_name=آیا مایلید که نامی انتخاب کنید تا بعدا راحت‌تر آن را پیدا کنید؟ نام فعلی:
 door_hanger_button=تایید
 
 # Infobar strings
 
+infobar_screenshare_no_guest_message=هر موقع دوستان شما متصل شوند، آنها قادر خواهند بود هر زبانه‌ای که روی‌اش کلیک می‌کنید را ببینند.
 infobar_screenshare_browser_message2=شما در حال اشتراک‌گذاری زبانه‌های خود هستید. هر زبانه‌ای که بر روی‌اش کلیک کنید، توسط دوستانتان دید میشود
+infobar_screenshare_browser_message3=شما هم‌اکنون در حال اشتراک‌گذاری زبانه‌های خود هستید. دوستان شما هر زبانه‌ای که روی‌اش کلیک کنید را خواهند دید.
 infobar_screenshare_stop_sharing_message=شما دیگر در حال اشتراک‌گذاری زبانه‌های خود نیستید
 infobar_button_restart_label2=راه‌اندازی مجدد اشتراک‌گذاری
 infobar_button_restart_accesskey=e
 infobar_button_stop_label2=توقف اشتراک‌گذاری
 infobar_button_stop_accesskey=S
 infobar_button_disconnect_label=قطع ارتباط
 infobar_button_disconnect_accesskey=D
 
@@ -234,8 +255,10 @@ rooms_room_join_label=پیوستن به گفت‌وگو
 rooms_room_joined_owner_connected_label2=هم‌اکنون دوست شما متصل شده است و می‌تواند زبانه‌های شما را ببیند.
 rooms_room_joined_owner_not_connected_label=دوست شما منتظر است تا {{roomURLHostname}} را همراه با شما ببیند.
 
 self_view_hidden_message=نمای فردی پنهان شده است ولی ارسال می‌شود؛ برای نمایش اندازه پنجره را تغییر دهید
 
 ## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
 ## as this will be replaced by clientShortname2.
 tos_failure_message={{clientShortname}} در کشور شما در دسترس نیست.
+
+display_name_guest=مهمان
--- a/browser/extensions/loop/chrome/locale/fr/loop.properties
+++ b/browser/extensions/loop/chrome/locale/fr/loop.properties
@@ -35,17 +35,17 @@ first_time_experience_subheading_button_
 
 ## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Utilisez-le pour vous organiser, travailler et rire ensemble.
 first_time_experience_content2=Utilisez vous pour réaliser vos projets : vous organiser, travailler et rire ensemble.
 first_time_experience_button_label2=Principe de fonctionnement
 
 ## First Time Experience Slides
-fte_slide_1_title=Naviguer sur le Web avec une autre personne
+fte_slide_1_title=Naviguez sur le Web avec une autre personne
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
 fte_slide_1_copy=Que ce soit pour planifier un voyage ou l'achat d'un cadeau, {{clientShortname2}} vous permet de prendre des décisions plus rapidement.
 fte_slide_2_title=Sur la même page au même moment
 fte_slide_2_copy=Utilisez la conversation texte ou vidéo pour partager vos idées, comparer vos choix et vous mettre d'accord.
 fte_slide_3_title=Invitez votre interlocuteur en lui envoyant un lien
 ## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
--- a/browser/extensions/loop/chrome/locale/gd/loop.properties
+++ b/browser/extensions/loop/chrome/locale/gd/loop.properties
@@ -23,39 +23,49 @@ sign_in_again_title_line_two2=a leantain
 sign_in_again_button=Clàraich a-steach
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
 sign_in_again_use_as_guest_button2=Cleachd {{clientSuperShortname}} mar aoigh
 
 panel_browse_with_friend_button=Rùraich an duilleag seo còmhla ri caraid
 panel_disconnect_button=Dì-cheangail
 
-## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
+## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Briog air putan Hello gus duilleagan-lìn a rùrachadh còmhla ri caraid.
+first_time_experience_subheading_button_above=Briog air a’ phutan gu h-àrd gus duilleagan-lìn a bhrabhsadh còmhla ri caraid.
 
-## LOCALIZATION_NOTE(first_time_experience_content): Message describing
+## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Bruidhinn ri chèile, dèan obair còmhla ri chèile, dèan gàire còmhla ri chèile.
+first_time_experience_content2=Dèan rudan còmhla: planadh, gàire, obair.
 first_time_experience_button_label2=Seo mar a dh’obraicheas e
 
+## First Time Experience Slides
+fte_slide_1_title=Brabhsaich duilleagan-lìn còmhla ri caraid
+## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
+## will be replaced by the short name 2.
+## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
+## will be replaced by the brand short name.
+
 invite_header_text_bold=Thoir cuireadh do chuideigin a rùrachadh na duilleige seo còmhla riut!
 invite_header_text3=Feumaidh tu co-dhiù dithis mus obraich Firefox Hello, nach cuir thu ceangal gu caraid a rùraicheas an lìon còmhla riut?
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Dèan lethbhreac dhen cheangal
 invite_copied_link_button=Lethbhreac air a dhèanamh!
 invite_email_link_button=Cuir an ceangal air a’ phost-d
 invite_facebook_button3=Facebook
 invite_your_link=An ceangal agad:
 
-# Status text
-display_name_guest=Aoigh
-
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
 session_expired_error_description=Dh’fhalbh an ùine air an t-seisean. Chan obraich gin dhe na URLaichean a chruthaich is a cho-roinn thu roimhe tuilleadh.
 could_not_authenticate=Cha b’ urrainn dhuinn dearbhadh a dhèanamh
 password_changed_question=Na dh’atharraich thu am facal-faire agad?
 try_again_later=Feuch ris a-rithist an ceann greis
 could_not_connect=Cha b’ urrainn dhuinn ceangal ris an fhrithealaiche
@@ -234,8 +244,10 @@ rooms_room_join_label=Gabh pàirt sa chòmhradh
 rooms_room_joined_owner_connected_label2=Tha do charaid ceangailte a-nis is chì iad na tabaichean agad.
 rooms_room_joined_owner_not_connected_label=Tha do charaid a’ feitheamh ort airson {{roomURLHostname}} a rùrachadh còmhla riut.
 
 self_view_hidden_message=Tha do dhealbh fhèin am falach ach 'ga chur fhathast; atharraich meud na h-uinneige gus fhaicinn
 
 ## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
 ## as this will be replaced by clientShortname2.
 tos_failure_message=Chan eil {{clientShortname}} ri fhaighinn ’nad dhùthaich.
+
+display_name_guest=Aoigh
--- a/browser/extensions/loop/chrome/locale/hu/loop.properties
+++ b/browser/extensions/loop/chrome/locale/hu/loop.properties
@@ -23,39 +23,58 @@ sign_in_again_title_line_two2=a {{clientShortname2}} használatának folytatásához
 sign_in_again_button=Bejelentkezés
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
 sign_in_again_use_as_guest_button2=A {{clientSuperShortname}} használata vendégként
 
 panel_browse_with_friend_button=Oldal böngészése ismerősével
 panel_disconnect_button=Bontás
 
-## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
+## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Kattintson a Hello gombra weboldalak böngészéséhez egy ismerősével.
+first_time_experience_subheading_button_above=Kattintson a fenti gombra weboldalak böngészéséhez egy ismerősével.
 
-## LOCALIZATION_NOTE(first_time_experience_content): Message describing
+## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Használja közös tervezésre, munkára, nevetésre.
+first_time_experience_content2=Használja feladatai elvégzéséhez: tervezzen, nevessen, dolgozzon közösen.
 first_time_experience_button_label2=Hogyan működik?
 
+## First Time Experience Slides
+fte_slide_1_title=Weboldalak böngészése ismerősével
+## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
+## will be replaced by the short name 2.
+fte_slide_1_copy=Ha utazást tervez vagy ajándékot vásárol, a {{clientShortname2}} segít gyorsabb döntést hozni valós időben.
+fte_slide_2_title=Kerüljenek egy lapra
+fte_slide_2_copy=Használja a beépített szöveges vagy videócsevegést ötletek megosztására, lehetőségek összehasonlítására és megegyezésre.
+fte_slide_3_title=Hívja meg ismerősét egy hivatkozás küldésével
+## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_3_copy=A {{clientSuperShortname}} a legtöbb asztali böngészővel működik. Felhasználói fiók nem szükséges, és mindenki ingyenesen csatlakozhat.
+## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_4_title=Keresse a {{clientSuperShortname}} ikont a kezdéshez
+## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
+## will be replaced by the brand short name.
+fte_slide_4_copy=Amint olyan oldalt talált, amit megosztana, hivatkozás létrehozásához kattintson a {{brandShortname}} ikonra. Majd küldje el ismerősének ahogy szeretné!
+
 invite_header_text_bold=Hívjon meg valakit az oldal közös böngészésére!
+invite_header_text_bold2=Hívja meg ismerősét, hogy csatlakozzon!
 invite_header_text3=A Firefox Hello használatához két ember kell: küldjön egy hivatkozást ismerősének, hogy közösen böngésszék a webet!
+invite_header_text4=Ossza meg ezt a hivatkozást, hogy együtt böngészhessék a webet.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Hivatkozás másolása
 invite_copied_link_button=Másolva!
 invite_email_link_button=Hivatkozás küldése
 invite_facebook_button3=Facebook
 invite_your_link=A hivatkozás:
 
-# Status text
-display_name_guest=Vendég
-
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
 session_expired_error_description=A munkamenet lejárt. A korábban létrehozott és megosztott egyik URL sem fog működni.
 could_not_authenticate=Nem sikerült a hitelesítés
 password_changed_question=Megváltoztatta a jelszavát?
 try_again_later=Próbálja újra később
 could_not_connect=Nem sikerült kapcsolódni a kiszolgálóhoz
@@ -176,17 +195,19 @@ room_name_untitled_page=Névtelen oldal
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=Viszontlátásra! Bármikor visszatérhet ehhez a megosztott munkamenethez a Hello panelen.
 door_hanger_prompt_name=Szeretne egy könnyebben megjegyezhető nevet adni neki? A jelenlegi név:
 door_hanger_button=OK
 
 # Infobar strings
 
+infobar_screenshare_no_guest_message=Amint ismerőse bejelentkezik, máris láthatja azokat a lapjait, amelyekre kattint.
 infobar_screenshare_browser_message2=Megosztja a lapjait. Ismerősei látni fogják bármely lap tartalmát, amelyre rákattint.
+infobar_screenshare_browser_message3=Mostmár megosztja a lapjait. Ismerőse láthatja bármely lap tartalmát, amelyre rákattint.
 infobar_screenshare_stop_sharing_message=Már nem osztja meg böngészőlapjait
 infobar_button_restart_label2=Megosztás újrakezdése
 infobar_button_restart_accesskey=r
 infobar_button_stop_label2=Megosztás leállítása
 infobar_button_stop_accesskey=L
 infobar_button_disconnect_label=Bontás
 infobar_button_disconnect_accesskey=o
 
@@ -234,8 +255,10 @@ rooms_room_join_label=Csatlakozás a csevegéshez
 rooms_room_joined_owner_connected_label2=Ismerőse csatlakozott, és láthatja az Ön böngészőlapjait.
 rooms_room_joined_owner_not_connected_label=Ismerőse arra vár, hogy közösen böngészhessék a következőt: {{roomURLHostname}}.
 
 self_view_hidden_message=A saját kamera képe elrejtve, de elküldésre kerül. Méretezze át az ablakot a megjelenítéshez
 
 ## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
 ## as this will be replaced by clientShortname2.
 tos_failure_message=A {{clientShortname}} nem érhető el ebben az országban.
+
+display_name_guest=Vendég
--- a/browser/extensions/loop/chrome/locale/id/loop.properties
+++ b/browser/extensions/loop/chrome/locale/id/loop.properties
@@ -1,15 +1,15 @@
 # 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/.
 
 # Panel Strings
 
-clientSuperShortname=Halo
+clientSuperShortname=Hello
 
 ## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
 ## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
 ## use "..." if \u2026 doesn't suit traditions in your locale.
 loopMenuItem_label=Mulai sebuah percakapan …
 loopMenuItem_accesskey=t
 
 ## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
@@ -21,47 +21,66 @@ loopMenuItem_accesskey=t
 sign_in_again_title_line_one=Silakan masuk kembali
 sign_in_again_title_line_two2=untuk memulai menggunakan {{clientShortname2}}
 sign_in_again_button=Masuk
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
 sign_in_again_use_as_guest_button2=Gunakan {{clientSuperShortname}} sebagai Tamu
 
 panel_browse_with_friend_button=Jelajahi laman ini bersama teman
-panel_stop_sharing_tabs_button=Berhenti membagikan tab Anda
+panel_disconnect_button=Terputus
 
-## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
+## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Klik tombol Hello untuk jelajahi laman Web bersama teman.
+first_time_experience_subheading_button_above=Klik pada tombol di atas untuk menjelajahi Web bersama teman.
 
-## LOCALIZATION_NOTE(first_time_experience_content): Message describing
+## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Gunakan ini untuk perencanaan bersama, pekerjaan bersama, dan tertawa bersama.
+first_time_experience_content2=Pergunakanlah untuk menyelesaikan sesuatu: merencanakan bersama, tertawa bersama, bekerja bersama.
 first_time_experience_button_label2=Lihat cara kerja
 
+## First Time Experience Slides
+fte_slide_1_title=Menjelajahi laman Web bersama teman
+## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
+## will be replaced by the short name 2.
+fte_slide_1_copy=Baik merencanakan perjalanan atau berbelanja hadiah, {{clientShortname2}} memungkinkan anda membuat keputusan lebih cepat dalam waktu singkat.
+fte_slide_2_title=Memiliki pemahaman yang sama
+fte_slide_2_copy=Menggunakan teks atau percakapan video yang tersedia untuk berbagi gagasan, emmbandingkan pilihan dan bermufakat.
+fte_slide_3_title=Mengundang teman dengan mengirimkan tautan
+## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_3_copy={{clientSuperShortname}} bekerja dengan hampir semua peramban. Tidak perlu membuat akun dan setiap orang terhubung secara gratis.
+## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
+## will be replaced by the super short brand name.
+fte_slide_4_title=Temukan ikon {{clientSuperShortname}} untuk memulai
+## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
+## will be replaced by the brand short name.
+fte_slide_4_copy=Saat Anda menemukan laman yang ingin didiskusikan, klik ikon {{brandShortname}} untuk membuat tautan. Lalu kirimkan ke teman seperti yang Anda inginkan!
+
 invite_header_text_bold=Undang seseorang untuk jelajahi laman ini bersama Anda!
+invite_header_text_bold2=Mengundang teman untuk bergabung!
 invite_header_text3=Membutuhkan 2 orang untuk menggunakan Firefox Hello, jadi kirimkan tautan ke teman untuk menjelajah Web bersama Anda!
+invite_header_text4=Bagikan tautan ini supaya anda bisa mulai menjelajahi Web bersama.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Salin Tautan
 invite_copied_link_button=Tersalin!
-invite_email_link_button=Email Tautan
+invite_email_link_button=Kirim Tautan
 invite_facebook_button3=Facebook
 invite_your_link=Tautan Anda:
 
-# Status text
-display_name_guest=Tamu
-
 # Error bars
 ## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
 ## These may be displayed at the top of the panel.
 session_expired_error_description=Sesi habis. Semua URLs yang sebelumnya Anda buat dan bagikan akan tidak berfungsi lagi.
 could_not_authenticate=Tidak Dapat Mengautentikasi
-password_changed_question=Apakah Anda mengganti password Anda?
+password_changed_question=Sudah ganti kata sandi Anda?
 try_again_later=Silakan coba kembali nanti
 could_not_connect=Tidak Dapat Terhubung Dengan Server
 check_internet_connection=Silakan periksa koneksi internet Anda
 login_expired=Sesi Masuk Anda Habis
 service_not_available=Layanan Tidak Tersedia Saat Ini
 problem_accessing_account=Terjadi Masalah Dalam Mengakses Akun Anda
 
 ## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
@@ -82,17 +101,17 @@ share_email_footer2=\n\n____________\nFi
 ## in a tweet.
 share_tweet=Gabung dengan saya untuk percakapan video pada {{clientShortname2}}!
 
 share_add_service_button=Tambah Layanan
 
 ## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
 ## These menu items are displayed from a panel's context menu for a conversation.
 copy_link_menuitem=Salin Tautan
-email_link_menuitem=Email Tautan
+email_link_menuitem=Kirim Tautan
 delete_conversation_menuitem2=Hapus
 
 panel_footer_signin_or_signup_link=Masuk atau Daftar
 
 settings_menu_item_account=Akun
 settings_menu_item_settings=Pengaturan
 settings_menu_item_signout=Keluar
 settings_menu_item_signin=Masuk
@@ -123,17 +142,17 @@ initiate_audio_video_call_button2=Mulai
 initiate_audio_video_call_tooltip2=Mulai percakapan video
 initiate_audio_call_button2=Percakapan video
 
 peer_ended_conversation2=Orang yang dipanggil telah mengakhiri percakapan.
 restart_call=Gabung Kembali
 
 ## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
 ## contact is offline.
-contact_offline_title=Orang ini tidak luring
+contact_offline_title=Orang ini tidak daring
 ## LOCALIZATION NOTE (call_timeout_notification_text): Title which is displayed
 ## when the call didn't go through.
 call_timeout_notification_text=Panggilan tidak sampai.
 
 ## LOCALIZATION NOTE (cancel_button):
 ## This button is displayed when a call has failed.
 cancel_button=Batal
 rejoin_button=Gabung Kembali Percakapan
@@ -172,43 +191,30 @@ tour_label=Tur
 rooms_list_recently_browsed2=Baru saja dijelajah
 rooms_list_currently_browsing2=Penjelajahan saat ini
 rooms_signout_alert=Percakapan yang terbuka akan ditutup
 room_name_untitled_page=Halaman Tanpa Judul
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=Sampai bertemu lagi! Anda dapat kembali ke sesi ini bersama setiap saat melalui panel Halo.
 door_hanger_prompt_name=Apakah Anda ingin memberikannya nama yang lebih mudah diingat? Nama:
-door_hanger_button=OK
+door_hanger_button=Oke
 
 # Infobar strings
 
+infobar_screenshare_no_guest_message=Segera setelah seorang teman bergabung, mereka akan dapat melihat tab manapun yang anda klik.
 infobar_screenshare_browser_message2=Anda membagikan tab Anda. Tab apapun yang diklik dapat terlihat oleh teman Anda
-infobar_screenshare_paused_browser_message=Pembagian tab dihentikan sementara
-infobar_button_gotit_label=Mengerti!
-infobar_button_gotit_accesskey=G
-infobar_button_pause_label=Tunda
-infobar_button_pause_accesskey=P
-infobar_button_restart_label=Mulai Ulang
-infobar_button_restart_accesskey=e
-infobar_button_resume_label=Lanjutkan
-infobar_button_resume_accesskey=R
-infobar_button_stop_label=Hentikan
+infobar_screenshare_browser_message3=Anda kini berbagi tab. Teman anda akan melihat setiap tab yang anda klik.
+infobar_screenshare_stop_sharing_message=Anda tidak lagi berbagi tab
+infobar_button_restart_label2=Ulangi proses berbagi
+infobar_button_restart_accesskey=R
+infobar_button_stop_label2=Hentikan proses berbagi
 infobar_button_stop_accesskey=S
-infobar_menuitem_dontshowagain_label=Jangan tampilkan lagi
-infobar_menuitem_dontshowagain_accesskey=D
-
-# Context in conversation strings
-
-## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
-## has no conversations available.
-no_conversations_message_heading2=Belum ada percakapan.
-## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
-## user to start a new conversation.
-no_conversations_start_message2=Mulai percakapan baru!
+infobar_button_disconnect_label=Terputus
+infobar_button_disconnect_accesskey=D
 
 # E10s not supported strings
 
 e10s_not_supported_button_label=Buka Jendela Baru
 e10s_not_supported_subheading={{brandShortname}} tidak dapat bekerja dalam jendela multi-proses.
 # 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/.
@@ -241,15 +247,18 @@ retry_call_button=Coba Lagi
 rooms_leave_button_label=Tinggalkan
 
 rooms_panel_title=Pilih percakapan atau mulai yang baru
 
 rooms_room_full_call_to_action_label=Pelajari selengkapnya tentang {{clientShortname}} »
 rooms_room_full_call_to_action_nonFx_label=Unduh {{brandShortname}} untuk memulai percakapan Anda sendiri
 rooms_room_full_label=Sudah ada dua orang dalam percakapan ini.
 rooms_room_join_label=Gabung ke dalam percakapan
-rooms_room_joined_label=Seseorang bergabung ke dalam percakapan!
+rooms_room_joined_owner_connected_label2=Kini teman Anda telah tersambung dan akan dapat melihat tab Anda.
+rooms_room_joined_owner_not_connected_label=Teman Anda sedang menunggu untuk menjelajahi {{roomURLHostname}} bersama Anda.
 
 self_view_hidden_message=Tampilan diri sedang tersembunyi tetapi tetap dikirim, ubah ukuran jendela untuk menampilkannya
 
 ## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
 ## as this will be replaced by clientShortname2.
 tos_failure_message={{clientShortname}} tidak tersedia di negara Anda.
+
+display_name_guest=Tamu
--- a/browser/extensions/loop/chrome/locale/it/loop.properties
+++ b/browser/extensions/loop/chrome/locale/it/loop.properties
@@ -21,38 +21,51 @@ loopMenuItem_accesskey=u
 sign_in_again_title_line_one=Accedi nuovamente
 sign_in_again_title_line_two2=per continuare a utilizzare {{clientShortname2}}
 sign_in_again_button=Accedi
 ## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
 ## will be replaced by the super short brandname.
 sign_in_again_use_as_guest_button2=Utilizza {{clientSuperShortname}} come ospite
 
 panel_browse_with_friend_button=Naviga in questa pagina insieme a un amico
+panel_disconnect_button=Disconnetti
 
 ## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
 ## user to create his or her first conversation.
 first_time_experience_subheading2=Fai clic sul pulsante Hello per navigare sul Web con un amico.
+first_time_experience_subheading_button_above=Fai clic sul pulsante Hello per navigare sul Web insieme a un altro utente.
 
 ## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
 ## ways to use Hello project.
 first_time_experience_content=Utilizzalo per fare progetti, lavorare o semplicemente fare una risata in compagnia.
+first_time_experience_content2=Utilizza questa funzione per organizzare eventi, collaborare a un lavoro o divertirti con gli amici.
 first_time_experience_button_label2=Scopri come funziona
 
 ## First Time Experience Slides
+fte_slide_1_title=Naviga sul Web insieme a un amico
 ## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
 ## will be replaced by the short name 2.
+fte_slide_1_copy=Quando organizzi un viaggio o acquisti un regalo in comune con gli amici, {{clientShortname2}} vi aiuta a prendere decisioni più rapide in tempo reale.
+fte_slide_2_title=Condividi le tue vedute
+fte_slide_2_copy=Grazie alla chat e alla video chat integrate puoi condividere idee, confrontare opzioni e raggiungere un accordo facilmente.
+fte_slide_3_title=Invita un link a un altro utente per invitarlo
 ## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
+fte_slide_3_copy={{clientSuperShortname}} è compatibile con la maggior parte dei browser per desktop. Il servizio è gratuito e non richiede la registrazione di alcun account.
 ## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
 ## will be replaced by the super short brand name.
+fte_slide_4_title=Per iniziare individua l’icona di {{clientSuperShortname}}
 ## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
 ## will be replaced by the brand short name.
+fte_slide_4_copy=Quando ti trovi su una pagina web che vuoi mostrare a qualcuno, genera un link facendo clic sull’icona di {{brandShortname}}, poi invialo all’interessato con il metodo che preferisci.
 
 invite_header_text_bold=Invita un amico e visita questa pagina insieme a lui.
+invite_header_text_bold2=Invita chi vuoi tu a unirsi alla conversazione!
 invite_header_text3=Servono due persone per utilizzare Firefox Hello: invita un amico e naviga sul Web insieme a lui!
+invite_header_text4=Condividi questo link per navigare sul Web in compagnia.
 ## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
 ## invite_email_link_button, invite_facebook_button2): These labels appear under
 ## an iconic button for the invite view.
 invite_copy_link_button=Copia link
 invite_copied_link_button=Copiato
 invite_email_link_button=Invia link per email
 invite_facebook_button3=Facebook
 invite_your_link=Link:
@@ -182,19 +195,26 @@ room_name_untitled_page=Pagina senza tit
 
 ## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
 door_hanger_return=Ci vediamo più tardi! È possibile ritornare a questa sessione condivisa in qualunque momento attraverso il pannello di Hello.
 door_hanger_prompt_name=Assegnare un nome più semplice da ricordare? Nome corrente:
 door_hanger_button=OK
 
 # Infobar strings
 
+infobar_screenshare_no_guest_message=L’utente che hai invitato sarà in grado di visualizzare ogni scheda che apri sul tuo browser dal momento in cui si unisce alla conversazione.
 infobar_screenshare_browser_message2=Le schede sono attualmente condivise. Seleziona una qualsiasi delle schede per condividerla
+infobar_screenshare_browser_message3=Stai condividendo le schede del tuo browser. Da questo momento il tuo interlocutore sarà in grado di visualizzare tutte le schede che apri.
+infobar_screenshare_stop_sharing_message=La condivisione schede è disattivata
+infobar_button_restart_label2=Riattiva condivisione
 infobar_button_restart_accesskey=v
+infobar_button_stop_label2=Termina condivisione
 infobar_button_stop_accesskey=I
+infobar_button_disconnect_label=Disconnetti
+infobar_button_disconnect_accesskey=D
 
 # E10s not supported strings
 
 e10s_not_supported_button_label=Apri una nuova finestra
 e10s_not_supported_subheading={{brandShortname}} non è compatibile con la modalità multiprocesso.
 # 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/.
@@ -227,15 +247,18 @@ retry_call_button=Riprova