Fixes poke-through scrollbars on Firefox/Mac.
Issue: 410
Patch by: rdayal,jgw
Review by: jgw,scottb,bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1152 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/Popup.gwt.xml b/user/src/com/google/gwt/user/Popup.gwt.xml
index 58f328c..b5d06bf 100644
--- a/user/src/com/google/gwt/user/Popup.gwt.xml
+++ b/user/src/com/google/gwt/user/Popup.gwt.xml
@@ -17,17 +17,26 @@
 <!-- This module is typically inherited via com.google.gwt.user.User        -->
 <!--                                                                        -->
 <module>
-	<inherits name="com.google.gwt.core.Core"/>
-	<inherits name="com.google.gwt.user.UserAgent"/>
+  <inherits name="com.google.gwt.core.Core"/>
+  <inherits name="com.google.gwt.user.UserAgent"/>
 
-	<!-- Fall through to this rule is the browser isn't IE -->
-	<replace-with class="com.google.gwt.user.client.ui.impl.PopupImpl">
-		<when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
-	</replace-with>
+  <!-- Fall through to this rule is the browser isn't IE or Mozilla -->
+  <replace-with class="com.google.gwt.user.client.ui.impl.PopupImpl">
+    <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
+  </replace-with>
 
-	<!-- IE has a completely different popup implementation -->
-	<replace-with class="com.google.gwt.user.client.ui.impl.PopupImplIE6">
-		<when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
-		<when-property-is name="user.agent" value="ie6"/>
-	</replace-with>
+  <!-- Mozilla needs a different implementation due to issue #410 -->
+  <replace-with class="com.google.gwt.user.client.ui.impl.PopupImplMozilla">
+    <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
+    <any>
+      <when-property-is name="user.agent" value="gecko"/>
+      <when-property-is name="user.agent" value="gecko1_8"/>
+    </any>
+  </replace-with>
+  
+  <!-- IE has a completely different popup implementation -->
+  <replace-with class="com.google.gwt.user.client.ui.impl.PopupImplIE6">
+    <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
+    <when-property-is name="user.agent" value="ie6"/>
+  </replace-with>
 </module>
diff --git a/user/src/com/google/gwt/user/client/ui/PopupPanel.java b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
index 18cf9d3..eb7d149 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -26,7 +26,11 @@
 /**
  * A panel that can "pop up" over other widgets. It overlays the browser's
  * client area (and any previously-created popups).
- * 
+ * <p/>
+ * The width and height of the PopupPanel cannot be explicitly set; they are
+ * determined by the PopupPanel's widget. Calls to {@link #setWidth(String)} and
+ * {@link #setHeight(String)} will call these methods on the PopupPanel's
+ * widget.
  * <p>
  * <img class='gallery' src='PopupPanel.png'/>
  * </p>
@@ -121,6 +125,10 @@
     return DOM.getElementPropertyInt(getElement(), "offsetTop");
   }
 
+  public String getTitle() {
+    return DOM.getElementProperty(getContainerElement(), "title");
+  }
+
   /**
    * Hides the popup. This has no effect if it is not currently visible.
    */
@@ -190,7 +198,7 @@
       }
     }
 
-    return !modal || (modal && eventTargetsPopup);
+    return !modal || eventTargetsPopup;
   }
 
   /**
@@ -233,10 +241,7 @@
   }
 
   public boolean remove(Widget w) {
-    if (!super.remove(w)) {
-      return false;
-    }
-    return true;
+    return super.remove(w);
   }
 
   public void removePopupListener(PopupListener listener) {
@@ -246,6 +251,19 @@
   }
 
   /**
+   * Calls {@link Widget#setHeight(String)} on this panel's widget. If this panel
+   * does not have a widget, then this call does nothing.
+   *
+   * @param height the new height of this panel's widget, in CSS units (e.g. "10px", "1em")
+   */
+  public void setHeight(String height) {
+    Widget childWidget = getWidget();
+    if (childWidget != null) {
+      childWidget.setHeight(height);
+    }
+  }
+
+  /**
    * Sets the popup's position relative to the browser's client area. The
    * popup's position may be set before calling {@link #show()}.
    * 
@@ -292,6 +310,19 @@
   }
 
   /**
+   * Calls {@link Widget#setWidth(String)} on this panel's widget. If this panel
+   * does not have a widget, then this call does nothing.
+   *
+   * @param width the new width of this panel's widget, in CSS units (e.g. "10px", "1em")
+   */
+   public void setWidth(String width) {
+    Widget childWidget = getWidget();
+    if (childWidget != null)  {
+      childWidget.setWidth(width);
+    }
+  }
+
+  /**
    * Shows the popup. It must have a child widget before this method is called.
    */
   public void show() {
@@ -306,6 +337,14 @@
     impl.onShow(getElement());
   }
 
+  protected Element getContainerElement() {
+    return impl.getContainerElement(getElement());
+  }
+
+  protected Element getStyleElement() {
+    return impl.getContainerElement(getElement());
+  }
+
   /**
    * This method is called when a widget is detached from the browser's
    * document. To receive notification before the PopupPanel is removed from the
@@ -339,4 +378,14 @@
       popupListeners.firePopupClosed(this, autoClosed);
     }
   }
+
+  public void setTitle(String title) {
+    Element containerElement = getContainerElement();
+     if (title == null || title.length() == 0) {
+       DOM.removeElementAttribute(containerElement, "title");
+     } else {
+       DOM.setElementAttribute(containerElement, "title", title);
+     }
+   }
 }
+
diff --git a/user/src/com/google/gwt/user/client/ui/UIObject.java b/user/src/com/google/gwt/user/client/ui/UIObject.java
index 676d8ac..11d7f74 100644
--- a/user/src/com/google/gwt/user/client/ui/UIObject.java
+++ b/user/src/com/google/gwt/user/client/ui/UIObject.java
@@ -319,7 +319,7 @@
    * @see #removeStyleName(String)
    */
   public void addStyleName(String style) {
-    setStyleName(element, style, true);
+    setStyleName(getStyleElement(), style, true);
   }
 
   /**
@@ -380,7 +380,7 @@
    * @see #removeStyleName(String)
    */
   public String getStyleName() {
-    String fullClassName = ensurePrimaryStyleName(element);
+    String fullClassName = ensurePrimaryStyleName(getStyleElement());
 
     // The base style name is always the first token of the full CSS class
     // name. There can be no leading whitespace in the class name, so it's not
@@ -418,7 +418,7 @@
    * @see #addStyleName(String)
    */
   public void removeStyleName(String style) {
-    setStyleName(element, style, false);
+    setStyleName(getStyleElement(), style, false);
   }
 
   /**
@@ -430,7 +430,8 @@
   public void setHeight(String height) {
     // This exists to deal with an inconsistency in IE's implementation where
     // it won't accept negative numbers in length measurements
-    assert extractLengthValue(height.trim().toLowerCase()) >= 0 : "CSS heights should not be negative";
+    assert extractLengthValue(height.trim().toLowerCase()) >= 0 :
+      "CSS heights should not be negative";
     DOM.setStyleAttribute(element, "height", height);
   }
 
@@ -470,7 +471,7 @@
    * @see #removeStyleName(String)
    */
   public void setStyleName(String style) {
-    resetStyleName(element, style);
+    resetStyleName(getStyleElement(), style);
   }
 
   /**
@@ -506,7 +507,8 @@
   public void setWidth(String width) {
     // This exists to deal with an inconsistency in IE's implementation where
     // it won't accept negative numbers in length measurements
-    assert extractLengthValue(width.trim().toLowerCase()) >= 0 : "CSS widths should not be negative";
+    assert extractLengthValue(width.trim().toLowerCase()) >= 0 :
+      "CSS widths should not be negative";
     DOM.setStyleAttribute(element, "width", width);
   }
 
@@ -551,6 +553,17 @@
   }
 
   /**
+   * Template method that returns the element to which style names will be
+   * applied. By default it returns the root element, but this method may be
+   * overridden to apply styles to a child element.
+   * 
+   * @return the element to which style names will be applied
+   */
+  protected Element getStyleElement() {
+    return element;
+  }
+
+  /**
    * Sets this object's browser element. UIObject subclasses must call this
    * method before attempting to call any other methods.
    * 
@@ -567,7 +580,7 @@
     }
 
     this.element = elem;
-    
+
     // We do not actually force the creation of a primary style name here.
     // Instead, we do it lazily -- when it is aboslutely required -- 
     // in getStyleName(), addStyleName(), and removeStyleName().
diff --git a/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java b/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
new file mode 100644
index 0000000..0b338d1
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/PopupImplMozilla.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui.impl;
+
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+
+/**
+ * Implementation class used by {@link com.google.gwt.user.client.ui.PopupPanel}.
+ * This implementation is identical to the implementation provided by
+ * {@link com.google.gwt.user.client.ui.impl.PopupImpl} in the case where
+ * Mozilla is NOT running on the Mac.
+ * <p/>
+ * A different implemention is provided for the Mac in order to prevent
+ * scrollbars underneath the PopupPanel from being rendered on top of the
+ * PopupPanel (issue #410). Unfortunately, the solution that fixes this
+ * problem for the Mac causes a problem with dragging a
+ * {@link com.google.gwt.user.client.ui.DialogBox} on Linux. While dragging
+ * the DialogBox (especially diagonally), it jitters significantly.
+ * <p/>
+ * We did not introduce a deferred binding rule for Mozilla on the Mac because
+ * this is the first instance in which we have a Mozilla-related bug fix which
+ * does not work on all platforms.
+ * <p/>
+ * This implementation can be simplified in the event that the jittering
+ * problem on Linux is fixed, or the scrollbar rendering problem on the Mac
+ * is fixed.
+ */
+public class PopupImplMozilla extends PopupImpl {
+
+  private Element containerElement = null;
+
+  public Element createElement() {
+    final Element outerElem = DOM.createDiv();
+
+    if (isMac()) {
+      // To solve the scrollbar rendering problem on the Mac, we have to make
+      // the PopupPanel a 'heavyweight' element by setting a style of
+      // 'overflow:auto' on the outermost div. This ensures that all of the
+      // elements that are children of this div will be rendered on top of
+      // any underlying scrollbars.
+
+      // Unfortunately, if we add a border to the outer div (which has
+      // a style of 'overflow:auto'), the border will not be rendered on top
+      // of underlying scrollbars. To get around this problem, we introduce an
+      // inner div which acts as the new containing element for the PopupPanel,
+      // and this element is the one to which all styling is applied to.
+      DOM.setInnerHTML(outerElem, "<div></div>");
+
+      // Mozilla determines the default stacking order for heavyweight elements
+      // by their order on the page. If the PopupPanel is declared before another
+      // heavyweight element on the page, then the  scrollbars of the heavyweight
+      // element will still shine through the PopupPanel. By setting
+      // 'overflow:auto' after all of the elements on the page have been rendered,
+      // the PopupPanel becomes the highest element in the stacking order.
+      DeferredCommand.addCommand(new Command() {
+        public void execute() {
+          DOM.setStyleAttribute(outerElem, "overflow", "auto");
+        }
+      });
+
+      // Keep a reference to the container element in order to avoid further
+      // calls to isMac() in getContainerElement().
+      containerElement = DOM.getFirstChild(outerElem);
+    } else {
+      containerElement = outerElem;
+    }
+
+    return outerElem;
+  }
+
+  public Element getContainerElement(Element popup) {
+    return containerElement;
+  }
+
+  private native boolean isMac() /*-{
+    if (navigator.userAgent.indexOf("Macintosh") != -1) {
+      return true;
+    }  
+    return false;
+  }-*/;
+}