Backed out 12 changesets (bug 1246592, bug 1239828, bug 1154277, bug 1245912, bug 1229195) for causing closed beta tree due to bustage on windows on a CLOSED TREE
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 16 Feb 2016 11:08:44 +0100
changeset 311262 b454ec296bf6ffd5a79d97d64c36b7bc427b2332
parent 311261 638572acfc9eeb137c403a226040876cf63e0de6
child 311263 d385d640f12e91a5c9ec54c076449d0d8ad6e2bf
push id5623
push usercbook@mozilla.com
push dateTue, 16 Feb 2016 10:09:13 +0000
treeherdermozilla-beta@b454ec296bf6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1246592, 1239828, 1154277, 1245912, 1229195
milestone45.0
backs out638572acfc9eeb137c403a226040876cf63e0de6
09e2d72b8754547e3be422186162e5c3cbf12e29
27634a68a77f226ab1d340a2ded09f54f5bd503b
caa0d1a712051076dd3fc57ec02fcf5d05737da7
06abb1db0df34d20a9d1fd415f376e0bc42c29d5
cefd1c9093ab0ad769398f3a38ae9210e8b28a42
533cba8abe30d3534b069a9a0f59e6c02d58d137
e753a943bd68e78c947b7b0266650c7e2d34f0a9
41eb59a46425fa286cba45ed4f1cd049b9cca4f4
aa59b35794d658d3f42f9d1ef590095e9e63e7a2
50b10870b65b53a21a46ea591fd3888d85038d63
86730afe0bd6f238bacb40de672eed99a8c6b74e
Backed out 12 changesets (bug 1246592, bug 1239828, bug 1154277, bug 1245912, bug 1229195) for causing closed beta tree due to bustage on windows on a CLOSED TREE Backed out changeset 638572acfc9e (bug 1239828) Backed out changeset 09e2d72b8754 (bug 1245912) Backed out changeset 27634a68a77f (bug 1229195) Backed out changeset caa0d1a71205 (bug 1154277) Backed out changeset 06abb1db0df3 (bug 1154277) Backed out changeset cefd1c9093ab (bug 1154277) Backed out changeset 533cba8abe30 (bug 1154277) Backed out changeset e753a943bd68 (bug 1154277) Backed out changeset 41eb59a46425 (bug 1246592) Backed out changeset aa59b35794d6 (bug 1239828) Backed out changeset 50b10870b65b (bug 1239828) Backed out changeset 86730afe0bd6 (bug 1239828)
.gitignore
browser/base/content/browser-social.js
browser/base/content/browser.css
browser/base/content/social-content.js
browser/base/content/socialchat.xml
browser/base/content/test/chat/browser.ini
browser/base/content/test/chat/browser_chatwindow.js
browser/base/content/test/chat/browser_focus.js
browser/base/content/test/chat/browser_tearoff.js
browser/base/content/test/chat/head.js
browser/base/content/test/social/browser.ini
browser/base/content/test/social/browser_social_chatwindowfocus.js
browser/base/content/test/social/browser_social_errorPage.js
browser/base/content/test/social/head.js
browser/extensions/loop/bootstrap.js
browser/extensions/loop/chrome/content/modules/LoopRooms.jsm
browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
browser/extensions/loop/chrome/content/panels/conversation.html
browser/extensions/loop/chrome/content/panels/css/panel.css
browser/extensions/loop/chrome/content/panels/js/conversation.js
browser/extensions/loop/chrome/content/panels/js/conversationAppStore.js
browser/extensions/loop/chrome/content/panels/js/panel.js
browser/extensions/loop/chrome/content/panels/js/roomStore.js
browser/extensions/loop/chrome/content/panels/js/roomViews.js
browser/extensions/loop/chrome/content/panels/panel.html
browser/extensions/loop/chrome/content/panels/test/README.md
browser/extensions/loop/chrome/content/panels/test/conversationAppStore_test.js
browser/extensions/loop/chrome/content/panels/test/conversation_test.js
browser/extensions/loop/chrome/content/panels/test/index.html
browser/extensions/loop/chrome/content/panels/test/panel_test.js
browser/extensions/loop/chrome/content/panels/test/roomStore_test.js
browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
browser/extensions/loop/chrome/content/preferences/prefs.js
browser/extensions/loop/chrome/content/shared/README.md
browser/extensions/loop/chrome/content/shared/css/common.css
browser/extensions/loop/chrome/content/shared/css/conversation.css
browser/extensions/loop/chrome/content/shared/img/cursor.svg
browser/extensions/loop/chrome/content/shared/js/actions.js
browser/extensions/loop/chrome/content/shared/js/linkifiedTextView.js
browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
browser/extensions/loop/chrome/content/shared/js/remoteCursorStore.js
browser/extensions/loop/chrome/content/shared/js/utils.js
browser/extensions/loop/chrome/content/shared/js/views.js
browser/extensions/loop/chrome/content/shared/test/index.html
browser/extensions/loop/chrome/content/shared/test/linkifiedTextView_test.js
browser/extensions/loop/chrome/content/shared/test/otSdkDriver_test.js
browser/extensions/loop/chrome/content/shared/test/remoteCursorStore_test.js
browser/extensions/loop/chrome/content/shared/test/utils_test.js
browser/extensions/loop/chrome/content/shared/test/vendor/chai.js
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
browser/extensions/loop/chrome/content/shared/test/vendor/sinon.js
browser/extensions/loop/chrome/content/shared/test/views_test.js
browser/extensions/loop/chrome/content/shared/vendor/sdk-content/css/ot.css
browser/extensions/loop/chrome/content/shared/vendor/sdk.js
browser/extensions/loop/chrome/locale/af/loop.properties
browser/extensions/loop/chrome/locale/ar/loop.properties
browser/extensions/loop/chrome/locale/as/loop.properties
browser/extensions/loop/chrome/locale/ast/loop.properties
browser/extensions/loop/chrome/locale/az/loop.properties
browser/extensions/loop/chrome/locale/be/loop.properties
browser/extensions/loop/chrome/locale/bg/loop.properties
browser/extensions/loop/chrome/locale/bn-BD/loop.properties
browser/extensions/loop/chrome/locale/bn-IN/loop.properties
browser/extensions/loop/chrome/locale/bs/loop.properties
browser/extensions/loop/chrome/locale/ca/loop.properties
browser/extensions/loop/chrome/locale/cs/loop.properties
browser/extensions/loop/chrome/locale/cy/loop.properties
browser/extensions/loop/chrome/locale/da/loop.properties
browser/extensions/loop/chrome/locale/de/loop.properties
browser/extensions/loop/chrome/locale/dsb/loop.properties
browser/extensions/loop/chrome/locale/el/loop.properties
browser/extensions/loop/chrome/locale/en-GB/loop.properties
browser/extensions/loop/chrome/locale/en-US/loop.properties
browser/extensions/loop/chrome/locale/eo/loop.properties
browser/extensions/loop/chrome/locale/es-CL/loop.properties
browser/extensions/loop/chrome/locale/es-ES/loop.properties
browser/extensions/loop/chrome/locale/es-MX/loop.properties
browser/extensions/loop/chrome/locale/et/loop.properties
browser/extensions/loop/chrome/locale/eu/loop.properties
browser/extensions/loop/chrome/locale/fa/loop.properties
browser/extensions/loop/chrome/locale/ff/loop.properties
browser/extensions/loop/chrome/locale/fi/loop.properties
browser/extensions/loop/chrome/locale/fr/loop.properties
browser/extensions/loop/chrome/locale/fy-NL/loop.properties
browser/extensions/loop/chrome/locale/fy/loop.properties
browser/extensions/loop/chrome/locale/ga/loop.properties
browser/extensions/loop/chrome/locale/gd/loop.properties
browser/extensions/loop/chrome/locale/gl/loop.properties
browser/extensions/loop/chrome/locale/gu-IN/loop.properties
browser/extensions/loop/chrome/locale/he/loop.properties
browser/extensions/loop/chrome/locale/hi-IN/loop.properties
browser/extensions/loop/chrome/locale/hr/loop.properties
browser/extensions/loop/chrome/locale/hsb/loop.properties
browser/extensions/loop/chrome/locale/ht/loop.properties
browser/extensions/loop/chrome/locale/hu/loop.properties
browser/extensions/loop/chrome/locale/hy-AM/loop.properties
browser/extensions/loop/chrome/locale/id/loop.properties
browser/extensions/loop/chrome/locale/it/loop.properties
browser/extensions/loop/chrome/locale/ja/loop.properties
browser/extensions/loop/chrome/locale/jar.mn
browser/extensions/loop/chrome/locale/kk/loop.properties
browser/extensions/loop/chrome/locale/km/loop.properties
browser/extensions/loop/chrome/locale/kn/loop.properties
browser/extensions/loop/chrome/locale/ko/loop.properties
browser/extensions/loop/chrome/locale/ku/loop.properties
browser/extensions/loop/chrome/locale/lij/loop.properties
browser/extensions/loop/chrome/locale/lt/loop.properties
browser/extensions/loop/chrome/locale/lv/loop.properties
browser/extensions/loop/chrome/locale/mk/loop.properties
browser/extensions/loop/chrome/locale/ml/loop.properties
browser/extensions/loop/chrome/locale/mn/loop.properties
browser/extensions/loop/chrome/locale/moz.build
browser/extensions/loop/chrome/locale/ms/loop.properties
browser/extensions/loop/chrome/locale/my/loop.properties
browser/extensions/loop/chrome/locale/nb-NO/loop.properties
browser/extensions/loop/chrome/locale/ne-NP/loop.properties
browser/extensions/loop/chrome/locale/nl/loop.properties
browser/extensions/loop/chrome/locale/or/loop.properties
browser/extensions/loop/chrome/locale/pa-IN/loop.properties
browser/extensions/loop/chrome/locale/pa/loop.properties
browser/extensions/loop/chrome/locale/pl/loop.properties
browser/extensions/loop/chrome/locale/pt-BR/loop.properties
browser/extensions/loop/chrome/locale/pt-PT/loop.properties
browser/extensions/loop/chrome/locale/pt/loop.properties
browser/extensions/loop/chrome/locale/rm/loop.properties
browser/extensions/loop/chrome/locale/ro/loop.properties
browser/extensions/loop/chrome/locale/ru/loop.properties
browser/extensions/loop/chrome/locale/si/loop.properties
browser/extensions/loop/chrome/locale/sk/loop.properties
browser/extensions/loop/chrome/locale/sl/loop.properties
browser/extensions/loop/chrome/locale/son/loop.properties
browser/extensions/loop/chrome/locale/sq/loop.properties
browser/extensions/loop/chrome/locale/sr/loop.properties
browser/extensions/loop/chrome/locale/sv-SE/loop.properties
browser/extensions/loop/chrome/locale/ta/loop.properties
browser/extensions/loop/chrome/locale/te/loop.properties
browser/extensions/loop/chrome/locale/th/loop.properties
browser/extensions/loop/chrome/locale/tr/loop.properties
browser/extensions/loop/chrome/locale/uk/loop.properties
browser/extensions/loop/chrome/locale/ur/loop.properties
browser/extensions/loop/chrome/locale/vi/loop.properties
browser/extensions/loop/chrome/locale/xh/loop.properties
browser/extensions/loop/chrome/locale/zh-CN/loop.properties
browser/extensions/loop/chrome/locale/zh-TW/loop.properties
browser/extensions/loop/chrome/locale/zu/loop.properties
browser/extensions/loop/chrome/skin/osx/platform.css
browser/extensions/loop/chrome/skin/shared/loop.css
browser/extensions/loop/chrome/skin/windows/toolbar-win10.png
browser/extensions/loop/chrome/skin/windows/toolbar-win10@2x.png
browser/extensions/loop/chrome/skin/windows/toolbar-win8.png
browser/extensions/loop/chrome/skin/windows/toolbar-win8@2x.png
browser/extensions/loop/chrome/skin/windows/toolbar.png
browser/extensions/loop/chrome/skin/windows/toolbar@2x.png
browser/extensions/loop/chrome/test/mochitest/.eslintrc
browser/extensions/loop/chrome/test/mochitest/browser.ini
browser/extensions/loop/chrome/test/mochitest/browser_LoopRooms_channel.js
browser/extensions/loop/chrome/test/mochitest/browser_fxa_login.js
browser/extensions/loop/chrome/test/mochitest/browser_mozLoop_sharingListeners.js
browser/extensions/loop/chrome/test/mochitest/browser_mozLoop_telemetry.js
browser/extensions/loop/chrome/test/xpcshell/test_looprooms.js
browser/extensions/loop/chrome/test/xpcshell/test_looprooms_getall.js
browser/extensions/loop/chrome/test/xpcshell/test_loopservice_dnd.js
browser/extensions/loop/install.rdf.in
browser/extensions/loop/jar.mn
browser/extensions/loop/moz.build
browser/extensions/loop/test/functional/config.py
browser/installer/package-manifest.in
browser/locales/Makefile.in
browser/modules/Chat.jsm
browser/modules/PanelFrame.jsm
dom/base/nsFrameLoader.cpp
toolkit/components/social/MozSocialAPI.jsm
toolkit/content/browser-child.js
toolkit/content/widgets/browser.xml
toolkit/content/widgets/remote-browser.xml
--- a/.gitignore
+++ b/.gitignore
@@ -5,19 +5,16 @@
 *.pyc
 *.pyo
 TAGS
 tags
 ID
 .DS_Store*
 *.pdb
 
-# Allow the id locale directory for loop ('ID' matches this normally)
-!browser/extensions/loop/chrome/locale/id
-
 # Vim swap files.
 .*.sw[a-z]
 
 # Emacs directory variable files.
 **/.dir-locals.el
 
 # User files that may appear at the root
 /.mozconfig*
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -365,17 +365,17 @@ SocialFlyout = {
     iframe.setAttribute("messagemanagergroup", "social");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
     iframe.setAttribute("context", "contentAreaContextMenu");
     iframe.setAttribute("origin", SocialSidebar.provider.origin);
     panel.appendChild(iframe);
     // the xbl bindings for the iframe probably don't exist yet, so we can't
     // access iframe.messageManager directly - but can get at it with this dance.
     let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
-    mm.sendAsyncMessage("Social:SetErrorURL",
+    mm.sendAsyncMessage("Social:SetErrorURL", null,
                         { template: "about:socialerror?mode=compactInfo&origin=%{origin}" });
   },
 
   unload: function() {
     let panel = this.panel;
     panel.hidePopup();
     if (!panel.firstChild)
       return
@@ -507,17 +507,17 @@ SocialShare = {
     iframe.setAttribute("context", "contentAreaContextMenu");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
     iframe.setAttribute("disableglobalhistory", "true");
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("message", "true");
     iframe.setAttribute("messagemanagergroup", "social");
     panel.lastChild.appendChild(iframe);
     let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
-    mm.sendAsyncMessage("Social:SetErrorURL",
+    mm.sendAsyncMessage("Social:SetErrorURL", null,
                         { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
 
     this.populateProviderMenu();
   },
 
   getSelectedProvider: function() {
     let provider;
     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -117,20 +117,16 @@ panelview {
   transition: transform var(--panelui-subview-transition-duration);
 }
 
 panelview:not([mainview]):not([current]) {
   transition: visibility 0s linear var(--panelui-subview-transition-duration);
   visibility: collapse;
 }
 
-browser[frameType="social"][remote="true"] {
-  -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
-}
-
 tabbrowser {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser");
 }
 
 .tabbrowser-tabs {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
 }
 
--- a/browser/base/content/social-content.js
+++ b/browser/base/content/social-content.js
@@ -6,179 +6,45 @@
 /* This content script should work in any browser or iframe and should not
  * depend on the frame being contained in tabbrowser. */
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
-// Tie `content` to this frame scripts' global scope explicitly. If we don't, then
-// `content` might be out of eval's scope and GC'ed before this script is done.
-// See bug 1229195 for empirical proof.
-var gContent = content;
-
 // social frames are always treated as app tabs
 docShell.isAppTab = true;
 
-var gDOMContentLoaded = false;
-addEventListener("DOMContentLoaded", function() {
-  gDOMContentLoaded = true;
-  sendAsyncMessage("DOMContentLoaded");
-});
-var gDOMTitleChangedByUs = false;
-addEventListener("DOMTitleChanged", function(e) {
-  if (!gDOMTitleChangedByUs) {
-    sendAsyncMessage("DOMTitleChanged", {
-      title: e.target.title
-    });
-  }
-  gDOMTitleChangedByUs = false;
-});
-
 // Error handling class used to listen for network errors in the social frames
 // and replace them with a social-specific error page
 SocialErrorListener = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
-                                         Ci.nsIWebProgressListener,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsISupports]),
 
   defaultTemplate: "about:socialerror?mode=tryAgainOnly&url=%{url}&origin=%{origin}",
   urlTemplate: null,
 
   init() {
-    addMessageListener("Loop:MonitorPeerConnectionLifecycle", this);
-    addMessageListener("Loop:GetAllWebrtcStats", this);
-    addMessageListener("Social:CustomEvent", this);
-    addMessageListener("Social:EnsureFocus", this);
-    addMessageListener("Social:EnsureFocusElement", this);
-    addMessageListener("Social:HookWindowCloseForPanelClose", this);
-    addMessageListener("Social:ListenForEvents", this);
-    addMessageListener("Social:SetDocumentTitle", this);
     addMessageListener("Social:SetErrorURL", this);
-    addMessageListener("Social:WaitForDocumentVisible", this);
-    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) {
-    let document = content.document;
-
-    switch (message.name) {
-      case "Loop:GetAllWebrtcStats":
-        content.WebrtcGlobalInformation.getAllStats(allStats => {
-          content.WebrtcGlobalInformation.getLogging("", logs => {
-            sendAsyncMessage("Loop:GetAllWebrtcStats", {
-              allStats: allStats,
-              logs: logs
-            });
-          });
-        }, message.data.peerConnectionID);
-        break;
-      case "Loop:MonitorPeerConnectionLifecycle":
-        let ourID = content.QueryInterface(Ci.nsIInterfaceRequestor)
-          .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
-
-        let onPCLifecycleChange = (pc, winID, type) => {
-          if (winID != ourID) {
-            return;
-          }
-
-          sendAsyncMessage("Loop:PeerConnectionLifecycleChange", {
-            iceConnectionState: pc.iceConnectionState,
-            locationHash: content.location.hash,
-            peerConnectionID: pc.id,
-            type: type
-          });
-        };
-
-        let pc_static = new content.RTCPeerConnectionStatic();
-        pc_static.registerPeerConnectionLifecycleCallback(onPCLifecycleChange);
-        break;
-      case "Social:CustomEvent":
-        let ev = new content.CustomEvent(message.data.name, message.data.detail ?
-          { detail: message.data.detail } : null);
-        content.dispatchEvent(ev);
-        break;
-      case "Social:EnsureFocus":
-        Services.focus.focusedWindow = content;
-        sendAsyncMessage("Social:FocusEnsured");
-        break;
-      case "Social:EnsureFocusElement":
-        let fm = Services.focus;
-        fm.moveFocus(document.defaultView, null, fm.MOVEFOCUS_FIRST, fm.FLAG_NOSCROLL);
-        sendAsyncMessage("Social:FocusEnsured");
-        break;
-      case "Social:HookWindowCloseForPanelClose":
-        // We allow window.close() to close the panel, so add an event handler for
-        // this, then cancel the event (so the window itself doesn't die) and
-        // close the panel instead.
-        // However, this is typically affected by the dom.allow_scripts_to_close_windows
-        // preference, but we can avoid that check by setting a flag on the window.
-        let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
-           .getInterface(Ci.nsIDOMWindowUtils);
-        dwu.allowScriptsToClose();
-
-        content.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) {
-          sendAsyncMessage("DOMWindowClose");
-          // preventDefault stops the default window.close() function being called,
-          // which doesn't actually close anything but causes things to get into
-          // a bad state (an internal 'closed' flag is set and debug builds start
-          // asserting as the window is used.).
-          // None of the windows we inject this API into are suitable for this
-          // default close behaviour, so even if we took no action above, we avoid
-          // the default close from doing anything.
-          evt.preventDefault();
-        }, true);
-        break;
-      case "Social:ListenForEvents":
-        for (let eventName of message.data.eventNames) {
-          content.addEventListener(eventName, this);
-        }
-        break;
-      case "Social:SetDocumentTitle":
-        let title = message.data.title;
-        if (title && (title = title.trim())) {
-          gDOMTitleChangedByUs = true;
-          document.title = title;
-        }
-        break;
-      case "Social:SetErrorURL":
-        // Either a url or null to reset to default template.
-        this.urlTemplate = message.data.template;
-        break;
-      case "Social:WaitForDocumentVisible":
-        if (!document.hidden) {
-          sendAsyncMessage("Social:DocumentVisible");
-          break;
-        }
-
-        document.addEventListener("visibilitychange", function onVisibilityChanged() {
-          document.removeEventListener("visibilitychange", onVisibilityChanged);
-          sendAsyncMessage("Social:DocumentVisible");
-        });
-        break;
-      case "WaitForDOMContentLoaded":
-        if (gDOMContentLoaded) {
-          sendAsyncMessage("DOMContentLoaded");
-        }
-        break;
+    switch(message.name) {
+      case "Social:SetErrorURL": {
+        // either a url or null to reset to default template
+        this.urlTemplate = message.objects.template;
+      }
     }
   },
 
-  handleEvent: function(event) {
-    sendAsyncMessage("Social:CustomEvent", {
-      name: event.type
-    });
-  },
-
   setErrorPage() {
     // if this is about:providerdirectory, use the directory iframe
     let frame = docShell.chromeEventHandler;
     let origin = frame.getAttribute("origin");
     let src = frame.getAttribute("src");
     if (src == "about:providerdirectory") {
       frame = content.document.getElementById("activation-frame");
       src = frame.getAttribute("src");
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -22,104 +22,94 @@
                            oncommand="document.getBindingParent(this).showNotifications(this); event.stopPropagation();"/>
         <xul:toolbarbutton anonid="minimize" class="chat-minimize-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).toggle();"/>
         <xul:toolbarbutton anonid="swap" class="chat-swap-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).swapWindows();"/>
         <xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).close();"/>
       </xul:hbox>
-      <xul:browser anonid="remote-content" class="chat-frame" flex="1"
-                   context="contentAreaContextMenu"
-                   disableglobalhistory="true"
-                   frameType="social"
-                   message="true"
-                   messagemanagergroup="social"
-                   tooltip="aHTMLTooltip"
-                   remote="true"
-                   xbl:inherits="src,origin"
-                   type="content"/>
-
       <xul:browser anonid="content" class="chat-frame" flex="1"
-                   context="contentAreaContextMenu"
-                   disableglobalhistory="true"
-                   message="true"
-                   messagemanagergroup="social"
-                   tooltip="aHTMLTooltip"
-                   xbl:inherits="src,origin"
-                   type="content"/>
+                  context="contentAreaContextMenu"
+                  disableglobalhistory="true"
+                  message="true"
+                  messagemanagergroup="social"
+                  tooltip="aHTMLTooltip"
+                  xbl:inherits="src,origin" type="content"/>
     </content>
 
-    <implementation implements="nsIDOMEventListener, nsIMessageListener">
+    <implementation implements="nsIDOMEventListener">
       <constructor><![CDATA[
         const kAnchorMap = new Map([
           ["", "notification-"],
           ["webRTC-shareScreen-", ""],
           ["webRTC-sharingScreen-", ""]
         ]);
-        const kBrowsers = [
-          document.getAnonymousElementByAttribute(this, "anonid", "content"),
-          document.getAnonymousElementByAttribute(this, "anonid", "remote-content")
-        ];
-        for (let content of kBrowsers) {
-          for (let [getterPrefix, idPrefix] of kAnchorMap) {
-            let getter = getterPrefix + "popupnotificationanchor";
-            let anonid = (idPrefix || getterPrefix) + "icon";
-            content.__defineGetter__(getter, () => {
-              delete content[getter];
-              return content[getter] = document.getAnonymousElementByAttribute(
-                this, "anonid", anonid);
-            });
-          }
+        for (let [getterPrefix, idPrefix] of kAnchorMap) {
+          let getter = getterPrefix + "popupnotificationanchor";
+          let anonid = (idPrefix || getterPrefix) + "icon";
+          this.content.__defineGetter__(getter, () => {
+            delete this.content[getter];
+            return this.content[getter] = document.getAnonymousElementByAttribute(
+              this, "anonid", anonid);
+          });
         }
 
-        let mm = this.content.messageManager;
+        let contentWindow = this.contentWindow;
         // process this._callbacks, then set to null so the chatbox creator
         // knows to make new callbacks immediately.
         if (this._callbacks) {
           for (let callback of this._callbacks) {
             callback(this);
           }
           this._callbacks = null;
         }
-
-        mm.addMessageListener("DOMTitleChanged", this);
-
-        mm.sendAsyncMessage("WaitForDOMContentLoaded");
-        mm.addMessageListener("DOMContentLoaded", function DOMContentLoaded(event) {
-          mm.removeMessageListener("DOMContentLoaded", DOMContentLoaded);
+        this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
+          if (event.target != this.contentDocument)
+            return;
+          this.removeEventListener("DOMContentLoaded", DOMContentLoaded, true);
           this.isActive = !this.minimized;
           this._chat.loadButtonSet(this, this.getAttribute("buttonSet"));
           this._deferredChatLoaded.resolve(this);
-        }.bind(this));
+        }, true);
 
-        this.setActiveBrowser();
+        if (this.src)
+          this.setAttribute("src", this.src);
       ]]></constructor>
 
       <field name="_deferredChatLoaded" readonly="true">
         Promise.defer();
       </field>
 
       <property name="promiseChatLoaded">
         <getter>
           return this._deferredChatLoaded.promise;
         </getter>
       </property>
 
-      <property name="content">
-        <getter>
-          return document.getAnonymousElementByAttribute(this, "anonid",
-            (this.remote ? "remote-" : "") + "content");
-        </getter>
-      </property>
+      <field name="content" readonly="true">
+        document.getAnonymousElementByAttribute(this, "anonid", "content");
+      </field>
 
       <field name="_chat" readonly="true">
         Cu.import("resource:///modules/Chat.jsm", {}).Chat;
       </field>
 
+      <property name="contentWindow">
+        <getter>
+          return this.content.contentWindow;
+        </getter>
+      </property>
+
+      <property name="contentDocument">
+        <getter>
+          return this.content.contentDocument;
+        </getter>
+      </property>
+
       <property name="minimized">
         <getter>
           return this.getAttribute("minimized") == "true";
         </getter>
         <setter><![CDATA[
           // Note that this.isActive is set via our transitionend handler so
           // the content doesn't see intermediate values.
           let parent = this.chatbar;
@@ -148,70 +138,39 @@
       <property name="isActive">
         <getter>
           return this.content.docShellIsActive;
         </getter>
         <setter>
           this.content.docShellIsActive = !!val;
 
           // let the chat frame know if it is being shown or hidden
-          this.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
-            name: val ? "socialFrameShow" : "socialFrameHide"
-          });
+          let evt = this.contentDocument.createEvent("CustomEvent");
+          evt.initCustomEvent(val ? "socialFrameShow" : "socialFrameHide", true, true, {});
+          this.contentDocument.documentElement.dispatchEvent(evt);
         </setter>
       </property>
 
-      <field name="_remote">false</field>
-      <property name="remote" onget="return this._remote;">
-        <setter><![CDATA[
-          this._remote = !!val;
-
-          this.setActiveBrowser();
-        ]]></setter>
-      </property>
-
-      <method name="setActiveBrowser">
-        <body><![CDATA[
-          // Make sure we only show one browser element at a time.
-          let content = document.getAnonymousElementByAttribute(this, "anonid", "content");
-          let remoteContent = document.getAnonymousElementByAttribute(this, "anonid", "remote-content");
-          remoteContent.setAttribute("hidden", !this.remote);
-          content.setAttribute("hidden", this.remote);
-          remoteContent.removeAttribute("src");
-          content.removeAttribute("src");
-
-          if (this.src) {
-            this.setAttribute("src", this.src);
-
-            // Stop loading of the document - that is set before this method was
-            // called - in the now hidden browser.
-            (this.remote ? content : remoteContent).setAttribute("src", "about:blank");
-          }
-        ]]></body>
-      </method>
-
       <method name="showNotifications">
         <parameter name="aAnchor"/>
         <body><![CDATA[
         PopupNotifications._reshowNotifications(aAnchor,
                                                 this.content);
         ]]></body>
       </method>
 
       <method name="swapDocShells">
         <parameter name="aTarget"/>
         <body><![CDATA[
-          aTarget.setAttribute("label", this.content.contentTitle);
+          aTarget.setAttribute("label", this.contentDocument.title);
 
-          aTarget.remote = this.remote;
           aTarget.src = this.src;
-          let content = aTarget.content;
-          content.setAttribute("origin", this.content.getAttribute("origin"));
-          content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
-          content.swapDocShells(this.content);
+          aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
+          aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
+          aTarget.content.swapDocShells(this.content);
         ]]></body>
       </method>
 
       <method name="setDecorationAttributes">
         <parameter name="aTarget"/>
         <body><![CDATA[
           if (this.hasAttribute("customSize"))
             aTarget.setAttribute("customSize", this.getAttribute("customSize"));
@@ -245,117 +204,91 @@
 
       <method name="swapWindows">
         <body><![CDATA[
         let deferred = Promise.defer();
         let title = this.getAttribute("label");
         if (this.chatbar) {
           this.chatbar.detachChatbox(this, { "centerscreen": "yes" }).then(
             chatbox => {
-              chatbox.content.messageManager.sendAsyncMessage("Social:SetDocumentTitle", {
-                title: title
-              });
+              chatbox.contentWindow.document.title = title;
               deferred.resolve(chatbox);
             }
           );
         } else {
           // attach this chatbox to the topmost browser window
           let Chat = Cu.import("resource:///modules/Chat.jsm").Chat;
           let win = Chat.findChromeWindowForChats();
           let chatbar = win.document.getElementById("pinnedchats");
           let origin = this.content.getAttribute("origin");
-          let cb = chatbar.openChat({
-            origin: origin,
-            title: title,
-            url: "about:blank"
-          });
+          let cb = chatbar.openChat(origin, title, "about:blank");
+          this.setDecorationAttributes(cb);
 
           cb.promiseChatLoaded.then(
             () => {
-              this.setDecorationAttributes(cb);
-
               this.swapDocShells(cb);
 
               chatbar.focus();
               this.close();
 
               // chatboxForURL is a map of URL -> chatbox used to avoid opening
               // duplicate chat windows. Ensure reattached chat windows aren't
               // registered with about:blank as their URL, otherwise reattaching
               // more than one chat window isn't possible.
               chatbar.chatboxForURL.delete("about:blank");
               chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
 
-              cb.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
-                name: "socialFrameAttached"
+              let attachEvent = new cb.contentWindow.CustomEvent("socialFrameAttached", {
+                bubbles: true,
+                cancelable: true,
               });
+              cb.contentDocument.dispatchEvent(attachEvent);
 
               deferred.resolve(cb);
             }
           );
         }
         return deferred.promise;
         ]]></body>
       </method>
 
       <method name="toggle">
         <body><![CDATA[
           this.minimized = !this.minimized;
         ]]></body>
       </method>
-
-      <method name="setTitle">
-        <body><![CDATA[
-          try {
-            this.setAttribute("label", this.content.contentTitle);
-          } catch (ex) {}
-          if (this.chatbar)
-            this.chatbar.updateTitlebar(this);
-        ]]></body>
-      </method>
-
-      <method name="receiveMessage">
-        <parameter name="aMessage" />
-        <body><![CDATA[
-          switch (aMessage.name) {
-            case "DOMTitleChanged":
-              this.setTitle();
-              break;
-          }
-        ]]></body>
-      </method>
     </implementation>
 
     <handlers>
       <handler event="focus" phase="capturing">
         if (this.chatbar)
           this.chatbar.selectedChat = this;
       </handler>
-      <handler event="DOMTitleChanged">
-        this.setTitle();
-      </handler>
+      <handler event="DOMTitleChanged"><![CDATA[
+        this.setAttribute('label', this.contentDocument.title);
+        if (this.chatbar)
+          this.chatbar.updateTitlebar(this);
+      ]]></handler>
       <handler event="DOMLinkAdded"><![CDATA[
-        // Much of this logic is from DOMLinkHandler in browser.js.
-        // This sets the presence icon for a chat user, we simply use favicon
-        // style updating.
+        // much of this logic is from DOMLinkHandler in browser.js
+        // this sets the presence icon for a chat user, we simply use favicon style updating
         let link = event.originalTarget;
         let rel = link.rel && link.rel.toLowerCase();
         if (!link || !link.ownerDocument || !rel || !link.href)
           return;
         if (link.rel.indexOf("icon") < 0)
           return;
 
-        let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {})
-          .ContentLinkHandler;
+        let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {}).ContentLinkHandler;
         let uri = ContentLinkHandler.getLinkIconURI(link);
         if (!uri)
           return;
 
-        // We made it this far, use it.
-        this.setAttribute("image", uri.spec);
+        // we made it this far, use it
+        this.setAttribute('image', uri.spec);
         if (this.chatbar)
           this.chatbar.updateTitlebar(this);
       ]]></handler>
       <handler event="transitionend">
         if (this.isActive == this.minimized)
           this.isActive = !this.minimized;
       </handler>
     </handlers>
@@ -390,17 +323,17 @@
       <field name="nub" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "nub");
       </field>
 
       <method name="focus">
         <body><![CDATA[
           if (!this.selectedChat)
             return;
-          this.selectedChat.content.messageManager.sendAsyncMessage("Social:EnsureFocus");
+          Services.focus.focusedWindow = this.selectedChat.contentWindow;
         ]]></body>
       </method>
 
       <method name="_isChatFocused">
         <parameter name="aChatbox"/>
         <body><![CDATA[
           // If there are no XBL bindings for the chat it can't be focused.
           if (!aChatbox.content)
@@ -555,17 +488,17 @@
         <body><![CDATA[
           // we ensure that the cached width for a child of this type is
           // up-to-date so we can use it when resizing.
           this.getTotalChildWidth(aChatbox);
           aChatbox.collapsed = true;
           aChatbox.isActive = false;
           let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
           menu.setAttribute("class", "menuitem-iconic");
-          menu.setAttribute("label", aChatbox.content.contentTitle);
+          menu.setAttribute("label", aChatbox.contentDocument.title);
           menu.setAttribute("image", aChatbox.getAttribute("image"));
           menu.chat = aChatbox;
           this.menuitemMap.set(aChatbox, menu);
           this.menupopup.appendChild(menu);
           this.nub.collapsed = false;
         ]]></body>
       </method>
 
@@ -624,57 +557,57 @@
             this.menuitemMap.delete(aChatbox);
             this.menupopup.removeChild(menuitem);
           }
           this.chatboxForURL.delete(aChatbox.src);
         ]]></body>
       </method>
 
       <method name="openChat">
-        <parameter name="aOptions"/>
+        <parameter name="aOrigin"/>
+        <parameter name="aTitle"/>
+        <parameter name="aURL"/>
+        <parameter name="aMode"/>
         <parameter name="aCallback"/>
         <body><![CDATA[
-          let {origin, title, url, mode} = aOptions;
-          let cb = this.chatboxForURL.get(url);
+          let cb = this.chatboxForURL.get(aURL);
           if (cb && (cb = cb.get())) {
             // A chatbox is still alive to us when it's parented and still has
             // content.
-            if (cb.parentNode) {
-              this.showChat(cb, mode);
+            if (cb.parentNode && cb.contentWindow) {
+              this.showChat(cb, aMode);
               if (aCallback) {
                 if (cb._callbacks == null) {
                   // Chatbox has already been created, so callback now.
                   aCallback(cb);
                 } else {
                   // Chatbox is yet to have bindings created...
                   cb._callbacks.push(aCallback);
                 }
               }
               return cb;
             }
-            this.chatboxForURL.delete(url);
+            this.chatboxForURL.delete(aURL);
           }
           cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
           cb._callbacks = [];
           if (aCallback) {
             // _callbacks is a javascript property instead of a <field> as it
             // must exist before the (possibly delayed) bindings are created.
             cb._callbacks.push(aCallback);
           }
-
-          cb.remote = !!aOptions.remote;
           // src also a javascript property; the src attribute is set in the ctor.
-          cb.src = url;
-          if (mode == "minimized")
+          cb.src = aURL;
+          if (aMode == "minimized")
             cb.setAttribute("minimized", "true");
-          cb.setAttribute("origin", origin);
-          cb.setAttribute("label", title);
+          cb.setAttribute("origin", aOrigin);
+          cb.setAttribute("label", aTitle);
           this.insertBefore(cb, this.firstChild);
           this.selectedChat = cb;
-          this.chatboxForURL.set(url, Cu.getWeakReference(cb));
+          this.chatboxForURL.set(aURL, Cu.getWeakReference(cb));
           this.resize();
           return cb;
         ]]></body>
       </method>
 
       <method name="resize">
         <body><![CDATA[
         // Checks the current size against the collapsed state of children
@@ -770,28 +703,31 @@
             if (event.target != otherWin.document)
               return;
 
             if (aChatbox.hasAttribute("customSize")) {
               otherWin.document.getElementById("chat-window").
                 setAttribute("customSize", aChatbox.getAttribute("customSize"));
             }
 
+            let document = aChatbox.contentDocument;
+            let detachEvent = new aChatbox.contentWindow.CustomEvent("socialFrameDetached", {
+              bubbles: true,
+              cancelable: true,
+            });
+
             otherWin.removeEventListener("load", _chatLoad, true);
             let otherChatbox = otherWin.document.getElementById("chatter");
             aChatbox.setDecorationAttributes(otherChatbox);
             aChatbox.swapDocShells(otherChatbox);
-
             aChatbox.close();
             chatbar.chatboxForURL.set(aChatbox.src, Cu.getWeakReference(otherChatbox));
 
             // All processing is done, now we can fire the event.
-            otherChatbox.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
-              name: "socialFrameDetached"
-            });
+            document.dispatchEvent(detachEvent);
 
             deferred.resolve(otherChatbox);
           }, true);
           return deferred.promise;
         ]]></body>
       </method>
 
     </implementation>
--- a/browser/base/content/test/chat/browser.ini
+++ b/browser/base/content/test/chat/browser.ini
@@ -1,10 +1,9 @@
-[DEFAULT]
-skip-if = buildapp == 'mulet' || e10s
-support-files =
-  head.js
-  chat.html
-
-[browser_chatwindow.js]
-[browser_focus.js]
-[browser_tearoff.js]
-skip-if = true # Bug 1245805 - tearing off chat windows causes a browser crash.
+[DEFAULT]
+skip-if = buildapp == 'mulet' || e10s
+support-files =
+  head.js
+  chat.html
+
+[browser_chatwindow.js]
+[browser_focus.js]
+[browser_tearoff.js]
--- a/browser/base/content/test/chat/browser_chatwindow.js
+++ b/browser/base/content/test/chat/browser_chatwindow.js
@@ -20,17 +20,19 @@ add_chat_task(function* testOpenCloseCha
   Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
 
   chatbox.toggle();
   is(chatbox.minimized, true, "chat is now minimized");
   // was no other chat to select, so selected becomes null.
   is(chatbar.selectedChat, null);
 
   // We check the content gets an unload event as we close it.
+  let promiseClosed = promiseOneEvent(chatbox.content, "unload", true);
   chatbox.close();
+  yield promiseClosed;
 });
 
 // In this case we open a chat minimized, then request the same chat again
 // without specifying minimized.  On that second call the chat should open,
 // selected, and no longer minimized.
 add_chat_task(function* testMinimized() {
   let chatbox = yield promiseOpenChat("http://example.com", "minimized");
   Assert.strictEqual(chatbox, chatbar.selectedChat);
@@ -75,19 +77,18 @@ add_chat_task(function* testOpenTwiceCal
 add_chat_task(function* testOpenTwiceCallbacks() {
   yield promiseOpenChatCallback("http://example.com");
   yield promiseOpenChatCallback("http://example.com");
 });
 
 // Bug 817782 - check chats work in new top-level windows.
 add_chat_task(function* testSecondTopLevelWindow() {
   const chatUrl = "http://example.com";
-  let winPromise = BrowserTestUtils.waitForNewWindow();
-  OpenBrowserWindow();
-  let secondWindow = yield winPromise;
+  let secondWindow = OpenBrowserWindow();
+  yield promiseOneEvent(secondWindow, "load");
   yield promiseOpenChat(chatUrl);
   // the chat was created - let's make sure it was created in the second window.
   Assert.equal(numChatsInWindow(window), 0, "main window has no chats");
   Assert.equal(numChatsInWindow(secondWindow), 1, "second window has 1 chat");
   secondWindow.close();
 });
 
 // Test that findChromeWindowForChats() returns the expected window.
--- a/browser/base/content/test/chat/browser_focus.js
+++ b/browser/base/content/test/chat/browser_focus.js
@@ -46,60 +46,47 @@ add_chat_task(function* testDefaultFocus
   // we used the default focus behaviour, which means that because this was
   // not the direct result of user action the chat should not be focused.
   Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
   Assert.ok(isTabFocused(), "the tab should remain focused.");
   Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
 });
 
 // Test default focus via user input.
-add_chat_task(function* testDefaultFocusUserInput() {
-  todo(false, "BrowserTestUtils.synthesizeMouseAtCenter doesn't move the user " +
-    "focus to the chat window, even though we're recording a click correctly.");
-  return;
-
+add_chat_task(function* testDefaultFocus() {
   yield setUp();
-  let browser = gBrowser.selectedTab.linkedBrowser;
-  let mm = browser.messageManager;
-
+  let tab = gBrowser.selectedTab;
   let deferred = Promise.defer();
-  mm.addMessageListener("ChatOpenerClicked", function handler() {
-    mm.removeMessageListener("ChatOpenerClicked", handler);
-    promiseOpenChat("http://example.com").then(chat => deferred.resolve(chat));
-  });
-
-  yield ContentTask.spawn(browser, null, function* () {
-    let button = content.document.getElementById("chat-opener");
-    button.addEventListener("click", function onclick() {
-      button.removeEventListener("click", onclick);
-      sendAsyncMessage("ChatOpenerClicked");
-    });
-  });
+  let button = tab.linkedBrowser.contentDocument.getElementById("chat-opener");
+  button.addEventListener("click", function onclick() {
+    button.removeEventListener("click", onclick);
+    promiseOpenChat("http://example.com").then(
+      chat => deferred.resolve(chat)
+    );
+  })
   // Note we must use synthesizeMouseAtCenter() rather than calling
   // .click() directly as this causes nsIDOMWindowUtils.isHandlingUserInput
   // to be true.
-  yield BrowserTestUtils.synthesizeMouseAtCenter("#chat-opener", {}, browser);
+  EventUtils.synthesizeMouseAtCenter(button, {}, button.ownerDocument.defaultView);
   let chat = yield deferred.promise;
 
   // we use the default focus behaviour but the chat was opened via user input,
   // so the chat should be focused.
   Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-  yield promiseWaitForCondition(() => !isTabFocused());
   Assert.ok(!isTabFocused(), "the tab should have lost focus.");
   Assert.ok(isChatFocused(chat), "the chat should have got focus.");
 });
 
 // We explicitly ask for the chat to be focused.
 add_chat_task(function* testExplicitFocus() {
   yield setUp();
   let chat = yield promiseOpenChat("http://example.com", undefined, true);
   // we use the default focus behaviour, which means that because this was
   // not the direct result of user action the chat should not be focused.
   Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-  yield promiseWaitForCondition(() => !isTabFocused());
   Assert.ok(!isTabFocused(), "the tab should have lost focus.");
   Assert.ok(isChatFocused(chat), "the chat should have got focus.");
 });
 
 // Open a minimized chat via default focus behaviour - it will open and not
 // have focus.  Then open the same chat without 'minimized' - it will be
 // restored but should still not have grabbed focus.
 add_chat_task(function* testNoFocusOnAutoRestore() {
@@ -122,135 +109,127 @@ add_chat_task(function* testFocusOnExpli
   let chat = yield promiseOpenChat("http://example.com");
   Assert.ok(!chat.minimized, "chat should have been opened restored");
   Assert.ok(isTabFocused(), "the tab should remain focused.");
   Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
   chat.minimized = true;
   Assert.ok(isTabFocused(), "tab should still be focused");
   Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
 
-  let promise = promiseOneMessage(chat.content, "Social:FocusEnsured");
+  let promise = promiseOneEvent(chat.contentWindow, "focus");
   // pretend we clicked on the titlebar
   chat.onTitlebarClick({button: 0});
   yield promise; // wait for focus event.
   Assert.ok(!chat.minimized, "chat should have been restored");
   Assert.ok(isChatFocused(chat), "chat should be focused");
   Assert.strictEqual(chat, chatbar.selectedChat, "chat is marked selected");
 });
 
 // Open 2 chats and give 1 focus.  Minimize the focused one - the second
 // should get focus.
 add_chat_task(function* testMinimizeFocused() {
   yield setUp();
   let chat1 = yield promiseOpenChat("http://example.com#1");
   let chat2 = yield promiseOpenChat("http://example.com#2");
   Assert.equal(numChatsInWindow(window), 2, "2 chats open");
   Assert.strictEqual(chatbar.selectedChat, chat2, "chat2 is selected");
-  let promise = promiseOneMessage(chat1.content, "Social:FocusEnsured");
+  let promise = promiseOneEvent(chat1.contentWindow, "focus");
   chatbar.selectedChat = chat1;
   chatbar.focus();
   yield promise; // wait for chat1 to get focus.
   Assert.strictEqual(chat1, chatbar.selectedChat, "chat1 is marked selected");
   Assert.notStrictEqual(chat2, chatbar.selectedChat, "chat2 is not marked selected");
-
-  todo(false, "Bug 1245803 should re-enable the test below to have a chat window " +
-    "re-gain focus when another chat window is minimized.");
-  return;
-
-  promise = promiseOneMessage(chat2.content, "Social:FocusEnsured");
+  promise = promiseOneEvent(chat2.contentWindow, "focus");
   chat1.minimized = true;
   yield promise; // wait for chat2 to get focus.
   Assert.notStrictEqual(chat1, chatbar.selectedChat, "chat1 is not marked selected");
   Assert.strictEqual(chat2, chatbar.selectedChat, "chat2 is marked selected");
 });
 
 // Open 2 chats, select and focus the second.  Pressing the TAB key should
 // cause focus to move between all elements in our chat window before moving
 // to the next chat window.
 add_chat_task(function* testTab() {
   yield setUp();
 
   function sendTabAndWaitForFocus(chat, eltid) {
+    let doc = chat.contentDocument;
     EventUtils.sendKey("tab");
-
-    return ContentTask.spawn(chat.content, { eltid: eltid }, function* (args) {
-      let doc = content.document;
-
-      // ideally we would use the 'focus' event here, but that doesn't work
-      // as expected for the iframe - the iframe itself never gets the focus
-      // event (apparently the sub-document etc does.)
-      // So just poll for the correct element getting focus...
-      yield new Promise(function(resolve, reject) {
-        let tries = 0;
-        let interval = content.setInterval(function() {
-          if (tries >= 30) {
-            clearInterval(interval);
-            reject("never got focus");
-            return;
-          }
-          tries++;
-          let elt = args.eltid ? doc.getElementById(args.eltid) : doc.documentElement;
-          if (doc.activeElement == elt) {
-            content.clearInterval(interval);
-            resolve();
-          }
-          info("retrying wait for focus: " + tries);
-          info("(the active element is " + doc.activeElement + "/" +
-            doc.activeElement.getAttribute("id") + ")");
-        }, 100);
-        info("waiting for element " + args.eltid + " to get focus");
-      });
-    });
+    // ideally we would use the 'focus' event here, but that doesn't work
+    // as expected for the iframe - the iframe itself never gets the focus
+    // event (apparently the sub-document etc does.)
+    // So just poll for the correct element getting focus...
+    let deferred = Promise.defer();
+    let tries = 0;
+    let interval = setInterval(function() {
+      if (tries >= 30) {
+        clearInterval(interval);
+        deferred.reject("never got focus");
+        return;
+      }
+      tries ++;
+      let elt = eltid ? doc.getElementById(eltid) : doc.documentElement;
+      if (doc.activeElement == elt) {
+        clearInterval(interval);
+        deferred.resolve();
+      }
+      info("retrying wait for focus: " + tries);
+      info("(the active element is " + doc.activeElement + "/" + doc.activeElement.getAttribute("id") + ")");
+    }, 100);
+    info("waiting for element " + eltid + " to get focus");
+    return deferred.promise;
   }
 
   let chat1 = yield promiseOpenChat(CHAT_URL + "#1");
   let chat2 = yield promiseOpenChat(CHAT_URL + "#2");
   chatbar.selectedChat = chat2;
-  let promise = promiseOneMessage(chat2.content, "Social:FocusEnsured");
+  let promise = promiseOneEvent(chat2.contentWindow, "focus");
   chatbar.focus();
   info("waiting for second chat to get focus");
   yield promise;
 
   // Our chats have 3 focusable elements, so it takes 4 TABs to move
   // to the new chat.
   yield sendTabAndWaitForFocus(chat2, "input1");
+  Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "input1",
+               "first input field has focus");
   Assert.ok(isChatFocused(chat2), "new chat still focused after first tab");
 
   yield sendTabAndWaitForFocus(chat2, "input2");
   Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
+  Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "input2",
+               "second input field has focus");
 
   yield sendTabAndWaitForFocus(chat2, "iframe");
   Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
+  Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "iframe",
+               "iframe has focus");
 
   // this tab now should move to the next chat, but focus the
   // document element itself (hence the null eltid)
   yield sendTabAndWaitForFocus(chat1, null);
   Assert.ok(isChatFocused(chat1), "first chat is focused");
 });
 
 // Open a chat and focus an element other than the first. Move focus to some
 // other item (the tab itself in this case), then focus the chatbar - the
 // same element that was previously focused should still have focus.
 add_chat_task(function* testFocusedElement() {
   yield setUp();
 
   // open a chat with focus requested.
   let chat = yield promiseOpenChat(CHAT_URL, undefined, true);
 
-  yield ContentTask.spawn(chat.content, null, function* () {
-    content.document.getElementById("input2").focus();
-  });
+  chat.contentDocument.getElementById("input2").focus();
 
   // set focus to the tab.
   let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
   let promise = promiseOneEvent(tabb.contentWindow, "focus");
   Services.focus.moveFocus(tabb.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
   yield promise;
 
-  promise = promiseOneMessage(chat.content, "Social:FocusEnsured");
+  promise = promiseOneEvent(chat.contentWindow, "focus");
   chatbar.focus();
   yield promise;
 
-  yield ContentTask.spawn(chat.content, null, function* () {
-    is(content.document.activeElement.getAttribute("id"), "input2",
-      "correct input field still has focus");
-  });
+  Assert.equal(chat.contentDocument.activeElement.getAttribute("id"), "input2",
+               "correct input field still has focus");
 });
--- a/browser/base/content/test/chat/browser_tearoff.js
+++ b/browser/base/content/test/chat/browser_tearoff.js
@@ -14,44 +14,41 @@ function promiseNewWindowLoaded() {
     onOpenWindow: function(xulwindow) {
       var domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                             .getInterface(Components.interfaces.nsIDOMWindow);
       Services.wm.removeListener(this);
       // wait for load to ensure the window is ready for us to test
       domwindow.addEventListener("load", function _load(event) {
         let doc = domwindow.document;
         if (event.target != doc)
-          return;
+            return;
         domwindow.removeEventListener("load", _load);
         deferred.resolve(domwindow);
       });
     },
   });
   return deferred.promise;
 }
 
 add_chat_task(function* testTearoffChat() {
   let chatbox = yield promiseOpenChat("http://example.com");
   Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
 
-  let chatTitle = yield ContentTask.spawn(chatbox.content, null, function* () {
-    let chatDoc = content.document;
-
-    // Mutate the chat document a bit before we tear it off.
-    let div = chatDoc.createElement("div");
-    div.setAttribute("id", "testdiv");
-    div.setAttribute("test", "1");
-    chatDoc.body.appendChild(div);
-
-    return chatDoc.title;
-  });
+  let chatDoc = chatbox.contentDocument;
+  let chatTitle = chatDoc.title;
 
   Assert.equal(chatbox.getAttribute("label"), chatTitle,
                "the new chatbox should show the title of the chat window");
 
+  // mutate the chat document a bit before we tear it off.
+  let div = chatDoc.createElement("div");
+  div.setAttribute("id", "testdiv");
+  div.setAttribute("test", "1");
+  chatDoc.body.appendChild(div);
+
   // chatbox is open, lets detach. The new chat window will be caught in
   // the window watcher below
   let promise = promiseNewWindowLoaded();
 
   let swap = document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
   swap.click();
 
   // and wait for the new window.
@@ -59,38 +56,34 @@ add_chat_task(function* testTearoffChat(
 
   Assert.equal(domwindow.document.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
   Assert.equal(numChatsInWindow(window), 0, "should be no chats in the chat bar");
 
   // get the chatbox from the new window.
   chatbox = domwindow.document.getElementById("chatter")
   Assert.equal(chatbox.getAttribute("label"), chatTitle, "window should have same title as chat");
 
-  yield ContentTask.spawn(chatbox.content, null, function* () {
-    div = content.document.getElementById("testdiv");
-    is(div.getAttribute("test"), "1", "docshell should have been swapped");
-    div.setAttribute("test", "2");
-  });
+  div = chatbox.contentDocument.getElementById("testdiv");
+  Assert.equal(div.getAttribute("test"), "1", "docshell should have been swapped");
+  div.setAttribute("test", "2");
 
   // swap the window back to the chatbar
   promise = promiseOneEvent(domwindow, "unload");
   swap = domwindow.document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
   swap.click();
 
   yield promise;
 
   Assert.equal(numChatsInWindow(window), 1, "chat should be docked back in the window");
   chatbox = chatbar.selectedChat;
   Assert.equal(chatbox.getAttribute("label"), chatTitle,
                "the new chatbox should show the title of the chat window again");
 
-  yield ContentTask.spawn(chatbox.content, null, function* () {
-    let div = content.document.getElementById("testdiv");
-    is(div.getAttribute("test"), "2", "docshell should have been swapped");
-  });
+  div = chatbox.contentDocument.getElementById("testdiv");
+  Assert.equal(div.getAttribute("test"), "2", "docshell should have been swapped");
 });
 
 // Similar test but with 2 chats.
 add_chat_task(function* testReattachTwice() {
   let chatbox1 = yield promiseOpenChat("http://example.com#1");
   let chatbox2 = yield promiseOpenChat("http://example.com#2");
   Assert.equal(numChatsInWindow(window), 2, "both chats should be docked in the window");
 
--- a/browser/base/content/test/chat/head.js
+++ b/browser/base/content/test/chat/head.js
@@ -10,73 +10,58 @@ const kDefaultButtonSet = new Set(["mini
 function promiseOpenChat(url, mode, focus, buttonSet = null) {
   let uri = Services.io.newURI(url, null, null);
   let origin = uri.prePath;
   let title = origin;
   let deferred = Promise.defer();
   // we just through a few hoops to ensure the content document is fully
   // loaded, otherwise tests that rely on that content may intermittently fail.
   let callback = function(chatbox) {
-    let mm = chatbox.content.messageManager;
-    mm.sendAsyncMessage("WaitForDOMContentLoaded");
-    mm.addMessageListener("DOMContentLoaded", function cb() {
-      mm.removeMessageListener("DOMContentLoaded", cb);
+    if (chatbox.contentDocument.readyState == "complete") {
+      // already loaded.
       deferred.resolve(chatbox);
-    });
+      return;
+    }
+    chatbox.addEventListener("load", function onload(event) {
+      if (event.target != chatbox.contentDocument || chatbox.contentDocument.location.href == "about:blank") {
+        return;
+      }
+      chatbox.removeEventListener("load", onload, true);
+      deferred.resolve(chatbox);
+    }, true);
   }
-  let chatbox = Chat.open(null, {
-    origin: origin,
-    title: title,
-    url: url,
-    mode: mode,
-    focus: focus
-  }, callback);
+  let chatbox = Chat.open(null, origin, title, url, mode, focus, callback);
   if (buttonSet) {
     chatbox.setAttribute("buttonSet", buttonSet);
   }
   return deferred.promise;
 }
 
 // Opens a chat, returns a promise resolved when the chat callback fired.
 function promiseOpenChatCallback(url, mode) {
   let uri = Services.io.newURI(url, null, null);
   let origin = uri.prePath;
   let title = origin;
   let deferred = Promise.defer();
   let callback = deferred.resolve;
-  Chat.open(null, {
-    origin: origin,
-    title: title,
-    url: url,
-    mode: mode
-  }, callback);
+  Chat.open(null, origin, title, url, mode, undefined, callback);
   return deferred.promise;
 }
 
 // Opens a chat, returns the chat window's promise which fires when the chat
 // starts loading.
 function promiseOneEvent(target, eventName, capture) {
   let deferred = Promise.defer();
   target.addEventListener(eventName, function handler(event) {
     target.removeEventListener(eventName, handler, capture);
     deferred.resolve();
   }, capture);
   return deferred.promise;
 }
 
-function promiseOneMessage(target, messageName) {
-  return new Promise(resolve => {
-    let mm = target.messageManager;
-    mm.addMessageListener(messageName, function handler() {
-      mm.removeMessageListener(messageName, handler);
-      resolve();
-    });
-  });
-}
-
 // Return the number of chats in a browser window.
 function numChatsInWindow(win) {
   let chatbar = win.document.getElementById("pinnedchats");
   return chatbar.childElementCount;
 }
 
 function promiseWaitForFocus() {
   let deferred = Promise.defer();
@@ -103,36 +88,8 @@ function add_chat_task(genFunction) {
         if (win.closed) {
           continue;
         }
         win.close();
       }
     }
   });
 }
-
-function waitForCondition(condition, nextTest, errorMsg) {
-  var tries = 0;
-  var interval = setInterval(function() {
-    if (tries >= 100) {
-      ok(false, errorMsg);
-      moveOn();
-    }
-    var conditionPassed;
-    try {
-      conditionPassed = condition();
-    } catch (e) {
-      ok(false, e + "\n" + e.stack);
-      conditionPassed = false;
-    }
-    if (conditionPassed) {
-      moveOn();
-    }
-    tries++;
-  }, 100);
-  var moveOn = function() { clearInterval(interval); nextTest(); };
-}
-
-function promiseWaitForCondition(aConditionFn) {
-  return new Promise((resolve, reject) => {
-    waitForCondition(aConditionFn, resolve, "Condition didn't pass.");
-  });
-}
--- a/browser/base/content/test/social/browser.ini
+++ b/browser/base/content/test/social/browser.ini
@@ -32,30 +32,27 @@ support-files =
 skip-if = e10s && debug # Leaking docshells (bug 1150147)
 [browser_blocklist.js]
 skip-if = e10s && debug # Leaking docshells (bug 1150147)
 [browser_share.js]
 skip-if = true # bug 1115131
 [browser_social_activation.js]
 skip-if = e10s && debug # e10s/Linux/Debug Leaking docshells (bug 1150147)
 [browser_social_chatwindow.js]
-skip-if = true # Bug 1245798 'document-element-inserted' is not fired for chat windows anymore, so no mozSocial
 [browser_social_chatwindow_resize.js]
-skip-if = true # Bug 1245798 'document-element-inserted' is not fired for chat windows anymore, so no mozSocial
 [browser_social_chatwindowfocus.js]
-skip-if = true # Bug 1245798 'document-element-inserted' is not fired for chat windows anymore, so no mozSocial
+skip-if = e10s # tab crash on data url used in this test
 [browser_social_contextmenu.js]
 skip-if = (os == 'linux' && e10s) # Bug 1072669 context menu relies on target element
 [browser_social_errorPage.js]
 [browser_social_flyout.js]
 [browser_social_isVisible.js]
 [browser_social_marks.js]
 [browser_social_marks_context.js]
 [browser_social_multiprovider.js]
 [browser_social_multiworker.js]
 [browser_social_perwindowPB.js]
 [browser_social_sidebar.js]
 [browser_social_status.js]
-skip-if = true # Bug 1245800 'onoffline' and 'ononline' not defined JS errors
 [browser_social_window.js]
 [browser_social_workercrash.js]
 #skip-if = !crashreporter
 skip-if = true # Bug 1060813 - frequent leaks on all platforms
--- a/browser/base/content/test/social/browser_social_chatwindowfocus.js
+++ b/browser/base/content/test/social/browser_social_chatwindowfocus.js
@@ -35,22 +35,23 @@ function openChatViaWorkerMessage(port, 
   let chatbar = getChatBar();
   let numExpected = chatbar.childElementCount + 1;
   port.postMessage({topic: "test-worker-chat", data: data});
   waitForCondition(() => chatbar.childElementCount == numExpected,
                    function() {
                       // so the child has been added, but we don't know if it
                       // has been intialized - re-request it and the callback
                       // means it's done.  Minimized, same as the worker.
-                      chatbar.openChat({
-                        origin: SocialSidebar.provider.origin,
-                        title: SocialSidebar.provider.name,
-                        url: data,
-                        mode: "minimized"
-                      }, function() { callback(); });
+                      chatbar.openChat(SocialSidebar.provider.origin,
+                                       SocialSidebar.provider.name,
+                                       data,
+                                       "minimized",
+                                       function() {
+                                          callback();
+                                       });
                    },
                    "No new chat appeared");
 }
 
 
 var isSidebarLoaded = false;
 
 function startTestAndWaitForSidebar(callback) {
--- a/browser/base/content/test/social/browser_social_errorPage.js
+++ b/browser/base/content/test/social/browser_social_errorPage.js
@@ -117,20 +117,16 @@ var tests = {
             }
           );
         }
       );
     });
   },
 
   testChatWindow: function(next) {
-    todo(false, "Bug 1245799 is needed to make error pages work again for chat windows.");
-    next();
-    return;
-
     let panelCallbackCount = 0;
     // chatwindow tests throw errors, which muddy test output, if the worker
     // doesn't get test-init
     goOffline().then(function() {
       openChat(
         manifest.sidebarURL, /* empty html page */
         function() { // the panel api callback
           panelCallbackCount++;
@@ -145,20 +141,16 @@ var tests = {
                             },
                            "error page didn't appear");
         }
       );
     });
   },
 
   testChatWindowAfterTearOff: function(next) {
-    todo(false, "Bug 1245799 is needed to make error pages work again for chat windows.");
-    next();
-    return;
-
     // Ensure that the error listener survives the chat window being detached.
     let url = manifest.sidebarURL; /* empty html page */
     let panelCallbackCount = 0;
     // chatwindow tests throw errors, which muddy test output, if the worker
     // doesn't get test-init
     // open a chat while we are still online.
     openChat(
       url,
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -492,27 +492,22 @@ function makeChat(mode, uniqueid, cb) {
   let chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
   // chatURL is not a part of the provider class, but is added by tests if we
   // want to use a specific url (different than above) for testing
   if (provider.chatURL) {
     chatUrl = provider.chatURL;
   }
   // Note that we use promiseChatLoaded instead of the callback to ensure the
   // content has started loading.
-  let chatbox = getChatBar().openChat({
-    origin: provider.origin,
-    title: provider.name,url: chatUrl + "?id=" + uniqueid,
-    mode: mode
-  });
+  let chatbox = getChatBar().openChat(provider.origin, provider.name,
+                                      chatUrl + "?id=" + uniqueid, mode);
   chatbox.promiseChatLoaded.then(
     () => {
     info("chat window has opened");
-    chatbox.content.messageManager.sendAsyncMessage("Social:SetDocumentTitle", {
-      title: uniqueid
-    });
+    chatbox.contentDocument.title = uniqueid;
     cb();
   });
 }
 
 function checkPopup() {
   // popup only showing if any collapsed popup children.
   let chatbar = getChatBar();
   let numCollapsed = 0;
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -4,19 +4,17 @@
 "use strict";
 
 /* exported startup, shutdown, install, uninstall */
 
 const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
 
 const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const kBrowserSharingNotificationId = "loop-sharing-notification";
-
-const MIN_CURSOR_DELTA = 3;
-const MIN_CURSOR_INTERVAL = 100;
+const kPrefBrowserSharingInfoBar = "browserSharing.showInfoBar";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
@@ -90,87 +88,119 @@ var WindowListener = {
         });
       },
 
       /**
        * Toggle between opening or hiding the Loop panel.
        *
        * @param {DOMEvent} [event] Optional event that triggered the call to this
        *                           function.
+       * @param {String}   [tabId] Optional name of the tab to select after the panel
+       *                           has opened. Does nothing when the panel is hidden.
        * @return {Promise}
        */
-      togglePanel: function(event) {
+      togglePanel: function(event, tabId = null) {
         if (!this.panel) {
           // We're on the hidden window! What fun!
           let obs = win => {
             Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
-            win.LoopUI.togglePanel(event);
+            win.LoopUI.togglePanel(event, tabId);
           };
           Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
           return window.OpenBrowserWindow();
         }
         if (this.panel.state == "open") {
           return new Promise(resolve => {
             this.panel.hidePopup();
             resolve();
           });
         }
 
-        return this.openPanel(event).then(mm => {
-          if (mm) {
-            mm.sendAsyncMessage("Social:EnsureFocusElement");
-          }
+        return this.openCallPanel(event, tabId).then(doc => {
+          let fm = Services.focus;
+          fm.moveFocus(doc.defaultView, null, fm.MOVEFOCUS_FIRST, fm.FLAG_NOSCROLL);
         }).catch(err => {
           Cu.reportError(err);
         });
       },
 
       /**
        * Opens the panel for Loop and sizes it appropriately.
        *
        * @param {event}  event   The event opening the panel, used to anchor
        *                         the panel to the button which triggers it.
+       * @param {String} [tabId] Identifier of the tab to select when the panel is
+       *                         opened. Example: 'rooms', 'contacts', etc.
        * @return {Promise}
        */
-      openPanel: function(event) {
+      openCallPanel: function(event, tabId = null) {
         return new Promise((resolve) => {
           let callback = iframe => {
-            let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
-            if (!("messageManager" in iframe)) {
-              iframe.messageManager = mm;
+            // Helper function to show a specific tab view in the panel.
+            function showTab() {
+              if (!tabId) {
+                resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
+                return;
+              }
+
+              let win = iframe.contentWindow;
+              let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
+                detail: {
+                  action: "selectTab",
+                  tab: tabId
+                }
+              }, win));
+              win.dispatchEvent(ev);
+              resolve(LoopUI.promiseDocumentVisible(iframe.contentDocument));
             }
-            this.hookWindowCloseForPanelClose(iframe);
+
+            // If the panel has been opened and initialized before, we can skip waiting
+            // for the content to load - because it's already there.
+            if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
+              showTab();
+              return;
+            }
 
-            mm.sendAsyncMessage("Social:WaitForDocumentVisible");
-            mm.addMessageListener("Social:DocumentVisible", () => resolve(mm));
+            let documentDOMLoaded = () => {
+              iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
+              // Handle window.close correctly on the panel.
+              this.hookWindowCloseForPanelClose(iframe.contentWindow);
+              iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
+                iframe.contentWindow.removeEventListener("loopPanelInitialized",
+                  loopPanelInitialized);
+                showTab();
+              });
+            };
+            iframe.addEventListener("DOMContentLoaded", documentDOMLoaded, true);
           };
 
           // Used to clear the temporary "login" state from the button.
           Services.obs.notifyObservers(null, "loop-status-changed", null);
 
           this.shouldResumeTour().then((resume) => {
             if (resume) {
               // Assume the conversation with the visitor wasn't open since we would
               // have resumed the tour as soon as the visitor joined if it was (and
               // the pref would have been set to false already.
               this.MozLoopService.resumeTour("waiting");
-              resolve(null);
+              resolve();
               return;
             }
 
             this.LoopAPI.initialize();
 
             let anchor = event ? event.target : this.toolbarButton.anchor;
-            this.PanelFrame.showPopup(
-              window,
-              anchor,
-              "loop", // Notification Panel Type
-              null,   // Origin
-              "about:looppanel", // Source
-              null, // Size
+            let setHeight = 410;
+            if (gBrowser.selectedBrowser.getAttribute("remote") === "true") {
+              setHeight = 262;
+            }
+            this.PanelFrame.showPopup(window, anchor,
+              "loop", null, "about:looppanel",
+              // Loop wants a fixed size for the panel. This also stops it dynamically resizing.
+              { width: 330, height: setHeight },
               callback);
           });
         });
       },
 
       /**
        * Method to know whether actions to open the panel should instead resume the tour.
        *
@@ -411,17 +441,17 @@ var WindowListener = {
 
           // We need a setTimeout here, otherwise the panel won't show after the
           // window received focus.
           window.setTimeout(() => {
             if (typeof options.onclick == "function") {
               options.onclick();
             } else {
               // Open the Loop panel as a default action.
-              this.openPanel(null, options.selectTab || null);
+              this.openCallPanel(null, options.selectTab || null);
             }
           }, 0);
         });
       },
 
       /**
        * Play a sound in this window IF there's no sound playing yet.
        *
@@ -451,20 +481,16 @@ var WindowListener = {
         if (!this._listeningToTabSelect) {
           gBrowser.tabContainer.addEventListener("TabSelect", this);
           this._listeningToTabSelect = true;
 
           // Watch for title changes as opposed to location changes as more
           // metadata about the page is available when this event fires.
           gBrowser.addEventListener("DOMTitleChanged", this);
           this._browserSharePaused = false;
-
-          // Add this event to the parent gBrowser to avoid adding and removing
-          // it for each individual tab's browsers.
-          gBrowser.addEventListener("mousemove", this);
         }
 
         this._maybeShowBrowserSharingInfoBar();
 
         // Get the first window Id for the listener.
         this.LoopAPI.broadcastPushMessage("BrowserSwitch",
           gBrowser.selectedBrowser.outerWindowID);
       },
@@ -475,17 +501,16 @@ var WindowListener = {
       stopBrowserSharing: function() {
         if (!this._listeningToTabSelect) {
           return;
         }
 
         this._hideBrowserSharingInfoBar();
         gBrowser.tabContainer.removeEventListener("TabSelect", this);
         gBrowser.removeEventListener("DOMTitleChanged", this);
-        gBrowser.removeEventListener("mousemove", this);
         this._listeningToTabSelect = false;
         this._browserSharePaused = false;
       },
 
       /**
        * Helper function to fetch a localized string via the MozLoopService API.
        * It's currently inconveniently wrapped inside a string of stringified JSON.
        *
@@ -503,54 +528,60 @@ 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();
 
+        // Don't show the infobar if it's been permanently disabled from the menu.
+        if (!this.MozLoopService.getLoopPref(kPrefBrowserSharingInfoBar)) {
+          return;
+        }
+
         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 pauseButtonLabel = this._getString(this._browserSharePaused ?
+                                               "infobar_button_resume_label" :
+                                               "infobar_button_pause_label");
+        let pauseButtonAccessKey = this._getString(this._browserSharePaused ?
+                                                   "infobar_button_resume_accesskey" :
+                                                   "infobar_button_pause_accesskey");
+        let barLabel = this._getString(this._browserSharePaused ?
+                                       "infobar_screenshare_paused_browser_message" :
+                                       "infobar_screenshare_browser_message2");
         let bar = box.appendNotification(
-          initStrings.message,
+          barLabel,
           kBrowserSharingNotificationId,
           // Icon is defined in browser theme CSS.
           null,
           box.PRIORITY_WARNING_LOW,
           [{
-            label: initStrings.label,
-            accessKey: initStrings.accessKey,
+            label: pauseButtonLabel,
+            accessKey: pauseButtonAccessKey,
             isDefault: false,
             callback: (event, buttonInfo, buttonNode) => {
               this._browserSharePaused = !this._browserSharePaused;
-              let stringObj = this._browserSharePaused ? pausedStrings : unpausedStrings;
-              bar.label = stringObj.message;
+              bar.label = this._getString(this._browserSharePaused ?
+                                          "infobar_screenshare_paused_browser_message" :
+                                          "infobar_screenshare_browser_message2");
               bar.classList.toggle("paused", this._browserSharePaused);
-              buttonNode.label = stringObj.label;
-              buttonNode.accessKey = stringObj.accesskey;
-              LoopUI.MozLoopService.toggleBrowserSharing(this._browserSharePaused);
+              buttonNode.label = this._getString(this._browserSharePaused ?
+                                                 "infobar_button_resume_label" :
+                                                 "infobar_button_pause_label");
+              buttonNode.accessKey = this._getString(this._browserSharePaused ?
+                                                     "infobar_button_resume_accesskey" :
+                                                     "infobar_button_pause_accesskey");
               return true;
             },
             type: "pause"
           },
           {
-            label: this._getString("infobar_button_disconnect_label"),
-            accessKey: this._getString("infobar_button_disconnect_accesskey"),
+            label: this._getString("infobar_button_stop_label"),
+            accessKey: this._getString("infobar_button_stop_accesskey"),
             isDefault: true,
             callback: () => {
               this._hideBrowserSharingInfoBar();
               LoopUI.MozLoopService.hangupAllChatWindows();
             },
             type: "stop"
           }]
         );
@@ -560,38 +591,41 @@ var WindowListener = {
 
         // Keep showing the notification bar until the user explicitly closes it.
         bar.persistence = -1;
       },
 
       /**
        * Hides the infobar, permanantly if requested.
        *
-       * @param   {Object}  browser Optional link to the browser we want to
-       *                    remove the infobar from. If not present, defaults
-       *                    to current browser instance.
-       * @return  {Boolean} |true| if the infobar was hidden here.
+       * @param {Boolean} permanently Flag that determines if the infobar will never
+       *                              been shown again. Defaults to `false`.
+       * @return {Boolean} |true| if the infobar was hidden here.
        */
-      _hideBrowserSharingInfoBar: function(browser) {
+      _hideBrowserSharingInfoBar: function(permanently = false, browser) {
         browser = browser || gBrowser.selectedBrowser;
         let box = gBrowser.getNotificationBox(browser);
         let notification = box.getNotificationWithValue(kBrowserSharingNotificationId);
         let removed = false;
         if (notification) {
           box.removeNotification(notification);
           removed = true;
         }
 
+        if (permanently) {
+          this.MozLoopService.setLoopPref(kPrefBrowserSharingInfoBar, false);
+        }
+
         return removed;
       },
 
       /**
        * Broadcast 'BrowserSwitch' event.
-       */
-      _notifyBrowserSwitch: function() {
+      */
+      _notifyBrowserSwitch() {
          // Get the first window Id for the listener.
         this.LoopAPI.broadcastPushMessage("BrowserSwitch",
           gBrowser.selectedBrowser.outerWindowID);
       },
 
       /**
        * Handles events from gBrowser.
        */
@@ -600,72 +634,32 @@ var WindowListener = {
           case "DOMTitleChanged":
             // Get the new title of the shared tab
             this._notifyBrowserSwitch();
             break;
           case "TabSelect":
             let wasVisible = false;
             // Hide the infobar from the previous tab.
             if (event.detail.previousTab) {
-              wasVisible = this._hideBrowserSharingInfoBar(event.detail.previousTab.linkedBrowser);
+              wasVisible = this._hideBrowserSharingInfoBar(false, event.detail.previousTab.linkedBrowser);
             }
 
             // We've changed the tab, so get the new window id.
             this._notifyBrowserSwitch();
 
             if (wasVisible) {
               // If the infobar was visible before, we should show it again after the
               // switch.
               this._maybeShowBrowserSharingInfoBar();
             }
             break;
-          case "mousemove":
-            this.handleMousemove(event);
-            break;
           }
       },
 
       /**
-       * 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) {
-          return;
-        }
-
-        // Only update every so often.
-        let now = Date.now();
-        if (now - this.lastCursorTime < MIN_CURSOR_INTERVAL) {
-          return;
-        }
-        this.lastCursorTime = now;
-
-        // Skip the update if cursor is out of bounds or didn't move much.
-        let browserBox = gBrowser.selectedBrowser.boxObject;
-        let deltaX = event.screenX - browserBox.screenX;
-        let deltaY = event.screenY - browserBox.screenY;
-        if (deltaX < 0 || deltaX > browserBox.width ||
-            deltaY < 0 || deltaY > browserBox.height ||
-            (Math.abs(deltaX - this.lastCursorX) < MIN_CURSOR_DELTA &&
-             Math.abs(deltaY - this.lastCursorY) < MIN_CURSOR_DELTA)) {
-          return;
-        }
-        this.lastCursorX = deltaX;
-        this.lastCursorY = deltaY;
-
-        this.LoopAPI.broadcastPushMessage("CursorPositionChange", {
-          ratioX: deltaX / browserBox.width,
-          ratioY: deltaY / browserBox.height
-        });
-      },
-
-      /**
        * Fetch the favicon of the currently selected tab in the format of a data-uri.
        *
        * @param  {Function} callback Function to be invoked with an error object as
        *                             its first argument when an error occurred or
        *                             a string as second argument when the favicon
        *                             has been fetched.
        */
       getFavicon: function(callback) {
--- a/browser/extensions/loop/chrome/content/modules/LoopRooms.jsm
+++ b/browser/extensions/loop/chrome/content/modules/LoopRooms.jsm
@@ -18,17 +18,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
                                   "resource://gre/modules/WebChannel.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
   const { EventEmitter } = Cu.import("resource://devtools/shared/event-emitter.js", {});
   return new EventEmitter();
 });
 XPCOMUtils.defineLazyGetter(this, "gLoopBundle", function() {
-  return Services.strings.createBundle("chrome://loop/locale/loop.properties");
+  return Services.strings.createBundle("chrome://browser/locale/loop/loop.properties");
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoopRoomsCache",
   "chrome://loop/content/modules/LoopRoomsCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "loopUtils",
   "chrome://loop/content/modules/utils.js", "utils");
 XPCOMUtils.defineLazyModuleGetter(this, "loopCrypto",
   "chrome://loop/content/shared/js/crypto.js", "LoopCrypto");
--- a/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
@@ -8,18 +8,16 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("chrome://loop/content/modules/MozLoopService.jsm");
 Cu.import("chrome://loop/content/modules/LoopRooms.jsm");
 Cu.importGlobalProperties(["Blob"]);
 
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabURL",
-                                        "resource:///modules/NewTabURL.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
                                         "resource://gre/modules/PageMetadata.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                         "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                         "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITour",
                                         "resource:///modules/UITour.jsm");
@@ -127,16 +125,17 @@ const updateSocialProvidersCache = funct
 var gAppVersionInfo = null;
 var gBrowserSharingListeners = new Set();
 var gBrowserSharingWindows = new Set();
 var gPageListeners = null;
 var gOriginalPageListeners = null;
 var gSocialProviders = null;
 var gStringBundle = null;
 var gStubbedMessageHandlers = null;
+var gOriginalPanelHeight = null;
 const kBatchMessage = "Batch";
 const kMaxLoopCount = 10;
 const kMessageName = "Loop:Message";
 const kPushMessageName = "Loop:Message:Push";
 const kPushSubscription = "pushSubscription";
 const kRoomsPushPrefix = "Rooms:";
 const kMessageHandlers = {
   /**
@@ -158,33 +157,29 @@ const kMessageHandlers = {
     if (!win || !browser) {
       // This may happen when an undocked conversation window is the only
       // window left.
       let err = new Error("No tabs available to share.");
       MozLoopService.log.error(err);
       reply(cloneableError(err));
       return;
     }
-
-    let autoStart = MozLoopService.getLoopPref("remote.autostart");
-    if (!autoStart && browser.getAttribute("remote") == "true") {
-      // Tab sharing might not be supported yet for e10s-enabled browsers.
+    if (browser.getAttribute("remote") == "true") {
+      // Tab sharing is not supported yet for e10s-enabled browsers. This will
+      // be fixed in bug 1137634.
       let err = new Error("Tab sharing is not supported for e10s-enabled browsers");
       MozLoopService.log.error(err);
       reply(cloneableError(err));
       return;
     }
 
     let [windowId] = message.data;
 
     win.LoopUI.startBrowserSharing();
 
-    // Point new tab to load about:home to avoid accidentally sharing top sites.
-    NewTabURL.override("about:home");
-
     gBrowserSharingWindows.add(Cu.getWeakReference(win));
     gBrowserSharingListeners.add(windowId);
     reply();
   },
 
   /**
    * Associates a session-id and a call-id with a window for debugging.
    *
@@ -361,19 +356,21 @@ const kMessageHandlers = {
    *                           [ ]
    * @param {Function} reply   Callback function, invoked with the result of this
    *                           message handler. The result will be sent back to
    *                           the senders' channel.
    */
   GetAllConstants: function(message, reply) {
     reply({
       LOOP_SESSION_TYPE: LOOP_SESSION_TYPE,
+      ROOM_CONTEXT_ADD: ROOM_CONTEXT_ADD,
       ROOM_CREATE: ROOM_CREATE,
       ROOM_DELETE: ROOM_DELETE,
       SHARING_ROOM_URL: SHARING_ROOM_URL,
+      SHARING_STATE_CHANGE: SHARING_STATE_CHANGE,
       TWO_WAY_MEDIA_CONN_LENGTH: TWO_WAY_MEDIA_CONN_LENGTH
     });
   },
 
   /**
    * Returns the app version information for use during feedback.
    *
    * @param {Object}   message Message meant for the handler function, containing
@@ -702,17 +699,17 @@ const kMessageHandlers = {
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           []
    * @param {Function} reply   Callback function, invoked with the result of this
    *                           message handler. The result will be sent back to
    *                           the senders' channel.
    */
-  IsMultiProcessActive: function(message, reply) {
+  IsMultiProcessEnabled: function(message, reply) {
     let win = Services.wm.getMostRecentWindow("navigator:browser");
     let browser = win && win.gBrowser.selectedBrowser;
     reply(!!(browser && browser.getAttribute("remote") == "true"));
   },
 
   /**
    * Start the FxA login flow using the OAuth client and params from the Loop
    * server.
@@ -871,18 +868,16 @@ const kMessageHandlers = {
     for (let win of gBrowserSharingWindows) {
       win = win.get();
       if (!win) {
         continue;
       }
       win.LoopUI.stopBrowserSharing();
     }
 
-    NewTabURL.reset();
-
     gBrowserSharingWindows.clear();
     reply();
   },
 
   "Rooms:*": function(action, message, reply) {
     LoopAPIInternal.handleObjectAPIMessage(LoopRooms, kRoomsPushPrefix,
       action, message, reply);
   },
@@ -922,16 +917,39 @@ const kMessageHandlers = {
    */
   SetLoopPref: function(message, reply) {
     let [prefName, value, prefType] = message.data;
     MozLoopService.setLoopPref(prefName, value, prefType);
     reply();
   },
 
   /**
+   * Set panel height
+   *
+   * @param {Object}   message Message meant for the handler function, containing
+   *                           the following parameters in its `data` property:
+   *                           [
+   *                             {Number} height The pixel height value.
+   *                           ]
+   * @param {Function} reply   Callback function, invoked with the result of this
+   *                           message handler. The result will be sent back to
+   *                           the senders' channel.
+   */
+  SetPanelHeight: function(message, reply) {
+    let [height] = message.data;
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    let node = win.LoopUI.browser;
+    if (!gOriginalPanelHeight) {
+      gOriginalPanelHeight = parseInt(win.getComputedStyle(node, null).height, 10);
+    }
+    node.style.height = (height || gOriginalPanelHeight) + "px";
+    reply();
+  },
+
+  /**
    * Used to record the screen sharing state for a window so that it can
    * be reflected on the toolbar button.
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           [
    *                             {String} windowId The id of the conversation window
    *                                               the state is being changed for.
--- a/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
@@ -21,16 +21,29 @@ const LOOP_SESSION_TYPE = {
 const TWO_WAY_MEDIA_CONN_LENGTH = {
   SHORTER_THAN_10S: 0,
   BETWEEN_10S_AND_30S: 1,
   BETWEEN_30S_AND_5M: 2,
   MORE_THAN_5M: 3
 };
 
 /**
+ * Values that we segment sharing state change telemetry probes into.
+ *
+ * @type {{WINDOW_ENABLED: Number, WINDOW_DISABLED: Number,
+ *   BROWSER_ENABLED: Number, BROWSER_DISABLED: Number}}
+ */
+const SHARING_STATE_CHANGE = {
+  WINDOW_ENABLED: 0,
+  WINDOW_DISABLED: 1,
+  BROWSER_ENABLED: 2,
+  BROWSER_DISABLED: 3
+};
+
+/**
  * Values that we segment sharing a room URL action telemetry probes into.
  *
  * @type {{COPY_FROM_PANEL: Number, COPY_FROM_CONVERSATION: Number,
  *   EMAIL_FROM_CALLFAILED: Number, EMAIL_FROM_CONVERSATION: Number}}
  */
 const SHARING_ROOM_URL = {
   COPY_FROM_PANEL: 0,
   COPY_FROM_CONVERSATION: 1,
@@ -54,46 +67,60 @@ const ROOM_CREATE = {
  *
  * @type {{DELETE_SUCCESS: Number, DELETE_FAIL: Number}}
  */
 const ROOM_DELETE = {
   DELETE_SUCCESS: 0,
   DELETE_FAIL: 1
 };
 
+/**
+ * Values that we segment room context action telemetry probes into.
+ *
+ * @type {{ADD_FROM_PANEL: Number, ADD_FROM_CONVERSATION: Number}}
+ */
+const ROOM_CONTEXT_ADD = {
+  ADD_FROM_PANEL: 0,
+  ADD_FROM_CONVERSATION: 1
+};
+
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL = "loop.debug.loglevel";
 
 const kChatboxHangupButton = {
   id: "loop-hangup",
   visibleWhenUndocked: false,
   onCommand: function(e, chatbox) {
-    let mm = chatbox.content.messageManager;
-    mm.sendAsyncMessage("Social:CustomEvent", { name: "LoopHangupNow" });
+    let window = chatbox.content.contentWindow;
+    let event = new window.CustomEvent("LoopHangupNow");
+    window.dispatchEvent(event);
   }
 };
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
 this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE",
-  "TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_ROOM_URL", "ROOM_CREATE", "ROOM_DELETE"];
+  "TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_STATE_CHANGE", "SHARING_ROOM_URL",
+  "ROOM_CREATE", "ROOM_DELETE", "ROOM_CONTEXT_ADD"];
 
 XPCOMUtils.defineConstant(this, "LOOP_SESSION_TYPE", LOOP_SESSION_TYPE);
 XPCOMUtils.defineConstant(this, "TWO_WAY_MEDIA_CONN_LENGTH", TWO_WAY_MEDIA_CONN_LENGTH);
+XPCOMUtils.defineConstant(this, "SHARING_STATE_CHANGE", SHARING_STATE_CHANGE);
 XPCOMUtils.defineConstant(this, "SHARING_ROOM_URL", SHARING_ROOM_URL);
 XPCOMUtils.defineConstant(this, "ROOM_CREATE", ROOM_CREATE);
 XPCOMUtils.defineConstant(this, "ROOM_DELETE", ROOM_DELETE);
+XPCOMUtils.defineConstant(this, "ROOM_CONTEXT_ADD", ROOM_CONTEXT_ADD);
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoopAPI",
   "chrome://loop/content/modules/MozLoopAPI.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
   "resource://gre/modules/media/RTCStatsReport.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "loopUtils",
   "chrome://loop/content/modules/utils.js", "utils");
@@ -738,54 +765,43 @@ var MozLoopServiceInternal = {
    *
    * @returns {Map} a map of element ids with localized string values
    */
   get localizedStrings() {
     if (gLocalizedStrings.size) {
       return gLocalizedStrings;
     }
 
-    // Load all strings from a bundle location preferring strings loaded later.
-    function loadAllStrings(location) {
-      let bundle = Services.strings.createBundle(location);
-      let enumerator = bundle.getSimpleEnumeration();
-      while (enumerator.hasMoreElements()) {
-        let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
-        gLocalizedStrings.set(string.key, string.value);
-      }
+    let stringBundle =
+      Services.strings.createBundle("chrome://browser/locale/loop/loop.properties");
+
+    let enumerator = stringBundle.getSimpleEnumeration();
+    while (enumerator.hasMoreElements()) {
+      let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+      gLocalizedStrings.set(string.key, string.value);
     }
-
-    // Load fallback/en-US strings then prefer the localized ones if available.
-    loadAllStrings("chrome://loop-locale-fallback/content/loop.properties");
-    loadAllStrings("chrome://loop/locale/loop.properties");
-
     // Supply the strings from the branding bundle on a per-need basis.
     let brandBundle =
       Services.strings.createBundle("chrome://branding/locale/brand.properties");
     // Unfortunately the `brandShortName` string is used by Loop with a lowercase 'N'.
     gLocalizedStrings.set("brandShortname", brandBundle.GetStringFromName("brandShortName"));
 
     return gLocalizedStrings;
   },
 
   /**
    * Saves loop logs to the saved-telemetry-pings folder.
    *
-   * @param {nsIDOMWindow} window The window object which can be communicated with
-   * @param {Object}        The peerConnection in question.
+   * @param {Object} pc The peerConnection in question.
    */
-  stageForTelemetryUpload: function(window, details) {
-    let mm = window.messageManager;
-    mm.addMessageListener("Loop:GetAllWebrtcStats", function getAllStats(message) {
-      mm.removeMessageListener("Loop:GetAllWebrtcStats", getAllStats);
-
-      let { allStats, logs } = message.data;
-      let internalFormat = allStats.reports[0]; // filtered on peerConnectionID
-
-      let report = convertToRTCStatsReport(internalFormat);
+  stageForTelemetryUpload: function(window, pc) {
+    window.WebrtcGlobalInformation.getAllStats(allStats => {
+      let internalFormat = allStats.reports[0]; // filtered on pc.id
+      window.WebrtcGlobalInformation.getLogging("", logs => {
+        let report = convertToRTCStatsReport(internalFormat);
         let logStr = "";
         logs.forEach(s => { logStr += s + "\n"; });
 
         // We have stats and logs.
 
         // Create worker job. ping = saved telemetry ping file header + payload
         //
         // Prepare payload according to https://wiki.mozilla.org/Loop/Telemetry
@@ -809,17 +825,17 @@ var MozLoopServiceInternal = {
                 appBuildID: ai.appBuildID,
                 appName: ai.name,
                 appVersion: ai.version,
                 reason: "loop",
                 OS: ai.OS,
                 version: Services.sysinfo.getProperty("version")
               },
               report: "ice failure",
-              connectionstate: details.iceConnectionState,
+              connectionstate: pc.iceConnectionState,
               stats: report,
               localSdp: internalFormat.localSdp,
               remoteSdp: internalFormat.remoteSdp,
               log: logStr
             }
           }
         };
 
@@ -828,61 +844,42 @@ var MozLoopServiceInternal = {
 
         let worker = new ChromeWorker("MozLoopWorker.js");
         worker.onmessage = function(e) {
           log.info(e.data.ok ?
             "Successfully staged loop report for telemetry upload." :
             ("Failed to stage loop report. Error: " + e.data.fail));
         };
         worker.postMessage(job);
-    });
-
-    mm.sendAsyncMessage("Loop:GetAllWebrtcStats", {
-      peerConnectionID: details.peerConnectionID
-    });
+      });
+    }, pc.id);
   },
 
   /**
    * Gets an id for the chat window, for now we just use the roomToken.
    *
    * @param  {Object} conversationWindowData The conversation window data.
    */
   getChatWindowID: function(conversationWindowData) {
     return conversationWindowData.roomToken;
   },
 
   getChatURL: function(chatWindowId) {
     return "about:loopconversation#" + chatWindowId;
   },
 
-  getChatWindows() {
-    let isLoopURL = ({ src }) => /^about:loopconversation#/.test(src);
-    return [...Chat.chatboxes].filter(isLoopURL);
-  },
-
   /**
    * Hangup and close all chat windows that are open.
    */
   hangupAllChatWindows() {
-    for (let chatbox of this.getChatWindows()) {
-      let mm = chatbox.content.messageManager;
-      mm.sendAsyncMessage("Social:CustomEvent", { name: "LoopHangupNow" });
-    }
-  },
-
-  /**
-   * Pause or resume all chat windows that are open.
-   */
-  toggleBrowserSharing(on = true) {
-    for (let chatbox of this.getChatWindows()) {
-      let mm = chatbox.content.messageManager;
-      mm.sendAsyncMessage("Social:CustomEvent", {
-        name: "ToggleBrowserSharing",
-        detail: on
-      });
+    let isLoopURL = ({ src }) => /^about:loopconversation#/.test(src);
+    let loopChatWindows = [...Chat.chatboxes].filter(isLoopURL);
+    for (let chatbox of loopChatWindows) {
+      let window = chatbox.content.contentWindow;
+      window.dispatchEvent(new window.CustomEvent("LoopHangupNow"));
     }
   },
 
   /**
    * Determines if a chat window is already open for a given window id.
    *
    * @param  {String}  chatWindowId The window id.
    * @return {Boolean}              True if the window is opened.
@@ -914,141 +911,135 @@ var MozLoopServiceInternal = {
 
     gConversationWindowData.set(windowId, conversationWindowData);
 
     let url = this.getChatURL(windowId);
 
     Chat.registerButton(kChatboxHangupButton);
 
     let callback = chatbox => {
-      let mm = chatbox.content.messageManager;
+      // We need to use DOMContentLoaded as otherwise the injection will happen
+      // in about:blank and then get lost.
+      // Sadly we can't use chatbox.promiseChatLoaded() as promise chaining
+      // involves event loop spins, which means it might be too late.
+      // Have we already done it?
+      if (chatbox.contentWindow.navigator.mozLoop) {
+        return;
+      }
 
-      let loaded = () => {
-        mm.removeMessageListener("DOMContentLoaded", loaded);
-        mm.sendAsyncMessage("Social:ListenForEvents", {
-          eventNames: ["LoopChatEnabled", "LoopChatMessageAppended",
-            "LoopChatDisabledMessageAppended", "socialFrameAttached",
-            "socialFrameDetached", "socialFrameHide", "socialFrameShow"]
-        });
+      let loaded = event => {
+        if (event.target != chatbox.contentDocument) {
+          return;
+        }
+        chatbox.removeEventListener("DOMContentLoaded", loaded, true);
 
         let chatbar = chatbox.parentNode;
+        let window = chatbox.contentWindow;
 
-        const kEventNamesMap = {
-          socialFrameAttached: "Loop:ChatWindowAttached",
-          socialFrameDetached: "Loop:ChatWindowDetached",
-          socialFrameHide: "Loop:ChatWindowHidden",
-          socialFrameShow: "Loop:ChatWindowShown",
-          unload: "Loop:ChatWindowClosed"
-        };
+        function socialFrameChanged(eventName) {
+          UITour.availableTargetsCache.clear();
+          UITour.notify(eventName);
+
+          if (eventName == "Loop:ChatWindowDetached" || eventName == "Loop:ChatWindowAttached") {
+            // After detach, re-attach of the chatbox, refresh its reference so
+            // we can keep using it here.
+            let ref = chatbar.chatboxForURL.get(chatbox.src);
+            chatbox = ref && ref.get() || chatbox;
+          } else if (eventName == "Loop:ChatWindowClosed") {
+            windowCloseCallback();
+            if (conversationWindowData.type == "room") {
+              // NOTE: if you add something here, please also consider if something
+              //       needs to be done on the content side as well (e.g.
+              //       activeRoomStore#windowUnload).
+              LoopAPI.sendMessageToHandler({
+                name: "HangupNow",
+                data: [conversationWindowData.roomToken, windowId]
+              });
+            }
+          }
+        }
+
+        window.addEventListener("socialFrameHide", socialFrameChanged.bind(null, "Loop:ChatWindowHidden"));
+        window.addEventListener("socialFrameShow", socialFrameChanged.bind(null, "Loop:ChatWindowShown"));
+        window.addEventListener("socialFrameDetached", socialFrameChanged.bind(null, "Loop:ChatWindowDetached"));
+        window.addEventListener("socialFrameAttached", socialFrameChanged.bind(null, "Loop:ChatWindowAttached"));
+        window.addEventListener("unload", socialFrameChanged.bind(null, "Loop:ChatWindowClosed"));
 
         const kSizeMap = {
           LoopChatEnabled: "loopChatEnabled",
           LoopChatDisabledMessageAppended: "loopChatDisabledMessageAppended",
           LoopChatMessageAppended: "loopChatMessageAppended"
         };
 
-        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();
-            UITour.notify(eventName);
+        function onChatEvent(ev) {
+          // When the chat box or messages are shown, resize the panel or window
+          // to be slightly higher to accomodate them.
+          let customSize = kSizeMap[ev.type];
+          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.
+          if (customSize && currSize != customSize && currSize != "loopChatMessageAppended") {
+            chatbox.setAttribute("customSize", customSize);
+            chatbox.parentNode.setAttribute("customSize", customSize);
+          }
+        }
 
-            if (eventName == "Loop:ChatWindowDetached" || eventName == "Loop:ChatWindowAttached") {
-              // After detach, re-attach of the chatbox, refresh its reference so
-              // we can keep using it here.
-              let ref = chatbar.chatboxForURL.get(chatbox.src);
-              chatbox = ref && ref.get() || chatbox;
-            }
-          } 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.
-            if (customSize && currSize != customSize && currSize != "loopChatMessageAppended") {
-              chatbox.setAttribute("customSize", customSize);
-              chatbox.parentNode.setAttribute("customSize", customSize);
-            }
-          }
-        });
+        window.addEventListener("LoopChatEnabled", onChatEvent);
+        window.addEventListener("LoopChatMessageAppended", onChatEvent);
+        window.addEventListener("LoopChatDisabledMessageAppended", onChatEvent);
 
         // Handle window.close correctly on the chatbox.
-        hookWindowCloseForPanelClose(chatbox.content);
-        messageName = "DOMWindowClose";
-        mm.addMessageListener(messageName, listeners[messageName] = () => {
-          // Remove message listeners.
-          for (let name of Object.getOwnPropertyNames(listeners)) {
-            mm.removeMessageListener(name, listeners[name]);
-          }
-          listeners = {};
+        hookWindowCloseForPanelClose(window);
 
-          windowCloseCallback();
+        let ourID = window.QueryInterface(Ci.nsIInterfaceRequestor)
+            .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
 
-          if (conversationWindowData.type == "room") {
-            // NOTE: if you add something here, please also consider if something
-            //       needs to be done on the content side as well (e.g.
-            //       activeRoomStore#windowUnload).
-            LoopAPI.sendMessageToHandler({
-              name: "HangupNow",
-              data: [conversationWindowData.roomToken, windowId]
-            });
+        let onPCLifecycleChange = (pc, winID, type) => {
+          if (winID != ourID) {
+            return;
           }
-        });
 
-        mm.sendAsyncMessage("Loop:MonitorPeerConnectionLifecycle");
-        messageName = "Loop:PeerConnectionLifecycleChange";
-        mm.addMessageListener(messageName, listeners[messageName] = message => {
           // Chat Window Id, this is different that the internal winId
-          let chatWindowId = message.data.locationHash.slice(1);
+          let chatWindowId = window.location.hash.slice(1);
           var context = this.conversationContexts.get(chatWindowId);
-          var peerConnectionID = message.data.peerConnectionID;
-          var exists = peerConnectionID.match(/session=(\S+)/);
+          var exists = pc.id.match(/session=(\S+)/);
           if (context && !exists) {
             // Not ideal but insert our data amidst existing data like this:
             // - 000 (id=00 url=http)
             // + 000 (session=000 call=000 id=00 url=http)
-            var pair = peerConnectionID.split("(");
+            var pair = pc.id.split("(");
             if (pair.length == 2) {
-              peerConnectionID = pair[0] + "(session=" + context.sessionId +
+              pc.id = pair[0] + "(session=" + context.sessionId +
                   (context.callId ? " call=" + context.callId : "") + " " + pair[1];
             }
           }
 
-          if (message.data.type == "iceconnectionstatechange") {
-            switch (message.data.iceConnectionState) {
+          if (type == "iceconnectionstatechange") {
+            switch (pc.iceConnectionState) {
               case "failed":
               case "disconnected":
                 if (Services.telemetry.canRecordExtended) {
-                  this.stageForTelemetryUpload(chatbox.content, message.data);
+                  this.stageForTelemetryUpload(window, pc);
                 }
                 break;
             }
           }
-        });
+        };
+
+        let pc_static = new window.RTCPeerConnectionStatic();
+        pc_static.registerPeerConnectionLifecycleCallback(onPCLifecycleChange);
 
         UITour.notify("Loop:ChatWindowOpened");
       };
-
-      mm.sendAsyncMessage("WaitForDOMContentLoaded");
-      mm.addMessageListener("DOMContentLoaded", loaded);
+      chatbox.addEventListener("DOMContentLoaded", loaded, true);
     };
 
     LoopAPI.initialize();
-    let chatboxInstance = Chat.open(null, {
-      origin: origin,
-      title: "",
-      url: url,
-      remote: MozLoopService.getLoopPref("remote.autostart")
-    }, callback);
+    let chatboxInstance = Chat.open(null, origin, "", url, undefined, undefined,
+                                    callback);
     if (!chatboxInstance) {
       return null;
     // It's common for unit tests to overload Chat.open.
     } else if (chatboxInstance.setAttribute) {
       // Set properties that influence visual appearance of the chatbox right
       // away to circumvent glitches.
       chatboxInstance.setAttribute("customSize", "loopDefault");
       chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
@@ -1289,34 +1280,21 @@ this.MozLoopService = {
       // Don't alert if we're in the doNotDisturb mode, or the participant
       // is the owner - the content code deals with the rest of the sounds.
       if (MozLoopServiceInternal.doNotDisturb || participant.owner) {
         return;
       }
 
       let window = gWM.getMostRecentWindow("navigator:browser");
       if (window) {
-        // The participant that joined isn't necessarily included in room.participants (depending on
-        // when the broadcast happens) so concatenate.
-        let isOwnerInRoom = room.participants.concat(participant).some(p => p.owner);
-        let bundle = MozLoopServiceInternal.localizedStrings;
-
-        let localizedString;
-        if (isOwnerInRoom) {
-          localizedString = bundle.get("rooms_room_joined_owner_connected_label2");
-        } else {
-          let l10nString = bundle.get("rooms_room_joined_owner_not_connected_label");
-          let roomUrlHostname = new URL(room.decryptedContext.urls[0].location).hostname.replace(/^www\./, "");
-          localizedString = l10nString.replace("{{roomURLHostname}}", roomUrlHostname);
-        }
         window.LoopUI.showNotification({
           sound: "room-joined",
           // Fallback to the brand short name if the roomName isn't available.
           title: room.roomName || MozLoopServiceInternal.localizedStrings.get("clientShortname2"),
-          message: localizedString,
+          message: MozLoopServiceInternal.localizedStrings.get("rooms_room_joined_label"),
           selectTab: "rooms"
         });
       }
     });
 
     LoopRooms.on("joined", this.maybeResumeTourOnRoomJoined.bind(this));
 
     // If there's no guest room created and the user hasn't
@@ -1431,20 +1409,16 @@ this.MozLoopService = {
 
   /**
    * Hangup and close all chat windows that are open.
    */
   hangupAllChatWindows() {
     return MozLoopServiceInternal.hangupAllChatWindows();
   },
 
-  toggleBrowserSharing(on) {
-    return MozLoopServiceInternal.toggleBrowserSharing(on);
-  },
-
   /**
    * Opens the chat window
    *
    * @param {Object} conversationWindowData The data to be obtained by the
    *                                        window when it opens.
    * @param {Function} windowCloseCallback Callback for when the window closes.
    * @returns {Number} The id of the window.
    */
--- a/browser/extensions/loop/chrome/content/panels/conversation.html
+++ b/browser/extensions/loop/chrome/content/panels/conversation.html
@@ -38,13 +38,12 @@
     <script type="text/javascript" src="shared/js/views.js"></script>
     <script type="text/javascript" src="shared/js/textChatStore.js"></script>
     <script type="text/javascript" src="shared/js/textChatView.js"></script>
     <script type="text/javascript" src="shared/js/linkifiedTextView.js"></script>
     <script type="text/javascript" src="shared/js/urlRegExps.js"></script>
     <script type="text/javascript" src="panels/js/conversationAppStore.js"></script>
     <script type="text/javascript" src="panels/js/feedbackViews.js"></script>
     <script type="text/javascript" src="panels/js/roomStore.js"></script>
-    <script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
     <script type="text/javascript" src="panels/js/roomViews.js"></script>
     <script type="text/javascript" src="panels/js/conversation.js"></script>
   </body>
 </html>
--- a/browser/extensions/loop/chrome/content/panels/css/panel.css
+++ b/browser/extensions/loop/chrome/content/panels/css/panel.css
@@ -20,23 +20,27 @@ body {
 
 /* Panel styles */
 
 .panel {
   /* hide the extra margin space that the panel resizer now wants to show */
   overflow: hidden;
   font: menu;
   background-color: #fbfbfb;
+  height: 410px;
   width: 330px;
 }
 
-/* Panel container */
+/* Panel container flexbox */
 .panel-content {
+  height: 410px;
   width: 330px;
-  display: block;
+  display: flex;
+  flex-flow: column nowrap;
+  align-items: flex-start;
 }
 
 .panel-content > .beta-ribbon {
   position: fixed;
   left: 0;
   top: 0;
   z-index: 1000;
 }
@@ -139,28 +143,36 @@ body {
 
 .content-area input:focus {
   border: 0.1rem solid #5cccee;
 }
 
 /* Rooms CSS */
 
 .room-list-loading {
+  position: relative;
   text-align: center;
-  padding-top: 5px;
-  padding-bottom: 5px;
+  /* makes sure that buttons are anchored above footer */
+  flex: 1;
 }
 
 .room-list-loading > img {
   width: 66px;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
 }
 
+
 /* Rooms */
 .rooms {
-  display: block;
+  flex: 1;
+  display: flex;
+  flex-flow: column nowrap;
   width: 100%;
 }
 
 .rooms > h1 {
   color: #666;
   font-size: 1rem;
   padding: .5rem 15px;
   height: 3rem;
@@ -190,66 +202,33 @@ body {
 }
 
 .new-room-view > .stop-sharing-button:hover {
   background-color: #ef6745;
   border-color: #ef6745;
 }
 
 .room-list {
+  /* xxx  not sure why flex needs the 3 value setting
+    but setting flex to just 1, the whole tab including the new room is scrollable.
+    seems to not like the 0% of the default setting - may be FF bug */
+  flex: 1 1 0;
   overflow-y: auto;
   overflow-x: hidden;
+  display: flex;
   flex-flow: column nowrap;
   width: 100%;
-  /* max number of rooms shown @ 28px */
-  max-height: calc(5 * 28px);
-  /* min-height because there is a browser min-height on panel */
-  min-height: 53px;
-  padding-top: 4px;
-}
-
-.room-list-empty {
-  display: inline-block;
-  /* min height so empty empty room list panel is same height as loading and
-  room-list panels */
-  min-height: 80px;
-}
-
-
-/* Adds blur to bottom of room-list to indicate more items in list */
-.room-list-blur {
-  /* height of room-list item */
-  height: 2.4rem;
-  /* -15px for scrollbar */
-  width: calc(100% - 15px);
-  /* At same DOM level as room-list, positioned after and moved to bottom of room-list */
-  position: absolute;
-  margin-top: -2.4rem;
-  /* starts gradient at about the 50% mark on the text, eyeballed the percentages */
-  background: linear-gradient(
-    to bottom,
-    rgba(251, 251, 251, 0) 0%,
-    rgba(251, 251, 251, 0) 38%, /* Because of padding and lineheight, start gradient at 38% */
-    rgba(251, 251, 251, 1) 65%, /* and end around 65% */
-    rgba(251, 251, 251, 1) 100%
-  );
-  pointer-events: none;
 }
 
 .room-list > .room-entry {
   padding: .2rem 15px;
   /* Always show the default pointer, even over the text part of the entry. */
   cursor: default;
 }
 
-/* Adds margin to the last room-entry to make gradient fade disappear for last item */
-.room-list-add-space > .room-entry:last-child {
-  margin-bottom: 8px;
-}
-
 .room-list > .room-entry > h2 {
   display: inline-block;
   vertical-align: middle;
   /* See .room-entry-context-item for the margin/size reductions.
    * An extra 16px to make space for the edit button. */
   width: calc(100% - 1rem - 32px);
 
   font-size: 1.3rem;
@@ -638,17 +617,16 @@ html[dir="rtl"] .settings-menu .dropdown
 /* First time use */
 
 .fte-get-started-content {
   /* Manual vertical centering */
   flex: 1;
   padding: 2rem 0 0;
   display: flex;
   flex-direction: column;
-  height: 553px;
 }
 
 .fte-title {
   margin: 0 20px;
 }
 
 .fte-title > img {
   width: 100%;
--- a/browser/extensions/loop/chrome/content/panels/js/conversation.js
+++ b/browser/extensions/loop/chrome/content/panels/js/conversation.js
@@ -62,17 +62,16 @@ loop.conversation = function (mozL10n) {
       }
 
       switch (this.state.windowType) {
         case "room":
           {
             return React.createElement(DesktopRoomConversationView, {
               chatWindowDetached: this.state.chatWindowDetached,
               dispatcher: this.props.dispatcher,
-              facebookEnabled: this.state.facebookEnabled,
               onCallTerminated: this.handleCallTerminated,
               roomStore: this.props.roomStore });
           }
         case "failed":
           {
             return React.createElement(RoomFailureView, {
               dispatcher: this.props.dispatcher,
               failureReason: FAILURE_DETAILS.UNKNOWN });
@@ -95,17 +94,17 @@ loop.conversation = function (mozL10n) {
     var locationHash = loop.shared.utils.locationData().hash;
     var windowId;
 
     var hash = locationHash.match(/#(.*)/);
     if (hash) {
       windowId = hash[1];
     }
 
-    var requests = [["GetAllConstants"], ["GetAllStrings"], ["GetLocale"], ["GetLoopPref", "ot.guid"], ["GetLoopPref", "feedback.periodSec"], ["GetLoopPref", "feedback.dateLastSeenSec"], ["GetLoopPref", "facebook.enabled"]];
+    var requests = [["GetAllConstants"], ["GetAllStrings"], ["GetLocale"], ["GetLoopPref", "ot.guid"], ["GetLoopPref", "textChat.enabled"], ["GetLoopPref", "feedback.periodSec"], ["GetLoopPref", "feedback.dateLastSeenSec"]];
     var prefetch = [["GetConversationWindowData", windowId]];
 
     return loop.requestMulti.apply(null, requests.concat(prefetch)).then(function (results) {
       // `requestIdx` is keyed off the order of the `requests` and `prefetch`
       // arrays. Be careful to update both when making changes.
       var requestIdx = 0;
       var constants = results[requestIdx];
       // Do the initial L10n setup, we do this before anything
@@ -135,60 +134,58 @@ loop.conversation = function (mozL10n) {
           // See nsIPrefBranch
           var PREF_STRING = 32;
           currGuid = guid;
           loop.request("SetLoopPref", "ot.guid", guid, PREF_STRING);
           callback(null);
         }
       });
 
+      // We want data channels only if the text chat preference is enabled.
+      var useDataChannels = results[++requestIdx];
+
       var dispatcher = new loop.Dispatcher();
       var sdkDriver = new loop.OTSdkDriver({
         constants: constants,
         isDesktop: true,
-        useDataChannels: true,
+        useDataChannels: useDataChannels,
         dispatcher: dispatcher,
         sdk: OT
       });
 
       // expose for functional tests
       loop.conversation._sdkDriver = sdkDriver;
 
       // Create the stores.
       var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
         isDesktop: true,
         sdkDriver: sdkDriver
       });
       var conversationAppStore = new loop.store.ConversationAppStore({
         activeRoomStore: activeRoomStore,
         dispatcher: dispatcher,
         feedbackPeriod: results[++requestIdx],
-        feedbackTimestamp: results[++requestIdx],
-        facebookEnabled: results[++requestIdx]
+        feedbackTimestamp: results[++requestIdx]
       });
 
       prefetch.forEach(function (req) {
         req.shift();
         loop.storeRequest(req, results[++requestIdx]);
       });
 
       var roomStore = new loop.store.RoomStore(dispatcher, {
         activeRoomStore: activeRoomStore,
         constants: constants
       });
       var textChatStore = new loop.store.TextChatStore(dispatcher, {
         sdkDriver: sdkDriver
       });
-      var remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
-        sdkDriver: sdkDriver
-      });
 
       loop.store.StoreMixin.register({
         conversationAppStore: conversationAppStore,
-        remoteCursorStore: remoteCursorStore,
         textChatStore: textChatStore
       });
 
       React.render(React.createElement(AppControllerView, {
         dispatcher: dispatcher,
         roomStore: roomStore }), document.querySelector("#main"));
 
       document.documentElement.setAttribute("lang", mozL10n.language.code);
--- a/browser/extensions/loop/chrome/content/panels/js/conversationAppStore.js
+++ b/browser/extensions/loop/chrome/content/panels/js/conversationAppStore.js
@@ -29,42 +29,40 @@ loop.store.ConversationAppStore = (funct
       throw new Error("Missing option feedbackPeriod");
     }
     if (!("feedbackTimestamp" in options)) {
       throw new Error("Missing option feedbackTimestamp");
     }
 
     this._activeRoomStore = options.activeRoomStore;
     this._dispatcher = options.dispatcher;
-    this._facebookEnabled = options.facebookEnabled;
     this._feedbackPeriod = options.feedbackPeriod;
     this._feedbackTimestamp = options.feedbackTimestamp;
     this._rootObj = ("rootObject" in options) ? options.rootObject : window;
     this._storeState = this.getInitialStoreState();
 
     // Start listening for specific events, coming from the window object.
     this._eventHandlers = {};
-    ["unload", "LoopHangupNow", "socialFrameAttached", "socialFrameDetached", "ToggleBrowserSharing"]
+    ["unload", "LoopHangupNow", "socialFrameAttached", "socialFrameDetached"]
       .forEach(function(eventName) {
         var handlerName = eventName + "Handler";
         this._eventHandlers[eventName] = this[handlerName].bind(this);
         this._rootObj.addEventListener(eventName, this._eventHandlers[eventName]);
       }.bind(this));
 
     this._dispatcher.register(this, [
       "getWindowData",
       "showFeedbackForm"
     ]);
   };
 
   ConversationAppStore.prototype = _.extend({
     getInitialStoreState: function() {
       return {
         chatWindowDetached: false,
-        facebookEnabled: this._facebookEnabled,
         // How often to display the form. Convert seconds to ms.
         feedbackPeriod: this._feedbackPeriod * 1000,
         // Date when the feedback form was last presented. Convert to ms.
         feedbackTimestamp: this._feedbackTimestamp * 1000,
         showFeedbackForm: false
       };
     },
 
@@ -158,27 +156,16 @@ loop.store.ConversationAppStore = (funct
           break;
         default:
           loop.shared.mixins.WindowCloseMixin.closeWindow();
           break;
       }
     },
 
     /**
-     * Event handler; invoked when the 'PauseScreenShare' event is dispatched from
-     * the window object.
-     * It'll attempt to pause or resume the screen share as appropriate.
-     */
-    ToggleBrowserSharingHandler: function(actionData) {
-      this._dispatcher.dispatch(new loop.shared.actions.ToggleBrowserSharing({
-        enabled: !actionData.detail
-      }));
-    },
-
-    /**
      * Event handler; invoked when the 'socialFrameAttached' event is dispatched
      * from the window object.
      */
     socialFrameAttachedHandler: function() {
       this.setStoreState({ chatWindowDetached: false });
     },
 
     /**
--- a/browser/extensions/loop/chrome/content/panels/js/panel.js
+++ b/browser/extensions/loop/chrome/content/panels/js/panel.js
@@ -15,23 +15,28 @@ loop.panel = function (_, mozL10n) {
   var FTU_VERSION = 1;
 
   var GettingStartedView = React.createClass({
     displayName: "GettingStartedView",
 
     mixins: [sharedMixins.WindowCloseMixin],
 
     handleButtonClick: function () {
-      loop.requestMulti(["OpenGettingStartedTour", "getting-started"], ["SetLoopPref", "gettingStarted.latestFTUVersion", FTU_VERSION]).then(function () {
+      loop.requestMulti(["OpenGettingStartedTour", "getting-started"], ["SetLoopPref", "gettingStarted.latestFTUVersion", FTU_VERSION], ["SetPanelHeight"]).then(function () {
         var event = new CustomEvent("GettingStartedSeen");
         window.dispatchEvent(event);
       }.bind(this));
       this.closeWindow();
     },
 
+    componentWillMount: function () {
+      // Set 553 pixel height to show the full FTU panel content.
+      loop.request("SetPanelHeight", 553);
+    },
+
     render: function () {
       return React.createElement(
         "div",
         { className: "fte-get-started-content" },
         React.createElement(
           "div",
           { className: "fte-title" },
           React.createElement("img", { className: "fte-logo", src: "shared/img/hello_logo.svg" }),
@@ -729,54 +734,35 @@ loop.panel = function (_, mozL10n) {
       this.setState(this.props.store.getStoreState());
     },
 
     /**
      * Let the user know we're loading rooms
      * @returns {Object} React render
      */
     _renderLoadingRoomsView: function () {
-      /* XXX should refactor and separate "rooms" amd perhaps room-list so that
-      we arent duplicating those elements all over */
       return React.createElement(
         "div",
-        { className: "rooms" },
+        { className: "room-list" },
         this._renderNewRoomButton(),
         React.createElement(
           "div",
-          { className: "room-list" },
-          React.createElement(
-            "div",
-            { className: "room-list-loading" },
-            React.createElement("img", { src: "shared/img/animated-spinner.svg" })
-          )
+          { className: "room-list-loading" },
+          React.createElement("img", { src: "shared/img/animated-spinner.svg" })
         )
       );
     },
 
     _renderNewRoomButton: function () {
       return React.createElement(NewRoomView, { dispatcher: this.props.dispatcher,
         inRoom: this.state.openedRoom !== null,
         pendingOperation: this.state.pendingCreation || this.state.pendingInitialRetrieval });
     },
 
-    _addListGradientIfNeeded: function () {
-      if (this.state.rooms.length > 5) {
-        return React.createElement("div", { className: "room-list-blur" });
-      }
-    },
-
     render: function () {
-      var roomListClasses = classNames({
-        "room-list": true,
-        // add extra space to last item so when scrolling to bottom,
-        // last item is not covered by the gradient
-        "room-list-add-space": this.state.rooms.length && this.state.rooms.length > 5
-      });
-
       if (this.state.error) {
         // XXX Better end user reporting of errors.
         console.error("RoomList error", this.state.error);
       }
 
       if (this.state.pendingInitialRetrieval) {
         return this._renderLoadingRoomsView();
       }
@@ -785,32 +771,31 @@ loop.panel = function (_, mozL10n) {
         "div",
         { className: "rooms" },
         this._renderNewRoomButton(),
         !this.state.rooms.length ? null : React.createElement(
           "h1",
           null,
           mozL10n.get(this.state.openedRoom === null ? "rooms_list_recently_browsed2" : "rooms_list_currently_browsing2")
         ),
-        !this.state.rooms.length ? React.createElement("div", { className: "room-list-empty" }) : React.createElement(
+        !this.state.rooms.length ? null : React.createElement(
           "div",
-          { className: roomListClasses },
+          { className: "room-list" },
           this.state.rooms.map(function (room) {
             if (this.state.openedRoom !== null && room.roomToken !== this.state.openedRoom) {
               return null;
             }
 
             return React.createElement(RoomEntry, {
               dispatcher: this.props.dispatcher,
               isOpenedRoom: room.roomToken === this.state.openedRoom,
               key: room.roomToken,
               room: room });
           }, this)
-        ),
-        this._addListGradientIfNeeded()
+        )
       );
     }
   });
 
   /**
    * Used for creating a new room with or without context.
    */
   var NewRoomView = React.createClass({
@@ -875,17 +860,17 @@ loop.panel = function (_, mozL10n) {
       return React.createElement(
         "div",
         { className: "new-room-view" },
         this.props.inRoom ? React.createElement(
           "button",
           { className: "btn btn-info stop-sharing-button",
             disabled: this.props.pendingOperation,
             onClick: this.handleStopSharingButtonClick },
-          mozL10n.get("panel_disconnect_button")
+          mozL10n.get("panel_stop_sharing_tabs_button")
         ) : React.createElement(
           "button",
           { className: "btn btn-info new-room-button",
             disabled: this.props.pendingOperation,
             onClick: this.handleCreateButtonClick },
           mozL10n.get("panel_browse_with_friend_button")
         )
       );
@@ -897,20 +882,16 @@ loop.panel = function (_, mozL10n) {
    */
   var E10sNotSupported = React.createClass({
     displayName: "E10sNotSupported",
 
     propTypes: {
       onClick: React.PropTypes.func.isRequired
     },
 
-    componentWillMount: function () {
-      loop.request("SetPanelHeight", 262);
-    },
-
     render: function () {
       return React.createElement(
         "div",
         { className: "error-content" },
         React.createElement(
           "header",
           { className: "error-title" },
           React.createElement("img", { src: "shared/img/sad_hello_icon_64x64.svg" }),
@@ -952,18 +933,17 @@ loop.panel = function (_, mozL10n) {
     },
 
     getInitialState: function () {
       return {
         fxAEnabled: loop.getStoredRequest(["GetFxAEnabled"]),
         hasEncryptionKey: loop.getStoredRequest(["GetHasEncryptionKey"]),
         userProfile: loop.getStoredRequest(["GetUserProfile"]),
         gettingStartedSeen: loop.getStoredRequest(["GetLoopPref", "gettingStarted.latestFTUVersion"]) >= FTU_VERSION,
-        multiProcessActive: loop.getStoredRequest(["IsMultiProcessActive"]),
-        remoteAutoStart: loop.getStoredRequest(["GetLoopPref", "remote.autostart"])
+        multiProcessEnabled: loop.getStoredRequest(["IsMultiProcessEnabled"])
       };
     },
 
     _serviceErrorToShow: function () {
       return new Promise(function (resolve) {
         loop.request("GetErrors").then(function (errors) {
           if (!errors || !Object.keys(errors).length) {
             resolve(null);
@@ -1042,17 +1022,17 @@ loop.panel = function (_, mozL10n) {
       loop.request("GetSelectedTabMetadata").then(function (metadata) {
         loop.request("OpenNonE10sWindow", metadata.url);
       });
     },
 
     render: function () {
       var NotificationListView = sharedViews.NotificationListView;
 
-      if (this.state.multiProcessActive && !this.state.remoteAutoStart) {
+      if (this.state.multiProcessEnabled) {
         return React.createElement(E10sNotSupported, { onClick: this.launchNonE10sWindow });
       }
 
       if (!this.props.gettingStartedSeen || !this.state.gettingStartedSeen) {
         return React.createElement(
           "div",
           { className: "fte-get-started-container",
             onContextMenu: this.handleContextMenu },
@@ -1092,17 +1072,17 @@ loop.panel = function (_, mozL10n) {
     }
   });
 
   /**
    * Panel initialisation.
    */
   function init() {
     var requests = [["GetAllConstants"], ["GetAllStrings"], ["GetLocale"], ["GetPluralRule"]];
-    var prefetch = [["GetLoopPref", "gettingStarted.latestFTUVersion"], ["GetLoopPref", "legal.ToS_url"], ["GetLoopPref", "legal.privacy_url"], ["GetLoopPref", "remote.autostart"], ["GetUserProfile"], ["GetFxAEnabled"], ["GetDoNotDisturb"], ["GetHasEncryptionKey"], ["IsMultiProcessActive"]];
+    var prefetch = [["GetLoopPref", "gettingStarted.latestFTUVersion"], ["GetLoopPref", "legal.ToS_url"], ["GetLoopPref", "legal.privacy_url"], ["GetUserProfile"], ["GetFxAEnabled"], ["GetDoNotDisturb"], ["GetHasEncryptionKey"], ["IsMultiProcessEnabled"]];
 
     return loop.requestMulti.apply(null, requests.concat(prefetch)).then(function (results) {
       // `requestIdx` is keyed off the order of the `requests` and `prefetch`
       // arrays. Be careful to update both when making changes.
       var requestIdx = 0;
       var constants = results[requestIdx];
       // Do the initial L10n setup, we do this before anything
       // else to ensure the L10n environment is setup correctly.
--- a/browser/extensions/loop/chrome/content/panels/js/roomStore.js
+++ b/browser/extensions/loop/chrome/content/panels/js/roomStore.js
@@ -267,16 +267,25 @@ loop.store = loop.store || {};
           }));
           return;
         }
 
         this.dispatchAction(new sharedActions.CreatedRoom({
           roomToken: result.roomToken
         }));
         loop.request("TelemetryAddValue", "LOOP_ROOM_CREATE", buckets.CREATE_SUCCESS);
+
+        // Since creating a room with context is only possible from the panel,
+        // we can record that as the action here.
+        var URLs = roomCreationData.decryptedContext.urls;
+        if (URLs && URLs.length) {
+          buckets = this._constants.ROOM_CONTEXT_ADD;
+          loop.request("TelemetryAddValue", "LOOP_ROOM_CONTEXT_ADD",
+            buckets.ADD_FROM_PANEL);
+        }
       }.bind(this));
     },
 
     /**
      * Executed when a room has been created
      */
     createdRoom: function(actionData) {
       this.setStoreState({ pendingCreation: false });
@@ -544,23 +553,34 @@ loop.store = loop.store || {};
           // Ensure async actions so that we get separate setStoreState events
           // that React components won't miss.
           setTimeout(function() {
             this.dispatchAction(new sharedActions.UpdateRoomContextDone());
           }.bind(this), 0);
           return;
         }
 
+        var hadContextBefore = !!oldRoomURL;
+
         this.setStoreState({ error: null });
         loop.request("Rooms:Update", actionData.roomToken, roomData).then(function(result2) {
           var isError = (result2 && result2.isError);
           var action = isError ?
             new sharedActions.UpdateRoomContextError({ error: result2 }) :
             new sharedActions.UpdateRoomContextDone();
           this.dispatchAction(action);
+
+          if (!isError && !hadContextBefore) {
+            // Since updating the room context data is only possible from the
+            // conversation window, we can assume that any newly added URL was
+            // done from there.
+            var buckets = this._constants.ROOM_CONTEXT_ADD;
+            loop.request("TelemetryAddValue", "LOOP_ROOM_CONTEXT_ADD",
+              buckets.ADD_FROM_CONVERSATION);
+          }
         }.bind(this));
       }.bind(this));
     },
 
     /**
      * Handles the updateRoomContextDone action.
      */
     updateRoomContextDone: function() {
--- a/browser/extensions/loop/chrome/content/panels/js/roomViews.js
+++ b/browser/extensions/loop/chrome/content/panels/js/roomViews.js
@@ -249,17 +249,16 @@ loop.roomViews = function (mozL10n) {
       TRIGGERED_RESET_DELAY: 2000
     },
 
     mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       error: React.PropTypes.object,
-      facebookEnabled: React.PropTypes.bool.isRequired,
       // This data is supplied by the activeRoomStore.
       roomData: React.PropTypes.object.isRequired,
       show: React.PropTypes.bool.isRequired,
       socialShareProviders: React.PropTypes.array
     },
 
     getInitialState: function () {
       return {
@@ -374,32 +373,28 @@ loop.roomViews = function (mozL10n) {
               onMouseOver: this.resetTriggeredButtons },
             React.createElement("img", { src: "shared/img/glyph-email-16x16.svg" }),
             React.createElement(
               "p",
               null,
               mozL10n.get("invite_email_link_button")
             )
           ),
-          (() => {
-            if (this.props.facebookEnabled) {
-              return React.createElement(
-                "div",
-                { className: "btn-facebook invite-button",
-                  onClick: this.handleFacebookButtonClick,
-                  onMouseOver: this.resetTriggeredButtons },
-                React.createElement("img", { src: "shared/img/glyph-facebook-16x16.svg" }),
-                React.createElement(
-                  "p",
-                  null,
-                  mozL10n.get("invite_facebook_button3")
-                )
-              );
-            }
-          })()
+          React.createElement(
+            "div",
+            { className: "btn-facebook invite-button",
+              onClick: this.handleFacebookButtonClick,
+              onMouseOver: this.resetTriggeredButtons },
+            React.createElement("img", { src: "shared/img/glyph-facebook-16x16.svg" }),
+            React.createElement(
+              "p",
+              null,
+              mozL10n.get("invite_facebook_button2")
+            )
+          )
         ),
         React.createElement(SocialShareDropdown, {
           dispatcher: this.props.dispatcher,
           ref: "menu",
           roomUrl: this.props.roomData.roomUrl,
           show: this.state.showMenu,
           socialShareProviders: this.props.socialShareProviders })
       );
@@ -412,17 +407,16 @@ loop.roomViews = function (mozL10n) {
   var DesktopRoomConversationView = React.createClass({
     displayName: "DesktopRoomConversationView",
 
     mixins: [ActiveRoomStoreMixin, sharedMixins.DocumentTitleMixin, sharedMixins.MediaSetupMixin, sharedMixins.RoomsAudioMixin, sharedMixins.WindowCloseMixin],
 
     propTypes: {
       chatWindowDetached: React.PropTypes.bool.isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
-      facebookEnabled: React.PropTypes.bool.isRequired,
       // The poster URLs are for UI-showcase testing and development.
       localPosterUrl: React.PropTypes.string,
       onCallTerminated: React.PropTypes.func.isRequired,
       remotePosterUrl: React.PropTypes.string,
       roomStore: React.PropTypes.instanceOf(loop.store.RoomStore).isRequired
     },
 
     componentWillUpdate: function (nextProps, nextState) {
@@ -557,19 +551,16 @@ loop.roomViews = function (mozL10n) {
 
     handleContextMenu: function (e) {
       e.preventDefault();
     },
 
     render: function () {
       if (this.state.roomName || this.state.roomContextUrls) {
         var roomTitle = this.state.roomName || this.state.roomContextUrls[0].description || this.state.roomContextUrls[0].location;
-        if (!roomTitle) {
-          roomTitle = mozL10n.get("room_name_untitled_page");
-        }
         this.setTitle(roomTitle);
       }
 
       var shouldRenderInvitationOverlay = this._shouldRenderInvitationOverlay();
       var roomData = this.props.roomStore.getStoreState("activeRoom");
 
       switch (this.state.roomState) {
         case ROOM_STATES.FAILED:
@@ -617,17 +608,16 @@ loop.roomViews = function (mozL10n) {
                   dispatcher: this.props.dispatcher,
                   hangup: this.leaveRoom,
                   publishStream: this.publishStream,
                   showHangup: this.props.chatWindowDetached,
                   video: { enabled: !this.state.videoMuted, visible: true } }),
                 React.createElement(DesktopRoomInvitationView, {
                   dispatcher: this.props.dispatcher,
                   error: this.state.error,
-                  facebookEnabled: this.props.facebookEnabled,
                   roomData: roomData,
                   show: shouldRenderInvitationOverlay,
                   socialShareProviders: this.state.socialShareProviders })
               )
             );
           }
       }
     }
--- a/browser/extensions/loop/chrome/content/panels/panel.html
+++ b/browser/extensions/loop/chrome/content/panels/panel.html
@@ -19,19 +19,18 @@
     <script type="text/javascript" src="shared/vendor/lodash.js"></script>
     <script type="text/javascript" src="shared/vendor/backbone.js"></script>
     <script type="text/javascript" src="shared/vendor/classnames.js"></script>
 
     <script type="text/javascript" src="shared/js/loopapi-client.js"></script>
     <script type="text/javascript" src="shared/js/utils.js"></script>
     <script type="text/javascript" src="shared/js/models.js"></script>
     <script type="text/javascript" src="shared/js/mixins.js"></script>
-    <script type="text/javascript" src="shared/js/store.js"></script>
-    <script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
-    <script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
     <script type="text/javascript" src="shared/js/views.js"></script>
     <script type="text/javascript" src="shared/js/validate.js"></script>
     <script type="text/javascript" src="shared/js/actions.js"></script>
     <script type="text/javascript" src="shared/js/dispatcher.js"></script>
+    <script type="text/javascript" src="shared/js/store.js"></script>
+    <script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
     <script type="text/javascript" src="panels/js/roomStore.js"></script>
     <script type="text/javascript" src="panels/js/panel.js"></script>
  </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/loop/chrome/content/panels/test/README.md
@@ -0,0 +1,21 @@
+== Mocha unit tests ==
+
+These unit tests use the browser build of the [Mocha test framework][1]
+and the Chai Assertion Library's [BDD interface][2].
+
+[1]: http://visionmedia.github.io/mocha/
+[2]: http://chaijs.com/api/bdd/
+
+Aim your browser at the index.html in this directory on your localhost using
+a file: or HTTP URL to run the tests.  Alternately, from the top-level of your
+Gecko source directory, execute:
+
+```
+./mach marionette-test browser/extensions/loop/test/manifest.ini
+```
+
+Next steps:
+
+* run using JS http server so the property security context for DOM elements
+is used
+
--- a/browser/extensions/loop/chrome/content/panels/test/conversationAppStore_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/conversationAppStore_test.js
@@ -278,21 +278,10 @@ describe("loop.store.ConversationAppStor
 
     describe("#socialFrameDetachedHandler", function() {
       it("should update the store correctly to reflect the detached state", function() {
         store.socialFrameDetachedHandler();
 
         expect(store.getStoreState().chatWindowDetached).to.eql(true);
       });
     });
-
-    describe("#ToggleBrowserSharingHandler", function() {
-      it("should dispatch the correct action", function() {
-        store.ToggleBrowserSharingHandler({ detail: false });
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.ToggleBrowserSharing({
-          enabled: true
-        }));
-      });
-    });
   });
 });
--- a/browser/extensions/loop/chrome/content/panels/test/conversation_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/conversation_test.js
@@ -1,20 +1,19 @@
 /* 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/. */
 
 describe("loop.conversation", function() {
   "use strict";
 
   var FeedbackView = loop.feedbackViews.FeedbackView;
-  var expect = chai.expect;
   var TestUtils = React.addons.TestUtils;
   var sharedActions = loop.shared.actions;
-  var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet, remoteCursorStore, dispatcher;
+  var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet;
 
   beforeEach(function() {
     sandbox = LoopMochaUtils.createSandbox();
     setLoopPrefStub = sandbox.stub();
 
     LoopMochaUtils.stubLoopRequest({
       GetDoNotDisturb: function() { return true; },
       GetAllStrings: function() {
@@ -72,24 +71,16 @@ describe("loop.conversation", function()
     // Bug 1040968
     mozL10nGet = sandbox.stub(document.mozL10n, "get", function(x) {
       return x;
     });
     document.mozL10n.initialize({
       getStrings: function() { return JSON.stringify({ textContent: "fakeText" }); },
       locale: "en_US"
     });
-
-    dispatcher = new loop.Dispatcher();
-
-    remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
-      sdkDriver: {}
-    });
-
-    loop.store.StoreMixin.register({ remoteCursorStore: remoteCursorStore });
   });
 
   afterEach(function() {
     loop.shared.mixins.setRootObject(window);
     sandbox.restore();
     LoopMochaUtils.restore();
   });
 
@@ -142,43 +133,44 @@ describe("loop.conversation", function()
       sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
         new loop.shared.actions.GetWindowData({
           windowId: "42"
         }));
     });
   });
 
   describe("AppControllerView", function() {
-    var activeRoomStore, ccView;
+    var activeRoomStore, ccView, dispatcher;
     var conversationAppStore, roomStore, feedbackPeriodMs = 15770000000;
     var ROOM_STATES = loop.store.ROOM_STATES;
 
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         React.createElement(loop.conversation.AppControllerView, {
           roomStore: roomStore,
           dispatcher: dispatcher
         }));
     }
 
     beforeEach(function() {
+      dispatcher = new loop.Dispatcher();
+
       activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, {
         mozLoop: {},
         sdkDriver: {}
       });
       roomStore = new loop.store.RoomStore(dispatcher, {
         activeRoomStore: activeRoomStore,
         constants: {}
       });
       conversationAppStore = new loop.store.ConversationAppStore({
         activeRoomStore: activeRoomStore,
         dispatcher: dispatcher,
         feedbackPeriod: 42,
-        feedbackTimestamp: 42,
-        facebookEnabled: false
+        feedbackTimestamp: 42
       });
 
       loop.store.StoreMixin.register({
         conversationAppStore: conversationAppStore
       });
     });
 
     afterEach(function() {
@@ -186,38 +178,20 @@ describe("loop.conversation", function()
     });
 
     it("should display the RoomView for rooms", function() {
       conversationAppStore.setStoreState({ windowType: "room" });
       activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
 
       ccView = mountTestComponent();
 
-      var desktopRoom = TestUtils.findRenderedComponentWithType(ccView,
+      TestUtils.findRenderedComponentWithType(ccView,
         loop.roomViews.DesktopRoomConversationView);
-
-      expect(desktopRoom.props.facebookEnabled).to.eql(false);
     });
 
-    it("should pass the correct value of facebookEnabled to DesktopRoomConversationView",
-      function() {
-        conversationAppStore.setStoreState({
-          windowType: "room",
-          facebookEnabled: true
-        });
-        activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
-
-        ccView = mountTestComponent();
-
-        var desktopRoom = TestUtils.findRenderedComponentWithType(ccView,
-            loop.roomViews.DesktopRoomConversationView);
-
-        expect(desktopRoom.props.facebookEnabled).to.eql(true);
-      });
-
     it("should display the RoomFailureView for failures", function() {
       conversationAppStore.setStoreState({
         outgoing: false,
         windowType: "failed"
       });
 
       ccView = mountTestComponent();
 
--- a/browser/extensions/loop/chrome/content/panels/test/index.html
+++ b/browser/extensions/loop/chrome/content/panels/test/index.html
@@ -55,17 +55,16 @@
   <script src="/add-on/shared/js/textChatStore.js"></script>
   <script src="/add-on/shared/js/textChatView.js"></script>
   <script src="/add-on/panels/js/conversationAppStore.js"></script>
   <script src="/add-on/panels/js/roomStore.js"></script>
   <script src="/add-on/panels/js/roomViews.js"></script>
   <script src="/add-on/panels/js/feedbackViews.js"></script>
   <script src="/add-on/panels/js/conversation.js"></script>
   <script src="/add-on/panels/js/panel.js"></script>
-  <script src="/add-on/shared/js/remoteCursorStore.js"></script>
 
   <!-- Test scripts -->
   <script src="conversationAppStore_test.js"></script>
   <script src="conversation_test.js"></script>
   <script src="feedbackViews_test.js"></script>
   <script src="panel_test.js"></script>
   <script src="roomViews_test.js"></script>
   <script src="l10n_test.js"></script>
--- a/browser/extensions/loop/chrome/content/panels/test/panel_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/panel_test.js
@@ -7,18 +7,17 @@ describe("loop.panel", function() {
 
   var expect = chai.expect;
   var TestUtils = React.addons.TestUtils;
   var sharedActions = loop.shared.actions;
 
   var sandbox, notifications, requestStubs;
   var fakeXHR, fakeWindow, fakeEvent;
   var requests = [];
-  var roomData, roomData2, roomData3, roomData4, roomData5, roomData6;
-  var roomList, roomName;
+  var roomData, roomData2, roomList, roomName;
 
   beforeEach(function() {
     sandbox = LoopMochaUtils.createSandbox();
     fakeXHR = sandbox.useFakeXMLHttpRequest();
     requests = [];
     // https://github.com/cjohansen/Sinon.JS/issues/393
     fakeXHR.xhr.onCreate = function(xhr) {
       requests.push(xhr);
@@ -66,41 +65,37 @@ describe("loop.panel", function() {
       },
       "Rooms:GetAll": function() {
         return [];
       },
       "Rooms:PushSubscription": sinon.stub(),
       Confirm: sinon.stub(),
       GetHasEncryptionKey: function() { return true; },
       HangupAllChatWindows: function() {},
-      IsMultiProcessActive: sinon.stub(),
+      IsMultiProcessEnabled: sinon.stub(),
       LoginToFxA: sinon.stub(),
       LogoutFromFxA: sinon.stub(),
       NotifyUITour: sinon.stub(),
       OpenURL: sinon.stub(),
       GetSelectedTabMetadata: sinon.stub().returns({}),
       GetUserProfile: function() { return null; }
     });
 
     loop.storedRequests = {
       GetFxAEnabled: true,
       GetHasEncryptionKey: true,
       GetUserProfile: null,
       GetDoNotDisturb: false,
       "GetLoopPref|gettingStarted.latestFTUVersion": 1,
       "GetLoopPref|legal.ToS_url": "",
       "GetLoopPref|legal.privacy_url": "",
-      "GetLoopPref|remote.autostart": false,
-      IsMultiProcessActive: false
+      IsMultiProcessEnabled: false
     };
 
     roomName = "First Room Name";
-    // XXX Multiple rooms needed for some tests, could clean up this code to
-    // utilize a function that generates a given number of rooms for use in
-    // those tests
     roomData = {
       roomToken: "QzBbvGmIZWU",
       roomUrl: "http://sample/QzBbvGmIZWU",
       decryptedContext: {
         roomName: roomName,
         urls: [{
           location: "http://testurl.com"
         }]
@@ -130,88 +125,16 @@ describe("loop.panel", function() {
           roomConnectionId: "2a1737a6-4a73-43b5-ae3e-906ec1e763cb"
       }, {
           displayName: "Bob",
             roomConnectionId: "781f212b-f1ea-4ce1-9105-7cfc36fb4ec7"
         }],
       ctime: 1405517417
     };
 
-    roomData3 = {
-      roomToken: "QzBbvlmIZWV",
-      roomUrl: "http://sample/QzBbvlmIZWV",
-      decryptedContext: {
-        roomName: "Second Room Name"
-      },
-      maxSize: 2,
-      participants: [{
-        displayName: "Bill",
-        account: "bill@example.com",
-        roomConnectionId: "2a1737a6-4a73-43b5-ae3e-906ec1e763cc"
-      }, {
-        displayName: "Bob",
-        roomConnectionId: "781f212b-f1ea-4ce1-9105-7cfc36fb4ec7"
-      }],
-      ctime: 1405517417
-    };
-
-    roomData4 = {
-      roomToken: "QzBbvlmIZWW",
-      roomUrl: "http://sample/QzBbvlmIZWW",
-      decryptedContext: {
-        roomName: "Second Room Name"
-      },
-      maxSize: 2,
-      participants: [{
-        displayName: "Bill",
-        account: "bill@example.com",
-        roomConnectionId: "2a1737a6-4a73-43b5-ae3e-906ec1e763cc"
-      }, {
-        displayName: "Bob",
-        roomConnectionId: "781f212b-f1ea-4ce1-9105-7cfc36fb4ec7"
-      }],
-      ctime: 1405517417
-    };
-
-    roomData5 = {
-      roomToken: "QzBbvlmIZWX",
-      roomUrl: "http://sample/QzBbvlmIZWX",
-      decryptedContext: {
-        roomName: "Second Room Name"
-      },
-      maxSize: 2,
-      participants: [{
-        displayName: "Bill",
-        account: "bill@example.com",
-        roomConnectionId: "2a1737a6-4a73-43b5-ae3e-906ec1e763cc"
-      }, {
-        displayName: "Bob",
-        roomConnectionId: "781f212b-f1ea-4ce1-9105-7cfc36fb4ec7"
-      }],
-      ctime: 1405517417
-    };
-
-    roomData6 = {
-      roomToken: "QzBbvlmIZWY",
-      roomUrl: "http://sample/QzBbvlmIZWY",
-      decryptedContext: {
-        roomName: "Second Room Name"
-      },
-      maxSize: 2,
-      participants: [{
-        displayName: "Bill",
-        account: "bill@example.com",
-        roomConnectionId: "2a1737a6-4a73-43b5-ae3e-906ec1e763cc"
-      }, {
-        displayName: "Bob",
-        roomConnectionId: "781f212b-f1ea-4ce1-9105-7cfc36fb4ec7"
-      }],
-      ctime: 1405517417
-    };
-
     roomList = [new loop.store.Room(roomData), new loop.store.Room(roomData2)];
 
     document.mozL10n.initialize({
       getStrings: function() {
         return JSON.stringify({ textContent: "fakeText" });
       },
       locale: "en-US"
     });
@@ -681,19 +604,18 @@ describe("loop.panel", function() {
         try {
           TestUtils.findRenderedComponentWithType(view, loop.panel.SignInRequestView);
           sinon.assert.fail("Should not find the GettingStartedView if it has been seen");
         } catch (ex) {
           // Do nothing
         }
       });
 
-      it("should render a E10sNotSupported when multiprocess is enabled and active", function() {
-        loop.storedRequests.IsMultiProcessActive = true;
-        loop.storedRequests["GetLoopPref|remote.autostart"] = false;
+      it("should render a E10sNotSupported when multiprocess is enabled", function() {
+        loop.storedRequests.IsMultiProcessEnabled = true;
 
         var view = createTestPanelView();
 
         expect(function() {
           TestUtils.findRenderedComponentWithType(view, loop.panel.E10sNotSupported);
         }).to.not.Throw();
       });
 
@@ -1039,20 +961,19 @@ describe("loop.panel", function() {
 
       sinon.assert.notCalled(fakeWindow.close);
 
       roomStore.setStoreState({ pendingCreation: false });
 
       sinon.assert.calledOnce(fakeWindow.close);
     });
 
-    it("should have room-list-empty element and not room-list element when no rooms", function() {
+    it("should not render the room list view when no rooms available", function() {
       var view = createTestComponent();
       var node = view.getDOMNode();
-      expect(node.querySelectorAll(".room-list-empty").length).to.eql(1);
       expect(node.querySelectorAll(".room-list").length).to.eql(0);
     });
 
     it("should display a loading animation when rooms are pending", function() {
       var view = createTestComponent();
       roomStore.setStoreState({ pendingInitialRetrieval: true });
 
       expect(view.getDOMNode().querySelectorAll(".room-list-loading").length).to.eql(1);
@@ -1063,62 +984,16 @@ describe("loop.panel", function() {
 
       var view = createTestComponent();
 
       var node = view.getDOMNode();
       expect(node.querySelectorAll(".room-opened").length).to.eql(0);
       expect(node.querySelectorAll(".room-entry").length).to.eql(2);
     });
 
-    it("should show gradient when more than 5 rooms in list", function() {
-      var sixRoomList = [
-        new loop.store.Room(roomData),
-        new loop.store.Room(roomData2),
-        new loop.store.Room(roomData3),
-        new loop.store.Room(roomData4),
-        new loop.store.Room(roomData5),
-        new loop.store.Room(roomData6)
-      ];
-      roomStore.setStoreState({ rooms: sixRoomList });
-
-      var view = createTestComponent();
-
-      var node = view.getDOMNode();
-      expect(node.querySelectorAll(".room-entry").length).to.eql(6);
-      expect(node.querySelectorAll(".room-list-blur").length).to.eql(1);
-    });
-
-    it("should not show gradient when 5 or less rooms in list", function() {
-      var fiveRoomList = [
-        new loop.store.Room(roomData),
-        new loop.store.Room(roomData2),
-        new loop.store.Room(roomData3),
-        new loop.store.Room(roomData4),
-        new loop.store.Room(roomData5)
-      ];
-      roomStore.setStoreState({ rooms: fiveRoomList });
-
-      var view = createTestComponent();
-
-      var node = view.getDOMNode();
-      expect(node.querySelectorAll(".room-entry").length).to.eql(5);
-      expect(node.querySelectorAll(".room-list-blur").length).to.eql(0);
-    });
-
-    it("should not show gradient when no rooms in list", function() {
-      var zeroRoomList = [];
-      roomStore.setStoreState({ rooms: zeroRoomList });
-
-      var view = createTestComponent();
-
-      var node = view.getDOMNode();
-      expect(node.querySelectorAll(".room-entry").length).to.eql(0);
-      expect(node.querySelectorAll(".room-list-blur").length).to.eql(0);
-    });
-
     it("should only show the opened room you're in when you're in a room", function() {
       roomStore.setStoreState({ rooms: roomList, openedRoom: roomList[0].roomToken });
 
       var view = createTestComponent();
 
       var node = view.getDOMNode();
       expect(node.querySelectorAll(".room-opened").length).to.eql(1);
       expect(node.querySelectorAll(".room-entry").length).to.eql(1);
--- a/browser/extensions/loop/chrome/content/panels/test/roomStore_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/roomStore_test.js
@@ -38,16 +38,20 @@ describe("loop.store.RoomStore", functio
           },
           ROOM_CREATE: {
             CREATE_SUCCESS: 0,
             CREATE_FAIL: 1
           },
           ROOM_DELETE: {
             DELETE_SUCCESS: 0,
             DELETE_FAIL: 1
+          },
+          ROOM_CONTEXT_ADD: {
+            ADD_FROM_PANEL: 0,
+            ADD_FROM_CONVERSATION: 1
           }
         };
       },
       CopyString: sinon.stub(),
       GetLoopPref: function(prefName) {
         if (prefName === "debug.dispatcher") {
           return false;
         }
@@ -233,16 +237,30 @@ describe("loop.store.RoomStore", functio
       it("should clear any existing room errors", function() {
         store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
 
         sinon.assert.calledOnce(fakeNotifications.remove);
         sinon.assert.calledWithExactly(fakeNotifications.remove,
           "create-room-error");
       });
 
+      it("should log a telemetry event when the operation with context is successful", function() {
+        fakeRoomCreationData.urls = [{
+          location: "http://invalid.com",
+          description: "fakeSite",
+          thumbnail: "fakeimage.png"
+        }];
+
+        store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
+
+        sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
+        sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
+          "LOOP_ROOM_CONTEXT_ADD", 0);
+      });
+
       it("should request creation of a new room", function() {
         store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
 
         sinon.assert.calledWith(requestStubs["Rooms:Create"], {
           decryptedContext: { },
           maxSize: store.maxRoomCreationSize
         });
       });
--- a/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
@@ -184,17 +184,16 @@ describe("loop.roomViews", function() {
       expect(fakeAudio.loop).to.equal(false);
     });
   });
 
   describe("DesktopRoomInvitationView", function() {
     function mountTestComponent(props) {
       props = _.extend({
         dispatcher: dispatcher,
-        facebookEnabled: false,
         roomData: { roomUrl: "http://invalid" },
         savingContext: false,
         show: true,
         showEditContext: false
       }, props);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.roomViews.DesktopRoomInvitationView, props));
     }
@@ -235,41 +234,20 @@ describe("loop.roomViews", function() {
         sinon.assert.calledWith(dispatcher.dispatch,
           new sharedActions.EmailRoomUrl({
             roomUrl: url,
             roomDescription: "www.mozilla.com",
             from: "conversation"
           }));
       });
 
-    it("should not display the Facebook Share button when it is disabled in prefs",
-      function() {
-        view = mountTestComponent({
-          facebookEnabled: false
-        });
-
-        expect(view.getDOMNode().querySelectorAll(".btn-facebook"))
-          .to.have.length.of(0);
-      });
-
-    it("should display the Facebook Share button only when it is enabled in prefs",
-      function() {
-        view = mountTestComponent({
-          facebookEnabled: true
-        });
-
-        expect(view.getDOMNode().querySelectorAll(".btn-facebook"))
-          .to.have.length.of(1);
-      });
-
     it("should dispatch a FacebookShareRoomUrl action when the facebook button is clicked",
       function() {
         var url = "http://invalid";
         view = mountTestComponent({
-          facebookEnabled: true,
           roomData: {
             roomUrl: url
           }
         });
 
         var facebookBtn = view.getDOMNode().querySelector(".btn-facebook");
 
         React.addons.TestUtils.Simulate.click(facebookBtn);
@@ -348,17 +326,16 @@ describe("loop.roomViews", function() {
 
       activeRoomStore.setStoreState({ roomUrl: "http://invalid " });
     });
 
     function mountTestComponent(props) {
       props = _.extend({
         chatWindowDetached: false,
         dispatcher: dispatcher,
-        facebookEnabled: false,
         roomStore: roomStore,
         onCallTerminated: onCallTerminatedStub
       }, props);
       return TestUtils.renderIntoDocument(
         React.createElement(loop.roomViews.DesktopRoomConversationView, props));
     }
 
     it("should NOT show the context menu on right click", function() {
--- a/browser/extensions/loop/chrome/content/preferences/prefs.js
+++ b/browser/extensions/loop/chrome/content/preferences/prefs.js
@@ -1,16 +1,17 @@
 pref("loop.enabled", true);
-pref("loop.remote.autostart", false);
+pref("loop.textChat.enabled", true);
 pref("loop.server", "https://loop.services.mozilla.com/v0");
 pref("loop.linkClicker.url", "https://hello.firefox.com/");
 pref("loop.gettingStarted.latestFTUVersion", 1);
 pref("loop.facebook.shareUrl", "https://www.facebook.com/sharer/sharer.php?u=%ROOM_URL%");
 pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
 pref("loop.gettingStarted.resumeOnFirstJoin", false);
+pref("loop.learnMoreUrl", "https://www.firefox.com/hello/");
 pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
 pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
 pref("loop.do_not_disturb", false);
 pref("loop.retry_delay.start", 60000);
 pref("loop.retry_delay.limit", 300000);
 pref("loop.ping.interval", 1800000);
 pref("loop.ping.timeout", 10000);
 pref("loop.debug.loglevel", "Error");
@@ -24,13 +25,9 @@ pref("loop.feedback.manualFormURL", "htt
 #ifdef DEBUG
 pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
 #else
 pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
 #endif
 pref("loop.fxa_oauth.tokendata", "");
 pref("loop.fxa_oauth.profile", "");
 pref("loop.support_url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cobrowsing");
-#ifdef LOOP_BETA
-pref("loop.facebook.enabled", true);
-#else
-pref("loop.facebook.enabled", false);
-#endif
+pref("loop.browserSharing.showInfoBar", true);
new file mode 100644
--- /dev/null
+++ b/browser/extensions/loop/chrome/content/shared/README.md
@@ -0,0 +1,12 @@
+Loop Shared Web Assets
+======================
+
+This directory contains web assets shared across the Loop client webapp and the
+Loop Firefox Component.
+
+Warning
+-------
+
+Any modification in these files will have possible side effects on both the
+Firefox component and the webapp. The `css/readme.html` file uses all the shared
+styles, you should use it as a way of checking for visual regressions.
--- a/browser/extensions/loop/chrome/content/shared/css/common.css
+++ b/browser/extensions/loop/chrome/content/shared/css/common.css
@@ -586,24 +586,8 @@ html[dir="rtl"] .context-wrapper > .cont
   font-weight: 700;
   clear: both;
 }
 
 /* Only underline the url, not the associated text */
 .clicks-allowed.context-wrapper:hover > .context-info > .context-url {
   text-decoration: underline;
 }
-
-.remote-video-box {
-  height: 100%;
-  width: 100%;
-}
-
-.remote-video-box > .remote-cursor {
-  background: url('../img/cursor.svg#orange') no-repeat;
-  height: 20px;
-  /* Svg cursor has a white outline so we need to offset it to ensure that the
-   * cursor points at a more precise position with the negative margin. */
-  margin: -2px;
-  position: absolute;
-  width: 15px;
-  z-index: 65534;
-}
--- a/browser/extensions/loop/chrome/content/shared/css/conversation.css
+++ b/browser/extensions/loop/chrome/content/shared/css/conversation.css
@@ -533,40 +533,40 @@ body[platform="win"] .share-service-drop
 }
 
 .media-wrapper > .local {
   flex: 0 1 auto;
   width: 200px;
   height: 150px;
 }
 
-.media-wrapper > .local .local-video,
-.media-wrapper > .focus-stream > .local .local-video {
+.media-wrapper > .local > .local-video,
+.media-wrapper > .focus-stream > .local > .local-video {
   width: 100%;
   height: 100%;
   /* Transform is to make the local video act like a mirror, as is the
      convention in video conferencing systems. */
   transform: scale(-1, 1);
   transform-origin: 50% 50% 0;
 }
 
 .media-wrapper > .remote {
   /* Works around an issue with object-fit: cover in Google Chrome - it doesn't
      currently crop but overlaps the surrounding elements.
      https://code.google.com/p/chromium/issues/detail?id=400829 */
   overflow: hidden;
 }
 
-.media-wrapper > .remote .remote-video {
+.media-wrapper > .remote > .remote-video {
   object-fit: cover;
   width: 100%;
   height: 100%;
 }
 
-.media-wrapper > .screen .screen-share-video {
+.media-wrapper > .screen > .screen-share-video {
   width: 100%;
   height: 100%;
 }
 
 /* Note: we can't use "display: flex;" for the text-chat-view itself,
    as this lets it overflow the expected column heights, and we can't
    fix its height. */
 .media-wrapper > .text-chat-view {
@@ -685,17 +685,17 @@ body[platform="win"] .share-service-drop
     flex: 1 1 auto;
     height: 20%;
     /* Ensure no previously specified widths take effect, and we take up no more
        than half the width. */
     width: auto;
     max-width: 50%;
   }
 
-  .media-wrapper.receiving-screen-share > .remote .remote-video {
+  .media-wrapper.receiving-screen-share > .remote > .remote-video {
       /* Reset the object-fit for this. */
     object-fit: contain;
   }
 
   .media-wrapper.receiving-screen-share > .local {
     /* Screen shares have remote & local video side-by-side on narrow screens */
     order: 3;
     flex: 1 1 auto;
deleted file mode 100644
--- a/browser/extensions/loop/chrome/content/shared/img/cursor.svg
+++ /dev/null
@@ -1,1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:x="http://www.w3.org/1999/xlink" viewBox="0 0 15 20"><style>use,:target~#orange{display:none}#orange,:target{display:inherit}#orange{fill:#f7a25e}#blue{fill:#00a9dc}</style><defs><g id="cursor"><path d="M3.387 12.783l2.833 5.009.469.829.851-.428 1.979-.995h-.001l.938-.472-.517-.914-2.553-4.513h5.244l-1.966-1.747-9-8-1.664-1.479v17.227-.001l1.8-2.4 1.587-2.116z" fill="#fff"/><path fill-rule="evenodd" d="M3.505 10.96l3.586 6.34 1.979-.995-3.397-6.005h4.327l-9-8v12l2.505-3.34z"/></g></defs><use id="blue" x:href="#cursor"/><use id="orange" x:href="#cursor"/></svg>
\ No newline at end of file
--- a/browser/extensions/loop/chrome/content/shared/js/actions.js
+++ b/browser/extensions/loop/chrome/content/shared/js/actions.js
@@ -126,25 +126,16 @@ loop.shared.actions = (function() {
     ReceivedTextChatMessage: Action.define("receivedTextChatMessage", {
       contentType: String,
       message: String,
       receivedTimestamp: String
       // sentTimestamp: String (optional)
     }),
 
     /**
-     * Notifies that cursor data has been received from the other peer.
-     */
-    ReceivedCursorData: Action.define("receivedCursorData", {
-      ratioX: Number,
-      ratioY: Number,
-      type: String
-    }),
-
-    /**
      * Used by the ongoing views to notify stores about the elements
      * required for the sdk.
      */
     SetupStreamElements: Action.define("setupStreamElements", {
       // The configuration for the publisher/subscribe options
       publisherConfig: Object
     }),
 
@@ -172,23 +163,16 @@ loop.shared.actions = (function() {
      */
     VideoDimensionsChanged: Action.define("videoDimensionsChanged", {
       isLocal: Boolean,
       videoType: String,
       dimensions: Object
     }),
 
     /**
-     * Used for notifying that the hasVideo property of the screen stream, has changed.
-     */
-    VideoScreenStreamChanged: Action.define("videoScreenStreamChanged", {
-      hasVideo: Boolean
-    }),
-
-    /**
      * A stream from local or remote media has been created.
      */
     MediaStreamCreated: Action.define("mediaStreamCreated", {
       hasVideo: Boolean,
       isLocal: Boolean,
       srcMediaElement: Object
     }),
 
@@ -229,24 +213,16 @@ loop.shared.actions = (function() {
 
     /**
      * Used to end a screen share.
      */
     EndScreenShare: Action.define("endScreenShare", {
     }),
 
     /**
-     * Used to mute or unmute a screen share.
-     */
-    ToggleBrowserSharing: Action.define("toggleBrowserSharing", {
-      // Whether or not to enable the stream.
-      enabled: Boolean
-    }),
-
-    /**
      * Used to notify that screen sharing is active or not.
      */
     ScreenSharingState: Action.define("screenSharingState", {
       // One of loop.shared.utils.SCREEN_SHARE_STATES.
       state: String
     }),
 
     /**
--- a/browser/extensions/loop/chrome/content/shared/js/linkifiedTextView.js
+++ b/browser/extensions/loop/chrome/content/shared/js/linkifiedTextView.js
@@ -73,38 +73,32 @@ loop.shared.views.LinkifiedTextView = fu
      * @param {String} s the raw string to be parsed
      *
      * @returns {Array} of strings and React <a> elements in order.
      */
     parseStringToElements: function (s) {
       var elements = [];
       var result = loop.shared.urlRegExps.fullUrlMatch.exec(s);
       var reactElementsCounter = 0; // For giving keys to each ReactElement.
-      var sanitizeURL;
+
       while (result) {
         // If there's text preceding the first link, push it onto the array
         // and update the string pointer.
         if (result.index) {
           elements.push(s.substr(0, result.index));
           s = s.substr(result.index);
         }
 
         // Push the first link itself, and advance the string pointer again.
-        // Bug 1196143 - formatURL sanitizes(decodes) the URL from IDN homographic attacks.
-        sanitizeURL = loop.shared.utils.formatURL(result[0]);
-        if (sanitizeURL && sanitizeURL.location) {
-          elements.push(React.createElement(
-            "a",
-            _extends({}, this._generateLinkAttributes(sanitizeURL.location), {
-              key: reactElementsCounter++ }),
-            sanitizeURL.location
-          ));
-        } else {
-          elements.push(result[0]);
-        }
+        elements.push(React.createElement(
+          "a",
+          _extends({}, this._generateLinkAttributes(result[0]), {
+            key: reactElementsCounter++ }),
+          result[0]
+        ));
         s = s.substr(result[0].length);
 
         // Check for another link, and perhaps continue...
         result = loop.shared.urlRegExps.fullUrlMatch.exec(s);
       }
 
       if (s) {
         elements.push(s);
--- a/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
+++ b/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
@@ -5,17 +5,16 @@
 var loop = loop || {};
 loop.OTSdkDriver = (function() {
   "use strict";
 
   var sharedActions = loop.shared.actions;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var STREAM_PROPERTIES = loop.shared.utils.STREAM_PROPERTIES;
   var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
-  var CURSOR_MESSAGE_TYPES = loop.shared.utils.CURSOR_MESSAGE_TYPES;
 
   /**
    * This is a wrapper for the OT sdk. It is used to translate the SDK events into
    * actions, and instruct the SDK what to do as a result of actions.
    */
   var OTSdkDriver = function(options) {
     if (!options.constants) {
       throw new Error("Missing option constants");
@@ -37,18 +36,17 @@ loop.OTSdkDriver = (function() {
     this.connections = {};
 
     // Setup the metrics object to keep track of the number of connections we have
     // and the amount of streams.
     this._resetMetrics();
 
     this.dispatcher.register(this, [
       "setupStreamElements",
-      "setMute",
-      "toggleBrowserSharing"
+      "setMute"
     ]);
 
     // Set loop.debug.twoWayMediaTelemetry to true in the browser
     // by changing the hidden pref loop.debug.twoWayMediaTelemetry using
     // about:config, or use
     //
     // localStorage.setItem("debug.twoWayMediaTelemetry", true);
     loop.shared.utils.getBoolPreference("debug.twoWayMediaTelemetry", function(enabled) {
@@ -94,20 +92,17 @@ loop.OTSdkDriver = (function() {
      * Returns the required data channel settings.
      */
     get _getDataChannelSettings() {
       return {
         channels: {
           // We use a single channel for text. To make things simpler, we
           // always send on the publisher channel, and receive on the subscriber
           // channel.
-          text: {},
-          cursor: {
-            reliable: true
-          }
+          text: {}
         }
       };
     },
 
     /**
      * Resets the metrics for the driver.
      */
     _resetMetrics: function() {
@@ -190,16 +185,18 @@ loop.OTSdkDriver = (function() {
 
       this._mockScreenSharePreviewEl = document.createElement("div");
 
       this.screenshare = this.sdk.initPublisher(this._mockScreenSharePreviewEl,
         config, this._onScreenSharePublishComplete.bind(this));
       this.screenshare.on("accessAllowed", this._onScreenShareGranted.bind(this));
       this.screenshare.on("accessDenied", this._onScreenSharePublishError.bind(this));
       this.screenshare.on("streamCreated", this._onScreenShareStreamCreated.bind(this));
+
+      this._noteSharingState(options.videoSource, true);
     },
 
     /**
      * Initiates switching the browser window that is being shared.
      *
      * @param {Integer} windowId  The windowId of the browser.
      */
     switchAcquiredWindow: function(windowId) {
@@ -224,31 +221,22 @@ loop.OTSdkDriver = (function() {
 
       this._notifyMetricsEvent("Publisher.streamDestroyed");
 
       this.session.unpublish(this.screenshare);
       this.screenshare.off("accessAllowed accessDenied streamCreated");
       this.screenshare.destroy();
       delete this.screenshare;
       delete this._mockScreenSharePreviewEl;
+      this._noteSharingState(this._windowId ? "browser" : "window", false);
       delete this._windowId;
       return true;
     },
 
     /**
-     * Paused or resumes an active screenshare session as appropriate.
-     *
-     * @param {sharedActions.ToggleBrowserSharing} actionData The data associated with the
-     *                                             action. See action.js.
-     */
-    toggleBrowserSharing: function(actionData) {
-      this.screenshare.publishVideo(actionData.enabled);
-    },
-
-    /**
      * Connects a session for the SDK, listening to the required events.
      *
      * sessionData items:
      * - sessionId: The OT session ID
      * - apiKey: The OT API key
      * - sessionToken: The token for the OT session
      * - sendTwoWayMediaTelemetry: boolean should we send telemetry on length
      *                             of media sessions.  Callers should ensure
@@ -680,68 +668,46 @@ loop.OTSdkDriver = (function() {
         type: "readyForDataChannel",
         to: sdkSubscriberObject.stream.connection
       }, function(signalError) {
         if (signalError) {
           console.error(signalError);
         }
       });
 
-      // Set up data channels with a given type and message/channel handlers.
-      var dataChannels = [
-        ["text",
-         function(message) {
-           // Append the timestamp. This is the time that gets shown.
-           message.receivedTimestamp = (new Date()).toISOString();
-           this.dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage(message));
-         }.bind(this),
-         function(channel) {
-           this._subscriberChannel = channel;
-           this._checkDataChannelsAvailable();
-         }.bind(this)],
-        ["cursor",
-         function(message) {
-           switch (message.type) {
-             case CURSOR_MESSAGE_TYPES.POSITION:
-               this.dispatcher.dispatch(new sharedActions.ReceivedCursorData(message));
-               break;
-           }
-         }.bind(this),
-         function(channel) {
-           this._subscriberCursorChannel = channel;
-         }.bind(this)]
-      ];
+      sdkSubscriberObject._.getDataChannel("text", {}, function(err, channel) {
+        // Sends will queue until the channel is fully open.
+        if (err) {
+          console.error(err);
+          this._notifyMetricsEvent("sdk.datachannel.sub." + err.message);
+          return;
+        }
+
+        channel.on({
+          message: function(ev) {
+            try {
+              var message = JSON.parse(ev.data);
+              /* Append the timestamp. This is the time that gets shown. */
+              message.receivedTimestamp = (new Date()).toISOString();
 
-      dataChannels.forEach(function(args) {
-        var type = args[0], onMessage = args[1], onChannel = args[2];
-        sdkSubscriberObject._.getDataChannel(type, {}, function(err, channel) {
-          // Sends will queue until the channel is fully open.
-          if (err) {
-            console.error(err);
-            this._notifyMetricsEvent("sdk.datachannel.sub." + type + "." + err.message);
-            return;
-          }
+              this.dispatcher.dispatch(
+                new sharedActions.ReceivedTextChatMessage(message));
+            } catch (ex) {
+              console.error("Failed to process incoming chat message", ex);
+            }
+          }.bind(this),
 
-          channel.on({
-            message: function(ev) {
-              try {
-                var message = JSON.parse(ev.data);
-                onMessage(message);
-              } catch (ex) {
-                console.error("Failed to process incoming chat message", ex);
-              }
-            },
+          close: function() {
+            // XXX We probably want to dispatch and handle this somehow.
+            console.log("Subscribed data channel closed!");
+          }
+        });
 
-            close: function() {
-              // XXX We probably want to dispatch and handle this somehow.
-              console.log("Subscribed " + type + " data channel closed!");
-            }
-          });
-          onChannel(channel);
-        }.bind(this));
+        this._subscriberChannel = channel;
+        this._checkDataChannelsAvailable();
       }.bind(this));
     },
 
     /**
      * Handles receiving the signal that the other end of the connection
      * has subscribed to the stream and we're ready to setup the data channel.
      *
      * We create the publisher data channel when we get the signal as it means
@@ -751,47 +717,34 @@ loop.OTSdkDriver = (function() {
      */
     _onReadyForDataChannel: function() {
       // If we don't want data channels, just ignore the message. We haven't
       // send the other side a message, so it won't display anything.
       if (!this._useDataChannels) {
         return;
       }
 
-      // Set up data channels with a given type and channel handler.
-      var dataChannels = [
-        ["text",
-         function(channel) {
-           this._publisherChannel = channel;
-           this._checkDataChannelsAvailable();
-         }.bind(this)],
-         ["cursor",
-          function(channel) {
-            this._publisherCursorChannel = channel;
-          }.bind(this)]
-        ];
+      // This won't work until a subscriber exists for this publisher
+      this.publisher._.getDataChannel("text", {}, function(err, channel) {
+        if (err) {
+          console.error(err);
+          this._notifyMetricsEvent("sdk.datachannel.pub." + err.message);
+          return;
+        }
 
-      // This won't work until a subscriber exists for this publisher
-      dataChannels.forEach(function(args) {
-        var type = args[0], onChannel = args[1];
-        this.publisher._.getDataChannel(type, {}, function(err, channel) {
-          if (err) {
-            console.error(err);
-            this._notifyMetricsEvent("sdk.datachannel.pub." + type + "." + err.message);
-            return;
+        this._publisherChannel = channel;
+
+        channel.on({
+          close: function() {
+            // XXX We probably want to dispatch and handle this somehow.
+            console.log("Published data channel closed!");
           }
+        });
 
-          channel.on({
-            close: function() {
-              // XXX We probably want to dispatch and handle this somehow.
-              console.log("Published " + type + " data channel closed!");
-            }
-          });
-          onChannel(channel);
-        }.bind(this));
+        this._checkDataChannelsAvailable();
       }.bind(this));
     },
 
     /**
      * Checks to see if all channels have been obtained, and if so it dispatches
      * a notification to the stores to inform them.
      */
     _checkDataChannelsAvailable: function() {
@@ -807,30 +760,16 @@ loop.OTSdkDriver = (function() {
      *
      * @param {String} message The message to send.
      */
     sendTextChatMessage: function(message) {
       this._publisherChannel.send(JSON.stringify(message));
     },
 
     /**
-     * Sends the cursor position on the data channel.
-     *
-     * @param {String} message The message to send.
-     */
-    sendCursorMessage: function(message) {
-      if (!this._publisherCursorChannel || !this._subscriberCursorChannel) {
-        return;
-      }
-
-      message.userID = this.session.sessionId;
-      this._publisherCursorChannel.send(JSON.stringify(message));
-    },
-
-    /**
      * Handles the event when the local stream is created.
      *
      * @param {StreamEvent} event The event details:
      * https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
      */
     _onLocalStreamCreated: function(event) {
       this._notifyMetricsEvent("Publisher.streamCreated");
 
@@ -1038,31 +977,22 @@ loop.OTSdkDriver = (function() {
           break;
       }
     },
 
     /**
      * Handles publishing of property changes to a stream.
      */
     _onStreamPropertyChanged: function(event) {
-      switch (event.changedProperty) {
-        case STREAM_PROPERTIES.VIDEO_DIMENSIONS:
-          this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({
-            isLocal: event.stream.connection.id === this.session.connection.id,
-            videoType: event.stream.videoType,
-            dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS]
-          }));
-          break;
-        case STREAM_PROPERTIES.HAS_VIDEO:
-          if (event.stream.videoType === "screen") {
-            this.dispatcher.dispatch(new sharedActions.VideoScreenStreamChanged({
-              hasVideo: event.newValue
-            }));
-          }
-          break;
+      if (event.changedProperty === STREAM_PROPERTIES.VIDEO_DIMENSIONS) {
+        this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({
+          isLocal: event.stream.connection.id === this.session.connection.id,
+          videoType: event.stream.videoType,
+          dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS]
+        }));
       }
     },
 
     /**
      * Handle the (remote) VideoEnabled event from the subscriber object
      * by dispatching an action with the (hidden) video element from
      * which to copy the stream when attaching it to visible video element
      * that the views control directly.
@@ -1250,14 +1180,34 @@ loop.OTSdkDriver = (function() {
       var callLengthSeconds = (endTime - startTime) / 1000;
       this._noteConnectionLength(callLengthSeconds);
     },
 
     /**
      * If set to true, make it easy to test/verify 2-way media connection
      * telemetry code operation by viewing the logs.
      */
-    _debugTwoWayMediaTelemetry: false
+    _debugTwoWayMediaTelemetry: false,
+
+    /**
+     * Note the sharing state.
+     *
+     * @param  {String}  type    Type of sharing that was flipped. May be 'window'
+     *                           or 'browser'.
+     * @param  {Boolean} enabled Flag that tells us if the feature was flipped on
+     *                           or off.
+     * @private
+     */
+    _noteSharingState: function(type, enabled) {
+      var bucket = this._constants.SHARING_STATE_CHANGE[type.toUpperCase() + "_" +
+        (enabled ? "ENABLED" : "DISABLED")];
+      if (typeof bucket === "undefined") {
+        console.error("No sharing state bucket found for '" + type + "'");
+        return;
+      }
+
+      loop.request("TelemetryAddValue", "LOOP_SHARING_STATE_CHANGE_1", bucket);
+    }
   };
 
   return OTSdkDriver;
 
 })();
deleted file mode 100644
--- a/browser/extensions/loop/chrome/content/shared/js/remoteCursorStore.js
+++ /dev/null
@@ -1,122 +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/. */
-
-var loop = loop || {};
-loop.store = loop.store || {};
-
-loop.store.RemoteCursorStore = (function() {
-  "use strict";
-
-  var CURSOR_MESSAGE_TYPES = loop.shared.utils.CURSOR_MESSAGE_TYPES;
-
-  /**
-   * A store to handle remote cursors events.
-   */
-  var RemoteCursorStore = loop.store.createStore({
-    actions: [
-      "receivedCursorData",
-      "videoDimensionsChanged",
-      "videoScreenStreamChanged"
-    ],
-
-    /**
-     * Initializes the store.
-     *
-     * @param  {Object} options An object containing options for this store.
-     *                          It should consist of:
-     *                          - sdkDriver: The sdkDriver to use for sending
-     *                                       the cursor events.
-     */
-    initialize: function(options) {
-      options = options || {};
-
-      if (!options.sdkDriver) {
-        throw new Error("Missing option sdkDriver");
-      }
-
-      this._sdkDriver = options.sdkDriver;
-      loop.subscribe("CursorPositionChange", this._cursorPositionChangeListener.bind(this));
-    },
-
-    /**
-     * Returns initial state data for this active room.
-     */
-    getInitialStoreState: function() {
-      return {
-        realVideoSize: null,
-        remoteCursorPosition: null
-      };
-    },
-
-    /**
-     * Sends cursor position through the sdk.
-     *
-     * @param {Object} event An object containing the cursor position and stream dimensions
-     *                       It should contains:
-     *                       - ratioX: Left position. Number between 0 and 1.
-     *                       - ratioY: Top position. Number between 0 and 1.
-     */
-    _cursorPositionChangeListener: function(event) {
-      this._sdkDriver.sendCursorMessage({
-        ratioX: event.ratioX,
-        ratioY: event.ratioY,
-        type: CURSOR_MESSAGE_TYPES.POSITION
-      });
-    },
-
-    /**
-     * Receives cursor data.
-     *
-     * @param {sharedActions.receivedCursorData} actionData
-     */
-    receivedCursorData: function(actionData) {
-      switch (actionData.type) {
-        case CURSOR_MESSAGE_TYPES.POSITION:
-          // TODO: handle cursor position if it's desktop instead of standalone
-          this.setStoreState({
-            remoteCursorPosition: {
-              ratioX: actionData.ratioX,
-              ratioY: actionData.ratioY
-            }
-          });
-          break;
-      }
-    },
-
-    /**
-     * Listen to stream dimension changes.
-     *
-     * @param {sharedActions.VideoDimensionsChanged} actionData
-     */
-    videoDimensionsChanged: function(actionData) {
-      if (actionData.videoType !== "screen") {
-        return;
-      }
-
-      this.setStoreState({
-        realVideoSize: {
-          height: actionData.dimensions.height,
-          width: actionData.dimensions.width
-        }
-      });
-    },
-
-    /**
-     * Listen to screen stream changes.
-     *
-     * @param {sharedActions.VideoScreenStreamChanged} actionData
-     */
-    videoScreenStreamChanged: function(actionData) {
-      if (actionData.hasVideo) {
-        return;
-      }
-
-      this.setStoreState({
-        remoteCursorPosition: null
-      });
-    }
-  });
-
-  return RemoteCursorStore;
-})();
--- a/browser/extensions/loop/chrome/content/shared/js/utils.js
+++ b/browser/extensions/loop/chrome/content/shared/js/utils.js
@@ -104,20 +104,16 @@ var inChrome = typeof Components != "und
 
   var CHAT_CONTENT_TYPES = {
     CONTEXT: "chat-context",
     TEXT: "chat-text",
     ROOM_NAME: "room-name",
     CONTEXT_TILE: "context-tile"
   };
 
-  var CURSOR_MESSAGE_TYPES = {
-    POSITION: "cursor-position"
-  };
-
   /**
    * Format a given date into an l10n-friendly string.
    *
    * @param {Integer} The timestamp in seconds to format.
    * @return {String} The formatted string.
    */
   function formatDate(timestamp) {
     var date = (new Date(timestamp * 1000));
@@ -366,47 +362,38 @@ var inChrome = typeof Components != "und
       hash: window.location.hash,
       pathname: window.location.pathname
     };
   }
 
   /**
    * Formats a url for display purposes. This includes converting the
    * domain to punycode, and then decoding the url.
-   * Intended to be used for both display and (uglier in confusing cases) clickthrough,
-   * as described by dveditz in comment 12 of the bug 1196143,
-   * as well as testing the behavior case in the browser.
    *
-   * @param {String}  url                   The url to format.
-   * @param {String}  suppressConsoleError  For testing, call with a boolean which is true to squash the default console error.
-   * @return {Object}                       An object containing the hostname and full location.
+   * @param {String} url The url to format.
+   * @return {Object}    An object containing the hostname and full location.
    */
-  function formatURL(url, suppressConsoleError) {
+  function formatURL(url) {
     // We're using new URL to pass this through the browser's ACE/punycode
     // processing system. If the browser considers a url to need to be
     // punycode encoded for it to be displayed, then new URL will do that for
     // us. This saves us needing our own punycode library.
-    // Note that URL does canonicalize hostname-only URLs,
-    // adding a slash to them, but this is ok for at least HTTP(S)
-    // because GET always has to specify a path, which will (by default) be
     var urlObject;
     try {
       urlObject = new URL(url);
-      // Finally, ensure we look good.
-      return {
-        hostname: urlObject.hostname,
-        location: decodeURI(urlObject.href)
-      };
     } catch (ex) {
-      if (suppressConsoleError ? !suppressConsoleError : true) {
-        console.log("Error occurred whilst parsing URL: ", ex);
-        console.trace();
-      }
+      console.error("Error occurred whilst parsing URL:", ex);
       return null;
     }
+
+    // Finally, ensure we look good.
+    return {
+      hostname: urlObject.hostname,
+      location: decodeURI(urlObject.href)
+    };
   }
 
   /**
    * Generates and opens a mailto: url with call URL information prefilled.
    * Note: This only works for Desktop.
    *
    * @param {String} callUrl              The call URL.
    * @param {String} [recipient]          The recipient email address (optional).
@@ -757,17 +744,16 @@ var inChrome = typeof Components != "und
     }
 
     return node;
   }
 
   this.utils = {
     CALL_TYPES: CALL_TYPES,
     CHAT_CONTENT_TYPES: CHAT_CONTENT_TYPES,
-    CURSOR_MESSAGE_TYPES: CURSOR_MESSAGE_TYPES,
     FAILURE_DETAILS: FAILURE_DETAILS,
     REST_ERRNOS: REST_ERRNOS,
     STREAM_PROPERTIES: STREAM_PROPERTIES,
     SCREEN_SHARE_STATES: SCREEN_SHARE_STATES,
     ROOM_INFO_FAILURES: ROOM_INFO_FAILURES,
     setRootObjects: setRootObjects,
     composeCallUrlEmail: composeCallUrlEmail,
     findParentNode: findParentNode,
--- a/browser/extensions/loop/chrome/content/shared/js/views.js
+++ b/browser/extensions/loop/chrome/content/shared/js/views.js
@@ -571,20 +571,20 @@ loop.shared.views = function (_, mozL10n
       }
 
       this.props.dispatcher.dispatch(new sharedActions.RecordClick({
         linkInfo: "Shared URL"
       }));
     },
 
     render: function () {
-      // Bug 1196143 - formatURL sanitizes(decodes) the URL from IDN homographic attacks.
-      // Try catch to not produce output if invalid url
+      var hostname;
+
       try {
-        var sanitizeURL = loop.shared.utils.formatURL(this.props.url, true).hostname;
+        hostname = new URL(this.props.url).hostname;
       } catch (ex) {
         return null;
       }
 
       var thumbnail = this.props.thumbnail;
 
       if (!thumbnail) {
         thumbnail = this.props.useDesktopPaths ? "shared/img/icons-16x16.svg#globe" : "shared/img/icons-16x16.svg#globe";
@@ -608,17 +608,17 @@ loop.shared.views = function (_, mozL10n
           React.createElement("img", { className: "context-preview", src: thumbnail }),
           React.createElement(
             "span",
             { className: "context-info" },
             this.props.description,
             React.createElement(
               "span",
               { className: "context-url" },
-              sanitizeURL
+              hostname
             )
           )
         )
       );
     }
   });
 
   /**
@@ -632,91 +632,51 @@ loop.shared.views = function (_, mozL10n
     // to use the pure render mixin here.
     mixins: [React.addons.PureRenderMixin],
 
     propTypes: {
       displayAvatar: React.PropTypes.bool.isRequired,
       isLoading: React.PropTypes.bool.isRequired,
       mediaType: React.PropTypes.string.isRequired,
       posterUrl: React.PropTypes.string,
-      shouldRenderRemoteCursor: React.PropTypes.bool,
       // Expecting "local" or "remote".
       srcMediaElement: React.PropTypes.object
     },
 
-    getInitialState: function () {
-      return {
-        videoElementSize: null
-      };
-    },
-
     componentDidMount: function () {
       if (!this.props.displayAvatar) {
         this.attachVideo(this.props.srcMediaElement);
       }
-
-      if (this.props.shouldRenderRemoteCursor) {
-        this.handleVideoDimensions();
-        window.addEventListener("resize", this.handleVideoDimensions);
-      }
-    },
-
-    componentWillUnmount: function () {
-      var videoElement = this.getDOMNode().querySelector("video");
-      if (!this.props.shouldRenderRemoteCursor || !videoElement) {
-        return;
-      }
-
-      window.removeEventListener("resize", this.handleVideoDimensions);
-      videoElement.removeEventListener("loadeddata", this.handleVideoDimensions);
     },
 
     componentDidUpdate: function () {
       if (!this.props.displayAvatar) {
         this.attachVideo(this.props.srcMediaElement);
       }
     },
 
-    handleVideoDimensions: function () {
-      var videoElement = this.getDOMNode().querySelector("video");
-      if (!videoElement) {
-        return;
-      }
-
-      this.setState({
-        videoElementSize: {
-          clientWidth: videoElement.clientWidth,
-          clientHeight: videoElement.clientHeight
-        }
-      });
-    },
-
     /**
      * Attaches a video stream from a donor video element to this component's
      * video element if the component is displaying one.
      *
      * @param {Object} srcMediaElement The src video object to clone the stream
      *                                from.
      *
      * XXX need to have a corresponding detachVideo or change this to syncVideo
      * to protect from leaks (bug 1171978)
      */
     attachVideo: function (srcMediaElement) {
       if (!srcMediaElement) {
         // Not got anything to display.
         return;
       }
 
-      var videoElement = this.getDOMNode().querySelector("video");
+      var videoElement = this.getDOMNode();
 
-      if (this.props.shouldRenderRemoteCursor) {
-        videoElement.addEventListener("loadeddata", this.handleVideoDimensions);
-      }
-
-      if (!videoElement || videoElement.tagName.toLowerCase() !== "video") {
+      if (videoElement.tagName.toLowerCase() !== "video") {
         // Must be displaying the avatar view, so don't try and attach video.
         return;
       }
 
       // Set the src of our video element
       var attrName = "";
       if ("srcObject" in videoElement) {
         // srcObject is according to the standard.
@@ -747,39 +707,33 @@ loop.shared.views = function (_, mozL10n
       if (this.props.displayAvatar) {
         return React.createElement(AvatarView, null);
       }
 
       if (!this.props.srcMediaElement && !this.props.posterUrl) {
         return React.createElement("div", { className: "no-video" });
       }
 
-      var optionalProps = {};
+      var optionalPoster = {};
       if (this.props.posterUrl) {
-        optionalProps.poster = this.props.posterUrl;
+        optionalPoster.poster = this.props.posterUrl;
       }
 
       // For now, always mute media. For local media, we should be muted anyway,
       // as we don't want to hear ourselves speaking.
       //
       // For remote media, we would ideally have this live video element in
       // control of the audio, but due to the current method of not rendering
       // the element at all when video is muted we have to rely on the hidden
       // dom element in the sdk driver to play the audio.
       // We might want to consider changing this if we add UI controls relating
       // to the remote audio at some stage in the future.
-      return React.createElement(
-        "div",
-        { className: "remote-video-box" },
-        this.state.videoElementSize && this.props.shouldRenderRemoteCursor ? React.createElement(RemoteCursorView, {
-          videoElementSize: this.state.videoElementSize }) : null,
-        React.createElement("video", _extends({}, optionalProps, {
-          className: this.props.mediaType + "-video",
-          muted: true }))
-      );
+      return React.createElement("video", _extends({}, optionalPoster, {
+        className: this.props.mediaType + "-video",
+        muted: true }));
     }
   });
 
   var MediaLayoutView = React.createClass({
     displayName: "MediaLayoutView",
 
     propTypes: {
       children: React.PropTypes.node,
@@ -844,18 +798,17 @@ loop.shared.views = function (_, mozL10n
         });
       }
     },
 
     renderLocalVideo: function () {
       return React.createElement(
         "div",
         { className: "local" },
-        React.createElement(MediaView, {
-          displayAvatar: this.props.localVideoMuted,
+        React.createElement(MediaView, { displayAvatar: this.props.localVideoMuted,
           isLoading: this.props.isLocalLoading,
           mediaType: "local",
           posterUrl: this.props.localPosterUrl,
           srcMediaElement: this.props.localSrcMediaElement })
       );
     },
 
     render: function () {
@@ -885,160 +838,50 @@ loop.shared.views = function (_, mozL10n
           React.createElement(
             "span",
             { className: "self-view-hidden-message" },
             mozL10n.get("self_view_hidden_message")
           ),
           React.createElement(
             "div",
             { className: remoteStreamClasses },
-            React.createElement(MediaView, {
-              displayAvatar: !this.props.renderRemoteVideo,
+            React.createElement(MediaView, { displayAvatar: !this.props.renderRemoteVideo,
               isLoading: this.props.isRemoteLoading,
               mediaType: "remote",
               posterUrl: this.props.remotePosterUrl,
               srcMediaElement: this.props.remoteSrcMediaElement }),
             this.state.localMediaAboslutelyPositioned ? this.renderLocalVideo() : null,
             this.props.displayScreenShare ? null : this.props.children
           ),
           React.createElement(
             "div",
             { className: screenShareStreamClasses },
-            React.createElement(MediaView, {
-              displayAvatar: false,
+            React.createElement(MediaView, { displayAvatar: false,
               isLoading: this.props.isScreenShareLoading,
               mediaType: "screen-share",
               posterUrl: this.props.screenSharePosterUrl,
-              shouldRenderRemoteCursor: true,
               srcMediaElement: this.props.screenShareMediaElement }),
             this.props.displayScreenShare ? this.props.children : null
           ),
           React.createElement(loop.shared.views.chat.TextChatView, {
             dispatcher: this.props.dispatcher,
             showInitialContext: this.props.showInitialContext,
             useDesktopPaths: this.props.useDesktopPaths }),
           this.state.localMediaAboslutelyPositioned ? null : this.renderLocalVideo()
         )
       );
     }
   });
 
-  var RemoteCursorView = React.createClass({
-    displayName: "RemoteCursorView",
-
-    mixins: [React.addons.PureRenderMixin, loop.store.StoreMixin("remoteCursorStore")],
-
-    propTypes: {
-      videoElementSize: React.PropTypes.object
-    },
-
-    getInitialState: function () {
-      return {
-        realVideoSize: null,
-        videoLetterboxing: null
-      };
-    },
-
-    componentWillMount: function () {
-      if (!this.state.realVideoSize) {
-        return;
-      }
-
-      this._calculateVideoLetterboxing();
-    },
-
-    componentWillReceiveProps: function (nextProps) {
-      if (!this.state.realVideoSize) {
-        return;
-      }
-
-      // In this case link generator or link clicker have resized their windows
-      // so we need to recalculate the video letterboxing.
-      this._calculateVideoLetterboxing(this.state.realVideoSize, nextProps.videoElementSize);
-    },
-
-    componentWillUpdate: function (nextProps, nextState) {
-      if (!this.state.realVideoSize || !nextState.realVideoSize) {
-        return;
-      }
-
-      if (!this.state.videoLetterboxing) {
-        // If this is the first time we receive the event, we must calculate the
-        // video letterboxing.
-        this._calculateVideoLetterboxing();
-        return;
-      }
-
-      if (nextState.realVideoSize.width !== this.state.realVideoSize.width || nextState.realVideoSize.height !== this.state.realVideoSize.height) {
-        // In this case link generator has resized his window so we need to
-        // recalculate the video letterboxing.
-        this._calculateVideoLetterboxing(nextState.realVideoSize);
-      }
-    },
-
-    _calculateVideoLetterboxing: function (realVideoSize, videoElementSize) {
-      realVideoSize = realVideoSize || this.state.realVideoSize;
-      videoElementSize = videoElementSize || this.props.videoElementSize;
-
-      var clientWidth = videoElementSize.clientWidth;
-      var clientHeight = videoElementSize.clientHeight;
-      var clientRatio = clientWidth / clientHeight;
-      var realVideoWidth = realVideoSize.width;
-      var realVideoHeight = realVideoSize.height;
-      var realVideoRatio = realVideoWidth / realVideoHeight;
-
-      // If the video element ratio is "wider" than the video content, then the
-      // full client height will be used and letterbox will be on the sides.
-      // E.g., video element is 3:2 and stream is 1:1, so we end up with 2:2.
-      var isWider = clientRatio > realVideoRatio;
-      var streamVideoHeight = isWider ? clientHeight : clientWidth / realVideoRatio;
-      var streamVideoWidth = isWider ? clientHeight * realVideoRatio : clientWidth;
-
-      this.setState({
-        videoLetterboxing: {
-          left: (clientWidth - streamVideoWidth) / 2,
-          top: (clientHeight - streamVideoHeight) / 2
-        },
-        streamVideoHeight: streamVideoHeight,
-        streamVideoWidth: streamVideoWidth
-      });
-    },
-
-    calculateCursorPosition: function () {
-      // We need to calculate the cursor postion based on the current video
-      // stream dimensions.
-      var remoteCursorPosition = this.state.remoteCursorPosition;
-      var ratioX = remoteCursorPosition.ratioX;
-      var ratioY = remoteCursorPosition.ratioY;
-
-      var cursorPositionX = this.state.streamVideoWidth * ratioX;
-      var cursorPositionY = this.state.streamVideoHeight * ratioY;
-
-      return {
-        left: cursorPositionX + this.state.videoLetterboxing.left,
-        top: cursorPositionY + this.state.videoLetterboxing.top
-      };
-    },
-
-    render: function () {
-      if (!this.state.remoteCursorPosition || !this.state.videoLetterboxing) {
-        return null;
-      }
-
-      return React.createElement("div", { className: "remote-cursor", style: this.calculateCursorPosition() });
-    }
-  });
-
   return {
     AvatarView: AvatarView,
     Button: Button,
     ButtonGroup: ButtonGroup,
     Checkbox: Checkbox,
     ContextUrlView: ContextUrlView,
     ConversationToolbar: ConversationToolbar,
     MediaControlButton: MediaControlButton,
     MediaLayoutView: MediaLayoutView,
     MediaView: MediaView,
     LoadingView: LoadingView,
-    NotificationListView: NotificationListView,
-    RemoteCursorView: RemoteCursorView
+    NotificationListView: NotificationListView
   };
 }(_, navigator.mozL10n || document.mozL10n);
--- a/browser/extensions/loop/chrome/content/shared/test/index.html
+++ b/browser/extensions/loop/chrome/content/shared/test/index.html
@@ -52,33 +52,31 @@
   <script src="/shared/js/otSdkDriver.js"></script>
   <script src="/shared/js/store.js"></script>
   <script src="/shared/js/activeRoomStore.js"></script>
   <script src="/shared/js/views.js"></script>
   <script src="/shared/js/textChatStore.js"></script>
   <script src="/shared/js/textChatView.js"></script>
   <script src="/shared/js/urlRegExps.js"></script>
   <script src="/shared/js/linkifiedTextView.js"></script>
-  <script src="/shared/js/remoteCursorStore.js"></script>
 
   <!-- Test scripts -->
   <script src="models_test.js"></script>
   <script src="mixins_test.js"></script>
   <script src="utils_test.js"></script>
   <script src="crypto_test.js"></script>
   <script src="views_test.js"></script>
   <script src="validate_test.js"></script>
   <script src="dispatcher_test.js"></script>
   <script src="activeRoomStore_test.js"></script>
   <script src="otSdkDriver_test.js"></script>
   <script src="store_test.js"></script>
   <script src="textChatStore_test.js"></script>
   <script src="textChatView_test.js"></script>
   <script src="linkifiedTextView_test.js"></script>
   <script src="loopapi-client_test.js"></script>
-  <script src="remoteCursorStore_test.js"></script>
 
   <script>
     LoopMochaUtils.addErrorCheckingTests();
     LoopMochaUtils.runTests();
   </script>
 </body>
 </html>
--- a/browser/extensions/loop/chrome/content/shared/test/linkifiedTextView_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/linkifiedTextView_test.js
@@ -63,43 +63,43 @@ describe("loop.shared.views.LinkifiedTex
       }
 
       describe("this.props.suppressTarget", function() {
         it("should make links w/o a target attr if suppressTarget is true",
           function() {
             var markup = renderToMarkup("http://example.com", { suppressTarget: true });
 
             expect(markup).to.equal(
-              '<p><a href="http://example.com/" rel="noreferrer">http://example.com/</a></p>');
+              '<p><a href="http://example.com" rel="noreferrer">http://example.com</a></p>');
           });
 
         it("should make links with target=_blank if suppressTarget is not given",
           function() {
             var markup = renderToMarkup("http://example.com", {});
 
             expect(markup).to.equal(
-              '<p><a href="http://example.com/" target="_blank" rel="noreferrer">http://example.com/</a></p>');
+              '<p><a href="http://example.com" target="_blank" rel="noreferrer">http://example.com</a></p>');
           });
       });
 
       describe("this.props.sendReferrer", function() {
         it("should make links w/o rel=noreferrer if sendReferrer is true",
           function() {
             var markup = renderToMarkup("http://example.com", { sendReferrer: true });
 
             expect(markup).to.equal(
-              '<p><a href="http://example.com/" target="_blank">http://example.com/</a></p>');
+              '<p><a href="http://example.com" target="_blank">http://example.com</a></p>');
           });
 
         it("should make links with rel=noreferrer if sendReferrer is not given",
           function() {
             var markup = renderToMarkup("http://example.com", {});
 
             expect(markup).to.equal(
-              '<p><a href="http://example.com/" target="_blank" rel="noreferrer">http://example.com/</a></p>');
+              '<p><a href="http://example.com" target="_blank" rel="noreferrer">http://example.com</a></p>');
           });
       });
 
       describe("this.props.linkClickHandler", function() {
         function mountTestComponent(string, extraProps) {
           return TestUtils.renderIntoDocument(
             React.createElement(
               LinkifiedTextView,
@@ -122,17 +122,17 @@ describe("loop.shared.views.LinkifiedTex
 
             var markup = renderToMarkup("http://example.com", {
               linkClickHandler: linkClickHandler,
               sendReferrer: false,
               suppressTarget: false
             });
 
             expect(markup).to.equal(
-              '<p><a href="http://example.com/">http://example.com/</a></p>');
+              '<p><a href="http://example.com">http://example.com</a></p>');
           });
 
         describe("#_handleClickEvent", function() {
           var fakeEvent;
           var fakeUrl = "http://example.com";
 
           beforeEach(function() {
             fakeEvent = {
@@ -187,74 +187,69 @@ describe("loop.shared.views.LinkifiedTex
       // testing for some subset of these.
       var tests = [
         {
           desc: "should only add a container to a string with no URLs",
           rawText: "This is a test.",
           markup: "<p>This is a test.</p>"
         },
         {
-          desc: "should linkify a string containing only a URL with a trailing slash",
+          desc: "should linkify a string containing only a URL",
           rawText: "http://example.com/",
           markup: '<p><a href="http://example.com/">http://example.com/</a></p>'
         },
         {
-          desc: "should linkify a string containing only a URL with no trailing slash",
-          rawText: "http://example.com",
-          markup: '<p><a href="http://example.com/">http://example.com/</a></p>'
-        },
-        {
           desc: "should linkify a URL with text preceding it",
           rawText: "This is a link to http://example.com",
-          markup: '<p>This is a link to <a href="http://example.com/">http://example.com/</a></p>'
+          markup: '<p>This is a link to <a href="http://example.com">http://example.com</a></p>'
         },
         {
           desc: "should linkify a URL with text before and after",
           rawText: "Look at http://example.com near the bottom",
-          markup: '<p>Look at <a href="http://example.com/">http://example.com/</a> near the bottom</p>'
+          markup: '<p>Look at <a href="http://example.com">http://example.com</a> near the bottom</p>'
         },
         {
           desc: "should linkify an http URL",
           rawText: "This is an http://example.com test",
-          markup: '<p>This is an <a href="http://example.com/">http://example.com/</a> test</p>'
+          markup: '<p>This is an <a href="http://example.com">http://example.com</a> test</p>'
         },
         {
           desc: "should linkify an https URL",
           rawText: "This is an https://example.com test",
-          markup: '<p>This is an <a href="https://example.com/">https://example.com/</a> test</p>'
+          markup: '<p>This is an <a href="https://example.com">https://example.com</a> test</p>'
         },
         {
           desc: "should not linkify a data URL",
           rawText: "This is an data:image/png;base64,iVBORw0KGgoAAA test",
           markup: "<p>This is an data:image/png;base64,iVBORw0KGgoAAA test</p>"
         },
         {
-          desc: "should linkify URLs with a port number and no trailing slash",
+          desc: "should linkify URLs with a port number",
           rawText: "Joe went to http://example.com:8000 today.",
-          markup: '<p>Joe went to <a href="http://example.com:8000/">http://example.com:8000/</a> today.</p>'
+          markup: '<p>Joe went to <a href="http://example.com:8000">http://example.com:8000</a> today.</p>'
         },
         {
           desc: "should linkify URLs with a port number and a trailing slash",
           rawText: "Joe went to http://example.com:8000/ today.",
           markup: '<p>Joe went to <a href="http://example.com:8000/">http://example.com:8000/</a> today.</p>'
         },
         {
           desc: "should linkify URLs with a port number and a path",
           rawText: "Joe went to http://example.com:8000/mysite/page today.",
           markup: '<p>Joe went to <a href="http://example.com:8000/mysite/page">http://example.com:8000/mysite/page</a> today.</p>'
         },
         {
           desc: "should linkify URLs with a port number and a query string",
           rawText: "Joe went to http://example.com:8000?page=index today.",
-          markup: '<p>Joe went to <a href="http://example.com:8000/?page=index">http://example.com:8000/?page=index</a> today.</p>'
+          markup: '<p>Joe went to <a href="http://example.com:8000?page=index">http://example.com:8000?page=index</a> today.</p>'
         },
         {
           desc: "should linkify a URL with a port number and a hash string",
           rawText: "Joe went to http://example.com:8000#page=index today.",
-          markup: '<p>Joe went to <a href="http://example.com:8000/#page=index">http://example.com:8000/#page=index</a> today.</p>'
+          markup: '<p>Joe went to <a href="http://example.com:8000#page=index">http://example.com:8000#page=index</a> today.</p>'
         },
         {
           desc: "should NOT include preceding ':' intros without a space",
           rawText: "the link:http://example.com/",
           markup: '<p>the link:<a href="http://example.com/">http://example.com/</a></p>'
         },
         {
           desc: "should NOT autolink URLs with 'javascript:' URI scheme",
@@ -279,97 +274,87 @@ describe("loop.shared.views.LinkifiedTex
         {
           desc: "should NOT autolink a string in the form of 'version:1.0'",
           rawText: "version:1.0",
           markup: "<p>version:1.0</p>"
         },
         {
           desc: "should linkify an ftp URL",
           rawText: "This is an ftp://example.com test",
-          markup: '<p>This is an <a href="ftp://example.com/">ftp://example.com/</a> test</p>'
+          markup: '<p>This is an <a href="ftp://example.com">ftp://example.com</a> test</p>'
         },
 
         // We don't want to include trailing dots in URLs, even though those
         // are valid DNS names, as that should match user intent more of the
         // time, as well as avoid this stuff:
         //
         // http://saynt2day.blogspot.it/2013/03/danger-of-trailing-dot-in-domain-name.html
         //
         {
           desc: "should linkify 'http://example.com.', w/o a trailing dot",
           rawText: "Joe went to http://example.com.",
-          markup: '<p>Joe went to <a href="http://example.com/">http://example.com/</a>.</p>'
+          markup: '<p>Joe went to <a href="http://example.com">http://example.com</a>.</p>'
         },
         // XXX several more tests like this we could port from Autolinkify.js
         // see https://bugzilla.mozilla.org/show_bug.cgi?id=1186254
         {
           desc: "should exclude invalid chars after domain part",
           rawText: "Joe went to http://www.example.com's today",
-          markup: '<p>Joe went to <a href="http://www.example.com/">http://www.example.com/</a>&#x27;s today</p>'
+          markup: '<p>Joe went to <a href="http://www.example.com">http://www.example.com</a>&#x27;s today</p>'
         },
         {
           desc: "should not linkify protocol-relative URLs",
           rawText: "//C/Programs",
           markup: "<p>//C/Programs</p>"
         },
-        {
-          desc: "should not linkify malformed URI sequences",
-          rawText: "http://www.example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%",
-          markup: "<p>http://www.example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%</p>"
-        },
         // do a few tests to convince ourselves that, when our code is handled
         // HTML in the input box, the integration of our code with React should
         // cause that to rendered to appear as HTML source code, rather than
         // getting injected into our real HTML DOM
         {
           desc: "should linkify simple HTML include an href properly escaped",
           rawText: '<p>Joe went to <a href="http://www.example.com">example</a></p>',
-          markup: '<p>&lt;p&gt;Joe went to &lt;a href=&quot;<a href="http://www.example.com/">http://www.example.com/</a>&quot;&gt;example&lt;/a&gt;&lt;/p&gt;</p>'
+          markup: '<p>&lt;p&gt;Joe went to &lt;a href=&quot;<a href="http://www.example.com">http://www.example.com</a>&quot;&gt;example&lt;/a&gt;&lt;/p&gt;</p>'
         },
         {
           desc: "should linkify HTML with nested tags and resource path properly escaped",
           rawText: '<a href="http://example.com"><img src="http://example.com" /></a>',
-          markup: '<p>&lt;a href=&quot;<a href="http://example.com/">http://example.com/</a>&quot;&gt;&lt;img src=&quot;<a href="http://example.com/">http://example.com/</a>&quot; /&gt;&lt;/a&gt;</p>'
-         },
-        {
-          desc: "should linkify and decode a string containing a Homographic attack URL with no trailing slash",
-          rawText: "http://ebаy.com",
-          markup: '<p><a href="http://xn--eby-7cd.com/">http://xn--eby-7cd.com/</a></p>'
-        }
+          markup: '<p>&lt;a href=&quot;<a href="http://example.com">http://example.com</a>&quot;&gt;&lt;img src=&quot;<a href="http://example.com">http://example.com</a>&quot; /&gt;&lt;/a&gt;</p>'
+         }
       ];
 
       var skippedTests = [
 
         // XXX lots of tests similar to below we could port:
         // see https://bugzilla.mozilla.org/show_bug.cgi?id=1186254
         {
           desc: "should link localhost URLs with an allowed URL scheme",
           rawText: "Joe went to http://localhost today",
           markup: '<p>Joe went to <a href="http://localhost">localhost</a></p> today'
         },
         // XXX lots of tests similar to below we could port:
         // see https://bugzilla.mozilla.org/show_bug.cgi?id=1186254
         {
           desc: "should not include a ? if at the end of a URL",
           rawText: "Did Joe go to http://example.com?",
-          markup: '<p>Did Joe go to <a href="http://example.com/">http://example.com/</a>?</p>'
+          markup: '<p>Did Joe go to <a href="http://example.com">http://example.com</a>?</p>'
         },
         {
           desc: "should linkify 'check out http://example.com/monkey.', w/o trailing dots",
           rawText: "check out http://example.com/monkey...",
           markup: '<p>check out <a href="http://example.com/monkey">http://example.com/monkey</a>...</p>'
         },
         // another variant of eating too many trailing characters, it includes
         // the trailing ", which it shouldn't.  Makes links inside pasted HTML
         // source be slightly broken. Not key for our target users, I suspect,
         // but still...
         {
           desc: "should linkify HTML with nested tags and a resource path properly escaped",
           rawText: '<a href="http://example.com"><img src="http://example.com/someImage.jpg" /></a>',
-          markup: '<p>&lt;a href=&quot;<a href="http://example.com/">http://example.com/</a>&quot;&gt;&lt;img src=&quot;<a href="http://example.com/someImage.jpg&quot;">http://example.com/someImage.jpg&quot;</a> /&gt;&lt;/a&gt;</p>'
+          markup: '<p>&lt;a href=&quot;<a href="http://example.com">http://example.com</a>&quot;&gt;&lt;img src=&quot;<a href="http://example.com/someImage.jpg&quot;">http://example.com/someImage.jpg&quot;</a> /&gt;&lt;/a&gt;</p>'
         },
         // XXX handle domains without schemes (bug 1186245)
         // see https://bugzilla.mozilla.org/show_bug.cgi?id=1186254
         {
           desc: "should linkify a.museum (known TLD), but not abc.qqqq",
           rawText: "a.museum should be linked, but abc.qqqq should not",
           markup: '<p><a href="http://a.museum">a.museum</a> should be linked, but abc.qqqq should not</p>'
         },
--- a/browser/extensions/loop/chrome/content/shared/test/otSdkDriver_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/otSdkDriver_test.js
@@ -5,17 +5,16 @@ describe("loop.OTSdkDriver", function() 
   "use strict";
 
   var expect = chai.expect;
   var sharedActions = loop.shared.actions;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var STREAM_PROPERTIES = loop.shared.utils.STREAM_PROPERTIES;
   var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
-  var CURSOR_MESSAGE_TYPES = loop.shared.utils.CURSOR_MESSAGE_TYPES;
 
   var sandbox, constants;
   var dispatcher, driver, requestStubs, publisher, screenshare, sdk, session;
   var sessionData, subscriber, publisherConfig, fakeEvent;
 
   beforeEach(function() {
     sandbox = LoopMochaUtils.createSandbox();
 
@@ -149,20 +148,17 @@ describe("loop.OTSdkDriver", function() 
       driver.setupStreamElements(new sharedActions.SetupStreamElements({
         publisherConfig: publisherConfig
       }));
     });
 
     it("should call initPublisher", function() {
       var expectedConfig = _.extend({
         channels: {
-          text: {},
-          cursor: {
-            reliable: true
-          }
+          text: {}
         }
       }, publisherConfig);
 
       sinon.assert.calledOnce(sdk.initPublisher);
       sinon.assert.calledWith(sdk.initPublisher,
         sinon.match.instanceOf(HTMLDivElement),
         expectedConfig);
     });
@@ -237,16 +233,17 @@ describe("loop.OTSdkDriver", function() 
     });
   });
 
   describe("#startScreenShare", function() {
     var options = {};
 
     beforeEach(function() {
       sandbox.stub(screenshare, "off");
+      sandbox.stub(driver, "_noteSharingState");
       options = {
         videoSource: "browser",
         constraints: {
           browserWindow: 42,
           scrollWithPage: true
         }
       };
 
@@ -254,16 +251,20 @@ describe("loop.OTSdkDriver", function() 
     });
 
     it("should initialize a publisher", function() {
       sinon.assert.calledOnce(sdk.initPublisher);
       sinon.assert.calledWithMatch(sdk.initPublisher,
       sinon.match.instanceOf(HTMLDivElement), options);
     });
 
+    it("should log a telemetry action", function() {
+      sinon.assert.calledWithExactly(driver._noteSharingState, "browser", true);
+    });
+
     it("should not do anything if publisher completed successfully", function() {
       sdk.initPublisher.callArg(2);
 
       sinon.assert.notCalled(screenshare.off);
       sinon.assert.notCalled(dispatcher.dispatch);
     });
 
     it("should clean up publisher if an error occurred", function() {
@@ -297,16 +298,17 @@ describe("loop.OTSdkDriver", function() 
           recvStreams: 0
         }));
     });
   });
 
   describe("Screenshare Access Denied", function() {
     beforeEach(function() {
       sandbox.stub(screenshare, "off");
+      sandbox.stub(driver, "_noteSharingState");
       var options = {
         videoSource: "browser",
         constraints: {
           browserWindow: 42,
           scrollWithPage: true
         }
       };
       driver.startScreenShare(options);
@@ -352,27 +354,42 @@ describe("loop.OTSdkDriver", function() 
     it("should not switch if the window is the same as the currently selected one", function() {
       driver.switchAcquiredWindow(42);
 
       sinon.assert.notCalled(publisher._.switchAcquiredWindow);
     });
   });
 
   describe("#endScreenShare", function() {
+    beforeEach(function() {
+      sandbox.stub(driver, "_noteSharingState");
+    });
+
     it("should unpublish the share", function() {
       driver.startScreenShare({
         videoSource: "window"
       });
       driver.session = session;
 
       driver.endScreenShare(new sharedActions.EndScreenShare());
 
       sinon.assert.calledOnce(session.unpublish);
     });
 
+    it("should log a telemetry action", function() {
+      driver.startScreenShare({
+        videoSource: "window"
+      });
+      driver.session = session;
+
+      driver.endScreenShare(new sharedActions.EndScreenShare());
+
+      sinon.assert.calledWithExactly(driver._noteSharingState, "window", false);
+    });
+
     it("should destroy the share", function() {
       driver.startScreenShare({
         videoSource: "window"
       });
       driver.session = session;
 
       expect(driver.endScreenShare()).to.equal(true);
 
@@ -388,16 +405,30 @@ describe("loop.OTSdkDriver", function() 
       });
       driver.session = session;
 
       driver.endScreenShare(new sharedActions.EndScreenShare());
 
       sinon.assert.calledOnce(session.unpublish);
     });
 
+    it("should log a telemetry action too when type is 'browser'", function() {
+      driver.startScreenShare({
+        videoSource: "browser",
+        constraints: {
+          browserWindow: 42
+        }
+      });
+      driver.session = session;
+
+      driver.endScreenShare(new sharedActions.EndScreenShare());
+
+      sinon.assert.calledWithExactly(driver._noteSharingState, "browser", false);
+    });
+
     it("should dispatch a ConnectionStatus action", function() {
       driver.startScreenShare({
         videoSource: "browser",
         constraints: {
           browserWindow: 42
         }
       });
       driver.session = session;
@@ -727,16 +758,54 @@ describe("loop.OTSdkDriver", function() 
         driver._sendTwoWayMediaTelemetry = false;
 
         driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
 
         sinon.assert.notCalled(requestStubs.TelemetryAddValue);
       });
   });
 
+  describe("#_noteSharingState", function() {
+    it("should record enabled sharing states for window", function() {
+      driver._noteSharingState("window", true);
+
+      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
+      sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
+        "LOOP_SHARING_STATE_CHANGE_1",
+        constants.SHARING_STATE_CHANGE.WINDOW_ENABLED);
+    });
+
+    it("should record enabled sharing states for browser", function() {
+      driver._noteSharingState("browser", true);
+
+      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
+      sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
+        "LOOP_SHARING_STATE_CHANGE_1",
+        constants.SHARING_STATE_CHANGE.BROWSER_ENABLED);
+    });
+
+    it("should record disabled sharing states for window", function() {
+      driver._noteSharingState("window", false);
+
+      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
+      sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
+        "LOOP_SHARING_STATE_CHANGE_1",
+        constants.SHARING_STATE_CHANGE.WINDOW_DISABLED);
+    });
+
+    it("should record disabled sharing states for browser", function() {
+      driver._noteSharingState("browser", false);
+
+      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
+      sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
+        "LOOP_SHARING_STATE_CHANGE_1",
+        constants.SHARING_STATE_CHANGE.BROWSER_DISABLED);
+    });
+  });
+
   describe("#forceDisconnectAll", function() {
     it("should not disconnect anything when not connected", function() {
       driver.session = session;
       driver.forceDisconnectAll(function() {});
 
       sinon.assert.notCalled(session.forceDisconnect);
     });
 
@@ -783,64 +852,16 @@ describe("loop.OTSdkDriver", function() 
 
         sinon.assert.calledOnce(driver._publisherChannel.send);
         sinon.assert.calledWithExactly(driver._publisherChannel.send,
           JSON.stringify(message));
       });
     });
   });
 
-  describe("#sendCursorMessage", function() {
-    beforeEach(function() {
-      driver.session = session;
-    });
-
-    it("should send a message on the publisher cursor data channel", function() {
-      driver._publisherCursorChannel = {
-        send: sinon.stub()
-      };
-
-      driver._subscriberCursorChannel = {};
-
-      var message = {
-        contentType: CURSOR_MESSAGE_TYPES.POSITION,
-        top: 10,
-        left: 10,
-        width: 100,
-        height: 100
-      };
-
-      driver.sendCursorMessage(message);
-
-      sinon.assert.calledOnce(driver._publisherCursorChannel.send);
-      sinon.assert.calledWithExactly(driver._publisherCursorChannel.send,
-        JSON.stringify(message));
-    });
-
-    it("should not send a message if no cursor data channel has been set", function() {
-      driver._publisherCursorChannel = {
-        send: sinon.stub()
-      };
-
-      driver._subscriberCursorChannel = null;
-
-      var message = {
-        contentType: CURSOR_MESSAGE_TYPES.POSITION,
-        top: 10,
-        left: 10,
-        width: 100,
-        height: 100
-      };
-
-      driver.sendCursorMessage(message);
-
-      sinon.assert.notCalled(driver._publisherCursorChannel.send);
-    });
-  });
-
   describe("Events: general media", function() {
     var fakeConnection, fakeStream, fakeSubscriberObject, videoElement;
 
     beforeEach(function() {
       fakeConnection = "fakeConnection";
       fakeStream = {
         hasVideo: true,
         videoType: "camera",
@@ -1199,19 +1220,18 @@ describe("loop.OTSdkDriver", function() 
             session.trigger("streamCreated", { stream: fakeStream });
 
             sinon.assert.notCalled(session.signal);
           });
 
           it("should get the data channel after subscribe is complete", function() {
             session.trigger("streamCreated", { stream: fakeStream });
 
-            sinon.assert.calledTwice(fakeSubscriberObject._.getDataChannel);
-            sinon.assert.calledWith(fakeSubscriberObject._.getDataChannel.getCall(0), "text", {});
-            sinon.assert.calledWith(fakeSubscriberObject._.getDataChannel.getCall(1), "cursor", {});
+            sinon.assert.calledOnce(fakeSubscriberObject._.getDataChannel);
+            sinon.assert.calledWith(fakeSubscriberObject._.getDataChannel, "text", {});
           });
 
           it("should not get the data channel if data channels are not wanted", function() {
             driver._useDataChannels = false;
 
             session.trigger("streamCreated", { stream: fakeStream });
 
             sinon.assert.notCalled(fakeSubscriberObject._.getDataChannel);
@@ -1219,30 +1239,30 @@ describe("loop.OTSdkDriver", function() 
 
           it("should log an error if the data channel couldn't be obtained", function() {
             var err = new Error("fakeError");
 
             fakeSubscriberObject._.getDataChannel.callsArgWith(2, err);
 
             session.trigger("streamCreated", { stream: fakeStream });
 
-            sinon.assert.calledTwice(console.error);
+            sinon.assert.calledOnce(console.error);
             sinon.assert.calledWithMatch(console.error, err);
           });
 
           it("should dispatch ConnectionStatus if the data channel couldn't be obtained", function() {
             fakeSubscriberObject._.getDataChannel.callsArgWith(2, new Error("fakeError"));
 
             session.trigger("streamCreated", { stream: fakeStream });
 
             sinon.assert.called(dispatcher.dispatch);
             sinon.assert.calledWithExactly(dispatcher.dispatch,
               new sharedActions.ConnectionStatus({
                 connections: 0,
-                event: "sdk.datachannel.sub.text.fakeError",
+                event: "sdk.datachannel.sub.fakeError",
                 sendStreams: 0,
                 state: "receiving",
                 recvStreams: 1
               }));
           });
 
           it("should dispatch `DataChannelsAvailable` if the publisher channel is setup", function() {
             // Fake a publisher channel.
@@ -1472,45 +1492,42 @@ describe("loop.OTSdkDriver", function() 
         connection: { id: "fake" },
         videoType: "screen",
         videoDimensions: {
           width: 320,
           height: 160
         }
       };
 
+      it("should not dispatch a VideoDimensionsChanged action for other properties", function() {
+        session.trigger("streamPropertyChanged", {
+          stream: stream,
+          changedProperty: STREAM_PROPERTIES.HAS_AUDIO
+        });
+        session.trigger("streamPropertyChanged", {
+          stream: stream,
+          changedProperty: STREAM_PROPERTIES.HAS_VIDEO
+        });
+
+        sinon.assert.notCalled(dispatcher.dispatch);
+      });
+
       it("should dispatch a VideoDimensionsChanged action", function() {
         session.connection = {
           id: "localUser"
         };
         session.trigger("streamPropertyChanged", {
           stream: stream,
           changedProperty: STREAM_PROPERTIES.VIDEO_DIMENSIONS
         });
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithMatch(dispatcher.dispatch,
           sinon.match.hasOwn("name", "videoDimensionsChanged"));
       });
-
-      it("should dispatch a VideoScreenShareChanged action", function() {
-        session.connection = {
-          id: "localUser"
-        };
-        session.trigger("streamPropertyChanged", {
-          stream: stream,
-          oldValue: false,
-          newValue: true,
-          changedProperty: STREAM_PROPERTIES.HAS_VIDEO
-        });
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithMatch(dispatcher.dispatch,
-          sinon.match.hasOwn("name", "videoScreenStreamChanged"));
-      });
     });
 
     describe("connectionCreated", function() {
       beforeEach(function() {
         session.connection = {
           id: "localUser"
         };
       });
@@ -1681,43 +1698,43 @@ describe("loop.OTSdkDriver", function() 
         driver._useDataChannels = false;
 
         session.trigger("signal:readyForDataChannel");
 
         sinon.assert.notCalled(publisher._.getDataChannel);
         sinon.assert.notCalled(subscriber._.getDataChannel);
       });
 
-      it("should get the data channels for the publisher", function() {
+      it("should get the data channel for the publisher", function() {
         session.trigger("signal:readyForDataChannel");
 
-        sinon.assert.calledTwice(publisher._.getDataChannel);
+        sinon.assert.calledOnce(publisher._.getDataChannel);
       });
 
-      it("should log an error if the data channels couldn't be obtained", function() {
+      it("should log an error if the data channel couldn't be obtained", function() {
         var err = new Error("fakeError");
 
         publisher._.getDataChannel.callsArgWith(2, err);
 
         session.trigger("signal:readyForDataChannel");
 
-        sinon.assert.calledTwice(console.error);
+        sinon.assert.calledOnce(console.error);
         sinon.assert.calledWithMatch(console.error, err);
       });
 
       it("should dispatch ConnectionStatus if the data channel couldn't be obtained", function() {
         publisher._.getDataChannel.callsArgWith(2, new Error("fakeError"));
 
         session.trigger("signal:readyForDataChannel");
 
-        sinon.assert.calledTwice(dispatcher.dispatch);
+        sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
           new sharedActions.ConnectionStatus({
             connections: 0,
-            event: "sdk.datachannel.pub.text.fakeError",
+            event: "sdk.datachannel.pub.fakeError",
             sendStreams: 0,
             state: "starting",
             recvStreams: 0
           }));
       });
 
       it("should dispatch `DataChannelsAvailable` if the subscriber channel is setup", function() {
         var fakeChannel = _.extend({}, Backbone.Events);
deleted file mode 100644
--- a/browser/extensions/loop/chrome/content/shared/test/remoteCursorStore_test.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-describe("loop.store.RemoteCursorStore", function() {
-  "use strict";
-
-  var expect = chai.expect;
-  var sharedActions = loop.shared.actions;
-  var CURSOR_MESSAGE_TYPES = loop.shared.utils.CURSOR_MESSAGE_TYPES;
-  var sandbox, dispatcher, store, fakeSdkDriver;
-
-  beforeEach(function() {
-    sandbox = LoopMochaUtils.createSandbox();
-
-    LoopMochaUtils.stubLoopRequest({
-      GetLoopPref: sinon.stub()
-    });
-
-    dispatcher = new loop.Dispatcher();
-    sandbox.stub(dispatcher, "dispatch");
-
-    fakeSdkDriver = {
-      sendCursorMessage: sinon.stub()
-    };
-
-    store = new loop.store.RemoteCursorStore(dispatcher, {
-      sdkDriver: fakeSdkDriver
-    });
-  });
-
-  afterEach(function() {
-    sandbox.restore();
-    LoopMochaUtils.restore();
-  });
-
-  describe("#constructor", function() {
-    it("should throw an error if sdkDriver is missing", function() {
-      expect(function() {
-        new loop.store.RemoteCursorStore(dispatcher, {});
-      }).to.Throw(/sdkDriver/);
-    });
-
-    it("should add a CursorPositionChange event listener", function() {
-      sandbox.stub(loop, "subscribe");
-      new loop.store.RemoteCursorStore(dispatcher, { sdkDriver: fakeSdkDriver });
-      sinon.assert.calledOnce(loop.subscribe);
-      sinon.assert.calledWith(loop.subscribe, "CursorPositionChange");
-    });
-  });
-
-  describe("#_cursorPositionChangeListener", function() {
-    it("should send cursor data through the sdk", function() {
-      var fakeEvent = {
-        ratioX: 10,
-        ratioY: 10
-      };
-
-      LoopMochaUtils.publish("CursorPositionChange", fakeEvent);
-
-      sinon.assert.calledOnce(fakeSdkDriver.sendCursorMessage);
-      sinon.assert.calledWith(fakeSdkDriver.sendCursorMessage, {
-        type: CURSOR_MESSAGE_TYPES.POSITION,
-        ratioX: fakeEvent.ratioX,
-        ratioY: fakeEvent.ratioY
-      });
-    });
-  });
-
-  describe("#receivedCursorData", function() {
-    it("should save the state", function() {
-      store.receivedCursorData(new sharedActions.ReceivedCursorData({
-        type: CURSOR_MESSAGE_TYPES.POSITION,
-        ratioX: 10,
-        ratioY: 10
-      }));
-
-      expect(store.getStoreState().remoteCursorPosition).eql({
-        ratioX: 10,
-        ratioY: 10
-      });
-    });
-  });
-
-  describe("#videoDimensionsChanged", function() {
-    beforeEach(function() {
-      store.setStoreState({
-        realVideoSize: null
-      });
-    });
-
-    it("should save the state", function() {
-      store.videoDimensionsChanged(new sharedActions.VideoDimensionsChanged({
-        isLocal: false,
-        videoType: "screen",
-        dimensions: {
-          height: 10,
-          width: 10
-        }
-      }));
-
-      expect(store.getStoreState().realVideoSize).eql({
-        height: 10,
-        width: 10
-      });
-    });
-
-    it("should not save the state if video type is not screen", function() {
-      store.videoDimensionsChanged(new sharedActions.VideoDimensionsChanged({
-        isLocal: false,
-        videoType: "camera",
-        dimensions: {
-          height: 10,
-          width: 10
-        }
-      }));
-
-      expect(store.getStoreState().realVideoSize).eql(null);
-    });
-  });
-
-  describe("#videoScreenStreamChanged", function() {
-    beforeEach(function() {
-      store.setStoreState({
-        remoteCursorPosition: {
-          ratioX: 1,
-          ratioY: 1
-        }
-      });
-    });
-
-    it("should remove cursor position if screen stream has no video", function() {
-      store.videoScreenStreamChanged(new sharedActions.VideoScreenStreamChanged({
-        hasVideo: false
-      }));
-
-      expect(store.getStoreState().remoteCursorPosition).eql(null);
-    });
-
-    it("should not remove cursor position if screen stream has video", function() {
-      sandbox.stub(store, "setStoreState");
-
-      store.videoScreenStreamChanged(new sharedActions.VideoScreenStreamChanged({
-        hasVideo: true
-      }));
-
-      sinon.assert.notCalled(store.setStoreState);
-      expect(store.getStoreState().remoteCursorPosition).eql({
-        ratioX: 1,
-        ratioY: 1
-      });
-    });
-  });
-});
--- a/browser/extensions/loop/chrome/content/shared/test/utils_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/utils_test.js
@@ -291,17 +291,17 @@ describe("loop.shared.utils", function()
         }
       });
     });
   });
 
   describe("#formatURL", function() {
     beforeEach(function() {
       // Stub to prevent console messages.
-      sandbox.stub(window.console, "log");
+      sandbox.stub(window.console, "error");
     });
 
     it("should decode encoded URIs", function() {
       expect(sharedUtils.formatURL("http://invalid.com/?a=Foo%20Bar"))
         .eql({
           location: "http://invalid.com/?a=Foo Bar",
           hostname: "invalid.com"
         });
@@ -313,27 +313,24 @@ describe("loop.shared.utils", function()
       // future.
       expect(sharedUtils.formatURL("http://\u0261oogle.com/"))
         .eql({
           location: "http://xn--oogle-qmc.com/",
           hostname: "xn--oogle-qmc.com"
         });
     });
 
-    it("should return null if suppressConsoleError is true and the url is not valid", function() {
-      expect(sharedUtils.formatURL("hinvalid//url", true)).eql(null);
+    it("should return null if it the url is not valid", function() {
+      expect(sharedUtils.formatURL("hinvalid//url")).eql(null);
     });
 
-    it("should return null if suppressConsoleError is true and is a malformed URI sequence", function() {
-      expect(sharedUtils.formatURL("http://www.example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%", true)).eql(null);
-    });
+    it("should log an error message to the console", function() {
+      sharedUtils.formatURL("hinvalid//url");
 
-    it("should log a malformed URI sequence error to the console", function() {
-      sharedUtils.formatURL("http://www.example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%");
-      sinon.assert.calledOnce(console.log);
+      sinon.assert.calledOnce(console.error);
     });
   });
 
   describe("#composeCallUrlEmail", function() {
     var requestStubs;
 
     beforeEach(function() {
       // fake mozL10n
--- a/browser/extensions/loop/chrome/content/shared/test/vendor/chai.js
+++ b/browser/extensions/loop/chrome/content/shared/test/vendor/chai.js
@@ -1,26 +1,23 @@
 (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.chai = f()}})(function(){var define,module,exports;return (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 = require('./lib/chai');
-
-},{"./lib/chai":2}],2:[function(require,module,exports){
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 var used = []
   , exports = module.exports = {};
 
 /*!
  * Chai version
  */
 
-exports.version = '3.5.0';
+exports.version = '3.0.0';
 
 /*!
  * Assertion Error
  */
 
 exports.AssertionError = require('assertion-error');
 
 /*!
@@ -91,17 +88,17 @@ exports.use(should);
 
 /*!
  * Assert interface
  */
 
 var assert = require('./chai/interface/assert');
 exports.use(assert);
 
-},{"./chai/assertion":3,"./chai/config":4,"./chai/core/assertions":5,"./chai/interface/assert":6,"./chai/interface/expect":7,"./chai/interface/should":8,"./chai/utils":22,"assertion-error":30}],3:[function(require,module,exports){
+},{"./chai/assertion":2,"./chai/config":3,"./chai/core/assertions":4,"./chai/interface/assert":5,"./chai/interface/expect":6,"./chai/interface/should":7,"./chai/utils":20,"assertion-error":28}],2:[function(require,module,exports){
 /*!
  * chai
  * http://chaijs.com
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 var config = require('./config');
@@ -182,18 +179,18 @@ module.exports = function (_chai, util) 
 
   /**
    * ### .assert(expression, message, negateMessage, expected, actual, showDiff)
    *
    * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass.
    *
    * @name assert
    * @param {Philosophical} expression to be tested
-   * @param {String|Function} message or function that returns message to display if expression fails
-   * @param {String|Function} negatedMessage or function that returns negatedMessage to display if negated expression fails
+   * @param {String or Function} message or function that returns message to display if expression fails
+   * @param {String or Function} negatedMessage or function that returns negatedMessage to display if negated expression fails
    * @param {Mixed} expected value (remember to check for negation)
    * @param {Mixed} actual (optional) will default to `this.obj`
    * @param {Boolean} showDiff (optional) when set to `true`, assert will display a diff in addition to the message if expression fails
    * @api private
    */
 
   Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) {
     var ok = util.test(this, arguments);
@@ -224,17 +221,17 @@ module.exports = function (_chai, util) 
         return flag(this, 'object');
       }
     , set: function (val) {
         flag(this, 'object', val);
       }
   });
 };
 
-},{"./config":4}],4:[function(require,module,exports){
+},{"./config":3}],3:[function(require,module,exports){
 module.exports = {
 
   /**
    * ### config.includeStack
    *
    * User configurable property, influences whether stack trace
    * is included in Assertion error message. Default of false
    * suppresses stack trace in the error message.
@@ -281,17 +278,17 @@ module.exports = {
    * @param {Number}
    * @api public
    */
 
   truncateThreshold: 40
 
 };
 
-},{}],5:[function(require,module,exports){
+},{}],4:[function(require,module,exports){
 /*!
  * chai
  * http://chaijs.com
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 module.exports = function (chai, _) {
@@ -319,17 +316,16 @@ module.exports = function (chai, _) {
    * - has
    * - have
    * - with
    * - at
    * - of
    * - same
    *
    * @name language chains
-   * @namespace BDD
    * @api public
    */
 
   [ 'to', 'be', 'been'
   , 'is', 'and', 'has', 'have'
   , 'with', 'that', 'which', 'at'
   , 'of', 'same' ].forEach(function (chain) {
     Assertion.addProperty(chain, function () {
@@ -343,17 +339,16 @@ module.exports = function (chai, _) {
    * Negates any of assertions following in the chain.
    *
    *     expect(foo).to.not.equal('bar');
    *     expect(goodFn).to.not.throw(Error);
    *     expect({ foo: 'baz' }).to.have.property('foo')
    *       .and.not.equal('bar');
    *
    * @name not
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('not', function () {
     flag(this, 'negate', true);
   });
 
   /**
@@ -368,34 +363,32 @@ module.exports = function (chai, _) {
    *
    * `.deep.property` special characters can be escaped
    * by adding two slashes before the `.` or `[]`.
    *
    *     var deepCss = { '.link': { '[target]': 42 }};
    *     expect(deepCss).to.have.deep.property('\\.link.\\[target\\]', 42);
    *
    * @name deep
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('deep', function () {
     flag(this, 'deep', true);
   });
 
   /**
    * ### .any
    *
    * Sets the `any` flag, (opposite of the `all` flag)
    * later used in the `keys` assertion.
    *
    *     expect(foo).to.have.any.keys('bar', 'baz');
    *
    * @name any
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('any', function () {
     flag(this, 'any', true);
     flag(this, 'all', false)
   });
 
@@ -404,17 +397,16 @@ module.exports = function (chai, _) {
    * ### .all
    *
    * Sets the `all` flag (opposite of the `any` flag)
    * later used by the `keys` assertion.
    *
    *     expect(foo).to.have.all.keys('bar', 'baz');
    *
    * @name all
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('all', function () {
     flag(this, 'all', true);
     flag(this, 'any', false);
   });
 
@@ -425,32 +417,30 @@ module.exports = function (chai, _) {
    * used either as language chains or to assert a value's
    * type.
    *
    *     // typeof
    *     expect('test').to.be.a('string');
    *     expect({ foo: 'bar' }).to.be.an('object');
    *     expect(null).to.be.a('null');
    *     expect(undefined).to.be.an('undefined');
-   *     expect(new Error).to.be.an('error');
    *     expect(new Promise).to.be.a('promise');
    *     expect(new Float32Array()).to.be.a('float32array');
    *     expect(Symbol()).to.be.a('symbol');
    *
    *     // es6 overrides
    *     expect({[Symbol.toStringTag]:()=>'foo'}).to.be.a('foo');
    *
    *     // language chain
    *     expect(foo).to.be.an.instanceof(Foo);
    *
    * @name a
    * @alias an
    * @param {String} type
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function an (type, msg) {
     if (msg) flag(this, 'message', msg);
     type = type.toLowerCase();
     var obj = flag(this, 'object')
       , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a ';
@@ -478,48 +468,44 @@ module.exports = function (chai, _) {
    *     expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
    *
    * @name include
    * @alias contain
    * @alias includes
    * @alias contains
    * @param {Object|String|Number} obj
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function includeChainingBehavior () {
     flag(this, 'contains', true);
   }
 
   function include (val, msg) {
-    _.expectTypes(this, ['array', 'object', 'string']);
-
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     var expected = false;
-
     if (_.type(obj) === 'array' && _.type(val) === 'object') {
       for (var i in obj) {
         if (_.eql(obj[i], val)) {
           expected = true;
           break;
         }
       }
     } else if (_.type(val) === 'object') {
       if (!flag(this, 'negate')) {
         for (var k in val) new Assertion(obj).property(k, val[k]);
         return;
       }
       var subset = {};
       for (var k in val) subset[k] = obj[k];
       expected = _.eql(subset, val);
     } else {
-      expected = (obj != undefined) && ~obj.indexOf(val);
+      expected = obj && ~obj.indexOf(val);
     }
     this.assert(
         expected
       , 'expected #{this} to include ' + _.inspect(val)
       , 'expected #{this} to not include ' + _.inspect(val));
   }
 
   Assertion.addChainableMethod('include', include, includeChainingBehavior);
@@ -527,24 +513,23 @@ module.exports = function (chai, _) {
   Assertion.addChainableMethod('contains', include, includeChainingBehavior);
   Assertion.addChainableMethod('includes', include, includeChainingBehavior);
 
   /**
    * ### .ok
    *
    * Asserts that the target is truthy.
    *
-   *     expect('everything').to.be.ok;
+   *     expect('everthing').to.be.ok;
    *     expect(1).to.be.ok;
    *     expect(false).to.not.be.ok;
    *     expect(undefined).to.not.be.ok;
    *     expect(null).to.not.be.ok;
    *
    * @name ok
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('ok', function () {
     this.assert(
         flag(this, 'object')
       , 'expected #{this} to be truthy'
       , 'expected #{this} to be falsy');
@@ -554,17 +539,16 @@ module.exports = function (chai, _) {
    * ### .true
    *
    * Asserts that the target is `true`.
    *
    *     expect(true).to.be.true;
    *     expect(1).to.not.be.true;
    *
    * @name true
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('true', function () {
     this.assert(
         true === flag(this, 'object')
       , 'expected #{this} to be true'
       , 'expected #{this} to be false'
@@ -576,17 +560,16 @@ module.exports = function (chai, _) {
    * ### .false
    *
    * Asserts that the target is `false`.
    *
    *     expect(false).to.be.false;
    *     expect(0).to.not.be.false;
    *
    * @name false
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('false', function () {
     this.assert(
         false === flag(this, 'object')
       , 'expected #{this} to be false'
       , 'expected #{this} to be true'
@@ -598,17 +581,16 @@ module.exports = function (chai, _) {
    * ### .null
    *
    * Asserts that the target is `null`.
    *
    *     expect(null).to.be.null;
    *     expect(undefined).to.not.be.null;
    *
    * @name null
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('null', function () {
     this.assert(
         null === flag(this, 'object')
       , 'expected #{this} to be null'
       , 'expected #{this} not to be null'
@@ -619,63 +601,41 @@ module.exports = function (chai, _) {
    * ### .undefined
    *
    * Asserts that the target is `undefined`.
    *
    *     expect(undefined).to.be.undefined;
    *     expect(null).to.not.be.undefined;
    *
    * @name undefined
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('undefined', function () {
     this.assert(
         undefined === flag(this, 'object')
       , 'expected #{this} to be undefined'
       , 'expected #{this} not to be undefined'
     );
   });
 
   /**
-   * ### .NaN
-   * Asserts that the target is `NaN`.
-   *
-   *     expect('foo').to.be.NaN;
-   *     expect(4).not.to.be.NaN;
-   *
-   * @name NaN
-   * @namespace BDD
-   * @api public
-   */
-
-  Assertion.addProperty('NaN', function () {
-    this.assert(
-        isNaN(flag(this, 'object'))
-        , 'expected #{this} to be NaN'
-        , 'expected #{this} not to be NaN'
-    );
-  });
-
-  /**
    * ### .exist
    *
    * Asserts that the target is neither `null` nor `undefined`.
    *
    *     var foo = 'hi'
    *       , bar = null
    *       , baz;
    *
    *     expect(foo).to.exist;
    *     expect(bar).to.not.exist;
    *     expect(baz).to.not.exist;
    *
    * @name exist
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('exist', function () {
     this.assert(
         null != flag(this, 'object')
       , 'expected #{this} to exist'
       , 'expected #{this} to not exist'
@@ -690,17 +650,16 @@ module.exports = function (chai, _) {
    * the `length` property. For objects, it gets the count of
    * enumerable keys.
    *
    *     expect([]).to.be.empty;
    *     expect('').to.be.empty;
    *     expect({}).to.be.empty;
    *
    * @name empty
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('empty', function () {
     var obj = flag(this, 'object')
       , expected = obj;
 
     if (Array.isArray(obj) || 'string' === typeof object) {
@@ -722,17 +681,16 @@ module.exports = function (chai, _) {
    * Asserts that the target is an arguments object.
    *
    *     function test () {
    *       expect(arguments).to.be.arguments;
    *     }
    *
    * @name arguments
    * @alias Arguments
-   * @namespace BDD
    * @api public
    */
 
   function checkArguments () {
     var obj = flag(this, 'object')
       , type = Object.prototype.toString.call(obj);
     this.assert(
         '[object Arguments]' === type
@@ -758,17 +716,16 @@ module.exports = function (chai, _) {
    *     expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });
    *
    * @name equal
    * @alias equals
    * @alias eq
    * @alias deep.equal
    * @param {Mixed} value
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertEqual (val, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     if (flag(this, 'deep')) {
       return this.eql(val);
@@ -795,17 +752,16 @@ module.exports = function (chai, _) {
    *
    *     expect({ foo: 'bar' }).to.eql({ foo: 'bar' });
    *     expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]);
    *
    * @name eql
    * @alias eqls
    * @param {Mixed} value
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertEql(obj, msg) {
     if (msg) flag(this, 'message', msg);
     this.assert(
         _.eql(obj, flag(this, 'object'))
       , 'expected #{this} to deeply equal #{exp}'
@@ -834,17 +790,16 @@ module.exports = function (chai, _) {
    *     expect('foo').to.have.length.above(2);
    *     expect([ 1, 2, 3 ]).to.have.length.above(2);
    *
    * @name above
    * @alias gt
    * @alias greaterThan
    * @param {Number} value
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertAbove (n, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     if (flag(this, 'doLength')) {
       new Assertion(obj, msg).to.have.property('length');
@@ -883,17 +838,16 @@ module.exports = function (chai, _) {
    *
    *     expect('foo').to.have.length.of.at.least(2);
    *     expect([ 1, 2, 3 ]).to.have.length.of.at.least(3);
    *
    * @name least
    * @alias gte
    * @param {Number} value
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertLeast (n, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     if (flag(this, 'doLength')) {
       new Assertion(obj, msg).to.have.property('length');
@@ -932,17 +886,16 @@ module.exports = function (chai, _) {
    *     expect('foo').to.have.length.below(4);
    *     expect([ 1, 2, 3 ]).to.have.length.below(4);
    *
    * @name below
    * @alias lt
    * @alias lessThan
    * @param {Number} value
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertBelow (n, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     if (flag(this, 'doLength')) {
       new Assertion(obj, msg).to.have.property('length');
@@ -981,17 +934,16 @@ module.exports = function (chai, _) {
    *
    *     expect('foo').to.have.length.of.at.most(4);
    *     expect([ 1, 2, 3 ]).to.have.length.of.at.most(3);
    *
    * @name most
    * @alias lte
    * @param {Number} value
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertMost (n, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     if (flag(this, 'doLength')) {
       new Assertion(obj, msg).to.have.property('length');
@@ -1029,17 +981,16 @@ module.exports = function (chai, _) {
    *
    *     expect('foo').to.have.length.within(2,4);
    *     expect([ 1, 2, 3 ]).to.have.length.within(2,4);
    *
    * @name within
    * @param {Number} start lowerbound inclusive
    * @param {Number} finish upperbound inclusive
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addMethod('within', function (start, finish, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object')
       , range = start + '..' + finish;
     if (flag(this, 'doLength')) {
@@ -1069,17 +1020,16 @@ module.exports = function (chai, _) {
    *
    *     expect(Chai).to.be.an.instanceof(Tea);
    *     expect([ 1, 2, 3 ]).to.be.instanceof(Array);
    *
    * @name instanceof
    * @param {Constructor} constructor
    * @param {String} message _optional_
    * @alias instanceOf
-   * @namespace BDD
    * @api public
    */
 
   function assertInstanceOf (constructor, msg) {
     if (msg) flag(this, 'message', msg);
     var name = _.getName(constructor);
     this.assert(
         flag(this, 'object') instanceof constructor
@@ -1154,17 +1104,16 @@ module.exports = function (chai, _) {
    *     expect(deepCss).to.have.deep.property('\\.link.\\[target\\]', 42);
    *
    * @name property
    * @alias deep.property
    * @param {String} name
    * @param {Mixed} value (optional)
    * @param {String} message _optional_
    * @returns value of property for chaining
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addMethod('property', function (name, val, msg) {
     if (msg) flag(this, 'message', msg);
 
     var isDeep = !!flag(this, 'deep')
       , descriptor = isDeep ? 'deep property ' : 'property '
@@ -1210,17 +1159,16 @@ module.exports = function (chai, _) {
    * Asserts that the target has an own property `name`.
    *
    *     expect('test').to.have.ownProperty('length');
    *
    * @name ownProperty
    * @alias haveOwnProperty
    * @param {String} name
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertOwnProperty (name, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     this.assert(
         obj.hasOwnProperty(name)
@@ -1243,17 +1191,16 @@ module.exports = function (chai, _) {
    *     expect('test').ownPropertyDescriptor('length').to.have.property('enumerable', false);
    *     expect('test').ownPropertyDescriptor('length').to.have.keys('value');
    *
    * @name ownPropertyDescriptor
    * @alias haveOwnPropertyDescriptor
    * @param {String} name
    * @param {Object} descriptor _optional_
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertOwnPropertyDescriptor (name, descriptor, msg) {
     if (typeof descriptor === 'string') {
       msg = descriptor;
       descriptor = null;
     }
@@ -1296,33 +1243,31 @@ module.exports = function (chai, _) {
    *     expect([ 1, 2, 3 ]).to.have.length.within(2,4);
    *
    * *Deprecation notice:* Using `length` as an assertion will be deprecated
    * in version 2.4.0 and removed in 3.0.0. Code using the old style of
    * asserting for `length` property value using `length(value)` should be
    * switched to use `lengthOf(value)` instead.
    *
    * @name length
-   * @namespace BDD
    * @api public
    */
 
   /**
    * ### .lengthOf(value[, message])
    *
    * Asserts that the target's `length` property has
    * the expected value.
    *
    *     expect([ 1, 2, 3]).to.have.lengthOf(3);
    *     expect('foobar').to.have.lengthOf(6);
    *
    * @name lengthOf
    * @param {Number} length
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertLengthChain () {
     flag(this, 'doLength', true);
   }
 
   function assertLength (n, msg) {
@@ -1349,17 +1294,16 @@ module.exports = function (chai, _) {
    * Asserts that the target matches a regular expression.
    *
    *     expect('foobar').to.match(/^foo/);
    *
    * @name match
    * @alias matches
    * @param {RegExp} RegularExpression
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
   function assertMatch(re, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     this.assert(
         re.exec(obj)
       , 'expected #{this} to match ' + re
@@ -1375,17 +1319,16 @@ module.exports = function (chai, _) {
    *
    * Asserts that the string target contains another string.
    *
    *     expect('foobar').to.have.string('bar');
    *
    * @name string
    * @param {String} string
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addMethod('string', function (str, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     new Assertion(obj, msg).is.a('string');
 
@@ -1426,18 +1369,17 @@ module.exports = function (chai, _) {
    *     expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']);
    *     expect({ foo: 1, bar: 2 }).to.have.all.keys({'bar': 6, 'foo': 7});
    *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']);
    *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys({'bar': 6});
    *
    *
    * @name keys
    * @alias key
-   * @param {...String|Array|Object} keys
-   * @namespace BDD
+   * @param {String...|Array|Object} keys
    * @api public
    */
 
   function assertKeys (keys) {
     var obj = flag(this, 'object')
       , str
       , ok = true
       , mixedArgsMsg = 'keys must be given single argument of Array|Object|String, or multiple String arguments';
@@ -1530,16 +1472,17 @@ module.exports = function (chai, _) {
    *     var err = new ReferenceError('This is a bad function.');
    *     var fn = function () { throw err; }
    *     expect(fn).to.throw(ReferenceError);
    *     expect(fn).to.throw(Error);
    *     expect(fn).to.throw(/bad function/);
    *     expect(fn).to.not.throw('good function');
    *     expect(fn).to.throw(ReferenceError, /bad function/);
    *     expect(fn).to.throw(err);
+   *     expect(fn).to.not.throw(new RangeError('Out of range.'));
    *
    * Please note that when a throw expectation is negated, it will check each
    * parameter independently, starting with error constructor type. The appropriate way
    * to check for the existence of a type of error but for a message that does not match
    * is to use `and`.
    *
    *     expect(fn).to.throw(ReferenceError)
    *        .and.not.throw(/good function/);
@@ -1547,17 +1490,16 @@ module.exports = function (chai, _) {
    * @name throw
    * @alias throws
    * @alias Throw
    * @param {ErrorConstructor} constructor
    * @param {String|RegExp} expected error message
    * @param {String} message _optional_
    * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
    * @returns error for chaining (null if no error)
-   * @namespace BDD
    * @api public
    */
 
   function assertThrows (constructor, errMsg, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     new Assertion(obj, msg).is.a('function');
 
@@ -1572,19 +1514,19 @@ module.exports = function (chai, _) {
     } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) {
       errMsg = constructor;
       constructor = null;
     } else if (constructor && constructor instanceof Error) {
       desiredError = constructor;
       constructor = null;
       errMsg = null;
     } else if (typeof constructor === 'function') {
-      name = constructor.prototype.name;
-      if (!name || (name === 'Error' && constructor !== Error)) {
-        name = constructor.name || (new constructor()).name;
+      name = constructor.prototype.name || constructor.name;
+      if (name === 'Error' && constructor !== Error) {
+        name = (new constructor()).name;
       }
     } else {
       constructor = null;
     }
 
     try {
       obj();
     } catch (err) {
@@ -1688,127 +1630,111 @@ module.exports = function (chai, _) {
    *
    * To check if a constructor will respond to a static function,
    * set the `itself` flag.
    *
    *     Klass.baz = function(){};
    *     expect(Klass).itself.to.respondTo('baz');
    *
    * @name respondTo
-   * @alias respondsTo
    * @param {String} method
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
-  function respondTo (method, msg) {
+  Assertion.addMethod('respondTo', function (method, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object')
       , itself = flag(this, 'itself')
       , context = ('function' === _.type(obj) && !itself)
         ? obj.prototype[method]
         : obj[method];
 
     this.assert(
         'function' === typeof context
       , 'expected #{this} to respond to ' + _.inspect(method)
       , 'expected #{this} to not respond to ' + _.inspect(method)
     );
-  }
-
-  Assertion.addMethod('respondTo', respondTo);
-  Assertion.addMethod('respondsTo', respondTo);
+  });
 
   /**
    * ### .itself
    *
    * Sets the `itself` flag, later used by the `respondTo` assertion.
    *
    *     function Foo() {}
    *     Foo.bar = function() {}
    *     Foo.prototype.baz = function() {}
    *
    *     expect(Foo).itself.to.respondTo('bar');
    *     expect(Foo).itself.not.to.respondTo('baz');
    *
    * @name itself
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addProperty('itself', function () {
     flag(this, 'itself', true);
   });
 
   /**
    * ### .satisfy(method)
    *
    * Asserts that the target passes a given truth test.
    *
    *     expect(1).to.satisfy(function(num) { return num > 0; });
    *
    * @name satisfy
-   * @alias satisfies
    * @param {Function} matcher
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
-  function satisfy (matcher, msg) {
+  Assertion.addMethod('satisfy', function (matcher, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
     var result = matcher(obj);
     this.assert(
         result
       , 'expected #{this} to satisfy ' + _.objDisplay(matcher)
       , 'expected #{this} to not satisfy' + _.objDisplay(matcher)
       , this.negate ? false : true
       , result
     );
-  }
-
-  Assertion.addMethod('satisfy', satisfy);
-  Assertion.addMethod('satisfies', satisfy);
+  });
 
   /**
    * ### .closeTo(expected, delta)
    *
    * Asserts that the target is equal `expected`, to within a +/- `delta` range.
    *
    *     expect(1.5).to.be.closeTo(1, 0.5);
    *
    * @name closeTo
-   * @alias approximately
    * @param {Number} expected
    * @param {Number} delta
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
-  function closeTo(expected, delta, msg) {
+  Assertion.addMethod('closeTo', function (expected, delta, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
 
     new Assertion(obj, msg).is.a('number');
     if (_.type(expected) !== 'number' || _.type(delta) !== 'number') {
-      throw new Error('the arguments to closeTo or approximately must be numbers');
+      throw new Error('the arguments to closeTo must be numbers');
     }
 
     this.assert(
         Math.abs(obj - expected) <= delta
       , 'expected #{this} to be close to ' + expected + ' +/- ' + delta
       , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta
     );
-  }
-
-  Assertion.addMethod('closeTo', closeTo);
-  Assertion.addMethod('approximately', closeTo);
+  });
 
   function isSubsetOf(subset, superset, cmp) {
     return subset.every(function(elem) {
       if (!cmp) return superset.indexOf(elem) !== -1;
 
       return superset.some(function(elem2) {
         return cmp(elem, elem2);
       });
@@ -1829,17 +1755,16 @@ module.exports = function (chai, _) {
    *     expect([4, 2]).to.have.members([2, 4]);
    *     expect([5, 2]).to.not.have.members([5, 2, 1]);
    *
    *     expect([{ id: 1 }]).to.deep.include.members([{ id: 1 }]);
    *
    * @name members
    * @param {Array} set
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   Assertion.addMethod('members', function (subset, msg) {
     if (msg) flag(this, 'message', msg);
     var obj = flag(this, 'object');
 
     new Assertion(obj).to.be.an('array');
@@ -1862,72 +1787,32 @@ module.exports = function (chai, _) {
         , 'expected #{this} to have the same members as #{act}'
         , 'expected #{this} to not have the same members as #{act}'
         , obj
         , subset
     );
   });
 
   /**
-   * ### .oneOf(list)
-   *
-   * Assert that a value appears somewhere in the top level of array `list`.
-   *
-   *     expect('a').to.be.oneOf(['a', 'b', 'c']);
-   *     expect(9).to.not.be.oneOf(['z']);
-   *     expect([3]).to.not.be.oneOf([1, 2, [3]]);
-   *
-   *     var three = [3];
-   *     // for object-types, contents are not compared
-   *     expect(three).to.not.be.oneOf([1, 2, [3]]);
-   *     // comparing references works
-   *     expect(three).to.be.oneOf([1, 2, three]);
-   *
-   * @name oneOf
-   * @param {Array<*>} list
-   * @param {String} message _optional_
-   * @namespace BDD
-   * @api public
-   */
-
-  function oneOf (list, msg) {
-    if (msg) flag(this, 'message', msg);
-    var expected = flag(this, 'object');
-    new Assertion(list).to.be.an('array');
-
-    this.assert(
-        list.indexOf(expected) > -1
-      , 'expected #{this} to be one of #{exp}'
-      , 'expected #{this} to not be one of #{exp}'
-      , list
-      , expected
-    );
-  }
-
-  Assertion.addMethod('oneOf', oneOf);
-
-
-  /**
    * ### .change(function)
    *
    * Asserts that a function changes an object property
    *
    *     var obj = { val: 10 };
    *     var fn = function() { obj.val += 3 };
    *     var noChangeFn = function() { return 'foo' + 'bar'; }
    *     expect(fn).to.change(obj, 'val');
-   *     expect(noChangeFn).to.not.change(obj, 'val')
+   *     expect(noChangFn).to.not.change(obj, 'val')
    *
    * @name change
    * @alias changes
    * @alias Change
    * @param {String} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertChanges (object, prop, msg) {
     if (msg) flag(this, 'message', msg);
     var fn = flag(this, 'object');
     new Assertion(object, msg).to.have.property(prop);
     new Assertion(fn).is.a('function');
@@ -1955,17 +1840,16 @@ module.exports = function (chai, _) {
    *     expect(fn).to.increase(obj, 'val');
    *
    * @name increase
    * @alias increases
    * @alias Increase
    * @param {String} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertIncreases (object, prop, msg) {
     if (msg) flag(this, 'message', msg);
     var fn = flag(this, 'object');
     new Assertion(object, msg).to.have.property(prop);
     new Assertion(fn).is.a('function');
@@ -1993,17 +1877,16 @@ module.exports = function (chai, _) {
    *     expect(fn).to.decrease(obj, 'val');
    *
    * @name decrease
    * @alias decreases
    * @alias Decrease
    * @param {String} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace BDD
    * @api public
    */
 
   function assertDecreases (object, prop, msg) {
     if (msg) flag(this, 'message', msg);
     var fn = flag(this, 'object');
     new Assertion(object, msg).to.have.property(prop);
     new Assertion(fn).is.a('function');
@@ -2016,144 +1899,19 @@ module.exports = function (chai, _) {
       , 'expected .' + prop + ' to decrease'
       , 'expected .' + prop + ' to not decrease'
     );
   }
 
   Assertion.addChainableMethod('decrease', assertDecreases);
   Assertion.addChainableMethod('decreases', assertDecreases);
 
-  /**
-   * ### .extensible
-   *
-   * Asserts that the target is extensible (can have new properties added to
-   * it).
-   *
-   *     var nonExtensibleObject = Object.preventExtensions({});
-   *     var sealedObject = Object.seal({});
-   *     var frozenObject = Object.freeze({});
-   *
-   *     expect({}).to.be.extensible;
-   *     expect(nonExtensibleObject).to.not.be.extensible;
-   *     expect(sealedObject).to.not.be.extensible;
-   *     expect(frozenObject).to.not.be.extensible;
-   *
-   * @name extensible
-   * @namespace BDD
-   * @api public
-   */
-
-  Assertion.addProperty('extensible', function() {
-    var obj = flag(this, 'object');
-
-    // In ES5, if the argument to this method is not an object (a primitive), then it will cause a TypeError.
-    // In ES6, a non-object argument will be treated as if it was a non-extensible ordinary object, simply return false.
-    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible
-    // The following provides ES6 behavior when a TypeError is thrown under ES5.
-
-    var isExtensible;
-
-    try {
-      isExtensible = Object.isExtensible(obj);
-    } catch (err) {
-      if (err instanceof TypeError) isExtensible = false;
-      else throw err;
-    }
-
-    this.assert(
-      isExtensible
-      , 'expected #{this} to be extensible'
-      , 'expected #{this} to not be extensible'
-    );
-  });
-
-  /**
-   * ### .sealed
-   *
-   * Asserts that the target is sealed (cannot have new properties added to it
-   * and its existing properties cannot be removed).
-   *
-   *     var sealedObject = Object.seal({});
-   *     var frozenObject = Object.freeze({});
-   *
-   *     expect(sealedObject).to.be.sealed;
-   *     expect(frozenObject).to.be.sealed;
-   *     expect({}).to.not.be.sealed;
-   *
-   * @name sealed
-   * @namespace BDD
-   * @api public
-   */
-
-  Assertion.addProperty('sealed', function() {
-    var obj = flag(this, 'object');
-
-    // In ES5, if the argument to this method is not an object (a primitive), then it will cause a TypeError.
-    // In ES6, a non-object argument will be treated as if it was a sealed ordinary object, simply return true.
-    // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed
-    // The following provides ES6 behavior when a TypeError is thrown under ES5.
-
-    var isSealed;
-
-    try {
-      isSealed = Object.isSealed(obj);
-    } catch (err) {
-      if (err instanceof TypeError) isSealed = true;
-      else throw err;
-    }
-
-    this.assert(
-      isSealed
-      , 'expected #{this} to be sealed'
-      , 'expected #{this} to not be sealed'
-    );
-  });
-
-  /**
-   * ### .frozen
-   *
-   * Asserts that the target is frozen (cannot have new properties added to it
-   * and its existing properties cannot be modified).
-   *
-   *     var frozenObject = Object.freeze({});
-   *
-   *     expect(frozenObject).to.be.frozen;
-   *     expect({}).to.not.be.frozen;
-   *
-   * @name frozen
-   * @namespace BDD
-   * @api public
-   */
-
-  Assertion.addProperty('frozen', function() {
-    var obj = flag(this, 'object');
-
-    // In ES5, if the argument to this method is not an object (a primitive), then it will cause a TypeError.
-    // In ES6, a non-object argument will be treated as if it was a frozen ordinary object, simply return true.
-    // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen
-    // The following provides ES6 behavior when a TypeError is thrown under ES5.
-
-    var isFrozen;
-
-    try {
-      isFrozen = Object.isFrozen(obj);
-    } catch (err) {
-      if (err instanceof TypeError) isFrozen = true;
-      else throw err;
-    }
-
-    this.assert(
-      isFrozen
-      , 'expected #{this} to be frozen'
-      , 'expected #{this} to not be frozen'
-    );
-  });
 };
 
-},{}],6:[function(require,module,exports){
+},{}],5:[function(require,module,exports){
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 
 module.exports = function (chai, util) {
@@ -2175,17 +1933,16 @@ module.exports = function (chai, util) {
    * Write your own test expressions.
    *
    *     assert('foo' !== 'bar', 'foo is not bar');
    *     assert(Array.isArray([]), 'empty arrays are arrays');
    *
    * @param {Mixed} expression to test for truthiness
    * @param {String} message to display on error
    * @name assert
-   * @namespace Assert
    * @api public
    */
 
   var assert = chai.assert = function (express, errmsg) {
     var test = new Assertion(null, null, chai.assert);
     test.assert(
         express
       , errmsg
@@ -2198,81 +1955,75 @@ module.exports = function (chai, util) {
    *
    * Throw a failure. Node.js `assert` module-compatible.
    *
    * @name fail
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
    * @param {String} operator
-   * @namespace Assert
    * @api public
    */
 
   assert.fail = function (actual, expected, message, operator) {
     message = message || 'assert.fail()';
     throw new chai.AssertionError(message, {
         actual: actual
       , expected: expected
       , operator: operator
     }, assert.fail);
   };
 
   /**
-   * ### .isOk(object, [message])
+   * ### .ok(object, [message])
    *
    * Asserts that `object` is truthy.
    *
-   *     assert.isOk('everything', 'everything is ok');
-   *     assert.isOk(false, 'this will fail');
-   *
-   * @name isOk
-   * @alias ok
+   *     assert.ok('everything', 'everything is ok');
+   *     assert.ok(false, 'this will fail');
+   *
+   * @name ok
    * @param {Mixed} object to test
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
-  assert.isOk = function (val, msg) {
+  assert.ok = function (val, msg) {
     new Assertion(val, msg).is.ok;
   };
 
   /**
-   * ### .isNotOk(object, [message])
+   * ### .notOk(object, [message])
    *
    * Asserts that `object` is falsy.
    *
-   *     assert.isNotOk('everything', 'this will fail');
-   *     assert.isNotOk(false, 'this will pass');
-   *
-   * @name isNotOk
-   * @alias notOk
+   *     assert.notOk('everything', 'this will fail');
+   *     assert.notOk(false, 'this will pass');
+   *
+   * @name notOk
    * @param {Mixed} object to test
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
-  assert.isNotOk = function (val, msg) {
+  assert.notOk = function (val, msg) {
     new Assertion(val, msg).is.not.ok;
   };
 
   /**
    * ### .equal(actual, expected, [message])
    *
    * Asserts non-strict equality (`==`) of `actual` and `expected`.
    *
    *     assert.equal(3, '3', '== coerces values to strings');
    *
    * @name equal
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.equal = function (act, exp, msg) {
     var test = new Assertion(act, msg, assert.equal);
 
     test.assert(
         exp == flag(test, 'object')
@@ -2289,17 +2040,16 @@ module.exports = function (chai, util) {
    * Asserts non-strict inequality (`!=`) of `actual` and `expected`.
    *
    *     assert.notEqual(3, 4, 'these numbers are not equal');
    *
    * @name notEqual
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notEqual = function (act, exp, msg) {
     var test = new Assertion(act, msg, assert.notEqual);
 
     test.assert(
         exp != flag(test, 'object')
@@ -2316,17 +2066,16 @@ module.exports = function (chai, util) {
    * Asserts strict equality (`===`) of `actual` and `expected`.
    *
    *     assert.strictEqual(true, true, 'these booleans are strictly equal');
    *
    * @name strictEqual
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.strictEqual = function (act, exp, msg) {
     new Assertion(act, msg).to.equal(exp);
   };
 
   /**
@@ -2335,17 +2084,16 @@ module.exports = function (chai, util) {
    * Asserts strict inequality (`!==`) of `actual` and `expected`.
    *
    *     assert.notStrictEqual(3, '3', 'no coercion for strict equality');
    *
    * @name notStrictEqual
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notStrictEqual = function (act, exp, msg) {
     new Assertion(act, msg).to.not.equal(exp);
   };
 
   /**
@@ -2354,17 +2102,16 @@ module.exports = function (chai, util) {
    * Asserts that `actual` is deeply equal to `expected`.
    *
    *     assert.deepEqual({ tea: 'green' }, { tea: 'green' });
    *
    * @name deepEqual
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.deepEqual = function (act, exp, msg) {
     new Assertion(act, msg).to.eql(exp);
   };
 
   /**
@@ -2373,189 +2120,105 @@ module.exports = function (chai, util) {
    * Assert that `actual` is not deeply equal to `expected`.
    *
    *     assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' });
    *
    * @name notDeepEqual
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notDeepEqual = function (act, exp, msg) {
     new Assertion(act, msg).to.not.eql(exp);
   };
 
+  /**
+   * ### .isTrue(value, [message])
+   *
+   * Asserts that `value` is true.
+   *
+   *     var teaServed = true;
+   *     assert.isTrue(teaServed, 'the tea has been served');
+   *
+   * @name isTrue
+   * @param {Mixed} value
+   * @param {String} message
+   * @api public
+   */
+
+  assert.isAbove = function (val, abv, msg) {
+    new Assertion(val, msg).to.be.above(abv);
+  };
+
    /**
    * ### .isAbove(valueToCheck, valueToBeAbove, [message])
    *
    * Asserts `valueToCheck` is strictly greater than (>) `valueToBeAbove`
    *
    *     assert.isAbove(5, 2, '5 is strictly greater than 2');
    *
    * @name isAbove
    * @param {Mixed} valueToCheck
    * @param {Mixed} valueToBeAbove
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
-  assert.isAbove = function (val, abv, msg) {
-    new Assertion(val, msg).to.be.above(abv);
-  };
-
-   /**
-   * ### .isAtLeast(valueToCheck, valueToBeAtLeast, [message])
-   *
-   * Asserts `valueToCheck` is greater than or equal to (>=) `valueToBeAtLeast`
-   *
-   *     assert.isAtLeast(5, 2, '5 is greater or equal to 2');
-   *     assert.isAtLeast(3, 3, '3 is greater or equal to 3');
-   *
-   * @name isAtLeast
-   * @param {Mixed} valueToCheck
-   * @param {Mixed} valueToBeAtLeast
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isAtLeast = function (val, atlst, msg) {
-    new Assertion(val, msg).to.be.least(atlst);
+  assert.isBelow = function (val, blw, msg) {
+    new Assertion(val, msg).to.be.below(blw);
   };
 
    /**
    * ### .isBelow(valueToCheck, valueToBeBelow, [message])
    *
    * Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow`
    *
    *     assert.isBelow(3, 6, '3 is strictly less than 6');
    *
    * @name isBelow
    * @param {Mixed} valueToCheck
    * @param {Mixed} valueToBeBelow
    * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isBelow = function (val, blw, msg) {
-    new Assertion(val, msg).to.be.below(blw);
-  };
-
-   /**
-   * ### .isAtMost(valueToCheck, valueToBeAtMost, [message])
-   *
-   * Asserts `valueToCheck` is less than or equal to (<=) `valueToBeAtMost`
-   *
-   *     assert.isAtMost(3, 6, '3 is less than or equal to 6');
-   *     assert.isAtMost(4, 4, '4 is less than or equal to 4');
-   *
-   * @name isAtMost
-   * @param {Mixed} valueToCheck
-   * @param {Mixed} valueToBeAtMost
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isAtMost = function (val, atmst, msg) {
-    new Assertion(val, msg).to.be.most(atmst);
-  };
-
-  /**
-   * ### .isTrue(value, [message])
-   *
-   * Asserts that `value` is true.
-   *
-   *     var teaServed = true;
-   *     assert.isTrue(teaServed, 'the tea has been served');
-   *
-   * @name isTrue
-   * @param {Mixed} value
-   * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isTrue = function (val, msg) {
     new Assertion(val, msg).is['true'];
   };
 
   /**
-   * ### .isNotTrue(value, [message])
-   *
-   * Asserts that `value` is not true.
-   *
-   *     var tea = 'tasty chai';
-   *     assert.isNotTrue(tea, 'great, time for tea!');
-   *
-   * @name isNotTrue
-   * @param {Mixed} value
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isNotTrue = function (val, msg) {
-    new Assertion(val, msg).to.not.equal(true);
-  };
-
-  /**
    * ### .isFalse(value, [message])
    *
    * Asserts that `value` is false.
    *
    *     var teaServed = false;
    *     assert.isFalse(teaServed, 'no tea yet? hmm...');
    *
    * @name isFalse
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isFalse = function (val, msg) {
     new Assertion(val, msg).is['false'];
   };
 
   /**
-   * ### .isNotFalse(value, [message])
-   *
-   * Asserts that `value` is not false.
-   *
-   *     var tea = 'tasty chai';
-   *     assert.isNotFalse(tea, 'great, time for tea!');
-   *
-   * @name isNotFalse
-   * @param {Mixed} value
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isNotFalse = function (val, msg) {
-    new Assertion(val, msg).to.not.equal(false);
-  };
-
-  /**
    * ### .isNull(value, [message])
    *
    * Asserts that `value` is null.
    *
    *     assert.isNull(err, 'there was no error');
    *
    * @name isNull
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNull = function (val, msg) {
     new Assertion(val, msg).to.equal(null);
   };
 
   /**
@@ -2564,69 +2227,34 @@ module.exports = function (chai, util) {
    * Asserts that `value` is not null.
    *
    *     var tea = 'tasty chai';
    *     assert.isNotNull(tea, 'great, time for tea!');
    *
    * @name isNotNull
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNotNull = function (val, msg) {
     new Assertion(val, msg).to.not.equal(null);
   };
 
   /**
-   * ### .isNaN
-   * Asserts that value is NaN
-   *
-   *    assert.isNaN('foo', 'foo is NaN');
-   *
-   * @name isNaN
-   * @param {Mixed} value
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isNaN = function (val, msg) {
-    new Assertion(val, msg).to.be.NaN;
-  };
-
-  /**
-   * ### .isNotNaN
-   * Asserts that value is not NaN
-   *
-   *    assert.isNotNaN(4, '4 is not NaN');
-   *
-   * @name isNotNaN
-   * @param {Mixed} value
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-  assert.isNotNaN = function (val, msg) {
-    new Assertion(val, msg).not.to.be.NaN;
-  };
-
-  /**
    * ### .isUndefined(value, [message])
    *
    * Asserts that `value` is `undefined`.
    *
    *     var tea;
    *     assert.isUndefined(tea, 'no tea defined');
    *
    * @name isUndefined
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isUndefined = function (val, msg) {
     new Assertion(val, msg).to.equal(undefined);
   };
 
   /**
@@ -2635,17 +2263,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is not `undefined`.
    *
    *     var tea = 'cup of chai';
    *     assert.isDefined(tea, 'tea has been defined');
    *
    * @name isDefined
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isDefined = function (val, msg) {
     new Assertion(val, msg).to.not.equal(undefined);
   };
 
   /**
@@ -2654,17 +2281,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is a function.
    *
    *     function serveTea() { return 'cup of tea'; };
    *     assert.isFunction(serveTea, 'great, we can have tea now');
    *
    * @name isFunction
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isFunction = function (val, msg) {
     new Assertion(val, msg).to.be.a('function');
   };
 
   /**
@@ -2673,57 +2299,54 @@ module.exports = function (chai, util) {
    * Asserts that `value` is _not_ a function.
    *
    *     var serveTea = [ 'heat', 'pour', 'sip' ];
    *     assert.isNotFunction(serveTea, 'great, we have listed the steps');
    *
    * @name isNotFunction
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNotFunction = function (val, msg) {
     new Assertion(val, msg).to.not.be.a('function');
   };
 
   /**
    * ### .isObject(value, [message])
    *
-   * Asserts that `value` is an object of type 'Object' (as revealed by `Object.prototype.toString`).
-   * _The assertion does not match subclassed objects._
+   * Asserts that `value` is an object (as revealed by
+   * `Object.prototype.toString`).
    *
    *     var selection = { name: 'Chai', serve: 'with spices' };
    *     assert.isObject(selection, 'tea selection is an object');
    *
    * @name isObject
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isObject = function (val, msg) {
     new Assertion(val, msg).to.be.a('object');
   };
 
   /**
    * ### .isNotObject(value, [message])
    *
-   * Asserts that `value` is _not_ an object of type 'Object' (as revealed by `Object.prototype.toString`).
+   * Asserts that `value` is _not_ an object.
    *
    *     var selection = 'chai'
    *     assert.isNotObject(selection, 'tea selection is not an object');
    *     assert.isNotObject(null, 'null is not an object');
    *
    * @name isNotObject
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNotObject = function (val, msg) {
     new Assertion(val, msg).to.not.be.a('object');
   };
 
   /**
@@ -2732,17 +2355,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is an array.
    *
    *     var menu = [ 'green', 'chai', 'oolong' ];
    *     assert.isArray(menu, 'what kind of tea do we want?');
    *
    * @name isArray
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isArray = function (val, msg) {
     new Assertion(val, msg).to.be.an('array');
   };
 
   /**
@@ -2751,17 +2373,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is _not_ an array.
    *
    *     var menu = 'green|chai|oolong';
    *     assert.isNotArray(menu, 'what kind of tea do we want?');
    *
    * @name isNotArray
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNotArray = function (val, msg) {
     new Assertion(val, msg).to.not.be.an('array');
   };
 
   /**
@@ -2770,17 +2391,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is a string.
    *
    *     var teaOrder = 'chai';
    *     assert.isString(teaOrder, 'order placed');
    *
    * @name isString
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isString = function (val, msg) {
     new Assertion(val, msg).to.be.a('string');
   };
 
   /**
@@ -2789,17 +2409,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is _not_ a string.
    *
    *     var teaOrder = 4;
    *     assert.isNotString(teaOrder, 'order placed');
    *
    * @name isNotString
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNotString = function (val, msg) {
     new Assertion(val, msg).to.not.be.a('string');
   };
 
   /**
@@ -2808,17 +2427,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is a number.
    *
    *     var cups = 2;
    *     assert.isNumber(cups, 'how many cups');
    *
    * @name isNumber
    * @param {Number} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNumber = function (val, msg) {
     new Assertion(val, msg).to.be.a('number');
   };
 
   /**
@@ -2827,17 +2445,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` is _not_ a number.
    *
    *     var cups = '2 cups please';
    *     assert.isNotNumber(cups, 'how many cups');
    *
    * @name isNotNumber
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNotNumber = function (val, msg) {
     new Assertion(val, msg).to.not.be.a('number');
   };
 
   /**
@@ -2849,17 +2466,16 @@ module.exports = function (chai, util) {
    *       , teaServed = false;
    *
    *     assert.isBoolean(teaReady, 'is the tea ready');
    *     assert.isBoolean(teaServed, 'has tea been served');
    *
    * @name isBoolean
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isBoolean = function (val, msg) {
     new Assertion(val, msg).to.be.a('boolean');
   };
 
   /**
@@ -2871,17 +2487,16 @@ module.exports = function (chai, util) {
    *       , teaServed = 'nope';
    *
    *     assert.isNotBoolean(teaReady, 'is the tea ready');
    *     assert.isNotBoolean(teaServed, 'has tea been served');
    *
    * @name isNotBoolean
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.isNotBoolean = function (val, msg) {
     new Assertion(val, msg).to.not.be.a('boolean');
   };
 
   /**
@@ -2896,17 +2511,16 @@ module.exports = function (chai, util) {
    *     assert.typeOf(/tea/, 'regexp', 'we have a regular expression');
    *     assert.typeOf(null, 'null', 'we have a null');
    *     assert.typeOf(undefined, 'undefined', 'we have an undefined');
    *
    * @name typeOf
    * @param {Mixed} value
    * @param {String} name
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.typeOf = function (val, type, msg) {
     new Assertion(val, msg).to.be.a(type);
   };
 
   /**
@@ -2916,17 +2530,16 @@ module.exports = function (chai, util) {
    * `Object.prototype.toString`.
    *
    *     assert.notTypeOf('tea', 'number', 'strings are not numbers');
    *
    * @name notTypeOf
    * @param {Mixed} value
    * @param {String} typeof name
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notTypeOf = function (val, type, msg) {
     new Assertion(val, msg).to.not.be.a(type);
   };
 
   /**
@@ -2938,17 +2551,16 @@ module.exports = function (chai, util) {
    *       , chai = new Tea('chai');
    *
    *     assert.instanceOf(chai, Tea, 'chai is an instance of tea');
    *
    * @name instanceOf
    * @param {Object} object
    * @param {Constructor} constructor
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.instanceOf = function (val, type, msg) {
     new Assertion(val, msg).to.be.instanceOf(type);
   };
 
   /**
@@ -2960,17 +2572,16 @@ module.exports = function (chai, util) {
    *       , chai = new String('chai');
    *
    *     assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea');
    *
    * @name notInstanceOf
    * @param {Object} object
    * @param {Constructor} constructor
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notInstanceOf = function (val, type, msg) {
     new Assertion(val, msg).to.not.be.instanceOf(type);
   };
 
   /**
@@ -2981,17 +2592,16 @@ module.exports = function (chai, util) {
    *
    *     assert.include('foobar', 'bar', 'foobar contains string "bar"');
    *     assert.include([ 1, 2, 3 ], 3, 'array contains value');
    *
    * @name include
    * @param {Array|String} haystack
    * @param {Mixed} needle
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.include = function (exp, inc, msg) {
     new Assertion(exp, msg, assert.include).include(inc);
   };
 
   /**
@@ -3002,17 +2612,16 @@ module.exports = function (chai, util) {
    *
    *     assert.notInclude('foobar', 'baz', 'string not include substring');
    *     assert.notInclude([ 1, 2, 3 ], 4, 'array not include contain value');
    *
    * @name notInclude
    * @param {Array|String} haystack
    * @param {Mixed} needle
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notInclude = function (exp, inc, msg) {
     new Assertion(exp, msg, assert.notInclude).not.include(inc);
   };
 
   /**
@@ -3021,17 +2630,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` matches the regular expression `regexp`.
    *
    *     assert.match('foobar', /^foo/, 'regexp matches');
    *
    * @name match
    * @param {Mixed} value
    * @param {RegExp} regexp
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.match = function (exp, re, msg) {
     new Assertion(exp, msg).to.match(re);
   };
 
   /**
@@ -3040,17 +2648,16 @@ module.exports = function (chai, util) {
    * Asserts that `value` does not match the regular expression `regexp`.
    *
    *     assert.notMatch('foobar', /^foo/, 'regexp does not match');
    *
    * @name notMatch
    * @param {Mixed} value
    * @param {RegExp} regexp
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notMatch = function (exp, re, msg) {
     new Assertion(exp, msg).to.not.match(re);
   };
 
   /**
@@ -3059,17 +2666,16 @@ module.exports = function (chai, util) {
    * Asserts that `object` has a property named by `property`.
    *
    *     assert.property({ tea: { green: 'matcha' }}, 'tea');
    *
    * @name property
    * @param {Object} object
    * @param {String} property
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.property = function (obj, prop, msg) {
     new Assertion(obj, msg).to.have.property(prop);
   };
 
   /**
@@ -3078,17 +2684,16 @@ module.exports = function (chai, util) {
    * Asserts that `object` does _not_ have a property named by `property`.
    *
    *     assert.notProperty({ tea: { green: 'matcha' }}, 'coffee');
    *
    * @name notProperty
    * @param {Object} object
    * @param {String} property
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notProperty = function (obj, prop, msg) {
     new Assertion(obj, msg).to.not.have.property(prop);
   };
 
   /**
@@ -3098,17 +2703,16 @@ module.exports = function (chai, util) {
    * string using dot- and bracket-notation for deep reference.
    *
    *     assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green');
    *
    * @name deepProperty
    * @param {Object} object
    * @param {String} property
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.deepProperty = function (obj, prop, msg) {
     new Assertion(obj, msg).to.have.deep.property(prop);
   };
 
   /**
@@ -3118,17 +2722,16 @@ module.exports = function (chai, util) {
    * can be a string using dot- and bracket-notation for deep reference.
    *
    *     assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong');
    *
    * @name notDeepProperty
    * @param {Object} object
    * @param {String} property
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.notDeepProperty = function (obj, prop, msg) {
     new Assertion(obj, msg).to.not.have.deep.property(prop);
   };
 
   /**
@@ -3139,17 +2742,16 @@ module.exports = function (chai, util) {
    *
    *     assert.propertyVal({ tea: 'is good' }, 'tea', 'is good');
    *
    * @name propertyVal
    * @param {Object} object
    * @param {String} property
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.propertyVal = function (obj, prop, val, msg) {
     new Assertion(obj, msg).to.have.property(prop, val);
   };
 
   /**
@@ -3160,17 +2762,16 @@ module.exports = function (chai, util) {
    *
    *     assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad');
    *
    * @name propertyNotVal
    * @param {Object} object
    * @param {String} property
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.propertyNotVal = function (obj, prop, val, msg) {
     new Assertion(obj, msg).to.not.have.property(prop, val);
   };
 
   /**
@@ -3182,17 +2783,16 @@ module.exports = function (chai, util) {
    *
    *     assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha');
    *
    * @name deepPropertyVal
    * @param {Object} object
    * @param {String} property
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.deepPropertyVal = function (obj, prop, val, msg) {
     new Assertion(obj, msg).to.have.deep.property(prop, val);
   };
 
   /**
@@ -3204,76 +2804,73 @@ module.exports = function (chai, util) {
    *
    *     assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha');
    *
    * @name deepPropertyNotVal
    * @param {Object} object
    * @param {String} property
    * @param {Mixed} value
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.deepPropertyNotVal = function (obj, prop, val, msg) {
     new Assertion(obj, msg).to.not.have.deep.property(prop, val);
   };
 
   /**
    * ### .lengthOf(object, length, [message])
    *
    * Asserts that `object` has a `length` property with the expected value.
    *
    *     assert.lengthOf([1,2,3], 3, 'array has length of 3');
-   *     assert.lengthOf('foobar', 6, 'string has length of 6');
+   *     assert.lengthOf('foobar', 5, 'string has length of 6');
    *
    * @name lengthOf
    * @param {Mixed} object
    * @param {Number} length
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.lengthOf = function (exp, len, msg) {
     new Assertion(exp, msg).to.have.length(len);
   };
 
   /**
    * ### .throws(function, [constructor/string/regexp], [string/regexp], [message])
    *
    * Asserts that `function` will throw an error that is an instance of
    * `constructor`, or alternately that it will throw an error with message
    * matching `regexp`.
    *
-   *     assert.throws(fn, 'function throws a reference error');
-   *     assert.throws(fn, /function throws a reference error/);
-   *     assert.throws(fn, ReferenceError);
-   *     assert.throws(fn, ReferenceError, 'function throws a reference error');
-   *     assert.throws(fn, ReferenceError, /function throws a reference error/);
+   *     assert.throw(fn, 'function throws a reference error');
+   *     assert.throw(fn, /function throws a reference error/);
+   *     assert.throw(fn, ReferenceError);
+   *     assert.throw(fn, ReferenceError, 'function throws a reference error');
+   *     assert.throw(fn, ReferenceError, /function throws a reference error/);
    *
    * @name throws
    * @alias throw
    * @alias Throw
    * @param {Function} function
    * @param {ErrorConstructor} constructor
    * @param {RegExp} regexp
    * @param {String} message
    * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
-   * @namespace Assert
    * @api public
    */
 
-  assert.throws = function (fn, errt, errs, msg) {
+  assert.Throw = function (fn, errt, errs, msg) {
     if ('string' === typeof errt || errt instanceof RegExp) {
       errs = errt;
       errt = null;
     }
 
-    var assertErr = new Assertion(fn, msg).to.throw(errt, errs);
+    var assertErr = new Assertion(fn, msg).to.Throw(errt, errs);
     return flag(assertErr, 'object');
   };
 
   /**
    * ### .doesNotThrow(function, [constructor/regexp], [message])
    *
    * Asserts that `function` will _not_ throw an error that is an instance of
    * `constructor`, or alternately that it will not throw an error with message
@@ -3282,17 +2879,16 @@ module.exports = function (chai, util) {
    *     assert.doesNotThrow(fn, Error, 'function does not throw');
    *
    * @name doesNotThrow
    * @param {Function} function
    * @param {ErrorConstructor} constructor
    * @param {RegExp} regexp
    * @param {String} message
    * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
-   * @namespace Assert
    * @api public
    */
 
   assert.doesNotThrow = function (fn, type, msg) {
     if ('string' === typeof type) {
       msg = type;
       type = null;
     }
@@ -3308,17 +2904,16 @@ module.exports = function (chai, util) {
    *     assert.operator(1, '<', 2, 'everything is ok');
    *     assert.operator(1, '>', 2, 'this will fail');
    *
    * @name operator
    * @param {Mixed} val1
    * @param {String} operator
    * @param {Mixed} val2
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.operator = function (val, operator, val2, msg) {
     var ok;
     switch(operator) {
       case '==':
         ok = val == val2;
@@ -3361,57 +2956,35 @@ module.exports = function (chai, util) {
    *
    *     assert.closeTo(1.5, 1, 0.5, 'numbers are close');
    *
    * @name closeTo
    * @param {Number} actual
    * @param {Number} expected
    * @param {Number} delta
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.closeTo = function (act, exp, delta, msg) {
     new Assertion(act, msg).to.be.closeTo(exp, delta);
   };
 
   /**
-   * ### .approximately(actual, expected, delta, [message])
-   *
-   * Asserts that the target is equal `expected`, to within a +/- `delta` range.
-   *
-   *     assert.approximately(1.5, 1, 0.5, 'numbers are close');
-   *
-   * @name approximately
-   * @param {Number} actual
-   * @param {Number} expected
-   * @param {Number} delta
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.approximately = function (act, exp, delta, msg) {
-    new Assertion(act, msg).to.be.approximately(exp, delta);
-  };
-
-  /**
    * ### .sameMembers(set1, set2, [message])
    *
    * Asserts that `set1` and `set2` have the same members.
    * Order is not taken into account.
    *
    *     assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members');
    *
    * @name sameMembers
    * @param {Array} set1
    * @param {Array} set2
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.sameMembers = function (set1, set2, msg) {
     new Assertion(set1, msg).to.have.same.members(set2);
   }
 
   /**
@@ -3421,17 +2994,16 @@ module.exports = function (chai, util) {
    * Order is not taken into account.
    *
    *     assert.sameDeepMembers([ {b: 3}, {a: 2}, {c: 5} ], [ {c: 5}, {b: 3}, {a: 2} ], 'same deep members');
    *
    * @name sameDeepMembers
    * @param {Array} set1
    * @param {Array} set2
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.sameDeepMembers = function (set1, set2, msg) {
     new Assertion(set1, msg).to.have.same.deep.members(set2);
   }
 
   /**
@@ -3441,79 +3013,37 @@ module.exports = function (chai, util) {
    * Order is not taken into account.
    *
    *     assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members');
    *
    * @name includeMembers
    * @param {Array} superset
    * @param {Array} subset
    * @param {String} message
-   * @namespace Assert
    * @api public
    */
 
   assert.includeMembers = function (superset, subset, msg) {
     new Assertion(superset, msg).to.include.members(subset);
   }
 
-  /**
-   * ### .includeDeepMembers(superset, subset, [message])
-   *
-   * Asserts that `subset` is included in `superset` - using deep equality checking.
-   * Order is not taken into account.
-   * Duplicates are ignored.
-   *
-   *     assert.includeDeepMembers([ {a: 1}, {b: 2}, {c: 3} ], [ {b: 2}, {a: 1}, {b: 2} ], 'include deep members');
-   *
-   * @name includeDeepMembers
-   * @param {Array} superset
-   * @param {Array} subset
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.includeDeepMembers = function (superset, subset, msg) {
-    new Assertion(superset, msg).to.include.deep.members(subset);
-  }
-
-  /**
-   * ### .oneOf(inList, list, [message])
-   *
-   * Asserts that non-object, non-array value `inList` appears in the flat array `list`.
-   *
-   *     assert.oneOf(1, [ 2, 1 ], 'Not found in list');
-   *
-   * @name oneOf
-   * @param {*} inList
-   * @param {Array<*>} list
-   * @param {String} message
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.oneOf = function (inList, list, msg) {
-    new Assertion(inList, msg).to.be.oneOf(list);
-  }
-
    /**
    * ### .changes(function, object, property)
    *
    * Asserts that a function changes the value of a property
    *
    *     var obj = { val: 10 };
    *     var fn = function() { obj.val = 22 };
    *     assert.changes(fn, obj, 'val');
    *
    * @name changes
    * @param {Function} modifier function
    * @param {Object} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace Assert
    * @api public
    */
 
   assert.changes = function (fn, obj, prop) {
     new Assertion(fn).to.change(obj, prop);
   }
 
    /**
@@ -3525,17 +3055,16 @@ module.exports = function (chai, util) {
    *     var fn = function() { console.log('foo'); };
    *     assert.doesNotChange(fn, obj, 'val');
    *
    * @name doesNotChange
    * @param {Function} modifier function
    * @param {Object} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace Assert
    * @api public
    */
 
   assert.doesNotChange = function (fn, obj, prop) {
     new Assertion(fn).to.not.change(obj, prop);
   }
 
    /**
@@ -3547,17 +3076,16 @@ module.exports = function (chai, util) {
    *     var fn = function() { obj.val = 13 };
    *     assert.increases(fn, obj, 'val');
    *
    * @name increases
    * @param {Function} modifier function
    * @param {Object} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace Assert
    * @api public
    */
 
   assert.increases = function (fn, obj, prop) {
     new Assertion(fn).to.increase(obj, prop);
   }
 
    /**
@@ -3569,17 +3097,16 @@ module.exports = function (chai, util) {
    *     var fn = function() { obj.val = 8 };
    *     assert.doesNotIncrease(fn, obj, 'val');
    *
    * @name doesNotIncrease
    * @param {Function} modifier function
    * @param {Object} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace Assert
    * @api public
    */
 
   assert.doesNotIncrease = function (fn, obj, prop) {
     new Assertion(fn).to.not.increase(obj, prop);
   }
 
    /**
@@ -3591,17 +3118,16 @@ module.exports = function (chai, util) {
    *     var fn = function() { obj.val = 5 };
    *     assert.decreases(fn, obj, 'val');
    *
    * @name decreases
    * @param {Function} modifier function
    * @param {Object} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace Assert
    * @api public
    */
 
   assert.decreases = function (fn, obj, prop) {
     new Assertion(fn).to.decrease(obj, prop);
   }
 
    /**
@@ -3613,194 +3139,57 @@ module.exports = function (chai, util) {
    *     var fn = function() { obj.val = 15 };
    *     assert.doesNotDecrease(fn, obj, 'val');
    *
    * @name doesNotDecrease
    * @param {Function} modifier function
    * @param {Object} object
    * @param {String} property name
    * @param {String} message _optional_
-   * @namespace Assert
    * @api public
    */
 
   assert.doesNotDecrease = function (fn, obj, prop) {
     new Assertion(fn).to.not.decrease(obj, prop);
   }
 
   /*!
    * ### .ifError(object)
    *
    * Asserts if value is not a false value, and throws if it is a true value.
-   * This is added to allow for chai to be a drop-in replacement for Node's
+   * This is added to allow for chai to be a drop-in replacement for Node's 
    * assert class.
    *
    *     var err = new Error('I am a custom error');
    *     assert.ifError(err); // Rethrows err!
    *
    * @name ifError
    * @param {Object} object
-   * @namespace Assert
    * @api public
    */
 
   assert.ifError = function (val) {
     if (val) {
       throw(val);
     }
   };
 
-  /**
-   * ### .isExtensible(object)
-   *
-   * Asserts that `object` is extensible (can have new properties added to it).
-   *
-   *     assert.isExtensible({});
-   *
-   * @name isExtensible
-   * @alias extensible
-   * @param {Object} object
-   * @param {String} message _optional_
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isExtensible = function (obj, msg) {
-    new Assertion(obj, msg).to.be.extensible;
-  };
-
-  /**
-   * ### .isNotExtensible(object)
-   *
-   * Asserts that `object` is _not_ extensible.
-   *
-   *     var nonExtensibleObject = Object.preventExtensions({});
-   *     var sealedObject = Object.seal({});
-   *     var frozenObject = Object.freese({});
-   *
-   *     assert.isNotExtensible(nonExtensibleObject);
-   *     assert.isNotExtensible(sealedObject);
-   *     assert.isNotExtensible(frozenObject);
-   *
-   * @name isNotExtensible
-   * @alias notExtensible
-   * @param {Object} object
-   * @param {String} message _optional_
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isNotExtensible = function (obj, msg) {
-    new Assertion(obj, msg).to.not.be.extensible;
-  };
-
-  /**
-   * ### .isSealed(object)
-   *
-   * Asserts that `object` is sealed (cannot have new properties added to it
-   * and its existing properties cannot be removed).
-   *
-   *     var sealedObject = Object.seal({});
-   *     var frozenObject = Object.seal({});
-   *
-   *     assert.isSealed(sealedObject);
-   *     assert.isSealed(frozenObject);
-   *
-   * @name isSealed
-   * @alias sealed
-   * @param {Object} object
-   * @param {String} message _optional_
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isSealed = function (obj, msg) {
-    new Assertion(obj, msg).to.be.sealed;
-  };
-
-  /**
-   * ### .isNotSealed(object)
-   *
-   * Asserts that `object` is _not_ sealed.
-   *
-   *     assert.isNotSealed({});
-   *
-   * @name isNotSealed
-   * @alias notSealed
-   * @param {Object} object
-   * @param {String} message _optional_
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isNotSealed = function (obj, msg) {
-    new Assertion(obj, msg).to.not.be.sealed;
-  };
-
-  /**
-   * ### .isFrozen(object)
-   *
-   * Asserts that `object` is frozen (cannot have new properties added to it
-   * and its existing properties cannot be modified).
-   *
-   *     var frozenObject = Object.freeze({});
-   *     assert.frozen(frozenObject);
-   *
-   * @name isFrozen
-   * @alias frozen
-   * @param {Object} object
-   * @param {String} message _optional_
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isFrozen = function (obj, msg) {
-    new Assertion(obj, msg).to.be.frozen;
-  };
-
-  /**
-   * ### .isNotFrozen(object)
-   *
-   * Asserts that `object` is _not_ frozen.
-   *
-   *     assert.isNotFrozen({});
-   *
-   * @name isNotFrozen
-   * @alias notFrozen
-   * @param {Object} object
-   * @param {String} message _optional_
-   * @namespace Assert
-   * @api public
-   */
-
-  assert.isNotFrozen = function (obj, msg) {
-    new Assertion(obj, msg).to.not.be.frozen;
-  };
-
   /*!
    * Aliases.
    */
 
   (function alias(name, as){
     assert[as] = assert[name];
     return alias;
   })
-  ('isOk', 'ok')
-  ('isNotOk', 'notOk')
-  ('throws', 'throw')
-  ('throws', 'Throw')
-  ('isExtensible', 'extensible')
-  ('isNotExtensible', 'notExtensible')
-  ('isSealed', 'sealed')
-  ('isNotSealed', 'notSealed')
-  ('isFrozen', 'frozen')
-  ('isNotFrozen', 'notFrozen');
+  ('Throw', 'throw')
+  ('Throw', 'throws');
 };
 
-},{}],7:[function(require,module,exports){
+},{}],6:[function(require,module,exports){
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 module.exports = function (chai, util) {
   chai.expect = function (val, message) {
@@ -3812,31 +3201,30 @@ module.exports = function (chai, util) {
    *
    * Throw a failure.
    *
    * @name fail
    * @param {Mixed} actual
    * @param {Mixed} expected
    * @param {String} message
    * @param {String} operator
-   * @namespace Expect
    * @api public
    */
 
   chai.expect.fail = function (actual, expected, message, operator) {
     message = message || 'expect.fail()';
     throw new chai.AssertionError(message, {
         actual: actual
       , expected: expected
       , operator: operator
     }, chai.expect.fail);
   };
 };
 
-},{}],8:[function(require,module,exports){
+},{}],7:[function(require,module,exports){
 /*!
  * chai
  * Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 module.exports = function (chai, util) {
   var Assertion = chai.Assertion;
@@ -3877,169 +3265,66 @@ module.exports = function (chai, util) {
      *
      * Throw a failure.
      *
      * @name fail
      * @param {Mixed} actual
      * @param {Mixed} expected
      * @param {String} message
      * @param {String} operator
-     * @namespace Should
      * @api public
      */
 
     should.fail = function (actual, expected, message, operator) {
       message = message || 'should.fail()';
       throw new chai.AssertionError(message, {
           actual: actual
         , expected: expected
         , operator: operator
       }, should.fail);
     };
 
-    /**
-     * ### .equal(actual, expected, [message])
-     *
-     * Asserts non-strict equality (`==`) of `actual` and `expected`.
-     *
-     *     should.equal(3, '3', '== coerces values to strings');
-     *
-     * @name equal
-     * @param {Mixed} actual
-     * @param {Mixed} expected
-     * @param {String} message
-     * @namespace Should
-     * @api public
-     */
-
     should.equal = function (val1, val2, msg) {
       new Assertion(val1, msg).to.equal(val2);
     };
 
-    /**
-     * ### .throw(function, [constructor/string/regexp], [string/regexp], [message])
-     *
-     * Asserts that `function` will throw an error that is an instance of
-     * `constructor`, or alternately that it will throw an error with message
-     * matching `regexp`.
-     *
-     *     should.throw(fn, 'function throws a reference error');
-     *     should.throw(fn, /function throws a reference error/);
-     *     should.throw(fn, ReferenceError);
-     *     should.throw(fn, ReferenceError, 'function throws a reference error');
-     *     should.throw(fn, ReferenceError, /function throws a reference error/);
-     *
-     * @name throw
-     * @alias Throw
-     * @param {Function} function
-     * @param {ErrorConstructor} constructor
-     * @param {RegExp} regexp
-     * @param {String} message
-     * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
-     * @namespace Should
-     * @api public
-     */
-
     should.Throw = function (fn, errt, errs, msg) {
       new Assertion(fn, msg).to.Throw(errt, errs);
     };
 
-    /**
-     * ### .exist
-     *
-     * Asserts that the target is neither `null` nor `undefined`.
-     *
-     *     var foo = 'hi';
-     *
-     *     should.exist(foo, 'foo exists');
-     *
-     * @name exist
-     * @namespace Should
-     * @api public
-     */
-
     should.exist = function (val, msg) {
       new Assertion(val, msg).to.exist;
     }
 
     // negation
     should.not = {}
 
-    /**
-     * ### .not.equal(actual, expected, [message])
-     *
-     * Asserts non-strict inequality (`!=`) of `actual` and `expected`.
-     *
-     *     should.not.equal(3, 4, 'these numbers are not equal');
-     *
-     * @name not.equal
-     * @param {Mixed} actual
-     * @param {Mixed} expected
-     * @param {String} message
-     * @namespace Should
-     * @api public
-     */
-
     should.not.equal = function (val1, val2, msg) {
       new Assertion(val1, msg).to.not.equal(val2);
     };
 
-    /**
-     * ### .throw(function, [constructor/regexp], [message])
-     *
-     * Asserts that `function` will _not_ throw an error that is an instance of
-     * `constructor`, or alternately that it will not throw an error with message
-     * matching `regexp`.
-     *
-     *     should.not.throw(fn, Error, 'function does not throw');
-     *
-     * @name not.throw
-     * @alias not.Throw
-     * @param {Function} function
-     * @param {ErrorConstructor} constructor
-     * @param {RegExp} regexp
-     * @param {String} message
-     * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
-     * @namespace Should
-     * @api public
-     */
-
     should.not.Throw = function (fn, errt, errs, msg) {
       new Assertion(fn, msg).to.not.Throw(errt, errs);
     };
 
-    /**
-     * ### .not.exist
-     *
-     * Asserts that the target is neither `null` nor `undefined`.
-     *
-     *     var bar = null;
-     *
-     *     should.not.exist(bar, 'bar does not exist');
-     *
-     * @name not.exist
-     * @namespace Should
-     * @api public
-     */
-
     should.not.exist = function (val, msg) {
       new Assertion(val, msg).to.not.exist;
     }
 
     should['throw'] = should['Throw'];
     should.not['throw'] = should.not['Throw'];
 
     return should;
   };
 
   chai.should = loadShould;
   chai.Should = loadShould;
 };
 
-},{}],9:[function(require,module,exports){
+},{}],8:[function(require,module,exports){
 /*!
  * Chai - addChainingMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependencies
@@ -4084,17 +3369,16 @@ var call  = Function.prototype.call,
  *
  *     expect(fooStr).to.be.foo('bar');
  *     expect(fooStr).to.be.foo.equal('foo');
  *
  * @param {Object} ctx object to which the method is added
  * @param {String} name of method to add
  * @param {Function} method function to be used for `name`, when called
  * @param {Function} chainingBehavior function to be called every time the property is accessed
- * @namespace Utils
  * @name addChainableMethod
  * @api public
  */
 
 module.exports = function (ctx, name, method, chainingBehavior) {
   if (typeof chainingBehavior !== 'function') {
     chainingBehavior = function () { };
   }
@@ -4143,17 +3427,17 @@ module.exports = function (ctx, name, me
 
         transferFlags(this, assert);
         return assert;
       }
     , configurable: true
   });
 };
 
-},{"../config":4,"./flag":13,"./transferFlags":29}],10:[function(require,module,exports){
+},{"../config":3,"./flag":11,"./transferFlags":27}],9:[function(require,module,exports){
 /*!
  * Chai - addMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 var config = require('../config');
 
@@ -4173,42 +3457,38 @@ var config = require('../config');
  *
  * Then can be used as any other assertion.
  *
  *     expect(fooStr).to.be.foo('bar');
  *
  * @param {Object} ctx object to which the method is added
  * @param {String} name of method to add
  * @param {Function} method function to be used for name
- * @namespace Utils
  * @name addMethod
  * @api public
  */
 var flag = require('./flag');
 
 module.exports = function (ctx, name, method) {
   ctx[name] = function () {
     var old_ssfi = flag(this, 'ssfi');
     if (old_ssfi && config.includeStack === false)
       flag(this, 'ssfi', ctx[name]);
     var result = method.apply(this, arguments);
     return result === undefined ? this : result;
   };
 };
 
-},{"../config":4,"./flag":13}],11:[function(require,module,exports){
+},{"../config":3,"./flag":11}],10:[function(require,module,exports){
 /*!
  * Chai - addProperty utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
-var config = require('../config');
-var flag = require('./flag');
-
 /**
  * ### addProperty (ctx, name, getter)
  *
  * Adds a property to the prototype of an object.
  *
  *     utils.addProperty(chai.Assertion.prototype, 'foo', function () {
  *       var obj = utils.flag(this, 'object');
  *       new chai.Assertion(obj).to.be.instanceof(Foo);
@@ -4220,80 +3500,31 @@ var flag = require('./flag');
  *
  * Then can be used as any other assertion.
  *
  *     expect(myFoo).to.be.foo;
  *
  * @param {Object} ctx object to which the property is added
  * @param {String} name of property to add
  * @param {Function} getter function to be used for name