Bug 797716 - improvements to the auto-resize code for social panels. r=felipe
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 05 Oct 2012 13:32:38 +1000
changeset 109312 b287821e8d5f5118a5c5c8e4dab6a0c21ecd01ca
parent 109311 e31bf74c3a405a0df4d0cdad48373c75a4b5904c
child 109313 a05bad13ec397418972b97079a0dd2dcacbf5e15
push id23619
push useremorley@mozilla.com
push dateFri, 05 Oct 2012 10:54:02 +0000
treeherdermozilla-central@3b458f4e0f42 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs797716
milestone18.0a1
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
Bug 797716 - improvements to the auto-resize code for social panels. r=felipe
browser/base/content/browser-social.js
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -1,12 +1,16 @@
 // 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 minimum sizes for the auto-resize panel code.
+const PANEL_MIN_HEIGHT = 300;
+const PANEL_MIN_WIDTH = 330;
+
 let SocialUI = {
   // Called on delayed startup to initialize UI
   init: function SocialUI_init() {
     Services.obs.addObserver(this, "social:pref-changed", false);
     Services.obs.addObserver(this, "social:ambient-notification-changed", false);
     Services.obs.addObserver(this, "social:profile-changed", false);
 
     Services.prefs.addObserver("social.sidebar.open", this, false);
@@ -194,52 +198,59 @@ let SocialChatBar = {
     if (!this.canShow)
       this.chatbar.removeAll();
   }
 }
 
 function sizeSocialPanelToContent(iframe) {
   // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
   let doc = iframe.contentDocument;
-  if (!doc) {
+  if (!doc || !doc.body) {
     return;
   }
-  // "notif" is an implementation detail that we should get rid of
-  // eventually
-  let body = doc.getElementById("notif") || doc.body;
-  if (!body) {
-    return;
-  }
-  // XXX - do we want a max for width and height here?
-  // The 300 and 330 defaults also seem arbitrary, so should be revisited.
-  // BUT - for at least one provider, the scrollWidth/offsetWidth/css width
-  // isn't set appropriately, so the 330 is "fixed" for now...
-  iframe.style.width = "330px";
-  // offsetHeight doesn't include margins, so account for that.
+  let body = doc.body;
+  // offsetHeight/Width don't include margins, so account for that.
   let cs = doc.defaultView.getComputedStyle(body);
   let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom);
-  let height = computedHeight || 300;
+  let height = Math.max(computedHeight, PANEL_MIN_HEIGHT);
   iframe.style.height = height + "px";
+  let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight);
+  let width = Math.max(computedWidth, PANEL_MIN_WIDTH);
+  iframe.style.width = width + "px";
+}
+
+function DynamicResizeWatcher() {
+  this._mutationObserver = null;
 }
 
-function setupDynamicPanelResizer(iframe) {
-  let doc = iframe.contentDocument;
-  let mo = new iframe.contentWindow.MutationObserver(function(mutations) {
+DynamicResizeWatcher.prototype = {
+  start: function DynamicResizeWatcher_start(iframe) {
+    this.stop(); // just in case...
+    let doc = iframe.contentDocument;
+    this._mutationObserver = new iframe.contentWindow.MutationObserver(function(mutations) {
+      sizeSocialPanelToContent(iframe);
+    });
+    // Observe anything that causes the size to change.
+    let config = {attributes: true, characterData: true, childList: true, subtree: true};
+    this._mutationObserver.observe(doc, config);
+    // and since this may be setup after the load event has fired we do an
+    // initial resize now.
     sizeSocialPanelToContent(iframe);
-  });
-  // Observe anything that causes the size to change.
-  let config = {attributes: true, characterData: true, childList: true, subtree: true};
-  mo.observe(doc, config);
-  doc.addEventListener("unload", function() {
-    if (mo) {
-      mo.disconnect();
-      mo = null;
+  },
+  stop: function DynamicResizeWatcher_stop() {
+    if (this._mutationObserver) {
+      try {
+        this._mutationObserver.disconnect();
+      } catch (ex) {
+        // may get "TypeError: can't access dead object" which seems strange,
+        // but doesn't seem to indicate a real problem, so ignore it...
+      }
+      this._mutationObserver = null;
     }
-  }, false);
-  sizeSocialPanelToContent(iframe);
+  }
 }
 
 let SocialFlyout = {
   get panel() {
     return document.getElementById("social-flyout-panel");
   },
 
   dispatchPanelEvent: function(name) {
@@ -267,32 +278,37 @@ let SocialFlyout = {
     panel.hidePopup();
     if (!panel.firstChild)
       return
     panel.removeChild(panel.firstChild);
   },
 
   onShown: function(aEvent) {
     let iframe = this.panel.firstChild;
+    this._dynamicResizer = new DynamicResizeWatcher();
     iframe.docShell.isActive = true;
     iframe.docShell.isAppTab = true;
     if (iframe.contentDocument.readyState == "complete") {
+      this._dynamicResizer.start(iframe);
       this.dispatchPanelEvent("socialFrameShow");
     } else {
       // first time load, wait for load and dispatch after load
       iframe.addEventListener("load", function panelBrowserOnload(e) {
         iframe.removeEventListener("load", panelBrowserOnload, true);
         setTimeout(function() {
+          SocialFlyout._dynamicResizer.start(iframe);
           SocialFlyout.dispatchPanelEvent("socialFrameShow");
         }, 0);
       }, true);
     }
   },
 
   onHidden: function(aEvent) {
+    this._dynamicResizer.stop();
+    this._dynamicResizer = null;
     this.panel.firstChild.docShell.isActive = false;
     this.dispatchPanelEvent("socialFrameHide");
   },
 
   open: function(aURL, yOffset, aCallback) {
     if (!Social.provider)
       return;
     let panel = this.panel;
@@ -300,17 +316,16 @@ let SocialFlyout = {
       this._createFrame();
     panel.hidden = false;
     let iframe = panel.firstChild;
 
     let src = iframe.getAttribute("src");
     if (src != aURL) {
       iframe.addEventListener("load", function documentLoaded() {
         iframe.removeEventListener("load", documentLoaded, true);
-        setupDynamicPanelResizer(iframe);
         if (aCallback) {
           try {
             aCallback(iframe.contentWindow);
           } catch(e) {
             Cu.reportError(e);
           }
         }
       }, true);
@@ -533,16 +548,17 @@ let SocialShareButton = {
 };
 
 var SocialToolbar = {
   // Called once, after window load, when the Social.provider object is initialized
   init: function SocialToolbar_init() {
     this.button.setAttribute("image", Social.provider.iconURL);
     this.updateButton();
     this.updateProfile();
+    this._dynamicResizer = new DynamicResizeWatcher();
   },
 
   get button() {
     return document.getElementById("social-provider-button");
   },
 
   updateButtonHiddenState: function SocialToolbar_updateButtonHiddenState() {
     let tbi = document.getElementById("social-toolbar-item");
@@ -673,36 +689,38 @@ var SocialToolbar = {
     }
 
     function dispatchPanelEvent(name) {
       let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
       evt.initCustomEvent(name, true, true, {});
       notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
     }
 
+    let dynamicResizer = this._dynamicResizer;
     panel.addEventListener("popuphidden", function onpopuphiding() {
       panel.removeEventListener("popuphidden", onpopuphiding);
       aToolbarButtonBox.removeAttribute("open");
+      dynamicResizer.stop();
       notificationFrame.docShell.isActive = false;
       dispatchPanelEvent("socialFrameHide");
     });
 
     panel.addEventListener("popupshown", function onpopupshown() {
       panel.removeEventListener("popupshown", onpopupshown);
       aToolbarButtonBox.setAttribute("open", "true");
       notificationFrame.docShell.isActive = true;
       notificationFrame.docShell.isAppTab = true;
       if (notificationFrame.contentDocument.readyState == "complete") {
-        setupDynamicPanelResizer(notificationFrame);
+        dynamicResizer.start(notificationFrame);
         dispatchPanelEvent("socialFrameShow");
       } else {
         // first time load, wait for load and dispatch after load
         notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
           notificationFrame.removeEventListener("load", panelBrowserOnload, true);
-          setupDynamicPanelResizer(notificationFrame);
+          dynamicResizer.start(notificationFrame);
           setTimeout(function() {
             dispatchPanelEvent("socialFrameShow");
           }, 0);
         }, true);
       }
     });
 
     let imageId = aToolbarButtonBox.getAttribute("id") + "-image";