Bug 911981 - Chat panels are lost while detaching/attaching. r=mixedpuppy, a=lsblakk
authorFlorian Quèze <florian@queze.net>
Tue, 14 Jan 2014 23:08:20 +0100
changeset 175858 c39ef9346edef94843be47d41dfe4d8fcf2d1fe0
parent 175857 c65cec1da7460e0bc094009b6f9f0eaf03ee0b6a
child 175859 6b861c14640bd080b479409e866347ec00de5b3e
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmixedpuppy, lsblakk
bugs911981
milestone28.0a2
Bug 911981 - Chat panels are lost while detaching/attaching. r=mixedpuppy, a=lsblakk
browser/base/content/socialchat.xml
browser/base/content/test/social/browser_chat_tearoff.js
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -138,16 +138,17 @@
                                                 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", null, null, null, null);
@@ -187,17 +188,26 @@
             win.document.title = provider.name;
           });
         } else {
           // attach this chatbox to the topmost browser window
           let findChromeWindowForChats = Cu.import("resource://gre/modules/MozSocialAPI.jsm").findChromeWindowForChats;
           let win = findChromeWindowForChats();
           let chatbar = win.SocialChatBar.chatbar;
           chatbar.openChat(provider, "about:blank", win => {
-            this.swapDocShells(chatbar.selectedChat);
+            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));
+
             chatbar.focus();
             this.close();
           });
         }
         ]]></body>
       </method>
 
       <method name="toggle">
@@ -498,17 +508,17 @@
           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.getAttribute('src'));
+          this.chatboxForURL.delete(aChatbox.src);
         ]]></body>
       </method>
 
       <method name="removeAll">
         <body><![CDATA[
           this.selectedChat = null;
           while (this.firstElementChild) {
             this._remove(this.firstElementChild);
@@ -638,17 +648,18 @@
         <parameter name="aChatbox"/>
         <parameter name="aOptions"/>
         <parameter name="aCallback"/>
         <body><![CDATA[
           let options = "";
           for (let name in aOptions)
             options += "," + name + "=" + aOptions[name];
 
-          let otherWin = window.openDialog("chrome://browser/content/chatWindow.xul", null, "chrome,all,dialog=no" + options);
+          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);
--- a/browser/base/content/test/social/browser_chat_tearoff.js
+++ b/browser/base/content/test/social/browser_chat_tearoff.js
@@ -194,9 +194,113 @@ var tests = {
           }, domwindow);
 
         }, false);
       }
     });
 
     port.postMessage({topic: "test-worker-chat", data: chatUrl});
   },
-}
\ No newline at end of file
+
+  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 = Social.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"});
+  }
+};