package org.vaadin.touchkit.gwt.client; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.ScrollEvent; import com.google.gwt.user.client.Window.ScrollHandler; import com.vaadin.client.ApplicationConfiguration; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.LayoutManager; public class Ios7SafariHackLoader extends TouchKitPlatformHackLoader { /* * Fixes iOS 7 viewport height issues by applying inline styles and forcing * the window to scroll to top whenever the content gets displaced (#14123, * #13749). See also http://stackoverflow.com/questions/19012135/ */ private static final class ViewportHeightHack implements RepeatingCommand, ScrollHandler { enum State { STOPPED, POLLING, ADJUSTING; } int height = 0; State state = State.STOPPED; boolean onScreenKeyboardMightBeVisible = false; int repetitions = 0; boolean ignoreNextScrollEvent = false; final Style bodyStyle = Document.get().getBody().getStyle(); final boolean iPad = Window.Navigator.getUserAgent().toLowerCase() .contains("ipad"); void onOrientationChange() { bodyStyle.clearHeight(); bodyStyle.clearPosition(); forceLayout(); if (deviceInLandscapeMode()) { height = iPad ? 0 : getWindowHeight(); schedule(1); } } @Override public void onWindowScroll(ScrollEvent event) { if (ignoreNextScrollEvent) { ignoreNextScrollEvent = false; } else if (deviceInLandscapeMode() && state == State.STOPPED && height != getWindowHeight()) { onScreenKeyboardMightBeVisible &= Document.get() .getDocumentElement().getOffsetHeight() != getWindowHeight(); if (!onScreenKeyboardMightBeVisible) { schedule(2); } } } void onFocusIn() { onScreenKeyboardMightBeVisible = true; } void onFocusOut() { onScreenKeyboardMightBeVisible = false; } @Override public boolean execute() { onScreenKeyboardMightBeVisible &= Document.get() .getDocumentElement().getOffsetHeight() != getWindowHeight(); if (onScreenKeyboardMightBeVisible && !iPad) { return true; } if (state == State.ADJUSTING) { Window.scrollTo(0, 0); ignoreNextScrollEvent = true; } if (height != getWindowHeight()) { height = getWindowHeight(); bodyStyle.setHeight(height, Unit.PX); bodyStyle.setPosition(Position.FIXED); state = State.ADJUSTING; } else if (state == State.ADJUSTING) { state = State.STOPPED; forceLayout(); if (repetitions > 0) { schedule(repetitions); } return false; } return true; } void forceLayout() { for (ApplicationConnection app : ApplicationConfiguration .getRunningApplications()) { LayoutManager.get(app).forceLayout(); } } void schedule(int times) { repetitions = times - 1; if (state == State.STOPPED) { Scheduler.get().scheduleFixedPeriod(this, 200); } state = State.POLLING; } native boolean deviceInLandscapeMode() /*-{ return Math.abs($wnd.orientation) === 90; }-*/; native void attachNativeEventListeners() /*-{ var that = this; $wnd.addEventListener('orientationchange', function () { that.@org.vaadin.touchkit.gwt.client.Ios7SafariHackLoader.ViewportHeightHack::onOrientationChange()(); }); $doc.addEventListener('focusin', function () { that.@org.vaadin.touchkit.gwt.client.Ios7SafariHackLoader.ViewportHeightHack::onFocusIn()(); }); $doc.addEventListener('focusout', function () { that.@org.vaadin.touchkit.gwt.client.Ios7SafariHackLoader.ViewportHeightHack::onFocusOut()(); }); }-*/; native int getWindowHeight() /*-{ return $wnd.innerHeight; }-*/; void activate() { if (deviceInLandscapeMode()) { state = State.ADJUSTING; execute(); state = State.STOPPED; } attachNativeEventListeners(); Window.addWindowScrollHandler(this); } } @Override public void load() { new ViewportHeightHack().activate(); } }