Bug 1162322 change social error handling to use messagemanager, r=markh
authorShane Caraveo <scaraveo@mozilla.com>
Fri, 22 May 2015 10:33:18 -0700
changeset 245081 13116475bbd5d9dbb7ea2c7ce90644faa82f8702
parent 245080 22761f1474f015a2d4b861a32e06bba692e6209c
child 245082 90ea370e792f932b57450123a664eceba10b1401
push id13106
push usermixedpuppy@gmail.com
push dateFri, 22 May 2015 17:33:40 +0000
treeherderfx-team@13116475bbd5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs1162322
milestone41.0a1
Bug 1162322 change social error handling to use messagemanager, r=markh
browser/base/content/aboutSocialError.xhtml
browser/base/content/browser-social.js
browser/base/content/browser.xul
browser/base/content/social-content.js
browser/base/content/socialchat.xml
browser/base/content/socialmarks.xml
browser/base/content/test/social/browser_social_errorPage.js
browser/base/content/test/social/browser_social_marks.js
browser/base/content/test/social/browser_social_status.js
browser/base/content/test/social/browser_social_workercrash.js
browser/base/jar.mn
browser/modules/PanelFrame.jsm
browser/modules/Social.jsm
toolkit/components/social/MozSocialAPI.jsm
--- a/browser/base/content/aboutSocialError.xhtml
+++ b/browser/base/content/aboutSocialError.xhtml
@@ -41,23 +41,24 @@
     Cu.import("resource://gre/modules/Services.jsm");
     Cu.import("resource:///modules/Social.jsm");
 
     let config = {
       tryAgainCallback: reloadProvider
     }
 
     function parseQueryString() {
-      let searchParams = new URLSearchParams(location.href.split("?")[1]);
+      let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
       let mode = searchParams.get("mode");
-      config.directory = searchParams.get("directory");
       config.origin = searchParams.get("origin");
       let encodedURL = searchParams.get("url");
       let url = decodeURIComponent(encodedURL);
-      if (config.directory) {
+      // directory does not have origin set, in that case use the url origin for
+      // the error message.
+      if (!config.origin) {
         let URI = Services.io.newURI(url, null, null);
         config.origin = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI).origin;
       }
 
       switch (mode) {
         case "compactInfo":
           document.getElementById("btnTryAgain").style.display = 'none';
           break;
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -55,16 +55,19 @@ XPCOMUtils.defineLazyGetter(this, "hookW
 SocialUI = {
   _initialized: false,
 
   // Called on delayed startup to initialize the UI
   init: function SocialUI_init() {
     if (this._initialized) {
       return;
     }
+    let mm = window.getGroupMessageManager("social");
+    mm.loadFrameScript("chrome://browser/content/content.js", true);
+    mm.loadFrameScript("chrome://browser/content/social-content.js", true);
 
     Services.obs.addObserver(this, "social:ambient-notification-changed", false);
     Services.obs.addObserver(this, "social:profile-changed", false);
     Services.obs.addObserver(this, "social:frameworker-error", false);
     Services.obs.addObserver(this, "social:providers-changed", false);
     Services.obs.addObserver(this, "social:provider-reload", false);
     Services.obs.addObserver(this, "social:provider-enabled", false);
     Services.obs.addObserver(this, "social:provider-disabled", false);
@@ -149,17 +152,17 @@ SocialUI = {
       case "social:profile-changed":
         // make sure anything that happens here only affects the provider for
         // which the profile is changing, and that anything we call actually
         // needs to change based on profile data.
         SocialStatus.updateButton(data);
         break;
       case "social:frameworker-error":
         if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) {
-          SocialSidebar.setSidebarErrorMessage();
+          SocialSidebar.loadFrameworkerFailure();
         }
         break;
       case "nsPref:changed":
         if (data == "social.toast-notifications.enabled") {
           SocialSidebar.updateToggleNotifications();
         }
         break;
     }
@@ -346,41 +349,39 @@ SocialFlyout = {
     doc.documentElement.dispatchEvent(evt);
   },
 
   _createFrame: function() {
     let panel = this.panel;
     if (!SocialUI.enabled || panel.firstChild)
       return;
     // create and initialize the panel for this window
-    let iframe = document.createElement("iframe");
+    let iframe = document.createElement("browser");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-panel-frame");
     iframe.setAttribute("flex", "1");
+    iframe.setAttribute("message", "true");
+    iframe.setAttribute("messagemanagergroup", "social");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
+    iframe.setAttribute("context", "contentAreaContextMenu");
     iframe.setAttribute("origin", SocialSidebar.provider.origin);
     panel.appendChild(iframe);
-  },
-
-  setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() {
-    this.iframe.removeAttribute("src");
-    this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
-                                 encodeURIComponent(this.iframe.getAttribute("origin")),
-                                 null, null, null, null);
-    sizeSocialPanelToContent(this.panel, this.iframe);
+    // the xbl bindings for the iframe probably don't exist yet, so we can't
+    // access iframe.messageManager directly - but can get at it with this dance.
+    let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
+    mm.sendAsyncMessage("Social:SetErrorURL", null,
+                        { template: "about:socialerror?mode=compactInfo&origin=%{origin}" });
   },
 
   unload: function() {
     let panel = this.panel;
     panel.hidePopup();
     if (!panel.firstChild)
       return
     let iframe = panel.firstChild;
-    if (iframe.socialErrorListener)
-      iframe.socialErrorListener.remove();
     panel.removeChild(iframe);
   },
 
   onShown: function(aEvent) {
     let panel = this.panel;
     let iframe = this.iframe;
     this._dynamicResizer = new DynamicResizeWatcher();
     iframe.docShell.isActive = true;
@@ -418,17 +419,16 @@ SocialFlyout = {
     // same url with only ref difference does not cause a new load, so we
     // want to go right to the callback
     let src = iframe.contentDocument && iframe.contentDocument.documentURIObject;
     if (!src || !src.equalsExceptRef(Services.io.newURI(aURL, null, null))) {
       iframe.addEventListener("load", function documentLoaded() {
         iframe.removeEventListener("load", documentLoaded, true);
         cb();
       }, true);
-      Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout))
       iframe.setAttribute("src", aURL);
     } else {
       // we still need to set the src to trigger the contents hashchange event
       // for ref changes
       iframe.setAttribute("src", aURL);
       cb();
     }
   },
@@ -498,21 +498,23 @@ SocialShare = {
     // create and initialize the panel for this window
     let iframe = document.createElement("browser");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-share-frame");
     iframe.setAttribute("context", "contentAreaContextMenu");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
     iframe.setAttribute("disableglobalhistory", "true");
     iframe.setAttribute("flex", "1");
+    iframe.setAttribute("message", "true");
+    iframe.setAttribute("messagemanagergroup", "social");
     panel.lastChild.appendChild(iframe);
-    iframe.addEventListener("load", function _firstload() {
-      iframe.removeEventListener("load", _firstload, true);
-      iframe.messageManager.loadFrameScript("chrome://browser/content/content.js", true);
-    }, true);
+    let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
+    mm.sendAsyncMessage("Social:SetErrorURL", null,
+                        { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
+
     this.populateProviderMenu();
   },
 
   getSelectedProvider: function() {
     let provider;
     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
     if (lastProviderOrigin) {
       provider = Social._getProviderFromOrigin(lastProviderOrigin);
@@ -591,33 +593,16 @@ SocialShare = {
     // share panel use is over, purge any history
     if (this.iframe.sessionHistory) {
       let purge = this.iframe.sessionHistory.count;
       if (purge > 0)
         this.iframe.sessionHistory.PurgeHistory(purge);
     }
   },
 
-  setErrorMessage: function() {
-    let iframe = this.iframe;
-    if (!iframe)
-      return;
-
-    let url;
-    let origin = iframe.getAttribute("origin");
-    if (!origin) {
-      // directory site is down
-      url = "about:socialerror?mode=tryAgainOnly&directory=1&url=" + encodeURIComponent(iframe.getAttribute("src"));
-    } else {
-      url = "about:socialerror?mode=compactInfo&origin=" + encodeURIComponent(origin);
-    }
-    iframe.webNavigation.loadURI(url, null, null, null, null);
-    sizeSocialPanelToContent(this.panel, iframe);
-  },
-
   sharePage: function(providerOrigin, graphData, target) {
     // if providerOrigin is undefined, we use the last-used provider, or the
     // current/default provider.  The provider selection in the share panel
     // will call sharePage with an origin for us to switch to.
     this._createFrame();
     let iframe = this.iframe;
 
     // graphData is an optional param that either defines the full set of data
@@ -759,17 +744,16 @@ SocialShare = {
     }, true);
     iframe.setAttribute("src", "about:providerdirectory");
     this._openPanel();
   },
 
   _openPanel: function() {
     let anchor = document.getAnonymousElementByAttribute(this.anchor, "class", "toolbarbutton-icon");
     this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
-    Social.setErrorListener(this.iframe, this.setErrorMessage.bind(this));
     Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(0);
   }
 };
 
 SocialSidebar = {
   _openStartTime: 0,
 
   // Whether the sidebar can be shown for this window.
@@ -913,27 +897,22 @@ SocialSidebar = {
       } else {
         this._unloadTimeoutId = setTimeout(
           this.unloadSidebar,
           Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
         );
       }
     } else {
       sbrowser.setAttribute("origin", this.provider.origin);
-      if (this.provider.errorState == "frameworker-error") {
-        SocialSidebar.setSidebarErrorMessage();
-        return;
-      }
 
       // Make sure the right sidebar URL is loaded
       if (sbrowser.getAttribute("src") != this.provider.sidebarURL) {
         // we check readyState right after setting src, we need a new content
         // viewer to ensure we are checking against the correct document.
         sbrowser.docShell.createAboutBlankContentViewer(null);
-        Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this));
         // setting isAppTab causes clicks on untargeted links to open new tabs
         sbrowser.docShell.isAppTab = true;
         sbrowser.setAttribute("src", this.provider.sidebarURL);
         PopupNotifications.locationChange(sbrowser);
       }
 
       // if the document has not loaded, delay until it is
       if (sbrowser.contentDocument.readyState != "complete") {
@@ -971,28 +950,23 @@ SocialSidebar = {
     // doesn't get destroyed until about:blank has loaded (which does not happen
     // as long as the element is hidden).
     sbrowser.docShell.createAboutBlankContentViewer(null);
     SocialFlyout.unload();
   },
 
   _unloadTimeoutId: 0,
 
-  setSidebarErrorMessage: function() {
-    let sbrowser = document.getElementById("social-sidebar-browser");
-    // a frameworker error "trumps" a sidebar error.
-    let origin = sbrowser.getAttribute("origin");
-    if (origin) {
-      origin = "&origin=" + encodeURIComponent(origin);
-    }
-    if (this.provider.errorState == "frameworker-error") {
-      sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure" + origin);
-    } else {
-      let url = encodeURIComponent(this.provider.sidebarURL);
-      sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url + origin, null, null);
+  loadFrameworkerFailure: function() {
+    if (this.provider && this.provider.errorState == "frameworker-error") {
+      // we have to explicitly load this error page since it is not being
+      // handled via the normal error page paths.
+      let sbrowser = document.getElementById("social-sidebar-browser");
+      sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure&origin=" +
+                            encodeURIComponent(this.provider.origin));
     }
   },
 
   _provider: null,
   ensureProvider: function() {
     if (this._provider)
       return;
     // origin for sidebar is persisted, so get the previously selected sidebar
@@ -1293,57 +1267,54 @@ SocialStatus = {
                                                         [ariaLabel, badge]);
       button.setAttribute("aria-label", ariaLabel);
     }
   },
 
   _onclose: function(frame) {
     frame.removeEventListener("close", this._onclose, true);
     frame.removeEventListener("click", this._onclick, true);
-    if (frame.socialErrorListener)
-      frame.socialErrorListener.remove();
   },
 
   _onclick: function() {
     Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(1);
   },
 
   showPopup: function(aToolbarButton) {
     // attach our notification panel if necessary
     let origin = aToolbarButton.getAttribute("origin");
     let provider = Social._getProviderFromOrigin(origin);
 
     PanelFrame.showPopup(window, aToolbarButton, "social", origin,
                          provider.statusURL, provider.getPageSize("status"),
                          (frame) => {
                           frame.addEventListener("close", () => { SocialStatus._onclose(frame) }, true);
                           frame.addEventListener("click", this._onclick, true);
-                          Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
                         });
     Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(1);
-  },
-
-  setPanelErrorMessage: function(aNotificationFrame) {
-    if (!aNotificationFrame)
-      return;
-
-    let src = aNotificationFrame.getAttribute("src");
-    aNotificationFrame.removeAttribute("src");
-    let origin = aNotificationFrame.getAttribute("origin");
-    aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
-                                            encodeURIComponent(src) + "&origin=" +
-                                            encodeURIComponent(origin),
-                                            null, null, null, null);
-    let panel = aNotificationFrame.parentNode;
-    sizeSocialPanelToContent(panel, aNotificationFrame);
-  },
-
+  }
 };
 
 
+let SocialMarksWidgetListener = {
+  onWidgetAdded: function(aWidgetId, aArea, aPosition) {
+    let node = document.getElementById(aWidgetId);
+    if (!node || !node.classList.contains("social-mark-button"))
+      return;
+    node._receiveMessage = node.receiveMessage.bind(node);
+    messageManager.addMessageListener("Social:ErrorPageNotify", node._receiveMessage);
+  },
+  onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, isRemoval) {
+    if (!isRemoval || !aNode || !aNode.classList.contains("social-mark-button"))
+      return;
+    messageManager.removeMessageListener("Social:ErrorPageNotify", aNode._receiveMessage);
+    delete aNode._receiveMessage;
+  }
+}
+
 /**
  * SocialMarks
  *
  * Handles updates to toolbox and signals all buttons to update when necessary.
  */
 SocialMarks = {
   get nodes() {
     let providers = [p for (p of Social.providers) if (p.markURL)];
@@ -1437,17 +1408,19 @@ SocialMarks = {
   },
 
   removeProvider: function(origin) {
     this._toolbarHelper.removeProviderButton(origin);
   },
 
   get _toolbarHelper() {
     delete this._toolbarHelper;
-    this._toolbarHelper = new ToolbarHelper("social-mark-button", CreateSocialMarkWidget);
+    this._toolbarHelper = new ToolbarHelper("social-mark-button",
+                                            CreateSocialMarkWidget,
+                                            SocialMarksWidgetListener);
     return this._toolbarHelper;
   },
 
   markLink: function(aOrigin, aUrl, aTarget) {
     // find the button for this provider, and open it
     let id = this._toolbarHelper.idFromOrigin(aOrigin);
     document.getElementById(id).markLink(aUrl, aTarget);
   }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1218,16 +1218,18 @@
                         oncommand="SocialUI.showLearnMore();"/>
             </menupopup>
           </toolbarbutton>
         </sidebarheader>
 
         <browser id="social-sidebar-browser"
                  type="content"
                  context="contentAreaContextMenu"
+                 message="true"
+                 messagemanagergroup="social"
                  disableglobalhistory="true"
                  tooltip="aHTMLTooltip"
                  popupnotificationanchor="social-sidebar-favico"
                  flex="1"
                  style="min-width: 14em; width: 18em; max-width: 36em;"/>
       </vbox>
       <vbox id="browser-border-end" hidden="true" layer="true"/>
     </hbox>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/social-content.js
@@ -0,0 +1,105 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+/* This content script should work in any browser or iframe and should not
+ * depend on the frame being contained in tabbrowser. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// social frames are always treated as app tabs
+docShell.isAppTab = true;
+
+// Error handling class used to listen for network errors in the social frames
+// and replace them with a social-specific error page
+SocialErrorListener = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsISupports]),
+
+  defaultTemplate: "about:socialerror?mode=tryAgainOnly&url=%{url}&origin=%{origin}",
+  urlTemplate: null,
+
+  init() {
+    addMessageListener("Social:SetErrorURL", this);
+    let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                              .getInterface(Components.interfaces.nsIWebProgress);
+    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+                                          Ci.nsIWebProgress.NOTIFY_LOCATION);
+  },
+  receiveMessage(message) {
+    switch(message.name) {
+      case "Social:SetErrorURL": {
+        // either a url or null to reset to default template
+        this.urlTemplate = message.objects.template;
+      }
+    }
+  },
+
+  setErrorPage() {
+    // if this is about:providerdirectory, use the directory iframe
+    let frame = docShell.chromeEventHandler;
+    let origin = frame.getAttribute("origin");
+    let src = frame.getAttribute("src");
+    if (src == "about:providerdirectory") {
+      frame = content.document.getElementById("activation-frame");
+      src = frame.getAttribute("src");
+    }
+
+    let url = this.urlTemplate || this.defaultTemplate;
+    url = url.replace("%{url}", encodeURIComponent(src));
+    url = url.replace("%{origin}", encodeURIComponent(origin));
+    if (frame != docShell.chromeEventHandler) {
+      // Unable to access frame.docShell here. This is our own frame and doesn't
+      // provide reload, so we'll just set the src.
+      frame.setAttribute("src", url);
+    } else {
+      let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+      webNav.loadURI(url, null, null, null, null);
+    }
+    sendAsyncMessage("Social:ErrorPageNotify", {
+        origin: origin,
+        url: src
+    });
+  },
+
+  onStateChange(aWebProgress, aRequest, aState, aStatus) {
+    let failure = false;
+    if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
+      if (aRequest instanceof Ci.nsIHttpChannel) {
+        try {
+          // Change the frame to an error page on 4xx (client errors)
+          // and 5xx (server errors).  responseStatus throws if it is not set.
+          failure = aRequest.responseStatus >= 400 &&
+                    aRequest.responseStatus < 600;
+        } catch (e) {
+          failure = aStatus != Components.results.NS_OK;
+        }
+      }
+    }
+
+    // Calling cancel() will raise some OnStateChange notifications by itself,
+    // so avoid doing that more than once
+    if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
+      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+      this.setErrorPage();
+    }
+  },
+
+  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+    if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+      this.setErrorPage();
+    }
+  },
+
+  onProgressChange() {},
+  onStatusChange() {},
+  onSecurityChange() {},
+};
+
+SocialErrorListener.init();
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -25,16 +25,18 @@
         <xul:toolbarbutton anonid="swap" class="chat-swap-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).swapWindows();"/>
         <xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).close();"/>
       </xul:hbox>
       <xul:browser anonid="content" class="chat-frame" flex="1"
                   context="contentAreaContextMenu"
                   disableglobalhistory="true"
+                  message="true"
+                  messagemanagergroup="social"
                   tooltip="aHTMLTooltip"
                   xbl:inherits="src,origin" type="content"/>
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <constructor><![CDATA[
         const kAnchorMap = new Map([
           ["", "notification-"],
@@ -67,23 +69,16 @@
         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);
         }, true);
 
-        // load content.js to support webrtc, fullscreen, etc.
-        this.addEventListener("load", function loaded(event) {
-          this.removeEventListener("load", loaded, true);
-          let mm = this.content.messageManager;
-          mm.loadFrameScript("chrome://browser/content/content.js", true);
-        }, true);
-
         if (this.src)
           this.setAttribute("src", this.src);
       ]]></constructor>
 
       <field name="_deferredChatLoaded" readonly="true">
         Promise.defer();
       </field>
 
--- a/browser/base/content/socialmarks.xml
+++ b/browser/base/content/socialmarks.xml
@@ -49,16 +49,18 @@
           let provider = Social._getProviderFromOrigin(this.getAttribute("origin"));
           let size = provider.getPageSize("marks");
           let {width, height} = size ? size : {width: 330, height: 100};
 
           let iframe = this._frame = document.createElement("iframe");
           iframe.setAttribute("type", "content");
           iframe.setAttribute("class", "social-panel-frame");
           iframe.setAttribute("flex", "1");
+          iframe.setAttribute("message", "true");
+          iframe.setAttribute("messagemanagergroup", "social");
           iframe.setAttribute("tooltip", "aHTMLTooltip");
           iframe.setAttribute("context", "contentAreaContextMenu");
           iframe.setAttribute("origin", provider.origin);
           iframe.setAttribute("style", "width: " + width + "px; height: " + height + "px;");
           this.panel.appendChild(iframe);
 
           this._frame.addEventListener("DOMLinkAdded", this);
           return this._frame;
@@ -129,16 +131,25 @@
         // that we can hide
         if (panel.hidePopup) {
           panel.hidePopup();
         }
         this.pageData = null;
         ]]></body>
       </method>
 
+      <method name="receiveMessage">
+        <parameter name="message"/>
+        <body><![CDATA[
+        if (message.name != "Social:ErrorPageNotify" || message.target != this.content)
+          return;
+        this.openPanel();
+        ]]></body>
+      </method>
+
       <method name="loadPanel">
         <parameter name="pageData"/>
         <parameter name="target"/>
         <body><![CDATA[
         let provider = this.provider;
         let panel = this.panel;
         panel.hidden = false;
 
@@ -201,47 +212,26 @@
               Social.unmarkURI(provider.origin, gBrowser.currentURI, () => {
                 this.update();
               });
             }
           }.bind(this);
           let unload = () => {
             contentWindow.removeEventListener("unload", unload);
             contentWindow.removeEventListener("socialMarkUpdate", markUpdate);
-            if (this.content.socialErrorListener)
-              this.content.socialErrorListener.remove();
           }
           contentWindow.addEventListener("socialMarkUpdate", markUpdate);
           contentWindow.addEventListener("unload", unload);
         }
         this.content.addEventListener("DOMContentLoaded", DOMContentLoaded, true);
-        Social.setErrorListener(this.content, this.setErrorMessage.bind(this));
         this._loading = true;
         this.content.setAttribute("src", endpoint);
         ]]></body>
       </method>
 
-      <method name="setErrorMessage">
-        <parameter name="aNotificationFrame"/>
-        <body><![CDATA[
-        if (!aNotificationFrame)
-          return;
-
-        let src = aNotificationFrame.getAttribute("src");
-        aNotificationFrame.removeAttribute("src");
-        let origin = aNotificationFrame.getAttribute("origin");
-        aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
-                                                encodeURIComponent(src) + "&origin=" +
-                                                encodeURIComponent(origin),
-                                                null, null, null, null);
-        // ensure the panel is open if error occurs on first click
-        this.openPanel();
-        ]]></body>
-      </method>
-
       <method name="openPanel">
         <parameter name="aResetOnClose"/>
         <body><![CDATA[
         let panel = this.panel;
         let anchor = document.getAnonymousElementByAttribute(this._anchor, "class", "toolbarbutton-icon");
         // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
         // handling from preventing it being opened in some cases.
         setTimeout(() => {
--- a/browser/base/content/test/social/browser_social_errorPage.js
+++ b/browser/base/content/test/social/browser_social_errorPage.js
@@ -57,27 +57,27 @@ function test() {
     runSocialTests(tests, undefined, function(next) { goOnline().then(next) }, finishcb);
   });
 }
 
 var tests = {
   testSidebar: function(next) {
     let sbrowser = document.getElementById("social-sidebar-browser");
     onSidebarLoad(function() {
-      ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "sidebar is on social error page");
+      ok(sbrowser.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0, "sidebar is on social error page");
       gc();
       // Add a new load listener, then find and click the "try again" button.
       onSidebarLoad(function() {
         // should still be on the error page.
-        ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "sidebar is still on social error page");
+        ok(sbrowser.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0, "sidebar is still on social error page");
         // go online and try again - this should work.
         goOnline().then(function () {
           onSidebarLoad(function() {
             // should now be on the correct page.
-            is(sbrowser.contentDocument.location.href, manifest.sidebarURL, "sidebar is now on social sidebar page");
+            is(sbrowser.contentDocument.documentURI, manifest.sidebarURL, "sidebar is now on social sidebar page");
             next();
           });
           sbrowser.contentDocument.getElementById("btnTryAgain").click();
         });
       });
       sbrowser.contentDocument.getElementById("btnTryAgain").click();
     });
     // go offline then attempt to load the sidebar - it should fail.
@@ -92,30 +92,30 @@ var tests = {
     goOffline().then(function() {
       openPanel(
         manifest.sidebarURL, /* empty html page */
         function() { // the panel api callback
           panelCallbackCount++;
         },
         function() { // the "load" callback.
           todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
-          let href = panel.firstChild.contentDocument.location.href;
-          ok(href.indexOf("about:socialerror?")==0, "flyout is on social error page");
+          let href = panel.firstChild.contentDocument.documentURI;
+          ok(href.indexOf("about:socialerror?mode=compactInfo")==0, "flyout is on social error page");
           // Bug 832943 - the listeners previously stopped working after a GC, so
           // force a GC now and try again.
           gc();
           openPanel(
             manifest.sidebarURL, /* empty html page */
             function() { // the panel api callback
               panelCallbackCount++;
             },
             function() { // the "load" callback.
               todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
-              let href = panel.firstChild.contentDocument.location.href;
-              ok(href.indexOf("about:socialerror?")==0, "flyout is on social error page");
+              let href = panel.firstChild.contentDocument.documentURI;
+              ok(href.indexOf("about:socialerror?mode=compactInfo")==0, "flyout is on social error page");
               gc();
               SocialFlyout.unload();
               next();
             }
           );
         }
       );
     });
@@ -129,17 +129,17 @@ var tests = {
       openChat(
         manifest.sidebarURL, /* empty html page */
         function() { // the panel api callback
           panelCallbackCount++;
         },
         function() { // the "load" callback.
           todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
           let chat = getChatBar().selectedChat;
-          waitForCondition(function() chat.content != null && chat.contentDocument.location.href.indexOf("about:socialerror?")==0,
+          waitForCondition(function() chat.content != null && chat.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0,
                            function() {
                             chat.close();
                             next();
                             },
                            "error page didn't appear");
         }
       );
     });
@@ -152,29 +152,29 @@ var tests = {
     // chatwindow tests throw errors, which muddy test output, if the worker
     // doesn't get test-init
     // open a chat while we are still online.
     openChat(
       url,
       null,
       function() { // the "load" callback.
         let chat = getChatBar().selectedChat;
-        is(chat.contentDocument.location.href, url, "correct url loaded");
+        is(chat.contentDocument.documentURI, url, "correct url loaded");
         // toggle to a detached window.
         chat.swapWindows().then(
           chat => {
             ok(!!chat.content, "we have chat content 1");
             waitForCondition(function() chat.content != null && chat.contentDocument.readyState == "complete",
                              function() {
               // now go offline and reload the chat - about:socialerror should be loaded.
               goOffline().then(function() {
                 ok(!!chat.content, "we have chat content 2");
                 chat.contentDocument.location.reload();
                 info("chat reload called");
-                waitForCondition(function() chat.contentDocument.location.href.indexOf("about:socialerror?")==0,
+                waitForCondition(function() chat.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0,
                                  function() {
                                   chat.close();
                                   next();
                                   },
                                  "error page didn't appear");
               });
             }, "swapped window loaded");
           }
--- a/browser/base/content/test/social/browser_social_marks.js
+++ b/browser/base/content/test/social/browser_social_marks.js
@@ -233,17 +233,17 @@ var tests = {
     addTab(activationURL, function(tab) {
       ok(!btn.disabled, "button is enabled");
       goOffline().then(function() {
         info("testing offline error page");
         // wait for popupshown
         ensureEventFired(btn.panel, "popupshown").then(() => {
           info("marks panel is open");
           ensureFrameLoaded(btn.content).then(() => {
-            is(btn.contentDocument.location.href.indexOf("about:socialerror?"), 0, "social error page is showing");
+            is(btn.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly"), 0, "social error page is showing "+btn.contentDocument.documentURI);
             // cleanup after the page has been unmarked
             ensureBrowserTabClosed(tab).then(() => {
               ok(btn.disabled, "button is disabled");
               goOnline().then(next);
             });
           });
         });
         btn.markCurrentPage();
--- a/browser/base/content/test/social/browser_social_status.js
+++ b/browser/base/content/test/social/browser_social_status.js
@@ -178,17 +178,17 @@ var tests = {
     port.postMessage({topic: "test-init"});
 
     goOffline().then(function() {
       info("testing offline error page");
       // wait for popupshown
       let panel = document.getElementById("social-notification-panel");
       ensureEventFired(panel, "popupshown").then(() => {
         ensureFrameLoaded(frame).then(() => {
-          is(frame.contentDocument.location.href.indexOf("about:socialerror?"), 0, "social error page is showing "+frame.contentDocument.location.href);
+          is(frame.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly"), 0, "social error page is showing "+frame.contentDocument.documentURI);
           panel.hidePopup();
           goOnline().then(next);
         });
       });
       // reload after going offline, wait for unload to open panel
       ensureEventFired(frame, "unload").then(() => {
         btn.click();
       });
--- a/browser/base/content/test/social/browser_social_workercrash.js
+++ b/browser/base/content/test/social/browser_social_workercrash.js
@@ -58,17 +58,17 @@ var tests = {
         mm.loadFrameScript(TEST_CONTENT_HELPER, false);
         // add an observer for the crash - after it sees the crash we attempt
         // a reload.
         let observer = new crashObserver(function() {
           info("Saw the process crash.")
           Services.obs.removeObserver(observer, 'ipc:content-shutdown');
           // Add another sidebar load listener - it should be the error page.
           onSidebarLoad(function() {
-            ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+            ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?mode=workerFailure")==0, "is on social error page");
             // after reloading, the sidebar should reload
             onSidebarLoad(function() {
               // now ping both workers - they should both be alive.
               ensureWorkerLoaded(gProviders[0], function() {
                 ensureWorkerLoaded(gProviders[1], function() {
                   // and we are done!
                   next();
                 });
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -80,16 +80,17 @@ browser.jar:
         content/browser/browser-pocket-de.properties  (content/browser-pocket-de.properties)
         content/browser/browser-pocket-es-ES.properties (content/browser-pocket-es-ES.properties)
         content/browser/browser-pocket-ja.properties  (content/browser-pocket-ja.properties)
         content/browser/browser-pocket-ru.properties  (content/browser-pocket-ru.properties)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 *       content/browser/chatWindow.xul                (content/chatWindow.xul)
         content/browser/tab-content.js                (content/tab-content.js)
         content/browser/content.js                    (content/content.js)
+        content/browser/social-content.js             (content/social-content.js)
         content/browser/defaultthemes/1.footer.jpg    (content/defaultthemes/1.footer.jpg)
         content/browser/defaultthemes/1.header.jpg    (content/defaultthemes/1.header.jpg)
         content/browser/defaultthemes/1.icon.jpg      (content/defaultthemes/1.icon.jpg)
         content/browser/defaultthemes/1.preview.jpg   (content/defaultthemes/1.preview.jpg)
         content/browser/defaultthemes/2.footer.jpg    (content/defaultthemes/2.footer.jpg)
         content/browser/defaultthemes/2.header.jpg    (content/defaultthemes/2.header.jpg)
         content/browser/defaultthemes/2.icon.jpg      (content/defaultthemes/2.icon.jpg)
         content/browser/defaultthemes/2.preview.jpg   (content/defaultthemes/2.preview.jpg)
--- a/browser/modules/PanelFrame.jsm
+++ b/browser/modules/PanelFrame.jsm
@@ -72,16 +72,20 @@ let PanelFrameInternal = {
         // work around bug 793057 - by making the panel roughly the final size
         // we are more likely to have the anchor in the correct position.
         "style": "width: " + width + "px; height: " + height + "px;",
         "dynamicresizer": !aSize,
 
         "origin": aOrigin,
         "src": aSrc
       };
+      if (aType == "social") {
+        attrs["message"] = "true";
+        attrs["messagemanagergroup"] = aType;
+      }
       for (let [k, v] of Iterator(attrs)) {
         frame.setAttribute(k, v);
       }
       aParent.appendChild(frame);
     } else {
       frame.setAttribute("origin", aOrigin);
       frame.setAttribute("src", aSrc);
     }
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -235,22 +235,16 @@ this.Social = {
         // remove the annotation
         providerList.splice(providerList.indexOf(origin), 1);
         promiseSetAnnotation(aURI, providerList).then(function() {
           if (aCallback)
             schedule(function() { aCallback(false); } );
         }).then(null, Cu.reportError);
       }
     }).then(null, Cu.reportError);
-  },
-
-  setErrorListener: function(iframe, errorHandler) {
-    if (iframe.socialErrorListener)
-      return iframe.socialErrorListener;
-    return new SocialErrorListener(iframe, errorHandler);
   }
 };
 
 function schedule(callback) {
   Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
 }
 
 function CreateSocialStatusWidget(aId, aProvider) {
@@ -317,94 +311,16 @@ function CreateSocialMarkWidget(aId, aPr
       node.setAttribute("tooltiptext", menuLabel);
       node.setAttribute("observes", "Social:PageShareOrMark");
 
       return node;
     }
   });
 };
 
-// Error handling class used to listen for network errors in the social frames
-// and replace them with a social-specific error page
-function SocialErrorListener(iframe, errorHandler) {
-  this.setErrorMessage = errorHandler;
-  this.iframe = iframe;
-  iframe.socialErrorListener = this;
-  // Force a layout flush by calling .clientTop so that the docShell of this
-  // frame is created for the error listener
-  iframe.clientTop;
-  iframe.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                                   .getInterface(Ci.nsIWebProgress)
-                                   .addProgressListener(this,
-                                                        Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
-                                                        Ci.nsIWebProgress.NOTIFY_LOCATION);
-}
-
-SocialErrorListener.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.nsISupports]),
-
-  remove: function() {
-    this.iframe.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                                     .getInterface(Ci.nsIWebProgress)
-                                     .removeProgressListener(this);
-    delete this.iframe.socialErrorListener;
-  },
-
-  onStateChange: function SPL_onStateChange(aWebProgress, aRequest, aState, aStatus) {
-    let failure = false;
-    if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
-      if (aRequest instanceof Ci.nsIHttpChannel) {
-        try {
-          // Change the frame to an error page on 4xx (client errors)
-          // and 5xx (server errors).  responseStatus throws if it is not set.
-          failure = aRequest.responseStatus >= 400 &&
-                    aRequest.responseStatus < 600;
-        } catch (e) {
-          failure = aStatus == Components.results.NS_ERROR_CONNECTION_REFUSED;
-        }
-      }
-    }
-
-    // Calling cancel() will raise some OnStateChange notifications by itself,
-    // so avoid doing that more than once
-    if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
-      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-      let origin = this.iframe.getAttribute("origin");
-      if (origin) {
-        let provider = Social._getProviderFromOrigin(origin);
-        provider.errorState = "content-error";
-      }
-      this.setErrorMessage(aWebProgress.QueryInterface(Ci.nsIDocShell)
-                              .chromeEventHandler);
-    }
-  },
-
-  onLocationChange: function SPL_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
-    if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
-      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-      let origin = this.iframe.getAttribute("origin");
-      if (origin) {
-        let provider = Social._getProviderFromOrigin(origin);
-        if (!provider.errorState)
-          provider.errorState = "content-error";
-      }
-      schedule(function() {
-        this.setErrorMessage(aWebProgress.QueryInterface(Ci.nsIDocShell)
-                              .chromeEventHandler);
-      }.bind(this));
-    }
-  },
-
-  onProgressChange: function SPL_onProgressChange() {},
-  onStatusChange: function SPL_onStatusChange() {},
-  onSecurityChange: function SPL_onSecurityChange() {},
-};
-
 
 function sizeSocialPanelToContent(panel, iframe, requestedSize) {
   let doc = iframe.contentDocument;
   if (!doc || !doc.body) {
     return;
   }
   // We need an element to use for sizing our panel.  See if the body defines
   // an id for that element, otherwise use the body itself.
--- a/toolkit/components/social/MozSocialAPI.jsm
+++ b/toolkit/components/social/MozSocialAPI.jsm
@@ -293,26 +293,18 @@ function getChromeWindow(contentWin) {
 this.openChatWindow =
  function openChatWindow(contentWindow, provider, url, callback, mode) {
   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;
   }
 
-  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);
-    });
-  }
   let chatbox = Chat.open(contentWindow, provider.origin, provider.name,
-                          fullURI.spec, mode, undefined, thisCallback);
+                          fullURI.spec, mode);
   if (callback) {
     chatbox.promiseChatLoaded.then(() => {
       callback(chatbox.contentWindow);
     });
   }
 }
 
 this.closeAllChatWindows = function closeAllChatWindows(provider) {