From 2333f6e8d9c3c2d4cf750e06b12e6b07f0f6166d Mon Sep 17 00:00:00 2001 From: "Randall E. Barker" Date: Mon, 18 Nov 2019 08:18:41 -0800 Subject: [PATCH] Limit total number of GeckoSessions created to prevent OOM (#2295) --- .../vrbrowser/browser/engine/Session.java | 33 ++++++++----- .../browser/engine/SessionState.java | 19 +++++--- .../browser/engine/SessionStore.java | 46 +++++++++++++++++++ .../vrbrowser/ui/widgets/WindowWidget.java | 1 + .../mozilla/vrbrowser/ui/widgets/Windows.java | 3 ++ 5 files changed, 84 insertions(+), 18 deletions(-) diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java index 32ea5cc73..2b12b8c45 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java @@ -99,6 +99,7 @@ protected Session(Context aContext, GeckoRuntime aRuntime, mRuntime = aRuntime; initialize(); mState = createSession(aSettings, aOpenMode); + mState.setActive(true); } protected Session(Context aContext, GeckoRuntime aRuntime, @NonNull SessionState aRestoreState) { @@ -325,7 +326,7 @@ private void cleanSessionListeners(GeckoSession aSession) { } public void suspend() { - if (mState.mIsActive) { + if (mState.isActive()) { Log.e(LOGTAG, "Active Sessions can not be suspended"); return; } @@ -345,22 +346,26 @@ private void restore() { .build(); } - String restoreUri = mState.mUri; - mState.mSession = createGeckoSession(settings); if (!mState.mSession.isOpen()) { mState.mSession.open(mRuntime); } + // data:text URLs can not be restored. + if (mState.mSessionState != null && ((mState.mUri == null) || mState.mUri.startsWith("data:text"))) { + mState.mSessionState = null; + mState.mUri = null; + } + if (mState.mSessionState != null) { mState.mSession.restoreState(mState.mSessionState); } - if ((mState.mSessionState == null) && (restoreUri != null)) { - mState.mSession.loadUri(restoreUri); + if ((mState.mSessionState == null) && (mState.mUri != null)) { + mState.mSession.loadUri(mState.mUri); } else if (mState.mSettings.isPrivateBrowsingEnabled() && mState.mUri == null) { loadPrivateBrowsingPage(); - } else if(mState.mSessionState == null || mState.mUri.equals(mContext.getResources().getString(R.string.about_blank)) || + } else if(mState.mSessionState == null || ((mState.mUri == null) || mState.mUri.equals(mContext.getResources().getString(R.string.about_blank))) || (mState.mSessionState != null && mState.mSessionState.size() == 0)) { loadHomePage(); } else if (mState.mUri != null && mState.mUri.contains(".youtube.com")) { @@ -368,6 +373,7 @@ private void restore() { } dumpAllState(); + mState.setActive(true); } @@ -410,6 +416,7 @@ private void recreateSession() { SessionState previous = mState; mState = createSession(previous.mSettings, SESSION_OPEN); + mState.setActive(true); if (previous.mSessionState != null) { mState.mSession.restoreState(previous.mSessionState); } @@ -435,7 +442,7 @@ private void closeSession(@NonNull SessionState aState) { aState.mDisplay = null; } aState.mSession.close(); - aState.mIsActive = false; + aState.setActive(false); } public void captureBitmap() { @@ -596,18 +603,19 @@ public void goForward() { public void setActive(boolean aActive) { // Flush the events queued while the session was inactive - if (mState.mSession != null && !mState.mIsActive && aActive) { + if (mState.mSession != null && !mState.isActive() && aActive) { flushQueuedEvents(); } if (mState.mSession != null) { mState.mSession.setActive(aActive); - } else { + mState.setActive(aActive); + } else if (aActive) { restore(); + } else { + Log.e(LOGTAG, "ERROR: Setting null GeckoView to inactive!"); } - mState.mIsActive = aActive; - for (SessionChangeListener listener: mSessionChangeListeners) { listener.onActiveStateChange(this, aActive); } @@ -661,6 +669,7 @@ public void toggleServo() { mState = createSession(settings, SESSION_OPEN); closeSession(previous); + mState.setActive(true); loadUri(uri); } @@ -705,7 +714,7 @@ public int getUaMode() { } public boolean isActive() { - return mState.mIsActive; + return mState.isActive(); } private static final String M_PREFIX = "m."; diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionState.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionState.java index 7615ec10c..5a1c6a502 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionState.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionState.java @@ -1,12 +1,7 @@ package org.mozilla.vrbrowser.browser.engine; -import android.graphics.Bitmap; - -import androidx.annotation.Nullable; - import com.google.gson.Gson; import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.annotations.JsonAdapter; @@ -25,7 +20,7 @@ @JsonAdapter(SessionState.SessionStateAdapterFactory.class) public class SessionState { - public transient boolean mIsActive; + private transient boolean mIsActive; public boolean mCanGoBack; public boolean mCanGoForward; public boolean mIsLoading; @@ -64,6 +59,18 @@ public GeckoSession.SessionState read(JsonReader in) { } } + boolean isActive() { + return mIsActive; + } + + void setActive(boolean active) { + if (active == mIsActive) { + return; + } + mIsActive = active; + SessionStore.get().sessionActiveStateChanged(); + } + public class SessionStateAdapterFactory implements TypeAdapterFactory { public TypeAdapter create(Gson gson, TypeToken type) { final TypeAdapter delegate = gson.getDelegateAdapter(this, type); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java index b67192897..ca7f3f2ec 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStore.java @@ -1,12 +1,15 @@ package org.mozilla.vrbrowser.browser.engine; +import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.geckoview.ContentBlocking; import org.mozilla.geckoview.GeckoRuntime; import org.mozilla.geckoview.GeckoRuntimeSettings; @@ -20,11 +23,15 @@ import org.mozilla.vrbrowser.browser.Services; import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.crashreporting.CrashReporterService; +import org.mozilla.vrbrowser.utils.SystemUtils; +import java.security.KeyStore; import java.util.ArrayList; import java.util.List; public class SessionStore implements GeckoSession.PermissionDelegate { + private static final String LOGTAG = SystemUtils.createLogtag(SessionStore.class); + private static final int MAX_GECKO_SESSIONS = 5; private static final String[] WEB_EXTENSIONS = new String[] { "webcompat_vimeo", @@ -48,6 +55,7 @@ public static SessionStore get() { private BookmarksStore mBookmarksStore; private HistoryStore mHistoryStore; private Services mServices; + private boolean mSuspendPending; private SessionStore() { mSessions = new ArrayList<>(); @@ -113,6 +121,7 @@ private Session addSession(@NonNull Session aSession) { aSession.setPermissionDelegate(this); aSession.addNavigationListener(mServices); mSessions.add(aSession); + sessionActiveStateChanged(); return aSession; } @@ -173,9 +182,46 @@ public void suspendAllInactiveSessions() { } public void setActiveSession(Session aSession) { + if (aSession != null) { + aSession.setActive(true); + } mActiveSession = aSession; } + + private void limitInactiveSessions() { + Log.d(LOGTAG, "Limiting Inactive Sessions"); + suspendAllInactiveSessions(); + mSuspendPending = false; + } + + void sessionActiveStateChanged() { + if (mSuspendPending) { + return; + } + int count = 0; + int activeCount = 0; + int inactiveCount = 0; + int suspendedCount = 0; + for(Session session: mSessions) { + if (session.getGeckoSession() != null) { + count++; + if (session.isActive()) { + activeCount++; + } else { + inactiveCount++; + } + } else { + suspendedCount++; + } + } + if (count > MAX_GECKO_SESSIONS) { + Log.d(LOGTAG, "Too many GeckoSessions. Active: " + activeCount + " Inactive: " + inactiveCount + " Suspended: " + suspendedCount); + mSuspendPending = true; + ThreadUtils.postToUiThread(this::limitInactiveSessions); + } + } + public Session getActiveSession() { return mActiveSession; } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java index 1ff4fd2f1..03bce7043 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java @@ -991,6 +991,7 @@ public boolean isLayer() { return mSurface != null && mTexture == null; } + @Override public void setVisible(boolean aVisible) { if (mWidgetPlacement.visible == aVisible) { diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java index d57bf0afa..d6bacc533 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Windows.java @@ -667,6 +667,9 @@ private void removeWindow(@NonNull WindowWidget aWindow) { } private void setWindowVisible(@NonNull WindowWidget aWindow, boolean aVisible) { + if (aVisible && (aWindow.getSession() != null) && (aWindow.getSession().getGeckoSession() == null)) { + setFirstPaint(aWindow, aWindow.getSession()); + } aWindow.setVisible(aVisible); aWindow.getTopBar().setVisible(aVisible); aWindow.getTitleBar().setVisible(aVisible);