diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java index 7e345a6d60..712f2c86b0 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java @@ -44,8 +44,8 @@ import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.PermissionDelegate; import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; import org.mozilla.vrbrowser.crashreporting.CrashReporterService; import org.mozilla.vrbrowser.crashreporting.GlobalExceptionHandler; import org.mozilla.vrbrowser.geolocation.GeolocationWrapper; @@ -449,7 +449,7 @@ void loadFromIntent(final Intent intent) { uri = Uri.parse(intent.getExtras().getString("url")); } - SessionStack activeStore = SessionStore.get().getActiveStore(); + Session activeStore = SessionStore.get().getActiveSession(); Bundle extras = intent.getExtras(); if (extras != null && extras.containsKey("homepage")) { @@ -466,15 +466,9 @@ void loadFromIntent(final Intent intent) { } if (activeStore != null) { - if (activeStore.getCurrentSession() == null) { - String url = (uri != null ? uri.toString() : null); - activeStore.newSessionWithUrl(url); - Log.d(LOGTAG, "Creating session and loading URI from intent: " + url); - - } else if (uri != null) { + if (uri != null) { Log.d(LOGTAG, "Loading URI from intent: " + uri.toString()); activeStore.loadUri(uri.toString()); - } else { mWindows.getFocusedWindow().loadHomeIfNotRestored(); } @@ -676,8 +670,8 @@ void dispatchCreateWidget(final int aHandle, final SurfaceTexture aTexture, fina Log.d(LOGTAG, "Widget: " + aHandle + " (" + aWidth + "x" + aHeight + ") received a null surface texture."); } else { Runnable aFirstDrawCallback = () -> { - if (!widget.getFirstDraw()) { - widget.setFirstDraw(true); + if (!widget.isComposited()) { + widget.setComposited(true); updateWidget(widget); } }; @@ -705,8 +699,8 @@ void dispatchCreateWidgetLayer(final int aHandle, final Surface aSurface, final if (aNativeCallback != 0) { queueRunnable(() -> runCallbackNative(aNativeCallback)); } - if (aSurface != null && !widget.getFirstDraw()) { - widget.setFirstDraw(true); + if (aSurface != null && !widget.isComposited()) { + widget.setComposited(true); updateWidget(widget); } }; @@ -773,12 +767,12 @@ void handleGesture(final int aType) { boolean consumed = false; if ((aType == GestureSwipeLeft) && (mLastGesture == GestureSwipeLeft)) { Log.d(LOGTAG, "Go back!"); - SessionStore.get().getActiveStore().goBack(); + SessionStore.get().getActiveSession().goBack(); consumed = true; } else if ((aType == GestureSwipeRight) && (mLastGesture == GestureSwipeRight)) { Log.d(LOGTAG, "Go forward!"); - SessionStore.get().getActiveStore().goForward(); + SessionStore.get().getActiveSession().goForward(); consumed = true; } if (mLastRunnable != null) { @@ -998,18 +992,18 @@ private void handlePoorPerformance() { if (window == null) { return; } - final String originalUrl = window.getSessionStack().getCurrentUri(); + final String originalUrl = window.getSession().getCurrentUri(); if (mPoorPerformanceWhiteList.contains(originalUrl)) { return; } - window.getSessionStack().loadHomePage(); + window.getSession().loadHomePage(); final String[] buttons = {getString(R.string.ok_button), getString(R.string.performance_unblock_page)}; window.showButtonPrompt(getString(R.string.performance_title), getString(R.string.performance_message), buttons, new ConfirmPromptWidget.ConfirmPromptDelegate() { @Override public void confirm(int index) { if (index == GeckoSession.PromptDelegate.ButtonPrompt.Type.NEGATIVE) { mPoorPerformanceWhiteList.add(originalUrl); - window.getSessionStack().loadUri(originalUrl); + window.getSession().loadUri(originalUrl); } } @@ -1129,7 +1123,7 @@ public void updateWidget(final Widget aWidget) { public void removeWidget(final Widget aWidget) { mWidgets.remove(aWidget.getHandle()); mWidgetContainer.removeView((View) aWidget); - aWidget.setFirstDraw(false); + aWidget.setComposited(false); queueRunnable(() -> removeWidgetNative(aWidget.getHandle())); if (aWidget == mActiveDialog) { mActiveDialog = null; @@ -1320,11 +1314,11 @@ public boolean isPermissionGranted(@NonNull String permission) { @Override public void requestPermission(String uri, @NonNull String permission, GeckoSession.PermissionDelegate.Callback aCallback) { - SessionStack activeStore = SessionStore.get().getActiveStore(); + Session session = SessionStore.get().getActiveSession(); if (uri != null && !uri.isEmpty()) { - mPermissionDelegate.onAppPermissionRequest(activeStore.getCurrentSession(), uri, permission, aCallback); + mPermissionDelegate.onAppPermissionRequest(session.getGeckoSession(), uri, permission, aCallback); } else { - mPermissionDelegate.onAndroidPermissionsRequest(activeStore.getCurrentSession(), new String[]{permission}, aCallback); + mPermissionDelegate.onAndroidPermissionsRequest(session.getGeckoSession(), new String[]{permission}, aCallback); } } @@ -1380,7 +1374,7 @@ public void setCPULevel(int aCPULevel) { public void openNewWindow(String uri) { WindowWidget newWindow = mWindows.addWindow(); if (newWindow != null) { - newWindow.getSessionStack().newSessionWithUrl(uri); + newWindow.getSession().loadUri(uri); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionChangeListener.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionChangeListener.java index c253c96237..68f74ed03a 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionChangeListener.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/SessionChangeListener.java @@ -3,7 +3,7 @@ import org.mozilla.geckoview.GeckoSession; public interface SessionChangeListener { - default void onNewSession(GeckoSession aSession, int aId) {}; - default void onRemoveSession(GeckoSession aSession, int aId) {}; - default void onCurrentSessionChange(GeckoSession aSession, int aId) {}; + default void onNewSession(GeckoSession aSession) {}; + default void onRemoveSession(GeckoSession aSession) {}; + default void onCurrentSessionChange(GeckoSession aOldSession, GeckoSession aSession) {}; } 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 new file mode 100644 index 0000000000..a02408b105 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/Session.java @@ -0,0 +1,1130 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.vrbrowser.browser.engine; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + +import org.mozilla.geckoview.AllowOrDeny; +import org.mozilla.geckoview.ContentBlocking; +import org.mozilla.geckoview.GeckoResult; +import org.mozilla.geckoview.GeckoRuntime; +import org.mozilla.geckoview.GeckoSession; +import org.mozilla.geckoview.GeckoSessionSettings; +import org.mozilla.geckoview.MediaElement; +import org.mozilla.geckoview.WebRequestError; +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.browser.Media; +import org.mozilla.vrbrowser.browser.SessionChangeListener; +import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.browser.UserAgentOverride; +import org.mozilla.vrbrowser.browser.VideoAvailabilityListener; +import org.mozilla.vrbrowser.geolocation.GeolocationData; +import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; +import org.mozilla.vrbrowser.utils.InternalPages; +import org.mozilla.vrbrowser.utils.SystemUtils; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.mozilla.vrbrowser.utils.ServoUtils.createServoSession; +import static org.mozilla.vrbrowser.utils.ServoUtils.isInstanceOfServoSession; +import static org.mozilla.vrbrowser.utils.ServoUtils.isServoAvailable; + +public class Session implements ContentBlocking.Delegate, GeckoSession.NavigationDelegate, + GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, GeckoSession.TextInputDelegate, + GeckoSession.PromptDelegate, GeckoSession.MediaDelegate, GeckoSession.HistoryDelegate, GeckoSession.PermissionDelegate, + SharedPreferences.OnSharedPreferenceChangeListener { + + private static final String LOGTAG = SystemUtils.createLogtag(Session.class); + // You can test a local file using: "resource://android/assets/webvr/index.html" + + private transient LinkedList mNavigationListeners; + private transient LinkedList mProgressListeners; + private transient LinkedList mContentListeners; + private transient LinkedList mSessionChangeListeners; + private transient LinkedList mTextInputListeners; + private transient LinkedList mVideoAvailabilityListeners; + private transient LinkedList mBitmapChangedListeners; + private transient UserAgentOverride mUserAgentOverride; + + private SessionState mState; + private transient GeckoSession.PermissionDelegate mPermissionDelegate; + private transient GeckoSession.PromptDelegate mPromptDelegate; + private transient GeckoSession.HistoryDelegate mHistoryDelegate; + private String mRegion; + private transient Context mContext; + private transient SharedPreferences mPrefs; + private transient GeckoRuntime mRuntime; + private boolean mUsePrivateMode; + private transient byte[] mPrivatePage; + + public interface BitmapChangedListener { + void onBitmapChanged(Bitmap aBitmap); + } + + protected Session(Context aContext, GeckoRuntime aRuntime, boolean aUsePrivateMode) { + mContext = aContext; + mRuntime = aRuntime; + mUsePrivateMode = aUsePrivateMode; + initialize(); + mState = createSession(); + setupSessionListeners(mState.mSession); + } + + protected Session(Context aContext, GeckoRuntime aRuntime, Session aFrom) { + mContext = aContext; + mRuntime = aRuntime; + mUsePrivateMode = aFrom.isPrivateMode(); + initialize(); + mState = createSession(aFrom.mState.mSettings); + setupSessionListeners(mState.mSession); + } + + private void initialize() { + mNavigationListeners = new LinkedList<>(); + mProgressListeners = new LinkedList<>(); + mContentListeners = new LinkedList<>(); + mSessionChangeListeners = new LinkedList<>(); + mTextInputListeners = new LinkedList<>(); + mVideoAvailabilityListeners = new LinkedList<>(); + mBitmapChangedListeners = new LinkedList<>(); + + if (mPrefs != null) { + mPrefs.registerOnSharedPreferenceChangeListener(this); + } + + mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + + InternalPages.PageResources pageResources = InternalPages.PageResources.create(R.raw.private_mode, R.raw.private_style); + mPrivatePage = InternalPages.createAboutPage(mContext, pageResources); + + if (mUserAgentOverride == null) { + mUserAgentOverride = new UserAgentOverride(); + mUserAgentOverride.loadOverridesFromAssets((Activity)mContext, mContext.getString(R.string.user_agent_override_file)); + } + } + + protected void shutdown() { + if (mState.mSession != null) { + if (mState.mSession.isOpen()) { + mState.mSession.close(); + } + mState.mSession = null; + } + + mNavigationListeners.clear(); + mProgressListeners.clear(); + mContentListeners.clear(); + mSessionChangeListeners.clear(); + mTextInputListeners.clear(); + mVideoAvailabilityListeners.clear(); + mBitmapChangedListeners.clear(); + + if (mPrefs != null) { + mPrefs.unregisterOnSharedPreferenceChangeListener(this); + } + } + + private void dumpAllState() { + for (GeckoSession.NavigationDelegate listener: mNavigationListeners) { + dumpState(listener); + } + for (GeckoSession.ProgressDelegate listener: mProgressListeners) { + dumpState(listener); + } + for (GeckoSession.ContentDelegate listener: mContentListeners) { + dumpState(listener); + } + } + + private void dumpState(GeckoSession.NavigationDelegate aListener) { + if (mState.mSession != null) { + aListener.onCanGoBack(mState.mSession, mState.mCanGoBack); + aListener.onCanGoForward(mState.mSession, mState.mCanGoForward); + aListener.onLocationChange(mState.mSession, mState.mUri); + } + } + + private void dumpState(GeckoSession.ProgressDelegate aListener) { + if (mState.mIsLoading) { + aListener.onPageStart(mState.mSession, mState.mUri); + } else { + aListener.onPageStop(mState.mSession, true); + } + + if (mState.mSecurityInformation != null) { + aListener.onSecurityChange(mState.mSession, mState.mSecurityInformation); + } + } + + private void dumpState(GeckoSession.ContentDelegate aListener) { + aListener.onTitleChange(mState.mSession, mState.mTitle); + } + + public void setPermissionDelegate(GeckoSession.PermissionDelegate aDelegate) { + mPermissionDelegate = aDelegate; + } + + public void setPromptDelegate(GeckoSession.PromptDelegate aDelegate) { + mPromptDelegate = aDelegate; + } + + public void setHistoryDelegate(GeckoSession.HistoryDelegate aDelegate) { + mHistoryDelegate = aDelegate; + } + + public void addNavigationListener(GeckoSession.NavigationDelegate aListener) { + mNavigationListeners.add(aListener); + dumpState(aListener); + } + + public void removeNavigationListener(GeckoSession.NavigationDelegate aListener) { + mNavigationListeners.remove(aListener); + } + + public void addProgressListener(GeckoSession.ProgressDelegate aListener) { + mProgressListeners.add(aListener); + dumpState(aListener); + } + + public void removeProgressListener(GeckoSession.ProgressDelegate aListener) { + mProgressListeners.remove(aListener); + } + + public void addContentListener(GeckoSession.ContentDelegate aListener) { + mContentListeners.add(aListener); + dumpState(aListener); + } + + public void removeContentListener(GeckoSession.ContentDelegate aListener) { + mContentListeners.remove(aListener); + } + + public void addSessionChangeListener(SessionChangeListener aListener) { + mSessionChangeListeners.add(aListener); + } + + public void removeSessionChangeListener(SessionChangeListener aListener) { + mSessionChangeListeners.remove(aListener); + } + + public void addTextInputListener(GeckoSession.TextInputDelegate aListener) { + mTextInputListeners.add(aListener); + } + + public void removeTextInputListener(GeckoSession.TextInputDelegate aListener) { + mTextInputListeners.remove(aListener); + } + + public void addVideoAvailabilityListener(VideoAvailabilityListener aListener) { + mVideoAvailabilityListeners.add(aListener); + } + + public void removeVideoAvailabilityListener(VideoAvailabilityListener aListener) { + mVideoAvailabilityListeners.remove(aListener); + } + + public void addBitmapChangedListener(BitmapChangedListener aListener) { + mBitmapChangedListeners.add(aListener); + } + + public void removeBitmapChangedListener(BitmapChangedListener aListener) { + mBitmapChangedListeners.remove(aListener); + } + + private void setupSessionListeners(GeckoSession aSession) { + aSession.setNavigationDelegate(this); + aSession.setProgressDelegate(this); + aSession.setContentDelegate(this); + aSession.getTextInput().setDelegate(this); + aSession.setPermissionDelegate(this); + aSession.setPromptDelegate(this); + aSession.setContentBlockingDelegate(this); + aSession.setMediaDelegate(this); + aSession.setHistoryDelegate(this); + } + + private void cleanSessionListeners(GeckoSession aSession) { + aSession.setContentDelegate(null); + aSession.setNavigationDelegate(null); + aSession.setProgressDelegate(null); + aSession.getTextInput().setDelegate(null); + aSession.setPromptDelegate(null); + aSession.setPermissionDelegate(null); + aSession.setContentBlockingDelegate(null); + aSession.setMediaDelegate(null); + aSession.setHistoryDelegate(null); + } + + public void restore(Session store) { + mRegion = store.mRegion; + if (store.mState.mLastUse > 0) { + mState.mLastUse = store.mState.mLastUse; + } + if (mState.mSession != null && store.mState.mSessionState != null) { + mState.mSessionState = store.mState.mSessionState; + mState.mSession.restoreState(store.mState.mSessionState); + } + + if (mUsePrivateMode) { + loadPrivateBrowsingPage(); + } else if(mState.mSessionState == null || mState.mUri.equals(mContext.getResources().getString(R.string.about_blank)) || + (mState.mSessionState != null && mState.mSessionState.size() == 0)) { + loadHomePage(); + } + + dumpAllState(); + } + + private SessionState createSession() { + SessionSettings settings = new SessionSettings.Builder() + .withDefaultSettings(mContext) + .build(); + + return createSession(settings); + } + + private SessionState createSession(@NonNull SessionSettings aSettings) { + SessionState state = new SessionState(); + state.mSettings = aSettings; + + GeckoSessionSettings geckoSettings = new GeckoSessionSettings.Builder() + .useMultiprocess(aSettings.isMultiprocessEnabled()) + .usePrivateMode(mUsePrivateMode) + .useTrackingProtection(aSettings.isTrackingProtectionEnabled()) + .build(); + + if (aSettings.isServoEnabled()) { + if (isServoAvailable()) { + mState.mSession = createServoSession(mContext); + } else { + Log.e(LOGTAG, "Attempt to create a ServoSession. Servo hasn't been enable at build time. Using a GeckoSession instead."); + state.mSession = new GeckoSession(geckoSettings); + } + } else { + state.mSession = new GeckoSession(geckoSettings); + } + + state.mSession.getSettings().setSuspendMediaWhenInactive(aSettings.isSuspendMediaWhenInactiveEnabled()); + state.mSession.getSettings().setUserAgentMode(aSettings.getUserAgentMode()); + state.mSession.getSettings().setUserAgentOverride(aSettings.getUserAgentOverride()); + + if (!state.mSession.isOpen()) { + state.mSession.open(mRuntime); + } + + for (SessionChangeListener listener: mSessionChangeListeners) { + listener.onNewSession(state.mSession); + } + + return state; + } + + private void recreateSession() { + SessionState previous = mState; + + mState = createSession(previous.mSettings); + if (previous.mSessionState != null) + mState.mSession.restoreState(previous.mSessionState); + if (previous.mSession != null) { + closeSession(previous.mSession); + } + + for (SessionChangeListener listener : mSessionChangeListeners) { + listener.onCurrentSessionChange(previous.mSession, mState.mSession); + } + } + + private void closeSession(@NonNull GeckoSession aSession) { + cleanSessionListeners(aSession); + for (SessionChangeListener listener : mSessionChangeListeners) { + listener.onRemoveSession(aSession); + } + aSession.setActive(false); + aSession.stop(); + aSession.close(); + } + + public void setBitmap(Bitmap aBitmap, GeckoSession aSession) { + if (aSession == mState.mSession) { + mState.mBitmap = aBitmap; + for (BitmapChangedListener listener: mBitmapChangedListeners) { + listener.onBitmapChanged(mState.mBitmap); + } + } + } + + public @Nullable Bitmap getBitmap() { + return mState.mBitmap; + } + + public void purgeHistory() { + if (mState.mSession != null) { + mState.mSession.purgeHistory(); + } + } + + public void setRegion(String aRegion) { + Log.d(LOGTAG, "Session setRegion: " + aRegion); + mRegion = aRegion != null ? aRegion.toLowerCase() : "worldwide"; + + // There is a region initialize and the home is already loaded + if (mState.mSession != null && isHomeUri(getCurrentUri())) { + mState.mSession.loadUri("javascript:window.location.replace('" + getHomeUri() + "');"); + } + } + + public String getHomeUri() { + String homepage = SettingsStore.getInstance(mContext).getHomepage(); + if (homepage.equals(mContext.getString(R.string.homepage_url)) && mRegion != null) { + homepage = homepage + "?region=" + mRegion; + } + return homepage; + } + + public Boolean isHomeUri(String aUri) { + return aUri != null && aUri.toLowerCase().startsWith( + SettingsStore.getInstance(mContext).getHomepage() + ); + } + + public String getCurrentUri() { + return mState.mUri; + } + + public String getCurrentTitle() { + return mState.mTitle; + } + + public boolean isSecure() { + return mState.mSecurityInformation != null && mState.mSecurityInformation.isSecure; + } + + public Media getFullScreenVideo() { + for (Media media: mState.mMediaElements) { + if (media.isFullscreen()) { + return media; + } + } + if (mState.mMediaElements.size() > 0) { + return mState.mMediaElements.get(mState.mMediaElements.size() - 1); + } + + return null; + } + + public boolean isInputActive() { + return mState.mIsInputActive; + } + + public boolean canGoBack() { + return mState.mCanGoBack || isInFullScreen(); + } + + public void goBack() { + if (isInFullScreen()) { + exitFullScreen(); + } else if (mState.mCanGoBack && mState.mSession != null) { + mState.mSession.goBack(); + } + } + + public void goForward() { + if (mState.mCanGoForward && mState.mSession != null) { + mState.mSession.goForward(); + } + } + + public void setActive(boolean aActive) { + if (mState.mSession != null) { + mState.mSession.setActive(aActive); + } + } + + public void reload() { + if (mState.mSession != null) { + mState.mSession.reload(); + } + } + + public void stop() { + if (mState.mSession != null) { + mState.mSession.stop(); + } + } + + public void loadUri(String aUri) { + if (aUri == null) { + aUri = getHomeUri(); + } + if (mState.mSession != null) { + Log.d(LOGTAG, "Loading URI: " + aUri); + mState.mSession.loadUri(aUri); + } + } + + public void loadHomePage() { + loadUri(getHomeUri()); + } + + public void loadPrivateBrowsingPage() { + if (mState.mSession != null) { + mState.mSession.loadData(mPrivatePage, "text/html"); + } + } + + public void toggleServo() { + if (mState.mSession == null) { + return; + } + + Log.v("servo", "toggleServo"); + SessionState previous = mState; + String uri = getCurrentUri(); + + SessionSettings settings = new SessionSettings.Builder() + .withDefaultSettings(mContext) + .withServo(!isInstanceOfServoSession(mState.mSession)) + .build(); + + mState = createSession(settings); + closeSession(previous.mSession); + loadUri(uri); + } + + public boolean isInFullScreen() { + return mState.mFullScreen; + } + + public void exitFullScreen() { + if (mState.mSession != null) { + mState.mSession.exitFullScreen(); + } + } + + public GeckoSession getGeckoSession() { + return mState.mSession; + } + + public boolean isPrivateMode() { + if (mState.mSession != null) { + return mState.mSession.getSettings().getUsePrivateMode(); + } + return false; + } + + // Session Settings + + protected void setServo(final boolean enabled) { + mState.mSettings.setServoEnabled(enabled); + if (mState.mSession != null && isInstanceOfServoSession(mState.mSession) != enabled) { + toggleServo(); + } + } + + public int getUaMode() { + return mState.mSession.getSettings().getUserAgentMode(); + } + + private static final String M_PREFIX = "m."; + private static final String MOBILE_PREFIX = "mobile."; + + private String checkForMobileSite(String aUri) { + if (aUri == null) { + return null; + } + String result = null; + URI uri; + try { + uri = new URI(aUri); + } catch (URISyntaxException | NullPointerException e) { + Log.d(LOGTAG, "Error parsing URL: " + aUri + " " + e.getMessage()); + return null; + } + String authority = uri.getAuthority(); + if (authority == null) { + return null; + } + authority = authority.toLowerCase(); + String foundPrefix = null; + if (authority.startsWith(M_PREFIX)) { + foundPrefix= M_PREFIX; + } else if (authority.startsWith(MOBILE_PREFIX)) { + foundPrefix = MOBILE_PREFIX; + } + if (foundPrefix != null) { + try { + uri = new URI(uri.getScheme(), authority.substring(foundPrefix.length()), uri.getPath(), uri.getQuery(), uri.getFragment()); + result = uri.toString(); + } catch (URISyntaxException | NullPointerException e) { + Log.d(LOGTAG, "Error dropping mobile prefix from: " + aUri + " " + e.getMessage()); + } + } + return result; + } + + public void setUaMode(int mode) { + if (mState.mSession == null) { + return; + } + mState.mSettings.setUserAgentMode(mode); + mState.mSession.getSettings().setUserAgentMode(mode); + String overrideUri = null; + if (mode == GeckoSessionSettings.USER_AGENT_MODE_DESKTOP) { + mState.mSettings.setViewportMode(GeckoSessionSettings.VIEWPORT_MODE_DESKTOP); + overrideUri = checkForMobileSite(mState.mUri); + } else { + mState.mSettings.setViewportMode(GeckoSessionSettings.VIEWPORT_MODE_MOBILE); + } + mState.mSession.getSettings().setViewportMode(mState.mSettings.getViewportMode()); + mState.mSession.loadUri(overrideUri != null ? overrideUri : mState.mUri, GeckoSession.LOAD_FLAGS_BYPASS_CACHE | GeckoSession.LOAD_FLAGS_REPLACE_HISTORY); + } + + protected void setMultiprocess(final boolean aEnabled) { + if (mState.mSettings.isMultiprocessEnabled() != aEnabled) { + mState.mSettings.setMultiprocessEnabled(aEnabled); + recreateSession(); + } + } + + protected void setTrackingProtection(final boolean aEnabled) { + if (mState.mSettings.isTrackingProtectionEnabled() != aEnabled) { + mState.mSettings.setTrackingProtectionEnabled(aEnabled); + recreateSession(); + } + } + + public void clearCache(final long clearFlags) { + if (mRuntime != null) { + // Per GeckoView Docs: + // Note: Any open session may re-accumulate previously cleared data. + // To ensure that no persistent data is left behind, you need to close all sessions prior to clearing data. + // https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/StorageController.html#clearData-long- + if (mState.mSession != null) { + mState.mSession.stop(); + mState.mSession.close(); + } + + mRuntime.getStorageController().clearData(clearFlags).then(aVoid -> { + recreateSession(); + return null; + }); + } + } + + public void updateLastUse() { + mState.mLastUse = System.currentTimeMillis(); + } + + public long getLastUse() { + return mState.mLastUse; + } + + // NavigationDelegate + + @Override + public void onLocationChange(@NonNull GeckoSession aSession, String aUri) { + if (mState.mSession != aSession) { + return; + } + + mState.mPreviousUri = mState.mUri; + mState.mUri = aUri; + + for (GeckoSession.NavigationDelegate listener : mNavigationListeners) { + listener.onLocationChange(aSession, aUri); + } + + // The homepage finishes loading after the region has been updated + if (mRegion != null && aUri.equalsIgnoreCase(SettingsStore.getInstance(mContext).getHomepage())) { + aSession.loadUri("javascript:window.location.replace('" + getHomeUri() + "');"); + } + } + + @Override + public void onCanGoBack(@NonNull GeckoSession aSession, boolean aCanGoBack) { + if (mState.mSession != aSession) { + return; + } + Log.d(LOGTAG, "Session onCanGoBack: " + (aCanGoBack ? "true" : "false")); + mState.mCanGoBack = aCanGoBack; + + for (GeckoSession.NavigationDelegate listener : mNavigationListeners) { + listener.onCanGoBack(aSession, aCanGoBack); + } + } + + @Override + public void onCanGoForward(@NonNull GeckoSession aSession, boolean aCanGoForward) { + if (mState.mSession != aSession) { + return; + } + Log.d(LOGTAG, "Session onCanGoForward: " + (aCanGoForward ? "true" : "false")); + mState.mCanGoForward = aCanGoForward; + + for (GeckoSession.NavigationDelegate listener : mNavigationListeners) { + listener.onCanGoForward(aSession, aCanGoForward); + } + } + + @Override + public @Nullable GeckoResult onLoadRequest(@NonNull GeckoSession aSession, @NonNull LoadRequest aRequest) { + String uri = aRequest.uri; + + Log.d(LOGTAG, "onLoadRequest: " + uri); + + String uriOverride = SessionUtils.checkYoutubeOverride(uri); + if (uriOverride != null) { + aSession.loadUri(uriOverride); + return GeckoResult.DENY; + } + + if (aSession == mState.mSession) { + Log.d(LOGTAG, "Testing for UA override"); + + final String userAgentOverride = mUserAgentOverride.lookupOverride(uri); + aSession.getSettings().setUserAgentOverride(userAgentOverride); + mState.mSettings.setUserAgentOverride(userAgentOverride); + } + + if (mContext.getString(R.string.about_private_browsing).equalsIgnoreCase(uri)) { + return GeckoResult.DENY; + } + + if (mNavigationListeners.size() == 0) { + return GeckoResult.ALLOW; + } + + final GeckoResult result = new GeckoResult<>(); + AtomicInteger count = new AtomicInteger(0); + AtomicBoolean allowed = new AtomicBoolean(false); + for (GeckoSession.NavigationDelegate listener: mNavigationListeners) { + GeckoResult listenerResult = listener.onLoadRequest(aSession, aRequest); + if (listenerResult != null) { + listenerResult.then(value -> { + if (AllowOrDeny.ALLOW.equals(value)) { + allowed.set(true); + } + if (count.getAndIncrement() == mNavigationListeners.size() - 1) { + result.complete(allowed.get() ? AllowOrDeny.ALLOW : AllowOrDeny.DENY); + } + + return null; + }); + + } else { + allowed.set(true); + if (count.getAndIncrement() == mNavigationListeners.size() - 1) { + result.complete(allowed.get() ? AllowOrDeny.ALLOW : AllowOrDeny.DENY); + } + } + } + + return result; + } + + @Override + public GeckoResult onNewSession(@NonNull GeckoSession aSession, @NonNull String aUri) { + Log.d(LOGTAG, "Session onNewSession: " + aUri); + + Session session = SessionStore.get().createSession(this); + return GeckoResult.fromValue(session.getGeckoSession()); + } + + @Override + public GeckoResult onLoadError(@NonNull GeckoSession session, String uri, @NonNull WebRequestError error) { + Log.d(LOGTAG, "Session onLoadError: " + uri); + + return GeckoResult.fromValue(InternalPages.createErrorPageDataURI(mContext, uri, error.category, error.code)); + } + + // Progress Listener + + @Override + public void onPageStart(@NonNull GeckoSession aSession, @NonNull String aUri) { + if (mState.mSession != aSession) { + return; + } + Log.d(LOGTAG, "Session onPageStart"); + mState.mIsLoading = true; + TelemetryWrapper.startPageLoadTime(); + + for (GeckoSession.ProgressDelegate listener : mProgressListeners) { + listener.onPageStart(aSession, aUri); + } + } + + @Override + public void onPageStop(@NonNull GeckoSession aSession, boolean b) { + if (mState.mSession != aSession) { + return; + } + Log.d(LOGTAG, "Session onPageStop"); + mState.mIsLoading = false; + if (!SessionUtils.isLocalizedContent(mState.mUri)) { + TelemetryWrapper.uploadPageLoadToHistogram(mState.mUri); + } + + for (GeckoSession.ProgressDelegate listener : mProgressListeners) { + listener.onPageStop(aSession, b); + } + } + + @Override + public void onSecurityChange(@NonNull GeckoSession aSession, @NonNull SecurityInformation aInformation) { + if (mState.mSession != aSession) { + return; + } + Log.d(LOGTAG, "Session onPageStop"); + mState.mSecurityInformation = aInformation; + + for (GeckoSession.ProgressDelegate listener : mProgressListeners) { + listener.onSecurityChange(aSession, aInformation); + } + } + + @Override + public void onSessionStateChange(@NonNull GeckoSession aSession, + @NonNull GeckoSession.SessionState aSessionState) { + if (mState.mSession == aSession) { + mState.mSessionState = aSessionState; + } + } + + // Content Delegate + + @Override + public void onTitleChange(@NonNull GeckoSession aSession, String aTitle) { + if (mState.mSession != aSession) { + return; + } + + mState.mTitle = aTitle; + + for (GeckoSession.ContentDelegate listener : mContentListeners) { + listener.onTitleChange(aSession, aTitle); + } + } + + @Override + public void onCloseRequest(@NonNull GeckoSession aSession) { + + } + + @Override + public void onFullScreen(@NonNull GeckoSession aSession, boolean aFullScreen) { + if (mState.mSession != aSession) { + return; + } + Log.d(LOGTAG, "Session onFullScreen"); + mState.mFullScreen = aFullScreen; + + for (GeckoSession.ContentDelegate listener : mContentListeners) { + listener.onFullScreen(aSession, aFullScreen); + } + } + + @Override + public void onContextMenu(@NonNull GeckoSession session, int screenX, int screenY, @NonNull ContextElement element) { + if (mState.mSession == session) { + for (GeckoSession.ContentDelegate listener : mContentListeners) { + listener.onContextMenu(session, screenX, screenY, element); + } + } + } + + @Override + public void onCrash(@NonNull GeckoSession session) { + Log.e(LOGTAG,"Child crashed. Creating new session"); + recreateSession(); + loadUri(getHomeUri()); + } + + @Override + public void onFirstComposite(@NonNull GeckoSession aSession) { + if (mState.mSession == aSession) { + for (GeckoSession.ContentDelegate listener : mContentListeners) { + listener.onFirstComposite(aSession); + } + } + } + + @Override + public void onFirstContentfulPaint(@NonNull GeckoSession aSession) { + if (mState.mSession == aSession) { + for (GeckoSession.ContentDelegate listener : mContentListeners) { + listener.onFirstContentfulPaint(aSession); + } + } + } + + // TextInput Delegate + + @Override + public void restartInput(@NonNull GeckoSession aSession, int reason) { + if (mState.mSession == aSession) { + for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { + listener.restartInput(aSession, reason); + } + } + } + + @Override + public void showSoftInput(@NonNull GeckoSession aSession) { + if (mState.mSession == aSession) { + mState.mIsInputActive = true; + for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { + listener.showSoftInput(aSession); + } + } + } + + @Override + public void hideSoftInput(@NonNull GeckoSession aSession) { + if (mState.mSession == aSession) { + mState.mIsInputActive = false; + for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { + listener.hideSoftInput(aSession); + } + } + } + + @Override + public void updateSelection(@NonNull GeckoSession aSession, int selStart, int selEnd, int compositionStart, int compositionEnd) { + if (mState.mSession == aSession) { + for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { + listener.updateSelection(aSession, selStart, selEnd, compositionStart, compositionEnd); + } + } + } + + @Override + public void updateExtractedText(@NonNull GeckoSession aSession, @NonNull ExtractedTextRequest request, @NonNull ExtractedText text) { + if (mState.mSession == aSession) { + for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { + listener.updateExtractedText(aSession, request, text); + } + } + } + + @Override + public void updateCursorAnchorInfo(@NonNull GeckoSession aSession, @NonNull CursorAnchorInfo info) { + if (mState.mSession == aSession) { + for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { + listener.updateCursorAnchorInfo(aSession, info); + } + } + } + + @Override + public void onContentBlocked(@NonNull final GeckoSession session, @NonNull final ContentBlocking.BlockEvent event) { + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.AD) != 0) { + Log.i(LOGTAG, "Blocking Ad: " + event.uri); + } + + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.ANALYTIC) != 0) { + Log.i(LOGTAG, "Blocking Analytic: " + event.uri); + } + + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.CONTENT) != 0) { + Log.i(LOGTAG, "Blocking Content: " + event.uri); + } + + if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.SOCIAL) != 0) { + Log.i(LOGTAG, "Blocking Social: " + event.uri); + } + } + + // PromptDelegate + + @Nullable + @Override + public GeckoResult onAlertPrompt(@NonNull GeckoSession aSession, @NonNull AlertPrompt alertPrompt) { + if (mState.mSession == aSession && mPromptDelegate != null) { + return mPromptDelegate.onAlertPrompt(aSession, alertPrompt); + } + return GeckoResult.fromValue(alertPrompt.dismiss()); + } + + @Nullable + @Override + public GeckoResult onButtonPrompt(@NonNull GeckoSession aSession, @NonNull ButtonPrompt buttonPrompt) { + if (mState.mSession == aSession && mPromptDelegate != null) { + return mPromptDelegate.onButtonPrompt(aSession, buttonPrompt); + } + return GeckoResult.fromValue(buttonPrompt.dismiss()); + } + + @Nullable + @Override + public GeckoResult onTextPrompt(@NonNull GeckoSession aSession, @NonNull TextPrompt textPrompt) { + if (mState.mSession == aSession && mPromptDelegate != null) { + return mPromptDelegate.onTextPrompt(aSession, textPrompt); + } + return GeckoResult.fromValue(textPrompt.dismiss()); + } + + @Nullable + @Override + public GeckoResult onAuthPrompt(@NonNull GeckoSession aSession, @NonNull AuthPrompt authPrompt) { + if (mState.mSession == aSession && mPromptDelegate != null) { + return mPromptDelegate.onAuthPrompt(aSession, authPrompt); + } + return GeckoResult.fromValue(authPrompt.dismiss()); + } + + @Nullable + @Override + public GeckoResult onChoicePrompt(@NonNull GeckoSession aSession, @NonNull ChoicePrompt choicePrompt) { + if (mState.mSession == aSession && mPromptDelegate != null) { + return mPromptDelegate.onChoicePrompt(aSession, choicePrompt); + } + return GeckoResult.fromValue(choicePrompt.dismiss()); + } + + @Nullable + @Override + public GeckoResult onColorPrompt(@NonNull GeckoSession aSession, @NonNull ColorPrompt colorPrompt) { + if (mState.mSession == aSession && mPromptDelegate != null) { + return mPromptDelegate.onColorPrompt(aSession, colorPrompt); + } + return GeckoResult.fromValue(colorPrompt.dismiss()); + } + + @Nullable + @Override + public GeckoResult onDateTimePrompt(@NonNull GeckoSession aSession, @NonNull DateTimePrompt dateTimePrompt) { + if (mState.mSession == aSession && mPromptDelegate != null) { + return mPromptDelegate.onDateTimePrompt(aSession, dateTimePrompt); + } + return GeckoResult.fromValue(dateTimePrompt.dismiss()); + } + + @Nullable + @Override + public GeckoResult onFilePrompt(@NonNull GeckoSession aSession, @NonNull FilePrompt filePrompt) { + if (mPromptDelegate != null) { + return mPromptDelegate.onFilePrompt(aSession, filePrompt); + } + return GeckoResult.fromValue(filePrompt.dismiss()); + } + + // MediaDelegate + + @Override + public void onMediaAdd(@NonNull GeckoSession aSession, @NonNull MediaElement element) { + if (mState.mSession != aSession) { + return; + } + Media media = new Media(element); + mState.mMediaElements.add(media); + + if (mState.mMediaElements.size() == 1) { + for (VideoAvailabilityListener listener: mVideoAvailabilityListeners) { + listener.onVideoAvailabilityChanged(true); + } + } + } + + @Override + public void onMediaRemove(@NonNull GeckoSession aSession, @NonNull MediaElement element) { + if (mState.mSession != aSession) { + return; + } + for (int i = 0; i < mState.mMediaElements.size(); ++i) { + Media media = mState.mMediaElements.get(i); + if (media.getMediaElement() == element) { + media.unload(); + mState.mMediaElements.remove(i); + if (mState.mMediaElements.size() == 0) { + for (VideoAvailabilityListener listener: mVideoAvailabilityListeners) { + listener.onVideoAvailabilityChanged(false); + } + } + return; + } + } + } + + // HistoryDelegate + + @Override + public void onHistoryStateChange(@NonNull GeckoSession aSession, @NonNull GeckoSession.HistoryDelegate.HistoryList historyList) { + if (mState.mSession == aSession && mHistoryDelegate != null) { + mHistoryDelegate.onHistoryStateChange(aSession, historyList); + } + } + + @Nullable + @Override + public GeckoResult onVisited(@NonNull GeckoSession aSession, @NonNull String url, @Nullable String lastVisitedURL, int flags) { + if (mState.mSession == aSession && mHistoryDelegate != null) { + return mHistoryDelegate.onVisited(aSession, url, lastVisitedURL, flags); + } + + return GeckoResult.fromValue(false); + } + + @UiThread + @Nullable + public GeckoResult getVisited(@NonNull GeckoSession aSession, @NonNull String[] urls) { + if (mState.mSession == aSession && mHistoryDelegate != null) { + return mHistoryDelegate.getVisited(aSession, urls); + } + + return GeckoResult.fromValue(new boolean[]{}); + } + + // PermissionDelegate + @Override + public void onAndroidPermissionsRequest(@NonNull GeckoSession aSession, @Nullable String[] strings, @NonNull Callback callback) { + if (mState.mSession == aSession && mPermissionDelegate != null) { + mPermissionDelegate.onAndroidPermissionsRequest(aSession, strings, callback); + } + } + + @Override + public void onContentPermissionRequest(@NonNull GeckoSession aSession, @Nullable String s, int i, @NonNull Callback callback) { + if (mState.mSession == aSession && mPermissionDelegate != null) { + mPermissionDelegate.onContentPermissionRequest(aSession, s, i, callback); + } + } + + @Override + public void onMediaPermissionRequest(@NonNull GeckoSession aSession, @NonNull String s, @Nullable MediaSource[] mediaSources, @Nullable MediaSource[] mediaSources1, @NonNull MediaCallback mediaCallback) { + if (mState.mSession == aSession && mPermissionDelegate != null) { + mPermissionDelegate.onMediaPermissionRequest(aSession, s, mediaSources, mediaSources1, mediaCallback); + } + } + + + // SharedPreferences.OnSharedPreferenceChangeListener + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (mContext != null) { + if (key.equals(mContext.getString(R.string.settings_key_geolocation_data))) { + GeolocationData data = GeolocationData.parse(sharedPreferences.getString(key, null)); + setRegion(data.getCountryCode()); + } + } + } +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStack.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStack.java deleted file mode 100644 index 120c0f9751..0000000000 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/engine/SessionStack.java +++ /dev/null @@ -1,1422 +0,0 @@ -/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.vrbrowser.browser.engine; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.inputmethod.CursorAnchorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; - -import org.mozilla.geckoview.AllowOrDeny; -import org.mozilla.geckoview.ContentBlocking; -import org.mozilla.geckoview.GeckoResult; -import org.mozilla.geckoview.GeckoRuntime; -import org.mozilla.geckoview.GeckoSession; -import org.mozilla.geckoview.GeckoSessionSettings; -import org.mozilla.geckoview.MediaElement; -import org.mozilla.geckoview.WebRequestError; -import org.mozilla.vrbrowser.R; -import org.mozilla.vrbrowser.browser.Media; -import org.mozilla.vrbrowser.browser.SessionChangeListener; -import org.mozilla.vrbrowser.browser.SettingsStore; -import org.mozilla.vrbrowser.browser.UserAgentOverride; -import org.mozilla.vrbrowser.browser.VideoAvailabilityListener; -import org.mozilla.vrbrowser.geolocation.GeolocationData; -import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; -import org.mozilla.vrbrowser.utils.InternalPages; -import org.mozilla.vrbrowser.utils.SystemUtils; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.mozilla.vrbrowser.utils.ServoUtils.createServoSession; -import static org.mozilla.vrbrowser.utils.ServoUtils.isInstanceOfServoSession; -import static org.mozilla.vrbrowser.utils.ServoUtils.isServoAvailable; - -public class SessionStack implements ContentBlocking.Delegate, GeckoSession.NavigationDelegate, - GeckoSession.ProgressDelegate, GeckoSession.ContentDelegate, GeckoSession.TextInputDelegate, - GeckoSession.PromptDelegate, GeckoSession.MediaDelegate, GeckoSession.HistoryDelegate, - SharedPreferences.OnSharedPreferenceChangeListener { - - private static final String LOGTAG = SystemUtils.createLogtag(SessionStack.class); - // You can test a local file using: "resource://android/assets/webvr/index.html" - public static final int NO_SESSION = -1; - - private transient LinkedList mNavigationListeners; - private transient LinkedList mProgressListeners; - private transient LinkedList mContentListeners; - private transient LinkedList mSessionChangeListeners; - private transient LinkedList mTextInputListeners; - private transient LinkedList mVideoAvailabilityListeners; - private transient UserAgentOverride mUserAgentOverride; - - private transient GeckoSession mCurrentSession; - private LinkedHashMap mSessions; - private transient GeckoSession.PermissionDelegate mPermissionDelegate; - private transient GeckoSession.PromptDelegate mPromptDelegate; - private transient GeckoSession.HistoryDelegate mHistoryDelegate; - private int mPreviousGeckoSessionId = NO_SESSION; - private String mRegion; - private transient Context mContext; - private transient SharedPreferences mPrefs; - private transient GeckoRuntime mRuntime; - private boolean mUsePrivateMode; - private transient byte[] mPrivatePage; - - protected SessionStack(Context context, GeckoRuntime runtime, boolean usePrivateMode) { - mRuntime = runtime; - mSessions = new LinkedHashMap<>(); - mUsePrivateMode = usePrivateMode; - - mNavigationListeners = new LinkedList<>(); - mProgressListeners = new LinkedList<>(); - mContentListeners = new LinkedList<>(); - mSessionChangeListeners = new LinkedList<>(); - mTextInputListeners = new LinkedList<>(); - mVideoAvailabilityListeners = new LinkedList<>(); - - if (mPrefs != null) { - mPrefs.registerOnSharedPreferenceChangeListener(this); - } - - mContext = context; - mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); - - InternalPages.PageResources pageResources = InternalPages.PageResources.create(R.raw.private_mode, R.raw.private_style); - mPrivatePage = InternalPages.createAboutPage(mContext, pageResources); - - if (mUserAgentOverride == null) { - mUserAgentOverride = new UserAgentOverride(); - mUserAgentOverride.loadOverridesFromAssets((Activity)mContext, mContext.getString(R.string.user_agent_override_file)); - } - } - - protected void shutdown() { - for (Map.Entry session : mSessions.entrySet()) { - session.getValue().mSession.close(); - } - - mSessions.clear(); - - mNavigationListeners.clear(); - mProgressListeners.clear(); - mContentListeners.clear(); - mSessionChangeListeners.clear(); - mTextInputListeners.clear(); - mVideoAvailabilityListeners.clear(); - - if (mPrefs != null) { - mPrefs.unregisterOnSharedPreferenceChangeListener(this); - } - - mCurrentSession = null; - mPreviousGeckoSessionId = NO_SESSION; - } - - private void dumpAllState(GeckoSession aSession) { - for (GeckoSession.NavigationDelegate listener: mNavigationListeners) { - dumpState(aSession, listener); - } - for (GeckoSession.ProgressDelegate listener: mProgressListeners) { - dumpState(aSession, listener); - } - for (GeckoSession.ContentDelegate listener: mContentListeners) { - dumpState(aSession, listener); - } - } - - private void dumpState(GeckoSession aSession, GeckoSession.NavigationDelegate aListener) { - boolean canGoForward = false; - boolean canGoBack = false; - String uri = ""; - if (aSession != null) { - SessionState state = mSessions.get(aSession.hashCode()); - if (state != null) { - canGoBack = state.mCanGoBack; - canGoForward = state.mCanGoForward; - uri = state.mUri; - } - } - aListener.onCanGoBack(aSession, canGoBack); - aListener.onCanGoForward(aSession, canGoForward); - aListener.onLocationChange(aSession, uri); - } - - private void dumpState(GeckoSession aSession, GeckoSession.ProgressDelegate aListener) { - boolean isLoading = false; - GeckoSession.ProgressDelegate.SecurityInformation securityInfo = null; - String uri = ""; - if (aSession != null) { - SessionState state = mSessions.get(aSession.hashCode()); - if (state != null) { - isLoading = state.mIsLoading; - securityInfo = state.mSecurityInformation; - uri = state.mUri; - } - } - if (isLoading) { - aListener.onPageStart(aSession, uri); - } else { - aListener.onPageStop(aSession, true); - } - - if (securityInfo != null) { - aListener.onSecurityChange(aSession, securityInfo); - } - } - - private void dumpState(GeckoSession aSession, GeckoSession.ContentDelegate aListener) { - String title = ""; - if (aSession != null) { - SessionState state = mSessions.get(aSession.hashCode()); - if (state != null) { - title = state.mTitle; - } - } - - aListener.onTitleChange(aSession, title); - } - - public void setPermissionDelegate(GeckoSession.PermissionDelegate aDelegate) { - mPermissionDelegate = aDelegate; - for (HashMap.Entry entry : mSessions.entrySet()) { - entry.getValue().mSession.setPermissionDelegate(aDelegate); - } - } - - public void setPromptDelegate(GeckoSession.PromptDelegate aDelegate) { - mPromptDelegate = aDelegate; - for (HashMap.Entry entry : mSessions.entrySet()) { - entry.getValue().mSession.setPromptDelegate(aDelegate); - } - } - - public void setHistoryDelegate(GeckoSession.HistoryDelegate aDelegate) { - mHistoryDelegate = aDelegate; - for (HashMap.Entry entry : mSessions.entrySet()) { - entry.getValue().mSession.setHistoryDelegate(aDelegate); - } - } - - public void addNavigationListener(GeckoSession.NavigationDelegate aListener) { - mNavigationListeners.add(aListener); - dumpState(mCurrentSession, aListener); - } - - public void removeNavigationListener(GeckoSession.NavigationDelegate aListener) { - mNavigationListeners.remove(aListener); - } - - public void addProgressListener(GeckoSession.ProgressDelegate aListener) { - mProgressListeners.add(aListener); - dumpState(mCurrentSession, aListener); - } - - public void removeProgressListener(GeckoSession.ProgressDelegate aListener) { - mProgressListeners.remove(aListener); - } - - public void addContentListener(GeckoSession.ContentDelegate aListener) { - mContentListeners.add(aListener); - dumpState(mCurrentSession, aListener); - } - - public void removeContentListener(GeckoSession.ContentDelegate aListener) { - mContentListeners.remove(aListener); - } - - public void addSessionChangeListener(SessionChangeListener aListener) { - mSessionChangeListeners.add(aListener); - } - - public void removeSessionChangeListener(SessionChangeListener aListener) { - mSessionChangeListeners.remove(aListener); - } - - public void addTextInputListener(GeckoSession.TextInputDelegate aListener) { - mTextInputListeners.add(aListener); - } - - public void removeTextInputListener(GeckoSession.TextInputDelegate aListener) { - mTextInputListeners.remove(aListener); - } - - public void addVideoAvailabilityListener(VideoAvailabilityListener aListener) { - mVideoAvailabilityListeners.add(aListener); - } - - public void removeVideoAvailabilityListener(VideoAvailabilityListener aListener) { - mVideoAvailabilityListeners.remove(aListener); - } - - public void restore(SessionStack store, int currentSessionId) { - mSessions.clear(); - - mPreviousGeckoSessionId = store.mPreviousGeckoSessionId; - mRegion = store.mRegion; - - for (Map.Entry entry : store.mSessions.entrySet()) { - SessionState state = entry.getValue(); - - GeckoSessionSettings geckoSettings = new GeckoSessionSettings.Builder() - .useMultiprocess(state.mSettings.isMultiprocessEnabled()) - .usePrivateMode(mUsePrivateMode) - .userAgentMode(state.mSettings.getUserAgentMode()) - .userAgentOverride(state.mSettings.getUserAgentOverride()) - .suspendMediaWhenInactive(state.mSettings.isSuspendMediaWhenInactiveEnabled()) - .useTrackingProtection(state.mSettings.isTrackingProtectionEnabled()) - .build(); - - if (state.mSettings.isServoEnabled()) { - if (isServoAvailable()) { - state.mSession = createServoSession(mContext); - } else { - Log.e(LOGTAG, "Attempt to create a ServoSession. Servo hasn't been enable at build time. Using a GeckoSession instead."); - state.mSession = new GeckoSession(geckoSettings); - } - } else { - state.mSession = new GeckoSession(geckoSettings); - } - - if (state.mSessionState != null) { - state.mSession.restoreState(state.mSessionState); - } - - int newSessionId = state.mSession.hashCode(); - - state.mSession.setNavigationDelegate(this); - state.mSession.setProgressDelegate(this); - state.mSession.setContentDelegate(this); - state.mSession.getTextInput().setDelegate(this); - state.mSession.setPermissionDelegate(mPermissionDelegate); - state.mSession.setPromptDelegate(mPromptDelegate); - state.mSession.setContentBlockingDelegate(this); - state.mSession.setMediaDelegate(this); - state.mSession.setHistoryDelegate(this); - for (SessionChangeListener listener: mSessionChangeListeners) { - listener.onNewSession(state.mSession, newSessionId); - } - - mSessions.put(newSessionId, state); - - if (entry.getKey() == currentSessionId) { - setCurrentSession(newSessionId); - } - - if (mUsePrivateMode) { - loadPrivateBrowsingPage(); - - } else if(state.mSessionState == null || state.mUri.equals(mContext.getResources().getString(R.string.about_blank)) || - (state.mSessionState != null && state.mSessionState.size() == 0)) { - loadHomePage(); - } - - dumpAllState(state.mSession); - } - } - - private int createSession() { - SessionSettings settings = new SessionSettings.Builder() - .withDefaultSettings(mContext) - .build(); - - return createSession(settings); - } - - private int createSession(@NonNull SessionSettings aSettings) { - SessionState state = new SessionState(); - state.mSettings = aSettings; - - GeckoSessionSettings geckoSettings = new GeckoSessionSettings.Builder() - .useMultiprocess(aSettings.isMultiprocessEnabled()) - .usePrivateMode(mUsePrivateMode) - .useTrackingProtection(aSettings.isTrackingProtectionEnabled()) - .build(); - - if (aSettings.isServoEnabled()) { - if (isServoAvailable()) { - state.mSession = createServoSession(mContext); - } else { - Log.e(LOGTAG, "Attempt to create a ServoSession. Servo hasn't been enable at build time. Using a GeckoSession instead."); - state.mSession = new GeckoSession(geckoSettings); - } - } else { - state.mSession = new GeckoSession(geckoSettings); - } - - int result = state.mSession.hashCode(); - mSessions.put(result, state); - - state.mSession.getSettings().setSuspendMediaWhenInactive(aSettings.isSuspendMediaWhenInactiveEnabled()); - state.mSession.getSettings().setUserAgentMode(aSettings.getUserAgentMode()); - state.mSession.getSettings().setUserAgentOverride(aSettings.getUserAgentOverride()); - state.mSession.setNavigationDelegate(this); - state.mSession.setProgressDelegate(this); - state.mSession.setContentDelegate(this); - state.mSession.getTextInput().setDelegate(this); - state.mSession.setPermissionDelegate(mPermissionDelegate); - state.mSession.setPromptDelegate(mPromptDelegate); - state.mSession.setContentBlockingDelegate(this); - state.mSession.setMediaDelegate(this); - state.mSession.setHistoryDelegate(this); - for (SessionChangeListener listener: mSessionChangeListeners) { - listener.onNewSession(state.mSession, result); - } - - return result; - } - - private void recreateAllSessions() { - Map sessions = (Map) mSessions.clone(); - for (Integer sessionId : sessions.keySet()) { - recreateSession(sessionId); - } - } - - private void recreateSession(int sessionId) { - SessionState previousSessionState = mSessions.get(sessionId); - - previousSessionState.mSession.stop(); - previousSessionState.mSession.close(); - - int newSessionId = createSession(previousSessionState.mSettings); - GeckoSession session = mSessions.get(newSessionId).mSession; - if (previousSessionState.mSessionState != null) - session.restoreState(previousSessionState.mSessionState); - setCurrentSession(newSessionId); - removeSession(sessionId); - } - - private void removeSession(int aSessionId) { - GeckoSession session = getSession(aSessionId); - if (session != null) { - session.setContentDelegate(null); - session.setNavigationDelegate(null); - session.setProgressDelegate(null); - session.getTextInput().setDelegate(null); - session.setPromptDelegate(null); - session.setPermissionDelegate(null); - session.setContentBlockingDelegate(null); - session.setMediaDelegate(null); - session.setHistoryDelegate(null); - mSessions.remove(aSessionId); - for (SessionChangeListener listener: mSessionChangeListeners) { - listener.onRemoveSession(session, aSessionId); - } - session.setActive(false); - session.stop(); - session.close(); - } - } - - public void newSession() { - SessionSettings settings = new SessionSettings.Builder().withDefaultSettings(mContext).build(); - int id = createSession(settings); - setCurrentSession(id); - } - - public void newSessionWithUrl(String url) { - newSession(); - loadUri(url); - } - - private void unstackSession() { - int currentSessionId = getCurrentSessionId(); - ArrayList sessionsStack = new ArrayList<>(mSessions.keySet()); - int index = sessionsStack.indexOf(currentSessionId); - if (index > 0) { - int prevSessionId = (Integer)sessionsStack.get(index-1); - setCurrentSession(prevSessionId); - removeSession(currentSessionId); - } - } - - public GeckoSession getSession(int aId) { - SessionState state = mSessions.get(aId); - if (state == null) { - return null; - } - return state.mSession; - } - - public boolean containsSession(GeckoSession aSession) { - return getSessionId(aSession) != NO_SESSION; - } - - private Integer getSessionId(GeckoSession aSession) { - for (Map.Entry entry : mSessions.entrySet()) { - if (entry.getValue().mSession == aSession) { - return entry.getKey(); - } - } - return NO_SESSION; - } - - public String getUriFromSession(GeckoSession aSession) { - Integer sessionId = getSessionId(aSession); - if (sessionId == NO_SESSION) { - return ""; - } - SessionState state = mSessions.get(sessionId); - if (state != null) { - return state.mUri; - } - - return ""; - } - - public void setCurrentSession(int aId) { - Log.d(LOGTAG, "Creating session: " + aId); - - if (mCurrentSession != null) { - mCurrentSession.setActive(false); - } - - mCurrentSession = null; - SessionState state = mSessions.get(aId); - if (state != null) { - mCurrentSession = state.mSession; - if (!mCurrentSession.isOpen()) { - mCurrentSession.open(mRuntime); - } - for (SessionChangeListener listener: mSessionChangeListeners) { - listener.onCurrentSessionChange(mCurrentSession, aId); - } - } - dumpAllState(mCurrentSession); - - if (mCurrentSession != null) { - mCurrentSession.setActive(true); - } - } - - public void purgeHistory() { - for (Map.Entry entry :mSessions.entrySet()) { - entry.getValue().mSession.purgeHistory(); - } - } - - public void setRegion(String aRegion) { - Log.d(LOGTAG, "SessionStack setRegion: " + aRegion); - mRegion = aRegion != null ? aRegion.toLowerCase() : "worldwide"; - - // There is a region initialize and the home is already loaded - if (mCurrentSession != null && isHomeUri(getCurrentUri())) { - mCurrentSession.loadUri("javascript:window.location.replace('" + getHomeUri() + "');"); - } - } - - public String getHomeUri() { - String homepage = SettingsStore.getInstance(mContext).getHomepage(); - if (homepage.equals(mContext.getString(R.string.homepage_url)) && mRegion != null) { - homepage = homepage + "?region=" + mRegion; - } - return homepage; - } - - public Boolean isHomeUri(String aUri) { - return aUri != null && aUri.toLowerCase().startsWith( - SettingsStore.getInstance(mContext).getHomepage() - ); - } - - public String getCurrentUri() { - String result = ""; - if (mCurrentSession != null) { - SessionState state = mSessions.get(mCurrentSession.hashCode()); - if (state == null) { - return result; - } - result = state.mUri; - } - return result; - } - - public String getCurrentTitle() { - String result = ""; - if (mCurrentSession != null) { - SessionState state = mSessions.get(mCurrentSession.hashCode()); - if (state == null) { - return result; - } - result = state.mTitle; - } - return result; - } - - public boolean isSecure() { - boolean isSecure = false; - if (mCurrentSession != null) { - SessionState state = mSessions.get(mCurrentSession.hashCode()); - if (state == null) { - return false; - } - isSecure = state.mSecurityInformation != null && state.mSecurityInformation.isSecure; - } - return isSecure; - } - - public Media getFullScreenVideo() { - if (mCurrentSession != null) { - SessionState state = mSessions.get(mCurrentSession.hashCode()); - if (state == null) { - return null; - } - for (Media media: state.mMediaElements) { - if (media.isFullscreen()) { - return media; - } - } - if (state.mMediaElements.size() > 0) { - return state.mMediaElements.get(state.mMediaElements.size() - 1); - } - } - - return null; - } - - public boolean isInputActive(int aSessionId) { - SessionState state = mSessions.get(aSessionId); - if (state != null) { - return state.mIsInputActive; - } - return false; - } - - public boolean canGoBack() { - if (mCurrentSession == null) { - return false; - } - - SessionState state = mSessions.get(mCurrentSession.hashCode()); - boolean canGoBack = false; - if (state != null) { - canGoBack = state.mCanGoBack; - } - - return canGoBack || mSessions.size() > 1; - } - - public void goBack() { - if (mCurrentSession == null) { - return; - } - if (isInFullScreen()) { - exitFullScreen(); - - } else { - int sessionId = getCurrentSessionId(); - SessionState state = mSessions.get(sessionId); - if (state.mCanGoBack) { - getCurrentSession().goBack(); - - } else { - unstackSession(); - } - } - } - - public void goForward() { - if (mCurrentSession == null) { - return; - } - mCurrentSession.goForward(); - } - - public void setActive(boolean aActive) { - if (mCurrentSession == null) { - return; - } - mCurrentSession.setActive(aActive); - } - - - public void reload() { - if (mCurrentSession == null) { - return; - } - mCurrentSession.reload(); - } - - public void stop() { - if (mCurrentSession == null) { - return; - } - mCurrentSession.stop(); - } - - public void loadUri(String aUri) { - if (mCurrentSession == null) { - return; - } - - if (aUri == null) { - aUri = getHomeUri(); - } - Log.d(LOGTAG, "Loading URI: " + aUri); - mCurrentSession.loadUri(aUri); - } - - public void loadHomePage() { - if (mCurrentSession == null) { - return; - } - - mCurrentSession.loadUri(getHomeUri()); - } - - public void loadPrivateBrowsingPage() { - if (mCurrentSession == null) { - return; - } - - mCurrentSession.loadData(mPrivatePage, "text/html"); - } - - public void toggleServo() { - if (mCurrentSession == null) { - return; - } - - Log.v("servo", "toggleServo"); - - if (!isInstanceOfServoSession(mCurrentSession)) { - if (mPreviousGeckoSessionId == SessionStack.NO_SESSION) { - mPreviousGeckoSessionId = getCurrentSessionId(); - String uri = getCurrentUri(); - SessionSettings settings = new SessionSettings.Builder() - .withDefaultSettings(mContext) - .withServo(true) - .build(); - int id = createSession(settings); - setCurrentSession(id); - loadUri(uri); - } else { - Log.e(LOGTAG, "Multiple Servo sessions not supported yet."); - } - } else { - removeSession(getCurrentSessionId()); - setCurrentSession(mPreviousGeckoSessionId); - mPreviousGeckoSessionId = SessionStack.NO_SESSION; - } - } - - public boolean isInFullScreen() { - if (mCurrentSession == null) { - return false; - } - - SessionState state = mSessions.get(mCurrentSession.hashCode()); - if (state != null) { - return state.mFullScreen; - } - - return false; - } - - public boolean isInFullScreen(GeckoSession aSession) { - Integer sessionId = getSessionId(aSession); - if (sessionId == NO_SESSION) { - return false; - } - SessionState state = mSessions.get(sessionId); - if (state != null) { - return state.mFullScreen; - } - - return false; - } - - public void exitFullScreen() { - if (mCurrentSession == null) { - return; - } - mCurrentSession.exitFullScreen(); - } - - public GeckoSession getCurrentSession() { - return mCurrentSession; - } - - public int getCurrentSessionId() { - if (mCurrentSession == null) { - return NO_SESSION; - } - return mCurrentSession.hashCode(); - } - - public boolean isPrivateMode() { - if (mCurrentSession != null) { - return mCurrentSession.getSettings().getUsePrivateMode(); - } - return false; - } - - // Session Settings - - protected void setServo(final boolean enabled) { - SessionState state = mSessions.get(mCurrentSession.hashCode()); - if (state != null) { - state.mSettings.setServoEnabled(enabled); - } - if (!enabled && mCurrentSession != null && isInstanceOfServoSession(mCurrentSession)) { - String uri = getCurrentUri(); - int id = createSession(); - setCurrentSession(id); - loadUri(uri); - } - } - - public int getUaMode() { - return mCurrentSession.getSettings().getUserAgentMode(); - } - - private static final String M_PREFIX = "m."; - private static final String MOBILE_PREFIX = "mobile."; - - private String checkForMobileSite(String aUri) { - if (aUri == null) { - return null; - } - String result = null; - URI uri; - try { - uri = new URI(aUri); - } catch (URISyntaxException | NullPointerException e) { - Log.d(LOGTAG, "Error parsing URL: " + aUri + " " + e.getMessage()); - return null; - } - String authority = uri.getAuthority(); - if (authority == null) { - return null; - } - authority = authority.toLowerCase(); - String foundPrefix = null; - if (authority.startsWith(M_PREFIX)) { - foundPrefix= M_PREFIX; - } else if (authority.startsWith(MOBILE_PREFIX)) { - foundPrefix = MOBILE_PREFIX; - } - if (foundPrefix != null) { - try { - uri = new URI(uri.getScheme(), authority.substring(foundPrefix.length()), uri.getPath(), uri.getQuery(), uri.getFragment()); - result = uri.toString(); - } catch (URISyntaxException | NullPointerException e) { - Log.d(LOGTAG, "Error dropping mobile prefix from: " + aUri + " " + e.getMessage()); - } - } - return result; - } - - public void setUaMode(int mode) { - if (mCurrentSession != null) { - SessionState state = mSessions.get(mCurrentSession.hashCode()); - if (state != null && state.mSettings.getUserAgentMode() != mode) { - state.mSettings.setUserAgentMode(mode); - mCurrentSession.getSettings().setUserAgentMode(mode); - String overrideUri = null; - if (mode == GeckoSessionSettings.USER_AGENT_MODE_DESKTOP) { - state.mSettings.setViewportMode(GeckoSessionSettings.VIEWPORT_MODE_DESKTOP); - overrideUri = checkForMobileSite(state.mUri); - } else { - state.mSettings.setViewportMode(GeckoSessionSettings.VIEWPORT_MODE_MOBILE); - } - mCurrentSession.getSettings().setViewportMode(state.mSettings.getViewportMode()); - mCurrentSession.loadUri(overrideUri != null ? overrideUri : state.mUri, GeckoSession.LOAD_FLAGS_BYPASS_CACHE | GeckoSession.LOAD_FLAGS_REPLACE_HISTORY); - } - } - } - - protected void setMultiprocess(final boolean aEnabled) { - for (Map.Entry entry : mSessions.entrySet()) { - SessionState state = entry.getValue(); - if (state != null && state.mSettings.isMultiprocessEnabled() != aEnabled) { - state.mSettings.setMultiprocessEnabled(aEnabled); - } - } - - recreateAllSessions(); - } - - protected void setTrackingProtection(final boolean aEnabled) { - for (Map.Entry entry : mSessions.entrySet()) { - SessionState state = entry.getValue(); - if (state != null && state.mSettings.isTrackingProtectionEnabled() != aEnabled) { - state.mSettings.setTrackingProtectionEnabled(aEnabled); - } - } - - recreateAllSessions(); - } - - public void clearCache(final long clearFlags) { - if (mRuntime != null) { - // Per GeckoView Docs: - // Note: Any open session may re-accumulate previously cleared data. - // To ensure that no persistent data is left behind, you need to close all sessions prior to clearing data. - // https://mozilla.github.io/geckoview/javadoc/mozilla-central/org/mozilla/geckoview/StorageController.html#clearData-long- - for (Map.Entry entry : mSessions.entrySet()) { - SessionState state = entry.getValue(); - if (state != null) { - state.mSession.stop(); - state.mSession.close(); - } - } - - mRuntime.getStorageController().clearData(clearFlags).then(aVoid -> { - recreateAllSessions(); - return null; - }); - } - } - - // NavigationDelegate - - @Override - public void onLocationChange(@NonNull GeckoSession aSession, String aUri) { - Log.d(LOGTAG, "SessionStack onLocationChange: " + aUri); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - Log.e(LOGTAG, "Unknown session!"); - return; - } - - state.mPreviousUri = state.mUri; - state.mUri = aUri; - - if (mCurrentSession == aSession) { - for (GeckoSession.NavigationDelegate listener : mNavigationListeners) { - listener.onLocationChange(aSession, aUri); - } - } - - // The homepage finishes loading after the region has been updated - if (mRegion != null && aUri.equalsIgnoreCase(SettingsStore.getInstance(mContext).getHomepage())) { - aSession.loadUri("javascript:window.location.replace('" + getHomeUri() + "');"); - } - } - - @Override - public void onCanGoBack(@NonNull GeckoSession aSession, boolean aCanGoBack) { - Log.d(LOGTAG, "SessionStack onCanGoBack: " + (aCanGoBack ? "true" : "false")); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - return; - } - state.mCanGoBack = aCanGoBack; - - if (mCurrentSession == aSession) { - for (GeckoSession.NavigationDelegate listener : mNavigationListeners) { - listener.onCanGoBack(aSession, aCanGoBack); - } - } - } - - @Override - public void onCanGoForward(@NonNull GeckoSession aSession, boolean aCanGoForward) { - Log.d(LOGTAG, "SessionStack onCanGoForward: " + (aCanGoForward ? "true" : "false")); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - return; - } - state.mCanGoForward = aCanGoForward; - - if (mCurrentSession == aSession) { - for (GeckoSession.NavigationDelegate listener : mNavigationListeners) { - listener.onCanGoForward(aSession, aCanGoForward); - } - } - } - - @Override - public @Nullable GeckoResult onLoadRequest(@NonNull GeckoSession aSession, @NonNull LoadRequest aRequest) { - String uri = aRequest.uri; - - Log.d(LOGTAG, "onLoadRequest: " + uri); - - String uriOverride = SessionUtils.checkYoutubeOverride(uri); - if (uriOverride != null) { - aSession.loadUri(uriOverride); - return GeckoResult.DENY; - } - - if (aSession == mCurrentSession) { - SessionState state = mSessions.get(getCurrentSessionId()); - Log.d(LOGTAG, "Testing for UA override"); - - final String userAgentOverride = mUserAgentOverride.lookupOverride(uri); - aSession.getSettings().setUserAgentOverride(userAgentOverride); - state.mSettings.setUserAgentOverride(userAgentOverride); - } - - if (mContext.getString(R.string.about_private_browsing).equalsIgnoreCase(uri)) { - return GeckoResult.DENY; - } - - if (mNavigationListeners.size() == 0) { - return GeckoResult.ALLOW; - } - - final GeckoResult result = new GeckoResult<>(); - AtomicInteger count = new AtomicInteger(0); - AtomicBoolean allowed = new AtomicBoolean(false); - for (GeckoSession.NavigationDelegate listener: mNavigationListeners) { - GeckoResult listenerResult = listener.onLoadRequest(aSession, aRequest); - if (listenerResult != null) { - listenerResult.then(value -> { - if (AllowOrDeny.ALLOW.equals(value)) { - allowed.set(true); - } - if (count.getAndIncrement() == mNavigationListeners.size() - 1) { - result.complete(allowed.get() ? AllowOrDeny.ALLOW : AllowOrDeny.DENY); - } - - return null; - }); - - } else { - allowed.set(true); - if (count.getAndIncrement() == mNavigationListeners.size() - 1) { - result.complete(allowed.get() ? AllowOrDeny.ALLOW : AllowOrDeny.DENY); - } - } - } - - return result; - } - - @Override - public GeckoResult onNewSession(@NonNull GeckoSession aSession, @NonNull String aUri) { - Log.d(LOGTAG, "SessionStack onNewSession: " + aUri); - - int sessionId; - SessionState state = mSessions.get(getCurrentSessionId()); - if (state != null) { - sessionId = createSession(state.mSettings); - - } else { - SessionSettings settings = new SessionSettings.Builder() - .withDefaultSettings(mContext) - .build(); - sessionId = createSession(settings); - } - - state = mSessions.get(sessionId); - mCurrentSession = state.mSession; - if (mCurrentSession != aSession) { - for (SessionChangeListener listener : mSessionChangeListeners) { - listener.onCurrentSessionChange(mCurrentSession, sessionId); - } - } - dumpAllState(mCurrentSession); - - return GeckoResult.fromValue(mCurrentSession); - } - - @Override - public GeckoResult onLoadError(@NonNull GeckoSession session, String uri, @NonNull WebRequestError error) { - Log.d(LOGTAG, "SessionStack onLoadError: " + uri); - - return GeckoResult.fromValue(InternalPages.createErrorPageDataURI(mContext, uri, error.category, error.code)); - } - - // Progress Listener - - @Override - public void onPageStart(@NonNull GeckoSession aSession, @NonNull String aUri) { - Log.d(LOGTAG, "SessionStack onPageStart"); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - return; - } - state.mIsLoading = true; - TelemetryWrapper.startPageLoadTime(); - - if (mCurrentSession == aSession) { - for (GeckoSession.ProgressDelegate listener : mProgressListeners) { - listener.onPageStart(aSession, aUri); - } - } - } - - @Override - public void onPageStop(@NonNull GeckoSession aSession, boolean b) { - Log.d(LOGTAG, "SessionStack onPageStop"); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - return; - } - - state.mIsLoading = false; - if (!SessionUtils.isLocalizedContent(state.mUri)) { - TelemetryWrapper.uploadPageLoadToHistogram(state.mUri); - } - - if (mCurrentSession == aSession) { - for (GeckoSession.ProgressDelegate listener : mProgressListeners) { - listener.onPageStop(aSession, b); - } - } - } - - @Override - public void onSecurityChange(@NonNull GeckoSession aSession, @NonNull SecurityInformation aInformation) { - Log.d(LOGTAG, "SessionStack onPageStop"); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - return; - } - - state.mSecurityInformation = aInformation; - - if (mCurrentSession == aSession) { - for (GeckoSession.ProgressDelegate listener : mProgressListeners) { - listener.onSecurityChange(aSession, aInformation); - } - } - } - - @Override - public void onSessionStateChange(@NonNull GeckoSession aSession, - @NonNull GeckoSession.SessionState aSessionState) { - SessionState state = mSessions.get(aSession.hashCode()); - if (state != null) { - state.mSessionState = aSessionState; - } - } - - // Content Delegate - - @Override - public void onTitleChange(@NonNull GeckoSession aSession, String aTitle) { - Log.d(LOGTAG, "SessionStack onTitleChange"); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - return; - } - - state.mTitle = aTitle; - - if (mCurrentSession == aSession) { - for (GeckoSession.ContentDelegate listener : mContentListeners) { - listener.onTitleChange(aSession, aTitle); - } - } - } - - @Override - public void onCloseRequest(@NonNull GeckoSession aSession) { - int sessionId = getSessionId(aSession); - if (getCurrentSessionId() == sessionId) { - unstackSession(); - } - } - - @Override - public void onFullScreen(@NonNull GeckoSession aSession, boolean aFullScreen) { - Log.d(LOGTAG, "SessionStack onFullScreen"); - SessionState state = mSessions.get(aSession.hashCode()); - if (state == null) { - return; - } - state.mFullScreen = aFullScreen; - - if (mCurrentSession == aSession) { - for (GeckoSession.ContentDelegate listener : mContentListeners) { - listener.onFullScreen(aSession, aFullScreen); - } - } - } - - @Override - public void onContextMenu(@NonNull GeckoSession session, int screenX, int screenY, @NonNull ContextElement element) { - if (mCurrentSession == session) { - for (GeckoSession.ContentDelegate listener : mContentListeners) { - listener.onContextMenu(session, screenX, screenY, element); - } - } - } - - @Override - public void onCrash(@NonNull GeckoSession session) { - Log.e(LOGTAG,"Child crashed. Creating new session"); - int crashedSessionId = getCurrentSessionId(); - int newSessionId = createSession(); - setCurrentSession(newSessionId); - loadUri(getHomeUri()); - removeSession(crashedSessionId); - } - - @Override - public void onFirstComposite(@NonNull GeckoSession aSession) { - if (mCurrentSession == aSession) { - for (GeckoSession.ContentDelegate listener : mContentListeners) { - listener.onFirstComposite(aSession); - } - } - } - - @Override - public void onFirstContentfulPaint(@NonNull GeckoSession aSession) { - if (mCurrentSession == aSession) { - for (GeckoSession.ContentDelegate listener : mContentListeners) { - listener.onFirstContentfulPaint(aSession); - } - } - } - - // TextInput Delegate - - @Override - public void restartInput(@NonNull GeckoSession aSession, int reason) { - if (aSession == mCurrentSession) { - for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { - listener.restartInput(aSession, reason); - } - } - } - - @Override - public void showSoftInput(@NonNull GeckoSession aSession) { - SessionState state = mSessions.get(getSessionId(aSession)); - if (state != null) { - state.mIsInputActive = true; - } - if (aSession == mCurrentSession) { - for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { - listener.showSoftInput(aSession); - } - } - } - - @Override - public void hideSoftInput(@NonNull GeckoSession aSession) { - SessionState state = mSessions.get(getSessionId(aSession)); - if (state != null) { - state.mIsInputActive = false; - } - if (aSession == mCurrentSession) { - for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { - listener.hideSoftInput(aSession); - } - } - } - - @Override - public void updateSelection(@NonNull GeckoSession aSession, int selStart, int selEnd, int compositionStart, int compositionEnd) { - if (aSession == mCurrentSession) { - for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { - listener.updateSelection(aSession, selStart, selEnd, compositionStart, compositionEnd); - } - } - } - - @Override - public void updateExtractedText(@NonNull GeckoSession aSession, @NonNull ExtractedTextRequest request, @NonNull ExtractedText text) { - if (aSession == mCurrentSession) { - for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { - listener.updateExtractedText(aSession, request, text); - } - } - } - - @Override - public void updateCursorAnchorInfo(@NonNull GeckoSession aSession, @NonNull CursorAnchorInfo info) { - if (aSession == mCurrentSession) { - for (GeckoSession.TextInputDelegate listener : mTextInputListeners) { - listener.updateCursorAnchorInfo(aSession, info); - } - } - } - - @Override - public void onContentBlocked(@NonNull final GeckoSession session, @NonNull final ContentBlocking.BlockEvent event) { - if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.AD) != 0) { - Log.i(LOGTAG, "Blocking Ad: " + event.uri); - } - - if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.ANALYTIC) != 0) { - Log.i(LOGTAG, "Blocking Analytic: " + event.uri); - } - - if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.CONTENT) != 0) { - Log.i(LOGTAG, "Blocking Content: " + event.uri); - } - - if ((event.getAntiTrackingCategory() & ContentBlocking.AntiTracking.SOCIAL) != 0) { - Log.i(LOGTAG, "Blocking Social: " + event.uri); - } - } - - // PromptDelegate - - @Nullable - @Override - public GeckoResult onAlertPrompt(@NonNull GeckoSession geckoSession, @NonNull AlertPrompt alertPrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onAlertPrompt(geckoSession, alertPrompt); - } - return GeckoResult.fromValue(alertPrompt.dismiss()); - } - - @Nullable - @Override - public GeckoResult onButtonPrompt(@NonNull GeckoSession geckoSession, @NonNull ButtonPrompt buttonPrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onButtonPrompt(geckoSession, buttonPrompt); - } - return GeckoResult.fromValue(buttonPrompt.dismiss()); - } - - @Nullable - @Override - public GeckoResult onTextPrompt(@NonNull GeckoSession geckoSession, @NonNull TextPrompt textPrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onTextPrompt(geckoSession, textPrompt); - } - return GeckoResult.fromValue(textPrompt.dismiss()); - } - - @Nullable - @Override - public GeckoResult onAuthPrompt(@NonNull GeckoSession geckoSession, @NonNull AuthPrompt authPrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onAuthPrompt(geckoSession, authPrompt); - } - return GeckoResult.fromValue(authPrompt.dismiss()); - } - - @Nullable - @Override - public GeckoResult onChoicePrompt(@NonNull GeckoSession geckoSession, @NonNull ChoicePrompt choicePrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onChoicePrompt(geckoSession, choicePrompt); - } - return GeckoResult.fromValue(choicePrompt.dismiss()); - } - - @Nullable - @Override - public GeckoResult onColorPrompt(@NonNull GeckoSession geckoSession, @NonNull ColorPrompt colorPrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onColorPrompt(geckoSession, colorPrompt); - } - return GeckoResult.fromValue(colorPrompt.dismiss()); - } - - @Nullable - @Override - public GeckoResult onDateTimePrompt(@NonNull GeckoSession geckoSession, @NonNull DateTimePrompt dateTimePrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onDateTimePrompt(geckoSession, dateTimePrompt); - } - return GeckoResult.fromValue(dateTimePrompt.dismiss()); - } - - @Nullable - @Override - public GeckoResult onFilePrompt(@NonNull GeckoSession geckoSession, @NonNull FilePrompt filePrompt) { - if (mPromptDelegate != null) { - return mPromptDelegate.onFilePrompt(geckoSession, filePrompt); - } - return GeckoResult.fromValue(filePrompt.dismiss()); - } - - // MediaDelegate - - @Override - public void onMediaAdd(@NonNull GeckoSession session, @NonNull MediaElement element) { - SessionState state = mSessions.get(getSessionId(session)); - if (state == null) { - return; - } - Media media = new Media(element); - state.mMediaElements.add(media); - - if (state.mMediaElements.size() == 1) { - for (VideoAvailabilityListener listener: mVideoAvailabilityListeners) { - listener.onVideoAvailabilityChanged(true); - } - } - } - - @Override - public void onMediaRemove(@NonNull GeckoSession session, @NonNull MediaElement element) { - SessionState state = mSessions.get(getSessionId(session)); - if (state == null) { - return; - } - for (int i = 0; i < state.mMediaElements.size(); ++i) { - Media media = state.mMediaElements.get(i); - if (media.getMediaElement() == element) { - media.unload(); - state.mMediaElements.remove(i); - if (state.mMediaElements.size() == 0) { - for (VideoAvailabilityListener listener: mVideoAvailabilityListeners) { - listener.onVideoAvailabilityChanged(false); - } - } - return; - } - } - } - - // HistoryDelegate - - @Override - public void onHistoryStateChange(@NonNull GeckoSession geckoSession, @NonNull GeckoSession.HistoryDelegate.HistoryList historyList) { - if (mHistoryDelegate != null) { - mHistoryDelegate.onHistoryStateChange(geckoSession, historyList); - } - } - - @Nullable - @Override - public GeckoResult onVisited(@NonNull GeckoSession geckoSession, @NonNull String url, @Nullable String lastVisitedURL, int flags) { - if (mHistoryDelegate != null) { - return mHistoryDelegate.onVisited(geckoSession, url, lastVisitedURL, flags); - } - - return GeckoResult.fromValue(false); - } - - @UiThread - @Nullable - public GeckoResult getVisited(@NonNull GeckoSession geckoSession, @NonNull String[] urls) { - if (mHistoryDelegate != null) { - return mHistoryDelegate.getVisited(geckoSession, urls); - } - - return GeckoResult.fromValue(new boolean[]{}); - } - - // SharedPreferences.OnSharedPreferenceChangeListener - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (mContext != null) { - if (key.equals(mContext.getString(R.string.settings_key_geolocation_data))) { - GeolocationData data = GeolocationData.parse(sharedPreferences.getString(key, null)); - setRegion(data.getCountryCode()); - } - } - } -} 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 16eb585f40..062a0dd4a5 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,5 +1,9 @@ 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; @@ -24,16 +28,18 @@ public class SessionState { public boolean mCanGoForward; public boolean mIsLoading; public boolean mIsInputActive; + public transient @Nullable Bitmap mBitmap; public transient GeckoSession.ProgressDelegate.SecurityInformation mSecurityInformation; - public String mUri; + public String mUri = ""; public String mPreviousUri; - public String mTitle; + public String mTitle = ""; public boolean mFullScreen; public transient GeckoSession mSession; public SessionSettings mSettings; public transient ArrayList mMediaElements = new ArrayList<>(); @JsonAdapter(SessionState.GeckoSessionStateAdapter.class) public GeckoSession.SessionState mSessionState; + public long mLastUse; public static class GeckoSessionStateAdapter extends TypeAdapter { @Override @@ -73,6 +79,7 @@ public void write(JsonWriter out, T value) throws IOException { out.name("mTitle").value(session.mTitle); out.name("mFullScreen").value(session.mFullScreen); out.name("mSettings").jsonValue(gson.toJson(session.mSettings)); + out.name("mLastUse").value(session.mLastUse); if (session.mSession != null) { if (session.mSession.getSettings().getUsePrivateMode()) { out.name("mSessionState").jsonValue(null); 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 be9d967528..1ce7691746 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 @@ -19,9 +19,8 @@ import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.crashreporting.CrashReporterService; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; -import java.util.Map; public class SessionStore implements GeckoSession.PermissionDelegate { @@ -43,15 +42,14 @@ public static SessionStore get() { private Context mContext; private GeckoRuntime mRuntime; - private HashMap mSessionStacks; - private Integer mActiveStoreId; + private ArrayList mSessions; + private Session mActiveSession; private PermissionDelegate mPermissionDelegate; private BookmarksStore mBookmarksStore; private HistoryStore mHistoryStore; private SessionStore() { - mSessionStacks = new HashMap<>(); - mActiveStoreId = NO_ACTIVE_STORE_ID; + mSessions = new ArrayList<>(); } public void setContext(Context context, Bundle aExtras) { @@ -101,32 +99,54 @@ public void initializeStores(Context context) { mHistoryStore = new HistoryStore(context); } - public SessionStack createSessionStack(int storeId, boolean privateMode) { - SessionStack store = new SessionStack(mContext, mRuntime, privateMode); - store.setPermissionDelegate(this); - mSessionStacks.put(storeId, store); + public Session createSession(boolean privateMode) { + Session session = new Session(mContext, mRuntime, privateMode); + session.setPermissionDelegate(this); + mSessions.add(session); - return store; + return session; } - public void destroySessionStack(int storeId) { - SessionStack store = mSessionStacks.remove(storeId); - if (store != null) { - store.setPermissionDelegate(null); - store.shutdown(); + public Session createSession(Session from) { + Session session = new Session(mContext, mRuntime, from); + session.setPermissionDelegate(this); + mSessions.add(session); + + return session; + } + + public ArrayList restoreSessions(ArrayList aSessions) { + ArrayList result = new ArrayList<>(); + for (Session restore: aSessions) { + Session newSession = createSession(restore); + newSession.restore(restore); + result.add(newSession); } + return result; } - public void setActiveStore(int storeId) { - mActiveStoreId = storeId; + public void destroySession(Session aSession) { + mSessions.remove(aSession); + if (aSession != null) { + aSession.setPermissionDelegate(null); + aSession.shutdown(); + } } - public SessionStack getSessionStack(int storeId) { - return mSessionStacks.get(storeId); + public void setActiveStore(Session aSession) { + mActiveSession = aSession; + } + + + public Session getActiveSession() { + return mActiveSession; } - public SessionStack getActiveStore() { - return mSessionStacks.get(mActiveStoreId); + public ArrayList getSortedSessions(boolean aPrivateMode) { + ArrayList result = new ArrayList<>(mSessions); + result.removeIf(session -> session.isPrivateMode() != aPrivateMode); + result.sort((o1, o2) -> (int)(o2.getLastUse() - o1.getLastUse())); + return result; } public void setPermissionDelegate(PermissionDelegate delegate) { @@ -142,27 +162,26 @@ public HistoryStore getHistoryStore() { } public void purgeSessionHistory() { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().purgeHistory(); + for (Session session: mSessions) { + session.purgeHistory(); } } public void onPause() { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().setActive(false); + for (Session session: mSessions) { + session.setActive(false); } } public void onResume() { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().setActive(true); + for (Session session: mSessions) { + session.setActive(true); } } public void onDestroy() { - HashMap sessionStacks = new HashMap<>(mSessionStacks); - for (Map.Entry entry : sessionStacks.entrySet()) { - destroySessionStack(entry.getKey()); + for (int i = mSessions.size() - 1; i >= 0; --i) { + destroySession(mSessions.get(i)); } if (mBookmarksStore != null) { @@ -183,26 +202,26 @@ public void onConfigurationChanged(Configuration newConfig) { // Session Settings public void setServo(final boolean enabled) { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().setServo(enabled); + for (Session session: mSessions) { + session.setServo(enabled); } } public void setUaMode(final int mode) { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().setUaMode(mode); + for (Session session: mSessions) { + session.setUaMode(mode); } } public void setMultiprocess(final boolean aEnabled) { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().setMultiprocess(aEnabled); + for (Session session: mSessions) { + session.setMultiprocess(aEnabled); } } public void setTrackingProtection(final boolean aEnabled) { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().setTrackingProtection(aEnabled); + for (Session session: mSessions) { + session.setTrackingProtection(aEnabled); } } @@ -243,8 +262,8 @@ public void setLocales(List locales) { } public void clearCache(long clearFlags) { - for (Map.Entry entry : mSessionStacks.entrySet()) { - entry.getValue().clearCache(clearFlags); + for (Session session: mSessions) { + session.clearCache(clearFlags); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java index 1ca6f3b799..39588ee25f 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/BookmarksView.java @@ -21,7 +21,7 @@ import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.BookmarksStore; import org.mozilla.vrbrowser.browser.SettingsStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.BookmarksBinding; import org.mozilla.vrbrowser.ui.adapters.BookmarkAdapter; @@ -91,8 +91,8 @@ public void onDestroy() { public void onClick(View view, BookmarkNode item) { mBinding.bookmarksList.requestFocusFromTouch(); - SessionStack sessionStack = SessionStore.get().getActiveStore(); - sessionStack.loadUri(item.getUrl()); + Session session = SessionStore.get().getActiveSession(); + session.loadUri(item.getUrl()); } @Override diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryView.java index cc2bad970f..243cd98a58 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/HistoryView.java @@ -21,7 +21,7 @@ import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.browser.HistoryStore; import org.mozilla.vrbrowser.browser.SettingsStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.databinding.HistoryBinding; import org.mozilla.vrbrowser.ui.adapters.HistoryAdapter; @@ -95,8 +95,8 @@ public void onDestroy() { public void onClick(View view, VisitInfo item) { mBinding.historyList.requestFocusFromTouch(); - SessionStack sessionStack = SessionStore.get().getActiveStore(); - sessionStack.loadUri(item.getUrl()); + Session session = SessionStore.get().getActiveSession(); + session.loadUri(item.getUrl()); } @Override diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java index 96bcd23205..f11360b426 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/NavigationURLBar.java @@ -34,7 +34,7 @@ import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.BookmarksStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.search.SearchEngineWrapper; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; @@ -80,7 +80,7 @@ public class NavigationURLBar extends FrameLayout { private boolean mBookmarkEnabled = true; private boolean mIsContextButtonsEnabled = true; private UIThreadExecutor mUIThreadExecutor = new UIThreadExecutor(); - private SessionStack mSessionStack; + private Session mSession; private Unit domainAutocompleteFilter(String text) { if (mURL != null) { @@ -113,7 +113,7 @@ public NavigationURLBar(Context context, AttributeSet attrs) { private void initialize(Context aContext) { mAudio = AudioEngine.fromContext(aContext); - mSessionStack = SessionStore.get().getActiveStore(); + mSession = SessionStore.get().getActiveSession(); // Inflate this data binding layout inflate(aContext, R.layout.navigation_url, this); @@ -168,7 +168,7 @@ private void initialize(Context aContext) { mUAModeButton = findViewById(R.id.uaModeButton); mUAModeButton.setTag(R.string.view_id_tag, R.id.uaModeButton); mUAModeButton.setOnClickListener(mUAModeListener); - setUAMode(mSessionStack.getUaMode()); + setUAMode(mSession.getUaMode()); mHintFading = findViewById(R.id.urlBarHintFadingEdge); mURLLeftContainer = findViewById(R.id.urlLeftContainer); @@ -196,9 +196,9 @@ private void initialize(Context aContext) { updateHintFading(); } - public void setSessionStack(SessionStack sessionStack) { - mSessionStack = sessionStack; - setUAMode(mSessionStack.getUaMode()); + public void setSession(Session session) { + mSession = session; + setUAMode(mSession.getUaMode()); } public void onPause() { @@ -262,14 +262,14 @@ private void handleBookmarkClick() { mAudio.playSound(AudioEngine.Sound.CLICK); } - String url = mSessionStack.getCurrentUri(); + String url = mSession.getCurrentUri(); if (StringUtils.isEmpty(url)) { return; } BookmarksStore bookmarkStore = SessionStore.get().getBookmarkStore(); bookmarkStore.isBookmarked(url).thenAcceptAsync(bookmarked -> { if (!bookmarked) { - bookmarkStore.addBookmark(url, mSessionStack.getCurrentTitle()); + bookmarkStore.addBookmark(url, mSession.getCurrentTitle()); setBookmarked(true); } else { // Delete @@ -322,10 +322,10 @@ public void setURL(String aURL) { if (aURL.startsWith("jar:")) { return; - } else if (aURL.startsWith("resource:") || mSessionStack.isHomeUri(aURL)) { + } else if (aURL.startsWith("resource:") || mSession.isHomeUri(aURL)) { aURL = ""; - } else if (aURL.startsWith("data:") && mSessionStack.isPrivateMode()) { + } else if (aURL.startsWith("data:") && mSession.isPrivateMode()) { aURL = ""; } else if (aURL.startsWith(getContext().getString(R.string.about_blank))) { @@ -533,8 +533,8 @@ public void handleURLEdit(String text) { TelemetryWrapper.urlBarEvent(false); } - if (mSessionStack.getCurrentUri() != url) { - mSessionStack.loadUri(url); + if (mSession.getCurrentUri() != url) { + mSession.loadUri(url); if (mDelegate != null) { mDelegate.onHideSearchPopup(); @@ -593,14 +593,14 @@ public void setClickable(boolean clickable) { view.requestFocusFromTouch(); - int uaMode = mSessionStack.getUaMode(); + int uaMode = mSession.getUaMode(); if (uaMode == GeckoSessionSettings.USER_AGENT_MODE_DESKTOP) { setUAMode(GeckoSessionSettings.USER_AGENT_MODE_VR); - mSessionStack.setUaMode(GeckoSessionSettings.USER_AGENT_MODE_VR); + mSession.setUaMode(GeckoSessionSettings.USER_AGENT_MODE_VR); }else { setUAMode(GeckoSessionSettings.USER_AGENT_MODE_DESKTOP); - mSessionStack.setUaMode(GeckoSessionSettings.USER_AGENT_MODE_DESKTOP); + mSession.setUaMode(GeckoSessionSettings.USER_AGENT_MODE_DESKTOP); } TelemetryWrapper.voiceInputEvent(); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/views/TabView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/TabView.java new file mode 100644 index 0000000000..1088da8a10 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/views/TabView.java @@ -0,0 +1,200 @@ +package org.mozilla.vrbrowser.ui.views; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.mozilla.geckoview.GeckoSession; +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.browser.engine.Session; +import org.mozilla.vrbrowser.utils.UrlUtils; + +public class TabView extends LinearLayout implements GeckoSession.ContentDelegate, Session.BitmapChangedListener { + protected RelativeLayout mTabCardView; + protected RelativeLayout mTabAddView; + protected RelativeLayout mTabActiveView; + protected View mTabHoverOverlay; + protected ImageView mPreview; + protected TextView mURL; + protected TextView mTitle; + protected UIButton mCloseButton; + protected ImageView mSelectionImage; + protected Delegate mDelegate; + protected Session mSession; + protected ImageView mTabAddIcon; + protected boolean mShowAddTab; + private boolean mSelecting; + private boolean mActive; + + public interface Delegate { + void onClose(TabView aSender); + void onClick(TabView aSender); + void onAdd(TabView aSender); + } + + public TabView(Context context) { + super(context); + } + + public TabView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public TabView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mTabAddView = findViewById(R.id.tabAddView); + mTabCardView = findViewById(R.id.tabCardView); + + mSelectionImage = findViewById(R.id.tabViewSelection); + mSelectionImage.setVisibility(View.GONE); + + mCloseButton = findViewById(R.id.tabViewCloseButton); + mCloseButton.setVisibility(View.GONE); + mCloseButton.setOnClickListener(v -> { + v.requestFocusFromTouch(); + if (mDelegate != null) { + mDelegate.onClose(this); + } + }); + + mPreview = findViewById(R.id.tabViewPreview); + mURL = findViewById(R.id.tabViewUrl); + mTitle = findViewById(R.id.tabViewTitle); + mTitle.setVisibility(View.GONE); + mTabAddIcon = findViewById(R.id.tabAddIcon); + mTabActiveView = findViewById(R.id.tabViewActive); + mTabHoverOverlay = findViewById(R.id.tabHoverOverlay); + + this.setOnClickListener(mCardClickListener); + } + + private OnClickListener mCardClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + if (mDelegate != null) { + if (!mShowAddTab && mSession != null) { + mDelegate.onClick(TabView.this); + } else { + mDelegate.onAdd(TabView.this); + } + } + } + }; + + public void attachToSession(@Nullable Session aSession) { + if (mSession != null) { + mSession.removeContentListener(this); + mSession.removeBitmapChangedListener(this); + } + setAddTabMode(false); + mSession = aSession; + mSession.addContentListener(this); + mSession.addBitmapChangedListener(this); + mShowAddTab = false; + Bitmap bitmap = aSession.getBitmap(); + if (bitmap != null) { + mPreview.setImageBitmap(bitmap); + } else { + mPreview.setImageDrawable(null); + } + + mURL.setText(UrlUtils.stripProtocol(aSession.getCurrentUri())); + if (!mShowAddTab) { + mTitle.setText(aSession.getCurrentTitle()); + } + } + + public Session getSession() { + return mSession; + } + + public void setDelegate(Delegate aDelegate) { + mDelegate = aDelegate; + } + + public void setAddTabMode(boolean aShow) { + if (mShowAddTab == aShow) { + return; + } + mShowAddTab = aShow; + if (mShowAddTab) { + mTabCardView.setVisibility(View.GONE); + mTabAddView.setVisibility(View.VISIBLE); + } else { + mTabCardView.setVisibility(View.VISIBLE); + mTabAddView.setVisibility(View.GONE); + } + } + + public void setSelecting(boolean aSelecting) { + mSelecting = aSelecting; + if (!mSelecting) { + setSelected(false); + } + if (mShowAddTab) { + setOnClickListener(mSelecting ? null : mCardClickListener); + } + } + + public void setActive(boolean aActive) { + if (mActive != aActive) { + mActive = aActive; + updateState(isHovered(), isSelected()); + } + } + + @Override + public void setSelected(boolean selected) { + super.setSelected(selected); + updateState(isHovered(), selected); + } + + @Override + public void onHoverChanged(boolean aHovered) { + super.onHoverChanged(aHovered); + boolean hovered = aHovered || mCloseButton.isHovered(); + updateState(hovered, isSelected()); + + } + + private void updateState(boolean aHovered, boolean aSelected) { + mCloseButton.setVisibility(aHovered && !aSelected && !mSelecting ? View.VISIBLE : View.GONE); + mTitle.setVisibility(aHovered && !aSelected ? View.VISIBLE : View.GONE); + mTabHoverOverlay.setVisibility((aHovered || aSelected) ? View.VISIBLE : View.GONE); + mSelectionImage.setVisibility(aSelected ? View.VISIBLE : View.GONE); + if (mShowAddTab) { + mTabAddView.setHovered(aHovered && !mSelecting); + mTabAddIcon.setHovered(aHovered && !mSelecting); + } + mTabActiveView.setVisibility(mActive && !aSelected ? View.VISIBLE : View.GONE); + } + + @Override + public void onTitleChange(@NonNull GeckoSession session, @Nullable String title) { + if (!mShowAddTab) { + mTitle.setText(title); + } + } + + @Override + public void onBitmapChanged(Bitmap aBitmap) { + if (aBitmap != null) { + mPreview.setImageBitmap(aBitmap); + } + } +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/KeyboardWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/KeyboardWidget.java index 6e318da625..65ed45d627 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/KeyboardWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/KeyboardWidget.java @@ -30,7 +30,7 @@ import org.mozilla.geckoview.GeckoSession; import org.mozilla.vrbrowser.R; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.input.CustomKeyboard; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; @@ -50,7 +50,6 @@ import org.mozilla.vrbrowser.ui.keyboards.ChinesePinyinKeyboard; import org.mozilla.vrbrowser.ui.keyboards.EnglishKeyboard; import org.mozilla.vrbrowser.utils.StringUtils; -import org.mozilla.vrbrowser.utils.SystemUtils; import java.util.ArrayList; import java.util.Locale; @@ -96,7 +95,7 @@ public class KeyboardWidget extends UIWidget implements CustomKeyboardView.OnKey private String mComposingText = ""; private String mComposingDisplayText = ""; private boolean mInternalDeleteHint = false; - private SessionStack mSessionStack; + private Session mSession; private boolean mInputRestarted = false; private class MoveTouchListener implements OnTouchListener { @@ -271,9 +270,9 @@ protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { @Override public void detachFromWindow() { - if (mSessionStack != null) { - mSessionStack.removeTextInputListener(this); - mSessionStack = null; + if (mSession != null) { + mSession.removeTextInputListener(this); + mSession = null; } } @@ -285,9 +284,9 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mAttachedWindow = aWindow; mWidgetPlacement.parentHandle = aWindow.getHandle(); - mSessionStack = aWindow.getSessionStack(); - if (mSessionStack != null) { - mSessionStack.addTextInputListener(this); + mSession = aWindow.getSession(); + if (mSession != null) { + mSession.addTextInputListener(this); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java index 2a69bb3c41..6d0abd5808 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java @@ -27,7 +27,7 @@ import org.mozilla.vrbrowser.browser.Media; import org.mozilla.vrbrowser.browser.SessionChangeListener; import org.mozilla.vrbrowser.browser.SettingsStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.search.suggestions.SuggestionsProvider; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.views.CustomUIButton; @@ -37,7 +37,6 @@ import org.mozilla.vrbrowser.ui.widgets.dialogs.VoiceSearchWidget; import org.mozilla.vrbrowser.utils.AnimationHelper; import org.mozilla.vrbrowser.utils.ServoUtils; -import org.mozilla.vrbrowser.utils.SystemUtils; import org.mozilla.vrbrowser.utils.UIThreadExecutor; import java.util.ArrayList; @@ -49,7 +48,7 @@ public class NavigationBarWidget extends UIWidget implements GeckoSession.Naviga WidgetManagerDelegate.UpdateListener, SessionChangeListener, NavigationURLBar.NavigationURLBarDelegate, VoiceSearchWidget.VoiceSearchDelegate, SharedPreferences.OnSharedPreferenceChangeListener, SuggestionsWidget.URLBarPopupDelegate, - WindowWidget.BookmarksViewDelegate, WindowWidget.HistoryViewDelegate, TrayListener { + WindowWidget.BookmarksViewDelegate, WindowWidget.HistoryViewDelegate, TrayListener, WindowWidget.WindowListener { private AudioEngine mAudio; private UIButton mBackButton; @@ -91,7 +90,6 @@ public class NavigationBarWidget extends UIWidget implements GeckoSession.Naviga private MediaControlsWidget mMediaControlsWidget; private Media mFullScreenMedia; private @VideoProjectionMenuWidget.VideoProjectionFlags Integer mAutoSelectedProjection; - private SessionStack mSessionStack; public NavigationBarWidget(Context aContext) { super(aContext); @@ -140,8 +138,8 @@ private void initialize(@NonNull Context aContext) { mBackButton.setOnClickListener(v -> { v.requestFocusFromTouch(); - if (mSessionStack.canGoBack()) { - mSessionStack.goBack(); + if (getSession().canGoBack()) { + getSession().goBack(); } if (mAudio != null) { @@ -151,7 +149,7 @@ private void initialize(@NonNull Context aContext) { mForwardButton.setOnClickListener(v -> { v.requestFocusFromTouch(); - mSessionStack.goForward(); + getSession().goForward(); if (mAudio != null) { mAudio.playSound(AudioEngine.Sound.CLICK); } @@ -160,9 +158,9 @@ private void initialize(@NonNull Context aContext) { mReloadButton.setOnClickListener(v -> { v.requestFocusFromTouch(); if (mIsLoading) { - mSessionStack.stop(); + getSession().stop(); } else { - mSessionStack.reload(); + getSession().reload(); } if (mAudio != null) { mAudio.playSound(AudioEngine.Sound.CLICK); @@ -171,7 +169,7 @@ private void initialize(@NonNull Context aContext) { mHomeButton.setOnClickListener(v -> { v.requestFocusFromTouch(); - mSessionStack.loadUri(mSessionStack.getHomeUri()); + getSession().loadUri(getSession().getHomeUri()); if (mAudio != null) { mAudio.playSound(AudioEngine.Sound.CLICK); } @@ -179,7 +177,7 @@ private void initialize(@NonNull Context aContext) { mServoButton.setOnClickListener(v -> { v.requestFocusFromTouch(); - mSessionStack.toggleServo(); + getSession().toggleServo(); if (mAudio != null) { mAudio.playSound(AudioEngine.Sound.CLICK); } @@ -339,8 +337,8 @@ public void releaseWidget() { // Workaround for https://issuetracker.google.com/issues/37123764 // exitFullScreenMode() may animate some views that are then released // so use a custom way to exit fullscreen here without triggering view updates. - if (mSessionStack.isInFullScreen()) { - mSessionStack.exitFullScreen(); + if (getSession().isInFullScreen()) { + getSession().exitFullScreen(); } mAttachedWindow.restoreBeforeFullscreenPlacement(); mAttachedWindow.setIsFullScreen(false); @@ -376,16 +374,13 @@ public void detachFromWindow() { exitFullScreenMode(); } - if (mSessionStack != null) { - mSessionStack.removeSessionChangeListener(this); - mSessionStack.removeNavigationListener(this); - mSessionStack.removeProgressListener(this); - mSessionStack.removeContentListener(this); - mSessionStack = null; + if (getSession() != null) { + cleanSession(getSession()); } if (mAttachedWindow != null) { mAttachedWindow.removeBookmarksViewListener(this); mAttachedWindow.removeHistoryViewListener(this); + mAttachedWindow.removeWindowListener(this); } mAttachedWindow = null; } @@ -401,6 +396,7 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mAttachedWindow = aWindow; mAttachedWindow.addBookmarksViewListener(this); mAttachedWindow.addHistoryViewListener(this); + mAttachedWindow.addWindowListener(this); if (mAttachedWindow != null) { mURLBar.setIsContentMode(mAttachedWindow.isBookmarksVisible() || mAttachedWindow.isHistoryVisible()); @@ -414,7 +410,7 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mURLBar.setInsecureVisibility(View.GONE); } else { - mURLBar.setURL(mAttachedWindow.getSessionStack().getCurrentUri()); + mURLBar.setURL(mAttachedWindow.getSession().getCurrentUri()); mURLBar.setHint(R.string.search_placeholder); mURLBar.setInsecureVisibility(View.VISIBLE); } @@ -422,19 +418,42 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { clearFocus(); - mSessionStack = aWindow.getSessionStack(); - if (mSessionStack != null) { - mURLBar.setSessionStack(mSessionStack); - mSessionStack.addSessionChangeListener(this); - mSessionStack.addNavigationListener(this); - mSessionStack.addProgressListener(this); - mSessionStack.addContentListener(this); - updateServoButton(); - handleSessionState(); + if (getSession() != null) { + setUpSession(getSession()); } handleWindowResize(); } + private Session getSession() { + if (mAttachedWindow != null) { + return mAttachedWindow.getSession(); + } + return null; + } + + private void setUpSession(@NonNull Session aSession) { + aSession.addSessionChangeListener(this); + aSession.addNavigationListener(this); + aSession.addProgressListener(this); + aSession.addContentListener(this); + mURLBar.setSession(getSession()); + updateServoButton(); + handleSessionState(); + } + + private void cleanSession(@NonNull Session aSession) { + aSession.removeSessionChangeListener(this); + aSession.removeNavigationListener(this); + aSession.removeProgressListener(this); + aSession.removeContentListener(this); + } + + @Override + public void onSessionChanged(@NonNull Session aOldSession, @NonNull Session aSession) { + cleanSession(aOldSession); + setUpSession(aSession); + } + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -488,8 +507,8 @@ private void exitFullScreenMode() { // We need to add a delay for the exitFullScreen() call to solve some viewport scaling issues, // See https://github.com/MozillaReality/FirefoxReality/issues/833 for more info. postDelayed(() -> { - if (mSessionStack.isInFullScreen()) { - mSessionStack.exitFullScreen(); + if (getSession().isInFullScreen()) { + getSession().exitFullScreen(); } }, 50); @@ -552,7 +571,6 @@ private void enterResizeMode() { } // Update preset styles - } @@ -597,7 +615,7 @@ private void enterVRVideo(@VideoProjectionMenuWidget.VideoProjectionFlags int aP // Backup the placement because the same widget is reused in FullScreen & MediaControl menus mProjectionMenuPlacement.copyFrom(mProjectionMenu.getPlacement()); - mFullScreenMedia = mSessionStack.getFullScreenVideo(); + mFullScreenMedia = getSession().getFullScreenVideo(); this.setVisible(false); if (mFullScreenMedia != null && mFullScreenMedia.getWidth() > 0 && mFullScreenMedia.getHeight() > 0) { @@ -669,10 +687,10 @@ public void updateServoButton() { // 2. Or, if the pref is enabled and the current url is white listed. boolean show = false; boolean isServoSession = false; - if (mSessionStack != null){ - GeckoSession currentSession = mSessionStack.getCurrentSession(); + if (getSession() != null){ + GeckoSession currentSession = getSession().getGeckoSession(); if (currentSession != null) { - String currentUri = mSessionStack.getCurrentUri(); + String currentUri = getSession().getCurrentUri(); boolean isPrefEnabled = SettingsStore.getInstance(mAppContext).isServoEnabled(); boolean isUrlWhiteListed = ServoUtils.isUrlInServoWhiteList(mAppContext, currentUri); isServoSession = ServoUtils.isInstanceOfServoSession(currentSession); @@ -697,8 +715,8 @@ private void closeFloatingMenus() { } private void handleSessionState() { - if (mSessionStack != null) { - boolean isPrivateMode = mSessionStack.isPrivateMode(); + if (getSession() != null) { + boolean isPrivateMode = getSession().isPrivateMode(); mURLBar.setPrivateMode(isPrivateMode); for (CustomUIButton button : mButtons) { @@ -707,13 +725,6 @@ private void handleSessionState() { } } - public void release() { - if (mSessionStack != null) { - mSessionStack.removeNavigationListener(this); - mSessionStack.removeProgressListener(this); - } - } - @Override public void onLocationChange(GeckoSession session, String url) { if (mURLBar != null && !mAttachedWindow.isBookmarksVisible() && !mAttachedWindow.isHistoryVisible()) { @@ -728,7 +739,7 @@ public void onLocationChange(GeckoSession session, String url) { @Override public void onCanGoBack(GeckoSession aSession, boolean canGoBack) { if (mBackButton != null) { - boolean enableBackButton = mSessionStack.canGoBack(); + boolean enableBackButton = getSession().canGoBack(); Log.d(LOGTAG, "Got onCanGoBack: " + (enableBackButton ? "true" : "false")); mBackButton.setEnabled(enableBackButton); @@ -821,7 +832,7 @@ public void onFullScreen(GeckoSession session, boolean aFullScreen) { exitResizeMode(ResizeAction.KEEP_SIZE); } AtomicBoolean autoEnter = new AtomicBoolean(false); - mAutoSelectedProjection = VideoProjectionMenuWidget.getAutomaticProjection(mSessionStack.getUriFromSession(session), autoEnter); + mAutoSelectedProjection = VideoProjectionMenuWidget.getAutomaticProjection(getSession().getCurrentUri(), autoEnter); if (mAutoSelectedProjection != null && autoEnter.get()) { mAutoEnteredVRVideo = true; postDelayed(() -> enterVRVideo(mAutoSelectedProjection), 300); @@ -860,13 +871,13 @@ private void handleWindowResize() { postInvalidate(); } - // SessionStack.SessionChangeListener + // Session.SessionChangeListener @Override - public void onCurrentSessionChange(GeckoSession aSession, int aId) { + public void onCurrentSessionChange(GeckoSession aOldSession, GeckoSession aSession) { handleSessionState(); - boolean isFullScreen = mSessionStack.isInFullScreen(aSession); + boolean isFullScreen = getSession().isInFullScreen(); if (isFullScreen && !mAttachedWindow.isFullScreen()) { enterFullScreenMode(); } else if (!isFullScreen && mAttachedWindow.isFullScreen()) { @@ -989,7 +1000,7 @@ public void onBookmarksShown(WindowWidget aWindow) { public void onBookmarksHidden(WindowWidget aWindow) { if (mAttachedWindow == aWindow) { mURLBar.setIsContentMode(false); - mURLBar.setURL(mSessionStack.getCurrentUri()); + mURLBar.setURL(getSession().getCurrentUri()); mURLBar.setHint(R.string.search_placeholder); } } @@ -1009,7 +1020,7 @@ public void onHistoryViewShown(WindowWidget aWindow) { public void onHistoryViewHidden(WindowWidget aWindow) { if (mAttachedWindow == aWindow) { mURLBar.setIsContentMode(false); - mURLBar.setURL(mSessionStack.getCurrentUri()); + mURLBar.setURL(getSession().getCurrentUri()); mURLBar.setHint(R.string.search_placeholder); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TabsWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TabsWidget.java new file mode 100644 index 0000000000..cc604d9fa8 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TabsWidget.java @@ -0,0 +1,341 @@ +package org.mozilla.vrbrowser.ui.widgets; + +import android.content.Context; +import android.graphics.Rect; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.browser.engine.Session; +import org.mozilla.vrbrowser.browser.engine.SessionStore; +import org.mozilla.vrbrowser.ui.views.TabView; +import org.mozilla.vrbrowser.ui.views.UIButton; +import org.mozilla.vrbrowser.ui.views.UITextButton; +import org.mozilla.vrbrowser.utils.ViewUtils; + +import java.util.ArrayList; + +public class TabsWidget extends UIWidget implements WidgetManagerDelegate.FocusChangeListener { + protected RecyclerView mTabsList; + protected GridLayoutManager mLayoutManager; + protected TabAdapter mAdapter; + protected boolean mPrivateMode; + protected TabDelegate mTabDelegate; + protected TextView mTabsAvailableCounter; + protected TextView mSelectedTabsCounter; + protected UITextButton mSelectTabsButton; + protected UITextButton mDoneButton; + protected UITextButton mCloseTabsButton; + protected UITextButton mCloseTabsAllButton; + protected UITextButton mSelectAllButton; + protected UITextButton mUnselectTabs; + protected LinearLayout mTabsSelectModeView; + protected View mTabSelectModeSeparator; + + protected boolean mSelecting; + protected ArrayList mSelectedTabs = new ArrayList<>(); + + public interface TabDelegate { + void onTabSelect(Session aTab); + void onTabAdd(); + void onTabsClose(ArrayList aTabs); + } + + public TabsWidget(Context aContext, boolean aPrivateMode) { + super(aContext); + mPrivateMode = aPrivateMode; + initialize(); + } + + @Override + protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { + Context context = getContext(); + aPlacement.width = WidgetPlacement.dpDimension(context, R.dimen.tabs_width); + aPlacement.height = WidgetPlacement.dpDimension(context, R.dimen.tabs_height); + aPlacement.worldWidth = WidgetPlacement.floatDimension(getContext(), R.dimen.window_world_width) * aPlacement.width/getWorldWidth(); + aPlacement.anchorX = 0.5f; + aPlacement.anchorY = 0.5f; + aPlacement.parentAnchorX = 0.5f; + aPlacement.parentAnchorY = 0.5f; + aPlacement.translationZ = WidgetPlacement.floatDimension(context, R.dimen.context_menu_z_distance); + aPlacement.visible = false; + } + + private void initialize() { + inflate(getContext(), R.layout.tabs, this); + mTabsList = findViewById(R.id.tabsRecyclerView); + mTabsList.setHasFixedSize(true); + final int columns = 4; + mLayoutManager = new GridLayoutManager(getContext(), columns); + mTabsList.setLayoutManager(mLayoutManager); + mTabsList.addItemDecoration(new GridSpacingItemDecoration(getContext(), columns)); + + mTabsAvailableCounter = findViewById(R.id.tabsAvailableCounter); + mSelectedTabsCounter = findViewById(R.id.tabsSelectedCounter); + mTabSelectModeSeparator = findViewById(R.id.tabsSelectModeSeparator); + + // specify an adapter (see also next example) + mAdapter = new TabAdapter(); + mTabsList.setAdapter(mAdapter); + + mTabsSelectModeView = findViewById(R.id.tabsSelectModeView); + + UIButton backButton = findViewById(R.id.tabsBackButton); + backButton.setOnClickListener(view -> { + view.requestFocusFromTouch(); + onDismiss(); + }); + + mSelectTabsButton = findViewById(R.id.tabsSelectButton); + mSelectTabsButton.setOnClickListener(view -> { + enterSelectMode(); + }); + + mDoneButton = findViewById(R.id.tabsDoneButton); + mDoneButton.setOnClickListener(view -> { + exitSelectMode(); + }); + + mCloseTabsButton = findViewById(R.id.tabsCloseButton); + mCloseTabsButton.setOnClickListener(v -> { + if (mTabDelegate != null) { + mTabDelegate.onTabsClose(mSelectedTabs); + } + onDismiss(); + }); + + mCloseTabsAllButton = findViewById(R.id.tabsCloseAllButton); + mCloseTabsAllButton.setOnClickListener(v -> { + if (mTabDelegate != null) { + mTabDelegate.onTabsClose(mAdapter.mTabs); + } + onDismiss(); + }); + + mSelectAllButton = findViewById(R.id.tabsSelectAllButton); + mSelectAllButton.setOnClickListener(v -> { + mSelectedTabs = new ArrayList<>(mAdapter.mTabs); + mAdapter.notifyDataSetChanged(); + updateSelectionMode(); + }); + + mUnselectTabs = findViewById(R.id.tabsUnselectButton); + mUnselectTabs.setOnClickListener(v -> { + mSelectedTabs.clear(); + mAdapter.notifyDataSetChanged(); + updateSelectionMode(); + }); + } + + + @Override + public void show(int aShowFlags) { + super.show(aShowFlags); + mAdapter.updateTabs(SessionStore.get().getSortedSessions(mPrivateMode)); + mWidgetManager.pushWorldBrightness(this, WidgetManagerDelegate.DEFAULT_DIM_BRIGHTNESS); + mWidgetManager.addFocusChangeListener(this); + } + + @Override + public void hide(@HideFlags int aHideFlags) { + super.hide(aHideFlags); + + mWidgetManager.popWorldBrightness(this); + mWidgetManager.removeFocusChangeListener(this); + } + + public void setTabDelegate(TabDelegate aDelegate) { + mTabDelegate = aDelegate; + } + + public class TabAdapter extends RecyclerView.Adapter { + private ArrayList mTabs = new ArrayList<>(); + + class MyViewHolder extends RecyclerView.ViewHolder { + // each data item is just a string in this case + TabView tabView; + MyViewHolder(TabView v) { + super(v); + tabView = v; + } + + } + + TabAdapter() {} + + void updateTabs(ArrayList aTabs) { + mTabs = aTabs; + notifyDataSetChanged(); + + if (mTabs.size() > 1) { + mTabsAvailableCounter.setText(getContext().getString(R.string.tabs_counter_plural, String.valueOf(mTabs.size()))); + } else { + mTabsAvailableCounter.setText(R.string.tabs_counter_singular); + } + } + + @Override + public TabAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + TabView view = (TabView)LayoutInflater.from(parent.getContext()).inflate(R.layout.tab_view, parent, false); + return new MyViewHolder(view); + } + + @Override + public void onBindViewHolder(MyViewHolder holder, int position) { + if (position > 0) { + Session session = mTabs.get(position - 1); + holder.tabView.attachToSession(session); + } else { + holder.tabView.setAddTabMode(true); + } + + holder.tabView.setSelecting(mSelecting); + holder.tabView.setSelected(mSelectedTabs.contains(holder.tabView.getSession())); + holder.tabView.setActive(SessionStore.get().getActiveSession() == holder.tabView.getSession()); + holder.tabView.setDelegate(new TabView.Delegate() { + @Override + public void onClose(TabView aSender) { + if (mTabDelegate != null) { + ArrayList closed = new ArrayList<>(); + closed.add(aSender.getSession()); + mTabDelegate.onTabsClose(closed); + } + if (mTabs.size() > 1) { + mTabs.remove(holder.getAdapterPosition() - 1); + mAdapter.notifyItemRemoved(holder.getAdapterPosition()); + } else { + onDismiss(); + } + } + + @Override + public void onClick(TabView aSender) { + if (mSelecting) { + if (aSender.isSelected()) { + aSender.setSelected(false); + mSelectedTabs.remove(aSender.getSession()); + } else { + aSender.setSelected(true); + mSelectedTabs.add(aSender.getSession()); + } + updateSelectionMode(); + return; + } + if (mTabDelegate != null) { + mTabDelegate.onTabSelect(aSender.getSession()); + } + onDismiss(); + } + + @Override + public void onAdd(TabView aSender) { + if (mTabDelegate != null) { + mTabDelegate.onTabAdd(); + } + onDismiss(); + } + }); + } + + @Override + public int getItemCount() { + return mTabs.size() + 1; + } + } + + private Runnable mSelectModeBackHandler = () -> exitSelectMode(); + + private void enterSelectMode() { + if (mSelecting) { + return; + } + mSelecting = true; + mSelectTabsButton.setVisibility(View.GONE); + mDoneButton.setVisibility(View.VISIBLE); + mAdapter.notifyDataSetChanged(); + updateSelectionMode(); + mWidgetManager.pushBackHandler(mSelectModeBackHandler); + } + + private void exitSelectMode() { + if (!mSelecting) { + return; + } + mSelecting = false; + mSelectTabsButton.setVisibility(View.VISIBLE); + mDoneButton.setVisibility(View.GONE); + mSelectedTabs.clear(); + mAdapter.notifyDataSetChanged(); + updateSelectionMode(); + mWidgetManager.popBackHandler(mSelectModeBackHandler); + } + + private void updateSelectionMode() { + mTabsSelectModeView.setVisibility(mSelecting ? View.VISIBLE : View.GONE); + mTabSelectModeSeparator.setVisibility(mSelecting ? View.VISIBLE : View.GONE); + if (mSelectedTabs.size() > 0) { + mCloseTabsButton.setVisibility(View.VISIBLE); + mUnselectTabs.setVisibility(View.VISIBLE); + mCloseTabsAllButton.setVisibility(View.GONE); + mSelectAllButton.setVisibility(View.GONE); + } else { + mCloseTabsButton.setVisibility(View.GONE); + mUnselectTabs.setVisibility(View.GONE); + mCloseTabsAllButton.setVisibility(View.VISIBLE); + mSelectAllButton.setVisibility(View.VISIBLE); + } + + if (mSelecting) { + if (mSelectedTabs.size() == 0) { + mSelectedTabsCounter.setText(R.string.tabs_selected_counter_none); + } else if (mSelectedTabs.size() > 1) { + mSelectedTabsCounter.setText(getContext().getString(R.string.tabs_selected_counter_plural, String.valueOf(mSelectedTabs.size()))); + } else { + mSelectedTabsCounter.setText(R.string.tabs_selected_counter_singular); + } + } + } + + @Override + protected void onDismiss() { + exitSelectMode(); + super.onDismiss(); + } + + public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { + private int mColumns; + private int mSpacing; + private int mSpacingFirst; + + public GridSpacingItemDecoration(Context aContext, int aColumns) { + mColumns = aColumns; + mSpacing = WidgetPlacement.pixelDimension(aContext, R.dimen.tabs_spacing); + mSpacingFirst = WidgetPlacement.pixelDimension(aContext, R.dimen.tabs_spacing_first_column); + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); // item position + int column = position % mColumns; // item column + int row = position / mColumns; + + outRect.left = column > 0 ? mSpacing / 2 : mSpacingFirst; + outRect.right = column == mColumns - 1 ? mSpacingFirst: mSpacing / 2; + outRect.top = row > 0 ? mSpacing : 0; + } + } + + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + if (ViewUtils.isChildrenOf(this, oldFocus) && this.isVisible() && + !ViewUtils.isChildrenOf(this, newFocus)) { + onDismiss(); + } + } +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java index 8228d07ea2..cf95e01653 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TitleBarWidget.java @@ -20,7 +20,6 @@ import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.browser.Media; import org.mozilla.vrbrowser.databinding.TitleBarBinding; -import org.mozilla.vrbrowser.utils.SystemUtils; import java.net.MalformedURLException; import java.net.URI; @@ -113,7 +112,7 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mWidgetPlacement.parentHandle = aWindow.getHandle(); mAttachedWindow = aWindow; - setPrivateMode(aWindow.getSessionStack().isPrivateMode()); + setPrivateMode(aWindow.getSession().isPrivateMode()); } @Override @@ -164,9 +163,9 @@ public void setURL(String urlString) { } public void setIsInsecure(boolean aIsInsecure) { - if (mAttachedWindow.getSessionStack().getCurrentUri() != null && - !(mAttachedWindow.getSessionStack().getCurrentUri().startsWith("data") && - mAttachedWindow.getSessionStack().isPrivateMode())) { + if (mAttachedWindow.getSession().getCurrentUri() != null && + !(mAttachedWindow.getSession().getCurrentUri().startsWith("data") && + mAttachedWindow.getSession().isPrivateMode())) { mBinding.insecureIcon.setVisibility(aIsInsecure ? View.VISIBLE : View.GONE); } } @@ -178,7 +177,7 @@ public void setInsecureVisibility(int visibility) { public void mediaAvailabilityChanged(boolean available) { mBinding.setIsMediaAvailable(false); if (available) { - mMedia = mAttachedWindow.getSessionStack().getFullScreenVideo(); + mMedia = mAttachedWindow.getSession().getFullScreenVideo(); if (mMedia != null) { mBinding.setIsMediaPlaying(mMedia.isPlaying()); mMedia.removeMediaListener(mMediaDelegate); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TopBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TopBarWidget.java index 2f0090fc61..418f5955d7 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TopBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TopBarWidget.java @@ -12,14 +12,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.mozilla.geckoview.GeckoSession; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; -import org.mozilla.vrbrowser.browser.SessionChangeListener; import org.mozilla.vrbrowser.ui.views.UIButton; import org.mozilla.vrbrowser.ui.views.UITextButton; -public class TopBarWidget extends UIWidget implements SessionChangeListener, WidgetManagerDelegate.UpdateListener { +public class TopBarWidget extends UIWidget implements WidgetManagerDelegate.UpdateListener { private UIButton mCloseButton; private UIButton mMoveLeftButton; @@ -141,7 +139,7 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { mWidgetPlacement.parentHandle = aWindow.getHandle(); mAttachedWindow = aWindow; - setPrivateMode(aWindow.getSessionStack().isPrivateMode()); + setPrivateMode(aWindow.getSession().isPrivateMode()); } @@ -158,13 +156,6 @@ private void setPrivateMode(boolean aPrivateMode) { mMoveRightButton.setBackground(getContext().getDrawable(aPrivateMode ? R.drawable.fullscreen_button_private_last : R.drawable.fullscreen_button_last)); } - // SessionStack.SessionChangeListener - - @Override - public void onCurrentSessionChange(GeckoSession aSession, int aId) { - handleSessionState(); - } - @Override public void setVisible(boolean aIsVisible) { if (mVisible == aIsVisible || mWidgetManager == null) { @@ -197,9 +188,6 @@ public void setMoveRightButtonEnabled(boolean aEnabled) { mMoveRightButton.setEnabled(aEnabled); } - private void handleSessionState() { - } - // WidgetManagerDelegate.UpdateListener @Override diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java index dcf5f22bb2..5c3ee5e17b 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayListener.java @@ -5,4 +5,5 @@ default void onBookmarksClicked() {} default void onPrivateBrowsingClicked() {} default void onAddWindowClicked() {} default void onHistoryClicked() {} + default void onTabsClicked() {} } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java index 780187792d..5a43bd89a9 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/TrayWidget.java @@ -23,7 +23,7 @@ import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.BookmarksStore; import org.mozilla.vrbrowser.browser.SessionChangeListener; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.views.UIButton; @@ -43,6 +43,7 @@ public class TrayWidget extends UIWidget implements SessionChangeListener, Windo private UIButton mPrivateButton; private UIButton mBookmarksButton; private UIButton mHistoryButton; + private UIButton mTabsButton; private AudioEngine mAudio; private int mSettingsDialogHandle = -1; private boolean mIsLastSessionPrivate; @@ -51,7 +52,7 @@ public class TrayWidget extends UIWidget implements SessionChangeListener, Windo private int mMaxPadding; private boolean mKeyboardVisible; private boolean mTrayVisible = true; - private SessionStack mSessionStack; + private Session mSession; private WindowWidget mAttachedWindow; private TooltipWidget mLibraryNotification; @@ -128,6 +129,18 @@ private void initialize(Context aContext) { }); mHistoryButton.setCurvedTooltip(false); + mTabsButton = findViewById(R.id.tabsButton); + mTabsButton.setOnHoverListener(mButtonScaleHoverListener); + mTabsButton.setOnClickListener(view -> { + if (mAudio != null) { + mAudio.playSound(AudioEngine.Sound.CLICK); + } + + notifyTabsClicked(); + view.requestFocusFromTouch(); + }); + mHistoryButton.setCurvedTooltip(false); + UIButton addWindowButton = findViewById(R.id.addwindowButton); addWindowButton.setOnHoverListener(mButtonScaleHoverListener); addWindowButton.setOnClickListener(view -> { @@ -224,6 +237,10 @@ private void notifyHistoryClicked() { mTrayListeners.forEach(TrayListener::onHistoryClicked); } + private void notifyTabsClicked() { + mTrayListeners.forEach(TrayListener::onTabsClicked); + } + private void notifyPrivateBrowsingClicked() { mTrayListeners.forEach(TrayListener::onPrivateBrowsingClicked); } @@ -255,8 +272,8 @@ protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { @Override public void releaseWidget() { - if (mSessionStack != null) { - mSessionStack.removeSessionChangeListener(this); + if (mSession != null) { + mSession.removeSessionChangeListener(this); } mWidgetManager.removeUpdateListener(this); @@ -291,9 +308,9 @@ public void hide(@HideFlags int aHideFlags) { public void detachFromWindow() { hideBookmarkNotification.run(); - if (mSessionStack != null) { - mSessionStack.removeSessionChangeListener(this); - mSessionStack = null; + if (mSession != null) { + mSession.removeSessionChangeListener(this); + mSession = null; } if (mAttachedWindow != null) { SessionStore.get().getBookmarkStore().addListener(mBookmarksListener); @@ -318,9 +335,9 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { SessionStore.get().getBookmarkStore().addListener(mBookmarksListener); - mSessionStack = aWindow.getSessionStack(); - if (mSessionStack != null) { - mSessionStack.addSessionChangeListener(this); + mSession = aWindow.getSession(); + if (mSession != null) { + mSession.addSessionChangeListener(this); handleSessionState(); } @@ -337,16 +354,16 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { } } - // SessionStack.SessionChangeListener + // Session.SessionChangeListener @Override - public void onCurrentSessionChange(GeckoSession aSession, int aId) { + public void onCurrentSessionChange(GeckoSession aOldSession, GeckoSession aSession) { handleSessionState(); } private void handleSessionState() { - if (mSessionStack != null) { - boolean isPrivateMode = mSessionStack.isPrivateMode(); + if (mSession != null) { + boolean isPrivateMode = mSession.isPrivateMode(); if (isPrivateMode != mIsLastSessionPrivate) { mPrivateButton.setPrivateMode(isPrivateMode); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/UIWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/UIWidget.java index 912306e2cb..c5a6002a47 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/UIWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/UIWidget.java @@ -222,12 +222,12 @@ public boolean isDialog() { } @Override - public void setFirstDraw(final boolean aIsFirstDraw) { + public void setComposited(final boolean aIsFirstDraw) { mWidgetPlacement.composited = aIsFirstDraw; } @Override - public boolean getFirstDraw() { + public boolean isComposited() { return mWidgetPlacement.composited; } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Widget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Widget.java index a0dff5a474..4f4db8d6d3 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Widget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/Widget.java @@ -27,8 +27,8 @@ public interface Widget { void handleResizeEvent(float aWorldWidth, float aWorldHeight); void handleMoveEvent(float aDeltaX, float aDeltaY, float aDeltaZ, float aRotation); void releaseWidget(); - void setFirstDraw(boolean aIsFirstDraw); - boolean getFirstDraw(); + void setComposited(boolean aIsFirstDraw); + boolean isComposited(); boolean isVisible(); boolean isDialog(); void setVisible(boolean aVisible); 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 64b09dc79e..c38627b8d7 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 @@ -36,7 +36,7 @@ import org.mozilla.vrbrowser.browser.SessionChangeListener; import org.mozilla.vrbrowser.browser.SettingsStore; import org.mozilla.vrbrowser.browser.VideoAvailabilityListener; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.callbacks.BookmarksCallback; @@ -75,7 +75,8 @@ public class WindowWidget extends UIWidget implements SessionChangeListener, GeckoSession.ContentDelegate, GeckoSession.PromptDelegate, GeckoSession.NavigationDelegate, VideoAvailabilityListener, - GeckoSession.HistoryDelegate, GeckoSession.ProgressDelegate { + GeckoSession.HistoryDelegate, GeckoSession.ProgressDelegate, + TabsWidget.TabDelegate { public interface HistoryViewDelegate { default void onHistoryViewShown(WindowWidget aWindow) {} @@ -87,7 +88,6 @@ default void onBookmarksShown(WindowWidget aWindow) {} default void onBookmarksHidden(WindowWidget aWindow) {} } - private int mSessionId; private GeckoDisplay mDisplay; private Surface mSurface; private int mWidth; @@ -115,7 +115,7 @@ default void onBookmarksHidden(WindowWidget aWindow) {} private boolean mIsInVRVideoMode; private View mView; private Point mLastMouseClickPos; - private SessionStack mSessionStack; + private Session mSession; private int mWindowId; private BookmarksView mBookmarksView; private HistoryView mHistoryView; @@ -125,7 +125,7 @@ default void onBookmarksHidden(WindowWidget aWindow) {} private Windows.WindowPlacement mWindowPlacementBeforeFullscreen = Windows.WindowPlacement.FRONT; private float mMaxWindowScale = 3; private boolean mIsRestored = false; - private WindowDelegate mWindowDelegate; + private ArrayList mListeners; boolean mActive = false; boolean mHovered = false; boolean mClickedAfterFocus = false; @@ -136,27 +136,37 @@ default void onBookmarksHidden(WindowWidget aWindow) {} private boolean mIsResizing; private boolean mIsFullScreen; private boolean mAfterFirstPaint; + private TabsWidget mTabsWidget; - public interface WindowDelegate { - void onFocusRequest(@NonNull WindowWidget aWindow); - void onBorderChanged(@NonNull WindowWidget aWindow); + public interface WindowListener { + default void onFocusRequest(@NonNull WindowWidget aWindow) {} + default void onBorderChanged(@NonNull WindowWidget aWindow) {} + default void onTabSelect(@NonNull WindowWidget aWindow, Session aTab) {} + default void onTabAdd(@NonNull WindowWidget aWindow) {} + default void onTabsClose(@NonNull WindowWidget aWindow, ArrayList aTabs) {} + default void onSessionChanged(@NonNull Session aOldSession, @NonNull Session aSession) {} } - public WindowWidget(Context aContext, int windowId, boolean privateMode) { + public WindowWidget(Context aContext, int windowId, boolean privateMode) { super(aContext); + mWindowId = windowId; + mSession = SessionStore.get().createSession(privateMode); + initialize(aContext); + } + + public WindowWidget(Context aContext, int windowId, Session aSession) { + super(aContext); + mWindowId = windowId; + mSession = aSession; + initialize(aContext); + } + + private void initialize(Context aContext) { mWidgetManager = (WidgetManagerDelegate) aContext; mBorderWidth = SettingsStore.getInstance(aContext).getTransparentBorderWidth(); + mListeners = new ArrayList<>(); - mWindowId = windowId; - mSessionStack = SessionStore.get().createSessionStack(mWindowId, privateMode); - mSessionStack.setPromptDelegate(this); - mSessionStack.addSessionChangeListener(this); - mSessionStack.addContentListener(this); - mSessionStack.addVideoAvailabilityListener(this); - mSessionStack.addNavigationListener(this); - mSessionStack.addProgressListener(this); - mSessionStack.setHistoryDelegate(this); - mSessionStack.newSession(); + setupListeners(mSession); mBookmarksView = new BookmarksView(aContext); mBookmarksView.setBookmarksCallback(mBookmarksCallback); @@ -173,7 +183,7 @@ public WindowWidget(Context aContext, int windowId, boolean privateMode) { mIsResizing = false; mIsFullScreen = false; initializeWidgetPlacement(mWidgetPlacement); - if (mSessionStack.isPrivateMode()) { + if (mSession.isPrivateMode()) { mWidgetPlacement.clearColor = ViewUtils.ARGBtoRGBA(getContext().getColor(R.color.window_private_clear_color)); } else { mWidgetPlacement.clearColor = ViewUtils.ARGBtoRGBA(getContext().getColor(R.color.window_blank_clear_color)); @@ -189,6 +199,10 @@ public WindowWidget(Context aContext, int windowId, boolean privateMode) { setFocusable(true); TelemetryWrapper.openWindowEvent(mWindowId); + + if (mSession.getGeckoSession() != null) { + onCurrentSessionChange(null, mSession.getGeckoSession()); + } } @Override @@ -206,6 +220,26 @@ protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { // Check Windows.placeWindow method for remaining placement set-up } + void setupListeners(Session aSession) { + aSession.setPromptDelegate(this); + aSession.addSessionChangeListener(this); + aSession.addContentListener(this); + aSession.addVideoAvailabilityListener(this); + aSession.addNavigationListener(this); + aSession.addProgressListener(this); + aSession.setHistoryDelegate(this); + } + + void cleanListeners(Session aSession) { + aSession.setPromptDelegate(null); + aSession.removeSessionChangeListener(this); + aSession.removeContentListener(this); + aSession.removeVideoAvailabilityListener(this); + aSession.removeNavigationListener(this); + aSession.removeProgressListener(this); + aSession.setHistoryDelegate(null); + } + @Override public void show(@ShowFlags int aShowFlags) { if (!mWidgetPlacement.visible) { @@ -222,7 +256,7 @@ public void show(@ShowFlags int aShowFlags) { clearFocus(); } - mSessionStack.setActive(true); + mSession.setActive(true); } @Override @@ -235,7 +269,7 @@ public void hide(@HideFlags int aHideFlag) { clearFocus(); - mSessionStack.setActive(false); + mSession.setActive(false); } @Override @@ -247,9 +281,8 @@ protected void onDismiss() { hideHistory(); } else { - SessionStack activeStore = SessionStore.get().getSessionStack(mWindowId); - if (activeStore.canGoBack()) { - activeStore.goBack(); + if (mSession.canGoBack()) { + mSession.goBack(); } } } @@ -257,14 +290,14 @@ protected void onDismiss() { @Override public void onPause() { super.onPause(); - mSessionStack.setActive(false); + mSession.setActive(false); } @Override public void onResume() { super.onResume(); if (isVisible() || mIsInVRVideoMode) { - mSessionStack.setActive(true); + mSession.setActive(true); } } @@ -274,13 +307,20 @@ public void close() { releaseWidget(); mBookmarksView.onDestroy(); mHistoryView.onDestroy(); - SessionStore.get().destroySessionStack(mWindowId); + SessionStore.get().destroySession(mSession); if (mTopBar != null) { mWidgetManager.removeWidget(mTopBar); } if (mTitleBar != null) { mWidgetManager.removeWidget(mTitleBar); } + if (mTabsWidget != null) { + if (!mTabsWidget.isReleased()) { + mTabsWidget.releaseWidget(); + } + mTabsWidget = null; + } + mListeners.clear(); } public void loadHomeIfNotRestored() { @@ -290,11 +330,11 @@ public void loadHomeIfNotRestored() { } public void loadHome() { - if (mSessionStack.isPrivateMode()) { - mSessionStack.loadPrivateBrowsingPage(); + if (mSession.isPrivateMode()) { + mSession.loadPrivateBrowsingPage(); } else { - mSessionStack.loadUri(SettingsStore.getInstance(getContext()).getHomepage()); + mSession.loadUri(SettingsStore.getInstance(getContext()).getHomepage()); } } @@ -560,9 +600,8 @@ public int getBorderWidth() { public void setActiveWindow(boolean active) { mActive = active; if (active) { - SessionStore.get().setActiveStore(mWindowId); - mSessionId = mSessionStack.getCurrentSessionId(); - GeckoSession session = mSessionStack.getSession(mSessionId); + SessionStore.get().setActiveStore(mSession); + GeckoSession session = mSession.getGeckoSession(); if (session != null) { session.getTextInput().setView(this); } @@ -593,18 +632,18 @@ private void updateTitleBar() { updateTitleBarUrl(getResources().getString(R.string.url_history_title)); } else { - updateTitleBarUrl(mSessionStack.getCurrentUri()); + updateTitleBarUrl(mSession.getCurrentUri()); } } private void updateTitleBarUrl(String url) { if (mTitleBar != null && url != null) { - mTitleBar.setIsInsecure(!mSessionStack.isSecure()); - if (url.startsWith("data") && mSessionStack.isPrivateMode()) { + mTitleBar.setIsInsecure(!mSession.isSecure()); + if (url.startsWith("data") && mSession.isPrivateMode()) { mTitleBar.setInsecureVisibility(GONE); mTitleBar.setURL(getResources().getString(R.string.private_browsing_title)); - } else if (url.equals(mSessionStack.getHomeUri())) { + } else if (url.equals(mSession.getHomeUri())) { mTitleBar.setInsecureVisibility(GONE); mTitleBar.setURL(getResources().getString(R.string.url_home_title, getResources().getString(R.string.app_name))); @@ -623,8 +662,8 @@ private void updateTitleBarUrl(String url) { } } - public SessionStack getSessionStack() { - return mSessionStack; + public Session getSession() { + return mSession; } public TopBarWidget getTopBar() { @@ -649,7 +688,7 @@ public void setSurfaceTexture(SurfaceTexture aTexture, final int aWidth, final i super.setSurfaceTexture(aTexture, aWidth, aHeight, aFirstDrawCallback); } else { - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); if (session == null) { return; } @@ -663,6 +702,7 @@ public void setSurfaceTexture(SurfaceTexture aTexture, final int aWidth, final i aTexture.setDefaultBufferSize(aWidth, aHeight); mSurface = new Surface(aTexture); if (mDisplay == null) { + Log.e("VRB", "makelele acquireDisplay1"); mDisplay = session.acquireDisplay(); } else { Log.e(LOGTAG, "GeckoDisplay was not null in BrowserWidget.setSurfaceTexture()"); @@ -677,7 +717,7 @@ public void setSurface(Surface aSurface, final int aWidth, final int aHeight, Ru super.setSurface(aSurface, aWidth, aHeight, aFirstDrawCallback); } else { - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); if (session == null) { return; } @@ -686,6 +726,7 @@ public void setSurface(Surface aSurface, final int aWidth, final int aHeight, Ru mSurface = aSurface; mFirstDrawCallback = aFirstDrawCallback; if (mDisplay == null) { + Log.e("VRB", "makelele acquireDisplay1"); mDisplay = session.acquireDisplay(); } else { Log.e(LOGTAG, "GeckoDisplay was not null in BrowserWidget.setSurfaceTexture()"); @@ -702,6 +743,9 @@ private void callSurfaceChanged() { if (mDisplay != null) { mDisplay.surfaceChanged(mSurface, mBorderWidth, mBorderWidth, mWidth - mBorderWidth * 2, mHeight - mBorderWidth * 2); } + if (mSession != null) { + mSession.updateLastUse(); + } } @Override @@ -738,9 +782,9 @@ public void handleTouchEvent(MotionEvent aEvent) { if (!mActive) { mClickedAfterFocus = true; updateBorder(); - if (mWindowDelegate != null) { - // Focus this window - mWindowDelegate.onFocusRequest(this); + // Focus this window + for (WindowListener listener: mListeners) { + listener.onFocusRequest(this); } // Return to discard first click after focus return; @@ -763,11 +807,10 @@ public void handleTouchEvent(MotionEvent aEvent) { requestFocus(); requestFocusFromTouch(); } - GeckoSession session = mSessionStack.getSession(mSessionId); - if (session == null) { - return; + GeckoSession session = mSession.getGeckoSession(); + if (session != null) { + session.getPanZoomController().onTouchEvent(aEvent); } - session.getPanZoomController().onTouchEvent(aEvent); } } @@ -790,12 +833,10 @@ public void handleHoverEvent(MotionEvent aEvent) { super.handleHoverEvent(aEvent); } else { - SessionStack activeStore = SessionStore.get().getActiveStore(); - GeckoSession session = activeStore.getSession(mSessionId); - if (session == null) { - return; + GeckoSession session = mSession.getGeckoSession(); + if (session != null) { + session.getPanZoomController().onMotionEvent(aEvent); } - session.getPanZoomController().onMotionEvent(aEvent); } } @@ -809,8 +850,8 @@ protected void updateBorder() { if (mWidgetPlacement.borderColor != color) { mWidgetPlacement.borderColor = color; mWidgetManager.updateWidget(this); - if (mWindowDelegate != null) { - mWindowDelegate.onBorderChanged(this); + for (WindowListener listener: mListeners) { + listener.onBorderChanged(this); } } } @@ -857,8 +898,14 @@ public boolean isFullScreen() { return mIsFullScreen; } - public void setWindowDelegate(WindowDelegate aDelegate) { - mWindowDelegate = aDelegate; + public void addWindowListener(WindowListener aListener) { + if (!mListeners.contains(aListener)) { + mListeners.add(aListener); + } + } + + public void removeWindowListener(WindowListener aListener) { + mListeners.remove(aListener); } @Override @@ -875,14 +922,8 @@ public void handleResizeEvent(float aWorldWidth, float aWorldHeight) { @Override public void releaseWidget() { - mSessionStack.setPromptDelegate(null); - mSessionStack.removeSessionChangeListener(this); - mSessionStack.removeContentListener(this); - mSessionStack.removeVideoAvailabilityListener(this); - mSessionStack.removeNavigationListener(this); - mSessionStack.removeProgressListener(this); - mSessionStack.setHistoryDelegate(null); - GeckoSession session = mSessionStack.getSession(mSessionId); + cleanListeners(mSession); + GeckoSession session = mSession.getGeckoSession(); if (mDisplay != null) { mDisplay.surfaceDestroyed(); if (session != null) { @@ -907,12 +948,19 @@ public void releaseWidget() { @Override - public void setFirstDraw(final boolean aIsFirstDraw) { - mWidgetPlacement.composited = aIsFirstDraw; + public void setComposited(final boolean aIsComposited) { + mWidgetPlacement.composited = aIsComposited; + if (!aIsComposited) { + mAfterFirstPaint = false; + } + } + + public void setFirstDrawCallback(Runnable aRunnable) { + mFirstDrawCallback = aRunnable; } @Override - public boolean getFirstDraw() { + public boolean isComposited() { return mWidgetPlacement.composited; } @@ -932,7 +980,7 @@ public void setVisible(boolean aVisible) { return; } if (!mIsInVRVideoMode) { - mSessionStack.setActive(aVisible); + mSession.setActive(aVisible); } mWidgetPlacement.visible = aVisible; if (!aVisible) { @@ -960,30 +1008,50 @@ public void draw(Canvas aCanvas) { } } - // SessionStack.GeckoSessionChange + public void setSession(@NonNull Session aSession) { + if (mSession != aSession) { + Session oldSession = mSession; + if (oldSession != null) { + cleanListeners(oldSession); + } - @Override - public void onCurrentSessionChange(GeckoSession aSession, int aId) { - Log.d(LOGTAG, "onCurrentSessionChange: " + this.toString()); - if (mSessionId == aId) { - Log.d(LOGTAG, "BrowserWidget.onCurrentSessionChange session id same, bail: " + aId); - return; + mSession = aSession; + setupListeners(mSession); + if (oldSession != null) { + onCurrentSessionChange(oldSession.getGeckoSession(), aSession.getGeckoSession()); + } else { + onCurrentSessionChange(null, aSession.getGeckoSession()); + } + for (WindowListener listener: mListeners) { + listener.onSessionChanged(oldSession, aSession); + } + if (mSession.getBitmap() == null) { + captureImage(); + } } + } - GeckoSession oldSession = mSessionStack.getSession(mSessionId); - if (oldSession != null && mDisplay != null) { - Log.d(LOGTAG, "Detach from previous session: " + mSessionId); - oldSession.getTextInput().setView(null); + public void releaseDisplay(GeckoSession aSession) { + if (aSession != null && mDisplay != null) { + Log.d(LOGTAG, "Detach from previous session: " + aSession.hashCode()); + aSession.getTextInput().setView(null); mDisplay.surfaceDestroyed(); - oldSession.releaseDisplay(mDisplay); + aSession.releaseDisplay(mDisplay); mDisplay = null; } + } + // Session.GeckoSessionChange + @Override + public void onCurrentSessionChange(GeckoSession aOldSession, GeckoSession aSession) { + Log.d(LOGTAG, "onCurrentSessionChange: " + this.toString()); + + releaseDisplay(aOldSession); mWidgetManager.setIsServoSession(isInstanceOfServoSession(aSession)); - mSessionId = aId; + Log.e("VRB", "makelele acquireDisplay3"); mDisplay = aSession.acquireDisplay(); - Log.d(LOGTAG, "surfaceChanged: " + aId); + Log.d(LOGTAG, "surfaceChanged: " + aSession.hashCode()); callSurfaceChanged(); aSession.getTextInput().setView(this); @@ -995,11 +1063,24 @@ public void onCurrentSessionChange(GeckoSession aSession, int aId) { } } + public void showTabsMenu() { + if (mTabsWidget == null) { + mTabsWidget = new TabsWidget(getContext(), mSession.isPrivateMode()); + mTabsWidget.getPlacement().parentHandle = mHandle; + mTabsWidget.setTabDelegate(this); + } + if (mTabsWidget.isVisible()) { + mTabsWidget.onDismiss(); + } else { + mTabsWidget.show(REQUEST_FOCUS); + } + } + // View @Override public InputConnection onCreateInputConnection(final EditorInfo outAttrs) { Log.d(LOGTAG, "BrowserWidget onCreateInputConnection"); - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); if (session == null) { return null; } @@ -1008,8 +1089,7 @@ public InputConnection onCreateInputConnection(final EditorInfo outAttrs) { @Override public boolean onCheckIsTextEditor() { - SessionStack sessionStack = SessionStore.get().getSessionStack(mWindowId); - return sessionStack.isInputActive(mSessionId); + return mSession.isInputActive(); } @@ -1018,7 +1098,7 @@ public boolean onKeyPreIme(int aKeyCode, KeyEvent aEvent) { if (super.onKeyPreIme(aKeyCode, aEvent)) { return true; } - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); return (session != null) && session.getTextInput().onKeyPreIme(aKeyCode, aEvent); } @@ -1027,7 +1107,7 @@ public boolean onKeyUp(int aKeyCode, KeyEvent aEvent) { if (super.onKeyUp(aKeyCode, aEvent)) { return true; } - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); return (session != null) && session.getTextInput().onKeyUp(aKeyCode, aEvent); } @@ -1036,7 +1116,7 @@ public boolean onKeyDown(int aKeyCode, KeyEvent aEvent) { if (super.onKeyDown(aKeyCode, aEvent)) { return true; } - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); return (session != null) && session.getTextInput().onKeyDown(aKeyCode, aEvent); } @@ -1045,7 +1125,7 @@ public boolean onKeyLongPress(int aKeyCode, KeyEvent aEvent) { if (super.onKeyLongPress(aKeyCode, aEvent)) { return true; } - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); return (session != null) && session.getTextInput().onKeyLongPress(aKeyCode, aEvent); } @@ -1054,7 +1134,7 @@ public boolean onKeyMultiple(int aKeyCode, int repeatCount, KeyEvent aEvent) { if (super.onKeyMultiple(aKeyCode, repeatCount, aEvent)) { return true; } - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); return (session != null) && session.getTextInput().onKeyMultiple(aKeyCode, repeatCount, aEvent); } @@ -1066,13 +1146,13 @@ protected void onFocusChanged(boolean aGainFocus, int aDirection, Rect aPrevious @Override public boolean onTouchEvent(MotionEvent aEvent) { - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); return (session != null) && session.getPanZoomController().onTouchEvent(aEvent) == PanZoomController.INPUT_RESULT_HANDLED; } @Override public boolean onGenericMotionEvent(MotionEvent aEvent) { - GeckoSession session = mSessionStack.getSession(mSessionId); + GeckoSession session = mSession.getGeckoSession(); return (session != null) && session.getPanZoomController().onMotionEvent(aEvent) == PanZoomController.INPUT_RESULT_HANDLED; } @@ -1482,6 +1562,23 @@ public void onVideoAvailabilityChanged(boolean aVideosAvailable) { } // GeckoSession.NavigationDelegate + @Override + public void onPageStop(@NonNull GeckoSession aSession, boolean b) { + captureImage(); + } + + public void captureImage() { + if (mDisplay == null || !mSession.getGeckoSession().isOpen()) { + return; + } + + mDisplay.capturePixels().then(bitmap -> { + if (bitmap != null) { + mSession.setBitmap(bitmap, mSession.getGeckoSession()); + } + return null; + }); + } @Override public void onLocationChange(@NonNull GeckoSession session, @Nullable String url) { @@ -1507,7 +1604,7 @@ public void onHistoryStateChange(@NonNull GeckoSession geckoSession, @NonNull Hi @Nullable @Override public GeckoResult onVisited(@NonNull GeckoSession geckoSession, @NonNull String url, @Nullable String lastVisitedURL, int flags) { - if (mSessionStack.isPrivateMode() || + if (mSession.isPrivateMode() || (flags & VISIT_TOP_LEVEL) == 0 || (flags & VISIT_UNRECOVERABLE_ERROR) != 0) { return GeckoResult.fromValue(false); @@ -1537,7 +1634,7 @@ public GeckoResult onVisited(@NonNull GeckoSession geckoSession, @NonNu @UiThread @Nullable public GeckoResult getVisited(@NonNull GeckoSession geckoSession, @NonNull String[] urls) { - if (mSessionStack.isPrivateMode()) { + if (mSession.isPrivateMode()) { return GeckoResult.fromValue(new boolean[]{}); } @@ -1564,4 +1661,26 @@ public void onSecurityChange(GeckoSession geckoSession, SecurityInformation secu } } + // TabsWidget.TabDelegate + + @Override + public void onTabSelect(Session aTab) { + for (WindowListener listener: mListeners) { + listener.onTabSelect(this, aTab); + } + } + + @Override + public void onTabAdd() { + for (WindowListener listener: mListeners) { + listener.onTabAdd(this); + } + } + + @Override + public void onTabsClose(ArrayList aTabs) { + for (WindowListener listener : mListeners) { + listener.onTabsClose(this, aTabs); + } + } } 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 d5d2fc6113..444dd70d6a 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 @@ -14,7 +14,8 @@ import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.browser.Media; import org.mozilla.vrbrowser.browser.SettingsStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; +import org.mozilla.vrbrowser.browser.engine.Session; +import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.utils.SystemUtils; @@ -28,7 +29,7 @@ import java.util.ArrayList; public class Windows implements TrayListener, TopBarWidget.Delegate, TitleBarWidget.Delegate, - GeckoSession.ContentDelegate, WindowWidget.WindowDelegate { + GeckoSession.ContentDelegate, WindowWidget.WindowListener { private static final String LOGTAG = SystemUtils.createLogtag(Windows.class); @@ -36,15 +37,12 @@ public class Windows implements TrayListener, TopBarWidget.Delegate, TitleBarWid class WindowState { WindowPlacement placement; - SessionStack sessionStack; - int currentSessionId; int textureWidth; int textureHeight; float worldWidth; + int tabIndex = -1; - public void load(WindowWidget aWindow) { - sessionStack = aWindow.getSessionStack(); - currentSessionId = aWindow.getSessionStack().getCurrentSessionId(); + public void load(WindowWidget aWindow, WindowsState aState) { WidgetPlacement widgetPlacement; if (aWindow.isFullScreen()) { widgetPlacement = aWindow.getBeforeFullscreenPlacement(); @@ -60,12 +58,14 @@ public void load(WindowWidget aWindow) { textureWidth = widgetPlacement.width; textureHeight = widgetPlacement.height; worldWidth = widgetPlacement.worldWidth; + tabIndex = aState.tabs.indexOf(aWindow.getSession()); } } class WindowsState { WindowPlacement focusedWindowPlacement = WindowPlacement.FRONT; ArrayList regularWindowsState = new ArrayList<>(); + ArrayList tabs = new ArrayList<>(); boolean privateMode = false; } @@ -126,9 +126,10 @@ private void saveState() { WindowsState state = new WindowsState(); state.privateMode = mPrivateMode; state.focusedWindowPlacement = mFocusedWindow.isFullScreen() ? mFocusedWindow.getmWindowPlacementBeforeFullscreen() : mFocusedWindow.getWindowPlacement(); + state.tabs = new ArrayList(SessionStore.get().getSortedSessions(false)); for (WindowWidget window : mRegularWindows) { WindowState windowState = new WindowState(); - windowState.load(window); + windowState.load(window, state); state.regularWindowsState.add(windowState); } Gson gson = new GsonBuilder().setPrettyPrinting().create(); @@ -153,7 +154,7 @@ private WindowsState restoreState() { Log.d(LOGTAG, "Windows state restored"); - } catch (IOException e) { + } catch (Exception e) { Log.w(LOGTAG, "Error restoring windows state: " + e.getLocalizedMessage()); } finally { @@ -181,15 +182,15 @@ public WindowWidget addWindow() { } if (mFullscreenWindow != null) { - mFullscreenWindow.getSessionStack().exitFullScreen(); - onFullScreen(mFullscreenWindow.getSessionStack().getCurrentSession(), false); + mFullscreenWindow.getSession().exitFullScreen(); + onFullScreen(mFullscreenWindow.getSession().getGeckoSession(), false); } WindowWidget frontWindow = getFrontWindow(); WindowWidget leftWindow = getLeftWindow(); WindowWidget rightWindow = getRightWindow(); - WindowWidget newWindow = createWindow(); + WindowWidget newWindow = createWindow(null); WindowWidget focusedWindow = getFocusedWindow(); if (frontWindow == null) { @@ -225,13 +226,13 @@ public WindowWidget addWindow() { return newWindow; } - private WindowWidget addWindow(@NonNull WindowState aState) { + private WindowWidget addRestoredWindow(@NonNull WindowState aState, @NonNull Session aSession) { if (getCurrentWindows().size() >= MAX_WINDOWS) { showMaxWindowsMessage(); return null; } - WindowWidget newWindow = createWindow(); + WindowWidget newWindow = createWindow(aSession); newWindow.getPlacement().width = aState.textureWidth; newWindow.getPlacement().height = aState.textureHeight; newWindow.getPlacement().worldWidth = aState.worldWidth; @@ -513,8 +514,8 @@ public boolean handleBack() { if (mFocusedWindow == null) { return false; } - if (mFocusedWindow.getSessionStack().canGoBack()) { - mFocusedWindow.getSessionStack().goBack(); + if (mFocusedWindow.getSession().canGoBack()) { + mFocusedWindow.getSession().goBack(); return true; } else if (isInPrivateMode()) { exitPrivateMode(); @@ -573,10 +574,16 @@ private WindowWidget getRightWindow() { private void restoreWindows() { WindowsState windowsState = restoreState(); if (windowsState != null) { + ArrayList restoredSessions = new ArrayList<>(); + if (windowsState.tabs != null && windowsState.tabs.size() > 0) { + restoredSessions = SessionStore.get().restoreSessions(windowsState.tabs); + } mPrivateMode = false; for (WindowState windowState : windowsState.regularWindowsState) { - WindowWidget window = addWindow(windowState); - window.getSessionStack().restore(windowState.sessionStack, windowState.currentSessionId); + if (windowState.tabIndex >= 0 && windowState.tabIndex < restoredSessions.size()) { + WindowWidget window = addRestoredWindow(windowState, restoredSessions.get(windowState.tabIndex)); + window.captureImage(); + } } mPrivateMode = !windowsState.privateMode; if (windowsState.privateMode) { @@ -611,10 +618,10 @@ private void removeWindow(@NonNull WindowWidget aWindow) { mPrivateWindows.remove(aWindow); aWindow.getTopBar().setVisible(false); aWindow.getTopBar().setDelegate((TopBarWidget.Delegate) null); - aWindow.setWindowDelegate(null); + aWindow.removeWindowListener(this); aWindow.getTitleBar().setVisible(false); aWindow.getTitleBar().setDelegate((TitleBarWidget.Delegate) null); - aWindow.getSessionStack().removeContentListener(this); + aWindow.getSession().removeContentListener(this); aWindow.close(); updateMaxWindowScales(); updateCurvedMode(true); @@ -762,7 +769,7 @@ private void updateTopBars() { ArrayList windows = getCurrentWindows(); WindowWidget leftWindow = getLeftWindow(); WindowWidget rightWindow = getRightWindow(); - boolean visible = mFullscreenWindow == null && (windows.size() > 1 || isInPrivateMode()); + boolean visible = mFullscreenWindow == null;// && (windows.size() > 1 || isInPrivateMode()); for (WindowWidget window: windows) { window.getTopBar().setVisible(visible); window.getTopBar().setClearMode((windows.size() == 1 && isInPrivateMode())); @@ -796,14 +803,20 @@ private void updateTitleBars() { } } - private WindowWidget createWindow() { + private WindowWidget createWindow(@Nullable Session aSession) { int newWindowId = sIndex++; - WindowWidget window = new WindowWidget(mContext, newWindowId, mPrivateMode); - window.setWindowDelegate(this); + WindowWidget window; + if (aSession != null) { + window = new WindowWidget(mContext, newWindowId, aSession); + } else { + window = new WindowWidget(mContext, newWindowId, mPrivateMode); + } + + window.addWindowListener(this); getCurrentWindows().add(window); window.getTopBar().setDelegate(this); window.getTitleBar().setDelegate(this); - window.getSessionStack().addContentListener(this); + window.getSession().addContentListener(this); if (mPrivateMode) { TelemetryWrapper.openWindowsEvent(mPrivateWindows.size() - 1, mPrivateWindows.size(), true); @@ -862,6 +875,13 @@ public void onHistoryClicked() { mFocusedWindow.switchHistory(); } + @Override + public void onTabsClicked() { + if (mFocusedWindow != null) { + mFocusedWindow.showTabsMenu(); + } + } + // TopBarWidget Delegate @Override public void onCloseClicked(TopBarWidget aWidget) { @@ -901,7 +921,7 @@ public void onTitleClicked(@NonNull TitleBarWidget titleBar) { public void onMediaPlayClicked(@NonNull TitleBarWidget titleBar) { for (WindowWidget window : getCurrentWindows()) { if (window.getTitleBar() == titleBar) { - window.getSessionStack().getFullScreenVideo().play(); + window.getSession().getFullScreenVideo().play(); } } } @@ -910,7 +930,7 @@ public void onMediaPlayClicked(@NonNull TitleBarWidget titleBar) { public void onMediaPauseClicked(@NonNull TitleBarWidget titleBar) { for (WindowWidget window : getCurrentWindows()) { if (window.getTitleBar() == titleBar) { - window.getSessionStack().getFullScreenVideo().pause(); + window.getSession().getFullScreenVideo().pause(); } } } @@ -919,11 +939,11 @@ private void setFullScreenSize(WindowWidget aWindow) { final float minScale = WidgetPlacement.floatDimension(mContext, R.dimen.window_fullscreen_min_scale); // Set browser fullscreen size float aspect = SettingsStore.getInstance(mContext).getWindowAspect(); - SessionStack sessionStack = mFocusedWindow.getSessionStack(); - if (sessionStack == null) { + Session session = mFocusedWindow.getSession(); + if (session == null) { return; } - Media media = sessionStack.getFullScreenVideo(); + Media media = session.getFullScreenVideo(); if (media != null && media.getWidth() > 0 && media.getHeight() > 0) { aspect = (float)media.getWidth() / (float)media.getHeight(); } @@ -968,7 +988,17 @@ public void onFullScreen(GeckoSession session, boolean aFullScreen) { @Nullable private WindowWidget getWindowWithSession(GeckoSession aSession) { for (WindowWidget window: getCurrentWindows()) { - if (window.getSessionStack().containsSession(aSession)) { + if (window.getSession().getGeckoSession() == aSession) { + return window; + } + } + return null; + } + + @Nullable + private WindowWidget getWindowWithSession(Session aSession) { + for (WindowWidget window: getCurrentWindows()) { + if (window.getSession() == aSession) { return window; } } @@ -989,4 +1019,89 @@ public void onBorderChanged(WindowWidget aWindow) { } + @Override + public void onTabSelect(@NonNull WindowWidget aWindow, Session aTab) { + WindowWidget windowToMove = getWindowWithSession(aTab); + if (windowToMove != null && windowToMove != aWindow) { + // Move session between windows + Session moveFrom = windowToMove.getSession(); + Session moveTo = aWindow.getSession(); + windowToMove.releaseDisplay(moveFrom.getGeckoSession()); + aWindow.releaseDisplay(moveTo.getGeckoSession()); + windowToMove.setSession(moveTo); + aWindow.setSession(moveFrom); + SessionStore.get().setActiveStore(aWindow.getSession()); + + } else{ + aWindow.setSession(aTab); + SessionStore.get().setActiveStore(aTab); + } + } + + @Override + public void onTabAdd(@NonNull WindowWidget aWindow) { + Session session = SessionStore.get().createSession(aWindow.getSession().isPrivateMode()); + aWindow.setComposited(false); + aWindow.setFirstDrawCallback(() -> { + if (!aWindow.isComposited()) { + aWindow.setComposited(true); + mWidgetManager.updateWidget(aWindow); + } + }); + mWidgetManager.updateWidget(aWindow); + aWindow.setSession(session); + session.loadHomePage(); + SessionStore.get().setActiveStore(session); + } + + @Override + public void onTabsClose(@NonNull WindowWidget aWindow, ArrayList aTabs) { + // Prepare available tabs to choose from + ArrayList available = SessionStore.get().getSortedSessions(mPrivateMode); + available.removeAll(aTabs); + available.removeIf(session -> getWindowWithSession(session) != null); + + // Sort windows by priority to take an available tab + WindowWidget front = getFrontWindow(); + ArrayList windows = new ArrayList<>(getCurrentWindows()); + windows.sort((w1, w2) -> { + // Max priority for the target window + if (w1 == aWindow) { + return -1; + } + if (w2 == aWindow) { + return 1; + } + // Front window has next max priority + if (w1 == front) { + return -1; + } + if (w2 == front) { + return 1; + } + return 0; + }); + + // Take tabs for each window + for (WindowWidget window: windows) { + if (!aTabs.contains(window.getSession())) { + // Window already contains a no closed tab + continue; + } + if (available.size() > 0) { + // Window contains a closed tab and we have a tab available from the list + window.setSession(available.get(0)); + available.remove(0); + } else { + // We don't have more tabs available for the front window, load home. + onTabAdd(window); + } + } + + for (Session session: aTabs) { + SessionStore.get().destroySession(session); + } + + SessionStore.get().setActiveStore(aWindow.getSession()); + } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/CrashDialogWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/CrashDialogWidget.java index 01db86b761..769c831c69 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/CrashDialogWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/CrashDialogWidget.java @@ -64,7 +64,7 @@ private void initialize(Context aContext) { mAudio.playSound(AudioEngine.Sound.CLICK); } - SessionStore.get().getActiveStore().newSessionWithUrl(getContext().getString(R.string.crash_dialog_learn_more_url)); + SessionStore.get().getActiveSession().loadUri(getContext().getString(R.string.crash_dialog_learn_more_url)); onDismiss(); }); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java index 4155a08392..5ab39ae6c1 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java @@ -33,9 +33,7 @@ import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate; import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; import org.mozilla.vrbrowser.utils.LocaleUtils; -import org.mozilla.vrbrowser.utils.SystemUtils; -import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; public class VoiceSearchWidget extends UIDialog implements WidgetManagerDelegate.PermissionListener, @@ -233,7 +231,7 @@ public void startVoiceSearch() { String locale = LocaleUtils.getVoiceSearchLocale(getContext()); mMozillaSpeechService.setLanguage(LocaleUtils.mapToMozillaSpeechLocales(locale)); boolean storeData = SettingsStore.getInstance(getContext()).isSpeechDataCollectionEnabled(); - if (SessionStore.get().getActiveStore().isPrivateMode()) { + if (SessionStore.get().getActiveSession().isPrivateMode()) { storeData = false; } mMozillaSpeechService.storeSamples(storeData); @@ -300,7 +298,7 @@ public void show(@ShowFlags int aShowFlags) { ThreadUtils.postToUiThread(() -> show(aShowFlags)); }, url -> { - mWidgetManager.getFocusedWindow().getSessionStack().loadUri(getResources().getString(R.string.private_policy_url)); + mWidgetManager.getFocusedWindow().getSession().loadUri(getResources().getString(R.string.private_policy_url)); onDismiss(); }); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java index 14abef0eff..4bee6e9e0f 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/ContentLanguageOptionsView.java @@ -56,7 +56,7 @@ private void initialize(Context aContext) { mDelegate.showView(new LanguageOptionsView(getContext(), mWidgetManager)); }); mBinding.headerLayout.setHelpClickListener(view -> { - SessionStore.get().getActiveStore().loadUri(getResources().getString(R.string.sumo_language_content_url)); + SessionStore.get().getActiveSession().loadUri(getResources().getString(R.string.sumo_language_content_url)); mDelegate.exitWholeSettings(); }); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java index ce7c7f89bf..72cf515a84 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/DisplayLanguageOptionsView.java @@ -41,7 +41,7 @@ private void initialize(Context aContext) { mDelegate.showView(new LanguageOptionsView(getContext(), mWidgetManager)); }); mBinding.headerLayout.setHelpClickListener(view -> { - SessionStore.get().getActiveStore().loadUri(getResources().getString(R.string.sumo_language_display_url)); + SessionStore.get().getActiveSession().loadUri(getResources().getString(R.string.sumo_language_display_url)); mDelegate.exitWholeSettings(); }); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/EnvironmentOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/EnvironmentOptionsView.java index 6d69a0d2a3..d47833efe4 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/EnvironmentOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/EnvironmentOptionsView.java @@ -50,7 +50,7 @@ private void initialize(Context aContext) { mBinding.envOverrideSwitch.setOnCheckedChangeListener(mEnvOverrideListener); setEnvOverride(SettingsStore.getInstance(getContext()).isEnvironmentOverrideEnabled()); mBinding.envOverrideSwitch.setHelpDelegate(() -> { - SessionStore.get().getActiveStore().loadUri(getContext().getString(R.string.environment_override_help_url)); + SessionStore.get().getActiveSession().loadUri(getContext().getString(R.string.environment_override_help_url)); exitWholeSettings(); }); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java index feef16981d..7845bdb895 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/PrivacyOptionsView.java @@ -59,7 +59,7 @@ private void initialize(Context aContext) { // Options mBinding.showPrivacyButton.setOnClickListener(v -> { - SessionStore.get().getActiveStore().newSessionWithUrl(getContext().getString(R.string.private_policy_url)); + SessionStore.get().getActiveSession().loadUri(getContext().getString(R.string.private_policy_url)); exitWholeSettings(); }); @@ -95,7 +95,7 @@ private void initialize(Context aContext) { mBinding.drmContentPlaybackSwitch.setOnCheckedChangeListener(mDrmContentListener); mBinding.drmContentPlaybackSwitch.setLinkClickListener((widget, url) -> { - SessionStore.get().getActiveStore().loadUri(url); + SessionStore.get().getActiveSession().loadUri(url); exitWholeSettings(); }); setDrmContent(SettingsStore.getInstance(getContext()).isDrmContentPlaybackEnabled(), false); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java index 8d10eaff3c..19d4e63e79 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/SettingsWidget.java @@ -23,8 +23,8 @@ import org.mozilla.vrbrowser.BuildConfig; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; +import org.mozilla.vrbrowser.browser.engine.Session; import org.mozilla.vrbrowser.browser.engine.SessionStore; -import org.mozilla.vrbrowser.browser.engine.SessionStack; import org.mozilla.vrbrowser.ui.views.HoneycombButton; import org.mozilla.vrbrowser.ui.widgets.UIWidget; import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate; @@ -161,7 +161,7 @@ private void initialize(Context aContext) { if (mAudio != null) { mAudio.playSound(AudioEngine.Sound.CLICK); } - SessionStore.get().getActiveStore().loadUri(getContext().getString(R.string.help_url)); + SessionStore.get().getActiveSession().loadUri(getContext().getString(R.string.help_url)); onDismiss(); }); @@ -218,8 +218,8 @@ private void onSettingsPrivacyClick() { } private void onSettingsReportClick() { - SessionStack sessionStack = SessionStore.get().getActiveStore(); - String url = sessionStack.getCurrentUri(); + Session session = SessionStore.get().getActiveSession(); + String url = session.getCurrentUri(); try { if (url == null) { @@ -227,9 +227,9 @@ private void onSettingsReportClick() { url = ""; } else if (url.startsWith("jar:") || url.startsWith("resource:") || url.startsWith("about:") || url.startsWith("data:")) { url = ""; - } else if (sessionStack.isHomeUri(url)) { + } else if (session.isHomeUri(url)) { // Use the original URL (without any hash). - url = sessionStack.getHomeUri(); + url = session.getHomeUri(); } url = URLEncoder.encode(url, "UTF-8"); @@ -238,7 +238,7 @@ private void onSettingsReportClick() { Log.e(LOGTAG, "Cannot encode URL"); } - sessionStack.newSessionWithUrl(getContext().getString(R.string.private_report_url, url)); + session.loadUri(getContext().getString(R.string.private_report_url, url)); onDismiss(); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java index 1b09e85acd..c63479df3d 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/settings/VoiceSearchLanguageOptionsView.java @@ -41,7 +41,7 @@ private void initialize(Context aContext) { mDelegate.showView(new LanguageOptionsView(getContext(), mWidgetManager)); }); mBinding.headerLayout.setHelpClickListener(view -> { - SessionStore.get().getActiveStore().loadUri(getResources().getString(R.string.sumo_language_voice_url)); + SessionStore.get().getActiveSession().loadUri(getResources().getString(R.string.sumo_language_voice_url)); mDelegate.exitWholeSettings(); }); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/utils/UrlUtils.java b/app/src/common/shared/org/mozilla/vrbrowser/utils/UrlUtils.java index c84df11aa2..1299d57779 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/utils/UrlUtils.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/utils/UrlUtils.java @@ -33,6 +33,30 @@ public static String stripCommonSubdomains(@Nullable String host) { return host.substring(start); } + public static String stripProtocol(@Nullable String host) { + if (host == null) { + return ""; + } + + if (host.startsWith("data:")) { + return ""; + } + + String result; + int index = host.indexOf("://"); + if (index >= 0) { + result = host.substring(index + 3); + } else { + result = host; + } + + if (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + + return result; + } + private static Pattern domainPattern = Pattern.compile("^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$"); public static boolean isDomain(String text) { return domainPattern.matcher(text).find(); diff --git a/app/src/main/cpp/VRLayer.cpp b/app/src/main/cpp/VRLayer.cpp index 26573a0792..fe5940c300 100644 --- a/app/src/main/cpp/VRLayer.cpp +++ b/app/src/main/cpp/VRLayer.cpp @@ -28,12 +28,14 @@ struct VRLayer::State { SurfaceChangedDelegate surfaceChangedDelegate; std::function pendingEvent; std::string name; + bool composited; State(): initialized(false), priority(0), drawIndex(0), drawRequested(false), drawInFront(false), + composited(false), currentEye(device::Eye::Left), clearColor(0), tintColor(1.0f, 1.0f, 1.0f, 1.0f) @@ -108,6 +110,10 @@ VRLayer::GetName() const { return m.name; } +bool VRLayer::IsComposited() const { + return m.composited; +} + bool VRLayer::ShouldDrawBefore(const VRLayer& aLayer) { if (m.layerType == VRLayer::LayerType::CUBEMAP || m.layerType == VRLayer::LayerType::EQUIRECTANGULAR) { @@ -202,6 +208,11 @@ VRLayer::SetName(const std::string &aName) { m.name = aName; } +void +VRLayer::SetComposited(bool aComposited) { + m.composited = aComposited; +} + void VRLayer::NotifySurfaceChanged(SurfaceChange aChange, const std::function& aFirstCompositeCallback) { if (m.surfaceChangedDelegate) { m.surfaceChangedDelegate(*this, aChange, aFirstCompositeCallback); diff --git a/app/src/main/cpp/VRLayer.h b/app/src/main/cpp/VRLayer.h index 2ea07971c2..e778d6c231 100644 --- a/app/src/main/cpp/VRLayer.h +++ b/app/src/main/cpp/VRLayer.h @@ -49,6 +49,7 @@ class VRLayer { const device::EyeRect& GetTextureRect(device::Eye aEye) const; bool GetDrawInFront() const; std::string GetName() const; + bool IsComposited() const; bool ShouldDrawBefore(const VRLayer& aLayer); void SetInitialized(bool aInitialized); @@ -64,6 +65,7 @@ class VRLayer { void SetSurfaceChangedDelegate(const SurfaceChangedDelegate& aDelegate); void SetDrawInFront(bool aDrawInFront); void SetName(const std::string& aName); + void SetComposited(bool aComposited); void NotifySurfaceChanged(SurfaceChange aChange, const std::function& aFirstCompositeCallback); protected: struct State; diff --git a/app/src/main/cpp/Widget.cpp b/app/src/main/cpp/Widget.cpp index 22ee3d2601..24266cc84d 100644 --- a/app/src/main/cpp/Widget.cpp +++ b/app/src/main/cpp/Widget.cpp @@ -453,7 +453,7 @@ void Widget::SetPlacement(const WidgetPlacementPtr& aPlacement) { bool wasComposited = m.placement->composited; m.placement = aPlacement; - if (!wasComposited && aPlacement->composited && m.root) { + if (wasComposited != aPlacement->composited && m.root) { m.root->ToggleAll(m.toggleState); int32_t textureWidth, textureHeight; GetSurfaceTextureSize(textureWidth, textureHeight); @@ -467,6 +467,7 @@ Widget::SetPlacement(const WidgetPlacementPtr& aPlacement) { if (layer) { layer->SetName(aPlacement->name); layer->SetClearColor(aPlacement->clearColor); + layer->SetComposited(aPlacement->composited); } } diff --git a/app/src/main/cpp/shaders/clear_color.fs b/app/src/main/cpp/shaders/clear_color.fs index 0a5d27b5f7..0041f43ea5 100644 --- a/app/src/main/cpp/shaders/clear_color.fs +++ b/app/src/main/cpp/shaders/clear_color.fs @@ -6,9 +6,9 @@ varying vec4 v_color; varying vec2 v_uv; void main() { - vec4 color = vec4(1.0f, 1.0f, 1.0f, 1.0f); - if ((v_uv.x < 0.0f) || (v_uv.x > 1.0f)) { - color.a = 0.0f; + vec4 color = vec4(1.0, 1.0, 1.0, 1.0); + if ((v_uv.x < 0.0) || (v_uv.x > 1.0)) { + color.a = 0.0; } gl_FragColor = color * v_color; } diff --git a/app/src/main/res/drawable/ic_icon_dialog_tabs_active.xml b/app/src/main/res/drawable/ic_icon_dialog_tabs_active.xml new file mode 100644 index 0000000000..99ecee26eb --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_dialog_tabs_active.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_icon_tray_tabs.xml b/app/src/main/res/drawable/ic_icon_tray_tabs.xml new file mode 100644 index 0000000000..e88a591bc4 --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_tray_tabs.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tab_add_background.xml b/app/src/main/res/drawable/tab_add_background.xml new file mode 100644 index 0000000000..adec1e7b29 --- /dev/null +++ b/app/src/main/res/drawable/tab_add_background.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/tab_add_icon_color.xml b/app/src/main/res/drawable/tab_add_icon_color.xml new file mode 100644 index 0000000000..6acb2c7c46 --- /dev/null +++ b/app/src/main/res/drawable/tab_add_icon_color.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_close_icon_color.xml b/app/src/main/res/drawable/tab_close_icon_color.xml new file mode 100644 index 0000000000..8bdca274bf --- /dev/null +++ b/app/src/main/res/drawable/tab_close_icon_color.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tab_view.xml b/app/src/main/res/layout/tab_view.xml new file mode 100644 index 0000000000..58b8bc482c --- /dev/null +++ b/app/src/main/res/layout/tab_view.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tabs.xml b/app/src/main/res/layout/tabs.xml new file mode 100644 index 0000000000..f78d41e0de --- /dev/null +++ b/app/src/main/res/layout/tabs.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tray.xml b/app/src/main/res/layout/tray.xml index d5d674f883..fcdaa270be 100644 --- a/app/src/main/res/layout/tray.xml +++ b/app/src/main/res/layout/tray.xml @@ -20,6 +20,15 @@ app:tooltipLayout="@layout/tooltip_tray" android:src="@drawable/ic_icon_tray_newwindow"/> + + 40dp 20dp + + 600dp + 350dp + 10dp + 20dp + 90dp + 400dp 200dp @@ -124,8 +131,8 @@ 0.25 -2.5 - 1.2 - 206dp + 1.42 + 246dp 40dp 4.0 20dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c479c34b13..ae631064b1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -981,4 +981,56 @@ Clear + + + Select + + + Done + + + Select all + + + Close tabs + + + Close all + + + Unselect all + + + 1 Tab + + + %1$s Tabs + + + 1 Tab selected + + + %1$s Tabs selected + + + 0 Tabs selected + + + Tabs + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 24074507ee..d21347ac4f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -261,6 +261,32 @@ true + + + +