Bug 1625326: Add priority management for non-content child processes to GeckoProcessManager; r=geckoview-reviewers,agi
authorAaron Klotz <aklotz@mozilla.com>
Tue, 19 May 2020 21:34:05 +0000
changeset 530921 06b7037addb54f59d8157ea8527ae3c8bcdd688e
parent 530920 a03ca1bbfefaacd231abbb71473c5e0e42cea20a
child 530922 1a79875b2cf67e3539939f7002b3e5ba5ceccd79
push id37434
push userabutkovits@mozilla.com
push dateWed, 20 May 2020 10:05:10 +0000
treeherdermozilla-central@005ef1c25992 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, agi
bugs1625326
milestone78.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 1625326: Add priority management for non-content child processes to GeckoProcessManager; r=geckoview-reviewers,agi * We modify connection management such that we now use more specific connection types for {content, non-content, socket process} connections. * We attach a native counterpart to the `GeckoProcessManager.ConnectionManager` instance that listens for app foreground, app background, and when the socket process is enabled, network state events. * On app background, all non-content processes are assigned BACKGROUND priority. Even though backgrounding the app will cause Android to drop all child processes' priority regardless of our priority settings, we still do this as to indicate to Android that these processes are relatively less important than our parent process. * When the socket process is enabled, we drop its priority when we detect that we have no network connectivity. Note that the network management code does the Right Thing if network connectivity changes while our app was in the background: we receive the network state change event once we return to foreground, therefore we do not need to do any special handling ourselves. Differential Revision: https://phabricator.services.mozilla.com/D74478
mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceAllocator.java
widget/android/GeckoProcessManager.cpp
widget/android/GeckoProcessManager.h
widget/android/moz.build
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
@@ -1,29 +1,33 @@
 /* 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.process;
 
 import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoNetworkManager;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.IGeckoEditableChild;
 import org.mozilla.gecko.IGeckoEditableParent;
 import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.process.ServiceAllocator.PriorityLevel;
+import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.XPCOMEventTarget;
 
 import org.mozilla.geckoview.GeckoResult;
 
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.support.annotation.NonNull;
+import android.support.v4.util.ArrayMap;
 import android.support.v4.util.ArraySet;
 import android.support.v4.util.SimpleArrayMap;
 import android.util.Log;
 
 
 public final class GeckoProcessManager extends IProcessManager.Stub {
     private static final String LOGTAG = "GeckoProcessManager";
     private static final GeckoProcessManager INSTANCE = new GeckoProcessManager();
@@ -65,16 +69,22 @@ public final class GeckoProcessManager e
             if (pid == INVALID_PID) {
                 throw new RuntimeException("Invalid PID");
             }
 
             mType = type;
             mPid = pid;
         }
 
+        @WrapForJNI
+        private Selector(@NonNull final GeckoProcessType type) {
+            mType = type;
+            mPid = INVALID_PID;
+        }
+
         public GeckoProcessType getType() {
             return mType;
         }
 
         public int getPid() {
             return mPid;
         }
 
@@ -97,25 +107,25 @@ public final class GeckoProcessManager e
             return 31 * mType.hashCode() + mPid;
         }
     }
 
     /**
      * Maintains state pertaining to an individual child process. Inheriting from
      * ServiceAllocator.InstanceInfo enables this class to work with ServiceAllocator.
      */
-    private static final class ChildConnection extends ServiceAllocator.InstanceInfo {
+    private static class ChildConnection extends ServiceAllocator.InstanceInfo {
         private IChildProcess mChild;
         private GeckoResult<IChildProcess> mPendingBind;
         private int mPid;
 
-        public ChildConnection(@NonNull final ServiceAllocator allocator,
-                               @NonNull final GeckoProcessType type,
-                               @NonNull final PriorityLevel priority) {
-            super(allocator, type, priority);
+        protected ChildConnection(@NonNull final ServiceAllocator allocator,
+                                  @NonNull final GeckoProcessType type,
+                                  @NonNull final PriorityLevel initialPriority) {
+            super(allocator, type, initialPriority);
             mPid = INVALID_PID;
         }
 
         public int getPid() {
             XPCOMEventTarget.assertOnLauncherThread();
             if (mChild == null) {
                 throw new IllegalStateException("Calling ChildConnection.getPid() on an unbound connection");
             }
@@ -220,45 +230,195 @@ public final class GeckoProcessManager e
             if (mPendingBind != null) {
                 mPendingBind.complete(mChild);
                 mPendingBind = null;
             }
         }
 
         @Override
         protected void onReleaseResources() {
+            XPCOMEventTarget.assertOnLauncherThread();
+
             // NB: This must happen *before* resetting mPid!
             GeckoProcessManager.INSTANCE.mConnections.removeConnection(this);
 
             mChild = null;
             mPid = INVALID_PID;
         }
     }
 
+    private static class NonContentConnection extends ChildConnection {
+        public NonContentConnection(@NonNull final ServiceAllocator allocator,
+                                    @NonNull final GeckoProcessType type) {
+            super(allocator, type, PriorityLevel.FOREGROUND);
+            if (type == GeckoProcessType.CONTENT) {
+                throw new AssertionError("Attempt to create a NonContentConnection as CONTENT");
+            }
+        }
+
+        protected void onAppForeground() {
+            setPriorityLevel(PriorityLevel.FOREGROUND);
+        }
+
+        protected void onAppBackground() {
+            setPriorityLevel(PriorityLevel.BACKGROUND);
+        }
+    }
+
+    private static final class SocketProcessConnection extends NonContentConnection {
+        private boolean mIsForeground = true;
+        private boolean mIsNetworkUp = true;
+
+        public SocketProcessConnection(@NonNull final ServiceAllocator allocator) {
+            super(allocator, GeckoProcessType.SOCKET);
+            GeckoProcessManager.INSTANCE.mConnections.enableNetworkNotifications();
+        }
+
+        public void onNetworkStateChange(final boolean isNetworkUp) {
+            mIsNetworkUp = isNetworkUp;
+            prioritize();
+        }
+
+        @Override
+        protected void onAppForeground() {
+            mIsForeground = true;
+            prioritize();
+        }
+
+        @Override
+        protected void onAppBackground() {
+            mIsForeground = false;
+            prioritize();
+        }
+
+        private static final PriorityLevel[][] sPriorityStates = initPriorityStates();
+
+        private static PriorityLevel[][] initPriorityStates() {
+            final PriorityLevel[][] states = new PriorityLevel[2][2];
+            // Background, no network
+            states[0][0] = PriorityLevel.IDLE;
+            // Background, network
+            states[0][1] = PriorityLevel.BACKGROUND;
+            // Foreground, no network
+            states[1][0] = PriorityLevel.IDLE;
+            // Foreground, network
+            states[1][1] = PriorityLevel.FOREGROUND;
+            return states;
+        }
+
+        private void prioritize() {
+            final PriorityLevel nextPriority = sPriorityStates[mIsForeground ? 1 : 0][mIsNetworkUp ? 1 : 0];
+            setPriorityLevel(nextPriority);
+        }
+    }
+
+    private static final class ContentConnection extends ChildConnection {
+        public ContentConnection(@NonNull final ServiceAllocator allocator,
+                                 @NonNull final PriorityLevel initialPriority) {
+            super(allocator, GeckoProcessType.CONTENT, initialPriority);
+        }
+    }
+
     /**
      * This class manages the state surrounding existing connections and their priorities.
      */
-    private static final class ConnectionManager {
+    private static final class ConnectionManager extends JNIObject {
         // Connections to non-content processes
-        private final SimpleArrayMap<GeckoProcessType, ChildConnection> mNonContentConnections;
+        private final ArrayMap<GeckoProcessType, NonContentConnection> mNonContentConnections;
         // Mapping of pid to content process
-        private final SimpleArrayMap<Integer, ChildConnection> mContentPids;
+        private final SimpleArrayMap<Integer, ContentConnection> mContentPids;
         // Set of initialized content process connections
-        private final ArraySet<ChildConnection> mContentConnections;
+        private final ArraySet<ContentConnection> mContentConnections;
         // Set of bound but uninitialized content connections
-        private final ArraySet<ChildConnection> mNonStartedContentConnections;
+        private final ArraySet<ContentConnection> mNonStartedContentConnections;
         // Allocator for service IDs
         private final ServiceAllocator mServiceAllocator;
+        private boolean mIsObservingNetwork = false;
 
         public ConnectionManager() {
-            mNonContentConnections = new SimpleArrayMap<GeckoProcessType, ChildConnection>();
-            mContentPids = new SimpleArrayMap<Integer, ChildConnection>();
-            mContentConnections = new ArraySet<ChildConnection>();
-            mNonStartedContentConnections = new ArraySet<ChildConnection>();
+            mNonContentConnections = new ArrayMap<GeckoProcessType, NonContentConnection>();
+            mContentPids = new SimpleArrayMap<Integer, ContentConnection>();
+            mContentConnections = new ArraySet<ContentConnection>();
+            mNonStartedContentConnections = new ArraySet<ContentConnection>();
             mServiceAllocator = new ServiceAllocator();
+
+            // Attach to native once JNI is ready.
+            if (GeckoThread.isStateAtLeast(GeckoThread.State.JNI_READY)) {
+                attachTo(this);
+            } else {
+                GeckoThread.queueNativeCallUntil(GeckoThread.State.JNI_READY, ConnectionManager.class,
+                                                 "attachTo", this);
+            }
+        }
+
+        private void enableNetworkNotifications() {
+            if (mIsObservingNetwork) {
+                return;
+            }
+
+            mIsObservingNetwork = true;
+
+            // Ensure that GeckoNetworkManager is monitoring network events so that we can
+            // prioritize the socket process.
+            ThreadUtils.runOnUiThread(() -> {
+                GeckoNetworkManager.getInstance().enableNotifications();
+            });
+
+            observeNetworkNotifications();
+        }
+
+        @WrapForJNI(dispatchTo = "gecko")
+        private static native void attachTo(ConnectionManager instance);
+
+        @WrapForJNI(dispatchTo = "gecko")
+        private native void observeNetworkNotifications();
+
+        @WrapForJNI(calledFrom = "gecko")
+        private void onBackground() {
+            XPCOMEventTarget.runOnLauncherThread(() -> onAppBackgroundInternal());
+        }
+
+        @WrapForJNI(calledFrom = "gecko")
+        private void onForeground() {
+            XPCOMEventTarget.runOnLauncherThread(() -> onAppForegroundInternal());
+        }
+
+        @WrapForJNI(calledFrom = "gecko")
+        private void onNetworkStateChange(final boolean isUp) {
+            XPCOMEventTarget.runOnLauncherThread(() -> onNetworkStateChangeInternal(isUp));
+        }
+
+        @Override
+        protected native void disposeNative();
+
+        private void onAppBackgroundInternal() {
+            XPCOMEventTarget.assertOnLauncherThread();
+
+            for (final NonContentConnection conn : mNonContentConnections.values()) {
+                conn.onAppBackground();
+            }
+        }
+
+        private void onAppForegroundInternal() {
+            XPCOMEventTarget.assertOnLauncherThread();
+
+            for (final NonContentConnection conn : mNonContentConnections.values()) {
+                conn.onAppForeground();
+            }
+        }
+
+        private void onNetworkStateChangeInternal(final boolean isUp) {
+            XPCOMEventTarget.assertOnLauncherThread();
+
+            final SocketProcessConnection conn = (SocketProcessConnection) mNonContentConnections.get(GeckoProcessType.SOCKET);
+            if (conn == null) {
+                return;
+            }
+
+            conn.onNetworkStateChange(isUp);
         }
 
         private void removeContentConnection(@NonNull final ChildConnection conn) {
             if (!mContentConnections.remove(conn) && !mNonStartedContentConnections.remove(conn)) {
                 throw new RuntimeException("Attempt to remove non-registered connection");
             }
 
             final int pid = conn.getPid();
@@ -291,37 +451,37 @@ public final class GeckoProcessManager e
          */
         public void onBindComplete(@NonNull final ChildConnection conn) {
             if (conn.getType() == GeckoProcessType.CONTENT) {
                 int pid = conn.getPid();
                 if (pid == INVALID_PID) {
                     throw new AssertionError("PID is invalid even though our caller just successfully retrieved it after binding");
                 }
 
-                mContentPids.put(Integer.valueOf(pid), conn);
+                mContentPids.put(Integer.valueOf(pid), (ContentConnection) conn);
             }
         }
 
         /**
          * Retrieve the ChildConnection for an already running content process.
          */
-        private ChildConnection getExistingContentConnection(@NonNull final Selector selector) {
+        private ContentConnection getExistingContentConnection(@NonNull final Selector selector) {
             XPCOMEventTarget.assertOnLauncherThread();
             if (selector.getType() != GeckoProcessType.CONTENT) {
                 throw new IllegalArgumentException("Selector is not for content!");
             }
 
             return mContentPids.get(Integer.valueOf(selector.getPid()));
         }
 
         /**
          * Unconditionally create a new content connection for the specified priority.
          */
-        private ChildConnection getNewContentConnection(@NonNull final PriorityLevel newPriority) {
-            final ChildConnection result = new ChildConnection(mServiceAllocator, GeckoProcessType.CONTENT, newPriority);
+        private ContentConnection getNewContentConnection(@NonNull final PriorityLevel newPriority) {
+            final ContentConnection result = new ContentConnection(mServiceAllocator, newPriority);
             mContentConnections.add(result);
 
             return result;
         }
 
         /**
          * Retrieve the ChildConnection for an already running child process of any type.
          */
@@ -341,38 +501,44 @@ public final class GeckoProcessManager e
          * Retrieve a ChildConnection for a content process for the purposes of starting. If there
          * are any preloaded content processes already running, we will use one of those.
          * Otherwise we will allocate a new ChildConnection.
          */
         private ChildConnection getContentConnectionForStart() {
             XPCOMEventTarget.assertOnLauncherThread();
 
             if (mNonStartedContentConnections.isEmpty()) {
-                // Initially start at BACKGROUND; Gecko will adjust as necessary.
-                return getNewContentConnection(PriorityLevel.BACKGROUND);
+                return getNewContentConnection(PriorityLevel.FOREGROUND);
             }
 
             final ChildConnection conn = mNonStartedContentConnections.removeAt(mNonStartedContentConnections.size() - 1);
+            conn.setPriorityLevel(PriorityLevel.FOREGROUND);
             return conn;
         }
 
         /**
          * Retrieve or create a new child process for the specified non-content process.
          */
         private ChildConnection getNonContentConnection(@NonNull final GeckoProcessType type) {
             XPCOMEventTarget.assertOnLauncherThread();
             if (type == GeckoProcessType.CONTENT) {
                 throw new IllegalArgumentException("Content processes not supported by this method");
             }
 
-            ChildConnection connection = mNonContentConnections.get(type);
+            NonContentConnection connection = mNonContentConnections.get(type);
             if (connection == null) {
-                connection = new ChildConnection(mServiceAllocator, type, PriorityLevel.FOREGROUND);
+                if (type == GeckoProcessType.SOCKET) {
+                    connection = new SocketProcessConnection(mServiceAllocator);
+                } else {
+                    connection = new NonContentConnection(mServiceAllocator, type);
+                }
+
                 mNonContentConnections.put(type, connection);
             }
+
             return connection;
         }
 
         /**
          * Retrieve a ChildConnection for the purposes of starting a new child process.
          */
         public ChildConnection getConnectionForStart(@NonNull final GeckoProcessType type) {
             if (type == GeckoProcessType.CONTENT) {
@@ -382,17 +548,17 @@ public final class GeckoProcessManager e
             return getNonContentConnection(type);
         }
 
         /**
          * Retrieve a ChildConnection for the purposes of preloading a new child process.
          */
         public ChildConnection getConnectionForPreload(@NonNull final GeckoProcessType type) {
             if (type == GeckoProcessType.CONTENT) {
-                final ChildConnection conn = getNewContentConnection(PriorityLevel.BACKGROUND);
+                final ContentConnection conn = getNewContentConnection(PriorityLevel.BACKGROUND);
                 mNonStartedContentConnections.add(conn);
                 return conn;
             }
 
             return getNonContentConnection(type);
         }
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceAllocator.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/ServiceAllocator.java
@@ -135,24 +135,24 @@ import java.util.Map.Entry;
         private boolean mCalledConnected = false;
         private boolean mCalledConnectionLost = false;
         private boolean mIsDefunct = false;
 
         private PriorityLevel mCurrentPriority;
         private int mRelativeImportance = 0;
 
         protected InstanceInfo(@NonNull final ServiceAllocator allocator, @NonNull final GeckoProcessType type,
-                               @NonNull final PriorityLevel priority) {
+                               @NonNull final PriorityLevel initialPriority) {
             mAllocator = allocator;
             mType = type;
             mId = mAllocator.allocate(type);
             mBindings = new EnumMap<PriorityLevel, Binding>(PriorityLevel.class);
             mBindDelegate = getBindServiceDelegate();
 
-            mCurrentPriority = priority;
+            mCurrentPriority = initialPriority;
         }
 
         private BindServiceDelegate getBindServiceDelegate() {
             if (mType != GeckoProcessType.CONTENT) {
                 // Non-content services just use default binding
                 return this.new DefaultBindDelegate();
             }
 
@@ -160,16 +160,20 @@ import java.util.Map.Entry;
             return mAllocator.mContentAllocPolicy.getBindServiceDelegate(this);
         }
 
         public PriorityLevel getPriorityLevel() {
             XPCOMEventTarget.assertOnLauncherThread();
             return mCurrentPriority;
         }
 
+        public boolean setPriorityLevel(@NonNull final PriorityLevel newPriority) {
+            return setPriorityLevel(newPriority, 0);
+        }
+
         public boolean setPriorityLevel(@NonNull final PriorityLevel newPriority,
                                         final int relativeImportance) {
             XPCOMEventTarget.assertOnLauncherThread();
             mCurrentPriority = newPriority;
             mRelativeImportance = relativeImportance;
 
             // If we haven't bound yet then we can just return
             if (mBindings.size() == 0) {
new file mode 100644
--- /dev/null
+++ b/widget/android/GeckoProcessManager.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "GeckoProcessManager.h"
+
+#include "nsINetworkLinkService.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/Services.h"
+
+namespace mozilla {
+
+/* static */ void GeckoProcessManager::Init() {
+  BaseNatives::Init();
+  ConnectionManager::Init();
+}
+
+NS_IMPL_ISUPPORTS(GeckoProcessManager::ConnectionManager, nsIObserver)
+
+NS_IMETHODIMP GeckoProcessManager::ConnectionManager::Observe(
+    nsISupports* aSubject, const char* aTopic, const char16_t* aData) {
+  java::GeckoProcessManager::ConnectionManager::LocalRef connMgr(mJavaConnMgr);
+  if (!connMgr) {
+    return NS_OK;
+  }
+
+  if (!strcmp("application-foreground", aTopic)) {
+    connMgr->OnForeground();
+    return NS_OK;
+  }
+
+  if (!strcmp("application-background", aTopic)) {
+    connMgr->OnBackground();
+    return NS_OK;
+  }
+
+  if (!strcmp(NS_NETWORK_LINK_TOPIC, aTopic)) {
+    const nsDependentString state(aData);
+    // state can be up, down, or unknown. For the purposes of socket process
+    // prioritization, we treat unknown as being up.
+    const bool isUp = !state.EqualsLiteral(NS_NETWORK_LINK_DATA_DOWN);
+    connMgr->OnNetworkStateChange(isUp);
+    return NS_OK;
+  }
+
+  return NS_OK;
+}
+
+/* static */ void GeckoProcessManager::ConnectionManager::AttachTo(
+    java::GeckoProcessManager::ConnectionManager::Param aInstance) {
+  RefPtr<ConnectionManager> native(new ConnectionManager());
+  BaseNatives::AttachNative(aInstance, native);
+
+  native->mJavaConnMgr = aInstance;
+
+  nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
+  obsServ->AddObserver(native, "application-background", false);
+  obsServ->AddObserver(native, "application-foreground", false);
+}
+
+void GeckoProcessManager::ConnectionManager::ObserveNetworkNotifications() {
+  nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
+  obsServ->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
+
+  const bool isUp = java::GeckoAppShell::IsNetworkLinkUp();
+
+  java::GeckoProcessManager::ConnectionManager::LocalRef connMgr(mJavaConnMgr);
+  if (!connMgr) {
+    return;
+  }
+
+  connMgr->OnNetworkStateChange(isUp);
+}
+
+}  // namespace mozilla
--- a/widget/android/GeckoProcessManager.h
+++ b/widget/android/GeckoProcessManager.h
@@ -3,26 +3,29 @@
  * 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/. */
 
 #ifndef GeckoProcessManager_h
 #define GeckoProcessManager_h
 
 #include "WidgetUtils.h"
 #include "nsAppShell.h"
+#include "nsIObserver.h"
 #include "nsWindow.h"
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/ContentProcessManager.h"
 #include "mozilla/java/GeckoProcessManagerNatives.h"
 
 namespace mozilla {
 
 class GeckoProcessManager final
     : public java::GeckoProcessManager::Natives<GeckoProcessManager> {
+  using BaseNatives = java::GeckoProcessManager::Natives<GeckoProcessManager>;
+
   GeckoProcessManager() = delete;
 
   static already_AddRefed<nsIWidget> GetWidget(int64_t aContentId,
                                                int64_t aTabId) {
     using namespace dom;
     MOZ_ASSERT(NS_IsMainThread());
 
     ContentProcessManager* const cpm = ContentProcessManager::GetSingleton();
@@ -33,17 +36,42 @@ class GeckoProcessManager final
     NS_ENSURE_TRUE(tab, nullptr);
 
     nsCOMPtr<nsPIDOMWindowOuter> domWin = tab->GetParentWindowOuter();
     NS_ENSURE_TRUE(domWin, nullptr);
 
     return widget::WidgetUtils::DOMWindowToWidget(domWin);
   }
 
+  class ConnectionManager final
+      : public java::GeckoProcessManager::ConnectionManager::Natives<
+            ConnectionManager>,
+        public nsIObserver {
+    using BaseNatives = java::GeckoProcessManager::ConnectionManager::Natives<
+        ConnectionManager>;
+
+    virtual ~ConnectionManager() = default;
+
+   public:
+    ConnectionManager() = default;
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+
+    static void AttachTo(
+        java::GeckoProcessManager::ConnectionManager::Param aInstance);
+    void ObserveNetworkNotifications();
+
+   private:
+    java::GeckoProcessManager::ConnectionManager::WeakRef mJavaConnMgr;
+  };
+
  public:
+  static void Init();
+
   static void GetEditableParent(jni::Object::Param aEditableChild,
                                 int64_t aContentId, int64_t aTabId) {
     nsCOMPtr<nsIWidget> widget = GetWidget(aContentId, aTabId);
     if (RefPtr<nsWindow> window = nsWindow::From(widget)) {
       java::GeckoProcessManager::SetEditableChildParent(
           aEditableChild, window->GetEditableParent());
     }
   }
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -121,16 +121,17 @@ SOURCES += [
 UNIFIED_SOURCES += [
     'AndroidAlerts.cpp',
     'AndroidBridge.cpp',
     'AndroidCompositorWidget.cpp',
     'AndroidContentController.cpp',
     'AndroidUiThread.cpp',
     'EventDispatcher.cpp',
     'GeckoEditableSupport.cpp',
+    'GeckoProcessManager.cpp',
     'GfxInfo.cpp',
     'ImageDecoderSupport.cpp',
     'nsAndroidProtocolHandler.cpp',
     'nsAppShell.cpp',
     'nsClipboard.cpp',
     'nsDeviceContextAndroid.cpp',
     'nsIdleServiceAndroid.cpp',
     'nsLookAndFeel.cpp',