Bug 797716 - improvements to the auto-resize code for social panels. r=felipe, a=gavin
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 05 Oct 2012 13:32:38 +1000
changeset 107063 fdc325aabb0e4e617479b1b2421eb027507ffdd1
parent 107062 7dd136adc519c91490002b0ae6c6a075d7a8ca5b
child 107064 49457c91c7c758ecb2a16d1f1f44b75917c266fe
push id2206
push usergsharp@mozilla.com
push dateFri, 05 Oct 2012 14:35:53 +0000
treeherdermozilla-aurora@52b3f1c7ca9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe, gavin
bugs797716
milestone17.0a2
Bug 797716 - improvements to the auto-resize code for social panels. r=felipe, a=gavin
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";