]> source.dussan.org Git - vaadin-framework.git/commitdiff
Merge branch 'master' into root-cleanup
authorJohannes Dahlström <johannesd@vaadin.com>
Mon, 27 Aug 2012 09:40:41 +0000 (12:40 +0300)
committerJohannes Dahlström <johannesd@vaadin.com>
Mon, 27 Aug 2012 09:40:41 +0000 (12:40 +0300)
Rename Root -> UI in root cleanup code

Conflicts:
client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
server/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java
server/src/com/vaadin/ui/UI.java

15 files changed:
1  2 
client/src/com/vaadin/terminal/gwt/client/ApplicationConfiguration.java
client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
client/src/com/vaadin/terminal/gwt/client/ui/UI/UIConnector.java
server/src/com/vaadin/Application.java
server/src/com/vaadin/terminal/DeploymentConfiguration.java
server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
server/src/com/vaadin/terminal/gwt/server/AbstractDeploymentConfiguration.java
server/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java
server/src/com/vaadin/terminal/gwt/server/Constants.java
server/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java
server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java
server/src/com/vaadin/ui/UI.java
shared/src/com/vaadin/shared/ApplicationConstants.java

index 27718716531e6d81d08b81f49c7fcfbe37a30564,6621de7f957e4f324d2c2db1ac7867ce1fc21d13..2eccd9bb8c811389637954b8cc0403f6bb4e8b87
@@@ -286,18 -289,10 +290,18 @@@ public class ApplicationConfiguration i
       * 
       * @return the root id
       */
-     public int getRootId() {
-         return rootId;
+     public int getUIId() {
+         return uiId;
      }
  
 +    /**
 +     * @return The interval in seconds between heartbeat requests, or a
 +     *         non-positive number if heartbeat is disabled.
 +     */
 +    public int getHeartbeatInterval() {
 +        return heartbeatInterval;
 +    }
 +
      public JavaScriptObject getVersionInfoJSObject() {
          return getJsoConfiguration(id).getVersionInfoJSObject();
      }
index 299278269c1854287a06b4ea5d5adcb4869ecc4f,fc063a19081c25b866187fb59971ea8f9797b9b8..450972ddc6614b8b4413450c5cedc31255610684
@@@ -240,10 -244,8 +244,10 @@@ public class ApplicationConnection 
  
          initializeClientHooks();
  
-         rootConnector.init(cnf.getRootPanelId(), this);
+         uIConnector.init(cnf.getRootPanelId(), this);
          showLoadingIndicator();
 +
 +        scheduleHeartbeat();
      }
  
      /**
      LayoutManager getLayoutManager() {
          return layoutManager;
      }
-     public SerializerMap getSerializerMap() {
-         return serializerMap;
-     }
 +
-                 ApplicationConstants.ROOT_ID_PARAMETER + "="
-                         + getConfiguration().getRootId());
 +    /**
 +     * Schedules a heartbeat request to occur after the configured heartbeat
 +     * interval elapses if the interval is a positive number. Otherwise, does
 +     * nothing.
 +     * 
 +     * @see #sendHeartbeat()
 +     * @see ApplicationConfiguration#getHeartbeatInterval()
 +     */
 +    protected void scheduleHeartbeat() {
 +        final int interval = getConfiguration().getHeartbeatInterval();
 +        if (interval > 0) {
 +            VConsole.log("Scheduling heartbeat in " + interval + " seconds");
 +            new Timer() {
 +                @Override
 +                public void run() {
 +                    sendHeartbeat();
 +                }
 +            }.schedule(interval * 1000);
 +        }
 +    }
 +
 +    /**
 +     * Sends a heartbeat request to the server.
 +     * <p>
 +     * Heartbeat requests are used to inform the server that the client-side is
 +     * still alive. If the client page is closed or the connection lost, the
 +     * server will eventually close the inactive Root.
 +     * <p>
 +     * <b>TODO</b>: Improved error handling, like in doUidlRequest().
 +     * 
 +     * @see #scheduleHeartbeat()
 +     */
 +    protected void sendHeartbeat() {
 +        final String uri = addGetParameters(
 +                translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
 +                        + ApplicationConstants.HEARTBEAT_REQUEST_PATH),
++                UIConstants.UI_ID_PARAMETER + "="
++                        + getConfiguration().getUIId());
 +
 +        final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
 +
 +        final RequestCallback callback = new RequestCallback() {
 +
 +            @Override
 +            public void onResponseReceived(Request request, Response response) {
 +                int status = response.getStatusCode();
 +                if (status == Response.SC_OK) {
 +                    // TODO Permit retry in some error situations
 +                    VConsole.log("Heartbeat response OK");
 +                    scheduleHeartbeat();
 +                } else {
 +                    VConsole.error("Heartbeat request failed with status code "
 +                            + status);
 +                }
 +            }
 +
 +            @Override
 +            public void onError(Request request, Throwable exception) {
 +                VConsole.error("Heartbeat request resulted in exception");
 +                VConsole.error(exception);
 +            }
 +        };
 +
 +        rb.setCallback(callback);
 +
 +        try {
 +            VConsole.log("Sending heartbeat request...");
 +            rb.send();
 +        } catch (RequestException re) {
 +            callback.onError(null, re);
 +        }
 +    }
  }
index 0000000000000000000000000000000000000000,f260481c3c87dec7009fc45d63ae34d418d05a2d..4e1bed1aa83c996f80580878ac22cded1feabd97
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,457 +1,456 @@@
 -
+ /*
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.terminal.gwt.client.ui.UI;
+ import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.List;
+ import com.google.gwt.core.client.Scheduler;
+ import com.google.gwt.dom.client.NativeEvent;
+ import com.google.gwt.dom.client.Style;
+ import com.google.gwt.dom.client.Style.Position;
+ import com.google.gwt.event.logical.shared.ResizeEvent;
+ import com.google.gwt.event.logical.shared.ResizeHandler;
+ import com.google.gwt.user.client.Command;
+ 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.Window;
+ import com.google.gwt.user.client.ui.RootPanel;
+ import com.google.gwt.user.client.ui.Widget;
+ import com.google.web.bindery.event.shared.HandlerRegistration;
+ import com.vaadin.shared.MouseEventDetails;
+ import com.vaadin.shared.ui.Connect;
+ import com.vaadin.shared.ui.Connect.LoadStyle;
+ import com.vaadin.shared.ui.ui.PageClientRpc;
+ import com.vaadin.shared.ui.ui.UIConstants;
+ import com.vaadin.shared.ui.ui.UIServerRpc;
+ import com.vaadin.shared.ui.ui.UIState;
+ 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.ConnectorHierarchyChangeEvent;
+ import com.vaadin.terminal.gwt.client.ConnectorMap;
+ import com.vaadin.terminal.gwt.client.Focusable;
+ import com.vaadin.terminal.gwt.client.Paintable;
+ import com.vaadin.terminal.gwt.client.UIDL;
+ import com.vaadin.terminal.gwt.client.VConsole;
+ import com.vaadin.terminal.gwt.client.communication.RpcProxy;
+ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
+ import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler;
+ import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector;
+ import com.vaadin.terminal.gwt.client.ui.ClickEventHandler;
+ import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler;
+ import com.vaadin.terminal.gwt.client.ui.layout.MayScrollChildren;
+ import com.vaadin.terminal.gwt.client.ui.notification.VNotification;
+ import com.vaadin.terminal.gwt.client.ui.window.WindowConnector;
+ import com.vaadin.ui.UI;
+ @Connect(value = UI.class, loadStyle = LoadStyle.EAGER)
+ public class UIConnector extends AbstractComponentContainerConnector
+         implements Paintable, MayScrollChildren {
+     private UIServerRpc rpc = RpcProxy.create(UIServerRpc.class, this);
+     private HandlerRegistration childStateChangeHandlerRegistration;
+     private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
+         @Override
+         public void onStateChanged(StateChangeEvent stateChangeEvent) {
+             // TODO Should use a more specific handler that only reacts to
+             // size changes
+             onChildSizeChange();
+         }
+     };
+     @Override
+     protected void init() {
+         super.init();
+         registerRpc(PageClientRpc.class, new PageClientRpc() {
+             @Override
+             public void setTitle(String title) {
+                 com.google.gwt.user.client.Window.setTitle(title);
+             }
+         });
+         getWidget().addResizeHandler(new ResizeHandler() {
+             @Override
+             public void onResize(ResizeEvent event) {
+                 rpc.resize(event.getHeight(), event.getWidth(),
+                         Window.getClientWidth(), Window.getClientHeight());
+                 if (getState().isImmediate()) {
+                     getConnection().sendPendingVariableChanges();
+                 }
+             }
+         });
+     }
+     @Override
+     public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) {
+         ConnectorMap paintableMap = ConnectorMap.get(getConnection());
+         getWidget().rendering = true;
+         getWidget().id = getConnectorId();
+         boolean firstPaint = getWidget().connection == null;
+         getWidget().connection = client;
+         getWidget().immediate = getState().isImmediate();
+         getWidget().resizeLazy = uidl.hasAttribute(UIConstants.RESIZE_LAZY);
+         String newTheme = uidl.getStringAttribute("theme");
+         if (getWidget().theme != null && !newTheme.equals(getWidget().theme)) {
+             // Complete page refresh is needed due css can affect layout
+             // calculations etc
+             getWidget().reloadHostPage();
+         } else {
+             getWidget().theme = newTheme;
+         }
+         // this also implicitly removes old styles
+         String styles = "";
+         styles += getWidget().getStylePrimaryName() + " ";
+         if (getState().hasStyles()) {
+             for (String style : getState().getStyles()) {
+                 styles += style + " ";
+             }
+         }
+         if (!client.getConfiguration().isStandalone()) {
+             styles += getWidget().getStylePrimaryName() + "-embedded";
+         }
+         getWidget().setStyleName(styles.trim());
+         getWidget().makeScrollable();
+         clickEventHandler.handleEventHandlerRegistration();
+         // Process children
+         int childIndex = 0;
+         // Open URL:s
+         boolean isClosed = false; // was this window closed?
+         while (childIndex < uidl.getChildCount()
+                 && "open".equals(uidl.getChildUIDL(childIndex).getTag())) {
+             final UIDL open = uidl.getChildUIDL(childIndex);
+             final String url = client.translateVaadinUri(open
+                     .getStringAttribute("src"));
+             final String target = open.getStringAttribute("name");
+             if (target == null) {
+                 // source will be opened to this browser window, but we may have
+                 // to finish rendering this window in case this is a download
+                 // (and window stays open).
+                 Scheduler.get().scheduleDeferred(new Command() {
+                     @Override
+                     public void execute() {
+                         VUI.goTo(url);
+                     }
+                 });
+             } else if ("_self".equals(target)) {
+                 // This window is closing (for sure). Only other opens are
+                 // relevant in this change. See #3558, #2144
+                 isClosed = true;
+                 VUI.goTo(url);
+             } else {
+                 String options;
+                 if (open.hasAttribute("border")) {
+                     if (open.getStringAttribute("border").equals("minimal")) {
+                         options = "menubar=yes,location=no,status=no";
+                     } else {
+                         options = "menubar=no,location=no,status=no";
+                     }
+                 } else {
+                     options = "resizable=yes,menubar=yes,toolbar=yes,directories=yes,location=yes,scrollbars=yes,status=yes";
+                 }
+                 if (open.hasAttribute("width")) {
+                     int w = open.getIntAttribute("width");
+                     options += ",width=" + w;
+                 }
+                 if (open.hasAttribute("height")) {
+                     int h = open.getIntAttribute("height");
+                     options += ",height=" + h;
+                 }
+                 Window.open(url, target, options);
+             }
+             childIndex++;
+         }
+         if (isClosed) {
+             // don't render the content, something else will be opened to this
+             // browser view
+             getWidget().rendering = false;
+             return;
+         }
+         // Handle other UIDL children
+         UIDL childUidl;
+         while ((childUidl = uidl.getChildUIDL(childIndex++)) != null) {
+             String tag = childUidl.getTag().intern();
+             if (tag == "actions") {
+                 if (getWidget().actionHandler == null) {
+                     getWidget().actionHandler = new ShortcutActionHandler(
+                             getWidget().id, client);
+                 }
+                 getWidget().actionHandler.updateActionMap(childUidl);
+             } else if (tag == "notifications") {
+                 for (final Iterator<?> it = childUidl.getChildIterator(); it
+                         .hasNext();) {
+                     final UIDL notification = (UIDL) it.next();
+                     VNotification.showNotification(client, notification);
+                 }
+             }
+         }
+         if (uidl.hasAttribute("focused")) {
+             // set focused component when render phase is finished
+             Scheduler.get().scheduleDeferred(new Command() {
+                 @Override
+                 public void execute() {
+                     ComponentConnector paintable = (ComponentConnector) uidl
+                             .getPaintableAttribute("focused", getConnection());
+                     final Widget toBeFocused = paintable.getWidget();
+                     /*
+                      * Two types of Widgets can be focused, either implementing
+                      * GWT HasFocus of a thinner Vaadin specific Focusable
+                      * interface.
+                      */
+                     if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) {
+                         final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) toBeFocused;
+                         toBeFocusedWidget.setFocus(true);
+                     } else if (toBeFocused instanceof Focusable) {
+                         ((Focusable) toBeFocused).focus();
+                     } else {
+                         VConsole.log("Could not focus component");
+                     }
+                 }
+             });
+         }
+         // Add window listeners on first paint, to prevent premature
+         // variablechanges
+         if (firstPaint) {
+             Window.addWindowClosingHandler(getWidget());
+             Window.addResizeHandler(getWidget());
+         }
+         // finally set scroll position from UIDL
+         if (uidl.hasVariable("scrollTop")) {
+             getWidget().scrollable = true;
+             getWidget().scrollTop = uidl.getIntVariable("scrollTop");
+             DOM.setElementPropertyInt(getWidget().getElement(), "scrollTop",
+                     getWidget().scrollTop);
+             getWidget().scrollLeft = uidl.getIntVariable("scrollLeft");
+             DOM.setElementPropertyInt(getWidget().getElement(), "scrollLeft",
+                     getWidget().scrollLeft);
+         } else {
+             getWidget().scrollable = false;
+         }
+         if (uidl.hasAttribute("scrollTo")) {
+             final ComponentConnector connector = (ComponentConnector) uidl
+                     .getPaintableAttribute("scrollTo", getConnection());
+             scrollIntoView(connector);
+         }
+         if (uidl.hasAttribute(UIConstants.FRAGMENT_VARIABLE)) {
+             getWidget().currentFragment = uidl
+                     .getStringAttribute(UIConstants.FRAGMENT_VARIABLE);
+             if (!getWidget().currentFragment.equals(History.getToken())) {
+                 History.newItem(getWidget().currentFragment, true);
+             }
+         } else {
+             // Initial request for which the server doesn't yet have a fragment
+             // (and haven't shown any interest in getting one)
+             getWidget().currentFragment = History.getToken();
+             // Include current fragment in the next request
+             client.updateVariable(getWidget().id,
+                     UIConstants.FRAGMENT_VARIABLE,
+                     getWidget().currentFragment, false);
+         }
+         if (firstPaint) {
+             // Queue the initial window size to be sent with the following
+             // request.
+             getWidget().sendClientResized();
+         }
+         getWidget().rendering = false;
+     }
+     public void init(String rootPanelId,
+             ApplicationConnection applicationConnection) {
+         DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN
+                 | Event.ONSCROLL);
+         // iview is focused when created so element needs tabIndex
+         // 1 due 0 is at the end of natural tabbing order
+         DOM.setElementProperty(getWidget().getElement(), "tabIndex", "1");
+         RootPanel root = RootPanel.get(rootPanelId);
+         // Remove the v-app-loading or any splash screen added inside the div by
+         // the user
+         root.getElement().setInnerHTML("");
+         root.addStyleName("v-theme-"
+                 + applicationConnection.getConfiguration().getThemeName());
+         root.add(getWidget());
+         if (applicationConnection.getConfiguration().isStandalone()) {
+             // set focus to iview element by default to listen possible keyboard
+             // shortcuts. For embedded applications this is unacceptable as we
+             // don't want to steal focus from the main page nor we don't want
+             // side-effects from focusing (scrollIntoView).
+             getWidget().getElement().focus();
+         }
+     }
+     private ClickEventHandler clickEventHandler = new ClickEventHandler(this) {
+         @Override
+         protected void fireClick(NativeEvent event,
+                 MouseEventDetails mouseDetails) {
+             rpc.click(mouseDetails);
+         }
+     };
+     @Override
+     public void updateCaption(ComponentConnector component) {
+         // NOP The main view never draws caption for its layout
+     }
+     @Override
+     public VUI getWidget() {
+         return (VUI) super.getWidget();
+     }
+     protected ComponentConnector getContent() {
+         return (ComponentConnector) getState().getContent();
+     }
+     protected void onChildSizeChange() {
+         ComponentConnector child = getContent();
+         Style childStyle = child.getWidget().getElement().getStyle();
+         /*
+          * Must set absolute position if the child has relative height and
+          * there's a chance of horizontal scrolling as some browsers will
+          * otherwise not take the scrollbar into account when calculating the
+          * height. Assuming v-view does not have an undefined width for now, see
+          * #8460.
+          */
+         if (child.isRelativeHeight() && !BrowserInfo.get().isIE9()) {
+             childStyle.setPosition(Position.ABSOLUTE);
+         } else {
+             childStyle.clearPosition();
+         }
+     }
+     /**
+      * Checks if the given sub window is a child of this UI Connector
+      * 
+      * @deprecated Should be replaced by a more generic mechanism for getting
+      *             non-ComponentConnector children
+      * @param wc
+      * @return
+      */
+     @Deprecated
+     public boolean hasSubWindow(WindowConnector wc) {
+         return getChildComponents().contains(wc);
+     }
+     /**
+      * Return an iterator for current subwindows. This method is meant for
+      * testing purposes only.
+      * 
+      * @return
+      */
+     public List<WindowConnector> getSubWindows() {
+         ArrayList<WindowConnector> windows = new ArrayList<WindowConnector>();
+         for (ComponentConnector child : getChildComponents()) {
+             if (child instanceof WindowConnector) {
+                 windows.add((WindowConnector) child);
+             }
+         }
+         return windows;
+     }
+     @Override
+     public UIState getState() {
+         return (UIState) super.getState();
+     }
+     @Override
+     public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
+         super.onConnectorHierarchyChange(event);
+         ComponentConnector oldChild = null;
+         ComponentConnector newChild = getContent();
+         for (ComponentConnector c : event.getOldChildren()) {
+             if (!(c instanceof WindowConnector)) {
+                 oldChild = c;
+                 break;
+             }
+         }
+         if (oldChild != newChild) {
+             if (childStateChangeHandlerRegistration != null) {
+                 childStateChangeHandlerRegistration.removeHandler();
+                 childStateChangeHandlerRegistration = null;
+             }
+             getWidget().setWidget(newChild.getWidget());
+             childStateChangeHandlerRegistration = newChild
+                     .addStateChangeHandler(childStateChangeHandler);
+             // Must handle new child here as state change events are already
+             // fired
+             onChildSizeChange();
+         }
+         for (ComponentConnector c : getChildComponents()) {
+             if (c instanceof WindowConnector) {
+                 WindowConnector wc = (WindowConnector) c;
+                 wc.setWindowOrderAndPosition();
+             }
+         }
+         // Close removed sub windows
+         for (ComponentConnector c : event.getOldChildren()) {
+             if (c.getParent() != this && c instanceof WindowConnector) {
+                 ((WindowConnector) c).getWidget().hide();
+             }
+         }
+     }
+     /**
+      * Tries to scroll the viewport so that the given connector is in view.
+      * 
+      * @param componentConnector
+      *            The connector which should be visible
+      * 
+      */
+     public void scrollIntoView(final ComponentConnector componentConnector) {
+         if (componentConnector == null) {
+             return;
+         }
+         Scheduler.get().scheduleDeferred(new Command() {
+             @Override
+             public void execute() {
+                 componentConnector.getWidget().getElement().scrollIntoView();
+             }
+         });
+     }
+ }
index 8efcadb66747b531f343e7d9c4d08b64ae5bed81,96d38e31cffda218e8b267b1e43e13711f517daa..bdad94355d8d4b2cd0283601f84f0cf785396679
@@@ -580,19 -579,19 +580,19 @@@ public class Application implements Ter
  
      /**
       * Ends the Application.
 -     * 
       * <p>
       * In effect this will cause the application stop returning any windows when
 -     * asked. When the application is closed, its state is removed from the
 -     * session and the browser window is redirected to the application logout
 -     * url set with {@link #setLogoutURL(String)}. If the logout url has not
 -     * been set, the browser window is reloaded and the application is
 -     * restarted.
 -     * </p>
 -     * .
 +     * asked. When the application is closed, close events are fired for its
-      * roots, its state is removed from the session, and the browser window is
++     * UIs, its state is removed from the session, and the browser window is
 +     * redirected to the application logout url set with
 +     * {@link #setLogoutURL(String)}. If the logout url has not been set, the
 +     * browser window is reloaded and the application is restarted.
       */
      public void close() {
          applicationIsRunning = false;
-         for (Root root : getRoots()) {
-             root.fireCloseEvent();
++        for (UI ui : getUIs()) {
++            ui.fireCloseEvent();
 +        }
      }
  
      /**
      public void modifyBootstrapResponse(BootstrapResponse response) {
          eventRouter.fireEvent(response);
      }
-      * Removes all those roots from the application for whom
-      * {@link #isRootAlive} returns false. Close events are fired for the
-      * removed roots.
 +
 +    /**
-      * @see Root.CloseEvent
-      * @see Root.CloseListener
-      * @see #isRootAlive(Root)
++     * Removes all those UIs from the application for which {@link #isUIAlive}
++     * returns false. Close events are fired for the removed UIs.
 +     * <p>
 +     * Called by the framework at the end of every request.
 +     * 
-     public void closeInactiveRoots() {
-         for (Iterator<Root> i = roots.values().iterator(); i.hasNext();) {
-             Root root = i.next();
-             if (!isRootAlive(root)) {
++     * @see UI.CloseEvent
++     * @see UI.CloseListener
++     * @see #isUIAlive(UI)
 +     * 
 +     * @since 7.0.0
 +     */
-                 retainOnRefreshRoots.values().remove(root.getRootId());
-                 root.fireCloseEvent();
++    public void closeInactiveUIs() {
++        for (Iterator<UI> i = uIs.values().iterator(); i.hasNext();) {
++            UI ui = i.next();
++            if (!isUIAlive(ui)) {
 +                i.remove();
-                         "Closed root #" + root.getRootId()
-                                 + " due to inactivity");
++                retainOnRefreshUIs.values().remove(ui.getUIId());
++                ui.fireCloseEvent();
 +                getLogger().info(
-      * UIDL request being received from a root before that root is removed from
-      * the application. This is a lower bound; it might take longer to close an
-      * inactive root. Returns a negative number if heartbeat is disabled and
++                        "Closed UI #" + ui.getUIId() + " due to inactivity");
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Returns the number of seconds that must pass without a valid heartbeat or
-      * @see #closeInactiveRoots()
++     * UIDL request being received from a UI before that UI is removed from the
++     * application. This is a lower bound; it might take longer to close an
++     * inactive UI. Returns a negative number if heartbeat is disabled and
 +     * timeout never occurs.
 +     * 
 +     * @see #getUidlRequestTimeout()
-         // Permit three missed heartbeats before closing the root
++     * @see #closeInactiveUIs()
 +     * @see DeploymentConfiguration#getHeartbeatInterval()
 +     * 
 +     * @since 7.0.0
 +     * 
 +     * @return The heartbeat timeout in seconds or a negative number if timeout
 +     *         never occurs.
 +     */
 +    protected int getHeartbeatTimeout() {
-      * being received from a root before the root is removed from the
-      * application, even though heartbeat requests are received. This is a lower
-      * bound; it might take longer to close an inactive root. Returns a negative
-      * number if
++        // Permit three missed heartbeats before closing the UI
 +        return (int) (configuration.getHeartbeatInterval() * (3.1));
 +    }
 +
 +    /**
 +     * Returns the number of seconds that must pass without a valid UIDL request
-      * This timeout only has effect if cleanup of inactive roots is enabled;
-      * otherwise heartbeat requests are enough to extend root lifetime
++     * being received from a UI before the UI is removed from the application,
++     * even though heartbeat requests are received. This is a lower bound; it
++     * might take longer to close an inactive UI. Returns a negative number if
 +     * <p>
-      * @see DeploymentConfiguration#isIdleRootCleanupEnabled()
++     * This timeout only has effect if cleanup of inactive UIs is enabled;
++     * otherwise heartbeat requests are enough to extend UI lifetime
 +     * indefinitely.
 +     * 
-      * @see #closeInactiveRoots()
++     * @see DeploymentConfiguration#isIdleUICleanupEnabled()
 +     * @see #getHeartbeatTimeout()
-         return configuration.isIdleRootCleanupEnabled() ? getContext()
++     * @see #closeInactiveUIs()
 +     * 
 +     * @since 7.0.0
 +     * 
 +     * @return The UIDL request timeout in seconds, or a negative number if
 +     *         timeout never occurs.
 +     */
 +    protected int getUidlRequestTimeout() {
-      * Returns whether the given root is alive (the client-side actively
++        return configuration.isIdleUICleanupEnabled() ? getContext()
 +                .getMaxInactiveInterval() : -1;
 +    }
 +
 +    /**
-      * @param root
-      *            The Root whose status to check
-      * @return true if the root is alive, false if it could be removed.
++     * Returns whether the given UI is alive (the client-side actively
 +     * communicates with the server) or whether it can be removed from the
 +     * application and eventually collected.
 +     * 
 +     * @since 7.0.0
 +     * 
-     protected boolean isRootAlive(Root root) {
++     * @param ui
++     *            The UI whose status to check
++     * @return true if the UI is alive, false if it could be removed.
 +     */
-                 && now - root.getLastHeartbeatTime() > 1000 * getHeartbeatTimeout()) {
++    protected boolean isUIAlive(UI ui) {
 +        long now = System.currentTimeMillis();
 +        if (getHeartbeatTimeout() >= 0
-                 && now - root.getLastUidlRequestTime() > 1000 * getUidlRequestTimeout()) {
++                && now - ui.getLastHeartbeatTime() > 1000 * getHeartbeatTimeout()) {
 +            return false;
 +        }
 +        if (getUidlRequestTimeout() >= 0
++                && now - ui.getLastUidlRequestTime() > 1000 * getUidlRequestTimeout()) {
 +            return false;
 +        }
 +        return true;
 +    }
  }
index d3fd4567f2c398cbb4627c6baae9b7d6e30664b4,8da088969d16b4b71b42c48a4a58473d77de9a9b..0cfbdb75443ed6cdf0e6ff750bf1d9f42dd5413c
@@@ -160,29 -153,4 +160,29 @@@ public interface DeploymentConfiguratio
       * @return The resource cache time.
       */
      public int getResourceCacheTime();
-      * Returns the number of seconds between heartbeat requests of a root, or a
 +
 +    /**
-      * Returns whether roots that have no other activity than heartbeat requests
++     * Returns the number of seconds between heartbeat requests of a UI, or a
 +     * non-positive number if heartbeat is disabled.
 +     * 
 +     * @since 7.0.0
 +     * 
 +     * @return The time between heartbeats.
 +     */
 +    public int getHeartbeatInterval();
 +
 +    /**
-      * @return True if roots receiving only heartbeat requests are eventually
-      *         closed; false if heartbeat requests extend root lifetime
++     * Returns whether UIs that have no other activity than heartbeat requests
 +     * should be closed after they have been idle the maximum inactivity time
 +     * enforced by the session.
 +     * 
 +     * @see ApplicationContext#getMaxInactiveInterval()
 +     * 
 +     * @since 7.0.0
 +     * 
-     public boolean isIdleRootCleanupEnabled();
++     * @return True if UIs receiving only heartbeat requests are eventually
++     *         closed; false if heartbeat requests extend UI lifetime
 +     *         indefinitely.
 +     */
++    public boolean isIdleUICleanupEnabled();
  }
index dea42b6b69292877ea1223ddd926f3da36c4cdd8,a9e60280901fb2d9bf83e65d311e84f0678fadd0..345f462239a2377d3f4a9d921d53890c9c562a82
@@@ -593,11 -583,6 +591,11 @@@ public abstract class AbstractApplicati
                  handleServiceException(wrappedRequest, wrappedResponse,
                          application, e);
              } finally {
-                     application.closeInactiveRoots();
 +
 +                if (applicationRunning) {
++                    application.closeInactiveUIs();
 +                }
 +
                  // Notifies transaction end
                  try {
                      if (transactionStarted) {
index 0fc8bc09a054642e3bc915ca9ed14d3e297f2599,6cf9b76b0d1bb1542ee281da3099519f598b2eea..13fd86916609c5b8f698844de0af5ad63d02b5a5
@@@ -360,11 -354,6 +360,11 @@@ public abstract class AbstractApplicati
          } catch (final Throwable e) {
              handleServiceException(request, response, application, e);
          } finally {
-                 application.closeInactiveRoots();
 +
 +            if (applicationRunning) {
++                application.closeInactiveUIs();
 +            }
 +
              // Notifies transaction end
              try {
                  if (transactionStarted) {
index b5ea6a8735a2de0f7505740c7a3b9cb2db5248f3,87eadd5df728470ec4c77e74824ede37c96a299b..a0ecd01b8928c9ac71fec8026b8bb87933643841
@@@ -580,11 -580,8 +581,11 @@@ public abstract class AbstractCommunica
                  return;
              }
  
-             // Keep the root alive
-             root.setLastUidlRequestTime(System.currentTimeMillis());
++            // Keep the UI alive
++            uI.setLastUidlRequestTime(System.currentTimeMillis());
 +
              // Change all variables based on request parameters
-             if (!handleVariables(request, response, callback, application, root)) {
+             if (!handleVariables(request, response, callback, application, uI)) {
  
                  // var inconsistency; the client is probably out-of-sync
                  SystemMessages ci = null;
  
      }
  
-      * the client-side to inform the server that the root sending the heartbeat
-      * is still alive (the browser window is open, the connection is up) even
-      * when there are no UIDL requests for a prolonged period of time. Roots
-      * that do not receive either heartbeat or UIDL requests are eventually
-      * removed from the application and garbage collected.
 +    /**
 +     * Handles a heartbeat request. Heartbeat requests are periodically sent by
-         Root root = null;
++     * the client-side to inform the server that the UI sending the heartbeat is
++     * still alive (the browser window is open, the connection is up) even when
++     * there are no UIDL requests for a prolonged period of time. UIs that do
++     * not receive either heartbeat or UIDL requests are eventually removed from
++     * the application and garbage collected.
 +     * 
 +     * @param request
 +     * @param response
 +     * @param application
 +     * @throws IOException
 +     */
 +    public void handleHeartbeatRequest(WrappedRequest request,
 +            WrappedResponse response, Application application)
 +            throws IOException {
-             int rootId = Integer.parseInt(request
-                     .getParameter(ApplicationConstants.ROOT_ID_PARAMETER));
-             root = application.getRootById(rootId);
++        UI ui = null;
 +        try {
-         if (root != null) {
-             root.setLastHeartbeatTime(System.currentTimeMillis());
++            int uiId = Integer.parseInt(request
++                    .getParameter(UIConstants.UI_ID_PARAMETER));
++            ui = application.getUIById(uiId);
 +        } catch (NumberFormatException nfe) {
 +            // null-check below handles this as well
 +        }
-             response.sendError(HttpServletResponse.SC_NOT_FOUND,
-                     "Root not found");
++        if (ui != null) {
++            ui.setLastHeartbeatTime(System.currentTimeMillis());
 +        } else {
++            response.sendError(HttpServletResponse.SC_NOT_FOUND, "UI not found");
 +        }
 +    }
 +
      public StreamVariable getStreamVariable(String connectorId,
              String variableName) {
          Map<String, StreamVariable> map = pidToNameToStreamVariable
index 30ba82f6642c0b74e35581ba89b4e685870c551b,ad5acad5e90e52e9ab2241bb98d64ba839964a91..4052f5a400c57a7e06549b6959c5ff96d92ed723
@@@ -44,8 -42,6 +44,8 @@@ public abstract class AbstractDeploymen
          checkProductionMode();
          checkXsrfProtection();
          checkResourceCacheTime();
-         checkIdleRootCleanup();
 +        checkHeartbeatInterval();
++        checkIdleUICleanup();
      }
  
      @Override
          return resourceCacheTime;
      }
  
-     public boolean isIdleRootCleanupEnabled() {
 +    /**
 +     * {@inheritDoc}
 +     * 
 +     * The default interval is 300 seconds (5 minutes).
 +     */
 +    @Override
 +    public int getHeartbeatInterval() {
 +        return heartbeatInterval;
 +    }
 +
 +    @Override
++    public boolean isIdleUICleanupEnabled() {
 +        return idleRootCleanupEnabled;
 +    }
 +
      /**
       * Log a warning if Vaadin is not running in production mode.
       */
          }
      }
  
-     private void checkIdleRootCleanup() {
 +    private void checkHeartbeatInterval() {
 +        try {
 +            heartbeatInterval = Integer
 +                    .parseInt(getApplicationOrSystemProperty(
 +                            Constants.SERVLET_PARAMETER_HEARTBEAT_RATE, "300"));
 +        } catch (NumberFormatException e) {
 +            getLogger().warning(
 +                    Constants.WARNING_HEARTBEAT_INTERVAL_NOT_NUMERIC);
 +            heartbeatInterval = 300;
 +        }
 +    }
 +
-                 Constants.SERVLET_PARAMETER_CLOSE_IDLE_ROOTS, "false").equals(
++    private void checkIdleUICleanup() {
 +        idleRootCleanupEnabled = getApplicationOrSystemProperty(
++                Constants.SERVLET_PARAMETER_CLOSE_IDLE_UIS, "false").equals(
 +                "true");
 +    }
 +
      private Logger getLogger() {
          return Logger.getLogger(getClass().getName());
      }
index adf26bf7f7a668c5b187705d96f0914ca4b28658,40386d6eb7d0cd1599d625833084ae55fca3a8a4..96402164883a11a2ffacf8c61cd71af5e295e4a6
@@@ -64,8 -58,6 +64,8 @@@ public interface Constants 
      static final String SERVLET_PARAMETER_PRODUCTION_MODE = "productionMode";
      static final String SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION = "disable-xsrf-protection";
      static final String SERVLET_PARAMETER_RESOURCE_CACHE_TIME = "resourceCacheTime";
-     static final String SERVLET_PARAMETER_CLOSE_IDLE_ROOTS = "closeIdleRoots";
 +    static final String SERVLET_PARAMETER_HEARTBEAT_RATE = "heartbeatRate";
++    static final String SERVLET_PARAMETER_CLOSE_IDLE_UIS = "closeIdleUIs";
  
      // Configurable parameter names
      static final String PARAMETER_VAADIN_RESOURCES = "Resources";
index 0000000000000000000000000000000000000000,aede1af54b92dbd80283083d25da9633ee1af777..17a028bcdf091ee887f01319ccc1f077567a0442
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1241 +1,1361 @@@
 -import com.vaadin.ui.Window.CloseListener;
+ /*
+  * Copyright 2011 Vaadin Ltd.
+  * 
+  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  * use this file except in compliance with the License. You may obtain a copy of
+  * the License at
+  * 
+  * http://www.apache.org/licenses/LICENSE-2.0
+  * 
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  * License for the specific language governing permissions and limitations under
+  * the License.
+  */
+ package com.vaadin.ui;
++import java.lang.reflect.Method;
+ import java.net.MalformedURLException;
+ import java.net.URL;
+ import java.util.ArrayList;
+ import java.util.Collection;
+ import java.util.Collections;
++import java.util.EventListener;
+ import java.util.Iterator;
+ import java.util.LinkedHashSet;
+ import java.util.Map;
+ import com.vaadin.Application;
+ import com.vaadin.annotations.EagerInit;
+ import com.vaadin.event.Action;
+ import com.vaadin.event.Action.Handler;
+ import com.vaadin.event.ActionManager;
+ import com.vaadin.event.MouseEvents.ClickEvent;
+ import com.vaadin.event.MouseEvents.ClickListener;
+ import com.vaadin.shared.EventId;
+ import com.vaadin.shared.MouseEventDetails;
+ import com.vaadin.shared.ui.BorderStyle;
+ import com.vaadin.shared.ui.ui.UIConstants;
+ import com.vaadin.shared.ui.ui.UIServerRpc;
+ import com.vaadin.shared.ui.ui.UIState;
+ import com.vaadin.terminal.Page;
+ import com.vaadin.terminal.Page.BrowserWindowResizeEvent;
+ import com.vaadin.terminal.Page.BrowserWindowResizeListener;
+ import com.vaadin.terminal.PaintException;
+ import com.vaadin.terminal.PaintTarget;
+ import com.vaadin.terminal.Resource;
+ import com.vaadin.terminal.Vaadin6Component;
+ import com.vaadin.terminal.WrappedRequest;
+ import com.vaadin.terminal.WrappedRequest.BrowserDetails;
+ import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
++import com.vaadin.tools.ReflectTools;
+ /**
+  * The topmost component in any component hierarchy. There is one UI for every
+  * Vaadin instance in a browser window. A UI may either represent an entire
+  * browser window (or tab) or some part of a html page where a Vaadin
+  * application is embedded.
+  * <p>
+  * The UI is the server side entry point for various client side features that
+  * are not represented as components added to a layout, e.g notifications, sub
+  * windows, and executing javascript in the browser.
+  * </p>
+  * <p>
+  * When a new UI instance is needed, typically because the user opens a URL in a
+  * browser window which points to {@link AbstractApplicationServlet},
+  * {@link Application#getUIForRequest(WrappedRequest)} is invoked to get a UI.
+  * That method does by default create a UI according to the
+  * {@value Application#UI_PARAMETER} parameter from web.xml.
+  * </p>
+  * <p>
+  * After a UI has been created by the application, it is initialized using
+  * {@link #init(WrappedRequest)}. This method is intended to be overridden by
+  * the developer to add components to the user interface and initialize
+  * non-component functionality. The component hierarchy is initialized by
+  * passing a {@link ComponentContainer} with the main layout of the view to
+  * {@link #setContent(ComponentContainer)}.
+  * </p>
+  * <p>
+  * If a {@link EagerInit} annotation is present on a class extending
+  * <code>UI</code>, the framework will use a faster initialization method which
+  * will not ensure that {@link BrowserDetails} are present in the
+  * {@link WrappedRequest} passed to the init method.
+  * </p>
+  * 
+  * @see #init(WrappedRequest)
+  * @see Application#getUI(WrappedRequest)
+  * 
+  * @since 7.0
+  */
+ public abstract class UI extends AbstractComponentContainer implements
+         Action.Container, Action.Notifier, Vaadin6Component {
+     /**
+      * Helper class to emulate the main window from Vaadin 6 using UIs. This
+      * class should be used in the same way as Window used as a browser level
+      * window in Vaadin 6 with {@link com.vaadin.Application.LegacyApplication}
+      */
+     @Deprecated
+     @EagerInit
+     public static class LegacyWindow extends UI {
+         private String name;
+         /**
+          * Create a new legacy window
+          */
+         public LegacyWindow() {
+             super();
+         }
+         /**
+          * Creates a new legacy window with the given caption
+          * 
+          * @param caption
+          *            the caption of the window
+          */
+         public LegacyWindow(String caption) {
+             super(caption);
+         }
+         /**
+          * Creates a legacy window with the given caption and content layout
+          * 
+          * @param caption
+          * @param content
+          */
+         public LegacyWindow(String caption, ComponentContainer content) {
+             super(caption, content);
+         }
+         @Override
+         protected void init(WrappedRequest request) {
+             // Just empty
+         }
+         /**
+          * Gets the unique name of the window. The name of the window is used to
+          * uniquely identify it.
+          * <p>
+          * The name also determines the URL that can be used for direct access
+          * to a window. All windows can be accessed through
+          * {@code http://host:port/app/win} where {@code http://host:port/app}
+          * is the application URL (as returned by {@link Application#getURL()}
+          * and {@code win} is the window name.
+          * </p>
+          * <p>
+          * Note! Portlets do not support direct window access through URLs.
+          * </p>
+          * 
+          * @return the Name of the Window.
+          */
+         public String getName() {
+             return name;
+         }
+         /**
+          * Sets the unique name of the window. The name of the window is used to
+          * uniquely identify it inside the application.
+          * <p>
+          * The name also determines the URL that can be used for direct access
+          * to a window. All windows can be accessed through
+          * {@code http://host:port/app/win} where {@code http://host:port/app}
+          * is the application URL (as returned by {@link Application#getURL()}
+          * and {@code win} is the window name.
+          * </p>
+          * <p>
+          * This method can only be called before the window is added to an
+          * application.
+          * <p>
+          * Note! Portlets do not support direct window access through URLs.
+          * </p>
+          * 
+          * @param name
+          *            the new name for the window or null if the application
+          *            should automatically assign a name to it
+          * @throws IllegalStateException
+          *             if the window is attached to an application
+          */
+         public void setName(String name) {
+             this.name = name;
+             // The name can not be changed in application
+             if (getApplication() != null) {
+                 throw new IllegalStateException(
+                         "Window name can not be changed while "
+                                 + "the window is in application");
+             }
+         }
+         /**
+          * Gets the full URL of the window. The returned URL is window specific
+          * and can be used to directly refer to the window.
+          * <p>
+          * Note! This method can not be used for portlets.
+          * </p>
+          * 
+          * @return the URL of the window or null if the window is not attached
+          *         to an application
+          */
+         public URL getURL() {
+             Application application = getApplication();
+             if (application == null) {
+                 return null;
+             }
+             try {
+                 return new URL(application.getURL(), getName() + "/");
+             } catch (MalformedURLException e) {
+                 throw new RuntimeException(
+                         "Internal problem getting window URL, please report");
+             }
+         }
+         /**
+          * Opens the given resource in this UI. The contents of this UI is
+          * replaced by the {@code Resource}.
+          * 
+          * @param resource
+          *            the resource to show in this UI
+          * 
+          * @deprecated As of 7.0, use getPage().open instead
+          */
+         @Deprecated
+         public void open(Resource resource) {
+             getPage().open(resource);
+         }
+         /* ********************************************************************* */
+         /**
+          * Opens the given resource in a window with the given name.
+          * <p>
+          * The supplied {@code windowName} is used as the target name in a
+          * window.open call in the client. This means that special values such
+          * as "_blank", "_self", "_top", "_parent" have special meaning. An
+          * empty or <code>null</code> window name is also a special case.
+          * </p>
+          * <p>
+          * "", null and "_self" as {@code windowName} all causes the resource to
+          * be opened in the current window, replacing any old contents. For
+          * downloadable content you should avoid "_self" as "_self" causes the
+          * client to skip rendering of any other changes as it considers them
+          * irrelevant (the page will be replaced by the resource). This can
+          * speed up the opening of a resource, but it might also put the client
+          * side into an inconsistent state if the window content is not
+          * completely replaced e.g., if the resource is downloaded instead of
+          * displayed in the browser.
+          * </p>
+          * <p>
+          * "_blank" as {@code windowName} causes the resource to always be
+          * opened in a new window or tab (depends on the browser and browser
+          * settings).
+          * </p>
+          * <p>
+          * "_top" and "_parent" as {@code windowName} works as specified by the
+          * HTML standard.
+          * </p>
+          * <p>
+          * Any other {@code windowName} will open the resource in a window with
+          * that name, either by opening a new window/tab in the browser or by
+          * replacing the contents of an existing window with that name.
+          * </p>
+          * 
+          * @param resource
+          *            the resource.
+          * @param windowName
+          *            the name of the window.
+          * @deprecated As of 7.0, use getPage().open instead
+          */
+         @Deprecated
+         public void open(Resource resource, String windowName) {
+             getPage().open(resource, windowName);
+         }
+         /**
+          * Opens the given resource in a window with the given size, border and
+          * name. For more information on the meaning of {@code windowName}, see
+          * {@link #open(Resource, String)}.
+          * 
+          * @param resource
+          *            the resource.
+          * @param windowName
+          *            the name of the window.
+          * @param width
+          *            the width of the window in pixels
+          * @param height
+          *            the height of the window in pixels
+          * @param border
+          *            the border style of the window.
+          * @deprecated As of 7.0, use getPage().open instead
+          */
+         @Deprecated
+         public void open(Resource resource, String windowName, int width,
+                 int height, BorderStyle border) {
+             getPage().open(resource, windowName, width, height, border);
+         }
+         /**
+          * Adds a new {@link BrowserWindowResizeListener} to this UI. The
+          * listener will be notified whenever the browser window within which
+          * this UI resides is resized.
+          * 
+          * @param resizeListener
+          *            the listener to add
+          * 
+          * @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent)
+          * @see #setResizeLazy(boolean)
+          * 
+          * @deprecated As of 7.0, use the similarly named api in Page instead
+          */
+         @Deprecated
+         public void addListener(BrowserWindowResizeListener resizeListener) {
+             getPage().addListener(resizeListener);
+         }
+         /**
+          * Removes a {@link BrowserWindowResizeListener} from this UI. The
+          * listener will no longer be notified when the browser window is
+          * resized.
+          * 
+          * @param resizeListener
+          *            the listener to remove
+          * @deprecated As of 7.0, use the similarly named api in Page instead
+          */
+         @Deprecated
+         public void removeListener(BrowserWindowResizeListener resizeListener) {
+             getPage().removeListener(resizeListener);
+         }
+         /**
+          * Gets the last known height of the browser window in which this UI
+          * resides.
+          * 
+          * @return the browser window height in pixels
+          * @deprecated As of 7.0, use the similarly named api in Page instead
+          */
+         @Deprecated
+         public int getBrowserWindowHeight() {
+             return getPage().getBrowserWindowHeight();
+         }
+         /**
+          * Gets the last known width of the browser window in which this UI
+          * resides.
+          * 
+          * @return the browser window width in pixels
+          * 
+          * @deprecated As of 7.0, use the similarly named api in Page instead
+          */
+         @Deprecated
+         public int getBrowserWindowWidth() {
+             return getPage().getBrowserWindowWidth();
+         }
+         /**
+          * Executes JavaScript in this window.
+          * 
+          * <p>
+          * This method allows one to inject javascript from the server to
+          * client. A client implementation is not required to implement this
+          * functionality, but currently all web-based clients do implement this.
+          * </p>
+          * 
+          * <p>
+          * Executing javascript this way often leads to cross-browser
+          * compatibility issues and regressions that are hard to resolve. Use of
+          * this method should be avoided and instead it is recommended to create
+          * new widgets with GWT. For more info on creating own, reusable
+          * client-side widgets in Java, read the corresponding chapter in Book
+          * of Vaadin.
+          * </p>
+          * 
+          * @param script
+          *            JavaScript snippet that will be executed.
+          * 
+          * @deprecated as of 7.0, use JavaScript.getCurrent().execute(String)
+          *             instead
+          */
+         @Deprecated
+         public void executeJavaScript(String script) {
+             getPage().getJavaScript().execute(script);
+         }
+         @Override
+         public void setCaption(String caption) {
+             // Override to provide backwards compatibility
+             getState().setCaption(caption);
+             getPage().setTitle(caption);
+         }
+     }
++    /**
++     * Event fired when a UI is removed from the application.
++     */
++    public static class CloseEvent extends Event {
++
++        private static final String CLOSE_EVENT_IDENTIFIER = "uiClose";
++
++        public CloseEvent(UI source) {
++            super(source);
++        }
++
++        public UI getUI() {
++            return (UI) getSource();
++        }
++    }
++
++    /**
++     * Interface for listening {@link UI.CloseEvent UI close events}.
++     * 
++     */
++    public interface CloseListener extends EventListener {
++
++        public static final Method closeMethod = ReflectTools.findMethod(
++                CloseListener.class, "click", CloseEvent.class);
++
++        /**
++         * Called when a CloseListener is notified of a CloseEvent.
++         * {@link UI#getCurrent()} returns <code>event.getUI()</code> within
++         * this method.
++         * 
++         * @param event
++         *            The close event that was fired.
++         */
++        public void close(CloseEvent event);
++    }
++
+     /**
+      * The application to which this UI belongs
+      */
+     private Application application;
+     /**
+      * List of windows in this UI.
+      */
+     private final LinkedHashSet<Window> windows = new LinkedHashSet<Window>();
+     /**
+      * The component that should be scrolled into view after the next repaint.
+      * Null if nothing should be scrolled into view.
+      */
+     private Component scrollIntoView;
+     /**
+      * The id of this UI, used to find the server side instance of the UI form
+      * which a request originates. A negative value indicates that the UI id has
+      * not yet been assigned by the Application.
+      * 
+      * @see Application#nextUIId
+      */
+     private int uiId = -1;
+     /**
+      * Keeps track of the Actions added to this component, and manages the
+      * painting and handling as well.
+      */
+     protected ActionManager actionManager;
+     /**
+      * Thread local for keeping track of the current UI.
+      */
+     private static final ThreadLocal<UI> currentUI = new ThreadLocal<UI>();
+     /** Identifies the click event */
+     private ConnectorTracker connectorTracker = new ConnectorTracker(this);
+     private Page page = new Page(this);
+     private UIServerRpc rpc = new UIServerRpc() {
+         @Override
+         public void click(MouseEventDetails mouseDetails) {
+             fireEvent(new ClickEvent(UI.this, mouseDetails));
+         }
+         @Override
+         public void resize(int viewWidth, int viewHeight, int windowWidth,
+                 int windowHeight) {
+             // TODO We're not doing anything with the view dimensions
+             getPage().setBrowserWindowSize(windowWidth, windowHeight);
+         }
+     };
++    /**
++     * Timestamp keeping track of the last heartbeat of this UI. Updated to the
++     * current time whenever the application receives a heartbeat or UIDL
++     * request from the client for this UI.
++     */
++    private long lastHeartbeat = System.currentTimeMillis();
++
++    private long lastUidlRequest = System.currentTimeMillis();
++
+     /**
+      * Creates a new empty UI without a caption. This UI will have a
+      * {@link VerticalLayout} with margins enabled as its content.
+      */
+     public UI() {
+         this((ComponentContainer) null);
+     }
+     /**
+      * Creates a new UI with the given component container as its content.
+      * 
+      * @param content
+      *            the content container to use as this UIs content.
+      * 
+      * @see #setContent(ComponentContainer)
+      */
+     public UI(ComponentContainer content) {
+         registerRpc(rpc);
+         setSizeFull();
+         setContent(content);
+     }
+     /**
+      * Creates a new empty UI with the given caption. This UI will have a
+      * {@link VerticalLayout} with margins enabled as its content.
+      * 
+      * @param caption
+      *            the caption of the UI, used as the page title if there's
+      *            nothing but the application on the web page
+      * 
+      * @see #setCaption(String)
+      */
+     public UI(String caption) {
+         this((ComponentContainer) null);
+         setCaption(caption);
+     }
+     /**
+      * Creates a new UI with the given caption and content.
+      * 
+      * @param caption
+      *            the caption of the UI, used as the page title if there's
+      *            nothing but the application on the web page
+      * @param content
+      *            the content container to use as this UIs content.
+      * 
+      * @see #setContent(ComponentContainer)
+      * @see #setCaption(String)
+      */
+     public UI(String caption, ComponentContainer content) {
+         this(content);
+         setCaption(caption);
+     }
+     @Override
+     protected UIState getState() {
+         return (UIState) super.getState();
+     }
+     @Override
+     public Class<? extends UIState> getStateType() {
+         // This is a workaround for a problem with creating the correct state
+         // object during build
+         return UIState.class;
+     }
+     /**
+      * Overridden to return a value instead of referring to the parent.
+      * 
+      * @return this UI
+      * 
+      * @see com.vaadin.ui.AbstractComponent#getUI()
+      */
+     @Override
+     public UI getUI() {
+         return this;
+     }
+     @Override
+     public void replaceComponent(Component oldComponent, Component newComponent) {
+         throw new UnsupportedOperationException();
+     }
+     @Override
+     public Application getApplication() {
+         return application;
+     }
+     @Override
+     public void paintContent(PaintTarget target) throws PaintException {
+         page.paintContent(target);
+         if (scrollIntoView != null) {
+             target.addAttribute("scrollTo", scrollIntoView);
+             scrollIntoView = null;
+         }
+         if (pendingFocus != null) {
+             // ensure focused component is still attached to this main window
+             if (pendingFocus.getUI() == this
+                     || (pendingFocus.getUI() != null && pendingFocus.getUI()
+                             .getParent() == this)) {
+                 target.addAttribute("focused", pendingFocus);
+             }
+             pendingFocus = null;
+         }
+         if (actionManager != null) {
+             actionManager.paintActions(null, target);
+         }
+         if (isResizeLazy()) {
+             target.addAttribute(UIConstants.RESIZE_LAZY, true);
+         }
+     }
+     /**
+      * Fire a click event to all click listeners.
+      * 
+      * @param object
+      *            The raw "value" of the variable change from the client side.
+      */
+     private void fireClick(Map<String, Object> parameters) {
+         MouseEventDetails mouseDetails = MouseEventDetails
+                 .deSerialize((String) parameters.get("mouseDetails"));
+         fireEvent(new ClickEvent(this, mouseDetails));
+     }
++    /**
++     * For internal use only.
++     */
++    public void fireCloseEvent() {
++        UI current = UI.getCurrent();
++        UI.setCurrent(this);
++        fireEvent(new CloseEvent(this));
++        UI.setCurrent(current);
++    }
++
+     @Override
+     @SuppressWarnings("unchecked")
+     public void changeVariables(Object source, Map<String, Object> variables) {
+         if (variables.containsKey(EventId.CLICK_EVENT_IDENTIFIER)) {
+             fireClick((Map<String, Object>) variables
+                     .get(EventId.CLICK_EVENT_IDENTIFIER));
+         }
+         // Actions
+         if (actionManager != null) {
+             actionManager.handleActions(variables, this);
+         }
+         if (variables.containsKey(UIConstants.FRAGMENT_VARIABLE)) {
+             String fragment = (String) variables
+                     .get(UIConstants.FRAGMENT_VARIABLE);
+             getPage().setFragment(fragment, true);
+         }
+     }
+     /*
+      * (non-Javadoc)
+      * 
+      * @see com.vaadin.ui.ComponentContainer#getComponentIterator()
+      */
+     @Override
+     public Iterator<Component> getComponentIterator() {
+         // TODO could directly create some kind of combined iterator instead of
+         // creating a new ArrayList
+         ArrayList<Component> components = new ArrayList<Component>();
+         if (getContent() != null) {
+             components.add(getContent());
+         }
+         components.addAll(windows);
+         return components.iterator();
+     }
+     /*
+      * (non-Javadoc)
+      * 
+      * @see com.vaadin.ui.ComponentContainer#getComponentCount()
+      */
+     @Override
+     public int getComponentCount() {
+         return windows.size() + (getContent() == null ? 0 : 1);
+     }
+     /**
+      * Sets the application to which this UI is assigned. It is not legal to
+      * change the application once it has been set nor to set a
+      * <code>null</code> application.
+      * <p>
+      * This method is mainly intended for internal use by the framework.
+      * </p>
+      * 
+      * @param application
+      *            the application to set
+      * 
+      * @throws IllegalStateException
+      *             if the application has already been set
+      * 
+      * @see #getApplication()
+      */
+     public void setApplication(Application application) {
+         if ((application == null) == (this.application == null)) {
+             throw new IllegalStateException("Application has already been set");
+         } else {
+             this.application = application;
+         }
+         if (application != null) {
+             attach();
+         } else {
+             detach();
+         }
+     }
+     /**
+      * Sets the id of this UI within its application. The UI id is used to route
+      * requests to the right UI.
+      * <p>
+      * This method is mainly intended for internal use by the framework.
+      * </p>
+      * 
+      * @param uiId
+      *            the id of this UI
+      * 
+      * @throws IllegalStateException
+      *             if the UI id has already been set
+      * 
+      * @see #getUIId()
+      */
+     public void setUIId(int uiId) {
+         if (this.uiId != -1) {
+             throw new IllegalStateException("UI id has already been defined");
+         }
+         this.uiId = uiId;
+     }
+     /**
+      * Gets the id of the UI, used to identify this UI within its application
+      * when processing requests. The UI id should be present in every request to
+      * the server that originates from this UI.
+      * {@link Application#getUIForRequest(WrappedRequest)} uses this id to find
+      * the route to which the request belongs.
+      * 
+      * @return
+      */
+     public int getUIId() {
+         return uiId;
+     }
+     /**
+      * Adds a window as a subwindow inside this UI. To open a new browser window
+      * or tab, you should instead use {@link open(Resource)} with an url
+      * pointing to this application and ensure
+      * {@link Application#getUI(WrappedRequest)} returns an appropriate UI for
+      * the request.
+      * 
+      * @param window
+      * @throws IllegalArgumentException
+      *             if the window is already added to an application
+      * @throws NullPointerException
+      *             if the given <code>Window</code> is <code>null</code>.
+      */
+     public void addWindow(Window window) throws IllegalArgumentException,
+             NullPointerException {
+         if (window == null) {
+             throw new NullPointerException("Argument must not be null");
+         }
+         if (window.getApplication() != null) {
+             throw new IllegalArgumentException(
+                     "Window is already attached to an application.");
+         }
+         attachWindow(window);
+     }
+     /**
+      * Helper method to attach a window.
+      * 
+      * @param w
+      *            the window to add
+      */
+     private void attachWindow(Window w) {
+         windows.add(w);
+         w.setParent(this);
+         markAsDirty();
+     }
+     /**
+      * Remove the given subwindow from this UI.
+      * 
+      * Since Vaadin 6.5, {@link CloseListener}s are called also when explicitly
+      * removing a window by calling this method.
+      * 
+      * Since Vaadin 6.5, returns a boolean indicating if the window was removed
+      * or not.
+      * 
+      * @param window
+      *            Window to be removed.
+      * @return true if the subwindow was removed, false otherwise
+      */
+     public boolean removeWindow(Window window) {
+         if (!windows.remove(window)) {
+             // Window window is not a subwindow of this UI.
+             return false;
+         }
+         window.setParent(null);
+         window.fireClose();
+         markAsDirty();
+         return true;
+     }
+     /**
+      * Gets all the windows added to this UI.
+      * 
+      * @return an unmodifiable collection of windows
+      */
+     public Collection<Window> getWindows() {
+         return Collections.unmodifiableCollection(windows);
+     }
+     @Override
+     public void focus() {
+         super.focus();
+     }
+     /**
+      * Component that should be focused after the next repaint. Null if no focus
+      * change should take place.
+      */
+     private Focusable pendingFocus;
+     private boolean resizeLazy = false;
+     /**
+      * This method is used by Component.Focusable objects to request focus to
+      * themselves. Focus renders must be handled at window level (instead of
+      * Component.Focusable) due we want the last focused component to be focused
+      * in client too. Not the one that is rendered last (the case we'd get if
+      * implemented in Focusable only).
+      * 
+      * To focus component from Vaadin application, use Focusable.focus(). See
+      * {@link Focusable}.
+      * 
+      * @param focusable
+      *            to be focused on next paint
+      */
+     public void setFocusedComponent(Focusable focusable) {
+         pendingFocus = focusable;
+         markAsDirty();
+     }
+     /**
+      * Scrolls any component between the component and UI to a suitable position
+      * so the component is visible to the user. The given component must belong
+      * to this UI.
+      * 
+      * @param component
+      *            the component to be scrolled into view
+      * @throws IllegalArgumentException
+      *             if {@code component} does not belong to this UI
+      */
+     public void scrollIntoView(Component component)
+             throws IllegalArgumentException {
+         if (component.getUI() != this) {
+             throw new IllegalArgumentException(
+                     "The component where to scroll must belong to this UI.");
+         }
+         scrollIntoView = component;
+         markAsDirty();
+     }
+     /**
+      * Gets the content of this UI. The content is a component container that
+      * serves as the outermost item of the visual contents of this UI.
+      * 
+      * @return a component container to use as content
+      * 
+      * @see #setContent(ComponentContainer)
+      * @see #createDefaultLayout()
+      */
+     public ComponentContainer getContent() {
+         return (ComponentContainer) getState().getContent();
+     }
+     /**
+      * Helper method to create the default content layout that is used if no
+      * content has not been explicitly defined.
+      * 
+      * @return a newly created layout
+      */
+     private static VerticalLayout createDefaultLayout() {
+         VerticalLayout layout = new VerticalLayout();
+         layout.setMargin(true);
+         return layout;
+     }
+     /**
+      * Sets the content of this UI. The content is a component container that
+      * serves as the outermost item of the visual contents of this UI. If no
+      * content has been set, a {@link VerticalLayout} with margins enabled will
+      * be used by default - see {@link #createDefaultLayout()}. The content can
+      * also be set in a constructor.
+      * 
+      * @return a component container to use as content
+      * 
+      * @see #UI(ComponentContainer)
+      * @see #createDefaultLayout()
+      */
+     public void setContent(ComponentContainer content) {
+         if (content == null) {
+             content = createDefaultLayout();
+         }
+         if (getState().getContent() != null) {
+             super.removeComponent((Component) getState().getContent());
+         }
+         getState().setContent(content);
+         if (content != null) {
+             super.addComponent(content);
+         }
+     }
+     /**
+      * Adds a component to this UI. The component is not added directly to the
+      * UI, but instead to the content container ({@link #getContent()}).
+      * 
+      * @param component
+      *            the component to add to this UI
+      * 
+      * @see #getContent()
+      */
+     @Override
+     public void addComponent(Component component) {
+         getContent().addComponent(component);
+     }
+     /**
+      * This implementation removes the component from the content container (
+      * {@link #getContent()}) instead of from the actual UI.
+      */
+     @Override
+     public void removeComponent(Component component) {
+         getContent().removeComponent(component);
+     }
+     /**
+      * This implementation removes the components from the content container (
+      * {@link #getContent()}) instead of from the actual UI.
+      */
+     @Override
+     public void removeAllComponents() {
+         getContent().removeAllComponents();
+     }
+     /**
+      * Internal initialization method, should not be overridden. This method is
+      * not declared as final because that would break compatibility with e.g.
+      * CDI.
+      * 
+      * @param request
+      *            the initialization request
+      */
+     public void doInit(WrappedRequest request) {
+         getPage().init(request);
+         // Call the init overridden by the application developer
+         init(request);
+     }
+     /**
+      * Initializes this UI. This method is intended to be overridden by
+      * subclasses to build the view and configure non-component functionality.
+      * Performing the initialization in a constructor is not suggested as the
+      * state of the UI is not properly set up when the constructor is invoked.
+      * <p>
+      * The {@link WrappedRequest} can be used to get information about the
+      * request that caused this UI to be created. By default, the
+      * {@link BrowserDetails} will be available in the request. If the browser
+      * details are not required, loading the application in the browser can take
+      * some shortcuts giving a faster initial rendering. This can be indicated
+      * by adding the {@link EagerInit} annotation to the UI class.
+      * </p>
+      * 
+      * @param request
+      *            the wrapped request that caused this UI to be created
+      */
+     protected abstract void init(WrappedRequest request);
+     /**
+      * Sets the thread local for the current UI. This method is used by the
+      * framework to set the current application whenever a new request is
+      * processed and it is cleared when the request has been processed.
+      * <p>
+      * The application developer can also use this method to define the current
+      * UI outside the normal request handling, e.g. when initiating custom
+      * background threads.
+      * </p>
+      * 
+      * @param uI
+      *            the UI to register as the current UI
+      * 
+      * @see #getCurrent()
+      * @see ThreadLocal
+      */
+     public static void setCurrent(UI ui) {
+         currentUI.set(ui);
+     }
+     /**
+      * Gets the currently used UI. The current UI is automatically defined when
+      * processing requests to the server. In other cases, (e.g. from background
+      * threads), the current UI is not automatically defined.
+      * 
+      * @return the current UI instance if available, otherwise <code>null</code>
+      * 
+      * @see #setCurrent(UI)
+      */
+     public static UI getCurrent() {
+         return currentUI.get();
+     }
+     public void setScrollTop(int scrollTop) {
+         throw new RuntimeException("Not yet implemented");
+     }
+     @Override
+     protected ActionManager getActionManager() {
+         if (actionManager == null) {
+             actionManager = new ActionManager(this);
+         }
+         return actionManager;
+     }
+     @Override
+     public <T extends Action & com.vaadin.event.Action.Listener> void addAction(
+             T action) {
+         getActionManager().addAction(action);
+     }
+     @Override
+     public <T extends Action & com.vaadin.event.Action.Listener> void removeAction(
+             T action) {
+         if (actionManager != null) {
+             actionManager.removeAction(action);
+         }
+     }
+     @Override
+     public void addActionHandler(Handler actionHandler) {
+         getActionManager().addActionHandler(actionHandler);
+     }
+     @Override
+     public void removeActionHandler(Handler actionHandler) {
+         if (actionManager != null) {
+             actionManager.removeActionHandler(actionHandler);
+         }
+     }
+     /**
+      * Should resize operations be lazy, i.e. should there be a delay before
+      * layout sizes are recalculated. Speeds up resize operations in slow UIs
+      * with the penalty of slightly decreased usability.
+      * <p>
+      * Default value: <code>false</code>
+      * 
+      * @param resizeLazy
+      *            true to use a delay before recalculating sizes, false to
+      *            calculate immediately.
+      */
+     public void setResizeLazy(boolean resizeLazy) {
+         this.resizeLazy = resizeLazy;
+         markAsDirty();
+     }
+     /**
+      * Checks whether lazy resize is enabled.
+      * 
+      * @return <code>true</code> if lazy resize is enabled, <code>false</code>
+      *         if lazy resize is not enabled
+      */
+     public boolean isResizeLazy() {
+         return resizeLazy;
+     }
+     /**
+      * Add a click listener to the UI. The listener is called whenever the user
+      * clicks inside the UI. Also when the click targets a component inside the
+      * UI, provided the targeted component does not prevent the click event from
+      * propagating.
+      * 
+      * Use {@link #removeListener(ClickListener)} to remove the listener.
+      * 
+      * @param listener
+      *            The listener to add
+      */
+     public void addListener(ClickListener listener) {
+         addListener(EventId.CLICK_EVENT_IDENTIFIER, ClickEvent.class, listener,
+                 ClickListener.clickMethod);
+     }
+     /**
+      * Remove a click listener from the UI. The listener should earlier have
+      * been added using {@link #addListener(ClickListener)}.
+      * 
+      * @param listener
+      *            The listener to remove
+      */
+     public void removeListener(ClickListener listener) {
+         removeListener(EventId.CLICK_EVENT_IDENTIFIER, ClickEvent.class,
+                 listener);
+     }
++    /**
++     * Adds a close listener to the UI. The listener is called when the UI is
++     * removed from the application.
++     * 
++     * @param listener
++     *            The listener to add.
++     */
++    public void addListener(CloseListener listener) {
++        addListener(CloseEvent.CLOSE_EVENT_IDENTIFIER, CloseEvent.class,
++                listener, CloseListener.closeMethod);
++    }
++
++    /**
++     * Removes a close listener from the UI if it has previously been added with
++     * {@link #addListener(ClickListener)}. Otherwise, has no effect.
++     * 
++     * @param listener
++     *            The listener to remove.
++     */
++    public void removeListener(CloseListener listener) {
++        removeListener(CloseEvent.CLOSE_EVENT_IDENTIFIER, CloseEvent.class,
++                listener);
++    }
++
+     @Override
+     public boolean isConnectorEnabled() {
+         // TODO How can a UI be invisible? What does it mean?
+         return isVisible() && isEnabled();
+     }
+     public ConnectorTracker getConnectorTracker() {
+         return connectorTracker;
+     }
+     public Page getPage() {
+         return page;
+     }
+     /**
+      * Setting the caption of a UI is not supported. To set the title of the
+      * HTML page, use Page.setTitle
+      * 
+      * @deprecated as of 7.0.0, use {@link Page#setTitle(String)}
+      */
+     @Override
+     @Deprecated
+     public void setCaption(String caption) {
+         throw new IllegalStateException(
+                 "You can not set the title of a UI. To set the title of the HTML page, use Page.setTitle");
+     }
+     /**
+      * Shows a notification message on the middle of the UI. The message
+      * automatically disappears ("humanized message").
+      * 
+      * Care should be taken to to avoid XSS vulnerabilities as the caption is
+      * rendered as html.
+      * 
+      * @see #showNotification(Notification)
+      * @see Notification
+      * 
+      * @param caption
+      *            The message
+      * 
+      * @deprecated As of 7.0, use Notification.show instead but be aware that
+      *             Notification.show does not allow HTML.
+      */
+     @Deprecated
+     public void showNotification(String caption) {
+         Notification notification = new Notification(caption);
+         notification.setHtmlContentAllowed(true);// Backwards compatibility
+         getPage().showNotification(notification);
+     }
+     /**
+      * Shows a notification message the UI. The position and behavior of the
+      * message depends on the type, which is one of the basic types defined in
+      * {@link Notification}, for instance Notification.TYPE_WARNING_MESSAGE.
+      * 
+      * Care should be taken to to avoid XSS vulnerabilities as the caption is
+      * rendered as html.
+      * 
+      * @see #showNotification(Notification)
+      * @see Notification
+      * 
+      * @param caption
+      *            The message
+      * @param type
+      *            The message type
+      * 
+      * @deprecated As of 7.0, use Notification.show instead but be aware that
+      *             Notification.show does not allow HTML.
+      */
+     @Deprecated
+     public void showNotification(String caption, Notification.Type type) {
+         Notification notification = new Notification(caption, type);
+         notification.setHtmlContentAllowed(true);// Backwards compatibility
+         getPage().showNotification(notification);
+     }
+     /**
+      * Shows a notification consisting of a bigger caption and a smaller
+      * description on the middle of the UI. The message automatically disappears
+      * ("humanized message").
+      * 
+      * Care should be taken to to avoid XSS vulnerabilities as the caption and
+      * description are rendered as html.
+      * 
+      * @see #showNotification(Notification)
+      * @see Notification
+      * 
+      * @param caption
+      *            The caption of the message
+      * @param description
+      *            The message description
+      * 
+      * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
+      *             be aware that HTML by default not allowed.
+      */
+     @Deprecated
+     public void showNotification(String caption, String description) {
+         Notification notification = new Notification(caption, description);
+         notification.setHtmlContentAllowed(true);// Backwards compatibility
+         getPage().showNotification(notification);
+     }
+     /**
+      * Shows a notification consisting of a bigger caption and a smaller
+      * description. The position and behavior of the message depends on the
+      * type, which is one of the basic types defined in {@link Notification} ,
+      * for instance Notification.TYPE_WARNING_MESSAGE.
+      * 
+      * Care should be taken to to avoid XSS vulnerabilities as the caption and
+      * description are rendered as html.
+      * 
+      * @see #showNotification(Notification)
+      * @see Notification
+      * 
+      * @param caption
+      *            The caption of the message
+      * @param description
+      *            The message description
+      * @param type
+      *            The message type
+      * 
+      * @deprecated As of 7.0, use new Notification(...).show(Page) instead but
+      *             be aware that HTML by default not allowed.
+      */
+     @Deprecated
+     public void showNotification(String caption, String description,
+             Notification.Type type) {
+         Notification notification = new Notification(caption, description, type);
+         notification.setHtmlContentAllowed(true);// Backwards compatibility
+         getPage().showNotification(notification);
+     }
+     /**
+      * Shows a notification consisting of a bigger caption and a smaller
+      * description. The position and behavior of the message depends on the
+      * type, which is one of the basic types defined in {@link Notification} ,
+      * for instance Notification.TYPE_WARNING_MESSAGE.
+      * 
+      * Care should be taken to avoid XSS vulnerabilities if html content is
+      * allowed.
+      * 
+      * @see #showNotification(Notification)
+      * @see Notification
+      * 
+      * @param caption
+      *            The message caption
+      * @param description
+      *            The message description
+      * @param type
+      *            The type of message
+      * @param htmlContentAllowed
+      *            Whether html in the caption and description should be
+      *            displayed as html or as plain text
+      * 
+      * @deprecated As of 7.0, use new Notification(...).show(Page).
+      */
+     @Deprecated
+     public void showNotification(String caption, String description,
+             Notification.Type type, boolean htmlContentAllowed) {
+         getPage()
+                 .showNotification(
+                         new Notification(caption, description, type,
+                                 htmlContentAllowed));
+     }
+     /**
+      * Shows a notification message.
+      * 
+      * @see Notification
+      * @see #showNotification(String)
+      * @see #showNotification(String, int)
+      * @see #showNotification(String, String)
+      * @see #showNotification(String, String, int)
+      * 
+      * @param notification
+      *            The notification message to show
+      * 
+      * @deprecated As of 7.0, use Notification.show instead
+      */
+     @Deprecated
+     public void showNotification(Notification notification) {
+         getPage().showNotification(notification);
+     }
++    /**
++     * Returns the timestamp (milliseconds since the epoch) of the last received
++     * heartbeat for this UI.
++     * 
++     * @see #heartbeat()
++     * @see Application#closeInactiveUIs()
++     * 
++     * @return The time the last heartbeat request occurred.
++     */
++    public long getLastHeartbeatTime() {
++        return lastHeartbeat;
++    }
++
++    /**
++     * Returns the timestamp (milliseconds since the epoch) of the last received
++     * UIDL request for this UI.
++     * 
++     * @return
++     */
++    public long getLastUidlRequestTime() {
++        return lastUidlRequest;
++    }
++
++    /**
++     * Sets the last heartbeat request timestamp for this UI. Called by the
++     * framework whenever the application receives a valid heartbeat request for
++     * this UI.
++     */
++    public void setLastHeartbeatTime(long lastHeartbeat) {
++        this.lastHeartbeat = lastHeartbeat;
++    }
++
++    /**
++     * Sets the last UIDL request timestamp for this UI. Called by the framework
++     * whenever the application receives a valid UIDL request for this UI.
++     */
++    public void setLastUidlRequestTime(long lastUidlRequest) {
++        this.lastUidlRequest = lastUidlRequest;
++    }
+ }