Bug 777351 - Fold LayerController into GeckoLayerClient. r=sriram
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 07 Aug 2012 10:39:04 -0400
changeset 101679 eeebc9bf9712a54d2d4bb09c9bbec593361e5509
parent 101678 d7f17e94071abfc04443c556ebe54e3f1edfa7b7
child 101680 2fb7d7b2e62d33cf634383a8142024fc6239a37e
push id23250
push useremorley@mozilla.com
push dateWed, 08 Aug 2012 16:23:03 +0000
treeherderautoland@b99a81e70b06 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssriram
bugs777351
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 777351 - Fold LayerController into GeckoLayerClient. r=sriram
mobile/android/base/FormAssistPopup.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoEvent.java
mobile/android/base/GeckoInputConnection.java
mobile/android/base/Makefile.in
mobile/android/base/PromptService.java
mobile/android/base/TextSelection.java
mobile/android/base/TextSelectionHandle.java
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/LayerController.java
mobile/android/base/gfx/LayerRenderer.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/gfx/TouchEventHandler.java
mobile/android/base/tests/MotionEventReplayer.java.in
--- a/mobile/android/base/FormAssistPopup.java
+++ b/mobile/android/base/FormAssistPopup.java
@@ -217,17 +217,17 @@ public class FormAssistPopup extends Rel
             top = (int) (rect.getDouble(1) * zoom);
             width = (int) (rect.getDouble(2) * zoom);
             height = (int) (rect.getDouble(3) * zoom);
         } catch (JSONException e) { } 
 
         int popupWidth = RelativeLayout.LayoutParams.FILL_PARENT;
         int popupLeft = left < 0 ? 0 : left;
 
-        FloatSize viewport = GeckoApp.mAppContext.getLayerController().getViewportSize();
+        FloatSize viewport = GeckoApp.mAppContext.getLayerClient().getViewportSize();
 
         // For autocomplete suggestions, if the input is smaller than the screen-width,
         // shrink the popup's width. Otherwise, keep it as FILL_PARENT.
         if (isAutoComplete && (left + width) < viewport.width) {
             popupWidth = left < 0 ? left + width : width;
 
             // Ensure the popup has a minimum width.
             if (popupWidth < sAutoCompleteMinWidth) {
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -3,17 +3,16 @@
  * 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.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;
 import org.mozilla.gecko.ui.PanZoomController;
 import org.mozilla.gecko.util.GeckoAsyncTask;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -149,17 +148,16 @@ abstract public class GeckoApp
     private PromptService mPromptService;
     private Favicons mFavicons;
     private TextSelection mTextSelection;
 
     protected DoorHangerPopup mDoorHangerPopup;
     protected FormAssistPopup mFormAssistPopup;
     protected TabsPanel mTabsPanel;
 
-    private LayerController mLayerController;
     private GeckoLayerClient mLayerClient;
     private AbsoluteLayout mPluginContainer;
 
     private FullScreenHolder mFullScreenPluginContainer;
     private View mFullScreenPluginView;
 
     private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
 
@@ -851,20 +849,20 @@ abstract public class GeckoApp
                 Tab tab = Tabs.getInstance().getTab(tabId);
                 if (backgroundColor != null) {
                     tab.setCheckerboardColor(backgroundColor);
                 } else {
                     // Default to white if no color is given
                     tab.setCheckerboardColor(Color.WHITE);
                 }
 
-                // Sync up the LayerController and the tab if the tab's
+                // Sync up the GeckoLayerClient and the tab if the tab's
                 // currently displayed.
-                if (getLayerController() != null && Tabs.getInstance().isSelectedTab(tab)) {
-                    getLayerController().setCheckerboardColor(tab.getCheckerboardColor());
+                if (getLayerClient() != null && Tabs.getInstance().isSelectedTab(tab)) {
+                    getLayerClient().setCheckerboardColor(tab.getCheckerboardColor());
                 }
             } else if (event.equals("DOMTitleChanged")) {
                 final int tabId = message.getInt("tabID");
                 final String title = message.getString("title");
                 handleTitleChanged(tabId, title);
                 Log.i(LOGTAG, "title - " + title);
             } else if (event.equals("DOMLinkAdded")) {
                 final int tabId = message.getInt("tabID");
@@ -997,29 +995,29 @@ abstract public class GeckoApp
             } else if (event.equals("Update:Restart")) {
                 doRestart("org.mozilla.gecko.restart_update");
             } else if (event.equals("Tab:ViewportMetadata")) {
                 int tabId = message.getInt("tabID");
                 Tab tab = Tabs.getInstance().getTab(tabId);
                 if (tab == null)
                     return;
                 tab.setZoomConstraints(new ZoomConstraints(message));
-                // Sync up the LayerController and the tab if the tab's currently displayed.
-                LayerController controller = getLayerController();
-                if (controller != null && Tabs.getInstance().isSelectedTab(tab)) {
-                    controller.setZoomConstraints(tab.getZoomConstraints());
+                // 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());
                 }
             } else if (event.equals("Tab:HasTouchListener")) {
                 int tabId = message.getInt("tabID");
                 final Tab tab = Tabs.getInstance().getTab(tabId);
                 tab.setHasTouchListeners(true);
                 mMainHandler.post(new Runnable() {
                     public void run() {
                         if (Tabs.getInstance().isSelectedTab(tab))
-                            mLayerController.getView().getTouchEventHandler().setWaitForTouchListeners(true);
+                            mLayerClient.getView().getTouchEventHandler().setWaitForTouchListeners(true);
                     }
                 });
             } else if (event.equals("Session:StatePurged")) {
                 onStatePurged();
             } else if (event.equals("Bookmark:Insert")) {
                 final String url = message.getString("url");
                 final String title = message.getString("title");
                 mMainHandler.post(new Runnable() {
@@ -1209,17 +1207,17 @@ abstract public class GeckoApp
         final Tab tab = Tabs.getInstance().getTab(tabId);
         if (tab == null)
             return;
 
         tab.setState("about:home".equals(uri) ? Tab.STATE_SUCCESS : Tab.STATE_LOADING);
         tab.updateIdentityData(null);
         tab.setReaderEnabled(false);
         if (Tabs.getInstance().isSelectedTab(tab))
-            getLayerController().getView().getRenderer().resetCheckerboard();
+            getLayerClient().getView().getRenderer().resetCheckerboard();
         mMainHandler.post(new Runnable() {
             public void run() {
                 Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.START, showProgress);
             }
         });
     }
 
     void handleDocumentStop(int tabId, boolean success) {
@@ -1346,24 +1344,24 @@ abstract public class GeckoApp
 
                 if (isFullScreen) {
                     addFullScreenPluginView(view);
                     return;
                 }
 
                 PluginLayer layer = (PluginLayer) tab.getPluginLayer(view);
                 if (layer == null) {
-                    layer = new PluginLayer(view, rect, mLayerController.getView().getRenderer().getMaxTextureSize());
+                    layer = new PluginLayer(view, rect, mLayerClient.getView().getRenderer().getMaxTextureSize());
                     tab.addPluginLayer(view, layer);
                 } else {
                     layer.reset(rect);
                     layer.setVisible(true);
                 }
 
-                mLayerController.getView().addLayer(layer);
+                mLayerClient.getView().addLayer(layer);
             }
         });
     }
 
     private void removeFullScreenPluginView(View view) {
         if (mFullScreenPluginView == null) {
             Log.w(LOGTAG, "Don't have a fullscreen plugin view");
             return;
@@ -1375,17 +1373,17 @@ abstract public class GeckoApp
         }
 
         mFullScreenPluginContainer.removeView(mFullScreenPluginView);
 
         // We need do do this on the next iteration in order to avoid
         // a deadlock, see comment below in FullScreenHolder
         mMainHandler.post(new Runnable() { 
             public void run() {
-                mLayerController.getView().setVisibility(View.VISIBLE);
+                mLayerClient.getView().setVisibility(View.VISIBLE);
             }
         });
 
         FrameLayout decor = (FrameLayout)getWindow().getDecorView();
         decor.removeView(mFullScreenPluginContainer);
         
         mFullScreenPluginView = null;
 
@@ -1430,29 +1428,29 @@ abstract public class GeckoApp
                     }
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:Settings",
                                                                                    ret.toString()));
                 }
             });
     }
     
     private void hidePluginLayer(Layer layer) {
-        LayerView layerView = mLayerController.getView();
+        LayerView layerView = mLayerClient.getView();
         layerView.removeLayer(layer);
         layerView.requestRender();
     }
 
     private void showPluginLayer(Layer layer) {
-        LayerView layerView = mLayerController.getView();
+        LayerView layerView = mLayerClient.getView();
         layerView.addLayer(layer);
         layerView.requestRender();
     }
 
     public void requestRender() {
-        mLayerController.getView().requestRender();
+        mLayerClient.getView().requestRender();
     }
     
     public void hidePlugins(Tab tab) {
         for (Layer layer : tab.getPluginLayers()) {
             if (layer instanceof PluginLayer) {
                 ((PluginLayer) layer).setVisible(false);
             }
 
@@ -1658,33 +1656,19 @@ abstract public class GeckoApp
         Tabs.getInstance().setContentResolver(getContentResolver());
         Tabs.registerOnTabsChangedListener(this);
 
         if (cameraView == null) {
             cameraView = new SurfaceView(this);
             cameraView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
         }
 
-        if (mLayerController == null) {
-            /*
-             * Create a layer client, but don't hook it up to the layer controller yet.
-             */
+        if (mLayerClient == null) {
             mLayerClient = new GeckoLayerClient(this);
-
-            /*
-             * 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));
+            mLayerClient.setView((LayerView)findViewById(R.id.layer_view));
         }
 
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
 
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - UI almost up");
 
         //register for events
@@ -2091,18 +2075,16 @@ abstract public class GeckoApp
         GeckoAppShell.unregisterGeckoEventListener("WebApps:Uninstall", this);
         GeckoAppShell.unregisterGeckoEventListener("DesktopMode:Changed", this);
         GeckoAppShell.unregisterGeckoEventListener("Share:Text", this);
         GeckoAppShell.unregisterGeckoEventListener("Share:Image", this);
         GeckoAppShell.unregisterGeckoEventListener("Sanitize:ClearHistory", this);
 
         deleteTempFiles();
 
-        if (mLayerController != null)
-            mLayerController.destroy();
         if (mLayerClient != null)
             mLayerClient.destroy();
         if (mDoorHangerPopup != null)
             mDoorHangerPopup.destroy();
         if (mFormAssistPopup != null)
             mFormAssistPopup.destroy();
         if (mPromptService != null)
             mPromptService.destroy();
@@ -2560,17 +2542,16 @@ abstract public class GeckoApp
             Log.e(LOGTAG, "error building JSON arguments");
         }
         Log.i(LOGTAG, "Sending message to Gecko: " + SystemClock.uptimeMillis() + " - Tab:Add");
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString()));
     }
 
     /* This method is referenced by Robocop via reflection. */
     public GeckoLayerClient getLayerClient() { return mLayerClient; }
-    public LayerController getLayerController() { return mLayerController; }
 
     public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
 
     // accelerometer
     public void onAccuracyChanged(Sensor sensor, int accuracy) {}
 
     public void onSensorChanged(SensorEvent event)
     {
@@ -2606,20 +2587,20 @@ abstract public class GeckoApp
             mWakeLocks.put(topic, wl);
         } else if (!state.equals("locked-foreground") && wl != null) {
             wl.release();
             mWakeLocks.remove(topic);
         }
     }
 
     private void connectGeckoLayerClient() {
-        LayerController layerController = getLayerController();
-        layerController.setLayerClient(mLayerClient);
-
-        layerController.getView().getTouchEventHandler().setOnTouchListener(new ContentTouchListener() {
+        GeckoLayerClient layerClient = getLayerClient();
+        layerClient.notifyGeckoReady();
+
+        layerClient.getView().getTouchEventHandler().setOnTouchListener(new ContentTouchListener() {
             private PointF initialPoint = null;
 
             @Override
             public boolean onTouch(View view, MotionEvent event) {
                 if (event == null)
                     return true;
 
                 if (super.onTouch(view, event))
@@ -2694,17 +2675,17 @@ abstract public class GeckoApp
              * event to be sent to Gecko. We synchronously wait for that to be
              * processed. Simultaneously, however, Flash is waiting on a mutex so
              * the post() below is an attempt to avoid a deadlock.
              */
             super.addView(view, index);
 
             mMainHandler.post(new Runnable() { 
                 public void run() {
-                    mLayerController.getView().setVisibility(View.INVISIBLE);
+                    mLayerClient.getView().setVisibility(View.INVISIBLE);
                 }
             });
         }
 
         /**
          * The methods below are simply copied from what Android WebKit does.
          * It wasn't ever called in my testing, but might as well
          * keep it in case it is for some reason. The methods
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -5,17 +5,16 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.GfxInfoThread;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
 import org.mozilla.gecko.gfx.IntSize;
-import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.RectUtils;
 import org.mozilla.gecko.gfx.ScreenshotLayer;
 import org.mozilla.gecko.mozglue.DirectBufferAllocator;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.GeckoBackgroundThread;
 
 import org.json.JSONObject;
@@ -534,21 +533,21 @@ public class GeckoAppShell
             });
 
         // and go
         GeckoAppShell.nativeRun(combinedArgs);
     }
 
     // Called on the UI thread after Gecko loads.
     private static void geckoLoaded() {
-        final LayerController layerController = GeckoApp.mAppContext.getLayerController();
-        LayerView v = layerController.getView();
+        final GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        LayerView v = layerClient.getView();
         mInputConnection = GeckoInputConnection.create(v);
         v.setInputConnectionHandler(mInputConnection);
-        layerController.setForceRedraw();
+        layerClient.setForceRedraw();
     }
 
     static void sendPendingEventsToGecko() {
         try {
             while (!gPendingEvents.isEmpty()) {
                 GeckoEvent e = gPendingEvents.removeFirst();
                 notifyGeckoOfEvent(e);
             }
@@ -1420,27 +1419,27 @@ public class GeckoAppShell
     public static String showFilePickerForMimeType(String aMimeType) {
         return sActivityHelper.showFilePicker(GeckoApp.mAppContext, aMimeType);
     }
 
     public static void performHapticFeedback(boolean aIsLongPress) {
         // Don't perform haptic feedback if a vibration is currently playing,
         // because the haptic feedback will nuke the vibration.
         if (!sVibrationMaybePlaying || System.nanoTime() >= sVibrationEndTime) {
-            LayerController layerController = GeckoApp.mAppContext.getLayerController();
-            LayerView layerView = layerController.getView();
+            GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+            LayerView layerView = layerClient.getView();
             layerView.performHapticFeedback(aIsLongPress ?
                                             HapticFeedbackConstants.LONG_PRESS :
                                             HapticFeedbackConstants.VIRTUAL_KEY);
         }
     }
 
     private static Vibrator vibrator() {
-        LayerController layerController = GeckoApp.mAppContext.getLayerController();
-        LayerView layerView = layerController.getView();
+        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        LayerView layerView = layerClient.getView();
 
         return (Vibrator) layerView.getContext().getSystemService(Context.VIBRATOR_SERVICE);
     }
 
     public static void vibrate(long milliseconds) {
         sVibrationEndTime = System.nanoTime() + milliseconds * 1000000;
         sVibrationMaybePlaying = true;
         vibrator().vibrate(milliseconds);
@@ -1478,17 +1477,17 @@ public class GeckoAppShell
                 // TODO
             }
         });
     }
 
     public static void notifyDefaultPrevented(final boolean defaultPrevented) {
         getMainHandler().post(new Runnable() {
             public void run() {
-                LayerView view = GeckoApp.mAppContext.getLayerController().getView();
+                LayerView view = GeckoApp.mAppContext.getLayerClient().getView();
                 view.getTouchEventHandler().handleEventListenerAction(!defaultPrevented);
             }
         });
     }
 
     public static boolean isNetworkLinkUp() {
         ConnectivityManager cm = (ConnectivityManager)
             GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -2396,21 +2395,21 @@ class ScreenshotHandler implements Runna
         if (handler == null) {
             return;
         }
 
         handler.screenshotWholePage(tab.getId());
     }
 
     private void screenshotWholePage(int tabId) {
-        LayerController layerController = GeckoApp.mAppContext.getLayerController();
-        if (layerController == null) {
+        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        if (layerClient == null) {
             return;
         }
-        ImmutableViewportMetrics viewport = layerController.getViewportMetrics();
+        ImmutableViewportMetrics viewport = layerClient.getViewportMetrics();
         RectF pageRect = viewport.getCssPageRect();
 
         if (FloatUtils.fuzzyEquals(pageRect.width(), 0) || FloatUtils.fuzzyEquals(pageRect.height(), 0)) {
             return;
         }
 
         synchronized (this) {
             // if we're doing a full-page screenshot, toss any
@@ -2500,24 +2499,24 @@ class ScreenshotHandler implements Runna
         Tab selectedTab = Tabs.getInstance().getSelectedTab();
         if (selectedTab == null || selectedTab.getId() != mTabId) {
             // tab changed, so bail out before we start screenshotting
             // the wrong tab. note we must do this *after* resetting
             // mIsRepaintRunnablePosted above.
             return;
         }
 
-        LayerController layerController = GeckoApp.mAppContext.getLayerController();
-        if (layerController == null) {
+        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        if (layerClient == 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 = layerController.getViewportMetrics();
+        ImmutableViewportMetrics viewport = layerClient.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
             int dstx = (int)(mWidthRatio * (dirtyRect.left - viewport.cssPageRectLeft));
             int dsty = (int)(mHeightRatio * (dirtyRect.top - viewport.cssPageRectTop));
             int dstw = (int)(mWidthRatio * dirtyRect.width());
             int dsth = (int)(mHeightRatio * dirtyRect.height());
             scheduleCheckerboardScreenshotEvent(dirtyRect, dstx, dsty, dstw, dsth);
@@ -2587,19 +2586,19 @@ class ScreenshotHandler implements Runna
                                 current = handler.mPendingScreenshots.element();
                                 current.slicePainted(left, top, right, bottom);
                                 if (current.sendNextEventToGecko()) {
                                     break;
                                 }
                                 // this screenshot has all its slices done, so push it out
                                 // to the layer renderer and remove it from the list
                             }
-                            LayerController layerController = GeckoApp.mAppContext.getLayerController();
-                            if (layerController != null) {
-                                layerController.getView().getRenderer().setCheckerboardBitmap(
+                            GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+                            if (layerClient != null) {
+                                layerClient.getView().getRenderer().setCheckerboardBitmap(
                                     data, bufferWidth, bufferHeight, handler.mPageRect,
                                     current.getPaintedRegion());
                             }
                         }
                         synchronized (handler.mPendingScreenshots) {
                             handler.mPendingScreenshots.remove();
                             handler.sendNextEventToGecko();
                         }
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -222,17 +222,17 @@ public class GeckoEvent {
                 mPointRadii = new Point[mCount];
             }
         }
     }
 
     public void addMotionPoint(int index, int eventIndex, MotionEvent event) {
         try {
             PointF geckoPoint = new PointF(event.getX(eventIndex), event.getY(eventIndex));
-            geckoPoint = GeckoApp.mAppContext.getLayerController().convertViewPointToLayerPoint(geckoPoint);
+            geckoPoint = GeckoApp.mAppContext.getLayerClient().convertViewPointToLayerPoint(geckoPoint);
     
             mPoints[index] = new Point(Math.round(geckoPoint.x), Math.round(geckoPoint.y));
             mPointIndicies[index] = event.getPointerId(eventIndex);
             // getToolMajor, getToolMinor and getOrientation are API Level 9 features
             if (Build.VERSION.SDK_INT >= 9) {
                 double radians = event.getOrientation(eventIndex);
                 mOrientations[index] = (float) Math.toDegrees(radians);
                 // w3c touchevents spec does not allow orientations == 90
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -1,17 +1,17 @@
 /* -*- 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.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.InputConnectionHandler;
-import org.mozilla.gecko.gfx.LayerController;
 
 import android.R;
 import android.content.Context;
 import android.os.Build;
 import android.os.SystemClock;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
@@ -289,18 +289,18 @@ class GeckoInputConnection
         // send us empty strings at inopportune times, deleting committed text. See bug 768106.
         if (text.length() == 0 && !hasCompositionString())
             return true;
 
         return super.setComposingText(text, newCursorPosition);
     }
 
     private static View getView() {
-        LayerController controller = GeckoApp.mAppContext.getLayerController();
-        return (controller == null ? null : controller.getView());
+        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        return (layerClient == null ? null : layerClient.getView());
     }
 
     private Span getSelection() {
         Editable content = getEditable();
         int start = Selection.getSelectionStart(content);
         int end = Selection.getSelectionEnd(content);
         return Span.clamp(start, end, content);
     }
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -126,17 +126,16 @@ FENNEC_JAVA_FILES = \
   gfx/FloatSize.java \
   gfx/GeckoLayerClient.java \
   gfx/GfxInfoThread.java \
   gfx/GLController.java \
   gfx/ImmutableViewportMetrics.java \
   gfx/InputConnectionHandler.java \
   gfx/IntSize.java \
   gfx/Layer.java \
-  gfx/LayerController.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
   gfx/PluginLayer.java \
   gfx/NinePatchTileLayer.java \
   gfx/PanningPerfAPI.java \
   gfx/PointUtils.java \
   gfx/RectUtils.java \
   gfx/ScreenshotLayer.java \
--- a/mobile/android/base/PromptService.java
+++ b/mobile/android/base/PromptService.java
@@ -1,16 +1,16 @@
 /* -*- 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.mozilla.gecko.gfx.LayerController;
+import org.mozilla.gecko.gfx.GeckoLayerClient;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
@@ -176,21 +176,21 @@ public class PromptService implements On
             promptServiceResult = waitForReturn();
         } catch (InterruptedException e) {
             Log.i(LOGTAG, "showing prompt ",  e);
         }
         return promptServiceResult;
     }
 
     public void show(String aTitle, String aText, PromptButton[] aButtons, PromptListItem[] aMenuList, boolean aMultipleSelection) {
-        final LayerController controller = GeckoApp.mAppContext.getLayerController();
-        controller.post(new Runnable() {
+        final GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        layerClient.post(new Runnable() {
             public void run() {
                 // treat actions that show a dialog as if preventDefault by content to prevent panning
-                controller.getPanZoomController().abortPanning();
+                layerClient.getPanZoomController().abortPanning();
             }
         });
 
         AlertDialog.Builder builder = new AlertDialog.Builder(GeckoApp.mAppContext);
         if (!aTitle.equals("")) {
             builder.setTitle(aTitle);
         }
 
--- a/mobile/android/base/TextSelection.java
+++ b/mobile/android/base/TextSelection.java
@@ -1,17 +1,17 @@
 /* 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.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.gfx.Layer.RenderContext;
-import org.mozilla.gecko.gfx.LayerController;
 import org.mozilla.gecko.util.FloatUtils;
 
 import org.json.JSONObject;
 
 import android.util.Log;
 import android.view.View;
 
 class TextSelection extends Layer implements GeckoEventListener {
@@ -50,28 +50,28 @@ class TextSelection extends Layer implem
                 GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
                     public void run() {
                         mStartHandle.setVisibility(View.VISIBLE);
                         mEndHandle.setVisibility(View.VISIBLE);
 
                         mViewLeft = 0.0f;
                         mViewTop = 0.0f;
                         mViewZoom = 0.0f;
-                        LayerController layerController = GeckoApp.mAppContext.getLayerController();
-                        if (layerController != null) {
-                            layerController.getView().addLayer(TextSelection.this);
+                        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+                        if (layerClient != null) {
+                            layerClient.getView().addLayer(TextSelection.this);
                         }
                     }
                 });
             } else if (event.equals("TextSelection:HideHandles")) {
                 GeckoApp.mAppContext.mMainHandler.post(new Runnable() {
                     public void run() {
-                        LayerController layerController = GeckoApp.mAppContext.getLayerController();
-                        if (layerController != null) {
-                            layerController.getView().removeLayer(TextSelection.this);
+                        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+                        if (layerClient != null) {
+                            layerClient.getView().removeLayer(TextSelection.this);
                         }
 
                         mStartHandle.setVisibility(View.GONE);
                         mEndHandle.setVisibility(View.GONE);
                     }
                 });
             } else if (event.equals("TextSelection:PositionHandles")) {
                 final int startLeft = message.getInt("startLeft");
--- a/mobile/android/base/TextSelectionHandle.java
+++ b/mobile/android/base/TextSelectionHandle.java
@@ -1,16 +1,16 @@
  /* 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.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.gfx.LayerController;
 
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -78,48 +78,48 @@ class TextSelectionHandle extends ImageV
         }
         return true;
     }
 
     private void move(int newX, int newY) {
         mLeft = mLeft + newX - mTouchStartX;
         mTop = mTop + newY - mTouchStartY;
 
-        LayerController layerController = GeckoApp.mAppContext.getLayerController();
-        if (layerController == null) {
-            Log.e(LOGTAG, "Can't move selection because layerController is null");
+        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        if (layerClient == null) {
+            Log.e(LOGTAG, "Can't move selection because layerClient 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 = layerController.convertViewPointToLayerPoint(geckoPoint);
+        geckoPoint = layerClient.convertViewPointToLayerPoint(geckoPoint);
 
         JSONObject args = new JSONObject();
         try {
             args.put("handleType", mHandleType.toString());
             args.put("x", Math.round(geckoPoint.x));
             args.put("y", Math.round(geckoPoint.y));
         } catch (Exception e) {
             Log.e(LOGTAG, "Error building JSON arguments for TextSelection:Move");
         }
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Move", args.toString()));
 
         setLayoutPosition();
     }
 
     void positionFromGecko(int left, int top) {
-        LayerController layerController = GeckoApp.mAppContext.getLayerController();
-        if (layerController == null) {
-            Log.e(LOGTAG, "Can't position handle because layerController is null");
+        GeckoLayerClient layerClient = GeckoApp.mAppContext.getLayerClient();
+        if (layerClient == null) {
+            Log.e(LOGTAG, "Can't position handle because layerClient is null");
             return;
         }
 
         mGeckoPoint = new PointF((float) left, (float) top);
-        ImmutableViewportMetrics metrics = layerController.getViewportMetrics();
+        ImmutableViewportMetrics metrics = layerClient.getViewportMetrics();
         repositionWithViewport(metrics.viewportRectLeft, metrics.viewportRectTop, metrics.zoomFactor);
     }
 
     void repositionWithViewport(float x, float y, float zoom) {
         PointF viewPoint = new PointF((mGeckoPoint.x * zoom) - x,
                                       (mGeckoPoint.y * zoom) - y);
 
         mLeft = Math.round(viewPoint.x) - (mHandleType.equals(HandleType.START) ? mWidth - mShadow : mShadow);
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -5,37 +5,43 @@
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.GeckoAppShell;
 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.Bitmap;
+import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.view.View;
+import android.view.GestureDetector;
 
 import java.util.HashMap;
 import java.util.Map;
 
-public class GeckoLayerClient implements GeckoEventResponder, LayerView.Listener {
+public class GeckoLayerClient
+        implements GeckoEventResponder, LayerView.Listener, PanZoomTarget
+{
     private static final String LOGTAG = "GeckoLayerClient";
 
-    private LayerController mLayerController;
     private LayerRenderer mLayerRenderer;
     private boolean mLayerRendererInitialized;
 
     private Context mContext;
     private IntSize mScreenSize;
     private IntSize mWindowSize;
     private DisplayPortMetrics mDisplayPort;
     private DisplayPortMetrics mReturnDisplayPort;
@@ -58,76 +64,174 @@ public class GeckoLayerClient implements
     private DrawListener mDrawListener;
 
     /* Used as a temporary ViewTransform by syncViewportInfo */
     private final 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
         mContext = context;
         mScreenSize = new IntSize(0, 0);
         mWindowSize = new IntSize(0, 0);
         mDisplayPort = new DisplayPortMetrics();
         mRecordDrawTimes = true;
         mDrawTimingQueue = new DrawTimingQueue();
         mCurrentViewTransform = new ViewTransform(0, 0, 1);
 
         mCompositorCreated = false;
+
+        mForceRedraw = true;
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics(displayMetrics));
+        mZoomConstraints = new ZoomConstraints(false);
+        mCheckerboardColor = Color.WHITE;
+        mCheckerboardShouldShowChecks = true;
+
+        mPanZoomController = new PanZoomController(this);
     }
 
-    /** Attaches the root layer to the layer controller so that Gecko appears. */
-    void setLayerController(LayerController layerController) {
-        LayerView view = layerController.getView();
+    public void setView(LayerView v) {
+        mView = v;
+        mView.connect(this);
+    }
 
-        mLayerController = layerController;
+    /** Attaches to root layer so that Gecko appears. */
+    public void notifyGeckoReady() {
+        mGeckoIsReady = true;
 
-        mRootLayer = new VirtualLayer(new IntSize(view.getWidth(), view.getHeight()));
-        mLayerRenderer = new LayerRenderer(view);
+        mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight()));
+        mLayerRenderer = new LayerRenderer(mView);
 
         GeckoAppShell.registerGeckoEventListener("Viewport:Update", this);
         GeckoAppShell.registerGeckoEventListener("Viewport:PageSize", this);
         GeckoAppShell.registerGeckoEventListener("Viewport:CalculateDisplayPort", this);
         GeckoAppShell.registerGeckoEventListener("Checkerboard:Toggle", this);
         GeckoAppShell.registerGeckoEventListener("Preferences:Data", this);
 
-        view.setListener(this);
-        view.setLayerRenderer(mLayerRenderer);
-        layerController.setRoot(mRootLayer);
+        mView.setListener(this);
+        mView.setLayerRenderer(mLayerRenderer);
 
         sendResizeEventIfNecessary(true);
 
         JSONArray prefs = new JSONArray();
         DisplayPortCalculator.addPrefNames(prefs);
         PluginLayer.addPrefNames(prefs);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Preferences:Get", prefs.toString()));
     }
 
     public void destroy() {
+        mPanZoomController.destroy();
         GeckoAppShell.unregisterGeckoEventListener("Viewport:Update", this);
         GeckoAppShell.unregisterGeckoEventListener("Viewport:PageSize", this);
         GeckoAppShell.unregisterGeckoEventListener("Viewport:CalculateDisplayPort", this);
         GeckoAppShell.unregisterGeckoEventListener("Checkerboard:Toggle", this);
         GeckoAppShell.unregisterGeckoEventListener("Preferences:Data", this);
     }
 
-    DisplayPortMetrics getDisplayPort() {
-        return mDisplayPort;
+    /**
+     * 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.
+     */
+    public boolean getRedrawHint() {
+        if (mForceRedraw) {
+            mForceRedraw = false;
+            return true;
+        }
+
+        if (!mPanZoomController.getRedrawHint()) {
+            return false;
+        }
+
+        return DisplayPortCalculator.aboutToCheckerboard(mViewportMetrics,
+                mPanZoomController.getVelocityVector(), mDisplayPort);
+    }
+
+    public 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.
+     */
+    public void setViewportSize(FloatSize size) {
+        ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
+        viewportMetrics.setSize(size);
+        mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
+
+        if (mGeckoIsReady) {
+            viewportSizeChanged();
+        }
+    }
+
+    public PanZoomController getPanZoomController() {
+        return mPanZoomController;
+    }
+
+    public GestureDetector.OnGestureListener getGestureListener() {
+        return mPanZoomController;
+    }
+
+    public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
+        return mPanZoomController;
+    }
+
+    public GestureDetector.OnDoubleTapListener getDoubleTapListener() {
+        return mPanZoomController;
     }
 
     /* 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(view.getWidth(), view.getHeight());
+        IntSize newWindowSize = new IntSize(mView.getWidth(), mView.getHeight());
 
         boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
         boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
 
         if (!force && !screenSizeChanged && !windowSizeChanged) {
             return;
         }
 
@@ -158,96 +262,129 @@ public class GeckoLayerClient implements
         // 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();
     }
 
+    /** 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();
+            }
+        });
+    }
+
     void adjustViewport(DisplayPortMetrics displayPort) {
-        ImmutableViewportMetrics metrics = mLayerController.getViewportMetrics();
+        ImmutableViewportMetrics metrics = getViewportMetrics();
 
         ViewportMetrics clampedMetrics = new ViewportMetrics(metrics);
         clampedMetrics.setViewport(clampedMetrics.getClampedViewport());
 
         if (displayPort == null) {
-            displayPort = DisplayPortCalculator.calculate(metrics,
-                    mLayerController.getPanZoomController().getVelocityVector());
+            displayPort = DisplayPortCalculator.calculate(metrics, mPanZoomController.getVelocityVector());
         }
 
         mDisplayPort = displayPort;
         mGeckoViewport = clampedMetrics;
 
         if (mRecordDrawTimes) {
             mDrawTimingQueue.add(displayPort);
         }
 
         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
      * returned.
      */
     private enum ViewportMessageType {
         UPDATE,       // The viewport has changed and should be entirely updated
         PAGE_SIZE     // The viewport's page-size has changed
     }
 
     /** Viewport message handler. */
     private void handleViewportMessage(JSONObject message, ViewportMessageType type) throws JSONException {
         ViewportMetrics messageMetrics = new ViewportMetrics(message);
-        synchronized (mLayerController) {
+        synchronized (this) {
             final ViewportMetrics newMetrics;
-            ImmutableViewportMetrics oldMetrics = mLayerController.getViewportMetrics();
+            ImmutableViewportMetrics oldMetrics = getViewportMetrics();
 
             switch (type) {
             default:
             case UPDATE:
                 newMetrics = messageMetrics;
                 // Keep the old viewport size
                 newMetrics.setSize(oldMetrics.getSize());
-                mLayerController.abortPanZoomAnimation();
+                abortPanZoomAnimation();
                 break;
             case PAGE_SIZE:
                 // adjust the page dimensions to account for differences in zoom
                 // between the rendered content (which is what Gecko tells us)
                 // and our zoom level (which may have diverged).
                 float scaleFactor = oldMetrics.zoomFactor / messageMetrics.getZoomFactor();
                 newMetrics = new ViewportMetrics(oldMetrics);
                 newMetrics.setPageRect(RectUtils.scale(messageMetrics.getPageRect(), scaleFactor), messageMetrics.getCssPageRect());
                 break;
             }
 
-            mLayerController.post(new Runnable() {
+            post(new Runnable() {
                 public void run() {
                     mGeckoViewport = newMetrics;
                 }
             });
-            mLayerController.setViewportMetrics(newMetrics);
-            mDisplayPort = DisplayPortCalculator.calculate(mLayerController.getViewportMetrics(), null);
+            setViewportMetrics(newMetrics);
+            mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
         }
         mReturnDisplayPort = mDisplayPort;
     }
 
     /** Implementation of GeckoEventResponder/GeckoEventListener. */
     public void handleMessage(String event, JSONObject message) {
         try {
             if ("Viewport:Update".equals(event)) {
                 handleViewportMessage(message, ViewportMessageType.UPDATE);
             } else if ("Viewport:PageSize".equals(event)) {
                 handleViewportMessage(message, ViewportMessageType.PAGE_SIZE);
             } else if ("Viewport:CalculateDisplayPort".equals(event)) {
                 ImmutableViewportMetrics newMetrics = new ImmutableViewportMetrics(new ViewportMetrics(message));
                 mReturnDisplayPort = DisplayPortCalculator.calculate(newMetrics, null);
             } else if ("Checkerboard:Toggle".equals(event)) {
                 boolean showChecks = message.getBoolean("value");
-                mLayerController.setCheckerboardShowChecks(showChecks);
+                setCheckerboardShowChecks(showChecks);
                 Log.i(LOGTAG, "Showing checks: " + showChecks);
             } else if ("Preferences:Data".equals(event)) {
                 JSONArray jsonPrefs = message.getJSONArray("preferences");
                 Map<String, Integer> prefValues = new HashMap<String, Integer>();
                 for (int i = jsonPrefs.length() - 1; i >= 0; i--) {
                     JSONObject pref = jsonPrefs.getJSONObject(i);
                     String name = pref.getString("name");
                     try {
@@ -281,93 +418,108 @@ public class GeckoLayerClient implements
         }
         try {
             return mReturnDisplayPort.toJSON();
         } finally {
             mReturnDisplayPort = null;
         }
     }
 
-    void geometryChanged() {
-        /* Let Gecko know if the screensize has changed */
-        sendResizeEventIfNecessary(false);
-        if (mLayerController.getRedrawHint())
-            adjustViewport(null);
-    }
-
     /*
      * 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;
     }
 
+    public boolean checkerboardShouldShowChecks() {
+        return mCheckerboardShouldShowChecks;
+    }
+
+    public int getCheckerboardColor() {
+        return mCheckerboardColor;
+    }
+
+    public void setCheckerboardShowChecks(boolean showChecks) {
+        mCheckerboardShouldShowChecks = showChecks;
+        mView.requestRender();
+    }
+
+    public void setCheckerboardColor(int newColor) {
+        mCheckerboardColor = newColor;
+        mView.requestRender();
+    }
+
+    public void setZoomConstraints(ZoomConstraints constraints) {
+        mZoomConstraints = constraints;
+    }
+
     /** This function is invoked by Gecko via JNI; be careful when modifying signature.
       * The compositor invokes this function just before compositing a frame where the document
       * is different from the document composited on the last frame. In these cases, the viewport
       * information we have in Java is no longer valid and needs to be replaced with the new
       * viewport information provided. setPageRect will never be invoked on the same frame that
       * this function is invoked on; and this function will always be called prior to syncViewportInfo.
       */
     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 (mLayerController) {
-            final ViewportMetrics currentMetrics = new ViewportMetrics(mLayerController.getViewportMetrics());
+        synchronized (this) {
+            final ViewportMetrics currentMetrics = new ViewportMetrics(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 the
-            // viewport in mLayerController. Usually this information is updated via handleViewportMessage
+            // viewport-related state we have lying around. This includes mGeckoViewport and
+            // mViewportMetrics. Usually this information is updated via handleViewportMessage
             // while we remain on the same document.
-            mLayerController.post(new Runnable() {
+            post(new Runnable() {
                 public void run() {
                     mGeckoViewport = currentMetrics;
                 }
             });
-            mLayerController.setViewportMetrics(currentMetrics);
+            setViewportMetrics(currentMetrics);
 
             Tab tab = Tabs.getInstance().getSelectedTab();
-            mLayerController.setCheckerboardColor(tab.getCheckerboardColor());
-            mLayerController.setZoomConstraints(tab.getZoomConstraints());
+            setCheckerboardColor(tab.getCheckerboardColor());
+            setZoomConstraints(tab.getZoomConstraints());
 
             // 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
             // that are in progress and send an updated display port request to browser.js as soon
             // as possible. We accomplish this by passing true to abortPanZoomAnimation, which
             // 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.
-            mLayerController.abortPanZoomAnimation();
-            mLayerController.getView().setPaintState(LayerView.PAINT_BEFORE_FIRST);
+            abortPanZoomAnimation();
+            mView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
         }
         DisplayPortCalculator.resetPageState();
         mDrawTimingQueue.reset();
-        mLayerController.getView().getRenderer().resetCheckerboard();
+        mView.getRenderer().resetCheckerboard();
         GeckoAppShell.screenshotWholePage(Tabs.getInstance().getSelectedTab());
     }
 
     /** This function is invoked by Gecko via JNI; be careful when modifying signature.
       * The compositor invokes this function whenever it determines that the page rect
       * has changed (based on the information it gets from layout). If setFirstPaintViewport
       * is invoked on a frame, then this function will not be. For any given frame, this
       * function will be invoked before syncViewportInfo.
       */
     public void setPageRect(float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
-        synchronized (mLayerController) {
+        synchronized (this) {
             RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
-            float ourZoom = mLayerController.getViewportMetrics().zoomFactor;
-            mLayerController.setPageRect(RectUtils.scale(cssPageRect, ourZoom), cssPageRect);
+            float ourZoom = getViewportMetrics().zoomFactor;
+            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
             // adjustViewport().
         }
     }
 
     /** This function is invoked by Gecko via JNI; be careful when modifying signature.
@@ -375,24 +527,23 @@ public class GeckoLayerClient implements
       * page to display, and to inform Java of the current display port. Since it is called
       * on every frame, it needs to be ultra-fast.
       * It avoids taking any locks or allocating any objects. We keep around a
       * mCurrentViewTransform so we don't need to allocate a new ViewTransform
       * everytime we're called. NOTE: we might be able to return a ImmutableViewportMetrics
       * 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
-        // on mLayerController.
+        // getViewportMetrics is thread safe so we don't need to synchronize.
         // 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 LayerController's viewport
+        // the native side, by the compositor). The viewport
         // metrics can change between here and there, as it's accessed outside
         // of the compositor thread.
-        mFrameMetrics = mLayerController.getViewportMetrics();
+        mFrameMetrics = getViewportMetrics();
 
         mCurrentViewTransform.x = mFrameMetrics.viewportRectLeft;
         mCurrentViewTransform.y = mFrameMetrics.viewportRectTop;
         mCurrentViewTransform.scale = mFrameMetrics.zoomFactor;
 
         mRootLayer.setPositionAndResolution(x, y, x + width, y + height, resolution);
 
         if (layersUpdated && mRecordDrawTimes) {
@@ -433,16 +584,24 @@ public class GeckoLayerClient implements
         mLayerRenderer.activateDefaultProgram();
     }
 
     /** This function is invoked by Gecko via JNI; be careful when modifying signature. */
     public void deactivateProgram() {
         mLayerRenderer.deactivateDefaultProgram();
     }
 
+    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();
     }
 
     /** Implementation of LayerView.Listener */
     public void compositionPauseRequested() {
         // We need to coordinate with Gecko when pausing composition, to ensure
@@ -467,30 +626,113 @@ public class GeckoLayerClient implements
         if (mCompositorCreated) {
             GeckoAppShell.scheduleResumeComposition(width, height);
             GeckoAppShell.sendEventToGecko(GeckoEvent.createCompositorResumeEvent());
         }
     }
 
     /** Implementation of LayerView.Listener */
     public void surfaceChanged(int width, int height) {
-        mLayerController.setViewportSize(new FloatSize(width, height));
+        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
         // aware of the changed surface.
         compositionResumeRequested(width, height);
         renderRequested();
     }
 
     /** Implementation of LayerView.Listener */
     public void compositorCreated() {
         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 = 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;
+    }
+
     /** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
     public void setDrawListener(DrawListener listener) {
         mDrawListener = listener;
     }
 
     /** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
     public interface DrawListener {
         public void drawFinished();
deleted file mode 100644
--- a/mobile/android/base/gfx/LayerController.java
+++ /dev/null
@@ -1,270 +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.gfx;
-
-import org.mozilla.gecko.ZoomConstraints;
-import org.mozilla.gecko.ui.PanZoomController;
-import org.mozilla.gecko.ui.PanZoomTarget;
-import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.util.DisplayMetrics;
-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 implements PanZoomTarget {
-    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 ZoomConstraints mZoomConstraints;
-
-    private boolean mForceRedraw;
-
-    public LayerController(Context context) {
-        mContext = context;
-        mForceRedraw = true;
-        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
-        mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics(displayMetrics));
-        mPanZoomController = new PanZoomController(this);
-        mCheckerboardShouldShowChecks = true;
-        mZoomConstraints = new ZoomConstraints(false);
-    }
-
-    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;
-        notifyLayerClientOfGeometryChange();
-    }
-
-    public Layer getRoot()                        { return mRootLayer; }
-    public LayerView getView()                    { return mView; }
-    public Context getContext()                   { return mContext; }
-    public ImmutableViewportMetrics getViewportMetrics()   { return mViewportMetrics; }
-    public Object getLock()                       { return this; }
-
-    public FloatSize getViewportSize() {
-        return mViewportMetrics.getSize();
-    }
-
-    public PanZoomController getPanZoomController()                                 { return mPanZoomController; }
-    public GestureDetector.OnGestureListener getGestureListener()                   { return mPanZoomController; }
-    public SimpleScaleGestureDetector.SimpleScaleGestureListener getScaleGestureListener() {
-        return mPanZoomController;
-    }
-    public GestureDetector.OnDoubleTapListener getDoubleTapListener()               { return mPanZoomController; }
-
-    /**
-     * 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();
-        }
-    }
-
-    /** 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.
-     * You must hold the monitor while calling this.
-     */
-    public void setViewportMetrics(ViewportMetrics viewport) {
-        mViewportMetrics = new ImmutableViewportMetrics(viewport);
-        mView.requestRender();
-        notifyLayerClientOfGeometryChange();
-    }
-
-    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);
-        }
-    }
-
-    public boolean post(Runnable action) { return mView.post(action); }
-
-    private 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 setZoomConstraints(ZoomConstraints constraints) {
-        mZoomConstraints = constraints;
-    }
-
-    public ZoomConstraints getZoomConstraints() {
-        return mZoomConstraints;
-    }
-}
--- a/mobile/android/base/gfx/LayerRenderer.java
+++ b/mobile/android/base/gfx/LayerRenderer.java
@@ -440,17 +440,17 @@ public class LayerRenderer {
         public void beginDrawing() {
             mFrameStartTime = SystemClock.uptimeMillis();
 
             TextureReaper.get().reap();
             TextureGenerator.get().fill();
 
             mUpdated = true;
 
-            Layer rootLayer = mView.getController().getRoot();
+            Layer rootLayer = mView.getLayerClient().getRoot();
 
             if (!mPageContext.fuzzyEquals(mLastPageContext)) {
                 // the viewport or page changed, so show the scrollbars again
                 // as per UX decision
                 mVertScrollLayer.unfade();
                 mHorizScrollLayer.unfade();
                 mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
             } else if (mFadeRunnable.timeToFade()) {
@@ -513,17 +513,17 @@ public class LayerRenderer {
             return mask;
         }
 
         /** This function is invoked via JNI; be careful when modifying signature. */
         public void drawBackground() {
             GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
 
             /* Update background color. */
-            mBackgroundColor = mView.getController().getCheckerboardColor();
+            mBackgroundColor = mView.getLayerClient().getCheckerboardColor();
 
             /* Clear to the page background colour. The bits set here need to
              * match up with those used in gfx/layers/opengl/LayerManagerOGL.cpp.
              */
             GLES20.glClearColor(((mBackgroundColor>>16)&0xFF) / 255.0f,
                                 ((mBackgroundColor>>8)&0xFF) / 255.0f,
                                 (mBackgroundColor&0xFF) / 255.0f,
                                 0.0f);
@@ -538,32 +538,32 @@ public class LayerRenderer {
             RectF untransformedPageRect = new RectF(0.0f, 0.0f, mPageRect.width(),
                                                     mPageRect.height());
             if (!untransformedPageRect.contains(mFrameMetrics.getViewport()))
                 mShadowLayer.draw(mPageContext);
 
             /* Draw the 'checkerboard'. We use gfx.show_checkerboard_pattern to
              * determine whether to draw the screenshot layer.
              */
-            if (mView.getController().checkerboardShouldShowChecks()) {
+            if (mView.getLayerClient().checkerboardShouldShowChecks()) {
                 /* Find the area the root layer will render into, to mask the checkerboard layer */
-                Rect rootMask = getMaskForLayer(mView.getController().getRoot());
+                Rect rootMask = getMaskForLayer(mView.getLayerClient().getRoot());
                 mCheckerboardLayer.setMask(rootMask);
 
                 /* Scissor around the page-rect, in case the page has shrunk
                  * since the screenshot layer was last updated.
                  */
                 setScissorRect(); // Calls glEnable(GL_SCISSOR_TEST))
                 mCheckerboardLayer.draw(mPageContext);
             }
         }
 
         // Draws the layer the client added to us.
         void drawRootLayer() {
-            Layer rootLayer = mView.getController().getRoot();
+            Layer rootLayer = mView.getLayerClient().getRoot();
             if (rootLayer == null) {
                 return;
             }
 
             rootLayer.draw(mPageContext);
         }
 
         /** This function is invoked via JNI; be careful when modifying signature. */
@@ -585,17 +585,17 @@ public class LayerRenderer {
             if (mPageRect.height() > mFrameMetrics.getHeight())
                 mVertScrollLayer.draw(mPageContext);
 
             /* Draw the horizontal scrollbar. */
             if (mPageRect.width() > mFrameMetrics.getWidth())
                 mHorizScrollLayer.draw(mPageContext);
 
             /* Measure how much of the screen is checkerboarding */
-            Layer rootLayer = mView.getController().getRoot();
+            Layer rootLayer = mView.getLayerClient().getRoot();
             if ((rootLayer != null) &&
                 (mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
                 // Find out how much of the viewport area is valid
                 Rect viewport = RectUtils.round(mPageContext.viewport);
                 Region validRegion = rootLayer.getValidRegion(mPageContext);
 
                 /* restrict the viewport to page bounds so we don't
                  * count overscroll as checkerboard */
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -29,25 +29,22 @@ import android.widget.FrameLayout;
 
 import java.nio.IntBuffer;
 
 import java.lang.reflect.Method;
 
 /**
  * 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 LayerController mController;
+    private GeckoLayerClient mLayerClient;
     private TouchEventHandler mTouchEventHandler;
     private GLController mGLController;
     private InputConnectionHandler mInputConnectionHandler;
     private LayerRenderer mRenderer;
     /* Must be a PAINT_xxx constant */
     private int mPaintState = PAINT_NONE;
 
     private SurfaceView mSurfaceView;
@@ -93,19 +90,19 @@ public class LayerView extends FrameLayo
             SurfaceHolder holder = mSurfaceView.getHolder();
             holder.addCallback(new SurfaceListener());
             holder.setFormat(PixelFormat.RGB_565);
         }
 
         mGLController = new GLController(this);
     }
 
-    void connect(LayerController controller) {
-        mController = controller;
-        mTouchEventHandler = new TouchEventHandler(getContext(), this, mController);
+    void connect(GeckoLayerClient layerClient) {
+        mLayerClient = layerClient;
+        mTouchEventHandler = new TouchEventHandler(getContext(), this, layerClient);
         mRenderer = new LayerRenderer(this);
         mInputConnectionHandler = null;
 
         setFocusable(true);
         setFocusableInTouchMode(true);
     }
 
     @Override
@@ -120,22 +117,22 @@ public class LayerView extends FrameLayo
         return mTouchEventHandler.handleEvent(event);
     }
 
     @Override
     public boolean onHoverEvent(MotionEvent event) {
         return mTouchEventHandler.handleEvent(event);
     }
 
-    public LayerController getController() { return mController; }
+    public GeckoLayerClient getLayerClient() { return mLayerClient; }
     public TouchEventHandler getTouchEventHandler() { return mTouchEventHandler; }
 
     /** The LayerRenderer calls this to indicate that the window has changed size. */
     public void setViewportSize(IntSize size) {
-        mController.setViewportSize(new FloatSize(size));
+        mLayerClient.setViewportSize(new FloatSize(size));
     }
 
     public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) {
         mInputConnectionHandler = inputConnectionHandler;
     }
 
     @Override
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
@@ -275,17 +272,17 @@ public class LayerView extends FrameLayo
             return mSurfaceView.getHolder();
 
         return mTextureView.getSurfaceTexture();
     }
 
     /** This function is invoked by Gecko (compositor thread) via JNI; be careful when modifying signature. */
     public static GLController registerCxxCompositor() {
         try {
-            LayerView layerView = GeckoApp.mAppContext.getLayerController().getView();
+            LayerView layerView = GeckoApp.mAppContext.getLayerClient().getView();
             layerView.mListener.compositorCreated();
             return layerView.getGLController();
         } catch (Exception e) {
             Log.e(LOGTAG, "Error registering compositor!", e);
             return null;
         }
     }
 
--- a/mobile/android/base/gfx/TouchEventHandler.java
+++ b/mobile/android/base/gfx/TouchEventHandler.java
@@ -120,27 +120,27 @@ public final class TouchEventHandler imp
     //   of the balance.)
     // - negative when we are in a state where we have received more ListenerTimeoutProcessors than
     //   default-prevented notifications. This means that the next ListenerTimeoutProcessor that
     //   we receive does correspond to the block at the head of the queue, but the next n
     //   default-prevented notifications need to be ignored as they are for blocks we have already
     //   processed. (n is the absolute value of the balance.)
     private int mProcessingBalance;
 
-    TouchEventHandler(Context context, LayerView view, LayerController controller) {
+    TouchEventHandler(Context context, LayerView view, GeckoLayerClient layerClient) {
         mView = view;
 
         mEventQueue = new LinkedList<MotionEvent>();
-        mGestureDetector = new GestureDetector(context, controller.getGestureListener());
-        mScaleGestureDetector = new SimpleScaleGestureDetector(controller.getScaleGestureListener());
-        mPanZoomController = controller.getPanZoomController();
+        mGestureDetector = new GestureDetector(context, layerClient.getGestureListener());
+        mScaleGestureDetector = new SimpleScaleGestureDetector(layerClient.getScaleGestureListener());
+        mPanZoomController = layerClient.getPanZoomController();
         mListenerTimeoutProcessor = new ListenerTimeoutProcessor();
         mDispatchEvents = true;
 
-        mGestureDetector.setOnDoubleTapListener(controller.getDoubleTapListener());
+        mGestureDetector.setOnDoubleTapListener(layerClient.getDoubleTapListener());
 
         Tabs.registerOnTabsChangedListener(this);
     }
 
     /* This function MUST be called on the UI thread */
     public boolean handleEvent(MotionEvent event) {
         // if we don't have gecko listeners, just dispatch the event
         // and be done with it, no extra work needed.
--- a/mobile/android/base/tests/MotionEventReplayer.java.in
+++ b/mobile/android/base/tests/MotionEventReplayer.java.in
@@ -74,17 +74,17 @@ class MotionEventReplayer {
     {
         // As an example, a line in the input stream might look like:
         //
         // MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=424.41055, y[0]=825.2412,
         //      toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0,
         //      edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=21972329,
         //      downTime=21972329, deviceId=6, source=0x1002 }
         //
-        // These can be generated by printing out event.toString() in LayerController's
+        // These can be generated by printing out event.toString() in LayerView'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.
         Pattern p = Pattern.compile("MotionEvent \\{ (.*?) \\}");
         Map<String, String> eventProperties = new HashMap<String, String>();
 
         boolean firstEvent = true;
         long timeDelta = 0L;