Backed out changesets 2f3f35b8cea3 and 7824a3826963 (bug 1002914) for intermittent mochitest-bc failures.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 07 May 2014 16:56:28 -0400
changeset 182008 8be0e21fd300b609e75f6d8b3a33e000ce342cae
parent 182007 865919c5d41add144d47973198c3a6791684c5ca
child 182033 39578a226d511611c745350b41d67b923350939d
child 182092 47f3a02e649ca20b1cbe8142f8b0eb974556cca4
push id26741
push userkwierso@gmail.com
push dateWed, 07 May 2014 21:32:53 +0000
treeherdermozilla-central@8be0e21fd300 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1002914
milestone32.0a1
backs out2f3f35b8cea3faf36a03e900df80ccee180b8b0b
7824a38269633d18eab35d100c9eb1facc32c7f7
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changesets 2f3f35b8cea3 and 7824a3826963 (bug 1002914) for intermittent mochitest-bc failures.
browser/base/content/browser-sets.inc
browser/base/content/browser-social.js
browser/base/content/socialchat.xml
browser/base/content/test/chat/browser.ini
browser/base/content/test/chat/browser_chatwindow.js
browser/base/content/test/chat/browser_focus.js
browser/base/content/test/chat/browser_tearoff.js
browser/base/content/test/chat/chat.html
browser/base/content/test/chat/head.js
browser/base/content/test/social/browser.ini
browser/base/content/test/social/browser_chat_tearoff.js
browser/base/content/test/social/browser_social_chatwindow.js
browser/base/content/test/social/browser_social_chatwindowfocus.js
browser/base/content/test/social/browser_social_errorPage.js
browser/base/content/test/social/head.js
browser/base/moz.build
browser/modules/Chat.jsm
browser/modules/moz.build
toolkit/components/social/MozSocialAPI.jsm
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -114,18 +114,18 @@
       oncommand="OpenBrowserWindow({remote: true});"/>
     <command id="Tools:NonRemoteWindow"
       oncommand="OpenBrowserWindow({remote: false});"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
     <command id="Social:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/>
     <command id="Social:ToggleSidebar" oncommand="SocialSidebar.toggleSidebar();" hidden="true"/>
     <command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
+    <command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
     <command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
-    <command id="Chat:Focus" oncommand="Cu.import('resource:///modules/Chat.jsm', {}).Chat.focus(window);"/>
   </commandset>
 
   <commandset id="placesCommands">
     <command id="Browser:ShowAllBookmarks"
              oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
     <command id="Browser:ShowAllHistory"
              oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
   </commandset>
@@ -375,25 +375,17 @@
     <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
 #ifdef XP_WIN
 # Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
 # overridden for other purposes there.
     <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
 #endif
 
     <!--<key id="markPage" key="&markPageCmd.commandkey;" command="Social:TogglePageMark" modifiers="accel,shift"/>-->
-    <key id="focusChatBar" key="&social.chatBar.commandkey;" command="Chat:Focus"
-#ifdef XP_MACOSX
-# Sadly the devtools uses shift-accel-c on non-mac and alt-accel-c everywhere else
-# So we just use the other
-         modifiers="accel,shift"
-#else
-         modifiers="accel,alt"
-#endif
-    />
+    <key id="focusChatBar" key="&social.chatBar.commandkey;" command="Social:FocusChat" modifiers="accel,shift"/>
 
     <key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
 
 #ifdef XP_MACOSX
     <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
 #endif
 
     <key id="key_gotoHistory"
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1,14 +1,15 @@
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 // the "exported" symbols
 let SocialUI,
+    SocialChatBar,
     SocialFlyout,
     SocialMarks,
     SocialShare,
     SocialSidebar,
     SocialStatus;
 
 (function() {
 
@@ -167,16 +168,17 @@ SocialUI = {
       Components.utils.reportError(e + "\n" + e.stack);
       throw e;
     }
   },
 
   _providersChanged: function() {
     SocialSidebar.clearProviderMenus();
     SocialSidebar.update();
+    SocialChatBar.update();
     SocialShare.populateProviderMenu();
     SocialStatus.populateToolbarPalette();
     SocialMarks.populateToolbarPalette();
     SocialShare.update();
   },
 
   // This handles "ActivateSocialFeature" events fired against content documents
   // in this window.
@@ -290,16 +292,55 @@ SocialUI = {
   updateState: function() {
     if (!this.enabled)
       return;
     SocialMarks.update();
     SocialShare.update();
   }
 }
 
+SocialChatBar = {
+  get chatbar() {
+    return document.getElementById("pinnedchats");
+  },
+  // Whether the chatbar is available for this window.  Note that in full-screen
+  // mode chats are available, but not shown.
+  get isAvailable() {
+    return SocialUI.enabled;
+  },
+  // Does this chatbar have any chats (whether minimized, collapsed or normal)
+  get hasChats() {
+    return !!this.chatbar.firstElementChild;
+  },
+  openChat: function(aProvider, aURL, aCallback, aMode) {
+    this.update();
+    if (!this.isAvailable)
+      return false;
+    this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
+    // We only want to focus the chat if it is as a result of user input.
+    let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindowUtils);
+    if (dwu.isHandlingUserInput)
+      this.chatbar.focus();
+    return true;
+  },
+  update: function() {
+    let command = document.getElementById("Social:FocusChat");
+    if (!this.isAvailable) {
+      this.chatbar.hidden = command.hidden = true;
+    } else {
+      this.chatbar.hidden = command.hidden = false;
+    }
+    command.setAttribute("disabled", command.hidden ? "true" : "false");
+  },
+  focus: function SocialChatBar_focus() {
+    this.chatbar.focus();
+  }
+}
+
 SocialFlyout = {
   get panel() {
     return document.getElementById("social-flyout-panel");
   },
 
   get iframe() {
     if (!this.panel.firstChild)
       this._createFrame();
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -25,53 +25,60 @@
                   context="contentAreaContextMenu"
                   disableglobalhistory="true"
                   tooltip="aHTMLTooltip"
                   xbl:inherits="src,origin" type="content"/>
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <constructor><![CDATA[
+        let Social = Components.utils.import("resource:///modules/Social.jsm", {}).Social;
         this.content.__defineGetter__("popupnotificationanchor",
                                       () => document.getAnonymousElementByAttribute(this, "anonid", "notification-icon"));
-
+        Social.setErrorListener(this.content, function(aBrowser) {
+          aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
+                                 encodeURIComponent(aBrowser.getAttribute("origin")),
+                                 null, null, null, null);
+        });
         if (!this.chatbar) {
           document.getAnonymousElementByAttribute(this, "anonid", "minimize").hidden = true;
           document.getAnonymousElementByAttribute(this, "anonid", "close").hidden = true;
         }
         let contentWindow = this.contentWindow;
-        // process this._callbacks, then set to null so the chatbox creator
-        // knows to make new callbacks immediately.
-        if (this._callbacks) {
-          for (let callback of this._callbacks) {
-            callback(this);
-          }
-          this._callbacks = null;
-        }
         this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
           if (event.target != this.contentDocument)
             return;
           this.removeEventListener("DOMContentLoaded", DOMContentLoaded, true);
           this.isActive = !this.minimized;
-          this._deferredChatLoaded.resolve(this);
+          // process this._callbacks, then set to null so the chatbox creator
+          // knows to make new callbacks immediately.
+          if (this._callbacks) {
+            for (let callback of this._callbacks) {
+              if (callback)
+                callback(contentWindow);
+            }
+            this._callbacks = null;
+          }
+
+          // content can send a socialChatActivity event to have the UI update.
+          let chatActivity = function() {
+            this.setAttribute("activity", true);
+            if (this.chatbar)
+              this.chatbar.updateTitlebar(this);
+          }.bind(this);
+          contentWindow.addEventListener("socialChatActivity", chatActivity);
+          contentWindow.addEventListener("unload", function unload() {
+            contentWindow.removeEventListener("unload", unload);
+            contentWindow.removeEventListener("socialChatActivity", chatActivity);
+          });
         }, true);
         if (this.src)
           this.setAttribute("src", this.src);
       ]]></constructor>
 
-      <field name="_deferredChatLoaded" readonly="true">
-        Promise.defer();
-      </field>
-
-      <property name="promiseChatLoaded">
-        <getter>
-          return this._deferredChatLoaded.promise;
-        </getter>
-      </property>
-
       <field name="content" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "content");
       </field>
 
       <property name="contentWindow">
         <getter>
           return this.content.contentWindow;
         </getter>
@@ -121,32 +128,40 @@
           this.content.docShell.isActive = !!val;
 
           // let the chat frame know if it is being shown or hidden
           let evt = this.contentDocument.createEvent("CustomEvent");
           evt.initCustomEvent(val ? "socialFrameShow" : "socialFrameHide", true, true, {});
           this.contentDocument.documentElement.dispatchEvent(evt);
         </setter>
       </property>
-
+      
       <method name="showNotifications">
         <body><![CDATA[
         PopupNotifications._reshowNotifications(this.content.popupnotificationanchor,
                                                 this.content);
         ]]></body>
       </method>
 
       <method name="swapDocShells">
         <parameter name="aTarget"/>
         <body><![CDATA[
           aTarget.setAttribute('label', this.contentDocument.title);
           aTarget.src = this.src;
           aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
           aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
+          this.content.socialErrorListener.remove();
+          aTarget.content.socialErrorListener.remove();
           this.content.swapDocShells(aTarget.content);
+          Social.setErrorListener(this.content, function(aBrowser) {}); // 'this' will be destroyed soon.
+          Social.setErrorListener(aTarget.content, function(aBrowser) {
+            aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
+                                 encodeURIComponent(aBrowser.getAttribute("origin")),
+                                 null, null, null, null);
+          });
         ]]></body>
       </method>
 
       <method name="onTitlebarClick">
         <parameter name="aEvent"/>
         <body><![CDATA[
           if (!this.chatbar)
             return;
@@ -166,50 +181,41 @@
           this.chatbar.remove(this);
         else
           window.close();
         ]]></body>
       </method>
 
       <method name="swapWindows">
         <body><![CDATA[
-        let deferred = Promise.defer();
-        let title = this.getAttribute("label");
+        let provider = Social._getProviderFromOrigin(this.content.getAttribute("origin"));
         if (this.chatbar) {
-          this.chatbar.detachChatbox(this, { "centerscreen": "yes" }).then(
-            chatbox => {
-              chatbox.contentWindow.document.title = title;
-              deferred.resolve(chatbox);
-            }
-          );
+          this.chatbar.detachChatbox(this, { "centerscreen": "yes" }, win => {
+            win.document.title = provider.name;
+          });
         } else {
           // attach this chatbox to the topmost browser window
-          let Chat = Cu.import("resource:///modules/Chat.jsm").Chat;
-          let win = Chat.findChromeWindowForChats();
-          let chatbar = win.document.getElementById("pinnedchats");
-          let origin = this.content.getAttribute("origin");
-          let cb = chatbar.openChat(origin, title, "about:blank");
-          cb.promiseChatLoaded.then(
-            () => {
-              this.swapDocShells(cb);
+          let findChromeWindowForChats = Cu.import("resource://gre/modules/MozSocialAPI.jsm").findChromeWindowForChats;
+          let win = findChromeWindowForChats();
+          let chatbar = win.SocialChatBar.chatbar;
+          chatbar.openChat(provider, "about:blank", win => {
+            let cb = chatbar.selectedChat;
+            this.swapDocShells(cb);
 
-              // chatboxForURL is a map of URL -> chatbox used to avoid opening
-              // duplicate chat windows. Ensure reattached chat windows aren't
-              // registered with about:blank as their URL, otherwise reattaching
-              // more than one chat window isn't possible.
-              chatbar.chatboxForURL.delete("about:blank");
-              chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
+            // chatboxForURL is a map of URL -> chatbox used to avoid opening
+            // duplicate chat windows. Ensure reattached chat windows aren't
+            // registered with about:blank as their URL, otherwise reattaching
+            // more than one chat window isn't possible.
+            chatbar.chatboxForURL.delete("about:blank");
+            chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
 
-              chatbar.focus();
-              this.close();
-              deferred.resolve(cb);
-            }
-          );
+            chatbar.focus();
+            this.close();
+          });
         }
-        return deferred.promise;
         ]]></body>
       </method>
 
       <method name="toggle">
         <body><![CDATA[
           this.minimized = !this.minimized;
         ]]></body>
       </method>
@@ -499,70 +505,76 @@
             this._selectAnotherChat();
           }
         ]]></body>
       </method>
 
       <method name="_remove">
         <parameter name="aChatbox"/>
         <body><![CDATA[
+          aChatbox.content.socialErrorListener.remove();
           this.removeChild(aChatbox);
           // child might have been collapsed.
           let menuitem = this.menuitemMap.get(aChatbox);
           if (menuitem) {
             this.menuitemMap.delete(aChatbox);
             this.menupopup.removeChild(menuitem);
           }
           this.chatboxForURL.delete(aChatbox.src);
         ]]></body>
       </method>
 
+      <method name="removeAll">
+        <body><![CDATA[
+          this.selectedChat = null;
+          while (this.firstElementChild) {
+            this._remove(this.firstElementChild);
+          }
+          // and the nub/popup must also die.
+          this.nub.collapsed = true;
+        ]]></body>
+      </method>
+
       <method name="openChat">
-        <parameter name="aOrigin"/>
-        <parameter name="aTitle"/>
+        <parameter name="aProvider"/>
         <parameter name="aURL"/>
+        <parameter name="aCallback"/>
         <parameter name="aMode"/>
-        <parameter name="aCallback"/>
         <body><![CDATA[
           let cb = this.chatboxForURL.get(aURL);
           if (cb) {
             cb = cb.get();
             if (cb.parentNode) {
               this.showChat(cb, aMode);
               if (aCallback) {
                 if (cb._callbacks == null) {
-                  // Chatbox has already been created, so callback now.
-                  aCallback(cb);
+                  // DOMContentLoaded has already fired, so callback now.
+                  aCallback(cb.contentWindow);
                 } else {
-                  // Chatbox is yet to have bindings created...
+                  // DOMContentLoaded for this chat is yet to fire...
                   cb._callbacks.push(aCallback);
                 }
               }
-              return cb;
+              return;
             }
             this.chatboxForURL.delete(aURL);
           }
           cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
-          cb._callbacks = [];
-          if (aCallback) {
-            // _callbacks is a javascript property instead of a <field> as it
-            // must exist before the (possibly delayed) bindings are created.
-            cb._callbacks.push(aCallback);
-          }
+          // _callbacks is a javascript property instead of a <field> as it
+          // must exist before the (possibly delayed) bindings are created.
+          cb._callbacks = [aCallback];
           // src also a javascript property; the src attribute is set in the ctor.
           cb.src = aURL;
           if (aMode == "minimized")
             cb.setAttribute("minimized", "true");
-          cb.setAttribute("origin", aOrigin);
-          cb.setAttribute("label", aTitle);
+          cb.setAttribute("origin", aProvider.origin);
           this.insertBefore(cb, this.firstChild);
           this.selectedChat = cb;
           this.chatboxForURL.set(aURL, Cu.getWeakReference(cb));
           this.resize();
-          return cb;
         ]]></body>
       </method>
 
       <method name="resize">
         <body><![CDATA[
         // Checks the current size against the collapsed state of children
         // and collapses or expands as necessary such that as many as possible
         // are shown.
@@ -631,42 +643,40 @@
 
       <method name="_getDragTarget">
         <parameter name="event"/>
         <body><![CDATA[
           return event.target.localName == "chatbox" ? event.target : null;
         ]]></body>
       </method>
 
-      <!-- Moves a chatbox to a new window. Returns a promise that is resolved
-           once the move to the other window is complete.
-      -->
+      <!-- Moves a chatbox to a new window. -->
       <method name="detachChatbox">
         <parameter name="aChatbox"/>
         <parameter name="aOptions"/>
+        <parameter name="aCallback"/>
         <body><![CDATA[
-          let deferred = Promise.defer();
           let options = "";
           for (let name in aOptions)
             options += "," + name + "=" + aOptions[name];
 
           let otherWin = window.openDialog("chrome://browser/content/chatWindow.xul",
                                            "_blank", "chrome,all,dialog=no" + options);
 
           otherWin.addEventListener("load", function _chatLoad(event) {
             if (event.target != otherWin.document)
               return;
 
             otherWin.removeEventListener("load", _chatLoad, true);
             let otherChatbox = otherWin.document.getElementById("chatter");
             aChatbox.swapDocShells(otherChatbox);
             aChatbox.close();
-            deferred.resolve(otherChatbox);
+            if (aCallback)
+              aCallback(otherWin);
           }, true);
-          return deferred.promise;
         ]]></body>
       </method>
 
     </implementation>
 
     <handlers>
       <handler event="popupshown"><![CDATA[
         this.nub.removeAttribute("activity");
@@ -735,20 +745,19 @@
         let winWidth = 400;
         let winHeight = 420;
         // ensure new window entirely within screen
         let left = Math.min(Math.max(eX, sX.value),
                             sX.value + sWidth.value - winWidth);
         let top = Math.min(Math.max(eY, sY.value),
                            sY.value + sHeight.value - winHeight);
 
-        let title = draggedChat.content.getAttribute("title");
-        this.detachChatbox(draggedChat, { screenX: left, screenY: top }).then(
-          chatbox => {
-            chatbox.contentWindow.document.title = title;
-          }
-        );
+        let provider = Social._getProviderFromOrigin(draggedChat.content.getAttribute("origin"));
+        this.detachChatbox(draggedChat, { screenX: left, screenY: top }, win => {
+          win.document.title = provider.name;
+        });
+
         event.stopPropagation();
       ]]></handler>
     </handlers>
   </binding>
 
 </bindings>
deleted file mode 100644
--- a/browser/base/content/test/chat/browser.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[DEFAULT]
-support-files =
-  head.js
-  chat.html
-
-[browser_chatwindow.js]
-[browser_focus.js]
-[browser_tearoff.js]
deleted file mode 100644
--- a/browser/base/content/test/chat/browser_chatwindow.js
+++ /dev/null
@@ -1,135 +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/. */
-
-let chatbar = document.getElementById("pinnedchats");
-
-add_chat_task(function* testOpenCloseChat() {
-  let chatbox = yield promiseOpenChat("http://example.com");
-  Assert.strictEqual(chatbox, chatbar.selectedChat);
-  // we requested a "normal" chat, so shouldn't be minimized
-  Assert.ok(!chatbox.minimized, "chat is not minimized");
-  Assert.equal(chatbar.childNodes.length, 1, "should be 1 chat open");
-
-
-  // now request the same URL again - we should get the same chat.
-  let chatbox2 = yield promiseOpenChat("http://example.com");
-  Assert.strictEqual(chatbox2, chatbox, "got the same chat");
-  Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-
-  chatbox.toggle();
-  is(chatbox.minimized, true, "chat is now minimized");
-  // was no other chat to select, so selected becomes null.
-  is(chatbar.selectedChat, null);
-
-  // We check the content gets an unload event as we close it.
-  let promiseClosed = promiseOneEvent(chatbox.content, "unload", true);
-  chatbox.close();
-  yield promiseClosed;
-});
-
-// In this case we open a chat minimized, then request the same chat again
-// without specifying minimized.  On that second call the chat should open,
-// selected, and no longer minimized.
-add_chat_task(function* testMinimized() {
-  let chatbox = yield promiseOpenChat("http://example.com", "minimized");
-  Assert.strictEqual(chatbox, chatbar.selectedChat);
-  Assert.ok(chatbox.minimized, "chat is minimized");
-  Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-  yield promiseOpenChat("http://example.com");
-  Assert.ok(!chatbox.minimized, false, "chat is no longer minimized");
-});
-
-// open enough chats to overflow the window, then check
-// if the menupopup is visible
-add_chat_task(function* testManyChats() {
-  Assert.ok(chatbar.menupopup.parentNode.collapsed, "popup nub collapsed at start");
-  // we should *never* find a test box that needs more than this to cause
-  // an overflow!
-  let maxToOpen = 20;
-  let numOpened = 0;
-  for (let i = 0; i < maxToOpen; i++) {
-    yield promiseOpenChat("http://example.com#" + i);
-    if (!chatbar.menupopup.parentNode.collapsed) {
-      info("the menu popup appeared");
-      return;
-    }
-  }
-  Assert.ok(false, "We didn't find a collapsed chat after " + maxToOpen + "chats!");
-});
-
-// Check that closeAll works as expected.
-add_chat_task(function* testOpenTwiceCallbacks() {
-  yield promiseOpenChat("http://example.com#1");
-  yield promiseOpenChat("http://example.com#2");
-  yield promiseOpenChat("http://test2.example.com");
-  Assert.equal(numChatsInWindow(window), 3, "should be 3 chats open");
-  Chat.closeAll("http://example.com");
-  Assert.equal(numChatsInWindow(window), 1, "should have closed 2 chats");
-  Chat.closeAll("http://test2.example.com");
-  Assert.equal(numChatsInWindow(window), 0, "should have closed last chat");
-});
-
-// Check that when we open the same chat twice, the callbacks are called back
-// twice.
-add_chat_task(function* testOpenTwiceCallbacks() {
-  yield promiseOpenChatCallback("http://example.com");
-  yield promiseOpenChatCallback("http://example.com");
-});
-
-// Bug 817782 - check chats work in new top-level windows.
-add_chat_task(function* testSecondTopLevelWindow() {
-  const chatUrl = "http://example.com";
-  let secondWindow = OpenBrowserWindow();
-  yield promiseOneEvent(secondWindow, "load");
-  yield promiseOpenChat(chatUrl);
-  // the chat was created - let's make sure it was created in the second window.
-  Assert.equal(numChatsInWindow(window), 0, "main window has no chats");
-  Assert.equal(numChatsInWindow(secondWindow), 1, "second window has 1 chat");
-  secondWindow.close();
-});
-
-// Test that chats are created in the correct window.
-add_chat_task(function* testChatWindowChooser() {
-  let chat = yield promiseOpenChat("http://example.com");
-  Assert.equal(numChatsInWindow(window), 1, "first window has the chat");
-  // create a second window - this will be the "most recent" and will
-  // therefore be the window that hosts the new chat (see bug 835111)
-  let secondWindow = OpenBrowserWindow();
-  yield promiseOneEvent(secondWindow, "load");
-  Assert.equal(numChatsInWindow(secondWindow), 0, "second window starts with no chats");
-  yield promiseOpenChat("http://example.com#2");
-  Assert.equal(numChatsInWindow(secondWindow), 1, "second window now has chats");
-  Assert.equal(numChatsInWindow(window), 1, "first window still has 1 chat");
-  chat.close();
-  Assert.equal(numChatsInWindow(window), 0, "first window now has no chats");
-  // now open another chat - it should still open in the second.
-  yield promiseOpenChat("http://example.com#3");
-  Assert.equal(numChatsInWindow(window), 0, "first window still has no chats");
-  Assert.equal(numChatsInWindow(secondWindow), 2, "second window has both chats");
-
-  // focus the first window, and open yet another chat - it
-  // should open in the first window.
-  window.focus();
-  yield promiseWaitForFocus();
-  chat = yield promiseOpenChat("http://example.com#4");
-  Assert.equal(numChatsInWindow(window), 1, "first window got new chat");
-  chat.close();
-  Assert.equal(numChatsInWindow(window), 0, "first window has no chats");
-
-  let privateWindow = OpenBrowserWindow({private: true});
-  yield promiseOneEvent(privateWindow, "load")
-
-  // open a last chat - the focused window can't accept
-  // chats (it's a private window), so the chat should open
-  // in the window that was selected before. This is known
-  // to be broken on Linux.
-  chat = yield promiseOpenChat("http://example.com#5");
-  let os = Services.appinfo.OS;
-  const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
-  let fn = BROKEN_WM_Z_ORDER ? todo : ok;
-  fn(numChatsInWindow(window) == 1, "first window got the chat");
-  chat.close();
-  privateWindow.close();
-  secondWindow.close();
-});
deleted file mode 100644
--- a/browser/base/content/test/chat/browser_focus.js
+++ /dev/null
@@ -1,226 +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/. */
-
-// Tests the focus functionality.
-
-const CHAT_URL = "https://example.com/browser/browser/base/content/test/chat/chat.html";
-
-// Is the currently opened tab focused?
-function isTabFocused() {
-  let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
-  return Services.focus.focusedWindow == tabb.contentWindow;
-}
-
-// Is the specified chat focused?
-function isChatFocused(chat) {
-  return chat.chatbar._isChatFocused(chat);
-}
-
-let chatbar = document.getElementById("pinnedchats");
-
-function* setUp() {
-  // Note that (probably) due to bug 604289, if a tab is focused but the
-  // focused element is null, our chat windows can "steal" focus.  This is
-  // avoided if we explicitly focus an element in the tab.
-  // So we load a page with an <input> field and focus that before testing.
-  let html = '<input id="theinput"><button id="chat-opener"></button>';
-  let url = "data:text/html;charset=utf-8," + encodeURI(html);
-  let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
-  yield promiseOneEvent(tab.linkedBrowser, "load", true);
-  tab.linkedBrowser.contentDocument.getElementById("theinput").focus();
-  registerCleanupFunction(function() {
-    gBrowser.removeTab(tab);
-  });
-}
-
-// Test default focus - not user input.
-add_chat_task(function* testDefaultFocus() {
-  yield setUp();
-  let chat = yield promiseOpenChat("http://example.com");
-  // we used the default focus behaviour, which means that because this was
-  // not the direct result of user action the chat should not be focused.
-  Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-  Assert.ok(isTabFocused(), "the tab should remain focused.");
-  Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
-});
-
-// Test default focus via user input.
-add_chat_task(function* testDefaultFocus() {
-  yield setUp();
-  let tab = gBrowser.selectedTab;
-  let deferred = Promise.defer();
-  let button = tab.linkedBrowser.contentDocument.getElementById("chat-opener");
-  button.addEventListener("click", function onclick() {
-    button.removeEventListener("click", onclick);
-    promiseOpenChat("http://example.com").then(
-      chat => deferred.resolve(chat)
-    );
-  })
-  // Note we must use synthesizeMouseAtCenter() rather than calling
-  // .click() directly as this causes nsIDOMWindowUtils.isHandlingUserInput
-  // to be true.
-  EventUtils.synthesizeMouseAtCenter(button, {}, button.ownerDocument.defaultView);
-  let chat = yield deferred.promise;
-
-  // we use the default focus behaviour but the chat was opened via user input,
-  // so the chat should be focused.
-  Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-  Assert.ok(!isTabFocused(), "the tab should have lost focus.");
-  Assert.ok(isChatFocused(chat), "the chat should have got focus.");
-});
-
-// We explicitly ask for the chat to be focused.
-add_chat_task(function* testExplicitFocus() {
-  yield setUp();
-  let chat = yield promiseOpenChat("http://example.com", undefined, true);
-  // we use the default focus behaviour, which means that because this was
-  // not the direct result of user action the chat should not be focused.
-  Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-  Assert.ok(!isTabFocused(), "the tab should have lost focus.");
-  Assert.ok(isChatFocused(chat), "the chat should have got focus.");
-});
-
-// Open a minimized chat via default focus behaviour - it will open and not
-// have focus.  Then open the same chat without 'minimized' - it will be
-// restored but should still not have grabbed focus.
-add_chat_task(function* testNoFocusOnAutoRestore() {
-  yield setUp();
-  let chat = yield promiseOpenChat("http://example.com", "minimized");
-  Assert.ok(chat.minimized, "chat is minimized");
-  Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-  Assert.ok(isTabFocused(), "the tab should remain focused.");
-  Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
-  yield promiseOpenChat("http://example.com");
-  Assert.ok(!chat.minimized, "chat should be restored");
-  Assert.ok(isTabFocused(), "the tab should remain focused.");
-  Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
-});
-
-// Here we open a chat, which will not be focused.  Then we minimize it and
-// restore it via a titlebar clock - it should get focus at that point.
-add_chat_task(function* testFocusOnExplicitRestore() {
-  yield setUp();
-  let chat = yield promiseOpenChat("http://example.com");
-  Assert.ok(!chat.minimized, "chat should have been opened restored");
-  Assert.ok(isTabFocused(), "the tab should remain focused.");
-  Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
-  chat.minimized = true;
-  Assert.ok(isTabFocused(), "tab should still be focused");
-  Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
-
-  let promise = promiseOneEvent(chat.contentWindow, "focus");
-  // pretend we clicked on the titlebar
-  chat.onTitlebarClick({button: 0});
-  yield promise; // wait for focus event.
-  Assert.ok(!chat.minimized, "chat should have been restored");
-  Assert.ok(isChatFocused(chat), "chat should be focused");
-  Assert.strictEqual(chat, chatbar.selectedChat, "chat is marked selected");
-});
-
-// Open 2 chats and give 1 focus.  Minimize the focused one - the second
-// should get focus.
-add_chat_task(function* testMinimizeFocused() {
-  yield setUp();
-  let chat1 = yield promiseOpenChat("http://example.com#1");
-  let chat2 = yield promiseOpenChat("http://example.com#2");
-  Assert.equal(numChatsInWindow(window), 2, "2 chats open");
-  Assert.strictEqual(chatbar.selectedChat, chat2, "chat2 is selected");
-  let promise = promiseOneEvent(chat1.contentWindow, "focus");
-  chatbar.selectedChat = chat1;
-  chatbar.focus();
-  yield promise; // wait for chat1 to get focus.
-  Assert.strictEqual(chat1, chatbar.selectedChat, "chat1 is marked selected");
-  Assert.notStrictEqual(chat2, chatbar.selectedChat, "chat2 is not marked selected");
-  promise = promiseOneEvent(chat2.contentWindow, "focus");
-  chat1.minimized = true;
-  yield promise; // wait for chat2 to get focus.
-  Assert.notStrictEqual(chat1, chatbar.selectedChat, "chat1 is not marked selected");
-  Assert.strictEqual(chat2, chatbar.selectedChat, "chat2 is marked selected");
-});
-
-// Open 2 chats, select and focus the second.  Pressing the TAB key should
-// cause focus to move between all elements in our chat window before moving
-// to the next chat window.
-add_chat_task(function* testTab() {
-  yield setUp();
-
-  function sendTabAndWaitForFocus(chat, eltid) {
-    let doc = chat.contentDocument;
-    EventUtils.sendKey("tab");
-    // ideally we would use the 'focus' event here, but that doesn't work
-    // as expected for the iframe - the iframe itself never gets the focus
-    // event (apparently the sub-document etc does.)
-    // So just poll for the correct element getting focus...
-    let deferred = Promise.defer();
-    let tries = 0;
-    let interval = setInterval(function() {
-      if (tries >= 30) {
-        clearInterval(interval);
-        deferred.reject("never got focus");
-        return;
-      }
-      tries ++;
-      let elt = eltid ? doc.getElementById(eltid) : doc.documentElement;
-      if (doc.activeElement == elt) {
-        clearInterval(interval);
-        deferred.resolve();
-      }
-    });
-    return deferred.promise;
-  }
-
-  let chat1 = yield promiseOpenChat(CHAT_URL + "#1");
-  let chat2 = yield promiseOpenChat(CHAT_URL + "#2");
-  chatbar.selectedChat = chat2;
-  let promise = promiseOneEvent(chat2.contentWindow, "focus");
-  chatbar.focus();
-  yield promise;
-
-  // Our chats have 3 focusable elements, so it takes 4 TABs to move
-  // to the new chat.
-  yield sendTabAndWaitForFocus(chat2, "input1");
-  Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "input1",
-               "first input field has focus");
-  Assert.ok(isChatFocused(chat2), "new chat still focused after first tab");
-
-  yield sendTabAndWaitForFocus(chat2, "input2");
-  Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
-  Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "input2",
-               "second input field has focus");
-
-  yield sendTabAndWaitForFocus(chat2, "iframe");
-  Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
-  Assert.equal(chat2.contentDocument.activeElement.getAttribute("id"), "iframe",
-               "iframe has focus");
-
-  // this tab now should move to the next chat, but focus the
-  // document element itself (hence the null eltid)
-  yield sendTabAndWaitForFocus(chat1, null);
-  Assert.ok(isChatFocused(chat1), "first chat is focused");
-});
-
-// Open a chat and focus an element other than the first. Move focus to some
-// other item (the tab itself in this case), then focus the chatbar - the
-// same element that was previously focused should still have focus.
-add_chat_task(function* testFocusedElement() {
-  yield setUp();
-
-  // open a chat with focus requested.
-  let chat = yield promiseOpenChat(CHAT_URL, undefined, true);
-
-  chat.contentDocument.getElementById("input2").focus();
-
-  // set focus to the tab.
-  let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
-  let promise = promiseOneEvent(tabb.contentWindow, "focus");
-  Services.focus.moveFocus(tabb.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
-  yield promise;
-
-  promise = promiseOneEvent(chat.contentWindow, "focus");
-  chatbar.focus();
-  yield promise;
-
-  Assert.equal(chat.contentDocument.activeElement.getAttribute("id"), "input2",
-               "correct input field still has focus");
-});
deleted file mode 100644
--- a/browser/base/content/test/chat/browser_tearoff.js
+++ /dev/null
@@ -1,128 +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/. */
-
-let chatbar = document.getElementById("pinnedchats");
-
-function promiseNewWindowLoaded() {
-  let deferred = Promise.defer();
-  Services.wm.addListener({
-    onWindowTitleChange: function() {},
-    onCloseWindow: function(xulwindow) {},
-    onOpenWindow: function(xulwindow) {
-      var domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                            .getInterface(Components.interfaces.nsIDOMWindow);
-      Services.wm.removeListener(this);
-      // wait for load to ensure the window is ready for us to test
-      domwindow.addEventListener("load", function _load(event) {
-        let doc = domwindow.document;
-        if (event.target != doc)
-            return;
-        domwindow.removeEventListener("load", _load);
-        deferred.resolve(domwindow);
-      });
-    },
-  });
-  return deferred.promise;
-}
-
-add_chat_task(function* testTearoffChat() {
-  let chatbox = yield promiseOpenChat("http://example.com");
-  Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
-
-  let chatDoc = chatbox.contentDocument;
-  let chatTitle = chatDoc.title;
-
-  Assert.equal(chatbox.getAttribute("label"), chatTitle,
-               "the new chatbox should show the title of the chat window");
-
-  // mutate the chat document a bit before we tear it off.
-  let div = chatDoc.createElement("div");
-  div.setAttribute("id", "testdiv");
-  div.setAttribute("test", "1");
-  chatDoc.body.appendChild(div);
-
-  // chatbox is open, lets detach. The new chat window will be caught in
-  // the window watcher below
-  let promise = promiseNewWindowLoaded();
-
-  let swap = document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
-  swap.click();
-
-  // and wait for the new window.
-  let domwindow = yield promise;
-
-  Assert.equal(domwindow.document.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
-  Assert.equal(numChatsInWindow(window), 0, "should be no chats in the chat bar");
-
-  // get the chatbox from the new window.
-  chatbox = domwindow.document.getElementById("chatter")
-  Assert.equal(chatbox.getAttribute("label"), chatTitle, "window should have same title as chat");
-
-  div = chatbox.contentDocument.getElementById("testdiv");
-  Assert.equal(div.getAttribute("test"), "1", "docshell should have been swapped");
-  div.setAttribute("test", "2");
-
-  // swap the window back to the chatbar
-  promise = promiseOneEvent(domwindow, "unload");
-  swap = domwindow.document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
-  swap.click();
-
-  yield promise;
-
-  Assert.equal(numChatsInWindow(window), 1, "chat should be docked back in the window");
-  chatbox = chatbar.selectedChat;
-  Assert.equal(chatbox.getAttribute("label"), chatTitle,
-               "the new chatbox should show the title of the chat window again");
-
-  div = chatbox.contentDocument.getElementById("testdiv");
-  Assert.equal(div.getAttribute("test"), "2", "docshell should have been swapped");
-});
-
-// Similar test but with 2 chats.
-add_chat_task(function* testReattachTwice() {
-  let chatbox1 = yield promiseOpenChat("http://example.com#1");
-  let chatbox2 = yield promiseOpenChat("http://example.com#2");
-  Assert.equal(numChatsInWindow(window), 2, "both chats should be docked in the window");
-
-  info("chatboxes are open, detach from window");
-  let promise = promiseNewWindowLoaded();
-  document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
-  let domwindow1 = yield promise;
-  chatbox1 = domwindow1.document.getElementById("chatter");
-  Assert.equal(numChatsInWindow(window), 1, "only second chat should be docked in the window");
-
-  promise = promiseNewWindowLoaded();
-  document.getAnonymousElementByAttribute(chatbox2, "anonid", "swap").click();
-  let domwindow2 = yield promise;
-  chatbox2 = domwindow2.document.getElementById("chatter");
-  Assert.equal(numChatsInWindow(window), 0, "should be no docked chats");
-
-  promise = promiseOneEvent(domwindow2, "unload");
-  domwindow2.document.getAnonymousElementByAttribute(chatbox2, "anonid", "swap").click();
-  yield promise;
-  Assert.equal(numChatsInWindow(window), 1, "one chat should be docked back in the window");
-
-  promise = promiseOneEvent(domwindow1, "unload");
-  domwindow1.document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
-  yield promise;
-  Assert.equal(numChatsInWindow(window), 2, "both chats should be docked back in the window");
-});
-
-// Check that Chat.closeAll() also closes detached windows.
-add_chat_task(function* testCloseAll() {
-  let chatbox1 = yield promiseOpenChat("http://example.com#1");
-  let chatbox2 = yield promiseOpenChat("http://example.com#2");
-
-  let promise = promiseNewWindowLoaded();
-  document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
-  let domwindow = yield promise;
-  chatbox1 = domwindow.document.getElementById("chatter");
-
-  let promiseWindowUnload = promiseOneEvent(domwindow, "unload");
-
-  Assert.equal(numChatsInWindow(window), 1, "second chat should still be docked");
-  Chat.closeAll("http://example.com");
-  yield promiseWindowUnload;
-  Assert.equal(numChatsInWindow(window), 0, "should be no chats left");
-});
deleted file mode 100644
--- a/browser/base/content/test/chat/chat.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<html>
-  <head>
-    <meta charset="utf-8">
-    <title>test chat window</title>
-  </head>
-  <body>
-    <p>This is a test chat window.</p>
-    <!-- a couple of input fields to help with focus testing -->
-    <input id="input1"/>
-    <input id="input2"/>
-    <!-- an iframe here so this one page generates multiple load events -->
-    <iframe id="iframe" src="data:text/plain:this is an iframe"></iframe>
-  </body>
-</html>
deleted file mode 100644
--- a/browser/base/content/test/chat/head.js
+++ /dev/null
@@ -1,74 +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/. */
-
-// Utility functions for Chat tests.
-
-let Chat = Cu.import("resource:///modules/Chat.jsm", {}).Chat;
-
-function promiseOpenChat(url, mode, focus) {
-  let uri = Services.io.newURI(url, null, null);
-  let origin = uri.prePath;
-  let title = origin;
-  let chatbox = Chat.open(null, origin, title, url, mode, focus);
-  return chatbox.promiseChatLoaded;
-}
-
-// Opens a chat, returns a promise resolved when the chat callback fired.
-function promiseOpenChatCallback(url, mode) {
-  let uri = Services.io.newURI(url, null, null);
-  let origin = uri.prePath;
-  let title = origin;
-  let deferred = Promise.defer();
-  let callback = deferred.resolve;
-  Chat.open(null, origin, title, url, mode, undefined, callback);
-  return deferred.promise;
-}
-
-// Opens a chat, returns the chat window's promise which fires when the chat
-// starts loading.
-function promiseOneEvent(target, eventName, capture) {
-  let deferred = Promise.defer();
-  target.addEventListener(eventName, function handler(event) {
-    target.removeEventListener(eventName, handler, capture);
-    deferred.resolve();
-  }, capture);
-  return deferred.promise;
-}
-
-// Return the number of chats in a browser window.
-function numChatsInWindow(win) {
-  let chatbar = win.document.getElementById("pinnedchats");
-  return chatbar.childElementCount;
-}
-
-function promiseWaitForFocus() {
-  let deferred = Promise.defer();
-  waitForFocus(deferred.resolve);
-  return deferred.promise;
-}
-
-// A simple way to clean up after each test.
-function add_chat_task(genFunction) {
-  add_task(function* () {
-    info("Starting chat test " + genFunction.name);
-    try {
-      yield genFunction();
-    } finally {
-      info("Finished chat test " + genFunction.name + " - cleaning up.");
-      // close all docked chats.
-      while (chatbar.childNodes.length) {
-        chatbar.childNodes[0].close();
-      }
-      // and non-docked chats.
-      let winEnum = Services.wm.getEnumerator("Social:Chat");
-      while (winEnum.hasMoreElements()) {
-        let win = winEnum.getNext();
-        if (win.closed) {
-          continue;
-        }
-        win.close();
-      }
-    }
-  });
-}
--- a/browser/base/content/test/social/browser.ini
+++ b/browser/base/content/test/social/browser.ini
@@ -21,16 +21,17 @@ support-files =
   social_sidebar.html
   social_sidebar_empty.html
   social_window.html
   social_worker.js
   unchecked.jpg
 
 [browser_addons.js]
 [browser_blocklist.js]
+[browser_chat_tearoff.js]
 [browser_defaults.js]
 [browser_share.js]
 [browser_social_activation.js]
 [browser_social_chatwindow.js]
 [browser_social_chatwindow_resize.js]
 [browser_social_chatwindowfocus.js]
 [browser_social_errorPage.js]
 [browser_social_flyout.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social/browser_chat_tearoff.js
@@ -0,0 +1,308 @@
+/* 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/. */
+
+function test() {
+  requestLongerTimeout(2); // only debug builds seem to need more time...
+  waitForExplicitFinish();
+
+  let manifest = { // normal provider
+    name: "provider 1",
+    origin: "https://example.com",
+    sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+    workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+    iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
+  };
+
+  let postSubTest = function(cb) {
+    let chats = document.getElementById("pinnedchats");
+    ok(chats.children.length == 0, "no chatty children left behind");
+    cb();
+  };
+  runSocialTestWithProvider(manifest, function (finishcb) {
+    SocialSidebar.show();
+    ok(SocialSidebar.provider, "sidebar provider exists");
+    runSocialTests(tests, undefined, postSubTest, function() {
+      finishcb();
+    });
+  });
+}
+
+var tests = {
+  testTearoffChat: function(next) {
+    let chats = document.getElementById("pinnedchats");
+    let chatTitle;
+    let port = SocialSidebar.provider.getWorkerPort();
+    ok(port, "provider has a port");
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "got-sidebar-message":
+          port.postMessage({topic: "test-chatbox-open"});
+          break;
+        case "got-chatbox-visibility":
+          // chatbox is open, lets detach. The new chat window will be caught in
+          // the window watcher below
+          let doc = chats.selectedChat.contentDocument;
+          // This message is (sometimes!) received a second time
+          // before we start our tests from the onCloseWindow
+          // callback.
+          if (doc.location == "about:blank")
+            return;
+          chatTitle = doc.title;
+          ok(chats.selectedChat.getAttribute("label") == chatTitle,
+             "the new chatbox should show the title of the chat window");
+          let div = doc.createElement("div");
+          div.setAttribute("id", "testdiv");
+          div.setAttribute("test", "1");
+          doc.body.appendChild(div);
+          let swap = document.getAnonymousElementByAttribute(chats.selectedChat, "anonid", "swap");
+          swap.click();
+          port.close();
+          break;
+        case "got-chatbox-message":
+          ok(true, "got chatbox message");
+          ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result);
+          chats.selectedChat.toggle();
+          break;
+      }
+    }
+
+    Services.wm.addListener({
+      onWindowTitleChange: function() {},
+      onCloseWindow: function(xulwindow) {},
+      onOpenWindow: function(xulwindow) {
+        var domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                              .getInterface(Components.interfaces.nsIDOMWindow);
+        Services.wm.removeListener(this);
+        // wait for load to ensure the window is ready for us to test
+        domwindow.addEventListener("load", function _load(event) {
+          let doc = domwindow.document;
+          if (event.target != doc)
+              return;
+
+          domwindow.removeEventListener("load", _load, false);
+
+          domwindow.addEventListener("unload", function _close(event) {
+            if (event.target != doc)
+              return;
+            domwindow.removeEventListener("unload", _close, false);
+            info("window has been closed");
+            waitForCondition(function() {
+              return chats.selectedChat && chats.selectedChat.contentDocument &&
+                     chats.selectedChat.contentDocument.readyState == "complete";
+            },function () {
+              ok(chats.selectedChat, "should have a chatbox in our window again");
+              ok(chats.selectedChat.getAttribute("label") == chatTitle,
+                 "the new chatbox should show the title of the chat window again");
+              let testdiv = chats.selectedChat.contentDocument.getElementById("testdiv");
+              is(testdiv.getAttribute("test"), "2", "docshell should have been swapped");
+              chats.selectedChat.close();
+              waitForCondition(function() {
+                return chats.children.length == 0;
+              },function () {
+                next();
+              });
+            });
+          }, false);
+
+          is(doc.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
+          // window is loaded, but the docswap does not happen until after load,
+          // and we have no event to wait on, so we'll wait for document state
+          // to be ready
+          let chatbox = doc.getElementById("chatter");
+          waitForCondition(function() {
+            return chats.selectedChat == null &&
+                   chatbox.contentDocument &&
+                   chatbox.contentDocument.readyState == "complete";
+          },function() {
+            ok(chatbox.getAttribute("label") == chatTitle,
+               "detached window should show the title of the chat window");
+            let testdiv = chatbox.contentDocument.getElementById("testdiv");
+            is(testdiv.getAttribute("test"), "1", "docshell should have been swapped");
+            testdiv.setAttribute("test", "2");
+            // swap the window back to the chatbar
+            let swap = doc.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
+            swap.click();
+          }, domwindow);
+        }, false);
+      }
+    });
+
+    port.postMessage({topic: "test-init", data: { id: 1 }});
+  },
+
+  testCloseOnLogout: function(next) {
+    let chats = document.getElementById("pinnedchats");
+    const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
+    let port = SocialSidebar.provider.getWorkerPort();
+    ok(port, "provider has a port");
+    port.postMessage({topic: "test-init"});
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "got-chatbox-visibility":
+          // chatbox is open, lets detach. The new chat window will be caught in
+          // the window watcher below
+          let doc = chats.selectedChat.contentDocument;
+          // This message is (sometimes!) received a second time
+          // before we start our tests from the onCloseWindow
+          // callback.
+          if (doc.location == "about:blank")
+            return;
+          info("chatbox is open, detach from window");
+          let swap = document.getAnonymousElementByAttribute(chats.selectedChat, "anonid", "swap");
+          swap.click();
+          break;
+      }
+    }
+
+    Services.wm.addListener({
+      onWindowTitleChange: function() {},
+      onCloseWindow: function(xulwindow) {},
+      onOpenWindow: function(xulwindow) {
+        let domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                              .getInterface(Components.interfaces.nsIDOMWindow);
+        Services.wm.removeListener(this);
+        // wait for load to ensure the window is ready for us to test, make sure
+        // we're not getting called for about:blank
+        domwindow.addEventListener("load", function _load(event) {
+          let doc = domwindow.document;
+          if (event.target != doc)
+              return;
+          domwindow.removeEventListener("load", _load, false);
+
+          domwindow.addEventListener("unload", function _close(event) {
+            if (event.target != doc)
+              return;
+            domwindow.removeEventListener("unload", _close, false);
+            ok(true, "window has been closed");
+            next();
+          }, false);
+
+          is(doc.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
+          // window is loaded, but the docswap does not happen until after load,
+          // and we have no event to wait on, so we'll wait for document state
+          // to be ready
+          let chatbox = doc.getElementById("chatter");
+          waitForCondition(function() {
+            return chats.children.length == 0 &&
+                   chatbox.contentDocument &&
+                   chatbox.contentDocument.readyState == "complete";
+          },function() {
+            // logout, we should get unload next
+            port.postMessage({topic: "test-logout"});
+            port.close();
+          }, domwindow);
+
+        }, false);
+      }
+    });
+
+    port.postMessage({topic: "test-worker-chat", data: chatUrl});
+  },
+
+  testReattachTwice: function(next) {
+    let chats = document.getElementById("pinnedchats");
+    const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
+    let chatBoxCount = 0, reattachCount = 0;
+    let port = SocialSidebar.provider.getWorkerPort();
+    ok(port, "provider has a port");
+    port.postMessage({topic: "test-init"});
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "got-chatbox-visibility":
+          // chatbox is open, lets detach. The new chat window will be caught in
+          // the window watcher below
+          let doc = chats.selectedChat.contentDocument;
+          // This message is (sometimes!) received a second time
+          // before we start our tests from the onCloseWindow
+          // callback.
+          if (doc.location == "about:blank")
+            return;
+          if (++chatBoxCount != 2) {
+            // open the second chat window
+            port.postMessage({topic: "test-worker-chat", data: chatUrl + "?id=2"});
+            return;
+          }
+          info("chatbox is open, detach from window");
+          let chat1 = chats.firstChild;
+          let chat2 = chat1.nextSibling;
+          document.getAnonymousElementByAttribute(chat1, "anonid", "swap").click();
+          document.getAnonymousElementByAttribute(chat2, "anonid", "swap").click();
+          break;
+      }
+    };
+
+    let firstChatWindowDoc;
+    Services.wm.addListener({
+      onWindowTitleChange: function() {},
+      onCloseWindow: function(xulwindow) {},
+      onOpenWindow: function(xulwindow) {
+        let listener = this;
+        let domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                                 .getInterface(Components.interfaces.nsIDOMWindow);
+        // wait for load to ensure the window is ready for us to test, make sure
+        // we're not getting called for about:blank
+        domwindow.addEventListener("load", function _load(event) {
+          let doc = domwindow.document;
+          if (event.target != doc)
+            return;
+          domwindow.removeEventListener("load", _load, false);
+
+          domwindow.addEventListener("unload", function _close(event) {
+            if (event.target != doc)
+              return;
+            domwindow.removeEventListener("unload", _close, false);
+            ok(true, "window has been closed");
+            waitForCondition(function() {
+              return chats.selectedChat && chats.selectedChat.contentDocument &&
+                     chats.selectedChat.contentDocument.readyState == "complete";
+            }, function () {
+              ++reattachCount;
+              if (reattachCount == 1) {
+                info("reattaching second chat window");
+                let chatbox = firstChatWindowDoc.getElementById("chatter");
+                firstChatWindowDoc.getAnonymousElementByAttribute(chatbox, "anonid", "swap").click();
+                firstChatWindowDoc = null;
+              }
+              else if (reattachCount == 2) {
+                is(chats.children.length, 2, "both chat windows should be reattached");
+                chats.removeAll();
+                waitForCondition(() => chats.children.length == 0, function () {
+                  info("no chat window left");
+                  is(chats.chatboxForURL.size, 0, "chatboxForURL map should be empty");
+                  next();
+                });
+              }
+            }, "waited too long for the window to reattach");
+          }, false);
+
+          is(doc.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
+          if (!firstChatWindowDoc) {
+            firstChatWindowDoc = doc;
+            return;
+          }
+          Services.wm.removeListener(listener);
+
+          // window is loaded, but the docswap does not happen until after load,
+          // and we have no event to wait on, so we'll wait for document state
+          // to be ready
+          let chatbox = doc.getElementById("chatter");
+          waitForCondition(function() {
+            return chats.children.length == 0 &&
+                   chatbox.contentDocument &&
+                   chatbox.contentDocument.readyState == "complete";
+          },function() {
+            info("reattaching chat window");
+            doc.getAnonymousElementByAttribute(chatbox, "anonid", "swap").click();
+          }, "waited too long for the chat window to be detached");
+
+        }, false);
+      }
+    });
+
+    port.postMessage({topic: "test-worker-chat", data: chatUrl + "?id=1"});
+  }
+};
--- a/browser/base/content/test/social/browser_social_chatwindow.js
+++ b/browser/base/content/test/social/browser_social_chatwindow.js
@@ -39,20 +39,16 @@ function openChat(provider, callback) {
     }
   }
   let url = chatUrl + "?" + (chatId++);
   port.postMessage({topic: "test-init"});
   port.postMessage({topic: "test-worker-chat", data: url});
   gURLsNotRemembered.push(url);
 }
 
-function windowHasChats(win) {
-  return !!getChatBar().firstElementChild;
-}
-
 function test() {
   requestLongerTimeout(2); // only debug builds seem to need more time...
   waitForExplicitFinish();
 
   let oldwidth = window.outerWidth; // we futz with these, so we restore them
   let oldleft = window.screenX;
   window.moveTo(0, window.screenY)
   let postSubTest = function(cb) {
@@ -106,16 +102,95 @@ var tests = {
           ok(true, "got chatbox message");
           ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result);
           chats.selectedChat.toggle();
           break;
       }
     }
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
+  testOpenMinimized: function(next) {
+    // In this case the sidebar opens a chat (without specifying minimized).
+    // We then minimize it and have the sidebar reopen the chat (again without
+    // minimized).  On that second call the chat should open and no longer
+    // be minimized.
+    let chats = document.getElementById("pinnedchats");
+    let port = SocialSidebar.provider.getWorkerPort();
+    let seen_opened = false;
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "test-init-done":
+          port.postMessage({topic: "test-chatbox-open"});
+          break;
+        case "chatbox-opened":
+          is(e.data.result, "ok", "the sidebar says it got a chatbox");
+          if (!seen_opened) {
+            // first time we got the opened message, so minimize the chat then
+            // re-request the same chat to be opened - we should get the
+            // message again and the chat should be restored.
+            ok(!chats.selectedChat.minimized, "chat not initially minimized")
+            chats.selectedChat.minimized = true
+            seen_opened = true;
+            port.postMessage({topic: "test-chatbox-open"});
+          } else {
+            // This is the second time we've seen this message - there should
+            // be exactly 1 chat open and it should no longer be minimized.
+            let chats = document.getElementById("pinnedchats");
+            ok(!chats.selectedChat.minimized, "chat no longer minimized")
+            chats.selectedChat.close();
+            is(chats.selectedChat, null, "should only have been one chat open");
+            port.close();
+            next();
+          }
+      }
+    }
+    port.postMessage({topic: "test-init", data: { id: 1 }});
+  },
+  testManyChats: function(next) {
+    // open enough chats to overflow the window, then check
+    // if the menupopup is visible
+    let port = SocialSidebar.provider.getWorkerPort();
+    let chats = document.getElementById("pinnedchats");
+    ok(port, "provider has a port");
+    ok(chats.menupopup.parentNode.collapsed, "popup nub collapsed at start");
+    port.postMessage({topic: "test-init"});
+    // we should *never* find a test box that needs more than this to cause
+    // an overflow!
+    let maxToOpen = 20;
+    let numOpened = 0;
+    let maybeOpenAnother = function() {
+      if (numOpened++ >= maxToOpen) {
+        ok(false, "We didn't find a collapsed chat after " + maxToOpen + "chats!");
+        closeAllChats();
+        next();
+      }
+      port.postMessage({topic: "test-chatbox-open", data: { id: numOpened }});
+    }
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "got-chatbox-message":
+          if (!chats.menupopup.parentNode.collapsed) {
+            maybeOpenAnother();
+            break;
+          }
+          ok(true, "popup nub became visible");
+          // close our chats now
+          while (chats.selectedChat) {
+            chats.selectedChat.close();
+          }
+          ok(!chats.selectedChat, "chats are all closed");
+          port.close();
+          next();
+          break;
+      }
+    }
+    maybeOpenAnother();
+  },
   testWorkerChatWindow: function(next) {
     const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
     let chats = document.getElementById("pinnedchats");
     let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
@@ -159,20 +234,71 @@ var tests = {
           ok(!chat.parentNode, "chat is now closed");
           port.close();
           next();
           break;
       }
     }
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
+  testSameChatCallbacks: function(next) {
+    let chats = document.getElementById("pinnedchats");
+    let port = SocialSidebar.provider.getWorkerPort();
+    let seen_opened = false;
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "test-init-done":
+          port.postMessage({topic: "test-chatbox-open"});
+          break;
+        case "chatbox-opened":
+          is(e.data.result, "ok", "the sidebar says it got a chatbox");
+          if (seen_opened) {
+            // This is the second time we've seen this message - there should
+            // be exactly 1 chat open.
+            let chats = document.getElementById("pinnedchats");
+            chats.selectedChat.close();
+            is(chats.selectedChat, null, "should only have been one chat open");
+            port.close();
+            next();
+          } else {
+            // first time we got the opened message, so re-request the same
+            // chat to be opened - we should get the message again.
+            seen_opened = true;
+            port.postMessage({topic: "test-chatbox-open"});
+          }
+      }
+    }
+    port.postMessage({topic: "test-init", data: { id: 1 }});
+  },
+
+  // check removeAll does the right thing
+  testRemoveAll: function(next, mode) {
+    let port = SocialSidebar.provider.getWorkerPort();
+    port.postMessage({topic: "test-init"});
+    get3ChatsForCollapsing(mode || "normal", function() {
+      let chatbar = window.SocialChatBar.chatbar;
+      chatbar.removeAll();
+      // should be no evidence of any chats left.
+      is(chatbar.childNodes.length, 0, "should be no chats left");
+      checkPopup();
+      is(chatbar.selectedChat, null, "nothing should be selected");
+      is(chatbar.chatboxForURL.size, 0, "chatboxForURL map should be empty");
+      port.close();
+      next();
+    });
+  },
+
+  testRemoveAllMinimized: function(next) {
+    this.testRemoveAll(next, "minimized");
+  },
 
   // Check what happens when you close the only visible chat.
   testCloseOnlyVisible: function(next) {
-    let chatbar = getChatBar();
+    let chatbar = window.SocialChatBar.chatbar;
     let chatWidth = undefined;
     let num = 0;
     is(chatbar.childNodes.length, 0, "chatbar starting empty");
     is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
 
     makeChat("normal", "first chat", function() {
       // got the first one.
       checkPopup();
@@ -200,26 +326,81 @@ var tests = {
       });
     });
   },
 
   testShowWhenCollapsed: function(next) {
     let port = SocialSidebar.provider.getWorkerPort();
     port.postMessage({topic: "test-init"});
     get3ChatsForCollapsing("normal", function(first, second, third) {
-      let chatbar = getChatBar();
+      let chatbar = window.SocialChatBar.chatbar;
       chatbar.showChat(first);
       ok(!first.collapsed, "first should no longer be collapsed");
       ok(second.collapsed ||  third.collapsed, false, "one of the others should be collapsed");
       closeAllChats();
       port.close();
       next();
     });
   },
 
+  testActivity: function(next) {
+    let port = SocialSidebar.provider.getWorkerPort();
+    port.postMessage({topic: "test-init"});
+    get3ChatsForCollapsing("normal", function(first, second, third) {
+      let chatbar = window.SocialChatBar.chatbar;
+      is(chatbar.selectedChat, third, "third chat should be selected");
+      ok(!chatbar.selectedChat.hasAttribute("activity"), "third chat should have no activity");
+      // send an activity message to the second.
+      ok(!second.hasAttribute("activity"), "second chat should have no activity");
+      let chat2 = second.content;
+      let evt = chat2.contentDocument.createEvent("CustomEvent");
+      evt.initCustomEvent("socialChatActivity", true, true, {});
+      chat2.contentDocument.documentElement.dispatchEvent(evt);
+      // second should have activity.
+      ok(second.hasAttribute("activity"), "second chat should now have activity");
+      // select the second - it should lose "activity"
+      chatbar.selectedChat = second;
+      ok(!second.hasAttribute("activity"), "second chat should no longer have activity");
+      // Now try the first - it is collapsed, so the 'nub' also gets activity attr.
+      ok(!first.hasAttribute("activity"), "first chat should have no activity");
+      let chat1 = first.content;
+      let evt = chat1.contentDocument.createEvent("CustomEvent");
+      evt.initCustomEvent("socialChatActivity", true, true, {});
+      chat1.contentDocument.documentElement.dispatchEvent(evt);
+      ok(first.hasAttribute("activity"), "first chat should now have activity");
+      ok(chatbar.nub.hasAttribute("activity"), "nub should also have activity");
+      // first is collapsed, so use openChat to get it.
+      chatbar.openChat(SocialSidebar.provider, first.getAttribute("src"));
+      ok(!first.hasAttribute("activity"), "first chat should no longer have activity");
+      // The nub should lose the activity flag here too
+      todo(!chatbar.nub.hasAttribute("activity"), "Bug 806266 - nub should no longer have activity");
+      // TODO: tests for bug 806266 should arrange to have 2 chats collapsed
+      // then open them checking the nub is updated correctly.
+      // Now we will go and change the embedded browser in the second chat and
+      // ensure the activity magic still works (ie, check that the unload for
+      // the browser didn't cause our event handlers to be removed.)
+      ok(!second.hasAttribute("activity"), "second chat should have no activity");
+      let subiframe = chat2.contentDocument.getElementById("iframe");
+      subiframe.contentWindow.addEventListener("unload", function subunload() {
+        subiframe.contentWindow.removeEventListener("unload", subunload);
+        // ensure all other unload listeners have fired.
+        executeSoon(function() {
+          let evt = chat2.contentDocument.createEvent("CustomEvent");
+          evt.initCustomEvent("socialChatActivity", true, true, {});
+          chat2.contentDocument.documentElement.dispatchEvent(evt);
+          ok(second.hasAttribute("activity"), "second chat still has activity after unloading sub-iframe");
+          closeAllChats();
+          port.close();
+          next();
+        })
+      })
+      subiframe.setAttribute("src", "data:text/plain:new location for iframe");
+    });
+  },
+
   testOnlyOneCallback: function(next) {
     let chats = document.getElementById("pinnedchats");
     let port = SocialSidebar.provider.getWorkerPort();
     let numOpened = 0;
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
@@ -227,41 +408,121 @@ var tests = {
           break;
         case "chatbox-opened":
           numOpened += 1;
           port.postMessage({topic: "ping"});
           break;
         case "pong":
           executeSoon(function() {
             is(numOpened, 1, "only got one open message");
-            chats.selectedChat.close();
+            chats.removeAll();
             port.close();
             next();
           });
       }
     }
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
 
+  testSecondTopLevelWindow: function(next) {
+    // Bug 817782 - check chats work in new top-level windows.
+    const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
+    let port = SocialSidebar.provider.getWorkerPort();
+    let secondWindow;
+    port.onmessage = function(e) {
+      if (e.data.topic == "test-init-done") {
+        secondWindow = OpenBrowserWindow();
+        secondWindow.addEventListener("load", function loadListener() {
+          secondWindow.removeEventListener("load", loadListener);
+          port.postMessage({topic: "test-worker-chat", data: chatUrl});
+        });
+      } else if (e.data.topic == "got-chatbox-message") {
+        // the chat was created - let's make sure it was created in the second window.
+        is(secondWindow.SocialChatBar.chatbar.childElementCount, 1);
+        secondWindow.close();
+        next();
+      }
+    }
+    port.postMessage({topic: "test-init"});
+  },
+
+  testChatWindowChooser: function(next) {
+    // Tests that when a worker creates a chat, it is opened in the correct
+    // window.
+    // open a chat (it will open in the main window)
+    ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
+    openChat(SocialSidebar.provider, function() {
+      ok(window.SocialChatBar.hasChats, "first window has the chat");
+      // create a second window - this will be the "most recent" and will
+      // therefore be the window that hosts the new chat (see bug 835111)
+      let secondWindow = OpenBrowserWindow();
+      secondWindow.addEventListener("load", function loadListener() {
+        secondWindow.removeEventListener("load", loadListener);
+        ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
+        openChat(SocialSidebar.provider, function() {
+          ok(secondWindow.SocialChatBar.hasChats, "second window now has chats");
+          is(window.SocialChatBar.chatbar.childElementCount, 1, "first window still has 1 chat");
+          window.SocialChatBar.chatbar.removeAll();
+          // now open another chat - it should still open in the second.
+          openChat(SocialSidebar.provider, function() {
+            ok(!window.SocialChatBar.hasChats, "first window has no chats");
+            ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
+
+            // focus the first window, and open yet another chat - it
+            // should open in the first window.
+            waitForFocus(function() {
+              openChat(SocialSidebar.provider, function() {
+                ok(window.SocialChatBar.hasChats, "first window has chats");
+                window.SocialChatBar.chatbar.removeAll();
+                ok(!window.SocialChatBar.hasChats, "first window has no chats");
+
+                let privateWindow = OpenBrowserWindow({private: true});
+                privateWindow.addEventListener("load", function loadListener() {
+                  privateWindow.removeEventListener("load", loadListener);
+
+                  // open a last chat - the focused window can't accept
+                  // chats (it's a private window), so the chat should open
+                  // in the window that was selected before. This is known
+                  // to be broken on Linux.
+                  openChat(SocialSidebar.provider, function() {
+                    let os = Services.appinfo.OS;
+                    const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
+                    let fn = BROKEN_WM_Z_ORDER ? todo : ok;
+                    fn(window.SocialChatBar.hasChats, "first window has a chat");
+                    window.SocialChatBar.chatbar.removeAll();
+
+                    privateWindow.close();
+                    secondWindow.close();
+                    next();
+                  });
+                });
+              });
+            });
+            window.focus();
+          });
+        });
+      })
+    });
+  },
   testMultipleProviderChat: function(next) {
     // test incomming chats from all providers
     openChat(Social.providers[0], function() {
       openChat(Social.providers[1], function() {
         openChat(Social.providers[2], function() {
           let chats = document.getElementById("pinnedchats");
           waitForCondition(function() chats.children.length == Social.providers.length,
             function() {
               ok(true, "one chat window per provider opened");
               // test logout of a single provider
               let provider = Social.providers[2];
               let port = provider.getWorkerPort();
               port.postMessage({topic: "test-logout"});
               waitForCondition(function() chats.children.length == Social.providers.length - 1,
                 function() {
-                  closeAllChats();
+                  chats.removeAll();
                   waitForCondition(function() chats.children.length == 0,
                                    function() {
                                     ok(!chats.selectedChat, "multiprovider chats are all closed");
                                     port.close();
                                     next();
                                    },
                                    "chat windows didn't close");
                 },
--- a/browser/base/content/test/social/browser_social_chatwindowfocus.js
+++ b/browser/base/content/test/social/browser_social_chatwindowfocus.js
@@ -4,17 +4,17 @@
 
 // Is the currently opened tab focused?
 function isTabFocused() {
   let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
   return Services.focus.focusedWindow == tabb.contentWindow;
 }
 
 function isChatFocused(chat) {
-  return getChatBar()._isChatFocused(chat);
+  return SocialChatBar.chatbar._isChatFocused(chat);
 }
 
 function openChatViaUser() {
   let sidebarDoc = document.getElementById("social-sidebar-browser").contentDocument;
   let button = sidebarDoc.getElementById("chat-opener");
   // Note we must use synthesizeMouseAtCenter() rather than calling
   // .click() directly as this causes nsIDOMWindowUtils.isHandlingUserInput
   // to be true.
@@ -27,31 +27,30 @@ function openChatViaSidebarMessage(port,
       callback();
   }
   port.postMessage({topic: "test-chatbox-open", data: data});
 }
 
 function openChatViaWorkerMessage(port, data, callback) {
   // sadly there is no message coming back to tell us when the chat has
   // been opened, so we wait until one appears.
-  let chatbar = getChatBar();
+  let chatbar = SocialChatBar.chatbar;
   let numExpected = chatbar.childElementCount + 1;
   port.postMessage({topic: "test-worker-chat", data: data});
   waitForCondition(function() chatbar.childElementCount == numExpected,
                    function() {
                       // so the child has been added, but we don't know if it
                       // has been intialized - re-request it and the callback
                       // means it's done.  Minimized, same as the worker.
-                      chatbar.openChat(SocialSidebar.provider.origin,
-                                       SocialSidebar.provider.name,
-                                       data,
-                                       "minimized",
-                                       function() {
-                                          callback();
-                                       });
+                      SocialChatBar.openChat(SocialSidebar.provider,
+                                             data,
+                                             function() {
+                                                callback();
+                                             },
+                                             "minimized");
                    },
                    "No new chat appeared");
 }
 
 
 let isSidebarLoaded = false;
 
 function startTestAndWaitForSidebar(callback) {
@@ -105,17 +104,17 @@ function test() {
     let preSubTest = function(cb) {
       // XXX - when bug 604289 is fixed it should be possible to just do:
       // tab.linkedBrowser.contentWindow.focus()
       // but instead we must do:
       tab.linkedBrowser.contentDocument.getElementById("theinput").focus();
       waitForCondition(function() isTabFocused(), cb, "tab should have focus");
     }
     let postSubTest = function(cb) {
-      closeAllChats();
+      window.SocialChatBar.chatbar.removeAll();
       cb();
     }
     // and run the tests.
     runSocialTestWithProvider(manifest, function (finishcb) {
       SocialSidebar.show();
       runSocialTests(tests, preSubTest, postSubTest, function () {
         finishcb();
       });
@@ -128,46 +127,235 @@ function test() {
 }
 
 var tests = {
   // In this test the worker asks the sidebar to open a chat.  As that means
   // we aren't handling user-input we will not focus the chatbar.
   // Then we do it again - should still not be focused.
   // Then we perform a user-initiated request - it should get focus.
   testNoFocusWhenViaWorker: function(next) {
-    let chatbar = getChatBar();
     startTestAndWaitForSidebar(function(port) {
       openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
         ok(true, "got chatbox message");
-        is(chatbar.childElementCount, 1, "exactly 1 chat open");
+        is(SocialChatBar.chatbar.childElementCount, 1, "exactly 1 chat open");
         ok(isTabFocused(), "tab should still be focused");
         // re-request the same chat via a message.
         openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
-          is(chatbar.childElementCount, 1, "still exactly 1 chat open");
+          is(SocialChatBar.chatbar.childElementCount, 1, "still exactly 1 chat open");
           ok(isTabFocused(), "tab should still be focused");
           // re-request the same chat via user event.
           openChatViaUser();
-          waitForCondition(function() isChatFocused(chatbar.selectedChat),
+          waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
                            function() {
-            is(chatbar.childElementCount, 1, "still exactly 1 chat open");
-            is(chatbar.selectedChat, chatbar.firstElementChild, "chat should be selected");
+            is(SocialChatBar.chatbar.childElementCount, 1, "still exactly 1 chat open");
+            is(SocialChatBar.chatbar.selectedChat, SocialChatBar.chatbar.firstElementChild, "chat should be selected");
             next();
           }, "chat should be focused");
         });
       });
     });
   },
 
   // In this test we arrange for the sidebar to open the chat via a simulated
   // click.  This should cause the new chat to be opened and focused.
   testFocusWhenViaUser: function(next) {
     startTestAndWaitForSidebar(function(port) {
-      let chatbar = getChatBar();
       openChatViaUser();
-      ok(chatbar.firstElementChild, "chat opened");
-      waitForCondition(function() isChatFocused(chatbar.selectedChat),
+      ok(SocialChatBar.chatbar.firstElementChild, "chat opened");
+      waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
                        function() {
-        is(chatbar.selectedChat, chatbar.firstElementChild, "chat is selected");
+        is(SocialChatBar.chatbar.selectedChat, SocialChatBar.chatbar.firstElementChild, "chat is selected");
         next();
       }, "chat should be focused");
     });
   },
+
+  // Open a chat via the worker - it will open and not have focus.
+  // Then open the same chat via a sidebar message - it will be restored but
+  // should still not have grabbed focus.
+  testNoFocusOnAutoRestore: function(next) {
+    const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html?id=1";
+    let chatbar = SocialChatBar.chatbar;
+    startTestAndWaitForSidebar(function(port) {
+      openChatViaWorkerMessage(port, chatUrl, function() {
+        is(chatbar.childElementCount, 1, "exactly 1 chat open");
+        // bug 865086 opening minimized still sets the window as selected
+        todo(chatbar.selectedChat != chatbar.firstElementChild, "chat is not selected");
+        ok(isTabFocused(), "tab should be focused");
+        openChatViaSidebarMessage(port, {stealFocus: 1, id: 1}, function() {
+          is(chatbar.childElementCount, 1, "still 1 chat open");
+          ok(!chatbar.firstElementChild.minimized, "chat no longer minimized");
+          // bug 865086 because we marked it selected on open, it still is
+          todo(chatbar.selectedChat != chatbar.firstElementChild, "chat is not selected");
+          ok(isTabFocused(), "tab should still be focused");
+          next();
+        });
+      });
+    });
+  },
+
+  // Here we open a chat, which will not be focused.  Then we minimize it and
+  // restore it via a titlebar clock - it should get focus at that point.
+  testFocusOnExplicitRestore: function(next) {
+    startTestAndWaitForSidebar(function(port) {
+      openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
+        ok(true, "got chatbox message");
+        ok(isTabFocused(), "tab should still be focused");
+        let chatbox = SocialChatBar.chatbar.firstElementChild;
+        ok(chatbox, "chat opened");
+        chatbox.minimized = true;
+        ok(isTabFocused(), "tab should still be focused");
+        // pretend we clicked on the titlebar
+        chatbox.onTitlebarClick({button: 0});
+        waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
+                         function() {
+          ok(!chatbox.minimized, "chat should have been restored");
+          ok(isChatFocused(chatbox), "chat should be focused");
+          is(chatbox, SocialChatBar.chatbar.selectedChat, "chat is marked selected");
+          next();
+        }, "chat should have focus");
+      });
+    });
+  },
+
+  // Open 2 chats and give 1 focus.  Minimize the focused one - the second
+  // should get focus.
+  testMinimizeFocused: function(next) {
+    let chatbar = SocialChatBar.chatbar;
+    startTestAndWaitForSidebar(function(port) {
+      openChatViaSidebarMessage(port, {stealFocus: 1, id: 1}, function() {
+        let chat1 = chatbar.firstElementChild;
+        openChatViaSidebarMessage(port, {stealFocus: 1, id: 2}, function() {
+          is(chatbar.childElementCount, 2, "exactly 2 chats open");
+          let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
+          chatbar.selectedChat = chat1;
+          chatbar.focus();
+          waitForCondition(function() isChatFocused(chat1),
+                           function() {
+            is(chat1, SocialChatBar.chatbar.selectedChat, "chat1 is marked selected");
+            isnot(chat2, SocialChatBar.chatbar.selectedChat, "chat2 is not marked selected");
+            chat1.minimized = true;
+            waitForCondition(function() isChatFocused(chat2),
+                             function() {
+              // minimizing the chat with focus should give it to another.
+              isnot(chat1, SocialChatBar.chatbar.selectedChat, "chat1 is not marked selected");
+              is(chat2, SocialChatBar.chatbar.selectedChat, "chat2 is marked selected");
+              next();
+            }, "chat2 should have focus");
+          }, "chat1 should have focus");
+        });
+      });
+    });
+  },
+
+  // Open 2 chats, select (but not focus) one, then re-request it be
+  // opened via a message.  Focus should not move.
+  testReopenNonFocused: function(next) {
+    let chatbar = SocialChatBar.chatbar;
+    startTestAndWaitForSidebar(function(port) {
+      openChatViaSidebarMessage(port, {id: 1}, function() {
+        let chat1 = chatbar.firstElementChild;
+        openChatViaSidebarMessage(port, {id: 2}, function() {
+          let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
+          chatbar.selectedChat = chat2;
+          // tab still has focus
+          ok(isTabFocused(), "tab should still be focused");
+          // re-request the first.
+          openChatViaSidebarMessage(port, {id: 1}, function() {
+            is(chatbar.selectedChat, chat1, "chat1 now selected");
+            ok(isTabFocused(), "tab should still be focused");
+            next();
+          });
+        });
+      });
+    });
+  },
+
+  // Open 2 chats, select and focus the second.  Pressing the TAB key should
+  // cause focus to move between all elements in our chat window before moving
+  // to the next chat window.
+  testTab: function(next) {
+    function sendTabAndWaitForFocus(chat, eltid, callback) {
+      // ideally we would use the 'focus' event here, but that doesn't work
+      // as expected for the iframe - the iframe itself never gets the focus
+      // event (apparently the sub-document etc does.)
+      // So just poll for the correct element getting focus...
+      let doc = chat.contentDocument;
+      EventUtils.sendKey("tab");
+      waitForCondition(function() {
+        let elt = eltid ? doc.getElementById(eltid) : doc.documentElement;
+        return doc.activeElement == elt;
+      }, callback, "element " + eltid + " never got focus");
+    }
+
+    let chatbar = SocialChatBar.chatbar;
+    startTestAndWaitForSidebar(function(port) {
+      openChatViaSidebarMessage(port, {id: 1}, function() {
+        let chat1 = chatbar.firstElementChild;
+        openChatViaSidebarMessage(port, {id: 2}, function() {
+          let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
+          chatbar.selectedChat = chat2;
+          chatbar.focus();
+          waitForCondition(function() isChatFocused(chatbar.selectedChat),
+                           function() {
+            // Our chats have 3 focusable elements, so it takes 4 TABs to move
+            // to the new chat.
+            sendTabAndWaitForFocus(chat2, "input1", function() {
+              is(chat2.contentDocument.activeElement.getAttribute("id"), "input1",
+                 "first input field has focus");
+              ok(isChatFocused(chat2), "new chat still focused after first tab");
+              sendTabAndWaitForFocus(chat2, "input2", function() {
+                ok(isChatFocused(chat2), "new chat still focused after tab");
+                is(chat2.contentDocument.activeElement.getAttribute("id"), "input2",
+                   "second input field has focus");
+                sendTabAndWaitForFocus(chat2, "iframe", function() {
+                  ok(isChatFocused(chat2), "new chat still focused after tab");
+                  is(chat2.contentDocument.activeElement.getAttribute("id"), "iframe",
+                     "iframe has focus");
+                  // this tab now should move to the next chat, but focus the
+                  // document element itself (hence the null eltid)
+                  sendTabAndWaitForFocus(chat1, null, function() {
+                    ok(isChatFocused(chat1), "first chat is focused");
+                    next();
+                  });
+                });
+              });
+            });
+          }, "chat should have focus");
+        });
+      });
+    });
+  },
+
+  // Open a chat and focus an element other than the first. Move focus to some
+  // other item (the tab itself in this case), then focus the chatbar - the
+  // same element that was previously focused should still have focus.
+  testFocusedElement: function(next) {
+    let chatbar = SocialChatBar.chatbar;
+    startTestAndWaitForSidebar(function(port) {
+      openChatViaUser();
+      let chat = chatbar.firstElementChild;
+      // need to wait for the content to load before we can focus it.
+      chat.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
+        chat.removeEventListener("DOMContentLoaded", DOMContentLoaded);
+        chat.contentDocument.getElementById("input2").focus();
+        waitForCondition(function() isChatFocused(chat),
+                         function() {
+          is(chat.contentDocument.activeElement.getAttribute("id"), "input2",
+             "correct input field has focus");
+          // set focus to the tab.
+          let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+          Services.focus.moveFocus(tabb.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
+          waitForCondition(function() isTabFocused(),
+                           function() {
+            chatbar.focus();
+            waitForCondition(function() isChatFocused(chat),
+                             function() {
+              is(chat.contentDocument.activeElement.getAttribute("id"), "input2",
+                 "correct input field still has focus");
+              next();
+            }, "chat took focus");
+          }, "tab has focus");
+        }, "chat took focus");
+      });
+    });
+  },
 };
--- a/browser/base/content/test/social/browser_social_errorPage.js
+++ b/browser/base/content/test/social/browser_social_errorPage.js
@@ -4,18 +4,16 @@
 
 function gc() {
   Cu.forceGC();
   let wu =  window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                   .getInterface(Components.interfaces.nsIDOMWindowUtils);
   wu.garbageCollect();
 }
 
-let openChatWindow = Cu.import("resource://gre/modules/MozSocialAPI.jsm", {}).openChatWindow;
-
 // Support for going on and offline.
 // (via browser/base/content/test/browser_bookmark_titles.js)
 let origProxyType = Services.prefs.getIntPref('network.proxy.type');
 
 function goOffline() {
   // Simulate a network outage with offline mode. (Localhost is still
   // accessible in offline mode, so disable the test proxy as well.)
   if (!Services.io.offline)
@@ -39,20 +37,19 @@ function openPanel(url, panelCallback, l
   SocialFlyout.panel.firstChild.addEventListener("load", function panelLoad() {
     SocialFlyout.panel.firstChild.removeEventListener("load", panelLoad, true);
     loadCallback();
   }, true);
 }
 
 function openChat(url, panelCallback, loadCallback) {
   // open a chat window
-  let chatbar = getChatBar();
-  openChatWindow(null, SocialSidebar.provider, url, panelCallback);
-  chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
-    chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
+  SocialChatBar.openChat(SocialSidebar.provider, url, panelCallback);
+  SocialChatBar.chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
+    SocialChatBar.chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
     loadCallback();
   }, true);
 }
 
 function onSidebarLoad(callback) {
   let sbrowser = document.getElementById("social-sidebar-browser");
   sbrowser.addEventListener("load", function load() {
     sbrowser.removeEventListener("load", load, true);
@@ -152,61 +149,30 @@ var tests = {
           );
         });
       }
     );
   },
 
   testChatWindow: function(next) {
     let panelCallbackCount = 0;
-    // go offline and open a chat.
+    // go offline and open a flyout.
     goOffline();
     openChat(
       "https://example.com/browser/browser/base/content/test/social/social_chat.html",
       function() { // the panel api callback
         panelCallbackCount++;
       },
       function() { // the "load" callback.
         executeSoon(function() {
           todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
-          let chat = getChatBar().selectedChat;
+          let chat = SocialChatBar.chatbar.selectedChat;
           waitForCondition(function() chat.contentDocument.location.href.indexOf("about:socialerror?")==0,
                            function() {
                             chat.close();
                             next();
                             },
                            "error page didn't appear");
         });
       }
     );
-  },
-
-  testChatWindowAfterTearOff: function(next) {
-    // Ensure that the error listener survives the chat window being detached.
-    let url = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
-    let panelCallbackCount = 0;
-    // open a chat while we are still online.
-    openChat(
-      url,
-      null,
-      function() { // the "load" callback.
-        executeSoon(function() {
-          let chat = getChatBar().selectedChat;
-          is(chat.contentDocument.location.href, url, "correct url loaded");
-          // toggle to a detached window.
-          chat.swapWindows().then(
-            chat => {
-              // now go offline and reload the chat - about:socialerror should be loaded.
-              goOffline();
-              chat.contentDocument.location.reload();
-              waitForCondition(function() chat.contentDocument.location.href.indexOf("about:socialerror?")==0,
-                               function() {
-                                chat.close();
-                                next();
-                                },
-                               "error page didn't appear");
-            }
-          );
-        });
-      }
-    );
   }
 }
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -232,16 +232,18 @@ function checkSocialUI(win) {
       is(a, b, msg)
     else
       ++numGoodTests;
   }
   function isbool(a, b, msg) {
     _is(!!a, !!b, msg);
   }
   isbool(win.SocialSidebar.canShow, sidebarEnabled, "social sidebar active?");
+  isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?");
+  isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?");
 
   let contextMenus = [
     {
       type: "link",
       id: "context-marklinkMenu",
       label: "social.marklinkMenu.label"
     },
     {
@@ -272,16 +274,18 @@ function checkSocialUI(win) {
     }
     for (let m of menus)
       _is(m.parentNode, parent, "menu has correct parent");
   }
 
   // and for good measure, check all the social commands.
   isbool(!doc.getElementById("Social:ToggleSidebar").hidden, sidebarEnabled, "Social:ToggleSidebar visible?");
   isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
+  isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?");
+  isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
 
   // and report on overall success of failure of the various checks here.
   is(numGoodTests, numTests, "The Social UI tests succeeded.")
 }
 
 function waitForNotification(topic, cb) {
   function observer(subject, topic, data) {
     Services.obs.removeObserver(observer, topic);
@@ -394,17 +398,17 @@ function loadIntoTab(tab, url, callback)
 function get3ChatsForCollapsing(mode, cb) {
   // We make one chat, then measure its size.  We then resize the browser to
   // ensure a second can be created fully visible but a third can not - then
   // create the other 2.  first will will be collapsed, second fully visible
   // and the third also visible and the "selected" one.
   // To make our life easier we don't go via the worker and ports so we get
   // more control over creation *and* to make the code much simpler.  We
   // assume the worker/port stuff is individually tested above.
-  let chatbar = getChatBar();
+  let chatbar = window.SocialChatBar.chatbar;
   let chatWidth = undefined;
   let num = 0;
   is(chatbar.childNodes.length, 0, "chatbar starting empty");
   is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
 
   makeChat(mode, "first chat", function() {
     // got the first one.
     checkPopup();
@@ -438,31 +442,33 @@ function get3ChatsForCollapsing(mode, cb
     });
   }, mode);
 }
 
 function makeChat(mode, uniqueid, cb) {
   info("making a chat window '" + uniqueid +"'");
   let provider = SocialSidebar.provider;
   const chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
-  // Note that we use promiseChatLoaded instead of the callback to ensure the
-  // content has started loading.
-  let chatbox = getChatBar().openChat(provider.origin, provider.name,
-                                      chatUrl + "?id=" + uniqueid, mode);
-  chatbox.promiseChatLoaded.then(
-    () => {
+  let isOpened = window.SocialChatBar.openChat(provider, chatUrl + "?id=" + uniqueid, function(chat) {
     info("chat window has opened");
-    chatbox.contentDocument.title = uniqueid;
-    cb();
-  });
+    // we can't callback immediately or we might close the chat during
+    // this event which upsets the implementation - it is only 1/2 way through
+    // handling the load event.
+    chat.document.title = uniqueid;
+    executeSoon(cb);
+  }, mode);
+  if (!isOpened) {
+    ok(false, "unable to open chat window, no provider? more failures to come");
+    executeSoon(cb);
+  }
 }
 
 function checkPopup() {
   // popup only showing if any collapsed popup children.
-  let chatbar = getChatBar();
+  let chatbar = window.SocialChatBar.chatbar;
   let numCollapsed = 0;
   for (let chat of chatbar.childNodes) {
     if (chat.collapsed) {
       numCollapsed += 1;
       // and it have a menuitem weakmap
       is(chatbar.menuitemMap.get(chat).nodeName, "menuitem", "collapsed chat has a menu item");
     } else {
       ok(!chatbar.menuitemMap.has(chat), "open chat has no menu item");
@@ -471,17 +477,17 @@ function checkPopup() {
   is(chatbar.menupopup.parentNode.collapsed, numCollapsed == 0, "popup matches child collapsed state");
   is(chatbar.menupopup.childNodes.length, numCollapsed, "popup has correct count of children");
   // todo - check each individual elt is what we expect?
 }
 // Resize the main window so the chat area's boxObject is |desired| wide.
 // Does a callback passing |true| if the window is now big enough or false
 // if we couldn't resize large enough to satisfy the test requirement.
 function resizeWindowToChatAreaWidth(desired, cb, count = 0) {
-  let current = getChatBar().getBoundingClientRect().width;
+  let current = window.SocialChatBar.chatbar.getBoundingClientRect().width;
   let delta = desired - current;
   info(count + ": resizing window so chat area is " + desired + " wide, currently it is "
        + current + ".  Screen avail is " + window.screen.availWidth
        + ", current outer width is " + window.outerWidth);
 
   // WTF?  Sometimes we will get fractional values due to the - err - magic
   // of DevPointsPerCSSPixel etc, so we allow a couple of pixels difference.
   let widthDeltaCloseEnough = function(d) {
@@ -504,17 +510,17 @@ function resizeWindowToChatAreaWidth(des
     info(count + ": skipping this as screen available width is less than necessary");
     executeSoon(function() {
       cb(false);
     });
     return;
   }
   function resize_handler(event) {
     // we did resize - but did we get far enough to be able to continue?
-    let newSize = getChatBar().getBoundingClientRect().width;
+    let newSize = window.SocialChatBar.chatbar.getBoundingClientRect().width;
     let sizedOk = widthDeltaCloseEnough(newSize - desired);
     if (!sizedOk)
       return;
     window.removeEventListener("resize", resize_handler, true);
     info(count + ": resized window width is " + newSize);
     executeSoon(function() {
       cb(sizedOk);
     });
@@ -552,27 +558,20 @@ function resizeAndCheckWidths(first, sec
       let m = new MutationObserver(collapsedObserver);
       m.observe(first, {attributes: true });
       m.observe(second, {attributes: true });
       m.observe(third, {attributes: true });
     }
   }, count);
 }
 
-function getChatBar() {
-  return document.getElementById("pinnedchats");
-}
-
 function getPopupWidth() {
-  let chatbar = getChatBar();
-  let popup = chatbar.menupopup;
+  let popup = window.SocialChatBar.chatbar.menupopup;
   ok(!popup.parentNode.collapsed, "asking for popup width when it is visible");
   let cs = document.defaultView.getComputedStyle(popup.parentNode);
   let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
   return popup.parentNode.getBoundingClientRect().width + margins;
 }
 
 function closeAllChats() {
-  let chatbar = getChatBar();
-  while (chatbar.selectedChat) {
-    chatbar.selectedChat.close();
-  }
+  let chatbar = window.SocialChatBar.chatbar;
+  chatbar.removeAll();
 }
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -8,17 +8,16 @@ MOCHITEST_MANIFESTS += [
     'content/test/general/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'content/test/chrome/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
-    'content/test/chat/browser.ini',
     'content/test/general/browser.ini',
     'content/test/newtab/browser.ini',
     'content/test/plugins/browser.ini',
     'content/test/social/browser.ini',
 ]
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
deleted file mode 100644
--- a/browser/modules/Chat.jsm
+++ /dev/null
@@ -1,191 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-// A module for working with chat windows.
-
-this.EXPORTED_SYMBOLS = ["Chat"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
-  "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-// A couple of internal helper function.
-function isWindowChromeless(win) {
-  // XXX - stolen from browser-social.js, but there's no obvious place to
-  // put this so it can be shared.
-
-  // Is this a popup window that doesn't want chrome shown?
-  let docElem = win.document.documentElement;
-  // extrachrome is not restored during session restore, so we need
-  // to check for the toolbar as well.
-  let chromeless = docElem.getAttribute("chromehidden").contains("extrachrome") ||
-                   docElem.getAttribute('chromehidden').contains("toolbar");
-  return chromeless;
-}
-
-function isWindowGoodForChats(win) {
-  return !win.closed &&
-         !!win.document.getElementById("pinnedchats") &&
-         !isWindowChromeless(win) &&
-         !PrivateBrowsingUtils.isWindowPrivate(win);
-}
-
-function getChromeWindow(contentWin) {
-  return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIWebNavigation)
-                   .QueryInterface(Ci.nsIDocShellTreeItem)
-                   .rootTreeItem
-                   .QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIDOMWindow);
-}
-
-/*
- * The exported Chat object
- */
-
-let Chat = {
-  /**
-   * Open a new chatbox.
-   *
-   * @param contentWindow [optional]
-   *        The content window that requested this chat.  May be null.
-   * @param origin
-   *        The origin for the chat.  This is primarily used as an identifier
-   *        to help identify all chats from the same provider.
-   * @param title
-   *        The title to be used if a new chat window is created.
-   * @param url
-   *        The URL for the that.  Should be under the origin.  If an existing
-   *        chatbox exists with the same URL, it will be reused and returned.
-   * @param mode [optional]
-   *        May be undefined or 'minimized'
-   * @param focus [optional]
-   *        Indicates if the chatbox should be focused.  If undefined the chat
-   *        will be focused if the window is currently handling user input (ie,
-   *        if the chat is being opened as a direct result of user input)
-
-   * @return A chatbox binding.  This binding has a number of promises which
-   *         can be used to determine when the chatbox is being created and
-   *         has loaded.  Will return null if no chat can be created (Which
-   *         should only happen in edge-cases)
-   */
-  open: function(contentWindow, origin, title, url, mode, focus, callback) {
-    let chromeWindow = this.findChromeWindowForChats(contentWindow);
-    if (!chromeWindow) {
-      Cu.reportError("Failed to open a chat window - no host window could be found.");
-      return null;
-    }
-
-    let chatbar = chromeWindow.document.getElementById("pinnedchats");
-    chatbar.hidden = false;
-    let chatbox = chatbar.openChat(origin, title, url, mode, callback);
-    // getAttention is ignored if the target window is already foreground, so
-    // we can call it unconditionally.
-    chromeWindow.getAttention();
-    // If focus is undefined we want automatic focus handling, and only focus
-    // if a direct result of user action.
-    if (focus === undefined) {
-      let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                            .getInterface(Ci.nsIDOMWindowUtils);
-      focus = dwu.isHandlingUserInput;
-    }
-    if (focus) {
-      chatbar.focus();
-    }
-    return chatbox;
-  },
-
-  /**
-   * Close all chats from the specified origin.
-   *
-   * @param origin
-   *        The origin from which all chats should be closed.
-   */
-  closeAll: function(origin) {
-    // close all attached chat windows
-    let winEnum = Services.wm.getEnumerator("navigator:browser");
-    while (winEnum.hasMoreElements()) {
-      let win = winEnum.getNext();
-      let chatbar = win.document.getElementById("pinnedchats");
-      if (!chatbar)
-        continue;
-      let chats = [c for (c of chatbar.children) if (c.content.getAttribute("origin") == origin)];
-      [c.close() for (c of chats)];
-    }
-
-    // close all standalone chat windows
-    winEnum = Services.wm.getEnumerator("Social:Chat");
-    while (winEnum.hasMoreElements()) {
-      let win = winEnum.getNext();
-      if (win.closed)
-        continue;
-      let chatOrigin = win.document.getElementById("chatter").content.getAttribute("origin");
-      if (origin == chatOrigin)
-        win.close();
-    }
-  },
-
-  /**
-   * Focus the chatbar associated with a window
-   *
-   * @param window
-   */
-  focus: function(win) {
-    let chatbar = win.document.getElementById("pinnedchats");
-    if (chatbar && !chatbar.hidden) {
-      chatbar.focus();
-    }
-
-  },
-
-  // This is exported as socialchat.xml needs to find a window when a chat
-  // is re-docked.
-  findChromeWindowForChats: function(preferredWindow) {
-    if (preferredWindow) {
-      preferredWindow = getChromeWindow(preferredWindow);
-      if (isWindowGoodForChats(preferredWindow)) {
-        return preferredWindow;
-      }
-    }
-    // no good - we just use the "most recent" browser window which can host
-    // chats (we used to try and "group" all chats in the same browser window,
-    // but that didn't work out so well - see bug 835111
-
-    // Try first the most recent window as getMostRecentWindow works
-    // even on platforms where getZOrderDOMWindowEnumerator is broken
-    // (ie. Linux).  This will handle most cases, but won't work if the
-    // foreground window is a popup.
-
-    let mostRecent = Services.wm.getMostRecentWindow("navigator:browser");
-    if (isWindowGoodForChats(mostRecent))
-      return mostRecent;
-
-    let topMost, enumerator;
-    // *sigh* - getZOrderDOMWindowEnumerator is broken except on Mac and
-    // Windows.  We use BROKEN_WM_Z_ORDER as that is what some other code uses
-    // and a few bugs recommend searching mxr for this symbol to identify the
-    // workarounds - we want this code to be hit in such searches.
-    let os = Services.appinfo.OS;
-    const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
-    if (BROKEN_WM_Z_ORDER) {
-      // this is oldest to newest and no way to change the order.
-      enumerator = Services.wm.getEnumerator("navigator:browser");
-    } else {
-      // here we explicitly ask for bottom-to-top so we can use the same logic
-      // where BROKEN_WM_Z_ORDER is true.
-      enumerator = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", false);
-    }
-    while (enumerator.hasMoreElements()) {
-      let win = enumerator.getNext();
-      if (!win.closed && isWindowGoodForChats(win))
-        topMost = win;
-    }
-    return topMost;
-  },
-}
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -4,17 +4,16 @@
 # 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/.
 
 TEST_DIRS += ['test']
 
 EXTRA_JS_MODULES += [
     'BrowserNewTabPreloader.jsm',
     'BrowserUITelemetry.jsm',
-    'Chat.jsm',
     'ContentClick.jsm',
     'ContentLinkHandler.jsm',
     'ContentSearch.jsm',
     'CustomizationTabPreloader.jsm',
     'Feeds.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'RemotePrompt.jsm',
--- a/toolkit/components/social/MozSocialAPI.jsm
+++ b/toolkit/components/social/MozSocialAPI.jsm
@@ -3,18 +3,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SocialService", "resource://gre/modules/SocialService.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Social", "resource:///modules/Social.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Chat", "resource:///modules/Chat.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["MozSocialAPI", "openChatWindow", "findChromeWindowForChats", "closeAllChatWindows"];
 
 this.MozSocialAPI = {
   _enabled: false,
   _everEnabled: false,
   set enabled(val) {
@@ -122,17 +120,17 @@ function attachToWindow(provider, target
       }
     },
     openChatWindow: {
       enumerable: true,
       configurable: true,
       writable: true,
       value: function(toURL, callback) {
         let url = targetWindow.document.documentURIObject.resolve(toURL);
-        openChatWindow(targetWindow, provider, url, callback);
+        openChatWindow(getChromeWindow(targetWindow), provider, url, callback);
       }
     },
     openPanel: {
       enumerable: true,
       configurable: true,
       writable: true,
       value: function(toURL, offset, callback) {
         let chromeWindow = getChromeWindow(targetWindow);
@@ -267,36 +265,97 @@ function getChromeWindow(contentWin) {
   return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIWebNavigation)
                    .QueryInterface(Ci.nsIDocShellTreeItem)
                    .rootTreeItem
                    .QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindow);
 }
 
+function isWindowGoodForChats(win) {
+  return win.SocialChatBar
+         && win.SocialChatBar.isAvailable
+         && !PrivateBrowsingUtils.isWindowPrivate(win);
+}
+
+function findChromeWindowForChats(preferredWindow) {
+  if (preferredWindow && isWindowGoodForChats(preferredWindow))
+    return preferredWindow;
+  // no good - we just use the "most recent" browser window which can host
+  // chats (we used to try and "group" all chats in the same browser window,
+  // but that didn't work out so well - see bug 835111
+
+  // Try first the most recent window as getMostRecentWindow works
+  // even on platforms where getZOrderDOMWindowEnumerator is broken
+  // (ie. Linux).  This will handle most cases, but won't work if the
+  // foreground window is a popup.
+
+  let mostRecent = Services.wm.getMostRecentWindow("navigator:browser");
+  if (isWindowGoodForChats(mostRecent))
+    return mostRecent;
+
+  let topMost, enumerator;
+  // *sigh* - getZOrderDOMWindowEnumerator is broken except on Mac and
+  // Windows.  We use BROKEN_WM_Z_ORDER as that is what some other code uses
+  // and a few bugs recommend searching mxr for this symbol to identify the
+  // workarounds - we want this code to be hit in such searches.
+  let os = Services.appinfo.OS;
+  const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
+  if (BROKEN_WM_Z_ORDER) {
+    // this is oldest to newest and no way to change the order.
+    enumerator = Services.wm.getEnumerator("navigator:browser");
+  } else {
+    // here we explicitly ask for bottom-to-top so we can use the same logic
+    // where BROKEN_WM_Z_ORDER is true.
+    enumerator = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", false);
+  }
+  while (enumerator.hasMoreElements()) {
+    let win = enumerator.getNext();
+    if (!win.closed && isWindowGoodForChats(win))
+      topMost = win;
+  }
+  return topMost;
+}
+
 this.openChatWindow =
- function openChatWindow(contentWindow, provider, url, callback, mode) {
+ function openChatWindow(chromeWindow, provider, url, callback, mode) {
+  chromeWindow = findChromeWindowForChats(chromeWindow);
+  if (!chromeWindow) {
+    Cu.reportError("Failed to open a social chat window - no host window could be found.");
+    return;
+  }
   let fullURI = provider.resolveUri(url);
   if (!provider.isSameOrigin(fullURI)) {
     Cu.reportError("Failed to open a social chat window - the requested URL is not the same origin as the provider.");
     return;
   }
+  if (!chromeWindow.SocialChatBar.openChat(provider, fullURI.spec, callback, mode)) {
+    Cu.reportError("Failed to open a social chat window - the chatbar is not available in the target window.");
+    return;
+  }
+  // getAttention is ignored if the target window is already foreground, so
+  // we can call it unconditionally.
+  chromeWindow.getAttention();
+}
 
-  let thisCallback = function(chatbox) {
-    // All social chat windows get a special error listener.
-    Social.setErrorListener(chatbox.content, function(aBrowser) {
-      aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
-                             encodeURIComponent(aBrowser.getAttribute("origin")),
-                             null, null, null, null);
-    });
+this.closeAllChatWindows =
+ function closeAllChatWindows(provider) {
+  // close all attached chat windows
+  let winEnum = Services.wm.getEnumerator("navigator:browser");
+  while (winEnum.hasMoreElements()) {
+    let win = winEnum.getNext();
+    if (!win.SocialChatBar)
+      continue;
+    let chats = [c for (c of win.SocialChatBar.chatbar.children) if (c.content.getAttribute("origin") == provider.origin)];
+    [c.close() for (c of chats)];
   }
-  let chatbox = Chat.open(contentWindow, provider.origin, provider.name,
-                          fullURI.spec, mode, undefined, thisCallback);
-  if (callback) {
-    chatbox.promiseChatLoaded.then(() => {
-      callback(chatbox.contentWindow);
-    });
+
+  // close all standalone chat windows
+  winEnum = Services.wm.getEnumerator("Social:Chat");
+  while (winEnum.hasMoreElements()) {
+    let win = winEnum.getNext();
+    if (win.closed)
+      continue;
+    let origin = win.document.getElementById("chatter").content.getAttribute("origin");
+    if (provider.origin == origin)
+      win.close();
   }
 }
-
-this.closeAllChatWindows = function closeAllChatWindows(provider) {
-  return Chat.closeAll(provider.origin);
-}