]> source.dussan.org Git - vaadin-framework.git/commitdiff
Merge remote branch 'origin/6.8'
authorLeif Åstrand <leif@vaadin.com>
Fri, 4 May 2012 06:05:58 +0000 (09:05 +0300)
committerLeif Åstrand <leif@vaadin.com>
Fri, 4 May 2012 06:05:58 +0000 (09:05 +0300)
Conflicts:
src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java
src/com/vaadin/terminal/gwt/client/BrowserInfo.java
src/com/vaadin/terminal/gwt/client/HistoryImplIEVaadin.java
src/com/vaadin/terminal/gwt/client/ui/VButton.java
src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java
src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java
src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java
src/com/vaadin/terminal/gwt/client/ui/VNotification.java
src/com/vaadin/terminal/gwt/client/ui/VView.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java
src/com/vaadin/terminal/gwt/server/RequestTimer.java
src/com/vaadin/ui/Button.java
tests/test.xml
tests/testbench/com/vaadin/tests/components/TouchScrollables.java

26 files changed:
1  2 
src/com/vaadin/data/util/ContainerHierarchicalWrapper.java
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
src/com/vaadin/terminal/gwt/client/BrowserInfo.java
src/com/vaadin/terminal/gwt/client/ComputedStyle.java
src/com/vaadin/terminal/gwt/client/Util.java
src/com/vaadin/terminal/gwt/client/VBrowserDetails.java
src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java
src/com/vaadin/terminal/gwt/client/ui/button/ButtonConnector.java
src/com/vaadin/terminal/gwt/client/ui/button/ButtonState.java
src/com/vaadin/terminal/gwt/client/ui/button/VButton.java
src/com/vaadin/terminal/gwt/client/ui/datefield/VDateFieldCalendar.java
src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java
src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java
src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java
src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/RequestTimer.java
src/com/vaadin/ui/Button.java
src/com/vaadin/ui/Table.java
tests/test.xml
tests/testbench/com/vaadin/tests/components/TouchScrollables.java
tests/testbench/com/vaadin/tests/components/combobox/GridLayoutComboBoxZoomOut.java
tests/testbench/com/vaadin/tests/components/datefield/InlineDateFields.java
tests/testbench/com/vaadin/tests/layouts/layouttester/LayoutTesterApplication.html

index be6d5781129748570a2d0cd9e436eaa20efadebf,dcf52827ed5aaaad1ec6d3cba479eb0df39a6a93..739c232a721eb0c67aeb559219ce81c9226f7655
@@@ -1034,13 -990,11 +1034,13 @@@ public class ApplicationConnection 
                      json.getValueMap("typeMappings"), widgetSet);
          }
  
 +        handleUIDLDuration.logDuration(
 +                " * Handling type mappings from server completed", 10);
          /*
-          * Hook for TestBench to get details about server status
+          * Hook for e.g. TestBench to get details about server peformance
           */
-         if (json.containsKey("tbss")) {
-             testBenchServerStatus = json.getValueMap("tbss");
+         if (json.containsKey("timings")) {
+             serverTimingInfo = json.getValueMap("timings");
          }
  
          Command c = new Command() {
index ef1dc481b1a39205cf17c0a1a934b27db5e45eaa,8ec610c089d90b4fa7e7c52dac9f13900d48a24d..064f17530183dc2d602a0084e85b29ab43e39da6
@@@ -301,15 -399,76 +308,70 @@@ public class BrowserInfo 
      }
  
      /**
 -     * Returns the current date and time of the browser. This will not be
 -     * entirely accurate due to varying network latencies, but should provide a
 -     * close-enough value for most cases.
 +     * Indicates whether the browser might require juggling to properly update
 +     * sizes inside elements with overflow: auto.
       * 
 -     * @return the current date and time of the browser.
 +     * @return <code>true</code> if the browser requires the workaround,
 +     *         otherwise <code>false</code>
       */
 -    public Date getCurrentDate() {
 -        return new Date();
 -    }
 -
 -    /**
 -     * @return true if the browser runs on a touch based device.
 -     */
 -    public boolean isTouchDevice() {
 -        return touchDevice;
 +    public boolean requiresOverflowAutoFix() {
 +        return (getWebkitVersion() > 0 || getOperaVersion() >= 11)
 +                && Util.getNativeScrollbarSize() > 0;
      }
  
+     /**
+      * Checks if the browser is run on iOS
+      * 
+      * @return true if the browser is run on iOS, false otherwise
+      */
+     public boolean isIOS() {
+         return browserDetails.isIOS();
+     }
+     /**
+      * Checks if the browser is run on Android
+      * 
+      * @return true if the browser is run on Android, false otherwise
+      */
+     public boolean isAndroid() {
+         return browserDetails.isAndroid();
+     }
+     /**
+      * Checks if the browser is capable of handling scrolling natively or if a
+      * touch scroll helper is needed for scrolling.
+      * 
+      * @return true if browser needs a touch scroll helper, false if the browser
+      *         can handle scrolling natively
+      */
+     public boolean requiresTouchScrollDelegate() {
+         if (!isTouchDevice()) {
+             return false;
+         }
+         if (isAndroid() && isWebkit() && getWebkitVersion() < 534) {
+             return true;
+         }
+         // if (isIOS() && isWebkit() && getWebkitVersion() < ???) {
+         // return true;
+         // }
+         return false;
+     }
+     /**
+      * Tests if this is an Android devices with a broken scrollTop
+      * implementation
+      * 
+      * @return true if scrollTop cannot be trusted on this device, false
+      *         otherwise
+      */
+     public boolean isAndroidWithBrokenScrollTop() {
+         return isAndroid()
+                 && (getOperatingSystemMajorVersion() == 3 || getOperatingSystemMajorVersion() == 4);
+     }
+     private int getOperatingSystemMajorVersion() {
+         return browserDetails.getOperatingSystemMajorVersion();
+     }
  }
index 89e106f063aa31c545e478b0bb89c4e2a2ecebea,1212447a6528f74822ddbb7072b6cb04264e510b..6e0417149c7e46591b7f6ae88585e32eaa998ea4
@@@ -324,33 -370,45 +395,71 @@@ public class VBrowserDetails implement
       * @return true if run on Linux, false otherwise
       */
      public boolean isLinux() {
-         return isLinux;
+         return os == OperatingSystem.LINUX;
+     }
+     /**
+      * Tests if the browser is run on Android.
+      * 
+      * @return true if run on Android, false otherwise
+      */
+     public boolean isAndroid() {
+         return os == OperatingSystem.ANDROID;
+     }
+     /**
+      * Tests if the browser is run in iOS.
+      * 
+      * @return true if run in iOS, false otherwise
+      */
+     public boolean isIOS() {
+         return os == OperatingSystem.IOS;
+     }
+     /**
+      * Returns the major version of the operating system. Currently only
+      * supported for mobile devices (iOS/Android)
+      * 
+      * @return The major version or -1 if unknown
+      */
+     public int getOperatingSystemMajorVersion() {
+         return osMajorVersion;
+     }
+     /**
+      * Returns the minor version of the operating system. Currently only
+      * supported for mobile devices (iOS/Android)
+      * 
+      * @return The minor version or -1 if unknown
+      */
+     public int getOperatingSystemMinorVersion() {
+         return osMinorVersion;
      }
  
 +    /**
 +     * Checks if the browser is so old that it simply won't work with a Vaadin
 +     * application. NOTE that the browser might still be capable of running
 +     * Crome Frame, so you might still want to check
 +     * {@link #isChromeFrameCapable()} if this returns true.
 +     * 
 +     * @return true if the browser won't work, false if not the browser is
 +     *         supported or might work
 +     */
 +    public boolean isTooOldToFunctionProperly() {
 +        if (isIE() && getBrowserMajorVersion() < 8) {
 +            return true;
 +        }
 +        if (isSafari() && getBrowserMajorVersion() < 5) {
 +            return true;
 +        }
 +        if (isFirefox() && getBrowserMajorVersion() < 4) {
 +            return true;
 +        }
 +        if (isOpera() && getBrowserMajorVersion() < 11) {
 +            return true;
 +        }
 +
 +        return false;
 +    }
 +
  }
index 62a5e8ac8b2799f2c86fb52cfe20b572a93e3c4e,0000000000000000000000000000000000000000..a555ecd392a22bf5a71ac2f690650f38ecefff1f
mode 100644,000000..100644
--- /dev/null
@@@ -1,134 -1,0 +1,138 @@@
-         getWidget().setText(getState().getCaption());
 +/*
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +
 +package com.vaadin.terminal.gwt.client.ui.button;
 +
 +import com.google.gwt.core.client.GWT;
 +import com.google.gwt.event.dom.client.BlurEvent;
 +import com.google.gwt.event.dom.client.BlurHandler;
 +import com.google.gwt.event.dom.client.ClickEvent;
 +import com.google.gwt.event.dom.client.ClickHandler;
 +import com.google.gwt.event.dom.client.FocusEvent;
 +import com.google.gwt.event.dom.client.FocusHandler;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.terminal.gwt.client.EventHelper;
 +import com.vaadin.terminal.gwt.client.MouseEventDetails;
 +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder;
 +import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc;
 +import com.vaadin.terminal.gwt.client.communication.RpcProxy;
 +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
 +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector;
 +import com.vaadin.terminal.gwt.client.ui.Connect;
 +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle;
 +import com.vaadin.terminal.gwt.client.ui.Icon;
 +import com.vaadin.ui.Button;
 +
 +@Connect(value = Button.class, loadStyle = LoadStyle.EAGER)
 +public class ButtonConnector extends AbstractComponentConnector implements
 +        BlurHandler, FocusHandler, ClickHandler {
 +
 +    private ButtonServerRpc rpc = RpcProxy.create(ButtonServerRpc.class, this);
 +    private FocusAndBlurServerRpc focusBlurProxy = RpcProxy.create(
 +            FocusAndBlurServerRpc.class, this);
 +
 +    private HandlerRegistration focusHandlerRegistration = null;
 +    private HandlerRegistration blurHandlerRegistration = null;
 +
 +    @Override
 +    public boolean delegateCaptionHandling() {
 +        return false;
 +    }
 +
 +    @Override
 +    public void init() {
 +        super.init();
 +        getWidget().addClickHandler(this);
 +        getWidget().client = getConnection();
 +    }
 +
 +    @Override
 +    public void onStateChanged(StateChangeEvent stateChangeEvent) {
 +        super.onStateChanged(stateChangeEvent);
 +        focusHandlerRegistration = EventHelper.updateFocusHandler(this,
 +                focusHandlerRegistration);
 +        blurHandlerRegistration = EventHelper.updateBlurHandler(this,
 +                blurHandlerRegistration);
 +        // Set text
++        if (getState().isHtmlContentAllowed()) {
++            getWidget().setHtml(getState().getCaption());
++        } else {
++            getWidget().setText(getState().getCaption());
++        }
 +
 +        // handle error
 +        if (null != getState().getErrorMessage()) {
 +            if (getWidget().errorIndicatorElement == null) {
 +                getWidget().errorIndicatorElement = DOM.createSpan();
 +                getWidget().errorIndicatorElement
 +                        .setClassName("v-errorindicator");
 +            }
 +            getWidget().wrapper.insertBefore(getWidget().errorIndicatorElement,
 +                    getWidget().captionElement);
 +
 +        } else if (getWidget().errorIndicatorElement != null) {
 +            getWidget().wrapper.removeChild(getWidget().errorIndicatorElement);
 +            getWidget().errorIndicatorElement = null;
 +        }
 +
 +        if (getState().getIcon() != null) {
 +            if (getWidget().icon == null) {
 +                getWidget().icon = new Icon(getConnection());
 +                getWidget().wrapper.insertBefore(getWidget().icon.getElement(),
 +                        getWidget().captionElement);
 +            }
 +            getWidget().icon.setUri(getState().getIcon().getURL());
 +        } else {
 +            if (getWidget().icon != null) {
 +                getWidget().wrapper.removeChild(getWidget().icon.getElement());
 +                getWidget().icon = null;
 +            }
 +        }
 +
 +        getWidget().clickShortcut = getState().getClickShortcutKeyCode();
 +    }
 +
 +    @Override
 +    protected Widget createWidget() {
 +        return GWT.create(VButton.class);
 +    }
 +
 +    @Override
 +    public VButton getWidget() {
 +        return (VButton) super.getWidget();
 +    }
 +
 +    @Override
 +    public ButtonState getState() {
 +        return (ButtonState) super.getState();
 +    }
 +
 +    public void onFocus(FocusEvent event) {
 +        // EventHelper.updateFocusHandler ensures that this is called only when
 +        // there is a listener on server side
 +        focusBlurProxy.focus();
 +    }
 +
 +    public void onBlur(BlurEvent event) {
 +        // EventHelper.updateFocusHandler ensures that this is called only when
 +        // there is a listener on server side
 +        focusBlurProxy.blur();
 +    }
 +
 +    public void onClick(ClickEvent event) {
 +        if (getState().isDisableOnClick()) {
 +            getWidget().setEnabled(false);
 +            rpc.disableOnClick();
 +        }
 +
 +        // Add mouse details
 +        MouseEventDetails details = MouseEventDetailsBuilder
 +                .buildMouseEventDetails(event.getNativeEvent(), getWidget()
 +                        .getElement());
 +        rpc.click(details);
 +
 +    }
 +}
index f26cdae0c665cc8c5ed128ee044b40232356010f,0000000000000000000000000000000000000000..fdc053b3aea1941892c94237dc9428255f6469c3
mode 100644,000000..100644
--- /dev/null
@@@ -1,65 -1,0 +1,95 @@@
 +/*
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +
 +package com.vaadin.terminal.gwt.client.ui.button;
 +
 +import com.vaadin.terminal.gwt.client.ComponentState;
 +import com.vaadin.ui.Button;
 +
 +/**
 + * Shared state for Button and NativeButton.
 + * 
 + * @see ComponentState
 + * 
 + * @since 7.0
 + */
 +public class ButtonState extends ComponentState {
 +    private boolean disableOnClick = false;
 +    private int clickShortcutKeyCode = 0;
++    /**
++     * If caption should be rendered in HTML
++     */
++    private boolean htmlContentAllowed = false;
 +
 +    /**
 +     * Checks whether the button should be disabled on the client side on next
 +     * click.
 +     * 
 +     * @return true if the button should be disabled on click
 +     */
 +    public boolean isDisableOnClick() {
 +        return disableOnClick;
 +    }
 +
 +    /**
 +     * Sets whether the button should be disabled on the client side on next
 +     * click.
 +     * 
 +     * @param disableOnClick
 +     *            true if the button should be disabled on click
 +     */
 +    public void setDisableOnClick(boolean disableOnClick) {
 +        this.disableOnClick = disableOnClick;
 +    }
 +
 +    /**
 +     * Returns the key code for activating the button via a keyboard shortcut.
 +     * 
 +     * See {@link Button#setClickShortcut(int, int...)} for more information.
 +     * 
 +     * @return key code or 0 for none
 +     */
 +    public int getClickShortcutKeyCode() {
 +        return clickShortcutKeyCode;
 +    }
 +
 +    /**
 +     * Sets the key code for activating the button via a keyboard shortcut.
 +     * 
 +     * See {@link Button#setClickShortcut(int, int...)} for more information.
 +     * 
 +     * @param clickShortcutKeyCode
 +     *            key code or 0 for none
 +     */
 +    public void setClickShortcutKeyCode(int clickShortcutKeyCode) {
 +        this.clickShortcutKeyCode = clickShortcutKeyCode;
 +    }
 +
++    /**
++     * Set whether the caption text is rendered as HTML or not. You might need
++     * to retheme button to allow higher content than the original text style.
++     * 
++     * If set to true, the captions are passed to the browser as html and the
++     * developer is responsible for ensuring no harmful html is used. If set to
++     * false, the content is passed to the browser as plain text.
++     * 
++     * @param htmlContentAllowed
++     *            <code>true</code> if caption is rendered as HTML,
++     *            <code>false</code> otherwise
++     */
++    public void setHtmlContentAllowed(boolean htmlContentAllowed) {
++        this.htmlContentAllowed = htmlContentAllowed;
++    }
++
++    /**
++     * Return HTML rendering setting.
++     * 
++     * @return <code>true</code> if the caption text is to be rendered as HTML,
++     *         <code>false</code> otherwise
++     */
++    public boolean isHtmlContentAllowed() {
++        return htmlContentAllowed;
++    }
++
 +}
index f7d73d3b5ec4face9c1cf9163b0e66c2d5ca14e1,0000000000000000000000000000000000000000..e5e7dbba8b58baa0bce8f6f22c43105bceff6e5a
mode 100644,000000..100644
--- /dev/null
@@@ -1,419 -1,0 +1,423 @@@
 +/*
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +
 +package com.vaadin.terminal.gwt.client.ui.button;
 +
 +import com.google.gwt.dom.client.Document;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.dom.client.NativeEvent;
 +import com.google.gwt.event.dom.client.ClickEvent;
 +import com.google.gwt.event.dom.client.ClickHandler;
 +import com.google.gwt.event.dom.client.KeyCodes;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.ui.Accessibility;
 +import com.google.gwt.user.client.ui.FocusWidget;
 +import com.vaadin.terminal.gwt.client.ApplicationConnection;
 +import com.vaadin.terminal.gwt.client.BrowserInfo;
 +import com.vaadin.terminal.gwt.client.Util;
 +import com.vaadin.terminal.gwt.client.VTooltip;
 +import com.vaadin.terminal.gwt.client.ui.Icon;
 +
 +public class VButton extends FocusWidget implements ClickHandler {
 +
 +    public static final String CLASSNAME = "v-button";
 +    private static final String CLASSNAME_PRESSED = "v-pressed";
 +
 +    // mouse movement is checked before synthesizing click event on mouseout
 +    protected static int MOVE_THRESHOLD = 3;
 +    protected int mousedownX = 0;
 +    protected int mousedownY = 0;
 +
 +    protected ApplicationConnection client;
 +
 +    protected final Element wrapper = DOM.createSpan();
 +
 +    protected Element errorIndicatorElement;
 +
 +    protected final Element captionElement = DOM.createSpan();
 +
 +    protected Icon icon;
 +
 +    /**
 +     * Helper flag to handle special-case where the button is moved from under
 +     * mouse while clicking it. In this case mouse leaves the button without
 +     * moving.
 +     */
 +    protected boolean clickPending;
 +
 +    private boolean enabled = true;
 +
 +    private int tabIndex = 0;
 +
 +    /*
 +     * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton
 +     */
 +
 +    /**
 +     * If <code>true</code>, this widget is capturing with the mouse held down.
 +     */
 +    private boolean isCapturing;
 +
 +    /**
 +     * If <code>true</code>, this widget has focus with the space bar down.
 +     */
 +    private boolean isFocusing;
 +
 +    /**
 +     * Used to decide whether to allow clicks to propagate up to the superclass
 +     * or container elements.
 +     */
 +    private boolean disallowNextClick = false;
 +    private boolean isHovering;
 +
 +    protected int clickShortcut = 0;
 +
 +    public VButton() {
 +        super(DOM.createDiv());
 +        setTabIndex(0);
 +        sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS
 +                | Event.KEYEVENTS);
 +        sinkEvents(VTooltip.TOOLTIP_EVENTS);
 +
 +        setStyleName(CLASSNAME);
 +
 +        // Add a11y role "button"
 +        Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON);
 +
 +        wrapper.setClassName(getStylePrimaryName() + "-wrap");
 +        getElement().appendChild(wrapper);
 +        captionElement.setClassName(getStylePrimaryName() + "-caption");
 +        wrapper.appendChild(captionElement);
 +
 +        addClickHandler(this);
 +    }
 +
 +    public void setText(String text) {
 +        captionElement.setInnerText(text);
 +    }
 +
++    public void setHtml(String html) {
++        captionElement.setInnerHTML(html);
++    }
++
 +    @SuppressWarnings("deprecation")
 +    @Override
 +    /*
 +     * Copy-pasted from GWT CustomButton, some minor modifications done:
 +     * 
 +     * -for IE/Opera added CLASSNAME_PRESSED
 +     * 
 +     * -event.preventDefault() commented from ONMOUSEDOWN (Firefox won't apply
 +     * :active styles if it is present)
 +     * 
 +     * -Tooltip event handling added
 +     * 
 +     * -onload event handler added (for icon handling)
 +     */
 +    public void onBrowserEvent(Event event) {
 +        if (client != null) {
 +            client.handleTooltipEvent(event, this);
 +        }
 +        if (DOM.eventGetType(event) == Event.ONLOAD) {
 +            Util.notifyParentOfSizeChange(this, true);
 +        }
 +        // Should not act on button if disabled.
 +        if (!isEnabled()) {
 +            // This can happen when events are bubbled up from non-disabled
 +            // children
 +            return;
 +        }
 +
 +        int type = DOM.eventGetType(event);
 +        switch (type) {
 +        case Event.ONCLICK:
 +            // If clicks are currently disallowed, keep it from bubbling or
 +            // being passed to the superclass.
 +            if (disallowNextClick) {
 +                event.stopPropagation();
 +                disallowNextClick = false;
 +                return;
 +            }
 +            break;
 +        case Event.ONMOUSEDOWN:
 +            if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
 +                // This was moved from mouseover, which iOS sometimes skips.
 +                // We're certainly hovering at this point, and we don't actually
 +                // need that information before this point.
 +                setHovering(true);
 +            }
 +            if (event.getButton() == Event.BUTTON_LEFT) {
 +                // save mouse position to detect movement before synthesizing
 +                // event later
 +                mousedownX = event.getClientX();
 +                mousedownY = event.getClientY();
 +
 +                disallowNextClick = true;
 +                clickPending = true;
 +                setFocus(true);
 +                DOM.setCapture(getElement());
 +                isCapturing = true;
 +                // Prevent dragging (on some browsers);
 +                // DOM.eventPreventDefault(event);
 +                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
 +                    addStyleName(CLASSNAME_PRESSED);
 +                }
 +            }
 +            break;
 +        case Event.ONMOUSEUP:
 +            if (isCapturing) {
 +                isCapturing = false;
 +                DOM.releaseCapture(getElement());
 +                if (isHovering() && event.getButton() == Event.BUTTON_LEFT) {
 +                    // Click ok
 +                    disallowNextClick = false;
 +                }
 +                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
 +                    removeStyleName(CLASSNAME_PRESSED);
 +                }
 +                // Explicitly prevent IE 8 from propagating mouseup events
 +                // upward (fixes #6753)
 +                if (BrowserInfo.get().isIE8()) {
 +                    event.stopPropagation();
 +                }
 +            }
 +            break;
 +        case Event.ONMOUSEMOVE:
 +            clickPending = false;
 +            if (isCapturing) {
 +                // Prevent dragging (on other browsers);
 +                DOM.eventPreventDefault(event);
 +            }
 +            break;
 +        case Event.ONMOUSEOUT:
 +            Element to = event.getRelatedTarget();
 +            if (getElement().isOrHasChild(DOM.eventGetTarget(event))
 +                    && (to == null || !getElement().isOrHasChild(to))) {
 +                if (clickPending
 +                        && Math.abs(mousedownX - event.getClientX()) < MOVE_THRESHOLD
 +                        && Math.abs(mousedownY - event.getClientY()) < MOVE_THRESHOLD) {
 +                    onClick();
 +                    break;
 +                }
 +                clickPending = false;
 +                if (isCapturing) {
 +                }
 +                setHovering(false);
 +                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
 +                    removeStyleName(CLASSNAME_PRESSED);
 +                }
 +            }
 +            break;
 +        case Event.ONBLUR:
 +            if (isFocusing) {
 +                isFocusing = false;
 +            }
 +            break;
 +        case Event.ONLOSECAPTURE:
 +            if (isCapturing) {
 +                isCapturing = false;
 +            }
 +            break;
 +        }
 +
 +        super.onBrowserEvent(event);
 +
 +        // Synthesize clicks based on keyboard events AFTER the normal key
 +        // handling.
 +        if ((event.getTypeInt() & Event.KEYEVENTS) != 0) {
 +            switch (type) {
 +            case Event.ONKEYDOWN:
 +                if (event.getKeyCode() == 32 /* space */) {
 +                    isFocusing = true;
 +                    event.preventDefault();
 +                }
 +                break;
 +            case Event.ONKEYUP:
 +                if (isFocusing && event.getKeyCode() == 32 /* space */) {
 +                    isFocusing = false;
 +
 +                    /*
 +                     * If click shortcut is space then the shortcut handler will
 +                     * take care of the click.
 +                     */
 +                    if (clickShortcut != 32 /* space */) {
 +                        onClick();
 +                    }
 +
 +                    event.preventDefault();
 +                }
 +                break;
 +            case Event.ONKEYPRESS:
 +                if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
 +
 +                    /*
 +                     * If click shortcut is enter then the shortcut handler will
 +                     * take care of the click.
 +                     */
 +                    if (clickShortcut != KeyCodes.KEY_ENTER) {
 +                        onClick();
 +                    }
 +
 +                    event.preventDefault();
 +                }
 +                break;
 +            }
 +        }
 +    }
 +
 +    final void setHovering(boolean hovering) {
 +        if (hovering != isHovering()) {
 +            isHovering = hovering;
 +        }
 +    }
 +
 +    final boolean isHovering() {
 +        return isHovering;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
 +     * .dom.client.ClickEvent)
 +     */
 +    public void onClick(ClickEvent event) {
 +        if (BrowserInfo.get().isSafari()) {
 +            VButton.this.setFocus(true);
 +        }
 +
 +        clickPending = false;
 +    }
 +
 +    /*
 +     * ALL BELOW COPY-PASTED FROM GWT CustomButton
 +     */
 +
 +    /**
 +     * Called internally when the user finishes clicking on this button. The
 +     * default behavior is to fire the click event to listeners. Subclasses that
 +     * override {@link #onClickStart()} should override this method to restore
 +     * the normal widget display.
 +     * <p>
 +     * To add custom code for a click event, override
 +     * {@link #onClick(ClickEvent)} instead of this.
 +     */
 +    protected void onClick() {
 +        // Allow the click we're about to synthesize to pass through to the
 +        // superclass and containing elements. Element.dispatchEvent() is
 +        // synchronous, so we simply set and clear the flag within this method.
 +
 +        disallowNextClick = false;
 +
 +        // Mouse coordinates are not always available (e.g., when the click is
 +        // caused by a keyboard event).
 +        NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false,
 +                false, false, false);
 +        getElement().dispatchEvent(evt);
 +    }
 +
 +    /**
 +     * Sets whether this button is enabled.
 +     * 
 +     * @param enabled
 +     *            <code>true</code> to enable the button, <code>false</code> to
 +     *            disable it
 +     */
 +
 +    @Override
 +    public final void setEnabled(boolean enabled) {
 +        if (isEnabled() != enabled) {
 +            this.enabled = enabled;
 +            if (!enabled) {
 +                cleanupCaptureState();
 +                Accessibility.removeState(getElement(),
 +                        Accessibility.STATE_PRESSED);
 +                super.setTabIndex(-1);
 +                addStyleName(ApplicationConnection.DISABLED_CLASSNAME);
 +            } else {
 +                Accessibility.setState(getElement(),
 +                        Accessibility.STATE_PRESSED, "false");
 +                super.setTabIndex(tabIndex);
 +                removeStyleName(ApplicationConnection.DISABLED_CLASSNAME);
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public final boolean isEnabled() {
 +        return enabled;
 +    }
 +
 +    @Override
 +    public final void setTabIndex(int index) {
 +        super.setTabIndex(index);
 +        tabIndex = index;
 +    }
 +
 +    /**
 +     * Resets internal state if this button can no longer service events. This
 +     * can occur when the widget becomes detached or disabled.
 +     */
 +    private void cleanupCaptureState() {
 +        if (isCapturing || isFocusing) {
 +            DOM.releaseCapture(getElement());
 +            isCapturing = false;
 +            isFocusing = false;
 +        }
 +    }
 +
 +    private static native int getHorizontalBorderAndPaddingWidth(Element elem)
 +    /*-{
 +        // THIS METHOD IS ONLY USED FOR INTERNET EXPLORER, IT DOESN'T WORK WITH OTHERS
 +      
 +      var convertToPixel = function(elem, value) {
 +          // From the awesome hack by Dean Edwards
 +            // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
 +
 +            // Remember the original values
 +            var left = elem.style.left, rsLeft = elem.runtimeStyle.left;
 +
 +            // Put in the new values to get a computed value out
 +            elem.runtimeStyle.left = elem.currentStyle.left;
 +            elem.style.left = value || 0;
 +            var ret = elem.style.pixelLeft;
 +
 +            // Revert the changed values
 +            elem.style.left = left;
 +            elem.runtimeStyle.left = rsLeft;
 +            
 +            return ret;
 +      }
 +      
 +      var ret = 0;
 +
 +        var sides = ["Right","Left"];
 +        for(var i=0; i<2; i++) {
 +            var side = sides[i];
 +            var value;
 +            // Border -------------------------------------------------------
 +            if(elem.currentStyle["border"+side+"Style"] != "none") {
 +                value = elem.currentStyle["border"+side+"Width"];
 +                if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
 +                    ret += convertToPixel(elem, value);
 +                } else if(value.length > 2) {
 +                    ret += parseInt(value.substr(0, value.length-2));
 +                }
 +            }
 +                    
 +            // Padding -------------------------------------------------------
 +            value = elem.currentStyle["padding"+side];
 +            if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
 +                ret += convertToPixel(elem, value);
 +            } else if(value.length > 2) {
 +                ret += parseInt(value.substr(0, value.length-2));
 +            }
 +        }
 +
 +      return ret;
 +    }-*/;
 +
 +}
index 21e0e0820d2bd35c0607f8a0234ad1276e3eb7d7,0000000000000000000000000000000000000000..84b3c678ebe9f5a4ea53a2d5bd5ed5eeb35e478c
mode 100644,000000..100644
--- /dev/null
@@@ -1,87 -1,0 +1,94 @@@
 +/* 
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +
 +package com.vaadin.terminal.gwt.client.ui.datefield;
 +
 +import java.util.Date;
 +
 +import com.google.gwt.event.dom.client.DomEvent;
 +import com.vaadin.terminal.gwt.client.DateTimeService;
 +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.FocusOutListener;
 +import com.vaadin.terminal.gwt.client.ui.datefield.VCalendarPanel.SubmitListener;
 +
 +/**
 + * A client side implementation for InlineDateField
 + */
 +public class VDateFieldCalendar extends VDateField {
 +
 +    protected final VCalendarPanel calendarPanel;
 +
 +    public VDateFieldCalendar() {
 +        super();
 +        calendarPanel = new VCalendarPanel();
 +        add(calendarPanel);
 +        calendarPanel.setSubmitListener(new SubmitListener() {
 +            public void onSubmit() {
 +                updateValueFromPanel();
 +            }
 +
 +            public void onCancel() {
 +                // TODO Auto-generated method stub
 +
 +            }
 +        });
 +        calendarPanel.setFocusOutListener(new FocusOutListener() {
 +            public boolean onFocusOut(DomEvent<?> event) {
 +                updateValueFromPanel();
 +                return false;
 +            }
 +        });
 +    }
 +
 +    /**
 +     * TODO refactor: almost same method as in VPopupCalendar.updateValue
 +     */
 +    @SuppressWarnings("deprecation")
 +    protected void updateValueFromPanel() {
++
++        // If field is invisible at the beginning, client can still be null when
++        // this function is called.
++        if (getClient() == null) {
++            return;
++        }
++
 +        Date date2 = calendarPanel.getDate();
 +        Date currentDate = getCurrentDate();
 +        if (currentDate == null || date2.getTime() != currentDate.getTime()) {
 +            setCurrentDate((Date) date2.clone());
 +            getClient().updateVariable(getId(), "year", date2.getYear() + 1900,
 +                    false);
 +            if (getCurrentResolution() > VDateField.RESOLUTION_YEAR) {
 +                getClient().updateVariable(getId(), "month",
 +                        date2.getMonth() + 1, false);
 +                if (getCurrentResolution() > RESOLUTION_MONTH) {
 +                    getClient().updateVariable(getId(), "day", date2.getDate(),
 +                            false);
 +                    if (getCurrentResolution() > RESOLUTION_DAY) {
 +                        getClient().updateVariable(getId(), "hour",
 +                                date2.getHours(), false);
 +                        if (getCurrentResolution() > RESOLUTION_HOUR) {
 +                            getClient().updateVariable(getId(), "min",
 +                                    date2.getMinutes(), false);
 +                            if (getCurrentResolution() > RESOLUTION_MIN) {
 +                                getClient().updateVariable(getId(), "sec",
 +                                        date2.getSeconds(), false);
 +                                if (getCurrentResolution() > RESOLUTION_SEC) {
 +                                    getClient().updateVariable(
 +                                            getId(),
 +                                            "msec",
 +                                            DateTimeService
 +                                                    .getMilliseconds(date2),
 +                                            false);
 +                                }
 +                            }
 +                        }
 +                    }
 +                }
 +            }
 +            if (isImmediate()) {
 +                getClient().sendPendingVariableChanges();
 +            }
 +        }
 +    }
 +}
index 801c40582639ec6c483c51ce3424fca0150f39e6,0000000000000000000000000000000000000000..84d3b73285fc6b4ab42e60b419894659b0779cf8
mode 100644,000000..100644
--- /dev/null
@@@ -1,125 -1,0 +1,129 @@@
-         getWidget().setText(getState().getCaption());
 +/*
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +package com.vaadin.terminal.gwt.client.ui.nativebutton;
 +
 +import com.google.gwt.core.client.GWT;
 +import com.google.gwt.event.dom.client.BlurEvent;
 +import com.google.gwt.event.dom.client.BlurHandler;
 +import com.google.gwt.event.dom.client.FocusEvent;
 +import com.google.gwt.event.dom.client.FocusHandler;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.terminal.gwt.client.EventHelper;
 +import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc;
 +import com.vaadin.terminal.gwt.client.communication.RpcProxy;
 +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
 +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector;
 +import com.vaadin.terminal.gwt.client.ui.Connect;
 +import com.vaadin.terminal.gwt.client.ui.Icon;
 +import com.vaadin.terminal.gwt.client.ui.button.ButtonServerRpc;
 +import com.vaadin.terminal.gwt.client.ui.button.ButtonState;
 +import com.vaadin.ui.NativeButton;
 +
 +@Connect(NativeButton.class)
 +public class NativeButtonConnector extends AbstractComponentConnector implements
 +        BlurHandler, FocusHandler {
 +
 +    private HandlerRegistration focusHandlerRegistration;
 +    private HandlerRegistration blurHandlerRegistration;
 +
 +    private FocusAndBlurServerRpc focusBlurRpc = RpcProxy.create(
 +            FocusAndBlurServerRpc.class, this);
 +
 +    @Override
 +    public void init() {
 +        super.init();
 +
 +        getWidget().buttonRpcProxy = RpcProxy.create(ButtonServerRpc.class,
 +                this);
 +        getWidget().client = getConnection();
 +        getWidget().paintableId = getConnectorId();
 +    }
 +
 +    @Override
 +    public boolean delegateCaptionHandling() {
 +        return false;
 +    }
 +
 +    @Override
 +    public void onStateChanged(StateChangeEvent stateChangeEvent) {
 +        super.onStateChanged(stateChangeEvent);
 +
 +        getWidget().disableOnClick = getState().isDisableOnClick();
 +        focusHandlerRegistration = EventHelper.updateFocusHandler(this,
 +                focusHandlerRegistration);
 +        blurHandlerRegistration = EventHelper.updateBlurHandler(this,
 +                blurHandlerRegistration);
 +
 +        // Set text
++        if (getState().isHtmlContentAllowed()) {
++            getWidget().setHTML(getState().getCaption());
++        } else {
++            getWidget().setText(getState().getCaption());
++        }
 +
 +        // handle error
 +        if (null != getState().getErrorMessage()) {
 +            if (getWidget().errorIndicatorElement == null) {
 +                getWidget().errorIndicatorElement = DOM.createSpan();
 +                getWidget().errorIndicatorElement
 +                        .setClassName("v-errorindicator");
 +            }
 +            getWidget().getElement().insertBefore(
 +                    getWidget().errorIndicatorElement,
 +                    getWidget().captionElement);
 +
 +        } else if (getWidget().errorIndicatorElement != null) {
 +            getWidget().getElement().removeChild(
 +                    getWidget().errorIndicatorElement);
 +            getWidget().errorIndicatorElement = null;
 +        }
 +
 +        if (getState().getIcon() != null) {
 +            if (getWidget().icon == null) {
 +                getWidget().icon = new Icon(getConnection());
 +                getWidget().getElement().insertBefore(
 +                        getWidget().icon.getElement(),
 +                        getWidget().captionElement);
 +            }
 +            getWidget().icon.setUri(getState().getIcon().getURL());
 +        } else {
 +            if (getWidget().icon != null) {
 +                getWidget().getElement().removeChild(
 +                        getWidget().icon.getElement());
 +                getWidget().icon = null;
 +            }
 +        }
 +
 +    }
 +
 +    @Override
 +    protected Widget createWidget() {
 +        return GWT.create(VNativeButton.class);
 +    }
 +
 +    @Override
 +    public VNativeButton getWidget() {
 +        return (VNativeButton) super.getWidget();
 +    }
 +
 +    @Override
 +    public ButtonState getState() {
 +        return (ButtonState) super.getState();
 +    }
 +
 +    public void onFocus(FocusEvent event) {
 +        // EventHelper.updateFocusHandler ensures that this is called only when
 +        // there is a listener on server side
 +        focusBlurRpc.focus();
 +    }
 +
 +    public void onBlur(BlurEvent event) {
 +        // EventHelper.updateFocusHandler ensures that this is called only when
 +        // there is a listener on server side
 +        focusBlurRpc.blur();
 +    }
 +
 +}
index d0b8f73eb15f5bed2f2464203b64ffc2c8b83eb6,0000000000000000000000000000000000000000..01ac4fab6ae7214cba879a422ee391cd31e2a432
mode 100644,000000..100644
--- /dev/null
@@@ -1,132 -1,0 +1,137 @@@
 +/*
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +
 +package com.vaadin.terminal.gwt.client.ui.nativebutton;
 +
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.event.dom.client.ClickEvent;
 +import com.google.gwt.event.dom.client.ClickHandler;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.ui.Button;
 +import com.vaadin.terminal.gwt.client.ApplicationConnection;
 +import com.vaadin.terminal.gwt.client.BrowserInfo;
 +import com.vaadin.terminal.gwt.client.MouseEventDetails;
 +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder;
 +import com.vaadin.terminal.gwt.client.Util;
 +import com.vaadin.terminal.gwt.client.VTooltip;
 +import com.vaadin.terminal.gwt.client.ui.Icon;
 +import com.vaadin.terminal.gwt.client.ui.button.ButtonServerRpc;
 +
 +public class VNativeButton extends Button implements ClickHandler {
 +
 +    public static final String CLASSNAME = "v-nativebutton";
 +
 +    protected String width = null;
 +
 +    protected String paintableId;
 +
 +    protected ApplicationConnection client;
 +
 +    ButtonServerRpc buttonRpcProxy;
 +
 +    protected Element errorIndicatorElement;
 +
 +    protected final Element captionElement = DOM.createSpan();
 +
 +    protected Icon icon;
 +
 +    /**
 +     * Helper flag to handle special-case where the button is moved from under
 +     * mouse while clicking it. In this case mouse leaves the button without
 +     * moving.
 +     */
 +    private boolean clickPending;
 +
 +    protected boolean disableOnClick = false;
 +
 +    public VNativeButton() {
 +        setStyleName(CLASSNAME);
 +
 +        getElement().appendChild(captionElement);
 +        captionElement.setClassName(getStyleName() + "-caption");
 +
 +        addClickHandler(this);
 +
 +        sinkEvents(VTooltip.TOOLTIP_EVENTS);
 +        sinkEvents(Event.ONMOUSEDOWN);
 +        sinkEvents(Event.ONMOUSEUP);
 +    }
 +
 +    @Override
 +    public void setText(String text) {
 +        captionElement.setInnerText(text);
 +    }
 +
++    @Override
++    public void setHTML(String html) {
++        captionElement.setInnerHTML(html);
++    }
++
 +    @Override
 +    public void onBrowserEvent(Event event) {
 +        super.onBrowserEvent(event);
 +
 +        if (DOM.eventGetType(event) == Event.ONLOAD) {
 +            Util.notifyParentOfSizeChange(this, true);
 +
 +        } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN
 +                && event.getButton() == Event.BUTTON_LEFT) {
 +            clickPending = true;
 +        } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) {
 +            clickPending = false;
 +        } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) {
 +            if (clickPending) {
 +                click();
 +            }
 +            clickPending = false;
 +        }
 +
 +        if (client != null) {
 +            client.handleTooltipEvent(event, this);
 +        }
 +    }
 +
 +    @Override
 +    public void setWidth(String width) {
 +        this.width = width;
 +        super.setWidth(width);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
 +     * .dom.client.ClickEvent)
 +     */
 +    public void onClick(ClickEvent event) {
 +        if (paintableId == null || client == null) {
 +            return;
 +        }
 +
 +        if (BrowserInfo.get().isSafari()) {
 +            VNativeButton.this.setFocus(true);
 +        }
 +        if (disableOnClick) {
 +            setEnabled(false);
 +            buttonRpcProxy.disableOnClick();
 +        }
 +
 +        // Add mouse details
 +        MouseEventDetails details = MouseEventDetailsBuilder
 +                .buildMouseEventDetails(event.getNativeEvent(), getElement());
 +        buttonRpcProxy.click(details);
 +
 +        clickPending = false;
 +    }
 +
 +    @Override
 +    public void setEnabled(boolean enabled) {
 +        if (isEnabled() != enabled) {
 +            super.setEnabled(enabled);
 +            setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled);
 +        }
 +    }
 +}
index eb97160f5295384a3201bf8434bc561d1adf6fad,0000000000000000000000000000000000000000..0d222044ba1f475bc23493cd499e1ac31ae5c62b
mode 100644,000000..100644
--- /dev/null
@@@ -1,431 -1,0 +1,438 @@@
 +/* 
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +
 +package com.vaadin.terminal.gwt.client.ui.notification;
 +
 +import java.util.ArrayList;
 +import java.util.Date;
 +import java.util.EventObject;
 +import java.util.Iterator;
 +
 +import com.google.gwt.core.client.GWT;
 +import com.google.gwt.event.dom.client.KeyCodes;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Element;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.Timer;
 +import com.google.gwt.user.client.ui.HTML;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.terminal.gwt.client.ApplicationConnection;
 +import com.vaadin.terminal.gwt.client.BrowserInfo;
 +import com.vaadin.terminal.gwt.client.UIDL;
 +import com.vaadin.terminal.gwt.client.Util;
 +import com.vaadin.terminal.gwt.client.ui.VOverlay;
 +import com.vaadin.terminal.gwt.client.ui.root.VRoot;
 +
 +public class VNotification extends VOverlay {
 +
 +    public static final int CENTERED = 1;
 +    public static final int CENTERED_TOP = 2;
 +    public static final int CENTERED_BOTTOM = 3;
 +    public static final int TOP_LEFT = 4;
 +    public static final int TOP_RIGHT = 5;
 +    public static final int BOTTOM_LEFT = 6;
 +    public static final int BOTTOM_RIGHT = 7;
 +
 +    public static final int DELAY_FOREVER = -1;
 +    public static final int DELAY_NONE = 0;
 +
 +    private static final String STYLENAME = "v-Notification";
 +    private static final int mouseMoveThreshold = 7;
 +    private static final int Z_INDEX_BASE = 20000;
 +    public static final String STYLE_SYSTEM = "system";
 +    private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps
 +
 +    private int startOpacity = 90;
 +    private int fadeMsec = 400;
 +    private int delayMsec = 1000;
 +
 +    private Timer fader;
 +    private Timer delay;
 +
 +    private int x = -1;
 +    private int y = -1;
 +
 +    private String temporaryStyle;
 +
 +    private ArrayList<EventListener> listeners;
 +    private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
 +
 +    public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style";
 +    public static final String ATTRIBUTE_NOTIFICATION_CAPTION = "caption";
 +    public static final String ATTRIBUTE_NOTIFICATION_MESSAGE = "message";
 +    public static final String ATTRIBUTE_NOTIFICATION_ICON = "icon";
 +    public static final String ATTRIBUTE_NOTIFICATION_POSITION = "position";
 +    public static final String ATTRIBUTE_NOTIFICATION_DELAY = "delay";
 +
 +    /**
 +     * Default constructor. You should use GWT.create instead.
 +     */
 +    public VNotification() {
 +        setStyleName(STYLENAME);
 +        sinkEvents(Event.ONCLICK);
 +        DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);
 +    }
 +
 +    /**
 +     * @deprecated Use static {@link #createNotification(int)} instead to enable
 +     *             GWT deferred binding.
 +     * 
 +     * @param delayMsec
 +     */
 +    @Deprecated
 +    public VNotification(int delayMsec) {
 +        this();
 +        this.delayMsec = delayMsec;
 +        if (BrowserInfo.get().isTouchDevice()) {
 +            new Timer() {
 +                @Override
 +                public void run() {
 +                    if (isAttached()) {
 +                        fade();
 +                    }
 +                }
 +            }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
 +        }
 +    }
 +
 +    /**
 +     * @deprecated Use static {@link #createNotification(int, int, int)} instead
 +     *             to enable GWT deferred binding.
 +     * 
 +     * @param delayMsec
 +     * @param fadeMsec
 +     * @param startOpacity
 +     */
 +    @Deprecated
 +    public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
 +        this(delayMsec);
 +        this.fadeMsec = fadeMsec;
 +        this.startOpacity = startOpacity;
 +    }
 +
 +    public void startDelay() {
 +        DOM.removeEventPreview(this);
 +        if (delayMsec > 0) {
 +            if (delay == null) {
 +                delay = new Timer() {
 +                    @Override
 +                    public void run() {
 +                        fade();
 +                    }
 +                };
 +                delay.schedule(delayMsec);
 +            }
 +        } else if (delayMsec == 0) {
 +            fade();
 +        }
 +    }
 +
 +    @Override
 +    public void show() {
 +        show(CENTERED);
 +    }
 +
 +    public void show(String style) {
 +        show(CENTERED, style);
 +    }
 +
 +    public void show(int position) {
 +        show(position, null);
 +    }
 +
 +    public void show(Widget widget, int position, String style) {
 +        setWidget(widget);
 +        show(position, style);
 +    }
 +
 +    public void show(String html, int position, String style) {
 +        setWidget(new HTML(html));
 +        show(position, style);
 +    }
 +
 +    public void show(int position, String style) {
 +        setOpacity(getElement(), startOpacity);
 +        if (style != null) {
 +            temporaryStyle = style;
 +            addStyleName(style);
 +            addStyleDependentName(style);
 +        }
 +        super.show();
 +        setPosition(position);
++        /**
++         * Android 4 fails to render notifications correctly without a little
++         * nudge (#8551)
++         */
++        if (BrowserInfo.get().isAndroid()) {
++            Util.setStyleTemporarily(getElement(), "display", "none");
++        }
 +    }
 +
 +    @Override
 +    public void hide() {
 +        DOM.removeEventPreview(this);
 +        cancelDelay();
 +        cancelFade();
 +        if (temporaryStyle != null) {
 +            removeStyleName(temporaryStyle);
 +            removeStyleDependentName(temporaryStyle);
 +            temporaryStyle = null;
 +        }
 +        super.hide();
 +        fireEvent(new HideEvent(this));
 +    }
 +
 +    public void fade() {
 +        DOM.removeEventPreview(this);
 +        cancelDelay();
 +        if (fader == null) {
 +            fader = new Timer() {
 +                private final long start = new Date().getTime();
 +
 +                @Override
 +                public void run() {
 +                    /*
 +                     * To make animation smooth, don't count that event happens
 +                     * on time. Reduce opacity according to the actual time
 +                     * spent instead of fixed decrement.
 +                     */
 +                    long now = new Date().getTime();
 +                    long timeEplaced = now - start;
 +                    float remainingFraction = 1 - timeEplaced
 +                            / (float) fadeMsec;
 +                    int opacity = (int) (startOpacity * remainingFraction);
 +                    if (opacity <= 0) {
 +                        cancel();
 +                        hide();
 +                        if (BrowserInfo.get().isOpera()) {
 +                            // tray notification on opera needs to explicitly
 +                            // define
 +                            // size, reset it
 +                            DOM.setStyleAttribute(getElement(), "width", "");
 +                            DOM.setStyleAttribute(getElement(), "height", "");
 +                        }
 +                    } else {
 +                        setOpacity(getElement(), opacity);
 +                    }
 +                }
 +            };
 +            fader.scheduleRepeating(FADE_ANIMATION_INTERVAL);
 +        }
 +    }
 +
 +    public void setPosition(int position) {
 +        final Element el = getElement();
 +        DOM.setStyleAttribute(el, "top", "");
 +        DOM.setStyleAttribute(el, "left", "");
 +        DOM.setStyleAttribute(el, "bottom", "");
 +        DOM.setStyleAttribute(el, "right", "");
 +        switch (position) {
 +        case TOP_LEFT:
 +            DOM.setStyleAttribute(el, "top", "0px");
 +            DOM.setStyleAttribute(el, "left", "0px");
 +            break;
 +        case TOP_RIGHT:
 +            DOM.setStyleAttribute(el, "top", "0px");
 +            DOM.setStyleAttribute(el, "right", "0px");
 +            break;
 +        case BOTTOM_RIGHT:
 +            DOM.setStyleAttribute(el, "position", "absolute");
 +            if (BrowserInfo.get().isOpera()) {
 +                // tray notification on opera needs explicitly defined size
 +                DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");
 +                DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");
 +            }
 +            DOM.setStyleAttribute(el, "bottom", "0px");
 +            DOM.setStyleAttribute(el, "right", "0px");
 +            break;
 +        case BOTTOM_LEFT:
 +            DOM.setStyleAttribute(el, "bottom", "0px");
 +            DOM.setStyleAttribute(el, "left", "0px");
 +            break;
 +        case CENTERED_TOP:
 +            center();
 +            DOM.setStyleAttribute(el, "top", "0px");
 +            break;
 +        case CENTERED_BOTTOM:
 +            center();
 +            DOM.setStyleAttribute(el, "top", "");
 +            DOM.setStyleAttribute(el, "bottom", "0px");
 +            break;
 +        default:
 +        case CENTERED:
 +            center();
 +            break;
 +        }
 +    }
 +
 +    private void cancelFade() {
 +        if (fader != null) {
 +            fader.cancel();
 +            fader = null;
 +        }
 +    }
 +
 +    private void cancelDelay() {
 +        if (delay != null) {
 +            delay.cancel();
 +            delay = null;
 +        }
 +    }
 +
 +    private void setOpacity(Element el, int opacity) {
 +        DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));
 +        if (BrowserInfo.get().isIE()) {
 +            DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity
 +                    + ")");
 +        }
 +    }
 +
 +    @Override
 +    public void onBrowserEvent(Event event) {
 +        DOM.removeEventPreview(this);
 +        if (fader == null) {
 +            fade();
 +        }
 +    }
 +
 +    @Override
 +    public boolean onEventPreview(Event event) {
 +        int type = DOM.eventGetType(event);
 +        // "modal"
 +        if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) {
 +            if (type == Event.ONCLICK) {
 +                if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
 +                    fade();
 +                    return false;
 +                }
 +            } else if (type == Event.ONKEYDOWN
 +                    && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
 +                fade();
 +                return false;
 +            }
 +            if (temporaryStyle == STYLE_SYSTEM) {
 +                return true;
 +            } else {
 +                return false;
 +            }
 +        }
 +        // default
 +        switch (type) {
 +        case Event.ONMOUSEMOVE:
 +
 +            if (x < 0) {
 +                x = DOM.eventGetClientX(event);
 +                y = DOM.eventGetClientY(event);
 +            } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
 +                    || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
 +                startDelay();
 +            }
 +            break;
 +        case Event.ONMOUSEDOWN:
 +        case Event.ONMOUSEWHEEL:
 +        case Event.ONSCROLL:
 +            startDelay();
 +            break;
 +        case Event.ONKEYDOWN:
 +            if (event.getRepeat()) {
 +                return true;
 +            }
 +            startDelay();
 +            break;
 +        default:
 +            break;
 +        }
 +        return true;
 +    }
 +
 +    public void addEventListener(EventListener listener) {
 +        if (listeners == null) {
 +            listeners = new ArrayList<EventListener>();
 +        }
 +        listeners.add(listener);
 +    }
 +
 +    public void removeEventListener(EventListener listener) {
 +        if (listeners == null) {
 +            return;
 +        }
 +        listeners.remove(listener);
 +    }
 +
 +    private void fireEvent(HideEvent event) {
 +        if (listeners != null) {
 +            for (Iterator<EventListener> it = listeners.iterator(); it
 +                    .hasNext();) {
 +                EventListener l = it.next();
 +                l.notificationHidden(event);
 +            }
 +        }
 +    }
 +
 +    public static void showNotification(ApplicationConnection client,
 +            final UIDL notification) {
 +        boolean onlyPlainText = notification
 +                .hasAttribute(VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
 +        String html = "";
 +        if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_ICON)) {
 +            final String parsedUri = client.translateVaadinUri(notification
 +                    .getStringAttribute(ATTRIBUTE_NOTIFICATION_ICON));
 +            html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
 +        }
 +        if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_CAPTION)) {
 +            String caption = notification
 +                    .getStringAttribute(ATTRIBUTE_NOTIFICATION_CAPTION);
 +            if (onlyPlainText) {
 +                caption = Util.escapeHTML(caption);
 +                caption = caption.replaceAll("\\n", "<br />");
 +            }
 +            html += "<h1>" + caption + "</h1>";
 +        }
 +        if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE)) {
 +            String message = notification
 +                    .getStringAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE);
 +            if (onlyPlainText) {
 +                message = Util.escapeHTML(message);
 +                message = message.replaceAll("\\n", "<br />");
 +            }
 +            html += "<p>" + message + "</p>";
 +        }
 +
 +        final String style = notification
 +                .hasAttribute(ATTRIBUTE_NOTIFICATION_STYLE) ? notification
 +                .getStringAttribute(ATTRIBUTE_NOTIFICATION_STYLE) : null;
 +        final int position = notification
 +                .getIntAttribute(ATTRIBUTE_NOTIFICATION_POSITION);
 +        final int delay = notification
 +                .getIntAttribute(ATTRIBUTE_NOTIFICATION_DELAY);
 +        createNotification(delay).show(html, position, style);
 +    }
 +
 +    public static VNotification createNotification(int delayMsec) {
 +        final VNotification notification = GWT.create(VNotification.class);
 +        notification.delayMsec = delayMsec;
 +        if (BrowserInfo.get().isTouchDevice()) {
 +            new Timer() {
 +                @Override
 +                public void run() {
 +                    if (notification.isAttached()) {
 +                        notification.fade();
 +                    }
 +                }
 +            }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY);
 +        }
 +        return notification;
 +    }
 +
 +    public class HideEvent extends EventObject {
 +
 +        public HideEvent(Object source) {
 +            super(source);
 +        }
 +    }
 +
 +    public interface EventListener extends java.util.EventListener {
 +        public void notificationHidden(HideEvent event);
 +    }
 +}
index 13fc55d7eabd8770408df9e8c5a5f7dc66a4c0cd,0000000000000000000000000000000000000000..12a69d5556e5def175505be9dbf18876d438a64d
mode 100644,000000..100644
--- /dev/null
@@@ -1,321 -1,0 +1,322 @@@
 +/*
 +@VaadinApache2LicenseForJavaFiles@
 + */
 +
 +package com.vaadin.terminal.gwt.client.ui.root;
 +
 +import java.util.ArrayList;
 +
 +import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 +import com.google.gwt.event.logical.shared.ResizeEvent;
 +import com.google.gwt.event.logical.shared.ResizeHandler;
 +import com.google.gwt.event.logical.shared.ValueChangeEvent;
 +import com.google.gwt.event.logical.shared.ValueChangeHandler;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.History;
 +import com.google.gwt.user.client.Timer;
 +import com.google.gwt.user.client.Window;
 +import com.google.gwt.user.client.ui.SimplePanel;
 +import com.vaadin.terminal.gwt.client.ApplicationConnection;
 +import com.vaadin.terminal.gwt.client.BrowserInfo;
 +import com.vaadin.terminal.gwt.client.ComponentConnector;
 +import com.vaadin.terminal.gwt.client.ConnectorMap;
 +import com.vaadin.terminal.gwt.client.Focusable;
 +import com.vaadin.terminal.gwt.client.VConsole;
 +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler;
 +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
 +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor;
 +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField;
 +
 +/**
 + *
 + */
 +public class VRoot extends SimplePanel implements ResizeHandler,
 +        Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable {
 +
 +    public static final String FRAGMENT_VARIABLE = "fragment";
 +
 +    private static final String CLASSNAME = "v-view";
 +
 +    public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain";
 +
 +    String theme;
 +
 +    String id;
 +
 +    ShortcutActionHandler actionHandler;
 +
 +    /** stored width for IE resize optimization */
 +    private int width;
 +
 +    /** stored height for IE resize optimization */
 +    private int height;
 +
 +    ApplicationConnection connection;
 +
 +    /** Identifies the click event */
 +    public static final String CLICK_EVENT_ID = "click";
 +
 +    /**
 +     * We are postponing resize process with IE. IE bugs with scrollbars in some
 +     * situations, that causes false onWindowResized calls. With Timer we will
 +     * give IE some time to decide if it really wants to keep current size
 +     * (scrollbars).
 +     */
 +    private Timer resizeTimer;
 +
 +    int scrollTop;
 +
 +    int scrollLeft;
 +
 +    boolean rendering;
 +
 +    boolean scrollable;
 +
 +    boolean immediate;
 +
 +    boolean resizeLazy = false;
 +
 +    /**
 +     * Attribute name for the lazy resize setting .
 +     */
 +    public static final String RESIZE_LAZY = "rL";
 +
 +    private HandlerRegistration historyHandlerRegistration;
 +
 +    /**
 +     * The current URI fragment, used to avoid sending updates if nothing has
 +     * changed.
 +     */
 +    String currentFragment;
 +
 +    /**
 +     * Listener for URI fragment changes. Notifies the server of the new value
 +     * whenever the value changes.
 +     */
 +    private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
 +        public void onValueChange(ValueChangeEvent<String> event) {
 +            String newFragment = event.getValue();
 +
 +            // Send the new fragment to the server if it has changed
 +            if (!newFragment.equals(currentFragment) && connection != null) {
 +                currentFragment = newFragment;
 +                connection.updateVariable(id, FRAGMENT_VARIABLE, newFragment,
 +                        true);
 +            }
 +        }
 +    };
 +
 +    private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200,
 +            new ScheduledCommand() {
 +                public void execute() {
 +                    windowSizeMaybeChanged(Window.getClientWidth(),
 +                            Window.getClientHeight());
 +                }
 +
 +            });
 +
 +    public VRoot() {
 +        super();
 +        setStyleName(CLASSNAME);
 +
 +        // Allow focusing the view by using the focus() method, the view
 +        // should not be in the document focus flow
 +        getElement().setTabIndex(-1);
 +    }
 +
 +    @Override
 +    protected void onAttach() {
 +        super.onAttach();
 +        historyHandlerRegistration = History
 +                .addValueChangeHandler(historyChangeHandler);
 +        currentFragment = History.getToken();
 +    }
 +
 +    @Override
 +    protected void onDetach() {
 +        super.onDetach();
 +        historyHandlerRegistration.removeHandler();
 +        historyHandlerRegistration = null;
 +    }
 +
 +    /**
 +     * Called when the window might have been resized.
 +     * 
 +     * @param newWidth
 +     *            The new width of the window
 +     * @param newHeight
 +     *            The new height of the window
 +     */
 +    protected void windowSizeMaybeChanged(int newWidth, int newHeight) {
 +        boolean changed = false;
 +        ComponentConnector connector = ConnectorMap.get(connection)
 +                .getConnector(this);
 +        if (width != newWidth) {
 +            width = newWidth;
 +            changed = true;
 +            connector.getLayoutManager().reportOuterWidth(connector, newWidth);
 +            VConsole.log("New window width: " + width);
 +        }
 +        if (height != newHeight) {
 +            height = newHeight;
 +            changed = true;
 +            connector.getLayoutManager()
 +                    .reportOuterHeight(connector, newHeight);
 +            VConsole.log("New window height: " + height);
 +        }
 +        if (changed) {
 +            VConsole.log("Running layout functions due to window resize");
 +
 +            sendClientResized();
 +
 +            connector.getLayoutManager().layoutNow();
 +        }
 +    }
 +
 +    public String getTheme() {
 +        return theme;
 +    }
 +
 +    /**
 +     * Used to reload host page on theme changes.
 +     */
 +    static native void reloadHostPage()
 +    /*-{
 +         $wnd.location.reload();
 +     }-*/;
 +
 +    /**
 +     * Evaluate the given script in the browser document.
 +     * 
 +     * @param script
 +     *            Script to be executed.
 +     */
 +    static native void eval(String script)
 +    /*-{
 +      try {
 +         if (script == null) return;
 +         $wnd.eval(script);
 +      } catch (e) {
 +      }
 +    }-*/;
 +
 +    /**
 +     * Returns true if the body is NOT generated, i.e if someone else has made
 +     * the page that we're running in. Otherwise we're in charge of the whole
 +     * page.
 +     * 
 +     * @return true if we're running embedded
 +     */
 +    public boolean isEmbedded() {
 +        return !getElement().getOwnerDocument().getBody().getClassName()
 +                .contains(ApplicationConnection.GENERATED_BODY_CLASSNAME);
 +    }
 +
 +    @Override
 +    public void onBrowserEvent(Event event) {
 +        super.onBrowserEvent(event);
 +        int type = DOM.eventGetType(event);
 +        if (type == Event.ONKEYDOWN && actionHandler != null) {
 +            actionHandler.handleKeyboardEvent(event);
 +            return;
 +        } else if (scrollable && type == Event.ONSCROLL) {
 +            updateScrollPosition();
 +        }
 +    }
 +
 +    /**
 +     * Updates scroll position from DOM and saves variables to server.
 +     */
 +    private void updateScrollPosition() {
 +        int oldTop = scrollTop;
 +        int oldLeft = scrollLeft;
 +        scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop");
 +        scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft");
 +        if (connection != null && !rendering) {
 +            if (oldTop != scrollTop) {
 +                connection.updateVariable(id, "scrollTop", scrollTop, false);
 +            }
 +            if (oldLeft != scrollLeft) {
 +                connection.updateVariable(id, "scrollLeft", scrollLeft, false);
 +            }
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google
 +     * .gwt.event.logical.shared.ResizeEvent)
 +     */
 +    public void onResize(ResizeEvent event) {
 +        onResize();
 +    }
 +
 +    /**
 +     * Called when a resize event is received.
 +     */
 +    void onResize() {
 +        /*
 +         * IE (pre IE9 at least) will give us some false resize events due to
 +         * problems with scrollbars. Firefox 3 might also produce some extra
 +         * events. We postpone both the re-layouting and the server side event
 +         * for a while to deal with these issues.
 +         * 
 +         * We may also postpone these events to avoid slowness when resizing the
 +         * browser window. Constantly recalculating the layout causes the resize
 +         * operation to be really slow with complex layouts.
 +         */
 +        boolean lazy = resizeLazy || BrowserInfo.get().isIE8();
 +
 +        if (lazy) {
 +            delayedResizeExecutor.trigger();
 +        } else {
 +            windowSizeMaybeChanged(Window.getClientWidth(),
 +                    Window.getClientHeight());
 +        }
 +    }
 +
 +    /**
 +     * Send new dimensions to the server.
 +     */
 +    private void sendClientResized() {
 +        connection.updateVariable(id, "height", height, false);
 +        connection.updateVariable(id, "width", width, immediate);
 +    }
 +
 +    public native static void goTo(String url)
 +    /*-{
 +       $wnd.location = url;
 +     }-*/;
 +
 +    public void onWindowClosing(Window.ClosingEvent event) {
 +        // Change focus on this window in order to ensure that all state is
 +        // collected from textfields
 +        // TODO this is a naive hack, that only works with text fields and may
 +        // cause some odd issues. Should be replaced with a decent solution, see
 +        // also related BeforeShortcutActionListener interface. Same interface
 +        // might be usable here.
 +        VTextField.flushChangesFromFocusedTextField();
 +    }
 +
 +    private native static void loadAppIdListFromDOM(ArrayList<String> list)
 +    /*-{
 +         var j;
 +         for(j in $wnd.vaadin.vaadinConfigurations) {
++            // $entry not needed as function is not exported
 +            list.@java.util.Collection::add(Ljava/lang/Object;)(j);
 +         }
 +     }-*/;
 +
 +    public ShortcutActionHandler getShortcutActionHandler() {
 +        return actionHandler;
 +    }
 +
 +    public void focus() {
 +        getElement().focus();
 +    }
 +
 +}
index 58d6a1859288593c37ffd91dad9570aa70350112,1741ec5e7fe6a0a1010aff385e707427f7f24189..77698805de1d6a95add839d0ffe3454c8ac98df5
@@@ -509,30 -326,9 +509,30 @@@ public abstract class AbstractApplicati
  
      protected void handleRequest(PortletRequest request,
              PortletResponse response) throws PortletException, IOException {
-         RequestTimer requestTimer = RequestTimer.get(wrappedRequest);
-         requestTimer.start(wrappedRequest);
+         RequestTimer requestTimer = new RequestTimer();
+         requestTimer.start();
 +        AbstractApplicationPortletWrapper portletWrapper = new AbstractApplicationPortletWrapper(
 +                this);
 +
 +        WrappedPortletRequest wrappedRequest;
 +
 +        String portalInfo = request.getPortalContext().getPortalInfo()
 +                .toLowerCase();
 +        if (portalInfo.contains("liferay")) {
 +            wrappedRequest = new WrappedLiferayRequest(request,
 +                    getDeploymentConfiguration());
 +        } else if (portalInfo.contains("gatein")) {
 +            wrappedRequest = new WrappedGateinRequest(request,
 +                    getDeploymentConfiguration());
 +        } else {
 +            wrappedRequest = new WrappedPortletRequest(request,
 +                    getDeploymentConfiguration());
 +        }
 +
 +        WrappedPortletResponse wrappedResponse = new WrappedPortletResponse(
 +                response, getDeploymentConfiguration());
 +
          RequestType requestType = getRequestType(request);
  
          if (requestType == RequestType.UNKNOWN) {
  
                          }
                      } finally {
-                     }
 +                        Root.setCurrentRoot(null);
 +                        Application.setCurrentApplication(null);
-                     requestTimer.stop();
-                     RequestTimer.set(wrappedRequest, requestTimer);
 +
+                         requestTimer
+                                 .stop((AbstractWebApplicationContext) application
+                                         .getContext());
+                     }
                  }
              }
          }
index 799271b9795996cc472c36e1c9e437df488f0278,d42b2142fe63e373c6a8222aa114c58bc74d76b6..6ab2748332afea810f279d24a45d8eee353d05ac
@@@ -394,18 -400,10 +394,18 @@@ public abstract class AbstractApplicati
      @Override
      protected void service(HttpServletRequest request,
              HttpServletResponse response) throws ServletException, IOException {
-         RequestTimer requestTimer = RequestTimer.get(request);
-         requestTimer.start(request);
 +        service(createWrappedRequest(request), createWrappedResponse(response));
 +    }
 +
 +    private void service(WrappedHttpServletRequest request,
 +            WrappedHttpServletResponse response) throws ServletException,
 +            IOException {
+         RequestTimer requestTimer = new RequestTimer();
+         requestTimer.start();
 +        AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper(
 +                this);
 +
          RequestType requestType = getRequestType(request);
          if (!ensureCookiesEnabled(requestType, request, response)) {
              return;
                                  .onRequestEnd(request, response);
                      }
                  } finally {
-                 }
 +                    Root.setCurrentRoot(null);
 +                    Application.setCurrentApplication(null);
-                 requestTimer.stop();
-                 RequestTimer.set(request, requestTimer);
 +
+                     requestTimer
+                             .stop((AbstractWebApplicationContext) application
+                                     .getContext());
+                 }
              }
  
          }
index b780f66a23c3942621766143fc32579caf5fe7bc,cb570f88e7c1cbae90a01fb12c50641297e8a7d1..c57e7d8bc105b512be99a70daf558a34e1cc8178
@@@ -1101,179 -1210,16 +1101,178 @@@ public abstract class AbstractCommunica
      }
  
      /**
-      * Adds the performance timing data used by TestBench 3 to the UIDL
+      * Adds the performance timing data (used by TestBench 3) to the UIDL
       * response.
       */
-     private void writePerformanceDataForTestBench(final WrappedRequest request,
-             final PrintWriter outWriter) {
-         Long totalTime = (Long) request.getAttribute("TOTAL");
-         Long lastRequestTime = (Long) request.getAttribute("LASTREQUEST");
-         outWriter.write(String.format(", \"tbss\":[%d, %d]", totalTime,
-                 lastRequestTime));
+     private void writePerformanceData(final PrintWriter outWriter) {
+         AbstractWebApplicationContext ctx = (AbstractWebApplicationContext) application
+                 .getContext();
+         outWriter.write(String.format(", \"timings\":[%d, %d]",
+                 ctx.getTotalSessionTime(), ctx.getLastRequestTime()));
      }
  
 +    private void legacyPaint(PaintTarget paintTarget,
 +            ArrayList<ClientConnector> dirtyVisibleConnectors)
 +            throws PaintException {
 +        List<Vaadin6Component> legacyComponents = new ArrayList<Vaadin6Component>();
 +        for (Connector connector : dirtyVisibleConnectors) {
 +            // All Components that want to use paintContent must implement
 +            // Vaadin6Component
 +            if (connector instanceof Vaadin6Component) {
 +                legacyComponents.add((Vaadin6Component) connector);
 +            }
 +        }
 +        sortByHierarchy((List) legacyComponents);
 +        for (Vaadin6Component c : legacyComponents) {
 +            logger.fine("Painting Vaadin6Component " + c.getClass().getName()
 +                    + "@" + Integer.toHexString(c.hashCode()));
 +            paintTarget.startTag("change");
 +            final String pid = c.getConnectorId();
 +            paintTarget.addAttribute("pid", pid);
 +            LegacyPaint.paint(c, paintTarget);
 +            paintTarget.endTag("change");
 +        }
 +
 +    }
 +
 +    private void sortByHierarchy(List<Component> paintables) {
 +        // Vaadin 6 requires parents to be painted before children as component
 +        // containers rely on that their updateFromUIDL method has been called
 +        // before children start calling e.g. updateCaption
 +        Collections.sort(paintables, new Comparator<Component>() {
 +            public int compare(Component c1, Component c2) {
 +                int depth1 = 0;
 +                while (c1.getParent() != null) {
 +                    depth1++;
 +                    c1 = c1.getParent();
 +                }
 +                int depth2 = 0;
 +                while (c2.getParent() != null) {
 +                    depth2++;
 +                    c2 = c2.getParent();
 +                }
 +                if (depth1 < depth2) {
 +                    return -1;
 +                }
 +                if (depth1 > depth2) {
 +                    return 1;
 +                }
 +                return 0;
 +            }
 +        });
 +
 +    }
 +
 +    private ClientCache getClientCache(Root root) {
 +        Integer rootId = Integer.valueOf(root.getRootId());
 +        ClientCache cache = rootToClientCache.get(rootId);
 +        if (cache == null) {
 +            cache = new ClientCache();
 +            rootToClientCache.put(rootId, cache);
 +        }
 +        return cache;
 +    }
 +
 +    /**
 +     * Checks if the component is visible in context, i.e. returns false if the
 +     * child is hidden, the parent is hidden or the parent says the child should
 +     * not be rendered (using
 +     * {@link HasComponents#isComponentVisible(Component)}
 +     * 
 +     * @param child
 +     *            The child to check
 +     * @return true if the child is visible to the client, false otherwise
 +     */
 +    static boolean isVisible(Component child) {
 +        HasComponents parent = child.getParent();
 +        if (parent == null || !child.isVisible()) {
 +            return child.isVisible();
 +        }
 +
 +        return parent.isComponentVisible(child) && isVisible(parent);
 +    }
 +
 +    private static class NullIterator<E> implements Iterator<E> {
 +
 +        public boolean hasNext() {
 +            return false;
 +        }
 +
 +        public E next() {
 +            return null;
 +        }
 +
 +        public void remove() {
 +        }
 +
 +    }
 +
 +    public static Iterable<Component> getChildComponents(HasComponents cc) {
 +        // TODO This must be moved to Root/Panel
 +        if (cc instanceof Root) {
 +            Root root = (Root) cc;
 +            List<Component> children = new ArrayList<Component>();
 +            if (root.getContent() != null) {
 +                children.add(root.getContent());
 +            }
 +            for (Window w : root.getWindows()) {
 +                children.add(w);
 +            }
 +            return children;
 +        } else if (cc instanceof Panel) {
 +            // This is so wrong.. (#2924)
 +            if (((Panel) cc).getContent() == null) {
 +                return Collections.emptyList();
 +            } else {
 +                return Collections.singleton((Component) ((Panel) cc)
 +                        .getContent());
 +            }
 +        }
 +        return cc;
 +    }
 +
 +    /**
 +     * Collects all pending RPC calls from listed {@link ClientConnector}s and
 +     * clears their RPC queues.
 +     * 
 +     * @param rpcPendingQueue
 +     *            list of {@link ClientConnector} of interest
 +     * @return ordered list of pending RPC calls
 +     */
 +    private List<ClientMethodInvocation> collectPendingRpcCalls(
 +            List<ClientConnector> rpcPendingQueue) {
 +        List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
 +        for (ClientConnector connector : rpcPendingQueue) {
 +            List<ClientMethodInvocation> paintablePendingRpc = connector
 +                    .retrievePendingRpcCalls();
 +            if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) {
 +                List<ClientMethodInvocation> oldPendingRpc = pendingInvocations;
 +                int totalCalls = pendingInvocations.size()
 +                        + paintablePendingRpc.size();
 +                pendingInvocations = new ArrayList<ClientMethodInvocation>(
 +                        totalCalls);
 +
 +                // merge two ordered comparable lists
 +                for (int destIndex = 0, oldIndex = 0, paintableIndex = 0; destIndex < totalCalls; destIndex++) {
 +                    if (paintableIndex >= paintablePendingRpc.size()
 +                            || (oldIndex < oldPendingRpc.size() && ((Comparable<ClientMethodInvocation>) oldPendingRpc
 +                                    .get(oldIndex))
 +                                    .compareTo(paintablePendingRpc
 +                                            .get(paintableIndex)) <= 0)) {
 +                        pendingInvocations.add(oldPendingRpc.get(oldIndex++));
 +                    } else {
 +                        pendingInvocations.add(paintablePendingRpc
 +                                .get(paintableIndex++));
 +                    }
 +                }
 +            }
 +        }
 +        return pendingInvocations;
 +    }
 +
 +    protected abstract InputStream getThemeResourceAsStream(Root root,
 +            String themeName, String resource);
 +
      private int getTimeoutInterval() {
          return maxInactiveInterval;
      }
index d47f444bef1b8fafb7dc14b9a54b38edcec631fb,1fec2d280bdfdd63bd7d6f268cb61f688579c74b..6c0edec466b74e3d50325d17a3415f1eb5deb7c5
@@@ -42,39 -35,8 +35,9 @@@ public class RequestTimer implements Se
          // Measure and store the total handling time. This data can be
          // used in TestBench 3 tests.
          long time = (System.nanoTime() - requestStartTime) / 1000000;
-         lastRequestTime = time;
-         totalSessionTime += time;
-     }
-     /**
-      * Returns a valid request timer for the specified request. Timers are
-      * session bound.
-      * 
-      * @param request
-      *            the request for which to get a valid timer.
-      * @return a valid timer.
-      */
-     public static RequestTimer get(WrappedRequest request) {
-         RequestTimer timer = (RequestTimer) request
-                 .getSessionAttribute(SESSION_ATTR_ID);
-         if (timer == null) {
-             timer = new RequestTimer();
-         }
-         return timer;
-     }
 +
-     /**
-      * Associates the specified request timer with the specified request. Since
-      * {@link #get(RequestWrapper)} will, at one point or another, return a new
-      * instance, this method should be called to keep the request <-> timer
-      * association updated.
-      * 
-      * @param request
-      *            the request for which to set the timer.
-      * @param requestTimer
-      *            the timer.
-      */
-     public static void set(WrappedRequest request, RequestTimer requestTimer) {
-         request.setSessionAttribute(RequestTimer.SESSION_ATTR_ID, requestTimer);
+         // The timings must be stored in the context, since a new
+         // RequestTimer is created for every request.
+         context.setLastRequestTime(time);
      }
  }
index f5e45ef3ef4eec0c003ca66f941d84b589b6dcd0,fdaef046e5233823c7d445e4157381b09d6ec2b7..876fe593e27987ff23f35e064c8674a2cd0c229b
@@@ -469,24 -703,33 +469,53 @@@ public class Button extends AbstractCom
          requestRepaint();
      }
  
 -        if (this.htmlContentAllowed != htmlContentAllowed) {
 -            this.htmlContentAllowed = htmlContentAllowed;
 +    public int getTabIndex() {
 +        return tabIndex;
 +    }
 +
 +    public void setTabIndex(int tabIndex) {
 +        this.tabIndex = tabIndex;
 +
 +    }
 +
 +    @Override
 +    public void focus() {
 +        // Overridden only to make public
 +        super.focus();
 +    }
 +
 +    @Override
 +    public ButtonState getState() {
 +        return (ButtonState) super.getState();
 +    }
 +
+     /**
+      * Set whether the caption text is rendered as HTML or not. You might need
+      * to retheme button to allow higher content than the original text style.
+      * 
+      * If set to true, the captions are passed to the browser as html and the
+      * developer is responsible for ensuring no harmful html is used. If set to
+      * false, the content is passed to the browser as plain text.
+      * 
+      * @param htmlContentAllowed
+      *            <code>true</code> if caption is rendered as HTML,
+      *            <code>false</code> otherwise
+      */
+     public void setHtmlContentAllowed(boolean htmlContentAllowed) {
 -        return htmlContentAllowed;
++        if (getState().isHtmlContentAllowed() != htmlContentAllowed) {
++            getState().setHtmlContentAllowed(htmlContentAllowed);
+             requestRepaint();
+         }
+     }
+     /**
+      * Return HTML rendering setting
+      * 
+      * @return <code>true</code> if the caption text is to be rendered as HTML,
+      *         <code>false</code> otherwise
+      */
+     public boolean isHtmlContentAllowed() {
++        return getState().isHtmlContentAllowed();
+     }
  }
Simple merge
diff --cc tests/test.xml
index 28459c37e93b1139033927b369030f3b84ef6a04,acbbe672efb87bdab8915555540acb402d71918c..67e48ee13756e41fefa7a523812b09fc5136d1ef
@@@ -11,7 -5,7 +11,7 @@@
        <!-- Configuration                                                      -->
        <!-- ================================================================== -->
        <!-- Browsers to use for testing -->
-       <property name="browsers-windows" value="winxp-ie8,win7-ie9,winxp-firefox11,winxp-safari5,winxp-googlechrome18,winxp-opera11" />
 -      <property name="browsers-windows" value="winxp-ie6,winxp-ie7,winxp-ie8,win7-ie9,winxp-firefox36,winxp-firefox12,winxp-safari4,winxp-safari5,winxp-googlechrome13,winxp-googlechrome18,winxp-opera1060,winxp-opera11" />
++      <property name="browsers-windows" value="winxp-ie8,win7-ie9,winxp-firefox12,winxp-safari5,winxp-googlechrome18,winxp-opera11" />
        <property name="browsers-linux" value="linux-firefox3,linux-opera10,linux-googlechrome8" />
        <property name="browsers-mac" value="osx-firefox3,osx-opera10,osx-googlechrome8,osx-safari4,osx-safari5" />
  
index 636bddead8b2716597ca79b1fa33c2faeee20668,52f5a7c067d20fb35b44f61cace6217b1ccd8358..053691e7380bfbcd79aa8a0c663005d03080363b
@@@ -33,9 -37,81 +37,80 @@@ public class TouchScrollables extends T
  
      @Override
      public void setup() {
-         testSelector = new HorizontalLayout();
          getLayout().addComponent(testSelector);
+         testSelector.setHeight("500px");
  
 -                        getLayout().getWindow(),
+         addTest(getPanelTest());
+         addTest(getSimpleTableTest());
+         addTest(getDDSortableTableTest());
+         addTest(getTabSheetTest());
+         addTest(getSplitPanelTest());
+         addTest(getAccordionTest());
+         addTest(getSubWindowTest());
+         TestUtils
+                 .injectCSS(
 -        p.setScrollable(true);
++                        getLayout().getRoot(),
+                         "body * {-webkit-user-select: none;} .v-table-row-drag-middle .v-table-cell-content {"
+                                 + "        background-color: inherit ; border-bottom: 1px solid cyan;"
+                                 + "}"
+                                 + ".v-table-row-drag-middle .v-table-cell-wrapper {"
+                                 + "        margin-bottom: -1px;" + "}" + ""
+                 );
+     }
+     private Component getPanelTest() {
+         Layout cssLayout = new CssLayout();
+         cssLayout.setCaption("Panel");
+         final Panel p = new Panel();
 -                        getLayout().getWindow().scrollIntoView(l);
+         p.setHeight("400px");
+         Label l50 = null;
+         for (int i = 0; i < 100; i++) {
+             Label c = new Label("Label" + i);
+             p.addComponent(c);
+             if (i == 50) {
+                 l50 = c;
+             }
+         }
+         final Label l = l50;
+         Button button = new Button("Scroll to label 50",
+                 new Button.ClickListener() {
+                     public void buttonClick(ClickEvent event) {
++                        getLayout().getRoot().scrollIntoView(l);
+                     }
+                 });
+         cssLayout.addComponent(button);
+         button = new Button("Scroll to 100px", new Button.ClickListener() {
+             public void buttonClick(ClickEvent event) {
+                 p.setScrollTop(100);
+             }
+         });
+         cssLayout.addComponent(button);
+         cssLayout.addComponent(p);
+         return cssLayout;
+     }
+     private Component getTabSheetTest() {
+         TabSheet ts = new TabSheet();
+         ts.setCaption("Tabsheet");
+         ts.setHeight("100%");
+         ts.addTab(getBigComponent(), "Tab 1");
+         ts.addTab(getBigComponent(), "Tab 2");
+         return ts;
+     }
+     private Component getSplitPanelTest() {
+         HorizontalSplitPanel sp = new HorizontalSplitPanel();
+         sp.setCaption("Splitpanel");
+         sp.addComponent(getBigComponent());
+         sp.addComponent(getBigComponent());
+         return sp;
+     }
+     private Component getSimpleTableTest() {
          CssLayout cssLayout = new CssLayout();
          final Table table = new Table();
  
index 0000000000000000000000000000000000000000,a192f1a3a644e45984a2bd1a2059d166ae9d91f0..37b0fe21a11ac2f81899b66942f0504048de3c5a
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,61 +1,62 @@@
 -import com.vaadin.tests.components.TestBase;
+ package com.vaadin.tests.components.combobox;
 -import com.vaadin.ui.Window;
++import com.vaadin.tests.components.AbstractTestCase;
+ import com.vaadin.ui.ComboBox;
+ import com.vaadin.ui.GridLayout;
+ import com.vaadin.ui.Label;
+ import com.vaadin.ui.Layout;
++import com.vaadin.ui.Root;
+ import com.vaadin.ui.Select;
 -public class GridLayoutComboBoxZoomOut extends TestBase {
+ @SuppressWarnings("serial")
 -    public void setup() {
 -        Window mainWindow = new Window("Gridlayoutbug Application");
++public class GridLayoutComboBoxZoomOut extends AbstractTestCase {
+     @Override
++    public void init() {
++        Root.LegacyWindow mainWindow = new Root.LegacyWindow(
++                "Gridlayoutbug Application");
+         setMainWindow(mainWindow);
+         Label description = new Label(
+                 "Open this application in Chrome, zoom out (cmd + \"-\") and "
+                         + "open the ComboBox for weird behaviour.");
+         mainWindow.addComponent(description);
+         Layout formLayout = new GridLayout(2, 1);
+         // formLayout.setWidth("100%");
+         formLayout.setWidth("1000px");
+         Select countryField = new ComboBox();
+         countryField.addItem("Finland");
+         countryField.addItem("Sweden");
+         countryField.addItem("Canada");
+         countryField.addItem("USA");
+         countryField.setCaption("Country");
+         countryField.setWidth("100%");
+         formLayout.addComponent(countryField);
+         Select statusField = new ComboBox();
+         statusField.addItem("Available");
+         statusField.addItem("On vacation");
+         statusField.addItem("Busy");
+         statusField.addItem("Left the building");
+         statusField.setCaption("Status");
+         statusField.setWidth("100%");
+         formLayout.addComponent(statusField);
+         mainWindow.addComponent(formLayout);
+     }
+     @Override
+     protected String getDescription() {
+         // TODO Auto-generated method stub
+         return null;
+     }
+     @Override
+     protected Integer getTicketNumber() {
+         // TODO Auto-generated method stub
+         return null;
+     }
+ }