From 535a8bc1c38ceb838feeef037cb14b4b109c4d2a Mon Sep 17 00:00:00 2001 From: Ed Morley Date: Wed, 1 Aug 2012 18:42:05 +0100 Subject: [PATCH] Backout d2ee4c12c0b3 (bug 777351), 5aa6f94160dd (bug 777351), b47c470168fc (bug 777351), 5fb303ba52f7 (bug 777351), be81e4c3d928 (bug 777351), abc5b9a922dc (bug 777075), 8f1fc980f1f1 (bug 777075), 0b194a7f47d4 (bug 777075), d10df9bfef60 (bug 777075), 65393fe32cce (bug 777075), b52dc1df2fde (bug 777075), 8aeda525c094 (bug 777075) for Android native R1 failures on a CLOSED TREE --- mobile/android/base/FormAssistPopup.java | 2 +- mobile/android/base/GeckoApp.java | 103 +++-- mobile/android/base/GeckoAppShell.java | 35 +- mobile/android/base/GeckoEvent.java | 2 +- mobile/android/base/GeckoInputConnection.java | 6 +- mobile/android/base/Makefile.in | 3 +- mobile/android/base/PromptService.java | 8 +- mobile/android/base/Tab.java | 38 +- mobile/android/base/TextSelection.java | 14 +- mobile/android/base/TextSelectionHandle.java | 18 +- mobile/android/base/ZoomConstraints.java | 46 --- mobile/android/base/gfx/GeckoLayerClient.java | 377 ++++-------------- mobile/android/base/gfx/LayerController.java | 360 +++++++++++++++++ mobile/android/base/gfx/LayerRenderer.java | 20 +- mobile/android/base/gfx/LayerView.java | 37 +- .../android/base/gfx/TouchEventHandler.java | 10 +- .../base/tests/MotionEventReplayer.java.in | 2 +- mobile/android/base/ui/PanZoomController.java | 154 +++---- mobile/android/base/ui/PanZoomTarget.java | 25 -- 19 files changed, 684 insertions(+), 576 deletions(-) delete mode 100644 mobile/android/base/ZoomConstraints.java create mode 100644 mobile/android/base/gfx/LayerController.java delete mode 100644 mobile/android/base/ui/PanZoomTarget.java diff --git a/mobile/android/base/FormAssistPopup.java b/mobile/android/base/FormAssistPopup.java index 9155660cc5f..416791ed051 100644 --- a/mobile/android/base/FormAssistPopup.java +++ b/mobile/android/base/FormAssistPopup.java @@ -222,7 +222,7 @@ public class FormAssistPopup extends RelativeLayout implements GeckoEventListene int popupWidth = RelativeLayout.LayoutParams.FILL_PARENT; int popupLeft = left < 0 ? 0 : left; - FloatSize viewport = GeckoApp.mAppContext.getLayerClient().getViewportSize(); + FloatSize viewport = GeckoApp.mAppContext.getLayerController().getViewportSize(); // For autocomplete suggestions, if the input is smaller than the screen-width, // shrink the popup's width. Otherwise, keep it as FILL_PARENT. diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 6166fb2d8eb..2bf8b58e7ec 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -8,6 +8,7 @@ package org.mozilla.gecko; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.gfx.GeckoLayerClient; import org.mozilla.gecko.gfx.Layer; +import org.mozilla.gecko.gfx.LayerController; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.gfx.PluginLayer; import org.mozilla.gecko.gfx.PointUtils; @@ -151,6 +152,7 @@ abstract public class GeckoApp protected FormAssistPopup mFormAssistPopup; protected TabsPanel mTabsPanel; + private LayerController mLayerController; private GeckoLayerClient mLayerClient; private AbsoluteLayout mPluginContainer; @@ -609,22 +611,31 @@ abstract public class GeckoApp return metrics; } - void getAndProcessThumbnailForTab(final Tab tab) { + public void getAndProcessThumbnailForTab(final Tab tab) { + boolean isSelectedTab = Tabs.getInstance().isSelectedTab(tab); + final Bitmap bitmap = isSelectedTab ? mLayerClient.getBitmap() : null; + if ("about:home".equals(tab.getURL())) { tab.updateThumbnail(null); return; } - if (tab.getState() == Tab.STATE_DELAYED) { - byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL()); - if (thumbnail != null) - processThumbnail(tab, null, thumbnail); - return; - } + if (bitmap != null) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos); + processThumbnail(tab, bitmap, bos.toByteArray()); + } else { + if (tab.getState() == Tab.STATE_DELAYED) { + byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL()); + if (thumbnail != null) + processThumbnail(tab, null, thumbnail); + return; + } - int dw = tab.getThumbnailWidth(); - int dh = tab.getThumbnailHeight(); - GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, 0, 0, 0, 0, dw, dh, dw, dh, ScreenshotHandler.SCREENSHOT_THUMBNAIL, tab.getThumbnailBuffer())); + int dw = tab.getThumbnailWidth(); + int dh = tab.getThumbnailHeight(); + GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, 0, 0, 0, 0, dw, dh, dw, dh, ScreenshotHandler.SCREENSHOT_THUMBNAIL, tab.getThumbnailBuffer())); + } } void handleThumbnailData(Tab tab, ByteBuffer data) { @@ -693,7 +704,10 @@ abstract public class GeckoApp tab.updateTitle(null); tab.updateIdentityData(null); tab.setReaderEnabled(false); - tab.setZoomConstraints(new ZoomConstraints()); + tab.setAllowZoom(true); + tab.setDefaultZoom(0); + tab.setMinZoom(0); + tab.setMaxZoom(0); tab.setHasTouchListeners(false); tab.setCheckerboardColor(Color.WHITE); @@ -864,10 +878,10 @@ abstract public class GeckoApp tab.setCheckerboardColor(Color.WHITE); } - // Sync up the GeckoLayerClient and the tab if the tab's + // Sync up the LayerController and the tab if the tab's // currently displayed. - if (getLayerClient() != null && Tabs.getInstance().isSelectedTab(tab)) { - getLayerClient().setCheckerboardColor(tab.getCheckerboardColor()); + if (getLayerController() != null && Tabs.getInstance().isSelectedTab(tab)) { + getLayerController().setCheckerboardColor(tab.getCheckerboardColor()); } } else if (event.equals("DOMTitleChanged")) { final int tabId = message.getInt("tabID"); @@ -1011,11 +1025,17 @@ abstract public class GeckoApp Tab tab = Tabs.getInstance().getTab(tabId); if (tab == null) return; - tab.setZoomConstraints(new ZoomConstraints(message)); - // Sync up the GeckoLayerClient and the tab if the tab is currently displayed. - GeckoLayerClient layerClient = getLayerClient(); - if (layerClient != null && Tabs.getInstance().isSelectedTab(tab)) { - layerClient.setZoomConstraints(tab.getZoomConstraints()); + tab.setAllowZoom(message.getBoolean("allowZoom")); + tab.setDefaultZoom((float) message.getDouble("defaultZoom")); + tab.setMinZoom((float) message.getDouble("minZoom")); + tab.setMaxZoom((float) message.getDouble("maxZoom")); + // Sync up the LayerController and the tab if the tab's currently displayed. + LayerController controller = getLayerController(); + if (controller != null && Tabs.getInstance().isSelectedTab(tab)) { + controller.setAllowZoom(tab.getAllowZoom()); + controller.setDefaultZoom(tab.getDefaultZoom()); + controller.setMinZoom(tab.getMinZoom()); + controller.setMaxZoom(tab.getMaxZoom()); } } else if (event.equals("Tab:HasTouchListener")) { int tabId = message.getInt("tabID"); @@ -1024,7 +1044,7 @@ abstract public class GeckoApp mMainHandler.post(new Runnable() { public void run() { if (Tabs.getInstance().isSelectedTab(tab)) - mLayerClient.getView().getTouchEventHandler().setWaitForTouchListeners(true); + mLayerController.getView().getTouchEventHandler().setWaitForTouchListeners(true); } }); } else if (event.equals("Session:StatePurged")) { @@ -1250,7 +1270,7 @@ abstract public class GeckoApp tab.updateIdentityData(null); tab.setReaderEnabled(false); if (Tabs.getInstance().isSelectedTab(tab)) - getLayerClient().getView().getRenderer().resetCheckerboard(); + getLayerController().getView().getRenderer().resetCheckerboard(); mMainHandler.post(new Runnable() { public void run() { Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.START, showProgress); @@ -1387,14 +1407,14 @@ abstract public class GeckoApp PluginLayer layer = (PluginLayer) tab.getPluginLayer(view); if (layer == null) { - layer = new PluginLayer(view, rect, mLayerClient.getView().getRenderer().getMaxTextureSize()); + layer = new PluginLayer(view, rect, mLayerController.getView().getRenderer().getMaxTextureSize()); tab.addPluginLayer(view, layer); } else { layer.reset(rect); layer.setVisible(true); } - mLayerClient.getView().addLayer(layer); + mLayerController.getView().addLayer(layer); } }); } @@ -1416,7 +1436,7 @@ abstract public class GeckoApp // a deadlock, see comment below in FullScreenHolder mMainHandler.post(new Runnable() { public void run() { - mLayerClient.getView().setVisibility(View.VISIBLE); + mLayerController.getView().setVisibility(View.VISIBLE); } }); @@ -1471,19 +1491,19 @@ abstract public class GeckoApp } private void hidePluginLayer(Layer layer) { - LayerView layerView = mLayerClient.getView(); + LayerView layerView = mLayerController.getView(); layerView.removeLayer(layer); layerView.requestRender(); } private void showPluginLayer(Layer layer) { - LayerView layerView = mLayerClient.getView(); + LayerView layerView = mLayerController.getView(); layerView.addLayer(layer); layerView.requestRender(); } public void requestRender() { - mLayerClient.getView().requestRender(); + mLayerController.getView().requestRender(); } public void hidePlugins(Tab tab) { @@ -1697,9 +1717,23 @@ abstract public class GeckoApp cameraView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } - if (mLayerClient == null) { + if (mLayerController == null) { + /* + * Create a layer client, but don't hook it up to the layer controller yet. + */ mLayerClient = new GeckoLayerClient(this); - mLayerClient.setView((LayerView)findViewById(R.id.layer_view)); + + /* + * Hook a placeholder layer client up to the layer controller so that the user can pan + * and zoom a cached screenshot of the previous page. This call will return null if + * there is no cached screenshot; in that case, we have no choice but to display a + * checkerboard. + * + * TODO: Fall back to a built-in screenshot of the Fennec Start page for a nice first- + * run experience, perhaps? + */ + mLayerController = new LayerController(this); + mLayerController.setView((LayerView)findViewById(R.id.layer_view)); } mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container); @@ -2120,6 +2154,8 @@ abstract public class GeckoApp deleteTempFiles(); + if (mLayerController != null) + mLayerController.destroy(); if (mLayerClient != null) mLayerClient.destroy(); if (mFormAssistPopup != null) @@ -2575,6 +2611,7 @@ abstract public class GeckoApp /* This method is referenced by Robocop via reflection. */ public GeckoLayerClient getLayerClient() { return mLayerClient; } + public LayerController getLayerController() { return mLayerController; } public AbsoluteLayout getPluginContainer() { return mPluginContainer; } @@ -2620,10 +2657,10 @@ abstract public class GeckoApp } private void connectGeckoLayerClient() { - GeckoLayerClient layerClient = getLayerClient(); - layerClient.notifyGeckoReady(); + LayerController layerController = getLayerController(); + layerController.setLayerClient(mLayerClient); - layerClient.getView().getTouchEventHandler().setOnTouchListener(new ContentTouchListener() { + layerController.getView().getTouchEventHandler().setOnTouchListener(new ContentTouchListener() { private PointF initialPoint = null; @Override @@ -2708,7 +2745,7 @@ abstract public class GeckoApp mMainHandler.post(new Runnable() { public void run() { - mLayerClient.getView().setVisibility(View.INVISIBLE); + mLayerController.getView().setVisibility(View.INVISIBLE); } }); } diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index 57ed4168426..9a5014c10a8 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -10,6 +10,7 @@ import org.mozilla.gecko.gfx.IntSize; import org.mozilla.gecko.gfx.GeckoLayerClient; import org.mozilla.gecko.gfx.GfxInfoThread; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; +import org.mozilla.gecko.gfx.LayerController; import org.mozilla.gecko.gfx.LayerView; import org.mozilla.gecko.gfx.ScreenshotLayer; import org.mozilla.gecko.gfx.RectUtils; @@ -539,10 +540,10 @@ public class GeckoAppShell // Called on the UI thread after Gecko loads. private static void geckoLoaded() { - final GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - LayerView v = layerClient.getView(); + final LayerController layerController = GeckoApp.mAppContext.getLayerController(); + LayerView v = layerController.getView(); mInputConnection = v.setInputConnectionHandler(); - layerClient.setForceRedraw(); + layerController.notifyLayerClientOfGeometryChange(); } static void sendPendingEventsToGecko() { @@ -1417,8 +1418,8 @@ public class GeckoAppShell // Don't perform haptic feedback if a vibration is currently playing, // because the haptic feedback will nuke the vibration. if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) { - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - LayerView layerView = layerClient.getView(); + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + LayerView layerView = layerController.getView(); layerView.performHapticFeedback(aIsLongPress ? HapticFeedbackConstants.LONG_PRESS : HapticFeedbackConstants.VIRTUAL_KEY); @@ -1426,8 +1427,8 @@ public class GeckoAppShell } private static Vibrator vibrator() { - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - LayerView layerView = layerClient.getView(); + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + LayerView layerView = layerController.getView(); return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE); } @@ -1475,7 +1476,7 @@ public class GeckoAppShell public static void notifyDefaultPrevented(final boolean defaultPrevented) { getMainHandler().post(new Runnable() { public void run() { - LayerView view = GeckoApp.mAppContext.getLayerClient().getView(); + LayerView view = GeckoApp.mAppContext.getLayerController().getView(); view.getTouchEventHandler().handleEventListenerAction(!defaultPrevented); } }); @@ -2393,11 +2394,11 @@ class ScreenshotHandler implements Runnable { } private void screenshotWholePage(int tabId) { - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - if (layerClient == null) { + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + if (layerController == null) { return; } - ImmutableViewportMetrics viewport = layerClient.getViewportMetrics(); + ImmutableViewportMetrics viewport = layerController.getViewportMetrics(); RectF pageRect = viewport.getCssPageRect(); if (FloatUtils.fuzzyEquals(pageRect.width(), 0) || FloatUtils.fuzzyEquals(pageRect.height(), 0)) { @@ -2497,14 +2498,14 @@ class ScreenshotHandler implements Runnable { return; } - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - if (layerClient == null) { + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + if (layerController == null) { // we could be in the midst of an activity tear-down and re-start, so guard // against a null layer controller. return; } - ImmutableViewportMetrics viewport = layerClient.getViewportMetrics(); + ImmutableViewportMetrics viewport = layerController.getViewportMetrics(); if (RectUtils.fuzzyEquals(mPageRect, viewport.getCssPageRect())) { // the page size hasn't changed, so our dirty rect is still valid and we can just // repaint that area @@ -2584,9 +2585,9 @@ class ScreenshotHandler implements Runnable { // this screenshot has all its slices done, so push it out // to the layer renderer and remove it from the list } - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - if (layerClient != null) { - layerClient.getView().getRenderer().setCheckerboardBitmap( + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + if (layerController != null) { + layerController.getView().getRenderer().setCheckerboardBitmap( data, bufferWidth, bufferHeight, handler.mPageRect, current.getPaintedRegion()); } diff --git a/mobile/android/base/GeckoEvent.java b/mobile/android/base/GeckoEvent.java index f54dbf35af0..cd9e8a56bcf 100644 --- a/mobile/android/base/GeckoEvent.java +++ b/mobile/android/base/GeckoEvent.java @@ -226,7 +226,7 @@ public class GeckoEvent { public void addMotionPoint(int index, int eventIndex, MotionEvent event) { try { PointF geckoPoint = new PointF(event.getX(eventIndex), event.getY(eventIndex)); - geckoPoint = GeckoApp.mAppContext.getLayerClient().convertViewPointToLayerPoint(geckoPoint); + geckoPoint = GeckoApp.mAppContext.getLayerController().convertViewPointToLayerPoint(geckoPoint); mPoints[index] = new Point(Math.round(geckoPoint.x), Math.round(geckoPoint.y)); mPointIndicies[index] = event.getPointerId(eventIndex); diff --git a/mobile/android/base/GeckoInputConnection.java b/mobile/android/base/GeckoInputConnection.java index ad35269235e..051559f05bc 100644 --- a/mobile/android/base/GeckoInputConnection.java +++ b/mobile/android/base/GeckoInputConnection.java @@ -38,7 +38,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import org.mozilla.gecko.gfx.InputConnectionHandler; -import org.mozilla.gecko.gfx.GeckoLayerClient; +import org.mozilla.gecko.gfx.LayerController; import java.util.Timer; import java.util.TimerTask; @@ -294,8 +294,8 @@ public class GeckoInputConnection } private static View getView() { - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - return (layerClient == null ? null : layerClient.getView()); + LayerController controller = GeckoApp.mAppContext.getLayerController(); + return (controller == null ? null : controller.getView()); } private Span getSelection() { diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 3414c22c7a0..8f14f52ce69 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -106,7 +106,6 @@ FENNEC_JAVA_FILES = \ TextSelection.java \ TextSelectionHandle.java \ WebAppAllocator.java \ - ZoomConstraints.java \ gfx/BitmapUtils.java \ gfx/BufferedCairoImage.java \ gfx/CairoGLInfo.java \ @@ -124,6 +123,7 @@ FENNEC_JAVA_FILES = \ gfx/InputConnectionHandler.java \ gfx/IntSize.java \ gfx/Layer.java \ + gfx/LayerController.java \ gfx/LayerRenderer.java \ gfx/LayerView.java \ gfx/PluginLayer.java \ @@ -144,7 +144,6 @@ FENNEC_JAVA_FILES = \ gfx/VirtualLayer.java \ ui/Axis.java \ ui/PanZoomController.java \ - ui/PanZoomTarget.java \ ui/SimpleScaleGestureDetector.java \ ui/SubdocumentScrollHelper.java \ GeckoNetworkManager.java \ diff --git a/mobile/android/base/PromptService.java b/mobile/android/base/PromptService.java index a0229cedc04..e6dd251dec4 100644 --- a/mobile/android/base/PromptService.java +++ b/mobile/android/base/PromptService.java @@ -34,7 +34,7 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import org.json.JSONArray; import org.json.JSONObject; -import org.mozilla.gecko.gfx.GeckoLayerClient; +import org.mozilla.gecko.gfx.LayerController; public class PromptService implements OnClickListener, OnCancelListener, OnItemClickListener, GeckoEventResponder { private static final String LOGTAG = "GeckoPromptService"; @@ -178,11 +178,11 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC } public void show(String aTitle, String aText, PromptButton[] aButtons, PromptListItem[] aMenuList, boolean aMultipleSelection) { - final GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - layerClient.post(new Runnable() { + final LayerController controller = GeckoApp.mAppContext.getLayerController(); + controller.post(new Runnable() { public void run() { // treat actions that show a dialog as if preventDefault by content to prevent panning - layerClient.getPanZoomController().abortPanning(); + controller.getPanZoomController().abortPanning(); } }); diff --git a/mobile/android/base/Tab.java b/mobile/android/base/Tab.java index 742422d53e4..bfc2b7519e1 100644 --- a/mobile/android/base/Tab.java +++ b/mobile/android/base/Tab.java @@ -52,7 +52,10 @@ public final class Tab { private String mDocumentURI; private String mContentType; private boolean mHasTouchListeners; - private ZoomConstraints mZoomConstraints; + private boolean mAllowZoom; + private float mDefaultZoom; + private float mMinZoom; + private float mMaxZoom; private ArrayList mPluginViews; private HashMap mPluginLayers; private ContentResolver mContentResolver; @@ -88,7 +91,6 @@ public final class Tab { mFaviconLoadId = 0; mDocumentURI = ""; mContentType = ""; - mZoomConstraints = new ZoomConstraints(); mPluginViews = new ArrayList(); mPluginLayers = new HashMap(); mState = "about:home".equals(url) ? STATE_SUCCESS : STATE_LOADING; @@ -281,12 +283,36 @@ public final class Tab { return mState; } - public void setZoomConstraints(ZoomConstraints constraints) { - mZoomConstraints = constraints; + public void setAllowZoom(boolean aValue) { + mAllowZoom = aValue; } - public ZoomConstraints getZoomConstraints() { - return mZoomConstraints; + public boolean getAllowZoom() { + return mAllowZoom; + } + + public void setDefaultZoom(float aValue) { + mDefaultZoom = aValue; + } + + public float getDefaultZoom() { + return mDefaultZoom; + } + + public void setMinZoom(float aValue) { + mMinZoom = aValue; + } + + public float getMinZoom() { + return mMinZoom; + } + + public void setMaxZoom(float aValue) { + mMaxZoom = aValue; + } + + public float getMaxZoom() { + return mMaxZoom; } public void setHasTouchListeners(boolean aValue) { diff --git a/mobile/android/base/TextSelection.java b/mobile/android/base/TextSelection.java index 0ac80658b90..622462db56c 100644 --- a/mobile/android/base/TextSelection.java +++ b/mobile/android/base/TextSelection.java @@ -8,7 +8,7 @@ import android.util.Log; import android.view.View; import org.mozilla.gecko.gfx.Layer; import org.mozilla.gecko.gfx.Layer.RenderContext; -import org.mozilla.gecko.gfx.GeckoLayerClient; +import org.mozilla.gecko.gfx.LayerController; import org.json.JSONObject; class TextSelection extends Layer implements GeckoEventListener { @@ -52,18 +52,18 @@ class TextSelection extends Layer implements GeckoEventListener { mViewLeft = 0.0f; mViewTop = 0.0f; mViewZoom = 0.0f; - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - if (layerClient != null) { - layerClient.getView().addLayer(TextSelection.this); + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + if (layerController != null) { + layerController.getView().addLayer(TextSelection.this); } } }); } else if (event.equals("TextSelection:HideHandles")) { GeckoApp.mAppContext.mMainHandler.post(new Runnable() { public void run() { - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - if (layerClient != null) { - layerClient.getView().removeLayer(TextSelection.this); + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + if (layerController != null) { + layerController.getView().removeLayer(TextSelection.this); } mStartHandle.setVisibility(View.GONE); diff --git a/mobile/android/base/TextSelectionHandle.java b/mobile/android/base/TextSelectionHandle.java index a493215f68d..6e8eedd6a4c 100644 --- a/mobile/android/base/TextSelectionHandle.java +++ b/mobile/android/base/TextSelectionHandle.java @@ -13,8 +13,8 @@ import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout; import android.widget.ImageView; -import org.mozilla.gecko.gfx.GeckoLayerClient; import org.mozilla.gecko.gfx.ImmutableViewportMetrics; +import org.mozilla.gecko.gfx.LayerController; import org.json.JSONObject; class TextSelectionHandle extends ImageView implements View.OnTouchListener { @@ -81,15 +81,15 @@ class TextSelectionHandle extends ImageView implements View.OnTouchListener { mLeft = mLeft + newX - mTouchStartX; mTop = mTop + newY - mTouchStartY; - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - if (layerClient == null) { - Log.e(LOGTAG, "Can't move selection because layerClient is null"); + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + if (layerController == null) { + Log.e(LOGTAG, "Can't move selection because layerController is null"); return; } // Send x coordinate on the right side of the start handle, left side of the end handle. float left = (float) mLeft + (mHandleType.equals(HandleType.START) ? mWidth - mShadow : mShadow); PointF geckoPoint = new PointF(left, (float) mTop); - geckoPoint = layerClient.convertViewPointToLayerPoint(geckoPoint); + geckoPoint = layerController.convertViewPointToLayerPoint(geckoPoint); JSONObject args = new JSONObject(); try { @@ -105,14 +105,14 @@ class TextSelectionHandle extends ImageView implements View.OnTouchListener { } void positionFromGecko(int left, int top) { - GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient(); - if (layerClient == null) { - Log.e(LOGTAG, "Can't position handle because layerClient is null"); + LayerController layerController = GeckoApp.mAppContext.getLayerController(); + if (layerController == null) { + Log.e(LOGTAG, "Can't position handle because layerController is null"); return; } mGeckoPoint = new PointF((float) left, (float) top); - ImmutableViewportMetrics metrics = layerClient.getViewportMetrics(); + ImmutableViewportMetrics metrics = layerController.getViewportMetrics(); repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor); } diff --git a/mobile/android/base/ZoomConstraints.java b/mobile/android/base/ZoomConstraints.java deleted file mode 100644 index 4829af05805..00000000000 --- a/mobile/android/base/ZoomConstraints.java +++ /dev/null @@ -1,46 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko; - -import org.json.JSONException; -import org.json.JSONObject; - -public final class ZoomConstraints { - private final boolean mAllowZoom; - private final float mDefaultZoom; - private final float mMinZoom; - private final float mMaxZoom; - - public ZoomConstraints() { - mAllowZoom = true; - mDefaultZoom = 0.0f; - mMinZoom = 0.0f; - mMaxZoom = 0.0f; - } - - ZoomConstraints(JSONObject message) throws JSONException { - mAllowZoom = message.getBoolean("allowZoom"); - mDefaultZoom = (float)message.getDouble("defaultZoom"); - mMinZoom = (float)message.getDouble("minZoom"); - mMaxZoom = (float)message.getDouble("maxZoom"); - } - - public final boolean getAllowZoom() { - return mAllowZoom; - } - - public final float getDefaultZoom() { - return mDefaultZoom; - } - - public final float getMinZoom() { - return mMinZoom; - } - - public final float getMaxZoom() { - return mMaxZoom; - } -} diff --git a/mobile/android/base/gfx/GeckoLayerClient.java b/mobile/android/base/gfx/GeckoLayerClient.java index 52658302d45..67e1c457357 100644 --- a/mobile/android/base/gfx/GeckoLayerClient.java +++ b/mobile/android/base/gfx/GeckoLayerClient.java @@ -10,29 +10,25 @@ import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.GeckoEventResponder; import org.mozilla.gecko.Tab; import org.mozilla.gecko.Tabs; -import org.mozilla.gecko.ZoomConstraints; -import org.mozilla.gecko.ui.PanZoomController; -import org.mozilla.gecko.ui.PanZoomTarget; -import org.mozilla.gecko.ui.SimpleScaleGestureDetector; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; -import android.graphics.Color; +import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; -import android.view.GestureDetector; +import android.view.View; import java.util.Map; import java.util.HashMap; -public class GeckoLayerClient - implements GeckoEventResponder, LayerView.Listener, PanZoomTarget -{ +public class GeckoLayerClient implements GeckoEventResponder, + LayerView.Listener { private static final String LOGTAG = "GeckoLayerClient"; + private LayerController mLayerController; private LayerRenderer mLayerRenderer; private boolean mLayerRendererInitialized; @@ -43,18 +39,11 @@ public class GeckoLayerClient private DisplayPortMetrics mReturnDisplayPort; private boolean mRecordDrawTimes; - private final DrawTimingQueue mDrawTimingQueue; + private DrawTimingQueue mDrawTimingQueue; private VirtualLayer mRootLayer; - /* The Gecko viewport as per the UI thread. Must be touched only on the UI thread. - * If any events being sent to Gecko that are relative to the Gecko viewport position, - * they must (a) be relative to this viewport, and (b) be sent on the UI thread to - * avoid races. As long as these two conditions are satisfied, and the events being - * sent to Gecko are processed in FIFO order, the events will properly be relative - * to the Gecko viewport position. Note that if Gecko updates its viewport independently, - * we get notified synchronously and also update this on the UI thread. - */ + /* The Gecko viewport as per the UI thread. Must be touched only on the UI thread. */ private ViewportMetrics mGeckoViewport; /* @@ -63,42 +52,17 @@ public class GeckoLayerClient */ private ImmutableViewportMetrics mFrameMetrics; + private String mLastCheckerboardColor; + /* Used by robocop for testing purposes */ private DrawListener mDrawListener; /* Used as a temporary ViewTransform by syncViewportInfo */ - private final ViewTransform mCurrentViewTransform; + private ViewTransform mCurrentViewTransform; /* This is written by the compositor thread and read by the UI thread. */ private volatile boolean mCompositorCreated; - private boolean mForceRedraw; - - /* The current viewport metrics. - * This is volatile so that we can read and write to it from different threads. - * We avoid synchronization to make getting the viewport metrics from - * the compositor as cheap as possible. The viewport is immutable so - * we don't need to worry about anyone mutating it while we're reading from it. - * Specifically: - * 1) reading mViewportMetrics from any thread is fine without synchronization - * 2) writing to mViewportMetrics requires synchronizing on the layer controller object - * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in - * case 1 above) you should always frist grab a local copy of the reference, and then use - * that because mViewportMetrics might get reassigned in between reading the different - * fields. */ - private volatile ImmutableViewportMetrics mViewportMetrics; - - private ZoomConstraints mZoomConstraints; - - private boolean mGeckoIsReady; - - /* The new color for the checkerboard. */ - private int mCheckerboardColor; - private boolean mCheckerboardShouldShowChecks; - - private final PanZoomController mPanZoomController; - private LayerView mView; - public GeckoLayerClient(Context context) { // we can fill these in with dummy values because they are always written // to before being read @@ -111,27 +75,16 @@ public class GeckoLayerClient mCurrentViewTransform = new ViewTransform(0, 0, 1); mCompositorCreated = false; - - mForceRedraw = true; - mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics()); - mZoomConstraints = new ZoomConstraints(); - mCheckerboardColor = Color.WHITE; - mCheckerboardShouldShowChecks = true; - - mPanZoomController = new PanZoomController(this); } - public void setView(LayerView v) { - mView = v; - mView.connect(this); - } + /** Attaches the root layer to the layer controller so that Gecko appears. */ + void setLayerController(LayerController layerController) { + LayerView view = layerController.getView(); - /** Attaches to root layer so that Gecko appears. */ - public void notifyGeckoReady() { - mGeckoIsReady = true; + mLayerController = layerController; - mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight())); - mLayerRenderer = new LayerRenderer(mView); + mRootLayer = new VirtualLayer(new IntSize(view.getWidth(), view.getHeight())); + mLayerRenderer = new LayerRenderer(view); GeckoAppShell.registerGeckoEventListener("Viewport:Update", this); GeckoAppShell.registerGeckoEventListener("Viewport:PageSize", this); @@ -139,8 +92,9 @@ public class GeckoLayerClient GeckoAppShell.registerGeckoEventListener("Checkerboard:Toggle", this); GeckoAppShell.registerGeckoEventListener("Preferences:Data", this); - mView.setListener(this); - mView.setLayerRenderer(mLayerRenderer); + view.setListener(this); + view.setLayerRenderer(mLayerRenderer); + layerController.setRoot(mRootLayer); sendResizeEventIfNecessary(true); @@ -151,7 +105,6 @@ public class GeckoLayerClient } public void destroy() { - mPanZoomController.destroy(); GeckoAppShell.unregisterGeckoEventListener("Viewport:Update", this); GeckoAppShell.unregisterGeckoEventListener("Viewport:PageSize", this); GeckoAppShell.unregisterGeckoEventListener("Viewport:CalculateDisplayPort", this); @@ -159,71 +112,17 @@ public class GeckoLayerClient GeckoAppShell.unregisterGeckoEventListener("Preferences:Data", this); } - /** - * Returns true if this client is fine with performing a redraw operation or false if it - * would prefer that the action didn't take place. - */ - private boolean getRedrawHint() { - if (mForceRedraw) { - mForceRedraw = false; - return true; - } - - if (!mPanZoomController.getRedrawHint()) { - return false; - } - - return DisplayPortCalculator.aboutToCheckerboard(mViewportMetrics, - mPanZoomController.getVelocityVector(), mDisplayPort); - } - - Layer getRoot() { - return mGeckoIsReady ? mRootLayer : null; - } - - public LayerView getView() { - return mView; - } - - public FloatSize getViewportSize() { - return mViewportMetrics.getSize(); - } - - /** - * The view calls this function to indicate that the viewport changed size. It must hold the - * monitor while calling it. - * - * TODO: Refactor this to use an interface. Expose that interface only to the view and not - * to the layer client. That way, the layer client won't be tempted to call this, which might - * result in an infinite loop. - */ - void setViewportSize(FloatSize size) { - ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); - viewportMetrics.setSize(size); - mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); - - if (mGeckoIsReady) { - // here we send gecko a resize message. The code in browser.js is responsible for - // picking up on that resize event, modifying the viewport as necessary, and informing - // us of the new viewport. - sendResizeEventIfNecessary(true); - // the following call also sends gecko a message, which will be processed after the resize - // message above has updated the viewport. this message ensures that if we have just put - // focus in a text field, we scroll the content so that the text field is in view. - GeckoAppShell.viewSizeChanged(); - } - } - - public PanZoomController getPanZoomController() { - return mPanZoomController; + DisplayPortMetrics getDisplayPort() { + return mDisplayPort; } /* Informs Gecko that the screen size has changed. */ private void sendResizeEventIfNecessary(boolean force) { DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); + View view = mLayerController.getView(); IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels); - IntSize newWindowSize = new IntSize(mView.getWidth(), mView.getHeight()); + IntSize newWindowSize = new IntSize(view.getWidth(), view.getHeight()); boolean screenSizeChanged = !mScreenSize.equals(newScreenSize); boolean windowSizeChanged = !mWindowSize.equals(newWindowSize); @@ -249,37 +148,30 @@ public class GeckoLayerClient GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Window:Resize", "")); } - /** Sets the current page rect. You must hold the monitor while calling this. */ - private void setPageRect(RectF rect, RectF cssRect) { - // Since the "rect" is always just a multiple of "cssRect" we don't need to - // check both; this function assumes that both "rect" and "cssRect" are relative - // the zoom factor in mViewportMetrics. - if (mViewportMetrics.getCssPageRect().equals(cssRect)) - return; - - ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); - viewportMetrics.setPageRect(rect, cssRect); - mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); - - // Page size is owned by the layer client, so no need to notify it of - // this change. - - post(new Runnable() { - public void run() { - mPanZoomController.pageRectUpdated(); - mView.requestRender(); - } - }); + public Bitmap getBitmap() { + return null; } - private void adjustViewport(DisplayPortMetrics displayPort) { - ImmutableViewportMetrics metrics = getViewportMetrics(); + void viewportSizeChanged() { + // here we send gecko a resize message. The code in browser.js is responsible for + // picking up on that resize event, modifying the viewport as necessary, and informing + // us of the new viewport. + sendResizeEventIfNecessary(true); + // the following call also sends gecko a message, which will be processed after the resize + // message above has updated the viewport. this message ensures that if we have just put + // focus in a text field, we scroll the content so that the text field is in view. + GeckoAppShell.viewSizeChanged(); + } + + void adjustViewport(DisplayPortMetrics displayPort) { + ImmutableViewportMetrics metrics = mLayerController.getViewportMetrics(); ViewportMetrics clampedMetrics = new ViewportMetrics(metrics); clampedMetrics.setViewport(clampedMetrics.getClampedViewport()); if (displayPort == null) { - displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector()); + displayPort = DisplayPortCalculator.calculate(metrics, + mLayerController.getPanZoomController().getVelocityVector()); } mDisplayPort = displayPort; @@ -292,17 +184,6 @@ public class GeckoLayerClient GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(clampedMetrics, displayPort)); } - /** Aborts any pan/zoom animation that is currently in progress. */ - private void abortPanZoomAnimation() { - if (mPanZoomController != null) { - post(new Runnable() { - public void run() { - mPanZoomController.abortAnimation(); - } - }); - } - } - /** * The different types of Viewport messages handled. All viewport events * expect a display-port to be returned, but can handle one not being @@ -316,9 +197,9 @@ public class GeckoLayerClient /** Viewport message handler. */ private void handleViewportMessage(JSONObject message, ViewportMessageType type) throws JSONException { ViewportMetrics messageMetrics = new ViewportMetrics(message); - synchronized (this) { + synchronized (mLayerController) { final ViewportMetrics newMetrics; - ImmutableViewportMetrics oldMetrics = getViewportMetrics(); + ImmutableViewportMetrics oldMetrics = mLayerController.getViewportMetrics(); switch (type) { default: @@ -326,7 +207,7 @@ public class GeckoLayerClient newMetrics = messageMetrics; // Keep the old viewport size newMetrics.setSize(oldMetrics.getSize()); - abortPanZoomAnimation(); + mLayerController.abortPanZoomAnimation(); break; case PAGE_SIZE: // adjust the page dimensions to account for differences in zoom @@ -338,13 +219,13 @@ public class GeckoLayerClient break; } - post(new Runnable() { + mLayerController.post(new Runnable() { public void run() { mGeckoViewport = newMetrics; } }); - setViewportMetrics(newMetrics); - mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null); + mLayerController.setViewportMetrics(newMetrics); + mDisplayPort = DisplayPortCalculator.calculate(mLayerController.getViewportMetrics(), null); } mReturnDisplayPort = mDisplayPort; } @@ -360,9 +241,9 @@ public class GeckoLayerClient ImmutableViewportMetrics newMetrics = new ImmutableViewportMetrics(new ViewportMetrics(message)); mReturnDisplayPort = DisplayPortCalculator.calculate(newMetrics, null); } else if ("Checkerboard:Toggle".equals(event)) { - mCheckerboardShouldShowChecks = message.getBoolean("value"); - mView.requestRender(); - Log.i(LOGTAG, "Showing checks: " + mCheckerboardShouldShowChecks); + boolean showChecks = message.getBoolean("value"); + mLayerController.setCheckerboardShowChecks(showChecks); + Log.i(LOGTAG, "Showing checks: " + showChecks); } else if ("Preferences:Data".equals(event)) { JSONArray jsonPrefs = message.getJSONArray("preferences"); Map prefValues = new HashMap(); @@ -405,21 +286,23 @@ public class GeckoLayerClient } } - boolean checkerboardShouldShowChecks() { - return mCheckerboardShouldShowChecks; + void geometryChanged() { + /* Let Gecko know if the screensize has changed */ + sendResizeEventIfNecessary(false); + if (mLayerController.getRedrawHint()) + adjustViewport(null); } - int getCheckerboardColor() { - return mCheckerboardColor; - } - - public void setCheckerboardColor(int newColor) { - mCheckerboardColor = newColor; - mView.requestRender(); - } - - public void setZoomConstraints(ZoomConstraints constraints) { - mZoomConstraints = constraints; + /* + * This function returns the last viewport that we sent to Gecko. If any additional events are + * being sent to Gecko that are relative on the Gecko viewport position, they must (a) be relative + * to this viewport, and (b) be sent on the UI thread to avoid races. As long as these two + * conditions are satisfied, and the events being sent to Gecko are processed in FIFO order, the + * events will properly be relative to the Gecko viewport position. Note that if Gecko updates + * its viewport independently, we get notified synchronously and also update this on the UI thread. + */ + public ViewportMetrics getGeckoViewportMetrics() { + return mGeckoViewport; } /** This function is invoked by Gecko via JNI; be careful when modifying signature. @@ -432,26 +315,29 @@ public class GeckoLayerClient public void setFirstPaintViewport(float offsetX, float offsetY, float zoom, float pageLeft, float pageTop, float pageRight, float pageBottom, float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) { - synchronized (this) { - final ViewportMetrics currentMetrics = new ViewportMetrics(getViewportMetrics()); + synchronized (mLayerController) { + final ViewportMetrics currentMetrics = new ViewportMetrics(mLayerController.getViewportMetrics()); currentMetrics.setOrigin(new PointF(offsetX, offsetY)); currentMetrics.setZoomFactor(zoom); currentMetrics.setPageRect(new RectF(pageLeft, pageTop, pageRight, pageBottom), new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom)); // Since we have switched to displaying a different document, we need to update any - // viewport-related state we have lying around. This includes mGeckoViewport and - // mViewportMetrics. Usually this information is updated via handleViewportMessage + // viewport-related state we have lying around. This includes mGeckoViewport and the + // viewport in mLayerController. Usually this information is updated via handleViewportMessage // while we remain on the same document. - post(new Runnable() { + mLayerController.post(new Runnable() { public void run() { mGeckoViewport = currentMetrics; } }); - setViewportMetrics(currentMetrics); + mLayerController.setViewportMetrics(currentMetrics); Tab tab = Tabs.getInstance().getSelectedTab(); - setCheckerboardColor(tab.getCheckerboardColor()); - setZoomConstraints(tab.getZoomConstraints()); + mLayerController.setCheckerboardColor(tab.getCheckerboardColor()); + mLayerController.setAllowZoom(tab.getAllowZoom()); + mLayerController.setDefaultZoom(tab.getDefaultZoom()); + mLayerController.setMinZoom(tab.getMinZoom()); + mLayerController.setMaxZoom(tab.getMaxZoom()); // At this point, we have just switched to displaying a different document than we // we previously displaying. This means we need to abort any panning/zooming animations @@ -460,12 +346,12 @@ public class GeckoLayerClient // sends the request after aborting the animation. The display port request is actually // a full viewport update, which is fine because if browser.js has somehow moved to // be out of sync with this first-paint viewport, then we force them back in sync. - abortPanZoomAnimation(); - mView.setPaintState(LayerView.PAINT_BEFORE_FIRST); + mLayerController.abortPanZoomAnimation(); + mLayerController.getView().setPaintState(LayerView.PAINT_BEFORE_FIRST); } DisplayPortCalculator.resetPageState(); mDrawTimingQueue.reset(); - mView.getRenderer().resetCheckerboard(); + mLayerController.getView().getRenderer().resetCheckerboard(); GeckoAppShell.screenshotWholePage(Tabs.getInstance().getSelectedTab()); } @@ -476,10 +362,10 @@ public class GeckoLayerClient * function will be invoked before syncViewportInfo. */ public void setPageRect(float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) { - synchronized (this) { + synchronized (mLayerController) { RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom); - float ourZoom = getViewportMetrics().zoomFactor; - setPageRect(RectUtils.scale(cssPageRect, ourZoom), cssPageRect); + float ourZoom = mLayerController.getZoomFactor(); + mLayerController.setPageRect(RectUtils.scale(cssPageRect, ourZoom), cssPageRect); // Here the page size of the document has changed, but the document being displayed // is still the same. Therefore, we don't need to send anything to browser.js; any // changes we need to make to the display port will get sent the next time we call @@ -497,13 +383,14 @@ public class GeckoLayerClient * which would avoid the copy into mCurrentViewTransform. */ public ViewTransform syncViewportInfo(int x, int y, int width, int height, float resolution, boolean layersUpdated) { - // getViewportMetrics is thread safe so we don't need to synchronize. + // getViewportMetrics is thread safe so we don't need to synchronize + // on mLayerController. // We save the viewport metrics here, so we later use it later in // createFrame (which will be called by nsWindow::DrawWindowUnderlay on - // the native side, by the compositor). The viewport + // the native side, by the compositor). The LayerController's viewport // metrics can change between here and there, as it's accessed outside // of the compositor thread. - mFrameMetrics = getViewportMetrics(); + mFrameMetrics = mLayerController.getViewportMetrics(); mCurrentViewTransform.x = mFrameMetrics.viewportRectLeft; mCurrentViewTransform.y = mFrameMetrics.viewportRectTop; @@ -554,14 +441,6 @@ public class GeckoLayerClient mLayerRenderer.deactivateDefaultProgram(); } - private void geometryChanged() { - /* Let Gecko know if the screensize has changed */ - sendResizeEventIfNecessary(false); - if (getRedrawHint()) { - adjustViewport(null); - } - } - /** Implementation of LayerView.Listener */ public void renderRequested() { GeckoAppShell.scheduleComposite(); @@ -596,7 +475,7 @@ public class GeckoLayerClient /** Implementation of LayerView.Listener */ public void surfaceChanged(int width, int height) { - setViewportSize(new FloatSize(width, height)); + mLayerController.setViewportSize(new FloatSize(width, height)); // We need to make this call even when the compositor isn't currently // paused (e.g. during an orientation change), to make the compositor @@ -610,89 +489,6 @@ public class GeckoLayerClient mCompositorCreated = true; } - /** Implementation of PanZoomTarget */ - public ImmutableViewportMetrics getViewportMetrics() { - return mViewportMetrics; - } - - /** Implementation of PanZoomTarget */ - public ZoomConstraints getZoomConstraints() { - return mZoomConstraints; - } - - /** Implementation of PanZoomTarget */ - public void setAnimationTarget(ViewportMetrics viewport) { - if (mGeckoIsReady) { - // We know what the final viewport of the animation is going to be, so - // immediately request a draw of that area by setting the display port - // accordingly. This way we should have the content pre-rendered by the - // time the animation is done. - ImmutableViewportMetrics metrics = new ImmutableViewportMetrics(viewport); - DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(metrics, null); - adjustViewport(displayPort); - } - } - - /** Implementation of PanZoomTarget - * You must hold the monitor while calling this. - */ - public void setViewportMetrics(ViewportMetrics viewport) { - mViewportMetrics = new ImmutableViewportMetrics(viewport); - mView.requestRender(); - if (mGeckoIsReady) { - geometryChanged(); - } - } - - /** Implementation of PanZoomTarget */ - public void setForceRedraw() { - mForceRedraw = true; - if (mGeckoIsReady) { - geometryChanged(); - } - } - - /** Implementation of PanZoomTarget */ - public boolean post(Runnable action) { - return mView.post(action); - } - - /** Implementation of PanZoomTarget */ - public Object getLock() { - return this; - } - - /** Implementation of PanZoomTarget - * Converts a point from layer view coordinates to layer coordinates. In other words, given a - * point measured in pixels from the top left corner of the layer view, returns the point in - * pixels measured from the last scroll position we sent to Gecko, in CSS pixels. Assuming the - * events being sent to Gecko are processed in FIFO order, this calculation should always be - * correct. - */ - public PointF convertViewPointToLayerPoint(PointF viewPoint) { - if (!mGeckoIsReady) { - return null; - } - - ImmutableViewportMetrics viewportMetrics = mViewportMetrics; - PointF origin = viewportMetrics.getOrigin(); - float zoom = viewportMetrics.zoomFactor; - ViewportMetrics geckoViewport = mGeckoViewport; - PointF geckoOrigin = geckoViewport.getOrigin(); - float geckoZoom = geckoViewport.getZoomFactor(); - - // viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page. - // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page. - // geckoOrigin / geckoZoom is where Gecko thinks it is (scrollTo position) in CSS pixels from - // the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from - // the current Gecko coordinate in CSS pixels. - PointF layerPoint = new PointF( - ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom), - ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom)); - - return layerPoint; - } - /** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */ public void setDrawListener(DrawListener listener) { mDrawListener = listener; @@ -703,3 +499,4 @@ public class GeckoLayerClient public void drawFinished(); } } + diff --git a/mobile/android/base/gfx/LayerController.java b/mobile/android/base/gfx/LayerController.java new file mode 100644 index 00000000000..7ceb68c0efe --- /dev/null +++ b/mobile/android/base/gfx/LayerController.java @@ -0,0 +1,360 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.gfx; + +import org.mozilla.gecko.ui.PanZoomController; +import org.mozilla.gecko.ui.SimpleScaleGestureDetector; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.PointF; +import android.graphics.RectF; +import android.view.GestureDetector; + +/** + * The layer controller manages a tile that represents the visible page. It does panning and + * zooming natively by delegating to a panning/zooming controller. Touch events can be dispatched + * to a higher-level view. + * + * Many methods require that the monitor be held, with a synchronized (controller) { ... } block. + */ +public class LayerController { + private static final String LOGTAG = "GeckoLayerController"; + + private Layer mRootLayer; /* The root layer. */ + private LayerView mView; /* The main rendering view. */ + private Context mContext; /* The current context. */ + + /* This is volatile so that we can read and write to it from different threads. + * We avoid synchronization to make getting the viewport metrics from + * the compositor as cheap as possible. The viewport is immutable so + * we don't need to worry about anyone mutating it while we're reading from it. + * Specifically: + * 1) reading mViewportMetrics from any thread is fine without synchronization + * 2) writing to mViewportMetrics requires synchronizing on the layer controller object + * 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in + * case 1 above) you should always frist grab a local copy of the reference, and then use + * that because mViewportMetrics might get reassigned in between reading the different + * fields. */ + private volatile ImmutableViewportMetrics mViewportMetrics; /* The current viewport metrics. */ + + /* + * The panning and zooming controller, which interprets pan and zoom gestures for us and + * updates our visible rect appropriately. + */ + private PanZoomController mPanZoomController; + + private GeckoLayerClient mLayerClient; /* The layer client. */ + + /* The new color for the checkerboard. */ + private int mCheckerboardColor = Color.WHITE; + private boolean mCheckerboardShouldShowChecks; + + private boolean mAllowZoom; + private float mDefaultZoom; + private float mMinZoom; + private float mMaxZoom; + + private boolean mForceRedraw; + + public LayerController(Context context) { + mContext = context; + + mForceRedraw = true; + mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics()); + mPanZoomController = new PanZoomController(this); + mCheckerboardShouldShowChecks = true; + } + + public void setView(LayerView v) { + mView = v; + mView.connect(this); + } + + public void setRoot(Layer layer) { mRootLayer = layer; } + + public void setLayerClient(GeckoLayerClient layerClient) { + mLayerClient = layerClient; + layerClient.setLayerController(this); + } + + public void destroy() { + mPanZoomController.destroy(); + } + + public void setForceRedraw() { + mForceRedraw = true; + } + + public Layer getRoot() { return mRootLayer; } + public LayerView getView() { return mView; } + public Context getContext() { return mContext; } + public ImmutableViewportMetrics getViewportMetrics() { return mViewportMetrics; } + + public RectF getViewport() { + return mViewportMetrics.getViewport(); + } + + public RectF getCssViewport() { + return mViewportMetrics.getCssViewport(); + } + + public FloatSize getViewportSize() { + return mViewportMetrics.getSize(); + } + + public RectF getPageRect() { + return mViewportMetrics.getPageRect(); + } + + public RectF getCssPageRect() { + return mViewportMetrics.getCssPageRect(); + } + + public PointF getOrigin() { + return mViewportMetrics.getOrigin(); + } + + public float getZoomFactor() { + return mViewportMetrics.zoomFactor; + } + + public Bitmap getBackgroundPattern() { return getDrawable("tabs_tray_selected_bg"); } + public Bitmap getShadowPattern() { return getDrawable("shadow"); } + + public PanZoomController getPanZoomController() { return mPanZoomController; } + public GestureDetector.OnGestureListener getGestureListener() { return mPanZoomController; } + public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() { + return mPanZoomController; + } + public GestureDetector.OnDoubleTapListener getDoubleTapListener() { return mPanZoomController; } + + private Bitmap getDrawable(String name) { + Resources resources = mContext.getResources(); + int resourceID = resources.getIdentifier(name, "drawable", mContext.getPackageName()); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + return BitmapFactory.decodeResource(mContext.getResources(), resourceID, options); + } + + /** + * The view calls this function to indicate that the viewport changed size. It must hold the + * monitor while calling it. + * + * TODO: Refactor this to use an interface. Expose that interface only to the view and not + * to the layer client. That way, the layer client won't be tempted to call this, which might + * result in an infinite loop. + */ + public void setViewportSize(FloatSize size) { + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + viewportMetrics.setSize(size); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); + + if (mLayerClient != null) { + mLayerClient.viewportSizeChanged(); + } + } + + /** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */ + public void scrollBy(PointF point) { + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + PointF origin = viewportMetrics.getOrigin(); + origin.offset(point.x, point.y); + viewportMetrics.setOrigin(origin); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); + + notifyLayerClientOfGeometryChange(); + mView.requestRender(); + } + + /** Sets the current page rect. You must hold the monitor while calling this. */ + public void setPageRect(RectF rect, RectF cssRect) { + // Since the "rect" is always just a multiple of "cssRect" we don't need to + // check both; this function assumes that both "rect" and "cssRect" are relative + // the zoom factor in mViewportMetrics. + if (mViewportMetrics.getCssPageRect().equals(cssRect)) + return; + + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + viewportMetrics.setPageRect(rect, cssRect); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); + + // Page size is owned by the layer client, so no need to notify it of + // this change. + + mView.post(new Runnable() { + public void run() { + mPanZoomController.pageRectUpdated(); + mView.requestRender(); + } + }); + } + + /** + * Sets the entire viewport metrics at once. This function does not notify the layer client or + * the pan/zoom controller, so you will need to call notifyLayerClientOfGeometryChange() or + * notifyPanZoomControllerOfGeometryChange() after calling this. You must hold the monitor + * while calling this. + */ + public void setViewportMetrics(ViewportMetrics viewport) { + mViewportMetrics = new ImmutableViewportMetrics(viewport); + mView.requestRender(); + } + + public void setAnimationTarget(ViewportMetrics viewport) { + if (mLayerClient != null) { + // We know what the final viewport of the animation is going to be, so + // immediately request a draw of that area by setting the display port + // accordingly. This way we should have the content pre-rendered by the + // time the animation is done. + ImmutableViewportMetrics metrics = new ImmutableViewportMetrics(viewport); + DisplayPortMetrics displayPort = DisplayPortCalculator.calculate(metrics, null); + mLayerClient.adjustViewport(displayPort); + } + } + + /** + * Scales the viewport, keeping the given focus point in the same place before and after the + * scale operation. You must hold the monitor while calling this. + */ + public void scaleWithFocus(float zoomFactor, PointF focus) { + ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics); + viewportMetrics.scaleTo(zoomFactor, focus); + mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics); + + // We assume the zoom level will only be modified by the + // PanZoomController, so no need to notify it of this change. + notifyLayerClientOfGeometryChange(); + mView.requestRender(); + } + + public boolean post(Runnable action) { return mView.post(action); } + + /** + * The view as well as the controller itself use this method to notify the layer client that + * the geometry changed. + */ + public void notifyLayerClientOfGeometryChange() { + if (mLayerClient != null) + mLayerClient.geometryChanged(); + } + + /** Aborts any pan/zoom animation that is currently in progress. */ + public void abortPanZoomAnimation() { + if (mPanZoomController != null) { + mView.post(new Runnable() { + public void run() { + mPanZoomController.abortAnimation(); + } + }); + } + } + + /** + * Returns true if this controller is fine with performing a redraw operation or false if it + * would prefer that the action didn't take place. + */ + public boolean getRedrawHint() { + if (mForceRedraw) { + mForceRedraw = false; + return true; + } + + if (!mPanZoomController.getRedrawHint()) { + return false; + } + + return DisplayPortCalculator.aboutToCheckerboard(mViewportMetrics, + mPanZoomController.getVelocityVector(), mLayerClient.getDisplayPort()); + } + + /** + * Converts a point from layer view coordinates to layer coordinates. In other words, given a + * point measured in pixels from the top left corner of the layer view, returns the point in + * pixels measured from the last scroll position we sent to Gecko, in CSS pixels. Assuming the + * events being sent to Gecko are processed in FIFO order, this calculation should always be + * correct. + */ + public PointF convertViewPointToLayerPoint(PointF viewPoint) { + if (mLayerClient == null) { + return null; + } + + ImmutableViewportMetrics viewportMetrics = mViewportMetrics; + PointF origin = viewportMetrics.getOrigin(); + float zoom = viewportMetrics.zoomFactor; + ViewportMetrics geckoViewport = mLayerClient.getGeckoViewportMetrics(); + PointF geckoOrigin = geckoViewport.getOrigin(); + float geckoZoom = geckoViewport.getZoomFactor(); + + // viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page. + // Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page. + // geckoOrigin / geckoZoom is where Gecko thinks it is (scrollTo position) in CSS pixels from + // the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from + // the current Gecko coordinate in CSS pixels. + PointF layerPoint = new PointF( + ((viewPoint.x + origin.x) / zoom) - (geckoOrigin.x / geckoZoom), + ((viewPoint.y + origin.y) / zoom) - (geckoOrigin.y / geckoZoom)); + + return layerPoint; + } + + /** Retrieves whether we should show checkerboard checks or not. */ + public boolean checkerboardShouldShowChecks() { + return mCheckerboardShouldShowChecks; + } + + /** Retrieves the color that the checkerboard should be. */ + public int getCheckerboardColor() { + return mCheckerboardColor; + } + + /** Sets whether or not the checkerboard should show checkmarks. */ + public void setCheckerboardShowChecks(boolean showChecks) { + mCheckerboardShouldShowChecks = showChecks; + mView.requestRender(); + } + + /** Sets a new color for the checkerboard. */ + public void setCheckerboardColor(int newColor) { + mCheckerboardColor = newColor; + mView.requestRender(); + } + + public void setAllowZoom(final boolean aValue) { + mAllowZoom = aValue; + } + + public boolean getAllowZoom() { + return mAllowZoom; + } + + public void setDefaultZoom(float aValue) { + mDefaultZoom = aValue; + } + + public float getDefaultZoom() { + return mDefaultZoom; + } + + public void setMinZoom(float aValue) { + mMinZoom = aValue; + } + + public float getMinZoom() { + return mMinZoom; + } + + public void setMaxZoom(float aValue) { + mMaxZoom = aValue; + } + + public float getMaxZoom() { + return mMaxZoom; + } +} diff --git a/mobile/android/base/gfx/LayerRenderer.java b/mobile/android/base/gfx/LayerRenderer.java index d5deac44790..f94fe2a56cc 100644 --- a/mobile/android/base/gfx/LayerRenderer.java +++ b/mobile/android/base/gfx/LayerRenderer.java @@ -143,12 +143,14 @@ public class LayerRenderer { public LayerRenderer(LayerView view) { mView = view; - CairoImage backgroundImage = new BufferedCairoImage(view.getBackgroundPattern()); + LayerController controller = view.getController(); + + CairoImage backgroundImage = new BufferedCairoImage(controller.getBackgroundPattern()); mBackgroundLayer = new SingleTileLayer(true, backgroundImage); mCheckerboardLayer = ScreenshotLayer.create(); - CairoImage shadowImage = new BufferedCairoImage(view.getShadowPattern()); + CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern()); mShadowLayer = new NinePatchTileLayer(shadowImage); mHorizScrollLayer = ScrollbarLayer.create(this, false); @@ -445,7 +447,7 @@ public class LayerRenderer { mUpdated = true; - Layer rootLayer = mView.getLayerClient().getRoot(); + Layer rootLayer = mView.getController().getRoot(); if (!mPageContext.fuzzyEquals(mLastPageContext)) { // the viewport or page changed, so show the scrollbars again @@ -518,7 +520,7 @@ public class LayerRenderer { GLES20.glDisable(GLES20.GL_SCISSOR_TEST); /* Update background color. */ - mBackgroundColor = mView.getLayerClient().getCheckerboardColor(); + mBackgroundColor = mView.getController().getCheckerboardColor(); /* Clear to the page background colour. The bits set here need to * match up with those used in gfx/layers/opengl/LayerManagerOGL.cpp. @@ -537,15 +539,15 @@ public class LayerRenderer { /* Draw the drop shadow, if we need to. */ RectF untransformedPageRect = new RectF(0.0f, 0.0f, mPageRect.width(), mPageRect.height()); - if (!untransformedPageRect.contains(mFrameMetrics.getViewport())) + if (!untransformedPageRect.contains(mView.getController().getViewport())) mShadowLayer.draw(mPageContext); /* Draw the 'checkerboard'. We use gfx.show_checkerboard_pattern to * determine whether to draw the screenshot layer. */ - if (mView.getLayerClient().checkerboardShouldShowChecks()) { + if (mView.getController().checkerboardShouldShowChecks()) { /* Find the area the root layer will render into, to mask the checkerboard layer */ - Rect rootMask = getMaskForLayer(mView.getLayerClient().getRoot()); + Rect rootMask = getMaskForLayer(mView.getController().getRoot()); mCheckerboardLayer.setMask(rootMask); /* Scissor around the page-rect, in case the page has shrunk @@ -558,7 +560,7 @@ public class LayerRenderer { // Draws the layer the client added to us. void drawRootLayer() { - Layer rootLayer = mView.getLayerClient().getRoot(); + Layer rootLayer = mView.getController().getRoot(); if (rootLayer == null) { return; } @@ -590,7 +592,7 @@ public class LayerRenderer { mHorizScrollLayer.draw(mPageContext); /* Measure how much of the screen is checkerboarding */ - Layer rootLayer = mView.getLayerClient().getRoot(); + Layer rootLayer = mView.getController().getRoot(); if ((rootLayer != null) && (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) { // Find out how much of the viewport area is valid diff --git a/mobile/android/base/gfx/LayerView.java b/mobile/android/base/gfx/LayerView.java index bb33af5f38a..c54c61d3018 100644 --- a/mobile/android/base/gfx/LayerView.java +++ b/mobile/android/base/gfx/LayerView.java @@ -9,7 +9,6 @@ import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoInputConnection; import android.content.Context; -import android.content.res.Resources; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; @@ -19,8 +18,6 @@ import android.view.TextureView; import android.widget.FrameLayout; import android.util.AttributeSet; import android.util.Log; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.PixelFormat; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -32,12 +29,15 @@ import java.nio.IntBuffer; /** * A view rendered by the layer compositor. * + * This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a + * mediator between the LayerRenderer and the LayerController. + * * Note that LayerView is accessed by Robocop via reflection. */ public class LayerView extends FrameLayout { private static String LOGTAG = "GeckoLayerView"; - private GeckoLayerClient mLayerClient; + private LayerController mController; private TouchEventHandler mTouchEventHandler; private GLController mGLController; private InputConnectionHandler mInputConnectionHandler; @@ -76,9 +76,9 @@ public class LayerView extends FrameLayout { mGLController = new GLController(this); } - void connect(GeckoLayerClient layerClient) { - mLayerClient = layerClient; - mTouchEventHandler = new TouchEventHandler(getContext(), this, layerClient); + void connect(LayerController controller) { + mController = controller; + mTouchEventHandler = new TouchEventHandler(getContext(), this, mController); mRenderer = new LayerRenderer(this); mInputConnectionHandler = null; @@ -103,12 +103,12 @@ public class LayerView extends FrameLayout { return mTouchEventHandler.handleEvent(event); } - public GeckoLayerClient getLayerClient() { return mLayerClient; } + public LayerController getController() { return mController; } public TouchEventHandler getTouchEventHandler() { return mTouchEventHandler; } /** The LayerRenderer calls this to indicate that the window has changed size. */ public void setViewportSize(IntSize size) { - mLayerClient.setViewportSize(new FloatSize(size)); + mController.setViewportSize(new FloatSize(size)); } public GeckoInputConnection setInputConnectionHandler() { @@ -220,23 +220,6 @@ public class LayerView extends FrameLayout { return mGLController; } - private Bitmap getDrawable(String name) { - Context context = getContext(); - Resources resources = context.getResources(); - int resourceID = resources.getIdentifier(name, "drawable", context.getPackageName()); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - return BitmapFactory.decodeResource(context.getResources(), resourceID, options); - } - - Bitmap getBackgroundPattern() { - return getDrawable("tabs_tray_selected_bg"); - } - - Bitmap getShadowPattern() { - return getDrawable("shadow"); - } - private void onSizeChanged(int width, int height) { mGLController.surfaceChanged(width, height); @@ -263,7 +246,7 @@ public class LayerView extends FrameLayout { /** This function is invoked by Gecko (compositor thread) via JNI; be careful when modifying signature. */ public static GLController registerCxxCompositor() { try { - LayerView layerView = GeckoApp.mAppContext.getLayerClient().getView(); + LayerView layerView = GeckoApp.mAppContext.getLayerController().getView(); layerView.mListener.compositorCreated(); return layerView.getGLController(); } catch (Exception e) { diff --git a/mobile/android/base/gfx/TouchEventHandler.java b/mobile/android/base/gfx/TouchEventHandler.java index b367f8b4b18..b66fe558de2 100644 --- a/mobile/android/base/gfx/TouchEventHandler.java +++ b/mobile/android/base/gfx/TouchEventHandler.java @@ -124,17 +124,17 @@ public final class TouchEventHandler implements Tabs.OnTabsChangedListener { // processed. (n is the absolute value of the balance.) private int mProcessingBalance; - TouchEventHandler(Context context, LayerView view, GeckoLayerClient layerClient) { + TouchEventHandler(Context context, LayerView view, LayerController controller) { mView = view; mEventQueue = new LinkedList(); - mPanZoomController = layerClient.getPanZoomController(); - mGestureDetector = new GestureDetector(context, mPanZoomController); - mScaleGestureDetector = new SimpleScaleGestureDetector(mPanZoomController); + mGestureDetector = new GestureDetector(context, controller.getGestureListener()); + mScaleGestureDetector = new SimpleScaleGestureDetector(controller.getScaleGestureListener()); + mPanZoomController = controller.getPanZoomController(); mListenerTimeoutProcessor = new ListenerTimeoutProcessor(); mDispatchEvents = true; - mGestureDetector.setOnDoubleTapListener(mPanZoomController); + mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener()); Tabs.registerOnTabsChangedListener(this); } diff --git a/mobile/android/base/tests/MotionEventReplayer.java.in b/mobile/android/base/tests/MotionEventReplayer.java.in index 63651a20545..9d6717bb554 100644 --- a/mobile/android/base/tests/MotionEventReplayer.java.in +++ b/mobile/android/base/tests/MotionEventReplayer.java.in @@ -79,7 +79,7 @@ class MotionEventReplayer { // edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=21972329, // downTime=21972329, deviceId=6, source=0x1002 } // - // These can be generated by printing out event.toString() in LayerView's + // These can be generated by printing out event.toString() in LayerController's // onTouchEvent function on a phone running Ice Cream Sandwich. Different // Android versions have different serializations of the motion event, and this // code could probably be modified to parse other serializations if needed. diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 463a2cf7b0e..e9d61bf7bdb 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -9,7 +9,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.mozilla.gecko.gfx.ImmutableViewportMetrics; +import org.mozilla.gecko.gfx.LayerController; import org.mozilla.gecko.gfx.PointUtils; import org.mozilla.gecko.gfx.ViewportMetrics; import org.mozilla.gecko.FloatUtils; @@ -17,7 +17,6 @@ import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoEvent; import org.mozilla.gecko.GeckoEventListener; -import org.mozilla.gecko.ZoomConstraints; import android.graphics.PointF; import android.graphics.RectF; import android.util.FloatMath; @@ -104,7 +103,7 @@ public class PanZoomController prevented the default actions yet. we still need to abort animations. */ } - private final PanZoomTarget mTarget; + private final LayerController mController; private final SubdocumentScrollHelper mSubscroller; private final Axis mX; private final Axis mY; @@ -122,8 +121,8 @@ public class PanZoomController /* Current state the pan/zoom UI is in. */ private PanZoomState mState; - public PanZoomController(PanZoomTarget target) { - mTarget = target; + public PanZoomController(LayerController controller) { + mController = controller; mSubscroller = new SubdocumentScrollHelper(this); mX = new AxisX(mSubscroller); mY = new AxisY(mSubscroller); @@ -154,14 +153,6 @@ public class PanZoomController mState = state; } - private ImmutableViewportMetrics getMetrics() { - return mTarget.getViewportMetrics(); - } - - private ViewportMetrics getMutableMetrics() { - return new ViewportMetrics(getMetrics()); - } - // for debugging bug 713011; it can be taken out once that is resolved. private void checkMainThread() { if (mMainThread != Thread.currentThread()) { @@ -178,16 +169,15 @@ public class PanZoomController final RectF zoomRect = new RectF(x, y, x + (float)message.getDouble("w"), y + (float)message.getDouble("h")); - mTarget.post(new Runnable() { + mController.post(new Runnable() { public void run() { animatedZoomTo(zoomRect); } }); } else if (MESSAGE_ZOOM_PAGE.equals(event)) { - ImmutableViewportMetrics metrics = getMetrics(); - RectF cssPageRect = metrics.getCssPageRect(); + RectF cssPageRect = mController.getCssPageRect(); - RectF viewableRect = metrics.getCssViewport(); + RectF viewableRect = mController.getCssViewport(); float y = viewableRect.top; // attempt to keep zoom keep focused on the center of the viewport float newHeight = viewableRect.height() * cssPageRect.width() / viewableRect.width(); @@ -196,7 +186,7 @@ public class PanZoomController y + dh/2, cssPageRect.width(), y + dh/2 + newHeight); - mTarget.post(new Runnable() { + mController.post(new Runnable() { public void run() { animatedZoomTo(r); } @@ -281,8 +271,9 @@ public class PanZoomController case NOTHING: // Don't do animations here; they're distracting and can cause flashes on page // transitions. - synchronized (mTarget.getLock()) { - mTarget.setViewportMetrics(getValidViewportMetrics()); + synchronized (mController) { + mController.setViewportMetrics(getValidViewportMetrics()); + mController.notifyLayerClientOfGeometryChange(); } break; } @@ -314,12 +305,13 @@ public class PanZoomController /** This must be called on the UI thread. */ public void pageRectUpdated() { if (mState == PanZoomState.NOTHING) { - synchronized (mTarget.getLock()) { + synchronized (mController) { ViewportMetrics validated = getValidViewportMetrics(); - if (! getMutableMetrics().fuzzyEquals(validated)) { + if (! (new ViewportMetrics(mController.getViewportMetrics())).fuzzyEquals(validated)) { // page size changed such that we are now in overscroll. snap to the // the nearest valid viewport - mTarget.setViewportMetrics(validated); + mController.setViewportMetrics(validated); + mController.notifyLayerClientOfGeometryChange(); } } } @@ -339,7 +331,8 @@ public class PanZoomController // We just interrupted a double-tap animation, so force a redraw in // case this touchstart is just a tap that doesn't end up triggering // a redraw - mTarget.setForceRedraw(); + mController.setForceRedraw(); + mController.notifyLayerClientOfGeometryChange(); // fall through case FLING: case BOUNCE: @@ -538,15 +531,6 @@ public class PanZoomController updatePosition(); } - private void scrollBy(PointF point) { - ViewportMetrics viewportMetrics = getMutableMetrics(); - PointF origin = viewportMetrics.getOrigin(); - origin.offset(point.x, point.y); - viewportMetrics.setOrigin(origin); - - mTarget.setViewportMetrics(viewportMetrics); - } - private void fling() { updatePosition(); @@ -563,7 +547,7 @@ public class PanZoomController private void bounce(ViewportMetrics metrics) { stopAnimationTimer(); - ViewportMetrics bounceStartMetrics = getMutableMetrics(); + ViewportMetrics bounceStartMetrics = new ViewportMetrics(mController.getViewportMetrics()); if (bounceStartMetrics.fuzzyEquals(metrics)) { setState(PanZoomState.NOTHING); return; @@ -573,7 +557,7 @@ public class PanZoomController // getRedrawHint() is returning false. This means we can safely call // setAnimationTarget to set the new final display port and not have it get // clobbered by display ports from intermediate animation frames. - mTarget.setAnimationTarget(metrics); + mController.setAnimationTarget(metrics); startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics)); } @@ -594,7 +578,7 @@ public class PanZoomController mAnimationRunnable = runnable; mAnimationTimer.scheduleAtFixedRate(new TimerTask() { @Override - public void run() { mTarget.post(runnable); } + public void run() { mController.post(runnable); } }, 0, 1000L/60L); } @@ -636,8 +620,8 @@ public class PanZoomController return; } if (! mSubscroller.scrollBy(displacement)) { - synchronized (mTarget.getLock()) { - scrollBy(displacement); + synchronized (mController) { + mController.scrollBy(displacement); } } } @@ -708,18 +692,20 @@ public class PanZoomController /* Performs one frame of a bounce animation. */ private void advanceBounce() { - synchronized (mTarget.getLock()) { + synchronized (mController) { float t = ZOOM_ANIMATION_FRAMES[mBounceFrame]; ViewportMetrics newMetrics = mBounceStartMetrics.interpolate(mBounceEndMetrics, t); - mTarget.setViewportMetrics(newMetrics); + mController.setViewportMetrics(newMetrics); + mController.notifyLayerClientOfGeometryChange(); mBounceFrame++; } } /* Concludes a bounce animation and snaps the viewport into place. */ private void finishBounce() { - synchronized (mTarget.getLock()) { - mTarget.setViewportMetrics(mBounceEndMetrics); + synchronized (mController) { + mController.setViewportMetrics(mBounceEndMetrics); + mController.notifyLayerClientOfGeometryChange(); mBounceFrame = -1; } } @@ -780,12 +766,13 @@ public class PanZoomController stopAnimationTimer(); // Force a viewport synchronisation - mTarget.setForceRedraw(); + mController.setForceRedraw(); + mController.notifyLayerClientOfGeometryChange(); } /* Returns the nearest viewport metrics with no overscroll visible. */ private ViewportMetrics getValidViewportMetrics() { - return getValidViewportMetrics(getMutableMetrics()); + return getValidViewportMetrics(new ViewportMetrics(mController.getViewportMetrics())); } private ViewportMetrics getValidViewportMetrics(ViewportMetrics viewportMetrics) { @@ -800,16 +787,14 @@ public class PanZoomController float minZoomFactor = 0.0f; float maxZoomFactor = MAX_ZOOM; - ZoomConstraints constraints = mTarget.getZoomConstraints(); + if (mController.getMinZoom() > 0) + minZoomFactor = mController.getMinZoom(); + if (mController.getMaxZoom() > 0) + maxZoomFactor = mController.getMaxZoom(); - if (constraints.getMinZoom() > 0) - minZoomFactor = constraints.getMinZoom(); - if (constraints.getMaxZoom() > 0) - maxZoomFactor = constraints.getMaxZoom(); - - if (!constraints.getAllowZoom()) { + if (!mController.getAllowZoom()) { // If allowZoom is false, clamp to the default zoom level. - maxZoomFactor = minZoomFactor = constraints.getDefaultZoom(); + maxZoomFactor = minZoomFactor = mController.getDefaultZoom(); } // Ensure minZoomFactor keeps the page at least as big as the viewport. @@ -850,25 +835,25 @@ public class PanZoomController private class AxisX extends Axis { AxisX(SubdocumentScrollHelper subscroller) { super(subscroller); } @Override - public float getOrigin() { return getMetrics().viewportRectLeft; } + public float getOrigin() { return mController.getOrigin().x; } @Override - protected float getViewportLength() { return getMetrics().getWidth(); } + protected float getViewportLength() { return mController.getViewportSize().width; } @Override - protected float getPageStart() { return getMetrics().pageRectLeft; } + protected float getPageStart() { return mController.getPageRect().left; } @Override - protected float getPageLength() { return getMetrics().getPageWidth(); } + protected float getPageLength() { return mController.getPageRect().width(); } } private class AxisY extends Axis { AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); } @Override - public float getOrigin() { return getMetrics().viewportRectTop; } + public float getOrigin() { return mController.getOrigin().y; } @Override - protected float getViewportLength() { return getMetrics().getHeight(); } + protected float getViewportLength() { return mController.getViewportSize().height; } @Override - protected float getPageStart() { return getMetrics().pageRectTop; } + protected float getPageStart() { return mController.getPageRect().top; } @Override - protected float getPageLength() { return getMetrics().getPageHeight(); } + protected float getPageLength() { return mController.getPageRect().height(); } } /* @@ -879,7 +864,7 @@ public class PanZoomController if (mState == PanZoomState.ANIMATED_ZOOM) return false; - if (!mTarget.getZoomConstraints().getAllowZoom()) + if (!mController.getAllowZoom()) return false; setState(PanZoomState.PINCHING); @@ -915,17 +900,15 @@ public class PanZoomController else spanRatio = 1.0f - (1.0f - spanRatio) * resistance; - synchronized (mTarget.getLock()) { - float newZoomFactor = getMetrics().zoomFactor * spanRatio; + synchronized (mController) { + float newZoomFactor = mController.getZoomFactor() * spanRatio; float minZoomFactor = 0.0f; float maxZoomFactor = MAX_ZOOM; - ZoomConstraints constraints = mTarget.getZoomConstraints(); - - if (constraints.getMinZoom() > 0) - minZoomFactor = constraints.getMinZoom(); - if (constraints.getMaxZoom() > 0) - maxZoomFactor = constraints.getMaxZoom(); + if (mController.getMinZoom() > 0) + minZoomFactor = mController.getMinZoom(); + if (mController.getMaxZoom() > 0) + maxZoomFactor = mController.getMaxZoom(); if (newZoomFactor < minZoomFactor) { // apply resistance when zooming past minZoomFactor, @@ -946,10 +929,10 @@ public class PanZoomController newZoomFactor = maxZoomFactor + excessZoom; } - scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(), - mLastZoomFocus.y - detector.getFocusY())); + mController.scrollBy(new PointF(mLastZoomFocus.x - detector.getFocusX(), + mLastZoomFocus.y - detector.getFocusY())); PointF focus = new PointF(detector.getFocusX(), detector.getFocusY()); - scaleWithFocus(newZoomFactor, focus); + mController.scaleWithFocus(newZoomFactor, focus); } mLastZoomFocus.set(detector.getFocusX(), detector.getFocusY()); @@ -966,17 +949,8 @@ public class PanZoomController startTouch(detector.getFocusX(), detector.getFocusY(), detector.getEventTime()); // Force a viewport synchronisation - mTarget.setForceRedraw(); - } - - /** - * Scales the viewport, keeping the given focus point in the same place before and after the - * scale operation. You must hold the monitor while calling this. - */ - private void scaleWithFocus(float zoomFactor, PointF focus) { - ViewportMetrics viewportMetrics = getMutableMetrics(); - viewportMetrics.scaleTo(zoomFactor, focus); - mTarget.setViewportMetrics(viewportMetrics); + mController.setForceRedraw(); + mController.notifyLayerClientOfGeometryChange(); } public boolean getRedrawHint() { @@ -998,7 +972,7 @@ public class PanZoomController String json; try { PointF point = new PointF(motionEvent.getX(), motionEvent.getY()); - point = mTarget.convertViewPointToLayerPoint(point); + point = mController.convertViewPointToLayerPoint(point); if (point == null) { return; } @@ -1019,7 +993,7 @@ public class PanZoomController @Override public boolean onSingleTapUp(MotionEvent motionEvent) { // When zooming is enabled, wait to see if there's a double-tap. - if (!mTarget.getZoomConstraints().getAllowZoom()) { + if (!mController.getAllowZoom()) { sendPointToGecko("Gesture:SingleTap", motionEvent); } // return false because we still want to get the ACTION_UP event that triggers this @@ -1029,7 +1003,7 @@ public class PanZoomController @Override public boolean onSingleTapConfirmed(MotionEvent motionEvent) { // When zooming is disabled, we handle this in onSingleTapUp. - if (mTarget.getZoomConstraints().getAllowZoom()) { + if (mController.getAllowZoom()) { sendPointToGecko("Gesture:SingleTap", motionEvent); } return true; @@ -1037,7 +1011,7 @@ public class PanZoomController @Override public boolean onDoubleTap(MotionEvent motionEvent) { - if (mTarget.getZoomConstraints().getAllowZoom()) { + if (mController.getAllowZoom()) { sendPointToGecko("Gesture:DoubleTap", motionEvent); } return true; @@ -1056,9 +1030,9 @@ public class PanZoomController */ private boolean animatedZoomTo(RectF zoomToRect) { setState(PanZoomState.ANIMATED_ZOOM); - final float startZoom = getMetrics().zoomFactor; + final float startZoom = mController.getZoomFactor(); - RectF viewport = getMetrics().getViewport(); + RectF viewport = mController.getViewport(); // 1. adjust the aspect ratio of zoomToRect to match that of the current viewport, // enlarging as necessary (if it gets too big, it will get shrunk in the next step). // while enlarging make sure we enlarge equally on both sides to keep the target rect @@ -1081,7 +1055,7 @@ public class PanZoomController float finalZoom = viewport.width() / zoomToRect.width(); - ViewportMetrics finalMetrics = getMutableMetrics(); + ViewportMetrics finalMetrics = new ViewportMetrics(mController.getViewportMetrics()); finalMetrics.setOrigin(new PointF(zoomToRect.left * finalMetrics.getZoomFactor(), zoomToRect.top * finalMetrics.getZoomFactor())); finalMetrics.scaleTo(finalZoom, new PointF(0.0f, 0.0f)); diff --git a/mobile/android/base/ui/PanZoomTarget.java b/mobile/android/base/ui/PanZoomTarget.java deleted file mode 100644 index ac4b6c48e9e..00000000000 --- a/mobile/android/base/ui/PanZoomTarget.java +++ /dev/null @@ -1,25 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.ui; - -import org.mozilla.gecko.ZoomConstraints; -import org.mozilla.gecko.gfx.ImmutableViewportMetrics; -import org.mozilla.gecko.gfx.ViewportMetrics; - -import android.graphics.PointF; - -public interface PanZoomTarget { - public ImmutableViewportMetrics getViewportMetrics(); - public ZoomConstraints getZoomConstraints(); - - public void setAnimationTarget(ViewportMetrics viewport); - public void setViewportMetrics(ViewportMetrics viewport); - public void setForceRedraw(); - - public boolean post(Runnable action); - public Object getLock(); - public PointF convertViewPointToLayerPoint(PointF viewPoint); -}