diff options
Diffstat (limited to 'src')
14 files changed, 438 insertions, 169 deletions
diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 2be35f053f..be6d578112 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -256,10 +256,10 @@ public class ApplicationConnection { /*-{ var ap = this; var client = {}; - client.isActive = function() { + client.isActive = $entry(function() { return ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::hasActiveRequest()() || ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::isExecutingDeferredCommands()(); - } + }); var vi = ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::getVersionInfo()(); if (vi) { client.getVersionInfo = function() { @@ -267,12 +267,21 @@ public class ApplicationConnection { } } - client.getElementByPath = function(id) { + client.getProfilingData = $entry(function() { + var pd = [ + ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::lastProcessingTime, + ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::totalProcessingTime + ]; + pd = pd.concat(ap.@com.vaadin.terminal.gwt.client.ApplicationConnection::testBenchServerStatus); + return pd; + }); + + client.getElementByPath = $entry(function(id) { return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); - } - client.getPathForElement = function(element) { + }); + client.getPathForElement = $entry(function(element) { return componentLocator.@com.vaadin.terminal.gwt.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); - } + }); $wnd.vaadin.clients[TTAppId] = client; }-*/; @@ -314,22 +323,22 @@ public class ApplicationConnection { if ($wnd.vaadin.forceSync) { oldSync = $wnd.vaadin.forceSync; } - $wnd.vaadin.forceSync = function() { + $wnd.vaadin.forceSync = $entry(function() { if (oldSync) { oldSync(); } app.@com.vaadin.terminal.gwt.client.ApplicationConnection::sendPendingVariableChanges()(); - } + }); var oldForceLayout; if ($wnd.vaadin.forceLayout) { oldForceLayout = $wnd.vaadin.forceLayout; } - $wnd.vaadin.forceLayout = function() { + $wnd.vaadin.forceLayout = $entry(function() { if (oldForceLayout) { oldForceLayout(); } app.@com.vaadin.terminal.gwt.client.ApplicationConnection::forceLayout()(); - } + }); }-*/; /** @@ -536,21 +545,22 @@ public class ApplicationConnection { return; case 503: - // We'll assume msec instead of the usual seconds - int delay = Integer.parseInt(response - .getHeader("Retry-After")); - VConsole.log("503, retrying in " + delay + "msec"); - (new Timer() { - @Override - public void run() { - // TODO why? Here used to be "activeRequests--;" - // but can't see why exactly - hasActiveRequest = false; - doUidlRequest(uri, payload, synchronous); - } - }).schedule(delay); - return; - + /* + * We'll assume msec instead of the usual seconds. If + * there's no Retry-After header, handle the error like + * a 500, as per RFC 2616 section 10.5.4. + */ + String delay = response.getHeader("Retry-After"); + if (delay != null) { + VConsole.log("503, retrying in " + delay + "msec"); + (new Timer() { + @Override + public void run() { + doUidlRequest(uri, payload, synchronous); + } + }).schedule(Integer.parseInt(delay)); + return; + } } if ((statusCode / 100) == 4) { @@ -561,6 +571,13 @@ public class ApplicationConnection { + statusCode, statusCode); endRequest(); return; + } else if ((statusCode / 100) == 5) { + // Something's wrong on the server, there's nothing the + // client can do except maybe try again. + showCommunicationError("Server error. Error code: " + + statusCode, statusCode); + endRequest(); + return; } String contentType = response.getHeader("Content-Type"); @@ -663,6 +680,26 @@ public class ApplicationConnection { } int cssWaits = 0; + + /** + * Holds the time spent rendering the last request + */ + protected int lastProcessingTime; + + /** + * Holds the total time spent rendering requests during the lifetime of the + * session. + */ + protected int totalProcessingTime; + + /** + * Holds the timing information from the server-side. How much time was + * spent servicing the last request and how much time has been spent + * servicing the session so far. These values are always one request behind, + * since they cannot be measured before the request is finished. + */ + private ValueMap testBenchServerStatus; + static final int MAX_CSS_WAITS = 100; protected void handleWhenCSSLoaded(final String jsonText, @@ -999,6 +1036,12 @@ public class ApplicationConnection { handleUIDLDuration.logDuration( " * Handling type mappings from server completed", 10); + /* + * Hook for TestBench to get details about server status + */ + if (json.containsKey("tbss")) { + testBenchServerStatus = json.getValueMap("tbss"); + } Command c = new Command() { public void execute() { @@ -1183,10 +1226,12 @@ public class ApplicationConnection { // TODO build profiling for widget impl loading time - final long prosessingTime = (new Date().getTime()) - - start.getTime(); + lastProcessingTime = (int) ((new Date().getTime()) - start + .getTime()); + totalProcessingTime += lastProcessingTime; + VConsole.log(" Processing time was " - + String.valueOf(prosessingTime) + "ms for " + + String.valueOf(lastProcessingTime) + "ms for " + jsonText.length() + " characters of JSON"); VConsole.log("Referenced paintables: " + connectorMap.size()); diff --git a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java index c2fa4f46bf..5eaf78f255 100644 --- a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java +++ b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java @@ -180,6 +180,9 @@ public class VDebugConsole extends VOverlay implements Console { private static final String help = "Drag title=move, shift-drag=resize, doubleclick title=min/max." + "Use debug=quiet to log only to browser console."; + private static final int DEFAULT_WIDTH = 650; + private static final int DEFAULT_HEIGHT = 400; + public VDebugConsole() { super(false, false); getElement().getStyle().setOverflow(Overflow.HIDDEN); @@ -301,10 +304,20 @@ public class VDebugConsole extends VOverlay implements Console { height = Integer.parseInt(split[3]); autoScrollValue = Boolean.valueOf(split[4]); } else { - width = 400; - height = 150; - top = Window.getClientHeight() - 160; - left = Window.getClientWidth() - 410; + int windowHeight = Window.getClientHeight(); + int windowWidth = Window.getClientWidth(); + width = DEFAULT_WIDTH; + height = DEFAULT_HEIGHT; + + if (height > windowHeight / 2) { + height = windowHeight / 2; + } + if (width > windowWidth / 2) { + width = windowWidth / 2; + } + + top = windowHeight - (height + 10); + left = windowWidth - (width + 10); } setPixelSize(width, height); setPopupPosition(left, top); diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 6b22f3c9f3..533d6a78ae 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -3,6 +3,8 @@ */ package com.vaadin.terminal.gwt.client.ui; +import java.util.ArrayList; + import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; @@ -21,6 +23,7 @@ import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.FocusImpl; +import com.vaadin.terminal.gwt.client.BrowserInfo; /** * A scrollhandlers similar to {@link ScrollPanel}. @@ -126,7 +129,11 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * @return the vertical scroll position, in pixels */ public int getScrollPosition() { - return getElement().getScrollTop(); + if (getElement().getPropertyJSO("_vScrollTop") != null) { + return getElement().getPropertyInt("_vScrollTop"); + } else { + return getElement().getScrollTop(); + } } /** @@ -146,7 +153,23 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * the new vertical scroll position, in pixels */ public void setScrollPosition(int position) { - getElement().setScrollTop(position); + if (isAndroidWithBrokenScrollTop()) { + ArrayList<com.google.gwt.dom.client.Element> elements = TouchScrollDelegate + .getElements(getElement()); + for (com.google.gwt.dom.client.Element el : elements) { + final Style style = el.getStyle(); + style.setProperty("webkitTransform", "translate3d(0px," + + -position + "px,0px)"); + } + getElement().setPropertyInt("_vScrollTop", position); + } else { + getElement().setScrollTop(position); + } + } + + private boolean isAndroidWithBrokenScrollTop() { + return BrowserInfo.getBrowserString().contains("Android 3") + || BrowserInfo.getBrowserString().contains("Android 4"); } public void onScroll(ScrollEvent event) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java index 3c08741de5..d4b59ed23a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java @@ -4,19 +4,22 @@ package com.vaadin.terminal.gwt.client.ui; import java.util.ArrayList; -import java.util.Date; +import com.google.gwt.animation.client.Animation; +import com.google.gwt.core.client.Duration; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Touch; +import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.vaadin.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.VConsole; /** @@ -64,27 +67,34 @@ public class TouchScrollDelegate implements NativePreviewHandler { private static final double FRICTION = 0.002; private static final double DECELERATION = 0.002; private static final int MAX_DURATION = 1500; - private int origX; private int origY; private Element[] scrollableElements; private Element scrolledElement; private int origScrollTop; private HandlerRegistration handlerRegistration; + private double lastAnimatedTranslateY; private int lastClientY; - private double pixxelsPerMs; - private boolean transitionPending = false; private int deltaScrollPos; private boolean transitionOn = false; private int finalScrollTop; private ArrayList<Element> layers; private boolean moved; + private ScrollHandler scrollHandler; private static TouchScrollDelegate activeScrollDelegate; + private static final boolean androidWithBrokenScrollTop = BrowserInfo + .getBrowserString().contains("Android 3") + || BrowserInfo.getBrowserString().contains("Android 4"); + public TouchScrollDelegate(Element... elements) { scrollableElements = elements; } + public void setScrollHandler(ScrollHandler scrollHandler) { + this.scrollHandler = scrollHandler; + } + public static TouchScrollDelegate getActiveScrollDelegate() { return activeScrollDelegate; } @@ -116,37 +126,8 @@ public class TouchScrollDelegate implements NativePreviewHandler { public void onTouchStart(TouchStartEvent event) { if (activeScrollDelegate == null && event.getTouches().length() == 1) { - - Touch touch = event.getTouches().get(0); - if (detectScrolledElement(touch)) { - VConsole.log("TouchDelegate takes over"); - event.stopPropagation(); - handlerRegistration = Event.addNativePreviewHandler(this); - activeScrollDelegate = this; - hookTransitionEndListener(scrolledElement - .getFirstChildElement()); - origX = touch.getClientX(); - origY = touch.getClientY(); - yPositions[0] = origY; - eventTimeStamps[0] = new Date(); - nextEvent = 1; - - if (transitionOn) { - // TODO calculate current position of ongoing transition, - // fix to that and start scroll from there. Another option - // is to investigate if we can get even close the same - // framerate with scheduler based impl instead of using - // transitions (GWT examples has impl of this, with jsni - // though). This is very smooth on native ipad, now we - // ignore touch starts during animation. - origScrollTop = scrolledElement.getScrollTop(); - } else { - origScrollTop = scrolledElement.getScrollTop(); - } - moved = false; - // event.preventDefault(); - // event.stopPropagation(); - } + NativeEvent nativeEvent = event.getNativeEvent(); + doTouchStart(nativeEvent); } else { /* * Touch scroll is currenly on (possibly bouncing). Ignore. @@ -154,16 +135,39 @@ public class TouchScrollDelegate implements NativePreviewHandler { } } - private native void hookTransitionEndListener(Element element) - /*-{ - if(!element.hasTransitionEndListener) { - var that = this; - element.addEventListener("webkitTransitionEnd",function(event){ - that.@com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate::onTransitionEnd()(); - },false); - element.hasTransitionEndListener = true; + private void doTouchStart(NativeEvent nativeEvent) { + if (transitionOn) { + momentum.cancel(); + } + Touch touch = nativeEvent.getTouches().get(0); + if (detectScrolledElement(touch)) { + VConsole.log("TouchDelegate takes over"); + nativeEvent.stopPropagation(); + handlerRegistration = Event.addNativePreviewHandler(this); + activeScrollDelegate = this; + origY = touch.getClientY(); + yPositions[0] = origY; + eventTimeStamps[0] = getTimeStamp(); + nextEvent = 1; + + origScrollTop = getScrollTop(); + VConsole.log("ST" + origScrollTop); + + moved = false; + // event.preventDefault(); + // event.stopPropagation(); + } + } + + private int getScrollTop() { + if (androidWithBrokenScrollTop) { + if (scrolledElement.getPropertyJSO("_vScrollTop") != null) { + return scrolledElement.getPropertyInt("_vScrollTop"); + } + return 0; } - }-*/; + return scrolledElement.getScrollTop(); + } private void onTransitionEnd() { if (finalScrollTop < 0) { @@ -175,7 +179,6 @@ public class TouchScrollDelegate implements NativePreviewHandler { } else { moveTransformationToScrolloffset(); } - transitionOn = false; } private void animateToScrollPosition(int to, int from) { @@ -184,7 +187,14 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (time <= 0) { time = 1; // get animation and transition end event } - translateTo(time, -to + origScrollTop); + VConsole.log("Animate " + time + " " + from + " " + to); + int translateTo = -to + origScrollTop; + int fromY = -from + origScrollTop; + if (androidWithBrokenScrollTop) { + fromY -= origScrollTop; + translateTo -= origScrollTop; + } + translateTo(time, fromY, translateTo); } private int getAnimationTimeForDistance(int dist) { @@ -200,16 +210,21 @@ public class TouchScrollDelegate implements NativePreviewHandler { * scrolltop, causing onscroll event. */ private void moveTransformationToScrolloffset() { - for (Element el : layers) { - Style style = el.getStyle(); - style.setProperty("webkitTransitionProperty", "none"); - style.setProperty("webkitTransform", "translate3d(0,0,0)"); + if (androidWithBrokenScrollTop) { + scrolledElement.setPropertyInt("_vScrollTop", finalScrollTop); + if (scrollHandler != null) { + scrollHandler.onScroll(null); + } + } else { + for (Element el : layers) { + Style style = el.getStyle(); + style.setProperty("webkitTransform", "translate3d(0,0,0)"); + } + scrolledElement.setScrollTop(finalScrollTop); } - scrolledElement.setScrollTop(finalScrollTop); activeScrollDelegate = null; handlerRegistration.removeHandler(); handlerRegistration = null; - } /** @@ -225,14 +240,7 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (el.isOrHasChild(target) && el.getScrollHeight() > el.getClientHeight()) { scrolledElement = el; - NodeList<Node> childNodes = scrolledElement.getChildNodes(); - layers = new ArrayList<Element>(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.getItem(i); - if (item.getNodeType() == Node.ELEMENT_NODE) { - layers.add((Element) item); - } - } + layers = getElements(scrolledElement); return true; } @@ -240,10 +248,21 @@ public class TouchScrollDelegate implements NativePreviewHandler { return false; } + public static ArrayList<Element> getElements(Element scrolledElement2) { + NodeList<Node> childNodes = scrolledElement2.getChildNodes(); + ArrayList<Element> l = new ArrayList<Element>(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.getItem(i); + if (item.getNodeType() == Node.ELEMENT_NODE) { + l.add((Element) item); + } + } + return l; + } + private void onTouchMove(NativeEvent event) { if (!moved) { - Date date = new Date(); - long l = (date.getTime() - eventTimeStamps[0].getTime()); + double l = (getTimeStamp() - eventTimeStamps[0]); VConsole.log(l + " ms from start to move"); } boolean handleMove = readPositionAndSpeed(event); @@ -255,15 +274,15 @@ public class TouchScrollDelegate implements NativePreviewHandler { int overscroll = (deltaScrollTop + origScrollTop) - getMaxFinalY(); overscroll = overscroll / 2; - if (overscroll > scrolledElement.getClientHeight() / 2) { - overscroll = scrolledElement.getClientHeight() / 2; + if (overscroll > getMaxOverScroll()) { + overscroll = getMaxOverScroll(); } deltaScrollTop = getMaxFinalY() + overscroll - origScrollTop; } else if (finalPos < 0) { // spring effect at the beginning int overscroll = finalPos / 2; - if (-overscroll > scrolledElement.getClientHeight() / 2) { - overscroll = -scrolledElement.getClientHeight() / 2; + if (-overscroll > getMaxOverScroll()) { + overscroll = -getMaxOverScroll(); } deltaScrollTop = overscroll - origScrollTop; } @@ -276,16 +295,20 @@ public class TouchScrollDelegate implements NativePreviewHandler { private void quickSetScrollPosition(int deltaX, int deltaY) { deltaScrollPos = deltaY; - translateTo(0, -deltaScrollPos); + if (androidWithBrokenScrollTop) { + deltaY += origScrollTop; + translateTo(-deltaY); + } else { + translateTo(-deltaScrollPos); + } } private static final int EVENTS_FOR_SPEED_CALC = 3; public static final int SIGNIFICANT_MOVE_THRESHOLD = 3; private int[] yPositions = new int[EVENTS_FOR_SPEED_CALC]; - private Date[] eventTimeStamps = new Date[EVENTS_FOR_SPEED_CALC]; + private double[] eventTimeStamps = new double[EVENTS_FOR_SPEED_CALC]; private int nextEvent = 0; - private Date transitionStart; - private Date transitionDuration; + private Animation momentum; /** * @@ -293,12 +316,11 @@ public class TouchScrollDelegate implements NativePreviewHandler { * @return */ private boolean readPositionAndSpeed(NativeEvent event) { - Date now = new Date(); Touch touch = event.getChangedTouches().get(0); lastClientY = touch.getClientY(); int eventIndx = nextEvent++; eventIndx = eventIndx % EVENTS_FOR_SPEED_CALC; - eventTimeStamps[eventIndx] = now; + eventTimeStamps[eventIndx] = getTimeStamp(); yPositions[eventIndx] = lastClientY; return isMovedSignificantly(); } @@ -348,15 +370,11 @@ public class TouchScrollDelegate implements NativePreviewHandler { // VConsole.log("To max overscroll"); finalY = getMaxFinalY() + getMaxOverScroll(); int fixedPixelsToMove = finalY - currentY; - pixelsPerMs = pixelsPerMs * pixelsToMove / fixedPixelsToMove - / FRICTION; pixelsToMove = fixedPixelsToMove; } else if (finalY < 0 - getMaxOverScroll()) { // VConsole.log("to min overscroll"); finalY = -getMaxOverScroll(); int fixedPixelsToMove = finalY - currentY; - pixelsPerMs = pixelsPerMs * pixelsToMove / fixedPixelsToMove - / FRICTION; pixelsToMove = fixedPixelsToMove; } else { duration = (int) (Math.abs(pixelsPerMs / DECELERATION)); @@ -380,8 +398,13 @@ public class TouchScrollDelegate implements NativePreviewHandler { return; } - int translateY = -finalY + origScrollTop; - translateTo(duration, translateY); + int translateTo = -finalY + origScrollTop; + int fromY = -currentY + origScrollTop; + if (androidWithBrokenScrollTop) { + fromY -= origScrollTop; + translateTo -= origScrollTop; + } + translateTo(duration, fromY, translateTo); } private double calculateSpeed() { @@ -392,47 +415,75 @@ public class TouchScrollDelegate implements NativePreviewHandler { } int idx = nextEvent % EVENTS_FOR_SPEED_CALC; final int firstPos = yPositions[idx]; - final Date firstTs = eventTimeStamps[idx]; + final double firstTs = eventTimeStamps[idx]; idx += EVENTS_FOR_SPEED_CALC; idx--; idx = idx % EVENTS_FOR_SPEED_CALC; final int lastPos = yPositions[idx]; - final Date lastTs = eventTimeStamps[idx]; + final double lastTs = eventTimeStamps[idx]; // speed as in change of scrolltop == -speedOfTouchPos - return (firstPos - lastPos) - / (double) (lastTs.getTime() - firstTs.getTime()); + return (firstPos - lastPos) / (lastTs - firstTs); } /** * Note positive scrolltop moves layer up, positive translate moves layer * down. - * - * @param duration - * @param translateY */ - private void translateTo(int duration, int translateY) { + private void translateTo(double translateY) { for (Element el : layers) { - final Style style = el.getStyle(); - if (duration > 0) { - style.setProperty("webkitTransitionDuration", duration + "ms"); - style.setProperty("webkitTransitionTimingFunction", - "cubic-bezier(0,0,0.25,1)"); - style.setProperty("webkitTransitionProperty", - "-webkit-transform"); - transitionOn = true; - transitionStart = new Date(); - transitionDuration = new Date(); - } else { - style.setProperty("webkitTransitionProperty", "none"); - } + Style style = el.getStyle(); style.setProperty("webkitTransform", "translate3d(0px," + translateY + "px,0px)"); } } + /** + * Note positive scrolltop moves layer up, positive translate moves layer + * down. + * + * @param duration + */ + private void translateTo(int duration, final int fromY, final int finalY) { + if (duration > 0) { + transitionOn = true; + + momentum = new Animation() { + + @Override + protected void onUpdate(double progress) { + lastAnimatedTranslateY = (fromY + (finalY - fromY) + * progress); + translateTo(lastAnimatedTranslateY); + } + + @Override + protected double interpolate(double progress) { + return 1 + Math.pow(progress - 1, 3); + } + + @Override + protected void onComplete() { + super.onComplete(); + transitionOn = false; + onTransitionEnd(); + } + + @Override + protected void onCancel() { + int delta = (int) (finalY - lastAnimatedTranslateY); + finalScrollTop -= delta; + moveTransformationToScrolloffset(); + transitionOn = false; + } + }; + momentum.run(duration); + } + } + private int getMaxOverScroll() { - return scrolledElement.getClientHeight() / 4; + return androidWithBrokenScrollTop ? 0 : scrolledElement + .getClientHeight() / 3; } private int getMaxFinalY() { @@ -441,14 +492,18 @@ public class TouchScrollDelegate implements NativePreviewHandler { } public void onPreviewNativeEvent(NativePreviewEvent event) { + int typeInt = event.getTypeInt(); if (transitionOn) { /* * TODO allow starting new events. See issue in onTouchStart */ event.cancel(); + + if (typeInt == Event.ONTOUCHSTART) { + doTouchStart(event.getNativeEvent()); + } return; } - int typeInt = event.getTypeInt(); switch (typeInt) { case Event.ONTOUCHMOVE: if (!event.isCanceled()) { @@ -484,4 +539,15 @@ public class TouchScrollDelegate implements NativePreviewHandler { public void setElements(com.google.gwt.user.client.Element[] elements) { scrollableElements = elements; } + + /** + * long calcucation are not very efficient in GWT, so this helper method + * returns timestamp in double. + * + * @return + */ + public static double getTimeStamp() { + return Duration.currentTimeMillis(); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java b/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java index 5208d7cacf..b4194c40a6 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/customlayout/VCustomLayout.java @@ -362,9 +362,9 @@ public class VCustomLayout extends ComplexPanel { private native void publishResizedFunction(Element element) /*-{ var self = this; - element.notifyChildrenOfSizeChange = function() { + element.notifyChildrenOfSizeChange = $entry(function() { self.@com.vaadin.terminal.gwt.client.ui.customlayout.VCustomLayout::notifyChildrenOfSizeChange()(); - }; + }); }-*/; /** diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java index 5cbfabbb11..d09b81e1e1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapper.java @@ -508,9 +508,9 @@ public class VDragAndDropWrapper extends VCustomComponent implements protected native void hookHtml5DragStart(Element el) /*-{ var me = this; - el.addEventListener("dragstart", function(ev) { + el.addEventListener("dragstart", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); }-*/; /** @@ -522,21 +522,21 @@ public class VDragAndDropWrapper extends VCustomComponent implements /*-{ var me = this; - el.addEventListener("dragenter", function(ev) { + el.addEventListener("dragenter", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); - el.addEventListener("dragleave", function(ev) { + el.addEventListener("dragleave", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); - el.addEventListener("dragover", function(ev) { + el.addEventListener("dragover", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); - el.addEventListener("drop", function(ev) { + el.addEventListener("drop", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); + }), false); }-*/; public boolean updateDropDetails(VDragEvent drag) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java index f819b0559a..bb511524e5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java +++ b/src/com/vaadin/terminal/gwt/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java @@ -39,9 +39,9 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { /*-{ var me = this; - el.attachEvent("ondragstart", function(ev) { + el.attachEvent("ondragstart", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); }-*/; @Override @@ -49,21 +49,21 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { /*-{ var me = this; - el.attachEvent("ondragenter", function(ev) { + el.attachEvent("ondragenter", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); - el.attachEvent("ondragleave", function(ev) { + el.attachEvent("ondragleave", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); - el.attachEvent("ondragover", function(ev) { + el.attachEvent("ondragover", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); - el.attachEvent("ondrop", function(ev) { + el.attachEvent("ondrop", $entry(function(ev) { return me.@com.vaadin.terminal.gwt.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); + })); }-*/; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java index 563ca04abe..c45c26c4ac 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java @@ -18,6 +18,7 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; @@ -545,6 +546,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (touchScrollDelegate == null) { touchScrollDelegate = new TouchScrollDelegate( scrollBodyPanel.getElement()); + touchScrollDelegate.setScrollHandler(this); } return touchScrollDelegate; @@ -3833,6 +3835,14 @@ public class VScrollTable extends FlowPanel implements HasWidgets, DOM.appendChild(container, preSpacer); DOM.appendChild(container, table); DOM.appendChild(container, postSpacer); + if (BrowserInfo.get().isTouchDevice()) { + NodeList<Node> childNodes = container.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Element item = (Element) childNodes.getItem(i); + item.getStyle().setProperty("webkitTransform", + "translate3d(0,0,0)"); + } + } } @@ -4370,7 +4380,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public class VScrollTableRow extends Panel implements ActionOwner { - private static final int TOUCHSCROLL_TIMEOUT = 70; + private static final int TOUCHSCROLL_TIMEOUT = 100; private static final int DRAGMODE_MULTIROW = 2; protected ArrayList<Widget> childWidgets = new ArrayList<Widget>(); private boolean selected = false; @@ -5020,9 +5030,11 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } }; } - contextTouchTimeout.cancel(); - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + } } break; case Event.ONMOUSEDOWN: @@ -6175,6 +6187,11 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * The row to ensure is visible */ private void ensureRowIsVisible(VScrollTableRow row) { + if (BrowserInfo.get().isTouchDevice()) { + // Skip due to android devices that have broken scrolltop will may + // get odd scrolling here. + return; + } Util.scrollIntoViewVertically(row.getElement()); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java index b2141e06e5..7bd392b503 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java @@ -234,9 +234,9 @@ public class VTextField extends TextBoxBase implements Field, ChangeHandler, protected native void attachCutEventListener(Element el) /*-{ var me = this; - el.oncut = function() { + el.oncut = $entry(function() { me.@com.vaadin.terminal.gwt.client.ui.textfield.VTextField::onCut()(); - }; + }); }-*/; protected native void detachCutEventListener(Element el) diff --git a/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java b/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java index d40f954cdc..484000b8d1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java +++ b/src/com/vaadin/terminal/gwt/client/ui/video/VVideo.java @@ -34,9 +34,9 @@ public class VVideo extends VMediaBase { private native void updateDimensionsWhenMetadataLoaded(Element el) /*-{ var self = this; - el.addEventListener('loadedmetadata', function(e) { - $entry(self.@com.vaadin.terminal.gwt.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight)); - }, false); + el.addEventListener('loadedmetadata', $entry(function(e) { + self.@com.vaadin.terminal.gwt.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight); + }), false); }-*/; diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java index 0d67086339..58d6a18592 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java @@ -530,6 +530,9 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet WrappedPortletResponse wrappedResponse = new WrappedPortletResponse( response, getDeploymentConfiguration()); + RequestTimer requestTimer = RequestTimer.get(wrappedRequest); + requestTimer.start(wrappedRequest); + RequestType requestType = getRequestType(request); if (requestType == RequestType.UNKNOWN) { @@ -719,6 +722,9 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet Root.setCurrentRoot(null); Application.setCurrentApplication(null); } + + requestTimer.stop(); + RequestTimer.set(wrappedRequest, requestTimer); } } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index 18cc3f97f4..799271b979 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -403,6 +403,9 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper( this); + RequestTimer requestTimer = RequestTimer.get(request); + requestTimer.start(request); + RequestType requestType = getRequestType(request); if (!ensureCookiesEnabled(requestType, request, response)) { return; @@ -539,6 +542,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements Application.setCurrentApplication(null); } + requestTimer.stop(); + RequestTimer.set(request, requestTimer); } } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index a1a917130f..b780f66a23 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -691,7 +691,7 @@ public abstract class AbstractCommunicationManager implements Serializable { outWriter.print(getSecurityKeyUIDL(request)); } - writeUidlResponse(repaintAll, outWriter, root, analyzeLayouts); + writeUidlResponse(request, repaintAll, outWriter, root, analyzeLayouts); closeJsonMessage(outWriter); @@ -735,7 +735,7 @@ public abstract class AbstractCommunicationManager implements Serializable { } @SuppressWarnings("unchecked") - public void writeUidlResponse(boolean repaintAll, + public void writeUidlResponse(WrappedRequest request, boolean repaintAll, final PrintWriter outWriter, Root root, boolean analyzeLayouts) throws PaintException { ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<ClientConnector>(); @@ -1096,6 +1096,20 @@ public abstract class AbstractCommunicationManager implements Serializable { if (dragAndDropService != null) { dragAndDropService.printJSONResponse(outWriter); } + + writePerformanceDataForTestBench(request, outWriter); + } + + /** + * 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 legacyPaint(PaintTarget paintTarget, @@ -2161,7 +2175,7 @@ public abstract class AbstractCommunicationManager implements Serializable { if (isXSRFEnabled(root.getApplication())) { pWriter.print(getSecurityKeyUIDL(request)); } - writeUidlResponse(true, pWriter, root, false); + writeUidlResponse(request, true, pWriter, root, false); pWriter.print("}"); String initialUIDL = sWriter.toString(); logger.log(Level.FINE, "Initial UIDL:" + initialUIDL); diff --git a/src/com/vaadin/terminal/gwt/server/RequestTimer.java b/src/com/vaadin/terminal/gwt/server/RequestTimer.java new file mode 100644 index 0000000000..d47f444bef --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/RequestTimer.java @@ -0,0 +1,80 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.server; + +import com.vaadin.terminal.WrappedRequest; + +/** + * Times the handling of requests and stores the information as an attribute in + * the request. The timing info is later passed on to the client in the UIDL and + * the client provides JavaScript API for accessing this data from e.g. + * TestBench. + * + * @author Jonatan Kronqvist / Vaadin Ltd + */ +public class RequestTimer { + public static final String SESSION_ATTR_ID = "REQUESTTIMER"; + + private long requestStartTime = 0; + private long totalSessionTime = 0; + private long lastRequestTime = -1; + + /** + * Starts the timing of a request. This should be called before any + * processing of the request. + * + * @param request + * the request. + */ + public void start(WrappedRequest request) { + requestStartTime = System.nanoTime(); + request.setAttribute("TOTAL", totalSessionTime); + request.setAttribute("LASTREQUEST", lastRequestTime); + } + + /** + * Stops the timing of a request. This should be called when all processing + * of a request has finished. + */ + public void stop() { + // 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); + } +} |