WIP on so many GeckoView things: Remote Debugger, Marionette, first paint, loadUri draft
authorNick Alexander <nalexander@mozilla.com>
Tue, 29 Nov 2016 12:26:08 -0800
changeset 445777 08da09b951ef60e9bdb4f95b1b8a024dbff9f1bb
parent 445776 8d85042bea8fb4f9a0c45056d17303ffdeaa4519
child 445778 2ffc75df008bb215f2fcc7b32d401af3a99d5cfe
push id37604
push usernalexander@mozilla.com
push dateWed, 30 Nov 2016 07:13:23 +0000
milestone53.0a1
WIP on so many GeckoView things: Remote Debugger, Marionette, first paint, loadUri MozReview-Commit-ID: Jjj7GqWqHoV
build.gradle
build/moz.configure/marionette.configure
mobile/android/app/build.gradle
mobile/android/app/omnijar/build.gradle
mobile/android/chrome/content/RemoteDebugger.js
mobile/android/chrome/content/geckoview.js
mobile/android/chrome/content/geckoview.xul
mobile/android/components/MobileComponents.manifest
mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
mobile/android/geckoview_example/build.gradle
mobile/android/geckoview_example/src/androidTest/java/org/mozilla/geckoview_example/GeckoViewActivityTest.java
mobile/android/geckoview_example/src/androidTest/java/org/mozilla/geckoview_example/SingleGeckoViewActivityTest.java
mobile/android/geckoview_example/src/main/AndroidManifest.xml
mobile/android/geckoview_example/src/main/assets/script.js
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewApplication.java
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/SingleGeckoViewActivity.java
mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml
mobile/android/modules/dbg-browser-actors.js
moz.configure
old-configure.in
widget/android/AndroidBridge.h
widget/android/AndroidJavaWrappers.h
--- a/build.gradle
+++ b/build.gradle
@@ -28,17 +28,17 @@ buildscript {
         }
         // For android-sdk-manager SNAPSHOT releases.
         maven {
             url "file://${gradle.mozconfig.topsrcdir}/mobile/android/gradle/m2repo"
         }
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.1.3'
+        classpath 'com.android.tools.build:gradle:2.2.2'
         classpath('com.stanfy.spoon:spoon-gradle-plugin:1.0.4') {
             // Without these, we get errors linting.
             exclude module: 'guava'
         }
         // Provided in tree.
         classpath 'com.jakewharton.sdkmanager:gradle-plugin:1.5.0-SNAPSHOT'
         classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.1'
     }
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/marionette.configure
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# Marionette is a Web Driver / Selenium comamnd server and client
+# automation driver for Mozilla's Gecko engine.  For more, see
+# https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette.
+option('--enable-marionette',
+       help='Enable internal Marionette command server')
+
+@depends('--enable-marionette', target, toolkit)
+def marionette(value, target, toolkit):
+    if value.origin != 'default':
+        return bool(value) or None
+
+    # Always enable Marionette if not Android or B2G.
+    if target.os == 'Android':
+        return False
+
+    if toolkit == 'gonk':
+        return False
+
+    return True
+
+set_config('ENABLE_MARIONETTE', marionette)
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -360,20 +360,20 @@ spoon {
 // See discussion at https://github.com/stanfy/spoon-gradle-plugin/issues/9.
 afterEvaluate {
     tasks["spoonLocal${android.testBuildType.capitalize()}AndroidTest"].outputs.upToDateWhen { false }
 
     // This is an awkward way to define different sets of instrumentation tests.
     // The task name itself is fished at runtime and the package name configured
     // in the spoon configuration.
     task runBrowserTests {
-        dependsOn tasks["spoonLocalOldDebugAndroidTest"]
+//        dependsOn tasks["spoonLocalOldDebugAndroidTest"]
     }
     task runBackgroundTests {
-        dependsOn tasks["spoonLocalOldDebugAndroidTest"]
+//        dependsOn tasks["spoonLocalOldDebugAndroidTest"]
     }
 }
 
 // Bug 1299015: Complain to treeherder if checkstyle, lint, or unittest fails.  It's not obvious
 // how to listen to individual errors in most cases, so we just link to the reports for now.
 def makeTaskExecutionListener(artifactRootUrl) {
     return new TaskExecutionListener() {
         void beforeExecute(Task task) {
--- a/mobile/android/app/omnijar/build.gradle
+++ b/mobile/android/app/omnijar/build.gradle
@@ -16,16 +16,17 @@ sourceSets {
         // Depend on the Gecko resources in mobile/android.
         resources {
             srcDir "${topsrcdir}/mobile/android/chrome"
             srcDir "${topsrcdir}/mobile/android/components"
             srcDir "${topsrcdir}/mobile/android/locales"
             srcDir "${topsrcdir}/mobile/android/modules"
             srcDir "${topsrcdir}/mobile/android/themes"
             srcDir "${topsrcdir}/toolkit"
+            srcDir "${topsrcdir}/widget/android"
         }
     }
 }
 
 apply plugin: 'idea'
 
 idea {
     module {
--- a/mobile/android/chrome/content/RemoteDebugger.js
+++ b/mobile/android/chrome/content/RemoteDebugger.js
@@ -62,39 +62,39 @@ var RemoteDebugger = {
   _promptForUSB(session) {
     if (session.authentication !== 'PROMPT') {
       // This dialog is not prepared for any other authentication method at
       // this time.
       return DebuggerServer.AuthenticationResult.DENY;
     }
 
     return new Promise(resolve => {
-      let title = Strings.browser.GetStringFromName("remoteIncomingPromptTitle");
-      let msg = Strings.browser.GetStringFromName("remoteIncomingPromptUSB");
-      let allow = Strings.browser.GetStringFromName("remoteIncomingPromptAllow");
-      let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny");
+      // let title = Strings.browser.GetStringFromName("remoteIncomingPromptTitle");
+      // let msg = Strings.browser.GetStringFromName("remoteIncomingPromptUSB");
+      // let allow = Strings.browser.GetStringFromName("remoteIncomingPromptAllow");
+      // let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny");
 
-      // Make prompt. Note: button order is in reverse.
-      let prompt = new Prompt({
-        window: null,
-        hint: "remotedebug",
-        title: title,
-        message: msg,
-        buttons: [ allow, deny ],
-        priority: 1
-      });
+      // // Make prompt. Note: button order is in reverse.
+      // let prompt = new Prompt({
+      //   window: null,
+      //   hint: "remotedebug",
+      //   title: title,
+      //   message: msg,
+      //   buttons: [ allow, deny ],
+      //   priority: 1
+      // });
 
-      prompt.show(data => {
-        let result = data.button;
-        if (result === 0) {
+      // prompt.show(data => {
+      //   let result = data.button;
+      //   if (result === 0) {
           resolve(DebuggerServer.AuthenticationResult.ALLOW);
-        } else {
-          resolve(DebuggerServer.AuthenticationResult.DENY);
-        }
-      });
+        // } else {
+        //   resolve(DebuggerServer.AuthenticationResult.DENY);
+        // }
+      // });
     });
   },
 
   _promptForTCP(session) {
     if (session.authentication !== 'OOB_CERT' || !session.client.cert) {
       // This dialog is not prepared for any other authentication method at
       // this time.
       return DebuggerServer.AuthenticationResult.DENY;
--- a/mobile/android/chrome/content/geckoview.js
+++ b/mobile/android/chrome/content/geckoview.js
@@ -6,30 +6,66 @@
 
 var Cc = Components.classes; /*global Components */
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils */
 
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+  "resource://gre/modules/BrowserUtils.jsm"); /*global BrowserUtils */
+
 XPCOMUtils.defineLazyModuleGetter(this, "Log",
- "resource://gre/modules/AndroidLog.jsm", "AndroidLog"); /*global Log */
+  "resource://gre/modules/AndroidLog.jsm", "AndroidLog"); /*global Log */
 
 XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
- "resource://gre/modules/Messaging.jsm", "Messaging"); /*global Messaging */
+  "resource://gre/modules/Messaging.jsm", "Messaging"); /*global Messaging */
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm", "Services"); /*global Services */
+  "resource://gre/modules/Services.jsm", "Services"); /*global Services */
 
 function dump(msg) {
-  Log.d("View", msg);
+  Log.d("ViewJS", msg);
 }
 
+// Strings is needed for RemoteDebugger.
+/**
+ * Cache of commonly used string bundles.
+ */
+var Strings = {
+  init: function () {
+    // XPCOMUtils.defineLazyGetter(Strings, "brand", () => Services.strings.createBundle("chrome://branding/locale/brand.properties"));
+    XPCOMUtils.defineLazyGetter(Strings, "browser", () => Services.strings.createBundle("chrome://browser/locale/browser.properties"));
+  },
+
+  flush: function () {
+    Services.strings.flushBundles();
+    this.init();
+  },
+};
+
+Strings.init();
+// End Strings.
+
+var lazilyLoadedBrowserScripts = [
+  ["RemoteDebugger", "chrome://browser/content/RemoteDebugger.js"], /*global RemoteDebugger */
+];
+
+lazilyLoadedBrowserScripts.forEach(function (aScript) {
+  let [name, script] = aScript;
+  XPCOMUtils.defineLazyGetter(window, name, function() {
+    let sandbox = {};
+    Services.scriptloader.loadSubScript(script, sandbox);
+    return sandbox[name];
+  });
+});
+
 var lazilyLoadedObserverScripts = [
+  ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
 ];
 
 lazilyLoadedObserverScripts.forEach(function (data) {
   let [name, notifications, script] = data;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
     Services.scriptloader.loadSubScript(script, sandbox);
@@ -40,14 +76,223 @@ lazilyLoadedObserverScripts.forEach(func
     Services.obs.addObserver(window[name], t, false);
     window[name].observe(s, t, d); // Explicitly notify new observer.
   };
   notifications.forEach((notification) => {
     Services.obs.addObserver(observer, notification, false);
   });
 });
 
-function startup() {
+var GeckoView = {
+  browser: null,
+
+  // Needed for Marionette.
+  get selectedBrowser() {
+    return this.browser;
+  },
+
+  // Needed for Marionette.
+  get selectedTab() {
+    return this.browser;
+  },
+
+  // Needed for Marionette.
+  getBrowserForTab(tab) {
+    return tab;
+  },
+
+  startup() {
+    this.browser = document.getElementById("content");
+    this.browser.setAttribute("type", "content-targetable");
+    this.browser.setAttribute("messagemanagergroup", "browsers");
+
+    // Needed for Marionette.
+    this.browser.permanentKey = {};
+
+    dump(`browser ${this.browser}`);
+
+    window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess(this.browser);
+
+    Services.androidBridge.browserApp = this;
+    Services.obs.addObserver(this, "before-first-paint", false);
+
+    RemoteDebugger.init();
+    ViewportHandler.init();
+ 
     dump("zerdatime " + Date.now() + " - GeckoView chrome startup finished.");
 
     // Notify Java that this GeckoView has loaded.
     Messaging.sendRequest({ type: "GeckoView:Ready" });
+
+    // Needed for Marionette?
+    Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
+  },
+
+  contentDocumentChanged: function() {
+    dump("contentDocumentChanged");
+    window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint = true;
+    Services.androidBridge.contentDocumentChanged(window);
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "before-first-paint":
+        dump("before-first-paint");
+        // Is it on the top level?
+        let contentDocument = aSubject;
+        if (contentDocument == this.browser.contentDocument) {
+          this.contentDocumentChanged();
+          dump("before-first-paint: contentDocumentChanged() called");
+        } else {
+          dump("before-first-paint: contentDocumentChanged() NOT called");
+        }
+        break;
+    }
+  },
+};
+
+const ViewportHandler = {
+  init: function init() {
+    Services.obs.addObserver(this, "Window:Resize", false);
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    dump(`observe ${aSubject} ${aTopic} ${aData}`);
+    if (aTopic == "Window:Resize" && aData) {
+      dump(`Window:Resize ${aData}`);
+      let scrollChange = JSON.parse(aData);
+      let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+      windowUtils.setNextPaintSyncId(scrollChange.id);
+    }
+  }
+};
+
+function nsBrowserAccess() {
 }
+
+nsBrowserAccess.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow]),
+
+  // _getBrowser: function _getBrowser(aURI, aOpener, aWhere, aFlags) {
+    // let isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+    // if (isExternal && aURI && aURI.schemeIs("chrome"))
+    //   return null;
+
+    // let loadflags = isExternal ?
+    //                   Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
+    //                   Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+    // if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+    //   if (isExternal) {
+    //     aWhere = Services.prefs.getIntPref("browser.link.open_external");
+    //   } else {
+    //     aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
+    //   }
+    // }
+
+    // Services.io.offline = false;
+
+    // let referrer;
+    // if (aOpener) {
+    //   try {
+    //     let location = aOpener.location;
+    //     referrer = Services.io.newURI(location, null, null);
+    //   } catch(e) { }
+    // }
+
+    // let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+    // let pinned = false;
+
+    // if (aURI && aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB) {
+    //   pinned = true;
+    //   let spec = aURI.spec;
+    //   let tabs = BrowserApp.tabs;
+    //   for (let i = 0; i < tabs.length; i++) {
+    //     let appOrigin = ss.getTabValue(tabs[i], "appOrigin");
+    //     if (appOrigin == spec) {
+    //       let tab = tabs[i];
+    //       BrowserApp.selectTab(tab);
+    //       return tab.browser;
+    //     }
+    //   }
+    // }
+
+    // // If OPEN_SWITCHTAB was not handled above, we need to open a new tab,
+    // // along with other OPEN_ values that create a new tab.
+    // let newTab = (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW ||
+    //               aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB ||
+    //               aWhere == Ci.nsIBrowserDOMWindow.OPEN_SWITCHTAB);
+    // let isPrivate = false;
+
+    // if (newTab) {
+    //   let parentId = -1;
+    //   if (!isExternal && aOpener) {
+    //     let parent = BrowserApp.getTabForWindow(aOpener.top);
+    //     if (parent) {
+    //       parentId = parent.id;
+    //       isPrivate = PrivateBrowsingUtils.isBrowserPrivate(parent.browser);
+    //     }
+    //   }
+
+    //   let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
+    //   // BrowserApp.addTab calls loadURIWithFlags with the appropriate params
+    //   let tab = BrowserApp.addTab(aURI ? aURI.spec : "about:blank", { flags: loadflags,
+    //                                                                   referrerURI: referrer,
+    //                                                                   external: isExternal,
+    //                                                                   parentId: parentId,
+    //                                                                   opener: openerWindow,
+    //                                                                   selected: true,
+    //                                                                   isPrivate: isPrivate,
+    //                                                                   pinned: pinned });
+
+    //   return tab.browser;
+    // }
+
+    // // OPEN_CURRENTWINDOW and illegal values
+    // let browser = BrowserApp.selectedBrowser;
+    // if (aURI && browser) {
+    //   browser.loadURIWithFlags(aURI.spec, loadflags, null, null, null);
+    // }
+
+    // return browser;
+  // },
+
+  openURI(aURI, aOpener, aWhere, aFlags) {
+    dump(`openURI: ${aURI}`);
+    // let browser = this._getBrowser(aURI, aOpener, aWhere, aFlags);
+    // TODO: error check, throw if parameters don't make sense for GeckoView.
+    GeckoView.browser.loadURIWithFlags(aURI.spec, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+    GeckoView.contentDocumentChanged();
+
+    return GeckoView.browser.contentWindow;
+  },
+
+  openURIInFrame(aURI, aParams, aWhere, aFlags) {
+    dump(`openURIInFrame: ${aURI}`);
+    // let browser = this._getBrowser(aURI, null, aWhere, aFlags);
+    // TODO: error check, throw if parameters don't make sense for GeckoView.
+    GeckoView.browser.loadURIWithFlags(aURI.spec, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+    GeckoView.contentDocumentChanged();
+
+    return GeckoView.browser.QueryInterface(Ci.nsIFrameLoaderOwner);
+  },
+
+  isTabContentWindow(aWindow) {
+    return GeckoView.browser.contentWindow == aWindow;
+  },
+
+  canClose() {
+    return BrowserUtils.canCloseWindow(window);
+  },
+};
+
+console.log("I GOT HERE");
+
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+Services.prefs.setBoolPref("devtools.remote.usb.enabled", true);
+Services.prefs.setBoolPref("devtools.remote.wifi.enabled", true);
+// Absolute path to the devtools unix domain socket file used
+// to communicate with a usb cable via adb forward.
+
+// TEST
+Services.prefs.setCharPref("devtools.debugger.unix-domain-socket", "/data/data/org.mozilla.geckoview_example/firefox-debugger-socket");
+
+// Needed for Marionette.
+window.BrowserApp = GeckoView;
--- a/mobile/android/chrome/content/geckoview.xul
+++ b/mobile/android/chrome/content/geckoview.xul
@@ -1,16 +1,16 @@
 <?xml version="1.0"?>
 <!-- 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/. -->
 
 <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
 
 <window id="main-window"
-        onload="startup();"
+        onload="GeckoView.startup();"
         windowtype="navigator:browser"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <browser id="content" type="content-primary" src="https://mozilla.com" flex="1" remote="true"/>
 
   <script type="application/javascript" src="chrome://browser/content/geckoview.js"/>
 </window>
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -22,19 +22,19 @@ contract @mozilla.org/network/protocol/a
 component {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b} DirectoryProvider.js
 contract @mozilla.org/browser/directory-provider;1 {ef0f7a87-c1ee-45a8-8d67-26f586e46a4b}
 category xpcom-directory-providers browser-directory-provider @mozilla.org/browser/directory-provider;1
 
 # stylesheets
 category agent-style-sheets browser-content-stylesheet chrome://browser/skin/content.css
 
 # SessionStore.js
-component {8c1f07d6-cba3-4226-a315-8bd43d67d032} SessionStore.js
-contract @mozilla.org/browser/sessionstore;1 {8c1f07d6-cba3-4226-a315-8bd43d67d032}
-category app-startup SessionStore service,@mozilla.org/browser/sessionstore;1
+# component {8c1f07d6-cba3-4226-a315-8bd43d67d032} SessionStore.js
+# contract @mozilla.org/browser/sessionstore;1 {8c1f07d6-cba3-4226-a315-8bd43d67d032}
+# category app-startup SessionStore service,@mozilla.org/browser/sessionstore;1
 
 # ContentPermissionPrompt.js
 component {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5} ContentPermissionPrompt.js
 contract @mozilla.org/content-permission/prompt;1 {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}
 
 # PromptService.js
 component {9a61149b-2276-4a0a-b79c-be994ad106cf} PromptService.js
 contract @mozilla.org/prompter;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/BaseGeckoInterface.java
@@ -45,16 +45,17 @@ public class BaseGeckoInterface implemen
         if (mProfile == null) {
             mProfile = GeckoProfile.get(mContext);
         }
         return mProfile;
     }
 
     @Override
     public Activity getActivity() {
+        // return null;
         return (Activity)mContext;
     }
 
     @Override
     public String getDefaultUAString() {
         return HardwareUtils.isTablet() ? BuildConfig.USER_AGENT_GECKOVIEW_TABLET :
                                           BuildConfig.USER_AGENT_GECKOVIEW_MOBILE;
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -746,18 +746,23 @@ public class GeckoAppShell
             break;
         default:
             Log.w(LOGTAG, "Error! Can't disable unknown SENSOR type " + aSensortype);
         }
     }
 
     @WrapForJNI(calledFrom = "gecko")
     private static void moveTaskToBack() {
-        if (getGeckoInterface() != null)
-            getGeckoInterface().getActivity().moveTaskToBack(true);
+        final GeckoInterface gi = getGeckoInterface();
+        if (gi != null) {
+            final Activity ga = gi.getActivity();
+            if (ga != null) {
+                getGeckoInterface().getActivity().moveTaskToBack(true);
+            }
+        }
     }
 
     @WrapForJNI(calledFrom = "gecko")
     public static void scheduleRestart() {
         getGeckoInterface().doRestart();
     }
 
     // Creates a homescreen shortcut for a web page.
@@ -1963,18 +1968,18 @@ public class GeckoAppShell
     /* Called by JNI from AndroidBridge, and by reflection from tests/BaseTest.java.in */
     @WrapForJNI(calledFrom = "gecko")
     @RobocopTarget
     public static boolean isTablet() {
         return HardwareUtils.isTablet();
     }
 
     private static boolean sImeWasEnabledOnLastResize = false;
-    public static void viewSizeChanged() {
-        GeckoView v = (GeckoView) getLayerView();
+    public static void viewSizeChanged(GeckoView v) {
+//        GeckoView v = (GeckoView) getLayerView();
         if (v == null) {
             return;
         }
         boolean imeIsEnabled = v.isIMEEnabled();
         if (imeIsEnabled && !sImeWasEnabledOnLastResize) {
             // The IME just came up after not being up, so let's scroll
             // to the focused input.
             notifyObservers("ScrollTo:FocusedInput", "");
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
@@ -2,21 +2,23 @@
  * 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.annotation.WrapForJNI;
 
+import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.util.Log;
 import android.view.Surface;
 import android.app.Activity;
+import android.view.WindowManager;
 
 import java.util.Arrays;
 import java.util.List;
 
 /*
  * Updates, locks and unlocks the screen orientation.
  *
  * Note: Replaces the OnOrientationChangeListener to avoid redundant rotation
@@ -106,21 +108,21 @@ public class GeckoScreenOrientation {
 
     /*
      * Update screen orientation.
      * Retrieve orientation and rotation via GeckoAppShell.
      *
      * @return Whether the screen orientation has changed.
      */
     public boolean update() {
-        Activity activity = getActivity();
-        if (activity == null) {
+        final Context appContext = GeckoAppShell.getApplicationContext();
+        if (appContext == null) {
             return false;
         }
-        Configuration config = activity.getResources().getConfiguration();
+        Configuration config = appContext.getResources().getConfiguration();
         return update(config.orientation);
     }
 
     /*
      * Update screen orientation given the android orientation.
      * Retrieve rotation via GeckoAppShell.
      *
      * @param aAndroidOrientation
@@ -216,37 +218,30 @@ public class GeckoScreenOrientation {
      * @return Whether the unlocking was successful.
      */
     public boolean unlock() {
         Log.d(LOGTAG, "unlocking");
         setRequestedOrientation(mDefaultScreenOrientation);
         return update();
     }
 
-    private Activity getActivity() {
-        if (GeckoAppShell.getGeckoInterface() == null) {
-            return null;
-        }
-        return GeckoAppShell.getGeckoInterface().getActivity();
-    }
-
     /*
      * Set the given requested orientation for the current activity.
      * This is essentially an unlock without an update.
      *
      * @param aScreenOrientation
      *        Gecko screen orientation.
      *
      * @return Whether the requested orientation was set. This can only fail if
      *         the current activity cannot be retrieved via GeckoAppShell.
      *
      */
     private boolean setRequestedOrientation(ScreenOrientation aScreenOrientation) {
         int activityOrientation = screenOrientationToActivityInfoOrientation(aScreenOrientation);
-        Activity activity = getActivity();
+        Activity activity = null; // TODO: getActivity();
         if (activity == null) {
             Log.w(LOGTAG, "setRequestOrientation: failed to get activity");
             return false;
         }
         if (activity.getRequestedOrientation() == activityOrientation) {
             return false;
         }
         activity.setRequestedOrientation(activityOrientation);
@@ -299,25 +294,26 @@ public class GeckoScreenOrientation {
                 return 270;
             default:
                 Log.w(LOGTAG, "getAngle: unexpected rotation value");
                 return 0;
         }
     }
 
     /*
-     * @return Device rotation from Display.getRotation().
+     * @return Device rotation.
      */
     private int getRotation() {
-        Activity activity = getActivity();
-        if (activity == null) {
-            Log.w(LOGTAG, "getRotation: failed to get activity");
+        final Context appContext = GeckoAppShell.getApplicationContext();
+        if (appContext == null) {
             return DEFAULT_ROTATION;
         }
-        return activity.getWindowManager().getDefaultDisplay().getRotation();
+        final WindowManager windowManager =
+                (WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE);
+        return windowManager.getDefaultDisplay().getRotation();
     }
 
     /*
      * Retrieve the screen orientation from an array string.
      *
      * @param aArray
      *        String containing comma-delimited strings.
      *
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoView.java
@@ -218,16 +218,18 @@ public class GeckoView extends LayerView
     }
 
     public GeckoView(Context context, AttributeSet attrs) {
         super(context, attrs);
         init(context);
     }
 
     private void init(Context context) {
+        Log.w(LOGTAG, "init: " + this);
+
         if (GeckoAppShell.getApplicationContext() == null) {
             GeckoAppShell.setApplicationContext(context.getApplicationContext());
         }
 
         // Set the GeckoInterface if the context is an activity and the GeckoInterface
         // has not already been set
         if (context instanceof Activity && getGeckoInterface() == null) {
             setGeckoInterface(new BaseGeckoInterface(context));
@@ -238,24 +240,26 @@ public class GeckoView extends LayerView
         GeckoAppShell.setLayerView(this);
 
         initializeView();
     }
 
     @Override
     protected Parcelable onSaveInstanceState()
     {
+        Log.w(LOGTAG, "onSaveInstanceState: " + this);
         final Parcelable superState = super.onSaveInstanceState();
         stateSaved = true;
         return new StateBinder(superState, this.window);
     }
 
     @Override
     protected void onRestoreInstanceState(final Parcelable state)
     {
+        Log.w(LOGTAG, "onRestoreInstanceState: " + this);
         final StateBinder stateBinder = (StateBinder) state;
 
         if (stateBinder.window != null) {
             this.window = stateBinder.window;
         }
         stateSaved = false;
 
         if (onAttachedToWindowCalled) {
@@ -263,44 +267,47 @@ public class GeckoView extends LayerView
         }
 
         // We have to always call super.onRestoreInstanceState because View keeps
         // track of these calls and throws an exception when we don't call it.
         super.onRestoreInstanceState(stateBinder.superState);
     }
 
     protected void openWindow() {
+        Log.w(LOGTAG, "openWindow: " + this);
         if (chromeURI == null) {
             chromeURI = getGeckoInterface().getDefaultChromeURI();
         }
 
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             Window.open(window, this, getCompositor(), eventDispatcher,
                         chromeURI, screenId);
         } else {
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, Window.class,
                     "open", window, GeckoView.class, this, Object.class, getCompositor(),
                     EventDispatcher.class, eventDispatcher,
                     String.class, chromeURI, screenId);
         }
     }
 
     protected void reattachWindow() {
+        Log.w(LOGTAG, "reattachWindow: " + this);
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             window.reattach(this, getCompositor(), eventDispatcher);
         } else {
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     window, "reattach", GeckoView.class, this,
                     Object.class, getCompositor(), EventDispatcher.class, eventDispatcher);
         }
     }
 
     @Override
     public void onAttachedToWindow()
     {
+        Log.w(LOGTAG, "onAttachedToWindow: " + this + ", window: " + window);
         final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
 
         if (window == null) {
             // Open a new nsWindow if we didn't have one from before.
             window = new Window();
             openWindow();
         } else {
             reattachWindow();
@@ -309,16 +316,17 @@ public class GeckoView extends LayerView
         super.onAttachedToWindow();
 
         onAttachedToWindowCalled = true;
     }
 
     @Override
     public void onDetachedFromWindow()
     {
+        Log.w(LOGTAG, "onDetachedFromWindow: " + this);
         super.onDetachedFromWindow();
         super.destroy();
 
         if (stateSaved) {
             // If we saved state earlier, we don't want to close the nsWindow.
             return;
         }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -1,15 +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.gfx;
 
+import org.mozilla.gecko.GeckoView;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.gfx.LayerView.DrawListener;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.AppConstants;
 
 import android.content.Context;
@@ -120,16 +121,17 @@ class GeckoLayerClient implements LayerV
     @Override // PanZoomTarget
     public boolean isGeckoReady() {
         return mGeckoIsReady;
     }
 
     /** Attaches to root layer so that Gecko appears. */
     @WrapForJNI(calledFrom = "gecko")
     private void onGeckoReady() {
+        Log.w(LOGTAG, "onGeckoReady");
         mGeckoIsReady = true;
 
         mLayerRenderer = mView.getRenderer();
 
         sendResizeEventIfNecessary(true, null);
 
         // Gecko being ready is one of the two conditions (along with having an available
         // surface) that cause us to create the compositor. So here, now that we know gecko
@@ -183,17 +185,17 @@ class GeckoLayerClient implements LayerV
             // 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, scrollChange);
 
             // 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();
+            GeckoAppShell.viewSizeChanged((GeckoView) mView);
         }
         return true;
     }
 
     PanZoomController getPanZoomController() {
         return mPanZoomController;
     }
 
@@ -229,45 +231,36 @@ class GeckoLayerClient implements LayerV
 
         if (mView != null) {
             mView.notifySizeChanged(mWindowSize.width, mWindowSize.height,
                                     mScreenSize.width, mScreenSize.height);
         }
 
         String json = "";
         try {
+            int id = ++sPaintSyncId;
+            if (id == 0) {
+                // never use 0 as that is the default value for "this is not
+                // a special transaction"
+                id = ++sPaintSyncId;
+            }
+            JSONObject jsonObj = new JSONObject();
             if (scrollChange != null) {
-                int id = ++sPaintSyncId;
-                if (id == 0) {
-                    // never use 0 as that is the default value for "this is not
-                    // a special transaction"
-                    id = ++sPaintSyncId;
-                }
-                JSONObject jsonObj = new JSONObject();
                 jsonObj.put("x", scrollChange.x / mViewportMetrics.zoomFactor);
                 jsonObj.put("y", scrollChange.y / mViewportMetrics.zoomFactor);
-                jsonObj.put("id", id);
-                json = jsonObj.toString();
             }
+            jsonObj.put("id", id);
+            json = jsonObj.toString();
         } catch (Exception e) {
             Log.e(LOGTAG, "Unable to convert point to JSON", e);
         }
+        Log.i(LOGTAG, "Sending Window:Resize: '" + json + "'");
         GeckoAppShell.notifyObservers("Window:Resize", json);
     }
 
-    /**
-     * 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
-    }
-
     @WrapForJNI(calledFrom = "gecko")
     void contentDocumentChanged() {
         mContentDocumentIsDisplayed = false;
     }
 
     @WrapForJNI(calledFrom = "gecko")
     boolean isContentDocumentDisplayed() {
         return mContentDocumentIsDisplayed;
@@ -277,16 +270,17 @@ class GeckoLayerClient implements LayerV
       * 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.
       */
     @WrapForJNI
     public void setFirstPaintViewport(float offsetX, float offsetY, float zoom,
             float cssPageLeft, float cssPageTop, float cssPageRight, float cssPageBottom) {
         synchronized (getLock()) {
+            Log.w(LOGTAG, "setFirstPaintViewport");
             ImmutableViewportMetrics currentMetrics = getViewportMetrics();
 
             RectF cssPageRect = new RectF(cssPageLeft, cssPageTop, cssPageRight, cssPageBottom);
             RectF pageRect = RectUtils.scaleAndRound(cssPageRect, zoom);
 
             final ImmutableViewportMetrics newMetrics = currentMetrics
                 .setViewportOrigin(offsetX, offsetY)
                 .setZoomFactor(zoom)
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
@@ -24,16 +24,17 @@ import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.InputDevice;
@@ -98,17 +99,20 @@ public class LayerView extends FrameLayo
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         /* package */ native void syncResumeResizeCompositor(int width, int height, Object surface);
 
         @WrapForJNI(calledFrom = "any", dispatchTo = "current")
         /* package */ native void syncInvalidateAndScheduleComposite();
 
         @WrapForJNI(calledFrom = "gecko")
         private void reattach() {
+            Log.w("GeckoCompositor", "reattach");
             mCompositorCreated = true;
+            LayerView.this.setPaintState(LayerView.PAINT_START);
+            requestRender();
         }
 
         @WrapForJNI(calledFrom = "gecko")
         private void destroy() {
             // The nsWindow has been closed. First mark our compositor as destroyed.
             LayerView.this.mCompositorCreated = false;
 
             LayerView.this.mLayerClient.setGeckoReady(false);
@@ -400,16 +404,17 @@ public class LayerView extends FrameLayo
         mListener = listener;
     }
 
     Listener getListener() {
         return mListener;
     }
 
     private void attachCompositor() {
+        Log.w(LOGTAG, "attachCompositor");
         final NativePanZoomController npzc = (NativePanZoomController) mPanZoomController;
 
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             mCompositor.attachToJava(mLayerClient, npzc);
         } else {
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                     mCompositor, "attachToJava",
                     GeckoLayerClient.class, mLayerClient,
@@ -427,18 +432,19 @@ public class LayerView extends FrameLayo
 
         mWidth = newWidth;
         mHeight = newHeight;
         mServerSurfaceValid = true;
 
         updateCompositor();
     }
 
-    void updateCompositor() {
+    public void updateCompositor() {
         ThreadUtils.assertOnUiThread();
+        Log.w(LOGTAG, "updateCompositor");
 
         if (mCompositorCreated) {
             // If the compositor has already been created, just resume it instead. We don't need
             // to block here because if the surface is destroyed before the compositor grabs it,
             // we can handle that gracefully (i.e. the compositor will remain paused).
             if (!mServerSurfaceValid) {
                 return;
             }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/mozglue/GeckoLoader.java
@@ -218,16 +218,18 @@ public final class GeckoLoader {
         putenv("MOZ_LINKER_EXTRACT=1");
         // Ensure that the cache dir is world-writable
         File cacheDir = new File(linkerCache);
         if (cacheDir.isDirectory()) {
             cacheDir.setWritable(true, false);
             cacheDir.setExecutable(true, false);
             cacheDir.setReadable(true, false);
         }
+
+        putenv("MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS={\"logging\":true,\"defaultPrefs.enabled\": true}");
     }
 
     @RobocopTarget
     public synchronized static void loadSQLiteLibs(final Context context, final String apkName) {
         if (sSQLiteLibsLoaded) {
             return;
         }
 
@@ -557,16 +559,19 @@ public final class GeckoLoader {
             thread.getUncaughtExceptionHandler();
         if (uncaughtHandler != null) {
             uncaughtHandler.uncaughtException(thread, new AbortException(msg));
         }
     }
 
     // These methods are implemented in mozglue/android/nsGeckoUtils.cpp
     private static native void putenv(String map);
+    public static void putenv2(String map) {
+        GeckoLoader.putenv(map);
+    }
 
     // These methods are implemented in mozglue/android/APKOpen.cpp
     public static native void nativeRun(String[] args, int crashFd, int ipcFd);
     private static native void loadGeckoLibsNative(String apkName);
     private static native void loadSQLiteLibsNative(String apkName);
     private static native void loadNSSLibsNative(String apkName);
     private static native void extractGeckoLibsNative(String apkName);
 }
--- a/mobile/android/geckoview_example/build.gradle
+++ b/mobile/android/geckoview_example/build.gradle
@@ -13,17 +13,21 @@ android {
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
     // This is extremely frustrating, but the only way to do it automation for
     // now.  Without this, we only get a "debugAndroidTest" configuration; we
     // have no "withoutGeckoBinariesAndroidTest" configuration.
-    testBuildType "withoutGeckoBinaries"
+    if (mozconfig.substs.MOZILLA_OFFICIAL) {
+        testBuildType "withoutGeckoBinaries"
+    } else {
+        testBuildType "withGeckoBinaries"
+    }
     buildTypes {
         release {
             minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
         withGeckoBinaries { // For consistency with :geckoview project in Task Cluster invocations.
             initWith debug
         }
@@ -40,16 +44,23 @@ dependencies {
 
     androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
     androidTestCompile 'com.android.support.test:runner:0.5'
     // Not defining this library again results in test-app assuming 23.1.1, and the following errors:
     // "Conflict with dependency 'com.android.support:support-annotations'. Resolved versions for app (23.4.0) and test app (23.1.1) differ."
     androidTestCompile 'com.android.support:support-annotations:23.4.0'
 
     compile project(':geckoview')
+
+    dependencies {
+        withGeckoBinariesCompile 'io.palaima.debugdrawer:debugdrawer:0.7.0'
+        releaseCompile 'io.palaima.debugdrawer:debugdrawer-no-op:0.7.0'
+
+        compile 'io.palaima.debugdrawer:debugdrawer-actions:0.7.0'
+    }
 }
 
 apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
 
 android.applicationVariants.all { variant ->
     // Like 'debug', 'release', or 'withoutGeckoBinaries'.
     def buildType = variant.buildType.name
 
deleted file mode 100644
--- a/mobile/android/geckoview_example/src/androidTest/java/org/mozilla/geckoview_example/GeckoViewActivityTest.java
+++ /dev/null
@@ -1,32 +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.geckoview_example;
-
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-
-@RunWith(AndroidJUnit4.class)
-public class GeckoViewActivityTest {
-
-    @Rule
-    public ActivityTestRule<GeckoViewActivity> mActivityRule = new ActivityTestRule(GeckoViewActivity.class);
-
-    @Test
-    public void testA() throws InterruptedException {
-        onView(withId(R.id.gecko_view))
-                .check(matches(isDisplayed()));
-    }
-}
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview_example/src/androidTest/java/org/mozilla/geckoview_example/SingleGeckoViewActivityTest.java
@@ -0,0 +1,31 @@
+/* -*- 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.geckoview_example;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+@RunWith(AndroidJUnit4.class)
+public class SingleGeckoViewActivityTest {
+
+    @Rule
+    public ActivityTestRule<SingleGeckoViewActivity> mActivityRule = new ActivityTestRule(SingleGeckoViewActivity.class);
+
+    @Test
+    public void testLoadUri() throws InterruptedException {
+        onView(withId(R.id.gecko_view))
+                .check(matches(isDisplayed()));
+    }
+}
--- a/mobile/android/geckoview_example/src/main/AndroidManifest.xml
+++ b/mobile/android/geckoview_example/src/main/AndroidManifest.xml
@@ -1,18 +1,19 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="org.mozilla.geckoview_example">
 
     <application android:allowBackup="true"
                  android:label="@string/app_name"
+                 android:name=".GeckoViewApplication"
                  android:supportsRtl="true">
 
         <uses-library android:name="android.test.runner" />
 
-        <activity android:name="org.mozilla.geckoview_example.GeckoViewActivity"
+        <activity android:name="org.mozilla.geckoview_example.SingleGeckoViewActivity"
                   android:label="GeckoViewActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview_example/src/main/assets/script.js
@@ -0,0 +1,45 @@
+/*
+ * Sample script injected into the 'chrome' scope of the GeckoView.
+ * This script sets up a simple system of injecting a JS object into all content loaded
+ * into the GeckoView.
+ */
+
+const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+console.log(`Injected script evaluating: ${GeckoView}`);
+
+function load() {
+  console.log(`Injected script loaded: ${arguments}`);
+}
+
+// function onWindowCreated(event) {
+//   // the target is an HTMLDocument
+//   let contentDocument = event.target;
+
+//   // We need the unprotected version of the contentWindow for injecting JS objects
+//   let unsafeWindow = contentDocument.defaultView.wrappedJSObject;
+
+//   let contentObject = {
+//     message: "Hello from an injected script",
+//     fetch: function(callback) {
+//       // Try calling into the host application to get some data and return it to content
+//       return GeckoView.sendRequestForResult({ type: "fetch" }).then(result => {
+//         // We need to Cu.cloneInto the 'chrome' result back into the 'content' callback
+//         callback(Cu.cloneInto(result, unsafeWindow));
+//       });
+//     }
+//   };
+
+//   // Use Cu.cloneInto to add the contentObject into the content window
+//   // https://developer.mozilla.org/en-US/docs/Components.utils.cloneInto
+//   unsafeWindow.special = Cu.cloneInto(contentObject, unsafeWindow, { cloneFunctions: true });
+// }
+
+// function onPageLoad(event) {
+//   // the target is an HTMLDocument
+//   let contentDocument = event.target;
+
+//   // We can get the <browser> element used to host the content
+//   let browser = BrowserApp.getBrowserForDocument(contentDocument);
+//   console.log("Page loaded: " + browser.contentTitle);
+// }
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewApplication.java
@@ -0,0 +1,28 @@
+/* -*- 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.geckoview_example;
+
+import android.app.Application;
+import android.util.Log;
+
+import org.mozilla.gecko.BaseGeckoInterface;
+import org.mozilla.gecko.GeckoAppShell;
+
+import static org.mozilla.gecko.GeckoView.setGeckoInterface;
+
+public class GeckoViewApplication extends Application {
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        GeckoAppShell.setApplicationContext(this);
+
+        final GeckoAppShell.GeckoInterface geckoInterface = new BaseGeckoInterface(this);
+        setGeckoInterface(geckoInterface);
+
+        Log.w("GeckoVA", "geckoInterface.getAppEventDispatcher(): " + geckoInterface.getAppEventDispatcher());
+    }
+}
rename from mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
rename to mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/SingleGeckoViewActivity.java
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/SingleGeckoViewActivity.java
@@ -1,81 +1,162 @@
 /* -*- 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.geckoview_example;
 
-import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 import android.util.Log;
+import android.view.View;
 import android.widget.Toast;
 
-import org.mozilla.gecko.BaseGeckoInterface;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.GeckoScreenOrientation;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.GeckoView;
 import org.mozilla.gecko.PrefsHelper;
+import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 
-import static org.mozilla.gecko.GeckoView.setGeckoInterface;
+import io.palaima.debugdrawer.DebugDrawer;
+import io.palaima.debugdrawer.actions.ActionsModule;
+import io.palaima.debugdrawer.actions.ButtonAction;
+
+import static org.mozilla.gecko.GeckoView.LOAD_DEFAULT;
 
-public class GeckoViewActivity extends Activity {
-    private static final String LOGTAG = "GeckoViewActivity";
+public class SingleGeckoViewActivity extends FragmentActivity {
+    private static final String LOGTAG = "SingleGeckoViewActivity";
 
-    GeckoView mGeckoView;
+    private DebugDrawer debugDrawer;
+    private GeckoView mGeckoView;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        Log.e("GeckoVA", "onCreate");
+
         super.onCreate(savedInstanceState);
 
-        final GeckoAppShell.GeckoInterface geckoInterface = new BaseGeckoInterface(this);
-        setGeckoInterface(geckoInterface);
-
-        Log.w("GeckoVA", "geckoInterface.getAppEventDispatcher(): " + geckoInterface.getAppEventDispatcher());
-
         // Gecko:Ready is a Gecko-wide thing.  GeckoView:Ready is per view?
-        geckoInterface.getAppEventDispatcher().registerUiThreadListener(
+        GeckoAppShell.getGeckoInterface().getAppEventDispatcher().registerUiThreadListener(
                 new BundleEventListener() {
                     @Override
                     public void handleMessage(String event, GeckoBundle message, EventCallback callback) {
                         Log.w("GeckoVA", "handleMessage: " + event);
+                        if ("GeckoView:Ready".equals(message)) {
+                            mGeckoView.setOverScrollMode(View.OVER_SCROLL_NEVER);
+                        }
                         if (callback != null) {
                             callback.sendSuccess(null);
                         }
                     }
                 },
                 "GeckoView:Ready");
 
+        final GeckoProfile profile = GeckoProfile.get(getApplicationContext());
+
+
+        //  * to the values of those prefs. So something like {"defaultPrefs.enabled": true}
+        // GeckoLoader.putenv2("MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS={\"logging\":true,\"defaultPrefs.enabled\": true}");
+
+        GeckoThread.init(profile, /* args */ null, /* action */ null, /* debugging */ false);
+        GeckoThread.launch();
+
+        GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
+
         setContentView(R.layout.geckoview_activity);
 
         mGeckoView = (GeckoView) findViewById(R.id.gecko_view);
-        mGeckoView.setChromeDelegate(new MyGeckoViewChrome());
-        mGeckoView.setContentDelegate(new MyGeckoViewContent());
+
+//        mGeckoView.setChromeDelegate(new MyGeckoViewChrome());
+//        mGeckoView.setContentDelegate(new MyGeckoViewContent());
 
 //        mGeckoView.getEventDispatcher().registerGeckoThreadListener(mGeckoView,
 //                "Gecko:Ready",
 //                "GeckoView:Message"
 //                );
+
+        ButtonAction buttonAction0 = new ButtonAction("log marker", new ButtonAction.Listener() {
+            @Override
+            public void onClick() {
+                Log.w(LOGTAG, "MARKER MARKER MARKER MARKER MARKER");
+            }
+        });
+
+        ButtonAction buttonAction1 = new ButtonAction("loadUri(\"https://google.com\"", new ButtonAction.Listener() {
+            @Override
+            public void onClick() {
+                // mGeckoView.updateCompositor();
+                // Toast.makeText(SingleGeckoViewActivity.this, "updateCompositor", Toast.LENGTH_LONG).show();
+                Log.w(LOGTAG, "loadUri(\"https://google.com\"");
+                mGeckoView.loadUri("https://google.com", LOAD_DEFAULT);
+            }
+        });
+
+        ButtonAction buttonAction2 = new ButtonAction("loadUri(\"https://reddit.com\"", new ButtonAction.Listener() {
+            @Override
+            public void onClick() {
+                // mGeckoView.updateCompositor();
+                // Toast.makeText(SingleGeckoViewActivity.this, "updateCompositor", Toast.LENGTH_LONG).show();
+                Log.w(LOGTAG, "loadUri(\"https://reddit.com\"");
+                mGeckoView.loadUri("https://reddit.com", LOAD_DEFAULT);
+            }
+        });
+
+        debugDrawer = new DebugDrawer.Builder(this).modules(
+                new ActionsModule(buttonAction0),
+                new ActionsModule(buttonAction1),
+                new ActionsModule(buttonAction2)
+        ).build();
+    }
+
+    @Override
+    protected void onPause() {
+        Log.e("GeckoVA", "onPause");
+
+        super.onPause();
+        debugDrawer.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        Log.e("GeckoVA", "onResume");
+
+        super.onResume();
+        debugDrawer.onResume();
+    }
+
+    @Override
+    protected void onRestart() {
+        Log.e("GeckoVA", "onRestart");
+
+        super.onRestart();
+    }
+
+    @Override
+    protected void onStop() {
+        Log.e("GeckoVA", "onStop");
+
+        super.onStop();
+        debugDrawer.onStop();
     }
 
     @Override
     protected void onStart() {
-        super.onStart();
+        Log.e("GeckoVA", "onStart");
 
-        final GeckoProfile profile = GeckoProfile.get(getApplicationContext());
-
-        GeckoThread.init(profile, /* args */ null, /* action */ null, /* debugging */ false);
-        GeckoThread.launch();
+        super.onStart();
+        debugDrawer.onStart();
     }
 
     private class MyGeckoViewChrome implements GeckoView.ChromeDelegate {
         @Override
         public void onReady(GeckoView view) {
             Log.i(LOGTAG, "Gecko is ready");
 
             // Inject a script that adds some code to the content window
@@ -97,33 +178,33 @@ public class GeckoViewActivity extends A
             Log.i(LOGTAG, "Alert!");
             result.confirm();
             Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
         }
 
         @Override
         public void onConfirm(GeckoView view, GeckoView.Browser browser, String message, final GeckoView.PromptResult result) {
             Log.i(LOGTAG, "Confirm!");
-            new AlertDialog.Builder(GeckoViewActivity.this)
-                .setTitle("javaScript dialog")
-                .setMessage(message)
-                .setPositiveButton(android.R.string.ok,
-                                   new DialogInterface.OnClickListener() {
-                                       public void onClick(DialogInterface dialog, int which) {
-                                           result.confirm();
-                                       }
-                                   })
-                .setNegativeButton(android.R.string.cancel,
-                                   new DialogInterface.OnClickListener() {
-                                       public void onClick(DialogInterface dialog, int which) {
-                                           result.cancel();
-                                       }
-                                   })
-                .create()
-                .show();
+            new AlertDialog.Builder(SingleGeckoViewActivity.this)
+                    .setTitle("javaScript dialog")
+                    .setMessage(message)
+                    .setPositiveButton(android.R.string.ok,
+                            new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int which) {
+                                    result.confirm();
+                                }
+                            })
+                    .setNegativeButton(android.R.string.cancel,
+                            new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int which) {
+                                    result.cancel();
+                                }
+                            })
+                    .create()
+                    .show();
         }
 
         @Override
         public void onPrompt(GeckoView view, GeckoView.Browser browser, String message, String defaultValue, GeckoView.PromptResult result) {
             result.cancel();
         }
 
         @Override
--- a/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml
+++ b/mobile/android/geckoview_example/src/main/res/layout/geckoview_activity.xml
@@ -5,9 +5,15 @@
 
     <org.mozilla.gecko.GeckoView
         android:id="@+id/gecko_view"
         android:layout_width="fill_parent"
         android:layout_height="match_parent"
         android:scrollbars="none"
         />
 
+    <!--<fragment class="org.mozilla.gecko.GeckoViewFragment"-->
+              <!--android:id="@+id/gecko_view"-->
+              <!--android:layout_width="match_parent"-->
+              <!--android:layout_height="match_parent"-->
+              <!--android:scrollbars="none"/>-->
+
 </LinearLayout>
--- a/mobile/android/modules/dbg-browser-actors.js
+++ b/mobile/android/modules/dbg-browser-actors.js
@@ -56,21 +56,23 @@ function MobileTabList(aConnection)
   BrowserTabList.call(this, aConnection);
 }
 
 MobileTabList.prototype = Object.create(BrowserTabList.prototype);
 
 MobileTabList.prototype.constructor = MobileTabList;
 
 MobileTabList.prototype._getSelectedBrowser = function(aWindow) {
-  return aWindow.BrowserApp.selectedBrowser;
+  console.log(`aWindow.location = '${aWindow.location}`);
+  console.log(aWindow);
+  return aWindow.GeckoView.browser; // BrowserApp.selectedBrowser;
 };
 
 MobileTabList.prototype._getChildren = function(aWindow) {
-  return aWindow.BrowserApp.tabs.map(tab => tab.browser);
+  return [aWindow.GeckoView.browser]; // BrowserApp.tabs.map(tab => tab.browser);
 };
 
 exports.register = function(handle) {
   handle.setRootActor(createRootActor);
 };
 
 exports.unregister = function(handle) {
   handle.setRootActor(null);
--- a/moz.configure
+++ b/moz.configure
@@ -330,12 +330,14 @@ def nsis_version(nsis):
 @checking('for 32-bit NSIS')
 def nsis_binary_type(nsis):
     bin_type = windows_binary_type(nsis)
     if bin_type != 'win32':
         raise FatalCheckError('%s is not a 32-bit Windows application' % nsis)
 
     return 'yes'
 
+include('build/moz.configure/marionette.configure')
+
 
 # Fallthrough to autoconf-based configure
 include('build/moz.configure/old.configure')
 # Please do not add anything after the include of old.configure.
--- a/old-configure.in
+++ b/old-configure.in
@@ -5248,27 +5248,16 @@ MOZ_ARG_DISABLE_BOOL(cookies,
     NECKO_COOKIES=,
     NECKO_COOKIES=1)
 AC_SUBST(NECKO_COOKIES)
 if test "$NECKO_COOKIES"; then
     AC_DEFINE(NECKO_COOKIES)
     _NON_GLOBAL_ACDEFINES="$_NON_GLOBAL_ACDEFINES NECKO_COOKIES"
 fi
 
-dnl
-dnl Always build Marionette if not Android or B2G
-dnl
-if test "$OS_TARGET" != Android -a x"$MOZ_WIDGET_TOOLKIT" != x"gonk"; then
-    AC_DEFINE(ENABLE_MARIONETTE)
-fi
-AC_SUBST(ENABLE_MARIONETTE)
-if test "$ENABLE_MARIONETTE"; then
-    AC_DEFINE(ENABLE_MARIONETTE)
-fi
-
 dnl ========================================================
 if test "$MOZ_DEBUG" -o "$MOZ_DMD"; then
     MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
 fi
 
 dnl ========================================================
 dnl =
 dnl = Maintainer debug option (no --enable equivalent)
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -33,17 +33,17 @@
 #include "mozilla/jni/Utils.h"
 #include "nsIObserver.h"
 #include "nsDataHashtable.h"
 
 #include "Units.h"
 
 // Some debug #defines
 // #define DEBUG_ANDROID_EVENTS
-// #define DEBUG_ANDROID_WIDGET
+#define DEBUG_ANDROID_WIDGET
 
 class nsPIDOMWindowOuter;
 
 namespace base {
 class Thread;
 } // end namespace base
 
 typedef void* EGLSurface;
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -17,17 +17,17 @@
 #include "nsIAndroidBridge.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/dom/Touch.h"
 #include "mozilla/EventForwards.h"
 #include "InputData.h"
 #include "Units.h"
 #include "FrameMetrics.h"
 
-//#define FORCE_ALOG 1
+#define FORCE_ALOG 1
 
 class nsIAndroidDisplayport;
 class nsIWidget;
 
 namespace mozilla {
 
 enum {
     // These keycode masks are not defined in android/keycodes.h: