Backed out changeset d385d640f12e (bug 1239828) from beta due to request from release management/Sylvestre
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Sun, 21 Feb 2016 12:04:44 +0100
changeset 311345 8bf2c5452d44589f181a8c96edc63c1990cfde32
parent 311344 4524d2f0a09019b355d20fbb90706bd93c075cde
child 311346 73fd4c217368b38a64aa2e59de8e3fc5bef8eedb
child 311348 afc380362d6e4f789c123c2ef9d06233f07d0589
push id5653
push usercbook@mozilla.com
push dateSun, 21 Feb 2016 11:06:23 +0000
treeherdermozilla-beta@8bf2c5452d44 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1239828
milestone45.0
backs outd385d640f12e91a5c9ec54c076449d0d8ad6e2bf
Backed out changeset d385d640f12e (bug 1239828) from beta due to request from release management/Sylvestre
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/it/loop.properties
browser/extensions/loop/chrome/locale/ja/loop.properties
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/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_mozLoop_sharingListeners.js
browser/extensions/loop/chrome/test/mochitest/browser_mozLoop_telemetry.js
browser/extensions/loop/install.rdf.in
browser/extensions/loop/jar.mn
browser/extensions/loop/test/functional/config.py
--- 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",
@@ -185,23 +183,24 @@ var WindowListener = {
               this.MozLoopService.resumeTour("waiting");
               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.
        *
@@ -482,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);
       },
@@ -506,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.
        *
@@ -534,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"
           }]
         );
@@ -591,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.
        */
@@ -631,67 +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) {
-        // 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 = {
   /**
@@ -171,19 +170,16 @@ const kMessageHandlers = {
       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.
    *
@@ -360,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
@@ -870,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);
   },
@@ -921,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,16 +67,26 @@ 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 window = chatbox.content.contentWindow;
@@ -78,23 +101,26 @@ Cu.import("resource://gre/modules/Promis
 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");
@@ -739,30 +765,24 @@ 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;
   },
@@ -841,44 +861,29 @@ var MozLoopServiceInternal = {
   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 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"));
     }
   },
 
   /**
-   * Pause or resume all chat windows that are open.
-   */
-  toggleBrowserSharing(on = true) {
-    for (let chatbox of this.getChatWindows()) {
-      let window = chatbox.content.contentWindow;
-      window.dispatchEvent(new window.CustomEvent("ToggleBrowserSharing", {
-        detail: on
-      }));
-    }
-  },
-
-  /**
    * 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.
    */
   isChatWindowOpen: function(chatWindowId) {
     if (this.mocks.isChatWindowOpen !== undefined) {
       return this.mocks.isChatWindowOpen;
@@ -925,23 +930,17 @@ var MozLoopServiceInternal = {
           return;
         }
         chatbox.removeEventListener("DOMContentLoaded", loaded, true);
 
         let chatbar = chatbox.parentNode;
         let window = chatbox.contentWindow;
 
         function socialFrameChanged(eventName) {
-          // `clearAvailableTargetsCache` is new in Firefox 46. The else branch
-          // supports Firefox 45.
-          if ("clearAvailableTargetsCache" in UITour) {
-            UITour.clearAvailableTargetsCache();
-          } else {
-            UITour.availableTargetsCache.clear();
-          }
+          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") {
@@ -1281,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
@@ -1423,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")
         )
       );
--- 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);
@@ -87,19 +86,16 @@ describe("loop.panel", function() {
       GetDoNotDisturb: false,
       "GetLoopPref|gettingStarted.latestFTUVersion": 1,
       "GetLoopPref|legal.ToS_url": "",
       "GetLoopPref|legal.privacy_url": "",
       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"
         }]
@@ -129,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"
     });
@@ -1037,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);
@@ -1061,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,15 +1,17 @@
 pref("loop.enabled", true);
+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");
@@ -23,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
     }),
 
@@ -222,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");
 
@@ -1241,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,106 +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"
-    ],
-
-    /**
-     * 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
-        }
-      });
-    }
-  });
-
-  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.
@@ -1678,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,120 +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);
-    });
-  });
-});
--- 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
- * @namespace Utils
  * @name addProperty
  * @api public
  */
 
 module.exports = function (ctx, name, getter) {
   Object.defineProperty(ctx, name,
-    { get: function addProperty() {
-        var old_ssfi = flag(this, 'ssfi');
-        if (old_ssfi && config.includeStack === false)
-          flag(this, 'ssfi', addProperty);
-
+    { get: function () {
         var result = getter.call(this);
         return result === undefined ? this : result;
       }
     , configurable: true
   });
 };
 
-},{"../config":4,"./flag":13}],12:[function(require,module,exports){
-/*!
- * Chai - expectTypes utility
- * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
- * MIT Licensed
- */
-
-/**
- * ### expectTypes(obj, types)
- *
- * Ensures that the object being tested against is of a valid type.
- *
- *     utils.expectTypes(this, ['array', 'object', 'string']);
- *
- * @param {Mixed} obj constructed Assertion
- * @param {Array} type A list of allowed types for this assertion
- * @namespace Utils
- * @name expectTypes
- * @api public
- */
-
-var AssertionError = require('assertion-error');
-var flag = require('./flag');
-var type = require('type-detect');
-
-module.exports = function (obj, types) {
-  var obj = flag(obj, 'object');
-  types = types.map(function (t) { return t.toLowerCase(); });
-  types.sort();
-
-  // Transforms ['lorem', 'ipsum'] into 'a lirum, or an ipsum'
-  var str = types.map(function (t, index) {
-    var art = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(t.charAt(0)) ? 'an' : 'a';
-    var or = types.length > 1 && index === types.length - 1 ? 'or ' : '';
-    return or + art + ' ' + t;
-  }).join(', ');
-
-  if (!types.some(function (expected) { return type(obj) === expected; })) {
-    throw new AssertionError(
-      'object tested must be ' + str + ', but ' + type(obj) + ' given'
-    );
-  }
-};
-
-},{"./flag":13,"assertion-error":30,"type-detect":35}],13:[function(require,module,exports){
+},{}],11:[function(require,module,exports){
 /*!
  * Chai - flag utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### flag(object, key, [value])
@@ -4304,81 +3535,77 @@ module.exports = function (obj, types) {
  * the value is not set.
  *
  *     utils.flag(this, 'foo', 'bar'); // setter
  *     utils.flag(this, 'foo'); // getter, returns `bar`
  *
  * @param {Object} object constructed Assertion
  * @param {String} key
  * @param {Mixed} value (optional)
- * @namespace Utils
  * @name flag
  * @api private
  */
 
 module.exports = function (obj, key, value) {
   var flags = obj.__flags || (obj.__flags = Object.create(null));
   if (arguments.length === 3) {
     flags[key] = value;
   } else {
     return flags[key];
   }
 };
 
-},{}],14:[function(require,module,exports){
+},{}],12:[function(require,module,exports){
 /*!
  * Chai - getActual utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * # getActual(object, [actual])
  *
  * Returns the `actual` value for an Assertion
  *
  * @param {Object} object (constructed Assertion)
  * @param {Arguments} chai.Assertion.prototype.assert arguments
- * @namespace Utils
- * @name getActual
  */
 
 module.exports = function (obj, args) {
   return args.length > 4 ? args[4] : obj._obj;
 };
 
-},{}],15:[function(require,module,exports){
+},{}],13:[function(require,module,exports){
 /*!
  * Chai - getEnumerableProperties utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### .getEnumerableProperties(object)
  *
  * This allows the retrieval of enumerable property names of an object,
  * inherited or not.
  *
  * @param {Object} object
  * @returns {Array}
- * @namespace Utils
  * @name getEnumerableProperties
  * @api public
  */
 
 module.exports = function getEnumerableProperties(object) {
   var result = [];
   for (var name in object) {
     result.push(name);
   }
   return result;
 };
 
-},{}],16:[function(require,module,exports){
+},{}],14:[function(require,module,exports){
 /*!
  * Chai - message composition utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependancies
@@ -4398,64 +3625,61 @@ var flag = require('./flag')
  *
  * Message template tags:
  * - `#{this}` current asserted object
  * - `#{act}` actual value
  * - `#{exp}` expected value
  *
  * @param {Object} object (constructed Assertion)
  * @param {Arguments} chai.Assertion.prototype.assert arguments
- * @namespace Utils
  * @name getMessage
  * @api public
  */
 
 module.exports = function (obj, args) {
   var negate = flag(obj, 'negate')
     , val = flag(obj, 'object')
     , expected = args[3]
     , actual = getActual(obj, args)
     , msg = negate ? args[2] : args[1]
     , flagMsg = flag(obj, 'message');
 
   if(typeof msg === "function") msg = msg();
   msg = msg || '';
   msg = msg
-    .replace(/#\{this\}/g, function () { return objDisplay(val); })
-    .replace(/#\{act\}/g, function () { return objDisplay(actual); })
-    .replace(/#\{exp\}/g, function () { return objDisplay(expected); });
+    .replace(/#{this}/g, objDisplay(val))
+    .replace(/#{act}/g, objDisplay(actual))
+    .replace(/#{exp}/g, objDisplay(expected));
 
   return flagMsg ? flagMsg + ': ' + msg : msg;
 };
 
-},{"./flag":13,"./getActual":14,"./inspect":23,"./objDisplay":24}],17:[function(require,module,exports){
+},{"./flag":11,"./getActual":12,"./inspect":21,"./objDisplay":22}],15:[function(require,module,exports){
 /*!
  * Chai - getName utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * # getName(func)
  *
  * Gets the name of a function, in a cross-browser way.
  *
  * @param {Function} a function (usually a constructor)
- * @namespace Utils
- * @name getName
  */
 
 module.exports = function (func) {
   if (func.name) return func.name;
 
   var match = /^\s?function ([^(]*)\(/.exec(func);
   return match && match[1] ? match[1] : "";
 };
 
-},{}],18:[function(require,module,exports){
+},{}],16:[function(require,module,exports){
 /*!
  * Chai - getPathInfo utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 var hasProperty = require('./hasProperty');
 
@@ -4471,17 +3695,16 @@ var hasProperty = require('./hasProperty
  * * parent - The parent object of the property referenced by `path`
  * * name - The name of the final property, a number if it was an array indexer
  * * value - The value of the property, if it exists, otherwise `undefined`
  * * exists - Whether the property exists or not
  *
  * @param {String} path
  * @param {Object} object
  * @returns {Object} info
- * @namespace Utils
  * @name getPathInfo
  * @api public
  */
 
 module.exports = function getPathInfo(path, obj) {
   var parsed = parsePath(path),
       last = parsed[parsed.length - 1];
 
@@ -4558,17 +3781,17 @@ function _getPathValue (parsed, obj, ind
       if (i == (l - 1)) res = tmp;
     } else {
       res = undefined;
     }
   }
   return res;
 }
 
-},{"./hasProperty":21}],19:[function(require,module,exports){
+},{"./hasProperty":19}],17:[function(require,module,exports){
 /*!
  * Chai - getPathValue utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * @see https://github.com/logicalparadox/filtr
  * MIT Licensed
  */
 
 var getPathInfo = require('./getPathInfo');
@@ -4594,64 +3817,62 @@ var getPathInfo = require('./getPathInfo
  *
  *     getPathValue('prop1.str', obj); // Hello
  *     getPathValue('prop1.att[2]', obj); // b
  *     getPathValue('prop2.arr[0].nested', obj); // Universe
  *
  * @param {String} path
  * @param {Object} object
  * @returns {Object} value or `undefined`
- * @namespace Utils
  * @name getPathValue
  * @api public
  */
 module.exports = function(path, obj) {
   var info = getPathInfo(path, obj);
   return info.value;
-};
-
-},{"./getPathInfo":18}],20:[function(require,module,exports){
+}; 
+
+},{"./getPathInfo":16}],18:[function(require,module,exports){
 /*!
  * Chai - getProperties utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### .getProperties(object)
  *
  * This allows the retrieval of property names of an object, enumerable or not,
  * inherited or not.
  *
  * @param {Object} object
  * @returns {Array}
- * @namespace Utils
  * @name getProperties
  * @api public
  */
 
 module.exports = function getProperties(object) {
-  var result = Object.getOwnPropertyNames(object);
+  var result = Object.getOwnPropertyNames(subject);
 
   function addProperty(property) {
     if (result.indexOf(property) === -1) {
       result.push(property);
     }
   }
 
-  var proto = Object.getPrototypeOf(object);
+  var proto = Object.getPrototypeOf(subject);
   while (proto !== null) {
     Object.getOwnPropertyNames(proto).forEach(addProperty);
     proto = Object.getPrototypeOf(proto);
   }
 
   return result;
 };
 
-},{}],21:[function(require,module,exports){
+},{}],19:[function(require,module,exports){
 /*!
  * Chai - hasProperty utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 var type = require('type-detect');
 
@@ -4670,29 +3891,28 @@ var type = require('type-detect');
  *       , str: 'Hello'
  *     }
  *
  * The following would be the results.
  *
  *     hasProperty('str', obj);  // true
  *     hasProperty('constructor', obj);  // true
  *     hasProperty('bar', obj);  // false
- *
+ *     
  *     hasProperty('length', obj.str); // true
  *     hasProperty(1, obj.str);  // true
  *     hasProperty(5, obj.str);  // false
  *
  *     hasProperty('length', obj.arr);  // true
  *     hasProperty(2, obj.arr);  // true
  *     hasProperty(3, obj.arr);  // false
  *
  * @param {Objuect} object
  * @param {String|Number} name
  * @returns {Boolean} whether it exists
- * @namespace Utils
  * @name getPathInfo
  * @api public
  */
 
 var literals = {
     'number': Number
   , 'string': String
 };
@@ -4707,17 +3927,17 @@ module.exports = function hasProperty(na
   // The `in` operator does not work with certain literals
   // box these before the check
   if(literals[ot] && typeof obj !== 'object')
     obj = new literals[ot](obj);
 
   return name in obj;
 };
 
-},{"type-detect":35}],22:[function(require,module,exports){
+},{"type-detect":33}],20:[function(require,module,exports){
 /*!
  * chai
  * Copyright(c) 2011 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Main exports
@@ -4733,21 +3953,16 @@ exports.test = require('./test');
 
 /*!
  * type utility
  */
 
 exports.type = require('type-detect');
 
 /*!
- * expectTypes utility
- */
-exports.expectTypes = require('./expectTypes');
-
-/*!
  * message utility
  */
 
 exports.getMessage = require('./getMessage');
 
 /*!
  * actual utility
  */
@@ -4839,17 +4054,18 @@ exports.overwriteMethod = require('./ove
 exports.addChainableMethod = require('./addChainableMethod');
 
 /*!
  * Overwrite chainable method
  */
 
 exports.overwriteChainableMethod = require('./overwriteChainableMethod');
 
-},{"./addChainableMethod":9,"./addMethod":10,"./addProperty":11,"./expectTypes":12,"./flag":13,"./getActual":14,"./getMessage":16,"./getName":17,"./getPathInfo":18,"./getPathValue":19,"./hasProperty":21,"./inspect":23,"./objDisplay":24,"./overwriteChainableMethod":25,"./overwriteMethod":26,"./overwriteProperty":27,"./test":28,"./transferFlags":29,"deep-eql":31,"type-detect":35}],23:[function(require,module,exports){
+
+},{"./addChainableMethod":8,"./addMethod":9,"./addProperty":10,"./flag":11,"./getActual":12,"./getMessage":14,"./getName":15,"./getPathInfo":16,"./getPathValue":17,"./hasProperty":19,"./inspect":21,"./objDisplay":22,"./overwriteChainableMethod":23,"./overwriteMethod":24,"./overwriteProperty":25,"./test":26,"./transferFlags":27,"deep-eql":29,"type-detect":33}],21:[function(require,module,exports){
 // This is (almost) directly from Node.js utils
 // https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js
 
 var getName = require('./getName');
 var getProperties = require('./getProperties');
 var getEnumerableProperties = require('./getEnumerableProperties');
 
 module.exports = inspect;
@@ -4859,18 +4075,16 @@ module.exports = inspect;
  * in the best way possible given the different types.
  *
  * @param {Object} obj The object to print out.
  * @param {Boolean} showHidden Flag that shows hidden (not enumerable)
  *    properties of objects.
  * @param {Number} depth Depth in which to descend in object. Default is 2.
  * @param {Boolean} colors Flag to turn on ANSI escape codes to color the
  *    output. Default is false (no coloring).
- * @namespace Utils
- * @name inspect
  */
 function inspect(obj, showHidden, depth, colors) {
   var ctx = {
     showHidden: showHidden,
     seen: [],
     stylize: function (str) { return str; }
   };
   return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth));
@@ -5176,17 +4390,17 @@ function isDate(d) {
 function isError(e) {
   return typeof e === 'object' && objectToString(e) === '[object Error]';
 }
 
 function objectToString(o) {
   return Object.prototype.toString.call(o);
 }
 
-},{"./getEnumerableProperties":15,"./getName":17,"./getProperties":20}],24:[function(require,module,exports){
+},{"./getEnumerableProperties":13,"./getName":15,"./getProperties":18}],22:[function(require,module,exports){
 /*!
  * Chai - flag utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependancies
@@ -5199,17 +4413,16 @@ var config = require('../config');
  * ### .objDisplay (object)
  *
  * Determines if an object or an array matches
  * criteria to be inspected in-line for error
  * messages or should be truncated.
  *
  * @param {Mixed} javascript object to inspect
  * @name objDisplay
- * @namespace Utils
  * @api public
  */
 
 module.exports = function (obj) {
   var str = inspect(obj)
     , type = Object.prototype.toString.call(obj);
 
   if (config.truncateThreshold && str.length >= config.truncateThreshold) {
@@ -5228,17 +4441,17 @@ module.exports = function (obj) {
     } else {
       return str;
     }
   } else {
     return str;
   }
 };
 
-},{"../config":4,"./inspect":23}],25:[function(require,module,exports){
+},{"../config":3,"./inspect":21}],23:[function(require,module,exports){
 /*!
  * Chai - overwriteChainableMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### overwriteChainableMethod (ctx, name, method, chainingBehavior)
@@ -5263,17 +4476,16 @@ module.exports = function (obj) {
  *
  *     expect(myFoo).to.have.length(3);
  *     expect(myFoo).to.have.length.above(3);
  *
  * @param {Object} ctx object whose method / property is to be overwritten
  * @param {String} name of method / property to overwrite
  * @param {Function} method function that returns a function to be used for name
  * @param {Function} chainingBehavior function that returns a function to be used for property
- * @namespace Utils
  * @name overwriteChainableMethod
  * @api public
  */
 
 module.exports = function (ctx, name, method, chainingBehavior) {
   var chainableBehavior = ctx.__methods[name];
 
   var _chainingBehavior = chainableBehavior.chainingBehavior;
@@ -5284,17 +4496,17 @@ module.exports = function (ctx, name, me
 
   var _method = chainableBehavior.method;
   chainableBehavior.method = function () {
     var result = method(_method).apply(this, arguments);
     return result === undefined ? this : result;
   };
 };
 
-},{}],26:[function(require,module,exports){
+},{}],24:[function(require,module,exports){
 /*!
  * Chai - overwriteMethod utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### overwriteMethod (ctx, name, fn)
@@ -5320,17 +4532,16 @@ module.exports = function (ctx, name, me
  *
  * Then can be used as any other assertion.
  *
  *     expect(myFoo).to.equal('bar');
  *
  * @param {Object} ctx object whose method is to be overwritten
  * @param {String} name of method to overwrite
  * @param {Function} method function that returns a function to be used for name
- * @namespace Utils
  * @name overwriteMethod
  * @api public
  */
 
 module.exports = function (ctx, name, method) {
   var _method = ctx[name]
     , _super = function () { return this; };
 
@@ -5338,17 +4549,17 @@ module.exports = function (ctx, name, me
     _super = _method;
 
   ctx[name] = function () {
     var result = method(_super).apply(this, arguments);
     return result === undefined ? this : result;
   }
 };
 
-},{}],27:[function(require,module,exports){
+},{}],25:[function(require,module,exports){
 /*!
  * Chai - overwriteProperty utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### overwriteProperty (ctx, name, fn)
@@ -5374,17 +4585,16 @@ module.exports = function (ctx, name, me
  *
  * Then can be used as any other assertion.
  *
  *     expect(myFoo).to.be.ok;
  *
  * @param {Object} ctx object whose property is to be overwritten
  * @param {String} name of property to overwrite
  * @param {Function} getter function that returns a getter function to be used for name
- * @namespace Utils
  * @name overwriteProperty
  * @api public
  */
 
 module.exports = function (ctx, name, getter) {
   var _get = Object.getOwnPropertyDescriptor(ctx, name)
     , _super = function () {};
 
@@ -5395,17 +4605,17 @@ module.exports = function (ctx, name, ge
     { get: function () {
         var result = getter(_super).call(this);
         return result === undefined ? this : result;
       }
     , configurable: true
   });
 };
 
-},{}],28:[function(require,module,exports){
+},{}],26:[function(require,module,exports){
 /*!
  * Chai - test utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependancies
@@ -5415,27 +4625,25 @@ var flag = require('./flag');
 
 /**
  * # test(object, expression)
  *
  * Test and object for expression.
  *
  * @param {Object} object (constructed Assertion)
  * @param {Arguments} chai.Assertion.prototype.assert arguments
- * @namespace Utils
- * @name test
  */
 
 module.exports = function (obj, args) {
   var negate = flag(obj, 'negate')
     , expr = args[0];
   return negate ? !expr : expr;
 };
 
-},{"./flag":13}],29:[function(require,module,exports){
+},{"./flag":11}],27:[function(require,module,exports){
 /*!
  * Chai - transferFlags utility
  * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /**
  * ### transferFlags(assertion, object, includeAll = true)
@@ -5450,17 +4658,16 @@ module.exports = function (obj, args) {
  *     utils.transferFlags(assertion, newAssertion);
  *
  *     var anotherAsseriton = new Assertion(myObj);
  *     utils.transferFlags(assertion, anotherAssertion, false);
  *
  * @param {Assertion} assertion the assertion to transfer the flags from
  * @param {Object} object the object to transfer the flags to; usually a new assertion
  * @param {Boolean} includeAll
- * @namespace Utils
  * @name transferFlags
  * @api private
  */
 
 module.exports = function (assertion, object, includeAll) {
   var flags = assertion.__flags || (assertion.__flags = Object.create(null));
 
   if (!object.__flags) {
@@ -5472,17 +4679,17 @@ module.exports = function (assertion, ob
   for (var flag in flags) {
     if (includeAll ||
         (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) {
       object.__flags[flag] = flags[flag];
     }
   }
 };
 
-},{}],30:[function(require,module,exports){
+},{}],28:[function(require,module,exports){
 /*!
  * assertion-error
  * Copyright(c) 2013 Jake Luer <jake@qualiancy.com>
  * MIT Licensed
  */
 
 /*!
  * Return a function that will copy properties from
@@ -5586,20 +4793,20 @@ AssertionError.prototype.toJSON = functi
   // include stack if exists and not turned off
   if (false !== stack && this.stack) {
     props.stack = this.stack;
   }
 
   return props;
 };
 
-},{}],31:[function(require,module,exports){
+},{}],29:[function(require,module,exports){
 module.exports = require('./lib/eql');
 
-},{"./lib/eql":32}],32:[function(require,module,exports){
+},{"./lib/eql":30}],30:[function(require,module,exports){
 /*!
  * deep-eql
  * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Module dependencies
@@ -5848,20 +5055,20 @@ function objectEqual(a, b, m) {
     if (!deepEqual(a[key], b[key], m)) {
       return false;
     }
   }
 
   return true;
 }
 
-},{"buffer":undefined,"type-detect":33}],33:[function(require,module,exports){
+},{"buffer":undefined,"type-detect":31}],31:[function(require,module,exports){
 module.exports = require('./lib/type');
 
-},{"./lib/type":34}],34:[function(require,module,exports){
+},{"./lib/type":32}],32:[function(require,module,exports){
 /*!
  * type-detect
  * Copyright(c) 2013 jake luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Primary Exports
@@ -5995,19 +5202,19 @@ Library.prototype.test = function (obj, 
     return test.test(obj);
   } else if (test && 'function' === getType(test)) {
     return test(obj);
   } else {
     throw new ReferenceError('Type test "' + type + '" not defined or invalid.');
   }
 };
 
-},{}],35:[function(require,module,exports){
-arguments[4][33][0].apply(exports,arguments)
-},{"./lib/type":36,"dup":33}],36:[function(require,module,exports){
+},{}],33:[function(require,module,exports){
+arguments[4][31][0].apply(exports,arguments)
+},{"./lib/type":34,"dup":31}],34:[function(require,module,exports){
 /*!
  * type-detect
  * Copyright(c) 2013 jake luer <jake@alogicalparadox.com>
  * MIT Licensed
  */
 
 /*!
  * Primary Exports
@@ -6133,10 +5340,13 @@ Library.prototype.test = function(obj, t
     return test.test(obj);
   } else if (test && 'function' === getType(test)) {
     return test(obj);
   } else {
     throw new ReferenceError('Type test "' + type + '" not defined or invalid.');
   }
 };
 
-},{}]},{},[1])(1)
+},{}],35:[function(require,module,exports){
+module.exports = require('./lib/chai');
+
+},{"./lib/chai":1}]},{},[35])(35)
 });
\ No newline at end of file
--- a/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
+++ b/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
@@ -131,51 +131,16 @@ body {
 }
 
 #mocha .test pre.error {
   color: #c00;
   max-height: 300px;
   overflow: auto;
 }
 
-#mocha .test .html-error {
-  overflow: auto;
-  color: black;
-  line-height: 1.5;
-  display: block;
-  float: left;
-  clear: left;
-  font: 12px/1.5 monaco, monospace;
-  margin: 5px;
-  padding: 15px;
-  border: 1px solid #eee;
-  max-width: 85%; /*(1)*/
-  max-width: calc(100% - 42px); /*(2)*/
-  max-height: 300px;
-  word-wrap: break-word;
-  border-bottom-color: #ddd;
-  -webkit-border-radius: 3px;
-  -webkit-box-shadow: 0 1px 3px #eee;
-  -moz-border-radius: 3px;
-  -moz-box-shadow: 0 1px 3px #eee;
-  border-radius: 3px;
-}
-
-#mocha .test .html-error pre.error {
-  border: none;
-  -webkit-border-radius: none;
-  -webkit-box-shadow: none;
-  -moz-border-radius: none;
-  -moz-box-shadow: none;
-  padding: 0;
-  margin: 0;
-  margin-top: 18px;
-  max-height: none;
-}
-
 /**
  * (1): approximate for browsers not supporting calc
  * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
  *      ^^ seriously
  */
 #mocha .test pre {
   display: block;
   float: left;
--- a/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
+++ b/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
@@ -1,60 +1,483 @@
-(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){
-(function (process){
-module.exports = process.env.COV
-  ? require('./lib-cov/mocha')
-  : require('./lib/mocha');
-
-}).call(this,require('_process'))
-},{"./lib-cov/mocha":undefined,"./lib/mocha":14,"_process":51}],2:[function(require,module,exports){
-/* eslint-disable no-unused-vars */
-module.exports = function(type) {
-  return function() {};
+;(function(){
+
+// CommonJS require()
+
+function require(p){
+    var path = require.resolve(p)
+      , mod = require.modules[path];
+    if (!mod) throw new Error('failed to require "' + p + '"');
+    if (!mod.exports) {
+      mod.exports = {};
+      mod.call(mod.exports, mod, mod.exports, require.relative(path));
+    }
+    return mod.exports;
+  }
+
+require.modules = {};
+
+require.resolve = function (path){
+    var orig = path
+      , reg = path + '.js'
+      , index = path + '/index.js';
+    return require.modules[reg] && reg
+      || require.modules[index] && index
+      || orig;
+  };
+
+require.register = function (path, fn){
+    require.modules[path] = fn;
+  };
+
+require.relative = function (parent) {
+    return function(p){
+      if ('.' != p.charAt(0)) return require(p);
+
+      var path = parent.split('/')
+        , segs = p.split('/');
+      path.pop();
+
+      for (var i = 0; i < segs.length; i++) {
+        var seg = segs[i];
+        if ('..' == seg) path.pop();
+        else if ('.' != seg) path.push(seg);
+      }
+
+      return require(path.join('/'));
+    };
+  };
+
+
+require.register("browser/debug.js", function(module, exports, require){
+module.exports = function(type){
+  return function(){
+  }
 };
 
-},{}],3:[function(require,module,exports){
+}); // module: browser/debug.js
+
+require.register("browser/diff.js", function(module, exports, require){
+/* See LICENSE file for terms of use */
+
+/*
+ * Text diff implementation.
+ *
+ * This library supports the following APIS:
+ * JsDiff.diffChars: Character by character diff
+ * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
+ * JsDiff.diffLines: Line based diff
+ *
+ * JsDiff.diffCss: Diff targeted at CSS content
+ *
+ * These methods are based on the implementation proposed in
+ * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
+ * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
+ */
+var JsDiff = (function() {
+  /*jshint maxparams: 5*/
+  function clonePath(path) {
+    return { newPos: path.newPos, components: path.components.slice(0) };
+  }
+  function removeEmpty(array) {
+    var ret = [];
+    for (var i = 0; i < array.length; i++) {
+      if (array[i]) {
+        ret.push(array[i]);
+      }
+    }
+    return ret;
+  }
+  function escapeHTML(s) {
+    var n = s;
+    n = n.replace(/&/g, '&amp;');
+    n = n.replace(/</g, '&lt;');
+    n = n.replace(/>/g, '&gt;');
+    n = n.replace(/"/g, '&quot;');
+
+    return n;
+  }
+
+  var Diff = function(ignoreWhitespace) {
+    this.ignoreWhitespace = ignoreWhitespace;
+  };
+  Diff.prototype = {
+      diff: function(oldString, newString) {
+        // Handle the identity case (this is due to unrolling editLength == 0
+        if (newString === oldString) {
+          return [{ value: newString }];
+        }
+        if (!newString) {
+          return [{ value: oldString, removed: true }];
+        }
+        if (!oldString) {
+          return [{ value: newString, added: true }];
+        }
+
+        newString = this.tokenize(newString);
+        oldString = this.tokenize(oldString);
+
+        var newLen = newString.length, oldLen = oldString.length;
+        var maxEditLength = newLen + oldLen;
+        var bestPath = [{ newPos: -1, components: [] }];
+
+        // Seed editLength = 0
+        var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
+        if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
+          return bestPath[0].components;
+        }
+
+        for (var editLength = 1; editLength <= maxEditLength; editLength++) {
+          for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
+            var basePath;
+            var addPath = bestPath[diagonalPath-1],
+                removePath = bestPath[diagonalPath+1];
+            oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
+            if (addPath) {
+              // No one else is going to attempt to use this value, clear it
+              bestPath[diagonalPath-1] = undefined;
+            }
+
+            var canAdd = addPath && addPath.newPos+1 < newLen;
+            var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
+            if (!canAdd && !canRemove) {
+              bestPath[diagonalPath] = undefined;
+              continue;
+            }
+
+            // Select the diagonal that we want to branch from. We select the prior
+            // path whose position in the new string is the farthest from the origin
+            // and does not pass the bounds of the diff graph
+            if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
+              basePath = clonePath(removePath);
+              this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
+            } else {
+              basePath = clonePath(addPath);
+              basePath.newPos++;
+              this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
+            }
+
+            var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);
+
+            if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
+              return basePath.components;
+            } else {
+              bestPath[diagonalPath] = basePath;
+            }
+          }
+        }
+      },
+
+      pushComponent: function(components, value, added, removed) {
+        var last = components[components.length-1];
+        if (last && last.added === added && last.removed === removed) {
+          // We need to clone here as the component clone operation is just
+          // as shallow array clone
+          components[components.length-1] =
+            {value: this.join(last.value, value), added: added, removed: removed };
+        } else {
+          components.push({value: value, added: added, removed: removed });
+        }
+      },
+      extractCommon: function(basePath, newString, oldString, diagonalPath) {
+        var newLen = newString.length,
+            oldLen = oldString.length,
+            newPos = basePath.newPos,
+            oldPos = newPos - diagonalPath;
+        while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
+          newPos++;
+          oldPos++;
+
+          this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
+        }
+        basePath.newPos = newPos;
+        return oldPos;
+      },
+
+      equals: function(left, right) {
+        var reWhitespace = /\S/;
+        if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
+          return true;
+        } else {
+          return left === right;
+        }
+      },
+      join: function(left, right) {
+        return left + right;
+      },
+      tokenize: function(value) {
+        return value;
+      }
+  };
+
+  var CharDiff = new Diff();
+
+  var WordDiff = new Diff(true);
+  var WordWithSpaceDiff = new Diff();
+  WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) {
+    return removeEmpty(value.split(/(\s+|\b)/));
+  };
+
+  var CssDiff = new Diff(true);
+  CssDiff.tokenize = function(value) {
+    return removeEmpty(value.split(/([{}:;,]|\s+)/));
+  };
+
+  var LineDiff = new Diff();
+  LineDiff.tokenize = function(value) {
+    var retLines = [],
+        lines = value.split(/^/m);
+
+    for(var i = 0; i < lines.length; i++) {
+      var line = lines[i],
+          lastLine = lines[i - 1];
+
+      // Merge lines that may contain windows new lines
+      if (line == '\n' && lastLine && lastLine[lastLine.length - 1] === '\r') {
+        retLines[retLines.length - 1] += '\n';
+      } else if (line) {
+        retLines.push(line);
+      }
+    }
+
+    return retLines;
+  };
+
+  return {
+    Diff: Diff,
+
+    diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
+    diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
+    diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); },
+    diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },
+
+    diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },
+
+    createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
+      var ret = [];
+
+      ret.push('Index: ' + fileName);
+      ret.push('===================================================================');
+      ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader));
+      ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader));
+
+      var diff = LineDiff.diff(oldStr, newStr);
+      if (!diff[diff.length-1].value) {
+        diff.pop();   // Remove trailing newline add
+      }
+      diff.push({value: '', lines: []});   // Append an empty value to make cleanup easier
+
+      function contextLines(lines) {
+        return lines.map(function(entry) { return ' ' + entry; });
+      }
+      function eofNL(curRange, i, current) {
+        var last = diff[diff.length-2],
+            isLast = i === diff.length-2,
+            isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed);
+
+        // Figure out if this is the last line for the given file and missing NL
+        if (!/\n$/.test(current.value) && (isLast || isLastOfType)) {
+          curRange.push('\\ No newline at end of file');
+        }
+      }
+
+      var oldRangeStart = 0, newRangeStart = 0, curRange = [],
+          oldLine = 1, newLine = 1;
+      for (var i = 0; i < diff.length; i++) {
+        var current = diff[i],
+            lines = current.lines || current.value.replace(/\n$/, '').split('\n');
+        current.lines = lines;
+
+        if (current.added || current.removed) {
+          if (!oldRangeStart) {
+            var prev = diff[i-1];
+            oldRangeStart = oldLine;
+            newRangeStart = newLine;
+
+            if (prev) {
+              curRange = contextLines(prev.lines.slice(-4));
+              oldRangeStart -= curRange.length;
+              newRangeStart -= curRange.length;
+            }
+          }
+          curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; }));
+          eofNL(curRange, i, current);
+
+          if (current.added) {
+            newLine += lines.length;
+          } else {
+            oldLine += lines.length;
+          }
+        } else {
+          if (oldRangeStart) {
+            // Close out any changes that have been output (or join overlapping)
+            if (lines.length <= 8 && i < diff.length-2) {
+              // Overlapping
+              curRange.push.apply(curRange, contextLines(lines));
+            } else {
+              // end the range and output
+              var contextSize = Math.min(lines.length, 4);
+              ret.push(
+                  '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize)
+                  + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize)
+                  + ' @@');
+              ret.push.apply(ret, curRange);
+              ret.push.apply(ret, contextLines(lines.slice(0, contextSize)));
+              if (lines.length <= 4) {
+                eofNL(ret, i, current);
+              }
+
+              oldRangeStart = 0;  newRangeStart = 0; curRange = [];
+            }
+          }
+          oldLine += lines.length;
+          newLine += lines.length;
+        }
+      }
+
+      return ret.join('\n') + '\n';
+    },
+
+    applyPatch: function(oldStr, uniDiff) {
+      var diffstr = uniDiff.split('\n');
+      var diff = [];
+      var remEOFNL = false,
+          addEOFNL = false;
+
+      for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) {
+        if(diffstr[i][0] === '@') {
+          var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
+          diff.unshift({
+            start:meh[3],
+            oldlength:meh[2],
+            oldlines:[],
+            newlength:meh[4],
+            newlines:[]
+          });
+        } else if(diffstr[i][0] === '+') {
+          diff[0].newlines.push(diffstr[i].substr(1));
+        } else if(diffstr[i][0] === '-') {
+          diff[0].oldlines.push(diffstr[i].substr(1));
+        } else if(diffstr[i][0] === ' ') {
+          diff[0].newlines.push(diffstr[i].substr(1));
+          diff[0].oldlines.push(diffstr[i].substr(1));
+        } else if(diffstr[i][0] === '\\') {
+          if (diffstr[i-1][0] === '+') {
+            remEOFNL = true;
+          } else if(diffstr[i-1][0] === '-') {
+            addEOFNL = true;
+          }
+        }
+      }
+
+      var str = oldStr.split('\n');
+      for (var i = diff.length - 1; i >= 0; i--) {
+        var d = diff[i];
+        for (var j = 0; j < d.oldlength; j++) {
+          if(str[d.start-1+j] !== d.oldlines[j]) {
+            return false;
+          }
+        }
+        Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines));
+      }
+
+      if (remEOFNL) {
+        while (!str[str.length-1]) {
+          str.pop();
+        }
+      } else if (addEOFNL) {
+        str.push('');
+      }
+      return str.join('\n');
+    },
+
+    convertChangesToXML: function(changes){
+      var ret = [];
+      for ( var i = 0; i < changes.length; i++) {
+        var change = changes[i];
+        if (change.added) {
+          ret.push('<ins>');
+        } else if (change.removed) {
+          ret.push('<del>');
+        }
+
+        ret.push(escapeHTML(change.value));
+
+        if (change.added) {
+          ret.push('</ins>');
+        } else if (change.removed) {
+          ret.push('</del>');
+        }
+      }
+      return ret.join('');
+    },
+
+    // See: http://code.google.com/p/google-diff-match-patch/wiki/API
+    convertChangesToDMP: function(changes){
+      var ret = [], change;
+      for ( var i = 0; i < changes.length; i++) {
+        change = changes[i];
+        ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]);
+      }
+      return ret;
+    }
+  };
+})();
+
+if (typeof module !== 'undefined') {
+    module.exports = JsDiff;
+}
+
+}); // module: browser/diff.js
+
+require.register("browser/escape-string-regexp.js", function(module, exports, require){
+'use strict';
+
+var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
+
+module.exports = function (str) {
+  if (typeof str !== 'string') {
+    throw new TypeError('Expected a string');
+  }
+
+  return str.replace(matchOperatorsRe,  '\\$&');
+};
+
+}); // module: browser/escape-string-regexp.js
+
+require.register("browser/events.js", function(module, exports, require){
 /**
  * Module exports.
  */
 
 exports.EventEmitter = EventEmitter;
 
 /**
- * Object#hasOwnProperty reference.
- */
-var objToString = Object.prototype.toString;
-
-/**
- * Check if a value is an array.
- *
- * @api private
- * @param {*} val The value to test.
- * @return {boolean} true if the value is a boolean, otherwise false.
- */
-function isArray(val) {
-  return objToString.call(val) === '[object Array]';
+ * Check if `obj` is an array.
+ */
+
+function isArray(obj) {
+  return '[object Array]' == {}.toString.call(obj);
 }
 
 /**
  * Event emitter constructor.
  *
  * @api public
  */
-function EventEmitter() {}
-
-/**
- * Add a listener.
+
+function EventEmitter(){};
+
+/**
+ * Adds a listener.
  *
  * @api public
- * @param {string} name Event name.
- * @param {Function} fn Event handler.
- * @return {EventEmitter} Emitter instance.
- */
-EventEmitter.prototype.on = function(name, fn) {
+ */
+
+EventEmitter.prototype.on = function (name, fn) {
   if (!this.$events) {
     this.$events = {};
   }
 
   if (!this.$events[name]) {
     this.$events[name] = fn;
   } else if (isArray(this.$events[name])) {
     this.$events[name].push(fn);
@@ -66,43 +489,39 @@ EventEmitter.prototype.on = function(nam
 };
 
 EventEmitter.prototype.addListener = EventEmitter.prototype.on;
 
 /**
  * Adds a volatile listener.
  *
  * @api public
- * @param {string} name Event name.
- * @param {Function} fn Event handler.
- * @return {EventEmitter} Emitter instance.
- */
-EventEmitter.prototype.once = function(name, fn) {
+ */
+
+EventEmitter.prototype.once = function (name, fn) {
   var self = this;
 
-  function on() {
+  function on () {
     self.removeListener(name, on);
     fn.apply(this, arguments);
-  }
+  };
 
   on.listener = fn;
   this.on(name, on);
 
   return this;
 };
 
 /**
- * Remove a listener.
+ * Removes a listener.
  *
  * @api public
- * @param {string} name Event name.
- * @param {Function} fn Event handler.
- * @return {EventEmitter} Emitter instance.
- */
-EventEmitter.prototype.removeListener = function(name, fn) {
+ */
+
+EventEmitter.prototype.removeListener = function (name, fn) {
   if (this.$events && this.$events[name]) {
     var list = this.$events[name];
 
     if (isArray(list)) {
       var pos = -1;
 
       for (var i = 0, l = list.length; i < l; i++) {
         if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
@@ -124,183 +543,201 @@ EventEmitter.prototype.removeListener = 
       delete this.$events[name];
     }
   }
 
   return this;
 };
 
 /**
- * Remove all listeners for an event.
+ * Removes all listeners for an event.
  *
  * @api public
- * @param {string} name Event name.
- * @return {EventEmitter} Emitter instance.
- */
-EventEmitter.prototype.removeAllListeners = function(name) {
+ */
+
+EventEmitter.prototype.removeAllListeners = function (name) {
   if (name === undefined) {
     this.$events = {};
     return this;
   }
 
   if (this.$events && this.$events[name]) {
     this.$events[name] = null;
   }
 
   return this;
 };
 
 /**
- * Get all listeners for a given event.
+ * Gets all listeners for a certain event.
  *
  * @api public
- * @param {string} name Event name.
- * @return {EventEmitter} Emitter instance.
- */
-EventEmitter.prototype.listeners = function(name) {
+ */
+
+EventEmitter.prototype.listeners = function (name) {
   if (!this.$events) {
     this.$events = {};
   }
 
   if (!this.$events[name]) {
     this.$events[name] = [];
   }
 
   if (!isArray(this.$events[name])) {
     this.$events[name] = [this.$events[name]];
   }
 
   return this.$events[name];
 };
 
 /**
- * Emit an event.
+ * Emits an event.
  *
  * @api public
- * @param {string} name Event name.
- * @return {boolean} true if at least one handler was invoked, else false.
- */
-EventEmitter.prototype.emit = function(name) {
+ */
+
+EventEmitter.prototype.emit = function (name) {
   if (!this.$events) {
     return false;
   }
 
   var handler = this.$events[name];
 
   if (!handler) {
     return false;
   }
 
-  var args = Array.prototype.slice.call(arguments, 1);
-
-  if (typeof handler === 'function') {
+  var args = [].slice.call(arguments, 1);
+
+  if ('function' == typeof handler) {
     handler.apply(this, args);
   } else if (isArray(handler)) {
     var listeners = handler.slice();
 
     for (var i = 0, l = listeners.length; i < l; i++) {
       listeners[i].apply(this, args);
     }
   } else {
     return false;
   }
 
   return true;
 };
 
-},{}],4:[function(require,module,exports){
+}); // module: browser/events.js
+
+require.register("browser/fs.js", function(module, exports, require){
+
+}); // module: browser/fs.js
+
+require.register("browser/glob.js", function(module, exports, require){
+
+}); // module: browser/glob.js
+
+require.register("browser/path.js", function(module, exports, require){
+
+}); // module: browser/path.js
+
+require.register("browser/progress.js", function(module, exports, require){
 /**
  * Expose `Progress`.
  */
 
 module.exports = Progress;
 
 /**
  * Initialize a new `Progress` indicator.
  */
+
 function Progress() {
   this.percent = 0;
   this.size(0);
   this.fontSize(11);
   this.font('helvetica, arial, sans-serif');
 }
 
 /**
- * Set progress size to `size`.
- *
+ * Set progress size to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
  * @api public
- * @param {number} size
- * @return {Progress} Progress instance.
- */
-Progress.prototype.size = function(size) {
-  this._size = size;
+ */
+
+Progress.prototype.size = function(n){
+  this._size = n;
   return this;
 };
 
 /**
- * Set text to `text`.
- *
+ * Set text to `str`.
+ *
+ * @param {String} str
+ * @return {Progress} for chaining
  * @api public
- * @param {string} text
- * @return {Progress} Progress instance.
- */
-Progress.prototype.text = function(text) {
-  this._text = text;
+ */
+
+Progress.prototype.text = function(str){
+  this._text = str;
   return this;
 };
 
 /**
- * Set font size to `size`.
- *
+ * Set font size to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
  * @api public
- * @param {number} size
- * @return {Progress} Progress instance.
- */
-Progress.prototype.fontSize = function(size) {
-  this._fontSize = size;
+ */
+
+Progress.prototype.fontSize = function(n){
+  this._fontSize = n;
   return this;
 };
 
 /**
- * Set font to `family`.
- *
- * @param {string} family
- * @return {Progress} Progress instance.
- */
-Progress.prototype.font = function(family) {
+ * Set font `family`.
+ *
+ * @param {String} family
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.font = function(family){
   this._font = family;
   return this;
 };
 
 /**
  * Update percentage to `n`.
  *
- * @param {number} n
- * @return {Progress} Progress instance.
- */
-Progress.prototype.update = function(n) {
+ * @param {Number} n
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.update = function(n){
   this.percent = n;
   return this;
 };
 
 /**
  * Draw on `ctx`.
  *
  * @param {CanvasRenderingContext2d} ctx
- * @return {Progress} Progress instance.
- */
-Progress.prototype.draw = function(ctx) {
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.draw = function(ctx){
   try {
-    var percent = Math.min(this.percent, 100);
-    var size = this._size;
-    var half = size / 2;
-    var x = half;
-    var y = half;
-    var rad = half - 1;
-    var fontSize = this._fontSize;
+    var percent = Math.min(this.percent, 100)
+      , size = this._size
+      , half = size / 2
+      , x = half
+      , y = half
+      , rad = half - 1
+      , fontSize = this._fontSize;
 
     ctx.font = fontSize + 'px ' + this._font;
 
     var angle = Math.PI * 2 * (percent / 100);
     ctx.clearRect(0, 0, size, size);
 
     // outer circle
     ctx.strokeStyle = '#9f9f9f';
@@ -310,435 +747,415 @@ Progress.prototype.draw = function(ctx) 
 
     // inner circle
     ctx.strokeStyle = '#eee';
     ctx.beginPath();
     ctx.arc(x, y, rad - 1, 0, angle, true);
     ctx.stroke();
 
     // text
-    var text = this._text || (percent | 0) + '%';
-    var w = ctx.measureText(text).width;
-
-    ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1);
-  } catch (err) {
-    // don't fail if we can't render progress
-  }
+    var text = this._text || (percent | 0) + '%'
+      , w = ctx.measureText(text).width;
+
+    ctx.fillText(
+        text
+      , x - w / 2 + 1
+      , y + fontSize / 2 - 1);
+  } catch (ex) {} //don't fail if we can't render progress
   return this;
 };
 
-},{}],5:[function(require,module,exports){
-(function (global){
-exports.isatty = function isatty() {
+}); // module: browser/progress.js
+
+require.register("browser/tty.js", function(module, exports, require){
+exports.isatty = function(){
   return true;
 };
 
-exports.getWindowSize = function getWindowSize() {
+exports.getWindowSize = function(){
   if ('innerHeight' in global) {
     return [global.innerHeight, global.innerWidth];
+  } else {
+    // In a Web Worker, the DOM Window is not available.
+    return [640, 480];
   }
-  // In a Web Worker, the DOM Window is not available.
-  return [640, 480];
 };
 
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{}],6:[function(require,module,exports){
+}); // module: browser/tty.js
+
+require.register("context.js", function(module, exports, require){
 /**
  * Expose `Context`.
  */
 
 module.exports = Context;
 
 /**
  * Initialize a new `Context`.
  *
  * @api private
  */
-function Context() {}
+
+function Context(){}
 
 /**
  * Set or get the context `Runnable` to `runnable`.
  *
- * @api private
  * @param {Runnable} runnable
  * @return {Context}
- */
-Context.prototype.runnable = function(runnable) {
-  if (!arguments.length) {
-    return this._runnable;
-  }
+ * @api private
+ */
+
+Context.prototype.runnable = function(runnable){
+  if (0 == arguments.length) return this._runnable;
   this.test = this._runnable = runnable;
   return this;
 };
 
 /**
  * Set test timeout `ms`.
  *
- * @api private
- * @param {number} ms
+ * @param {Number} ms
  * @return {Context} self
- */
-Context.prototype.timeout = function(ms) {
-  if (!arguments.length) {
-    return this.runnable().timeout();
-  }
+ * @api private
+ */
+
+Context.prototype.timeout = function(ms){
+  if (arguments.length === 0) return this.runnable().timeout();
   this.runnable().timeout(ms);
   return this;
 };
 
 /**
  * Set test timeout `enabled`.
  *
- * @api private
- * @param {boolean} enabled
+ * @param {Boolean} enabled
  * @return {Context} self
- */
-Context.prototype.enableTimeouts = function(enabled) {
+ * @api private
+ */
+
+Context.prototype.enableTimeouts = function (enabled) {
   this.runnable().enableTimeouts(enabled);
   return this;
 };
 
+
 /**
  * Set test slowness threshold `ms`.
  *
- * @api private
- * @param {number} ms
+ * @param {Number} ms
  * @return {Context} self
- */
-Context.prototype.slow = function(ms) {
+ * @api private
+ */
+
+Context.prototype.slow = function(ms){
   this.runnable().slow(ms);
   return this;
 };
 
 /**
  * Mark a test as skipped.
  *
+ * @return {Context} self
  * @api private
- * @return {Context} self
- */
-Context.prototype.skip = function() {
-  this.runnable().skip();
-  return this;
-};
-
-/**
- * Allow a number of retries on failed tests
- *
- * @api private
- * @param {number} n
- * @return {Context} self
- */
-Context.prototype.retries = function(n) {
-  if (!arguments.length) {
-    return this.runnable().retries();
-  }
-  this.runnable().retries(n);
-  return this;
+ */
+
+Context.prototype.skip = function(){
+    this.runnable().skip();
+    return this;
 };
 
 /**
  * Inspect the context void of `._runnable`.
  *
+ * @return {String}
  * @api private
- * @return {string}
- */
-Context.prototype.inspect = function() {
-  return JSON.stringify(this, function(key, val) {
-    return key === 'runnable' || key === 'test' ? undefined : val;
+ */
+
+Context.prototype.inspect = function(){
+  return JSON.stringify(this, function(key, val){
+    if ('_runnable' == key) return;
+    if ('test' == key) return;
+    return val;
   }, 2);
 };
 
-},{}],7:[function(require,module,exports){
+}); // module: context.js
+
+require.register("hook.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var Runnable = require('./runnable');
-var inherits = require('./utils').inherits;
 
 /**
  * Expose `Hook`.
  */
 
 module.exports = Hook;
 
 /**
  * Initialize a new `Hook` with the given `title` and callback `fn`.
  *
  * @param {String} title
  * @param {Function} fn
  * @api private
  */
+
 function Hook(title, fn) {
   Runnable.call(this, title, fn);
   this.type = 'hook';
 }
 
 /**
  * Inherit from `Runnable.prototype`.
  */
-inherits(Hook, Runnable);
+
+function F(){};
+F.prototype = Runnable.prototype;
+Hook.prototype = new F;
+Hook.prototype.constructor = Hook;
+
 
 /**
  * Get or set the test `err`.
  *
  * @param {Error} err
  * @return {Error}
  * @api public
  */
-Hook.prototype.error = function(err) {
-  if (!arguments.length) {
-    err = this._error;
+
+Hook.prototype.error = function(err){
+  if (0 == arguments.length) {
+    var err = this._error;
     this._error = null;
     return err;
   }
 
   this._error = err;
 };
 
-},{"./runnable":35,"./utils":39}],8:[function(require,module,exports){
+}); // module: hook.js
+
+require.register("interfaces/bdd.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
-var Suite = require('../suite');
-var Test = require('../test');
-var escapeRe = require('escape-string-regexp');
+var Suite = require('../suite')
+  , Test = require('../test')
+  , utils = require('../utils')
+  , escapeRe = require('browser/escape-string-regexp');
 
 /**
  * BDD-style interface:
  *
- *      describe('Array', function() {
- *        describe('#indexOf()', function() {
- *          it('should return -1 when not present', function() {
- *            // ...
+ *      describe('Array', function(){
+ *        describe('#indexOf()', function(){
+ *          it('should return -1 when not present', function(){
+ *
  *          });
  *
- *          it('should return the index when present', function() {
- *            // ...
+ *          it('should return the index when present', function(){
+ *
  *          });
  *        });
  *      });
  *
- * @param {Suite} suite Root suite.
- */
-module.exports = function(suite) {
+ */
+
+module.exports = function(suite){
   var suites = [suite];
 
-  suite.on('pre-require', function(context, file, mocha) {
+  suite.on('pre-require', function(context, file, mocha){
+
     var common = require('./common')(suites, context);
 
     context.before = common.before;
     context.after = common.after;
     context.beforeEach = common.beforeEach;
     context.afterEach = common.afterEach;
     context.run = mocha.options.delay && common.runWithSuite(suite);
     /**
      * Describe a "suite" with the given `title`
      * and callback `fn` containing nested suites
      * and/or tests.
      */
 
-    context.describe = context.context = function(title, fn) {
+    context.describe = context.context = function(title, fn){
       var suite = Suite.create(suites[0], title);
       suite.file = file;
       suites.unshift(suite);
       fn.call(suite);
       suites.shift();
       return suite;
     };
 
     /**
      * Pending describe.
      */
 
-    context.xdescribe = context.xcontext = context.describe.skip = function(title, fn) {
+    context.xdescribe =
+    context.xcontext =
+    context.describe.skip = function(title, fn){
       var suite = Suite.create(suites[0], title);
       suite.pending = true;
       suites.unshift(suite);
       fn.call(suite);
       suites.shift();
     };
 
     /**
      * Exclusive suite.
      */
 
-    context.describe.only = function(title, fn) {
+    context.describe.only = function(title, fn){
       var suite = context.describe(title, fn);
       mocha.grep(suite.fullTitle());
       return suite;
     };
 
     /**
      * Describe a specification or test-case
      * with the given `title` and callback `fn`
      * acting as a thunk.
      */
 
-    var it = context.it = context.specify = function(title, fn) {
+    context.it = context.specify = function(title, fn){
       var suite = suites[0];
-      if (suite.pending) {
-        fn = null;
-      }
+      if (suite.pending) fn = null;
       var test = new Test(title, fn);
       test.file = file;
       suite.addTest(test);
       return test;
     };
 
     /**
      * Exclusive test-case.
      */
 
-    context.it.only = function(title, fn) {
-      var test = it(title, fn);
+    context.it.only = function(title, fn){
+      var test = context.it(title, fn);
       var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
       return test;
     };
 
     /**
      * Pending test case.
      */
 
-    context.xit = context.xspecify = context.it.skip = function(title) {
+    context.xit =
+    context.xspecify =
+    context.it.skip = function(title){
       context.it(title);
     };
 
-    /**
-     * Number of attempts to retry.
-     */
-    context.it.retries = function(n) {
-      context.retries(n);
-    };
   });
 };
 
-},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":68}],9:[function(require,module,exports){
+}); // module: interfaces/bdd.js
+
+require.register("interfaces/common.js", function(module, exports, require){
+/**
+ * Functions common to more than one interface
+ * @module lib/interfaces/common
+ */
+
 'use strict';
 
-/**
- * Functions common to more than one interface.
- *
- * @param {Suite[]} suites
- * @param {Context} context
- * @return {Object} An object containing common functions.
- */
-module.exports = function(suites, context) {
+module.exports = function (suites, context) {
+
   return {
     /**
-     * This is only present if flag --delay is passed into Mocha. It triggers
-     * root suite execution.
-     *
-     * @param {Suite} suite The root wuite.
-     * @return {Function} A function which runs the root suite
+     * This is only present if flag --delay is passed into Mocha.  It triggers
+     * root suite execution.  Returns a function which runs the root suite.
      */
     runWithSuite: function runWithSuite(suite) {
       return function run() {
         suite.run();
       };
     },
 
     /**
      * Execute before running tests.
-     *
-     * @param {string} name
-     * @param {Function} fn
      */
-    before: function(name, fn) {
+    before: function (name, fn) {
       suites[0].beforeAll(name, fn);
     },
 
     /**
      * Execute after running tests.
-     *
-     * @param {string} name
-     * @param {Function} fn
      */
-    after: function(name, fn) {
+    after: function (name, fn) {
       suites[0].afterAll(name, fn);
     },
 
     /**
      * Execute before each test case.
-     *
-     * @param {string} name
-     * @param {Function} fn
      */
-    beforeEach: function(name, fn) {
+    beforeEach: function (name, fn) {
       suites[0].beforeEach(name, fn);
     },
 
     /**
      * Execute after each test case.
-     *
-     * @param {string} name
-     * @param {Function} fn
      */
-    afterEach: function(name, fn) {
+    afterEach: function (name, fn) {
       suites[0].afterEach(name, fn);
     },
 
     test: {
       /**
        * Pending test case.
-       *
-       * @param {string} title
        */
-      skip: function(title) {
+      skip: function (title) {
         context.test(title);
-      },
-
-      /**
-       * Number of retry attempts
-       *
-       * @param {string} n
-       */
-      retries: function(n) {
-        context.retries(n);
       }
     }
-  };
+  }
 };
 
-},{}],10:[function(require,module,exports){
+}); // module: interfaces/common.js
+
+require.register("interfaces/exports.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
-var Suite = require('../suite');
-var Test = require('../test');
+var Suite = require('../suite')
+  , Test = require('../test');
 
 /**
  * TDD-style interface:
  *
  *     exports.Array = {
  *       '#indexOf()': {
- *         'should return -1 when the value is not present': function() {
+ *         'should return -1 when the value is not present': function(){
  *
  *         },
  *
- *         'should return the correct index when the value is present': function() {
+ *         'should return the correct index when the value is present': function(){
  *
  *         }
  *       }
  *     };
  *
- * @param {Suite} suite Root suite.
- */
-module.exports = function(suite) {
+ */
+
+module.exports = function(suite){
   var suites = [suite];
 
   suite.on('require', visit);
 
   function visit(obj, file) {
     var suite;
     for (var key in obj) {
-      if (typeof obj[key] === 'function') {
+      if ('function' == typeof obj[key]) {
         var fn = obj[key];
         switch (key) {
           case 'before':
             suites[0].beforeAll(fn);
             break;
           case 'after':
             suites[0].afterAll(fn);
             break;
@@ -751,176 +1168,185 @@ module.exports = function(suite) {
           default:
             var test = new Test(key, fn);
             test.file = file;
             suites[0].addTest(test);
         }
       } else {
         suite = Suite.create(suites[0], key);
         suites.unshift(suite);
-        visit(obj[key], file);
+        visit(obj[key]);
         suites.shift();
       }
     }
   }
 };
 
-},{"../suite":37,"../test":38}],11:[function(require,module,exports){
+}); // module: interfaces/exports.js
+
+require.register("interfaces/index.js", function(module, exports, require){
 exports.bdd = require('./bdd');
 exports.tdd = require('./tdd');
 exports.qunit = require('./qunit');
 exports.exports = require('./exports');
 
-},{"./bdd":8,"./exports":10,"./qunit":12,"./tdd":13}],12:[function(require,module,exports){
+}); // module: interfaces/index.js
+
+require.register("interfaces/qunit.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
-var Suite = require('../suite');
-var Test = require('../test');
-var escapeRe = require('escape-string-regexp');
+var Suite = require('../suite')
+  , Test = require('../test')
+  , escapeRe = require('browser/escape-string-regexp')
+  , utils = require('../utils');
 
 /**
  * QUnit-style interface:
  *
  *     suite('Array');
  *
- *     test('#length', function() {
+ *     test('#length', function(){
  *       var arr = [1,2,3];
  *       ok(arr.length == 3);
  *     });
  *
- *     test('#indexOf()', function() {
+ *     test('#indexOf()', function(){
  *       var arr = [1,2,3];
  *       ok(arr.indexOf(1) == 0);
  *       ok(arr.indexOf(2) == 1);
  *       ok(arr.indexOf(3) == 2);
  *     });
  *
  *     suite('String');
  *
- *     test('#length', function() {
+ *     test('#length', function(){
  *       ok('foo'.length == 3);
  *     });
  *
- * @param {Suite} suite Root suite.
- */
-module.exports = function(suite) {
+ */
+
+module.exports = function(suite){
   var suites = [suite];
 
-  suite.on('pre-require', function(context, file, mocha) {
+  suite.on('pre-require', function(context, file, mocha){
+
     var common = require('./common')(suites, context);
 
     context.before = common.before;
     context.after = common.after;
     context.beforeEach = common.beforeEach;
     context.afterEach = common.afterEach;
     context.run = mocha.options.delay && common.runWithSuite(suite);
     /**
      * Describe a "suite" with the given `title`.
      */
 
-    context.suite = function(title) {
-      if (suites.length > 1) {
-        suites.shift();
-      }
+    context.suite = function(title){
+      if (suites.length > 1) suites.shift();
       var suite = Suite.create(suites[0], title);
       suite.file = file;
       suites.unshift(suite);
       return suite;
     };
 
     /**
      * Exclusive test-case.
      */
 
-    context.suite.only = function(title, fn) {
+    context.suite.only = function(title, fn){
       var suite = context.suite(title, fn);
       mocha.grep(suite.fullTitle());
     };
 
     /**
      * Describe a specification or test-case
      * with the given `title` and callback `fn`
      * acting as a thunk.
      */
 
-    context.test = function(title, fn) {
+    context.test = function(title, fn){
       var test = new Test(title, fn);
       test.file = file;
       suites[0].addTest(test);
       return test;
     };
 
     /**
      * Exclusive test-case.
      */
 
-    context.test.only = function(title, fn) {
+    context.test.only = function(title, fn){
       var test = context.test(title, fn);
       var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
     };
 
     context.test.skip = common.test.skip;
-    context.test.retries = common.test.retries;
+
   });
 };
 
-},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":68}],13:[function(require,module,exports){
+}); // module: interfaces/qunit.js
+
+require.register("interfaces/tdd.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
-var Suite = require('../suite');
-var Test = require('../test');
-var escapeRe = require('escape-string-regexp');
+var Suite = require('../suite')
+  , Test = require('../test')
+  , escapeRe = require('browser/escape-string-regexp')
+  , utils = require('../utils');
 
 /**
  * TDD-style interface:
  *
- *      suite('Array', function() {
- *        suite('#indexOf()', function() {
- *          suiteSetup(function() {
+ *      suite('Array', function(){
+ *        suite('#indexOf()', function(){
+ *          suiteSetup(function(){
  *
  *          });
  *
- *          test('should return -1 when not present', function() {
+ *          test('should return -1 when not present', function(){
  *
  *          });
  *
- *          test('should return the index when present', function() {
+ *          test('should return the index when present', function(){
  *
  *          });
  *
- *          suiteTeardown(function() {
+ *          suiteTeardown(function(){
  *
  *          });
  *        });
  *      });
  *
- * @param {Suite} suite Root suite.
- */
-module.exports = function(suite) {
+ */
+
+module.exports = function(suite){
   var suites = [suite];
 
-  suite.on('pre-require', function(context, file, mocha) {
+  suite.on('pre-require', function(context, file, mocha){
+
     var common = require('./common')(suites, context);
 
     context.setup = common.beforeEach;
     context.teardown = common.afterEach;
     context.suiteSetup = common.before;
     context.suiteTeardown = common.after;
     context.run = mocha.options.delay && common.runWithSuite(suite);
-
     /**
-     * Describe a "suite" with the given `title` and callback `fn` containing
-     * nested suites and/or tests.
+     * Describe a "suite" with the given `title`
+     * and callback `fn` containing nested suites
+     * and/or tests.
      */
-    context.suite = function(title, fn) {
+
+    context.suite = function(title, fn){
       var suite = Suite.create(suites[0], title);
       suite.file = file;
       suites.unshift(suite);
       fn.call(suite);
       suites.shift();
       return suite;
     };
 
@@ -933,156 +1359,145 @@ module.exports = function(suite) {
       suites.unshift(suite);
       fn.call(suite);
       suites.shift();
     };
 
     /**
      * Exclusive test-case.
      */
-    context.suite.only = function(title, fn) {
+
+    context.suite.only = function(title, fn){
       var suite = context.suite(title, fn);
       mocha.grep(suite.fullTitle());
     };
 
     /**
-     * Describe a specification or test-case with the given `title` and
-     * callback `fn` acting as a thunk.
+     * Describe a specification or test-case
+     * with the given `title` and callback `fn`
+     * acting as a thunk.
      */
-    context.test = function(title, fn) {
+
+    context.test = function(title, fn){
       var suite = suites[0];
-      if (suite.pending) {
-        fn = null;
-      }
+      if (suite.pending) fn = null;
       var test = new Test(title, fn);
       test.file = file;
       suite.addTest(test);
       return test;
     };
 
     /**
      * Exclusive test-case.
      */
 
-    context.test.only = function(title, fn) {
+    context.test.only = function(title, fn){
       var test = context.test(title, fn);
       var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
     };
 
     context.test.skip = common.test.skip;
-    context.test.retries = common.test.retries;
   });
 };
 
-},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":68}],14:[function(require,module,exports){
-(function (process,global,__dirname){
+}); // module: interfaces/tdd.js
+
+require.register("mocha.js", function(module, exports, require){
 /*!
  * mocha
  * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
  * MIT Licensed
  */
 
 /**
  * Module dependencies.
  */
 
-var escapeRe = require('escape-string-regexp');
-var path = require('path');
-var reporters = require('./reporters');
-var utils = require('./utils');
+var path = require('browser/path')
+  , escapeRe = require('browser/escape-string-regexp')
+  , utils = require('./utils');
 
 /**
  * Expose `Mocha`.
  */
 
 exports = module.exports = Mocha;
 
 /**
  * To require local UIs and reporters when running in node.
  */
 
-if (!process.browser) {
-  var cwd = process.cwd();
-  module.paths.push(cwd, path.join(cwd, 'node_modules'));
+if (typeof process !== 'undefined' && typeof process.cwd === 'function') {
+  var join = path.join
+    , cwd = process.cwd();
+  module.paths.push(cwd, join(cwd, 'node_modules'));
 }
 
 /**
  * Expose internals.
  */
 
 exports.utils = utils;
 exports.interfaces = require('./interfaces');
-exports.reporters = reporters;
+exports.reporters = require('./reporters');
 exports.Runnable = require('./runnable');
 exports.Context = require('./context');
 exports.Runner = require('./runner');
 exports.Suite = require('./suite');
 exports.Hook = require('./hook');
 exports.Test = require('./test');
 
 /**
  * Return image `name` path.
  *
+ * @param {String} name
+ * @return {String}
  * @api private
- * @param {string} name
- * @return {string}
- */
+ */
+
 function image(name) {
-  return path.join(__dirname, '../images', name + '.png');
+  return __dirname + '/../images/' + name + '.png';
 }
 
 /**
- * Set up mocha with `options`.
+ * Setup mocha with `options`.
  *
  * Options:
  *
  *   - `ui` name "bdd", "tdd", "exports" etc
  *   - `reporter` reporter instance, defaults to `mocha.reporters.spec`
  *   - `globals` array of accepted globals
  *   - `timeout` timeout in milliseconds
- *   - `retries` number of times to retry failed tests
  *   - `bail` bail on the first test failure
  *   - `slow` milliseconds to wait before considering a test slow
  *   - `ignoreLeaks` ignore global leaks
  *   - `fullTrace` display the full stack-trace on failing
  *   - `grep` string or regexp to filter tests with
  *
  * @param {Object} options
  * @api public
  */
+
 function Mocha(options) {
   options = options || {};
   this.files = [];
   this.options = options;
-  if (options.grep) {
-    this.grep(new RegExp(options.grep));
-  }
-  if (options.fgrep) {
-    this.grep(options.fgrep);
-  }
-  this.suite = new exports.Suite('', new exports.Context());
+  if (options.grep) this.grep(new RegExp(options.grep));
+  if (options.fgrep) this.grep(options.fgrep);
+  this.suite = new exports.Suite('', new exports.Context);
   this.ui(options.ui);
   this.bail(options.bail);
   this.reporter(options.reporter, options.reporterOptions);
-  if (typeof options.timeout !== 'undefined' && options.timeout !== null) {
-    this.timeout(options.timeout);
-  }
-  if (typeof options.retries !== 'undefined' && options.retries !== null) {
-    this.retries(options.retries);
-  }
+  if (null != options.timeout) this.timeout(options.timeout);
   this.useColors(options.useColors);
-  if (options.enableTimeouts !== null) {
-    this.enableTimeouts(options.enableTimeouts);
-  }
-  if (options.slow) {
-    this.slow(options.slow);
-  }
-
-  this.suite.on('pre-require', function(context) {
+  if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts);
+  if (options.slow) this.slow(options.slow);
+
+  this.suite.on('pre-require', function (context) {
     exports.afterEach = context.afterEach || context.teardown;
     exports.after = context.after || context.suiteTeardown;
     exports.beforeEach = context.beforeEach || context.setup;
     exports.before = context.before || context.suiteSetup;
     exports.describe = context.describe || context.suite;
     exports.it = context.it || context.test;
     exports.setup = context.setup || context.beforeEach;
     exports.suiteSetup = context.suiteSetup || context.before;
@@ -1092,399 +1507,356 @@ function Mocha(options) {
     exports.test = context.test || context.it;
     exports.run = context.run;
   });
 }
 
 /**
  * Enable or disable bailing on the first failure.
  *
+ * @param {Boolean} [bail]
  * @api public
- * @param {boolean} [bail]
- */
-Mocha.prototype.bail = function(bail) {
-  if (!arguments.length) {
-    bail = true;
-  }
+ */
+
+Mocha.prototype.bail = function(bail){
+  if (0 == arguments.length) bail = true;
   this.suite.bail(bail);
   return this;
 };
 
 /**
  * Add test `file`.
  *
+ * @param {String} file
  * @api public
- * @param {string} file
- */
-Mocha.prototype.addFile = function(file) {
+ */
+
+Mocha.prototype.addFile = function(file){
   this.files.push(file);
   return this;
 };
 
 /**
  * Set reporter to `reporter`, defaults to "spec".
  *
  * @param {String|Function} reporter name or constructor
  * @param {Object} reporterOptions optional options
  * @api public
- * @param {string|Function} reporter name or constructor
- * @param {Object} reporterOptions optional options
- */
-Mocha.prototype.reporter = function(reporter, reporterOptions) {
-  if (typeof reporter === 'function') {
+ */
+Mocha.prototype.reporter = function(reporter, reporterOptions){
+  if ('function' == typeof reporter) {
     this._reporter = reporter;
   } else {
     reporter = reporter || 'spec';
     var _reporter;
-    // Try to load a built-in reporter.
-    if (reporters[reporter]) {
-      _reporter = reporters[reporter];
+    try { _reporter = require('./reporters/' + reporter); } catch (err) {}
+    if (!_reporter) try { _reporter = require(reporter); } catch (err) {
+      err.message.indexOf('Cannot find module') !== -1
+        ? console.warn('"' + reporter + '" reporter not found')
+        : console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack);
     }
-    // Try to load reporters from process.cwd() and node_modules
-    if (!_reporter) {
-      try {
-        _reporter = require(reporter);
-      } catch (err) {
-        err.message.indexOf('Cannot find module') !== -1
-          ? console.warn('"' + reporter + '" reporter not found')
-          : console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack);
-      }
-    }
-    if (!_reporter && reporter === 'teamcity') {
-      console.warn('The Teamcity reporter was moved to a package named '
-        + 'mocha-teamcity-reporter '
-        + '(https://npmjs.org/package/mocha-teamcity-reporter).');
-    }
-    if (!_reporter) {
-      throw new Error('invalid reporter "' + reporter + '"');
-    }
+    if (!_reporter && reporter === 'teamcity')
+      console.warn('The Teamcity reporter was moved to a package named ' +
+        'mocha-teamcity-reporter ' +
+        '(https://npmjs.org/package/mocha-teamcity-reporter).');
+    if (!_reporter) throw new Error('invalid reporter "' + reporter + '"');
     this._reporter = _reporter;
   }
   this.options.reporterOptions = reporterOptions;
   return this;
 };
 
 /**
  * Set test UI `name`, defaults to "bdd".
  *
+ * @param {String} bdd
  * @api public
- * @param {string} bdd
- */
-Mocha.prototype.ui = function(name) {
+ */
+
+Mocha.prototype.ui = function(name){
   name = name || 'bdd';
   this._ui = exports.interfaces[name];
-  if (!this._ui) {
-    try {
-      this._ui = require(name);
-    } catch (err) {
-      throw new Error('invalid interface "' + name + '"');
-    }
-  }
+  if (!this._ui) try { this._ui = require(name); } catch (err) {}
+  if (!this._ui) throw new Error('invalid interface "' + name + '"');
   this._ui = this._ui(this.suite);
   return this;
 };
 
 /**
  * Load registered files.
  *
  * @api private
  */
-Mocha.prototype.loadFiles = function(fn) {
+
+Mocha.prototype.loadFiles = function(fn){
   var self = this;
   var suite = this.suite;
-  this.files.forEach(function(file) {
+  var pending = this.files.length;
+  this.files.forEach(function(file){
     file = path.resolve(file);
     suite.emit('pre-require', global, file, self);
     suite.emit('require', require(file), file, self);
     suite.emit('post-require', global, file, self);
+    --pending || (fn && fn());
   });
-  fn && fn();
 };
 
 /**
  * Enable growl support.
  *
  * @api private
  */
+
 Mocha.prototype._growl = function(runner, reporter) {
   var notify = require('growl');
 
-  runner.on('end', function() {
+  runner.on('end', function(){
     var stats = reporter.stats;
     if (stats.failures) {
       var msg = stats.failures + ' of ' + runner.total + ' tests failed';
       notify(msg, { name: 'mocha', title: 'Failed', image: image('error') });
     } else {
       notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
-        name: 'mocha',
-        title: 'Passed',
-        image: image('ok')
+          name: 'mocha'
+        , title: 'Passed'
+        , image: image('ok')
       });
     }
   });
 };
 
 /**
  * Add regexp to grep, if `re` is a string it is escaped.
  *
  * @param {RegExp|String} re
  * @return {Mocha}
  * @api public
- * @param {RegExp|string} re
- * @return {Mocha}
- */
-Mocha.prototype.grep = function(re) {
-  this.options.grep = typeof re === 'string' ? new RegExp(escapeRe(re)) : re;
+ */
+
+Mocha.prototype.grep = function(re){
+  this.options.grep = 'string' == typeof re
+    ? new RegExp(escapeRe(re))
+    : re;
   return this;
 };
 
 /**
  * Invert `.grep()` matches.
  *
  * @return {Mocha}
  * @api public
  */
-Mocha.prototype.invert = function() {
+
+Mocha.prototype.invert = function(){
   this.options.invert = true;
   return this;
 };
 
 /**
  * Ignore global leaks.
  *
  * @param {Boolean} ignore
  * @return {Mocha}
  * @api public
- * @param {boolean} ignore
- * @return {Mocha}
- */
-Mocha.prototype.ignoreLeaks = function(ignore) {
-  this.options.ignoreLeaks = Boolean(ignore);
+ */
+
+Mocha.prototype.ignoreLeaks = function(ignore){
+  this.options.ignoreLeaks = !!ignore;
   return this;
 };
 
 /**
  * Enable global leak checking.
  *
  * @return {Mocha}
  * @api public
  */
-Mocha.prototype.checkLeaks = function() {
+
+Mocha.prototype.checkLeaks = function(){
   this.options.ignoreLeaks = false;
   return this;
 };
 
 /**
  * Display long stack-trace on failing
  *
  * @return {Mocha}
  * @api public
  */
+
 Mocha.prototype.fullTrace = function() {
   this.options.fullStackTrace = true;
   return this;
 };
 
 /**
  * Enable growl support.
  *
  * @return {Mocha}
  * @api public
  */
-Mocha.prototype.growl = function() {
+
+Mocha.prototype.growl = function(){
   this.options.growl = true;
   return this;
 };
 
 /**
  * Ignore `globals` array or string.
  *
  * @param {Array|String} globals
  * @return {Mocha}
  * @api public
- * @param {Array|string} globals
- * @return {Mocha}
- */
-Mocha.prototype.globals = function(globals) {
+ */
+
+Mocha.prototype.globals = function(globals){
   this.options.globals = (this.options.globals || []).concat(globals);
   return this;
 };
 
 /**
  * Emit color output.
  *
  * @param {Boolean} colors
  * @return {Mocha}
  * @api public
- * @param {boolean} colors
- * @return {Mocha}
- */
-Mocha.prototype.useColors = function(colors) {
+ */
+
+Mocha.prototype.useColors = function(colors){
   if (colors !== undefined) {
     this.options.useColors = colors;
   }
   return this;
 };
 
 /**
  * Use inline diffs rather than +/-.
  *
  * @param {Boolean} inlineDiffs