Bug 598331 - Stop using a single notificationbox for all browsers (part 2) [r=mfinkle r=stechz]
authorWesley Johnston <wjohnston@mozilla.com>
Wed, 10 Nov 2010 16:49:23 -0500
changeset 66992 f3f9ac82b7ec18a85bdcead846a044e6fd6e39d2
parent 66991 893e75bc777d799ffc79e0f7a1c91cdd7e0ad26a
child 66993 c142acd68289c00ac630bbaa882bbc94671c8fbc
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, stechz
bugs598331
Bug 598331 - Stop using a single notificationbox for all browsers (part 2) [r=mfinkle r=stechz]
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.css
mobile/chrome/content/browser.js
mobile/chrome/content/browser.xul
mobile/chrome/content/input.js
mobile/chrome/content/notification.xml
mobile/chrome/tests/browser_tabs.js
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -692,17 +692,18 @@ var BrowserUI = {
     this.activePanel = null;
     Browser.selectedTab = aTab;
   },
 
   undoCloseTab: function undoCloseTab(aIndex) {
     let tab = null;
     let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
     if (ss.getClosedTabCount(window) > (aIndex || 0)) {
-      tab = ss.undoCloseTab(window, aIndex || 0);
+      let chromeTab = ss.undoCloseTab(window, aIndex || 0);
+      tab = Browser.getTabFromChrome(chromeTab);
     }
     return tab;
   },
 
   isTabsVisible: function isTabsVisible() {
     // The _1, _2 and _3 are to make the js2 emacs mode happy
     let [leftvis,_1,_2,_3] = Browser.computeSidebarVisibility();
     return (leftvis > 0.002);
@@ -1116,40 +1117,45 @@ var BrowserUI = {
         break;
       }
     }
   }
 };
 
 var TapHighlightHelper = {
   get _overlay() {
-    delete this._overlay;
-    return this._overlay = document.getElementById("content-overlay");
+    if (Browser.selectedTab)
+      return Browser.selectedTab.overlay;
+    return null;
   },
 
   show: function show(aRects) {
+    let overlay = this._overlay;
+    if (!overlay)
+      return;
+
     let browser = getBrowser();
     let scroll = browser.getPosition();
 
     let canvasArea = aRects.reduce(function(a, b) {
       return a.expandToContain(b);
     }, new Rect(0, 0, 0, 0)).map(function(val) val * browser.scale)
                             .translate(-scroll.x, -scroll.y);
 
-    let overlay = this._overlay;
     overlay.setAttribute("width", canvasArea.width);
     overlay.setAttribute("height", canvasArea.height);
 
     let ctx = overlay.getContext("2d");
     ctx.save();
     ctx.translate(-canvasArea.left, -canvasArea.top);
     ctx.scale(browser.scale, browser.scale);
 
     overlay.setAttribute("left", canvasArea.left);
     overlay.setAttribute("top", canvasArea.top);
+    ctx.clearRect(0, 0, canvasArea.width, canvasArea.height);
     ctx.fillStyle = "rgba(0, 145, 255, .5)";
     for (let i = aRects.length - 1; i >= 0; i--) {
       let rect = aRects[i];
       ctx.fillRect(rect.left - scroll.x / browser.scale, rect.top - scroll.y / browser.scale, rect.width, rect.height);
     }
     ctx.restore();
     overlay.style.display = "block";
 
@@ -1157,17 +1163,17 @@ var TapHighlightHelper = {
     mozRequestAnimationFrame();
   },
 
   /**
    * Hide the highlight. aGuaranteeShowMsecs specifies how many milliseconds the
    * highlight should be shown before it disappears.
    */
   hide: function hide(aGuaranteeShowMsecs) {
-    if (this._overlay.style.display == "none")
+    if (!this._overlay || this._overlay.style.display == "none")
       return;
 
     this._guaranteeShow = Math.max(0, aGuaranteeShowMsecs);
     if (this._guaranteeShow) {
       // _shownAt is set once highlight has been painted
       if (this._shownAt)
         setTimeout(this._hide.bind(this),
                    Math.max(0, this._guaranteeShow - (mozAnimationStartTime - this._shownAt)));
--- a/mobile/chrome/content/browser.css
+++ b/mobile/chrome/content/browser.css
@@ -41,17 +41,18 @@ setting[type="integer"] {
 setting[type="control"] {
   -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-control");
 }
 
 setting[type="string"] {
   -moz-binding: url("chrome://browser/content/bindings/setting.xml#setting-string");
 }
 
-notificationbox {
+#browsers > notificationbox {
+  -moz-binding: url("chrome://browser/content/notification.xml#stacked-notificationbox");
   overflow: -moz-hidden-unscrollable;
 }
 
 notification {
     -moz-binding: url("chrome://browser/content/notification.xml#notification");
 }
 
 notification[type="geo"] {
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -136,17 +136,17 @@ function onDebugKeyPress(ev) {
     CommandUpdater.doCommand("cmd_menu");
     break;
 #ifndef MOZ_PLATFORM_MAEMO
   case p:
     function dispatchMagnifyEvent(aName, aDelta) {
       let e = document.createEvent("SimpleGestureEvent");
       e.initSimpleGestureEvent("MozMagnifyGesture"+aName, true, true, window, null,
                                0, 0, 0, 0, false, false, false, false, 0, null, 0, aDelta);
-      document.getElementById("inputhandler-overlay").dispatchEvent(e);
+      Browser.selectedTab.inputHandler.dispatchEvent(e);
     }
     dispatchMagnifyEvent("Start", 0);
 
     let frame = 0;
     let timer = new Util.Timeout();
     timer.interval(100, function() {
       dispatchMagnifyEvent("Update", 20);
       if (++frame > 10) {
@@ -188,23 +188,22 @@ var Browser = {
     } catch (e) {
       // XXX whatever is calling startup needs to dump errors!
       dump("###########" + e + "\n");
     }
 
     // XXX change
 
     /* handles dispatching clicks on browser into clicks in content or zooms */
-    let inputHandlerOverlay = document.getElementById("inputhandler-overlay");
-    inputHandlerOverlay.customDragger = new Browser.MainDragger();
-
-    let keySender = new ContentCustomKeySender(inputHandlerOverlay);
+    Elements.browsers.customDragger = new Browser.MainDragger();
+
+    let keySender = new ContentCustomKeySender(Elements.browsers);
     let mouseModule = new MouseModule();
     let gestureModule = new GestureModule();
-    let scrollWheelModule = new ScrollwheelModule(inputHandlerOverlay);
+    let scrollWheelModule = new ScrollwheelModule(Elements.browsers);
 
     ContentTouchHandler.init();
 
     // Warning, total hack ahead. All of the real-browser related scrolling code
     // lies in a pretend scrollbox here. Let's not land this as-is. Maybe it's time
     // to redo all the dragging code.
     this.contentScrollbox = Elements.browsers;
     this.contentScrollboxScroller = {
@@ -715,17 +714,17 @@ var Browser = {
       tab = this.getTabFromChrome(tab);
 
     if (!tab)
       return;
 
     if (this._selectedTab == tab) {
       // Deck does not update its selectedIndex when children
       // are removed. See bug 602708
-      Elements.browsers.selectedPanel = tab.browser;
+      Elements.browsers.selectedPanel = tab.notification;
       return;
     }
 
     TapHighlightHelper.hide();
 
     if (this._selectedTab) {
       this._selectedTab.pageScrollOffset = this.getScrollboxPosition(this.pageScrollboxScroller);
 
@@ -741,22 +740,20 @@ var Browser = {
 
     this._selectedTab = tab;
 
     // Lock the toolbar if the new tab is still loading
     if (this._selectedTab.isLoading())
       BrowserUI.lockToolbar();
 
     if (lastTab)
-      lastTab.updateBrowser(false);
+      lastTab.active = false;
 
     if (tab)
-      tab.updateBrowser(true);
-
-    document.getElementById("tabs").selectedTab = tab.chromeTab;
+      tab.active = true;
 
     if (!isFirstTab) {
       // Update all of our UI to reflect the new tab's location
       BrowserUI.updateURI();
       getIdentityHandler().checkIdentity();
 
       let event = document.createEvent("Events");
       event.initEvent("TabSelect", true, false);
@@ -1492,17 +1489,17 @@ const ContentTouchHandler = {
     this._contextMenu = null;
   },
 
   /**
    * Check if the event concern the browser content
    */
   _targetIsContent: function _targetIsContent(aEvent) {
     let target = aEvent.target;
-    return target && target.id == "inputhandler-overlay";
+    return target && target.classList.contains("inputHandler");
   },
 
   _dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY, aModifiers) {
     let aX = aX || 0;
     let aY = aY || 0;
     let browser = getBrowser();
     let pos = browser.transformClientToBrowser(aX, aY);
     browser.messageManager.sendAsyncMessage(aName, {
@@ -2464,16 +2461,17 @@ var OfflineApps = {
       this.offlineAppRequested(aMessage.json, aMessage.target);
     }
   }
 };
 
 function Tab(aURI, aParams) {
   this._id = null;
   this._browser = null;
+  this._notification = null;
   this._state = null;
   this._listener = null;
   this._loading = false;
   this._chromeTab = null;
   this._metadata = null;
   this.owner = null;
 
   // Set to 0 since new tabs that have not been viewed yet are good tabs to
@@ -2485,24 +2483,40 @@ function Tab(aURI, aParams) {
   this.create(aURI, aParams || {});
 }
 
 Tab.prototype = {
   get browser() {
     return this._browser;
   },
 
+  get notification() {
+    return this._notification;
+  },
+
   get chromeTab() {
     return this._chromeTab;
   },
 
   get metadata() {
     return this._metadata || kDefaultMetadata;
   },
 
+  get inputHandler() {
+    if (!this._notification)
+      return null;
+    return this._notification.inputHandler;
+  },
+  
+  get overlay() {
+    if (!this._notification)
+      return null;
+    return this._notification.overlay;    
+  },
+
   /** Update browser styles when the viewport metadata changes. */
   updateViewportMetadata: function updateViewportMetadata(aMetadata) {
     this._metadata = aMetadata;
     this.updateViewportSize();
   },
 
   /**
    * Update browser size when the metadata or the window size changes.
@@ -2612,16 +2626,17 @@ Tab.prototype = {
   },
 
   _createBrowser: function _createBrowser(aURI, aInsertBefore) {
     if (this._browser)
       throw "Browser already exists";
  
     // Create a notification box around the browser
     let notification = this._notification = document.createElement("notificationbox");
+    notification.classList.add("inputHandler");
 
     // Create the browser using the current width the dynamically size the height
     let browser = this._browser = document.createElement("browser");
     browser.setAttribute("class", "window-width window-height");
     this._chromeTab.linkedBrowser = browser;
 
     browser.setAttribute("type", "content");
 
@@ -2649,25 +2664,27 @@ Tab.prototype = {
     this._listener = new ProgressController(this);
     browser.webProgress.addProgressListener(this._listener, flags);
 
     return browser;
   },
 
   _destroyBrowser: function _destroyBrowser() {
     if (this._browser) {
+      let notification = this._notification;
       let browser = this._browser;
       browser.removeProgressListener(this._listener);
       browser.messageManager.sendAsyncMessage("Browser:Blur", {});
 
+      this._notification = null;
       this._browser = null;
       this._listener = null;
       this._loading = false;
 
-      Elements.browsers.removeChild(browser);
+      Elements.browsers.removeChild(notification);
     }
   },
 
   clampZoomLevel: function clampZoomLevel(aScale) {
     let browser = this._browser;
     let bounded = Util.clamp(aScale, ZoomManager.MIN, ZoomManager.MAX);
 
     let md = this.metadata;
@@ -2746,31 +2763,41 @@ Tab.prototype = {
     // set yet. This also blows up for async canvas draws.
     if (!browser.contentWindowWidth || !browser.contentWindowHeight)
       return;
 
     this._thumbnailWindowId = browser.contentWindowId;
     this._chromeTab.updateThumbnail(browser, browser.contentWindowWidth, browser.contentWindowHeight);
   },
 
-  updateBrowser: function updateBrowser(aDisplay) {
+  set active(aActive) {
+    if (!this._browser)
+      return;
+
     let notification = this._notification;
     let browser = this._browser;
-    if (aDisplay) {
+
+    if (aActive) {
       browser.setAttribute("type", "content-primary");
-      notification.style.display = "";
+      Elements.browsers.selectedPanel = notification;
       browser.messageManager.sendAsyncMessage("Browser:Focus", {});
+      document.getElementById("tabs").selectedTab = this._chromeTab;
     }
     else {
       browser.setAttribute("type", "content");
-      notification.style.display = "none";
       browser.messageManager.sendAsyncMessage("Browser:Blur", {});
     }
   },
 
+  get active() {
+    if (!this._browser)
+      return false;
+    return this._browser.getAttribute("type") == "content-primary";
+  },
+
   toString: function() {
     return "[Tab " + (this._browser ? this._browser.currentURI.spec : "(no browser)") + "]";
   }
 };
 
 // Helper used to hide IPC / non-IPC differences for rendering to a canvas
 function rendererFactory(aBrowser, aCanvas) {
   let wrapper = {};
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -272,22 +272,18 @@
                 </toolbar>
               </box>
             </box>
 
             <notificationbox id="notifications" class="window-width"/>
 
             <!-- Content viewport -->
             <vbox id="content-viewport" class="window-width window-height">
-              <stack id="content-stack" flex="1">
               <!-- Content viewport -->
-                <deck id="browsers" flex="1"/>
-                <html:canvas id="content-overlay" style="display: none; z-index: 1000;" left="0" top="0"/>
-                <html:div id="inputhandler-overlay" style="z-index: 1001" tabindex="-1"/>
-              </stack>
+              <deck id="browsers" flex="1"/>
               <box id="content-navigator-spacer" hidden="true"/>
             </vbox>
           </vbox>
         </scrollbox>
 
         <!-- popup for content navigator helper -->
         <vbox id="content-navigator" class="window-width" top="0" spacer="content-navigator-spacer">
           <arrowscrollbox id="form-helper-autofill" collapsed="true" align="center" flex="1" orient="horizontal"
--- a/mobile/chrome/content/input.js
+++ b/mobile/chrome/content/input.js
@@ -520,27 +520,27 @@ var ScrollUtils = {
     let qinterface = null;
 
     for (; elem; elem = elem.parentNode) {
       try {
         if (elem.scrollBoxObject) {
           scrollbox = elem;
           qinterface = elem.scrollBoxObject;
           break;
+        } else if (elem.customDragger) {
+          scrollbox = elem;
+          break;
         } else if (elem.boxObject) {
           let qi = (elem._cachedSBO) ? elem._cachedSBO
                                      : elem.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
           if (qi) {
             scrollbox = elem;
             scrollbox._cachedSBO = qinterface = qi;
             break;
           }
-        } else if (elem.customDragger) {
-          scrollbox = elem;
-          break;
         }
       } catch (e) { /* we aren't here to deal with your exceptions, we'll just keep
                        traversing until we find something more well-behaved, as we
                        prefer default behaviour to whiny scrollers. */ }
     }
     return [scrollbox, qinterface, (scrollbox ? (scrollbox.customDragger || this._defaultDragger) : null)];
   },
 
@@ -1067,18 +1067,17 @@ GestureModule.prototype = {
       return;
 
     // Cancel other touch sequence events, and be courteous by allowing them
     // to say no.
     let event = document.createEvent("Events");
     event.initEvent("CancelTouchSequence", true, true);
     let success = aEvent.target.dispatchEvent(event);
 
-    if (!success || (aEvent.target instanceof XULElement) ||
-        !Browser.selectedTab.allowZoom)
+    if (!success || (aEvent.target instanceof XULElement) || !Browser.selectedTab.allowZoom)
       return;
 
     // create the AnimatedZoom object for fast arbitrary zooming
     this._pinchZoom = AnimatedZoom;
     this._pinchStartRect = AnimatedZoom.getStartRect();
     this._pinchDelta = 0;
 
     let browser = getBrowser();
--- a/mobile/chrome/content/notification.xml
+++ b/mobile/chrome/content/notification.xml
@@ -3,17 +3,51 @@
   <!ENTITY % notificationDTD SYSTEM
    "chrome://browser/locale/notification.dtd">
   %notificationDTD;
 ]>
 
 <bindings
     xmlns="http://www.mozilla.org/xbl"
     xmlns:xbl="http://www.mozilla.org/xbl"
-    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+    xmlns:html="http://www.w3.org/1999/xhtml">
+
+  <binding id="stacked-notificationbox" extends="chrome://global/content/bindings/notification.xml#notificationbox">
+    <content>
+      <xul:stack xbl:inherits="hidden=notificationshidden">
+        <xul:spacer/>
+        <children includes="notification"/>
+      </xul:stack>
+      <xul:stack flex="1">
+        <children/>
+        <html:canvas anonid="content-overlay"/>
+        <html:div flex="1" class="input-overlay" anonid="input-overlay"/>
+      </xul:stack>
+    </content>
+    <implementation>
+      <property name="inputHandler">
+        <getter>
+          return document.getAnonymousElementByAttribute(this, "anonid", "input-overlay");
+        </getter>
+      </property>
+
+      <property name="overlay">
+        <getter>
+          return document.getAnonymousElementByAttribute(this, "anonid", "content-overlay");
+        </getter>
+      </property>
+
+      <property name="customDragger">
+        <getter>
+          return this.parentNode.customDragger;
+        </getter>
+      </property>
+    </implementation>
+  </binding>
 
   <binding id="notification" extends="chrome://global/content/bindings/notification.xml#notification">
     <resources>
       <stylesheet src="chrome://browser/skin/notification.css"/>
     </resources>
 
     <content>
       <xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
--- a/mobile/chrome/tests/browser_tabs.js
+++ b/mobile/chrome/tests/browser_tabs.js
@@ -36,60 +36,60 @@ function load_tabs() {
 
   //Check tab switch
   new_tab_01.browser.addEventListener("load", tab_switch_01, true);
 }
 
 function tab_switch_01() {
   BrowserUI.selectTab(new_tab_01);
   is(Browser.selectedTab.browser.currentURI.spec, testURL_01, "Tab Switch 01 URL Matches");
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 
   //Add new tab
   new_tab_02 =  Browser.addTab(testURL_02,false);
   new_tab_02.browser.addEventListener("load", tab_switch_02, true);
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 }
 
 function tab_switch_02() {
   BrowserUI.selectTab(new_tab_02);
   is(Browser.selectedTab.browser.currentURI.spec, testURL_02, "Tab Switch 02 URL Matches");
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 
   BrowserUI.selectTab(new_tab_01);
   is(Browser.selectedTab.browser.currentURI.spec, testURL_01, "Tab Switch 01 URL Matches");
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 
   //Add new tab
   new_tab_03 =  Browser.addTab(testURL_03, true, new_tab_01);
   new_tab_03.browser.addEventListener("load", tab_switch_03, true);
 }
 
 function tab_switch_03() {
   is(Browser.selectedTab.browser.currentURI.spec, testURL_03, "Tab Switch 03 URL Matches"); 
   is(new_tab_03.owner, new_tab_01, "Tab 03 owned by tab 01");
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 
   Browser.closeTab(new_tab_03);
   is(Browser.selectedTab, new_tab_01, "Closing tab 03 returns to owner");
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 
   new_tab_03 =  Browser.addTab(testURL_03, true, new_tab_01);
   new_tab_03.browser.addEventListener("load", tab_switch_04, true);
 }
 
 function tab_switch_04() {
   is(Browser.selectedTab.browser.currentURI.spec, testURL_03, "Tab Switch 03 URL Matches"); 
   is(new_tab_03.owner, new_tab_01, "Tab 03 owned by tab 01");
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 
   Browser.closeTab(new_tab_01);
   is(Browser.selectedTab, new_tab_03, "Closing tab 01 keeps selectedTab");
   is(new_tab_03.owner, null, "Closing tab 01 nulls tab3 owner");
-  is(Browser.selectedTab.browser, Elements.browsers.selectedPanel, "Deck has correct browser");
+  is(Browser.selectedTab.notification, Elements.browsers.selectedPanel, "Deck has correct browser");
 
   done();
 }
 
 function done() {
   //Close new tab 
   Browser.closeTab(new_tab_01);
   Browser.closeTab(new_tab_02);