Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Commit

Permalink
Prevent GeckoSession creation until tab is viewed (#2238)
Browse files Browse the repository at this point in the history
* Prevent GeckoSession creation until tab is viewed

Fixes #2227
To prevent memory exhaustion, this patch watches for memory trim system
callbacks and suspends inactive tabs by deleting the GeckoSession.
Additionally, when the application is started or resumed, inactive tabs will
not have a GeckoSession created until they are viewed. Any new background tab
will also not have a GeckoSession created until the tab is viewed.

* Fix gray window when loading tab is replaced with already loaded tab

* Refactor Session to include GeckoDisplay

* Prevent crash in PromptDelegate
  • Loading branch information
bluemarvin authored and MortimerGoro committed Nov 14, 2019
1 parent 3ae95ba commit 0b2917e
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.mozilla.vrbrowser;

import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
Expand Down Expand Up @@ -92,7 +93,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;

public class VRBrowserActivity extends PlatformActivity implements WidgetManagerDelegate {
public class VRBrowserActivity extends PlatformActivity implements WidgetManagerDelegate, ComponentCallbacks2 {

private BroadcastReceiver mCrashReceiver = new BroadcastReceiver() {
@Override
Expand Down Expand Up @@ -637,6 +638,32 @@ private void postCrashFiles(final ArrayList<String> aFiles) {
});
}

@Override
public void onTrimMemory(int level) {

// Determine which lifecycle or system event was raised.
switch (level) {

case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
// Curently ignore these levels. They are handled somewhere else.
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
// It looks like these come in all at the same time so just always suspend inactive Sessions.
Log.d(LOGTAG, "Memory pressure, suspending inactive sessions.");
SessionStore.get().suspendAllInactiveSessions();
break;
default:
Log.e(LOGTAG, "onTrimMemory unknown level: " + level);
break;
}
}


@Override
public void onBackPressed() {
if (mIsPresentingImmersive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,16 +327,21 @@ public static PopUpRequest newRequest(@NonNull PopupPrompt prompt, @NonNull Geck
private SparseArray<Pair<String, LinkedList<PopUpRequest>>> mPopUpRequests = new SparseArray<>();

public void showPopUps(GeckoSession session) {
if (session == null) {
return;
}
Pair<String, LinkedList<PopUpRequest>> requests = mPopUpRequests.get(session.hashCode());
if (requests != null && !requests.second.isEmpty()) {
showPopUp(session.hashCode(), requests);
}
}

public boolean hasPendingPopUps(GeckoSession session) {
Pair<String, LinkedList<PopUpRequest>> requests = mPopUpRequests.get(session.hashCode());
if (requests != null) {
return !requests.second.isEmpty();
if (session != null) {
Pair<String, LinkedList<PopUpRequest>> requests = mPopUpRequests.get(session.hashCode());
if (requests != null) {
return !requests.second.isEmpty();
}
}

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ class Services(context: Context, places: Places): GeckoSession.NavigationDelegat
override fun onEvents(events: List<DeviceEvent>) {
CoroutineScope(Dispatchers.Main).launch {
Logger(logTag).info("Received ${events.size} device event(s)")
val events = events.filterIsInstance(DeviceEvent.TabReceived::class.java)
if (!events.isEmpty()) {
val tabs = events.map { event -> event.entries }.flatten()
val filteredEvents = events.filterIsInstance(DeviceEvent.TabReceived::class.java)
if (filteredEvents.isNotEmpty()) {
val tabs = filteredEvents.map { event -> event.entries }.flatten()
tabReceivedDelegate?.onTabsReceived(tabs)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ public class Session implements ContentBlocking.Delegate, GeckoSession.Navigatio
private transient Context mContext;
private transient SharedPreferences mPrefs;
private transient GeckoRuntime mRuntime;
private boolean mUsePrivateMode;
private transient byte[] mPrivatePage;
private boolean mIsActive;


public interface BitmapChangedListener {
Expand All @@ -95,33 +93,19 @@ public interface BitmapChangedListener {
public static final int SESSION_OPEN = 0;
public static final int SESSION_DO_NOT_OPEN = 1;

protected Session(Context aContext, GeckoRuntime aRuntime, boolean aUsePrivateMode) {
this(aContext, aRuntime, aUsePrivateMode, null, SESSION_OPEN);
}

protected Session(Context aContext, GeckoRuntime aRuntime, boolean aUsePrivateMode,
@Nullable SessionSettings aSettings, @SessionOpenModeFlags int aOpenMode) {
protected Session(Context aContext, GeckoRuntime aRuntime,
@NonNull SessionSettings aSettings, @SessionOpenModeFlags int aOpenMode) {
mContext = aContext;
mRuntime = aRuntime;
mUsePrivateMode = aUsePrivateMode;
initialize();
if (aSettings != null) {
mState = createSession(aSettings, aOpenMode);
} else {
mState = createSession(aOpenMode);
}

setupSessionListeners(mState.mSession);
mState = createSession(aSettings, aOpenMode);
}

protected Session(Context aContext, GeckoRuntime aRuntime, SessionState aRestoreState) {
protected Session(Context aContext, GeckoRuntime aRuntime, @NonNull SessionState aRestoreState) {
mContext = aContext;
mRuntime = aRuntime;
mUsePrivateMode = false;
initialize();
mState = aRestoreState;
restore();
setupSessionListeners(mState.mSession);
}

private void initialize() {
Expand Down Expand Up @@ -154,6 +138,7 @@ protected void shutdown() {
if (mState.mSession.isOpen()) {
mState.mSession.close();
}
mState.mDisplay = null;
mState.mSession = null;
}

Expand Down Expand Up @@ -339,6 +324,19 @@ private void cleanSessionListeners(GeckoSession aSession) {
aSession.setSelectionActionDelegate(null);
}

public void suspend() {
if (mState.mIsActive) {
Log.e(LOGTAG, "Active Sessions can not be suspended");
return;
}
if (mState.mSession == null) {
return;
}
Log.d(LOGTAG, "Suspending Session: " + mState.mId);
closeSession(mState);
mState.mSession = null;
}

private void restore() {
SessionSettings settings = mState.mSettings;
if (settings == null) {
Expand All @@ -347,34 +345,31 @@ private void restore() {
.build();
}

String restoreUri = mState.mUri;

mState.mSession = createGeckoSession(settings);
if (!mState.mSession.isOpen()) {
mState.mSession.open(mRuntime);
}

if (mState.mSessionState != null) {
mState.mSession.restoreState(mState.mSessionState);
}

if (mUsePrivateMode) {
if ((mState.mSessionState == null) && (restoreUri != null)) {
mState.mSession.loadUri(restoreUri);
} else if (mState.mSettings.isPrivateBrowsingEnabled() && mState.mUri == null) {
loadPrivateBrowsingPage();
} else if(mState.mSessionState == 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")) {
mState.mSession.loadUri(mState.mUri);
mState.mSession.loadUri(mState.mUri, GeckoSession.LOAD_FLAGS_REPLACE_HISTORY);
}

dumpAllState();
}

private SessionState createSession(@SessionOpenModeFlags int aOpenMode) {
SessionSettings settings = new SessionSettings.Builder()
.withDefaultSettings(mContext)
.build();

return createSession(settings, aOpenMode);
}

private SessionState createSession(@NonNull SessionSettings aSettings, @SessionOpenModeFlags int aOpenMode) {
SessionState state = new SessionState();
Expand All @@ -391,7 +386,7 @@ private SessionState createSession(@NonNull SessionSettings aSettings, @SessionO
private GeckoSession createGeckoSession(@NonNull SessionSettings aSettings) {
GeckoSessionSettings geckoSettings = new GeckoSessionSettings.Builder()
.useMultiprocess(SettingsStore.getInstance(mContext).isMultiprocessEnabled())
.usePrivateMode(mUsePrivateMode)
.usePrivateMode(aSettings.isPrivateBrowsingEnabled())
.useTrackingProtection(aSettings.isTrackingProtectionEnabled())
.userAgentMode(aSettings.getUserAgentMode())
.viewportMode(aSettings.getViewportMode())
Expand All @@ -406,6 +401,7 @@ private GeckoSession createGeckoSession(@NonNull SessionSettings aSettings) {
}

session.getSettings().setUserAgentOverride(aSettings.getUserAgentOverride());
setupSessionListeners(session);

return session;
}
Expand All @@ -414,28 +410,39 @@ private void recreateSession() {
SessionState previous = mState;

mState = createSession(previous.mSettings, SESSION_OPEN);
if (previous.mSessionState != null)
if (previous.mSessionState != null) {
mState.mSession.restoreState(previous.mSessionState);
}
if (previous.mSession != null) {
closeSession(previous.mSession);
closeSession(previous);
}
setupSessionListeners(mState.mSession);

for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onCurrentSessionChange(previous.mSession, mState.mSession);
}
}

private void closeSession(@NonNull GeckoSession aSession) {
cleanSessionListeners(aSession);
aSession.setActive(false);
aSession.stop();
aSession.close();
mIsActive = false;
private void closeSession(@NonNull SessionState aState) {
if (aState.mSession == null) {
return;
}
cleanSessionListeners(aState.mSession);
aState.mSession.setActive(false);
aState.mSession.stop();
if (aState.mDisplay != null) {
aState.mDisplay.surfaceDestroyed();
aState.mSession.releaseDisplay(aState.mDisplay);
aState.mDisplay = null;
}
aState.mSession.close();
aState.mIsActive = false;
}

public void captureBitmap(@NonNull GeckoDisplay aDisplay) {
aDisplay.capturePixels().then(bitmap -> {
public void captureBitmap() {
if (mState.mDisplay == null) {
return;
}
mState.mDisplay.capturePixels().then(bitmap -> {
if (bitmap != null) {
BitmapCache.getInstance(mContext).scaleBitmap(bitmap, 500, 280).thenAccept(scaledBitmap -> {
BitmapCache.getInstance(mContext).addBitmap(getId(), scaledBitmap);
Expand Down Expand Up @@ -517,10 +524,16 @@ public Boolean isHomeUri(String aUri) {
}

public String getCurrentUri() {
if (mState.mUri == null) {
return "";
}
return mState.mUri;
}

public String getCurrentTitle() {
if (mState.mTitle == null) {
return "";
}
return mState.mTitle;
}

Expand Down Expand Up @@ -583,15 +596,17 @@ public void goForward() {

public void setActive(boolean aActive) {
// Flush the events queued while the session was inactive
if (mState.mSession != null && !mIsActive && aActive) {
if (mState.mSession != null && !mState.mIsActive && aActive) {
flushQueuedEvents();
}

if (mState.mSession != null) {
mState.mSession.setActive(aActive);
} else {
restore();
}

mIsActive = aActive;
mState.mIsActive = aActive;

for (SessionChangeListener listener: mSessionChangeListeners) {
listener.onActiveStateChange(this, aActive);
Expand Down Expand Up @@ -645,7 +660,7 @@ public void toggleServo() {
.build();

mState = createSession(settings, SESSION_OPEN);
closeSession(previous.mSession);
closeSession(previous);
loadUri(uri);
}

Expand All @@ -670,6 +685,8 @@ public String getId() {
public boolean isPrivateMode() {
if (mState.mSession != null) {
return mState.mSession.getSettings().getUsePrivateMode();
} else if (mState.mSettings != null) {
return mState.mSettings.isPrivateBrowsingEnabled();
}
return false;
}
Expand All @@ -688,7 +705,7 @@ public int getUaMode() {
}

public boolean isActive() {
return mIsActive;
return mState.mIsActive;
}

private static final String M_PREFIX = "m.";
Expand Down Expand Up @@ -895,7 +912,7 @@ public void onCanGoForward(@NonNull GeckoSession aSession, boolean aCanGoForward
public GeckoResult<GeckoSession> onNewSession(@NonNull GeckoSession aSession, @NonNull String aUri) {
Log.d(LOGTAG, "Session onStackSession: " + aUri);

Session session = SessionStore.get().createSession(mUsePrivateMode, mState.mSettings, SESSION_DO_NOT_OPEN);
Session session = SessionStore.get().createSession(mState.mSettings, SESSION_DO_NOT_OPEN);
session.mState.mParentId = mState.mId;
for (SessionChangeListener listener: new LinkedList<>(mSessionChangeListeners)) {
listener.onStackSession(session);
Expand Down Expand Up @@ -1402,4 +1419,32 @@ public void onActiveStateChange(Session aSession, boolean aActive) {
}
}
}

// Display functions
public void releaseDisplay() {
surfaceDestroyed();
if (mState.mDisplay != null) {
if (mState.mSession != null) {
mState.mSession.releaseDisplay(mState.mDisplay);
}
mState.mDisplay = null;
}
}

public void surfaceDestroyed() {
if (mState.mDisplay != null) {
mState.mDisplay.surfaceDestroyed();
}
}

public void surfaceChanged(@NonNull final Surface surface, final int left, final int top,
final int width, final int height) {
if (mState.mSession == null) {
return;
}
if (mState.mDisplay == null) {
mState.mDisplay = mState.mSession.acquireDisplay();
}
mState.mDisplay.surfaceChanged(surface, left, top, width, height);
}
}
Loading

0 comments on commit 0b2917e

Please sign in to comment.