Commit b3f92034 authored by Bo Liu's avatar Bo Liu Committed by Commit Bot

weblayer: Support setRetainInstance

Need to ensure that when switching between Activities, nothing holds
onto the old Activity (ie Context) or Views from the old Activity.
Otherwise it could cause the old Activity to leak. This required fixes
in content as well.

Bug: 1033926
Change-Id: I731cd25b35171a572956d0846eabd259c19ae554
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1980476Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarShimi Zhang <ctzsm@chromium.org>
Commit-Queue: Bo <boliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#728805}
parent 34e57118
...@@ -312,6 +312,8 @@ class WebContentsAccessibilityAndroid::Connector ...@@ -312,6 +312,8 @@ class WebContentsAccessibilityAndroid::Connector
WebContentsAccessibilityAndroid* accessibility); WebContentsAccessibilityAndroid* accessibility);
~Connector() override; ~Connector() override;
void DeleteEarly();
// RenderWidgetHostConnector: // RenderWidgetHostConnector:
void UpdateRenderProcessConnection( void UpdateRenderProcessConnection(
RenderWidgetHostViewAndroid* old_rwhva, RenderWidgetHostViewAndroid* old_rwhva,
...@@ -337,6 +339,10 @@ WebContentsAccessibilityAndroid::Connector::~Connector() { ...@@ -337,6 +339,10 @@ WebContentsAccessibilityAndroid::Connector::~Connector() {
manager->set_web_contents_accessibility(nullptr); manager->set_web_contents_accessibility(nullptr);
} }
void WebContentsAccessibilityAndroid::Connector::DeleteEarly() {
RenderWidgetHostConnector::DestroyEarly();
}
void WebContentsAccessibilityAndroid::Connector::UpdateRenderProcessConnection( void WebContentsAccessibilityAndroid::Connector::UpdateRenderProcessConnection(
RenderWidgetHostViewAndroid* old_rwhva, RenderWidgetHostViewAndroid* old_rwhva,
RenderWidgetHostViewAndroid* new_rwhva) { RenderWidgetHostViewAndroid* new_rwhva) {
...@@ -371,6 +377,10 @@ WebContentsAccessibilityAndroid::~WebContentsAccessibilityAndroid() { ...@@ -371,6 +377,10 @@ WebContentsAccessibilityAndroid::~WebContentsAccessibilityAndroid() {
Java_WebContentsAccessibilityImpl_onNativeObjectDestroyed(env, obj); Java_WebContentsAccessibilityImpl_onNativeObjectDestroyed(env, obj);
} }
void WebContentsAccessibilityAndroid::DeleteEarly(JNIEnv* env) {
connector_->DeleteEarly();
}
jboolean WebContentsAccessibilityAndroid::IsEnabled( jboolean WebContentsAccessibilityAndroid::IsEnabled(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jobject>& obj) { const JavaParamRef<jobject>& obj) {
......
...@@ -42,6 +42,8 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid ...@@ -42,6 +42,8 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid
// Methods called from Java via JNI // Methods called from Java via JNI
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
void DeleteEarly(JNIEnv* env);
// Global methods. // Global methods.
jboolean IsEnabled(JNIEnv* env, jboolean IsEnabled(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj); const base::android::JavaParamRef<jobject>& obj);
......
...@@ -35,11 +35,14 @@ class RenderWidgetHostConnector::Observer ...@@ -35,11 +35,14 @@ class RenderWidgetHostConnector::Observer
void RenderWidgetHostViewDestroyed( void RenderWidgetHostViewDestroyed(
RenderWidgetHostViewAndroid* rwhva) override; RenderWidgetHostViewAndroid* rwhva) override;
void DestroyEarly();
void UpdateRenderWidgetHostView(RenderWidgetHostViewAndroid* new_rwhva); void UpdateRenderWidgetHostView(RenderWidgetHostViewAndroid* new_rwhva);
RenderWidgetHostViewAndroid* GetRenderWidgetHostViewAndroid() const; RenderWidgetHostViewAndroid* GetRenderWidgetHostViewAndroid() const;
RenderWidgetHostViewAndroid* active_rwhva() const { return active_rwhva_; } RenderWidgetHostViewAndroid* active_rwhva() const { return active_rwhva_; }
private: private:
void DoDestroy(WebContentsAndroid* web_contents_android);
RenderWidgetHostConnector* const connector_; RenderWidgetHostConnector* const connector_;
// Active RenderWidgetHostView connected to this instance. Can also point to // Active RenderWidgetHostView connected to this instance. Can also point to
...@@ -94,6 +97,16 @@ void RenderWidgetHostConnector::Observer::DidDetachInterstitialPage() { ...@@ -94,6 +97,16 @@ void RenderWidgetHostConnector::Observer::DidDetachInterstitialPage() {
void RenderWidgetHostConnector::Observer::WebContentsAndroidDestroyed( void RenderWidgetHostConnector::Observer::WebContentsAndroidDestroyed(
WebContentsAndroid* web_contents_android) { WebContentsAndroid* web_contents_android) {
DoDestroy(web_contents_android);
}
void RenderWidgetHostConnector::Observer::DestroyEarly() {
DoDestroy(
static_cast<WebContentsImpl*>(web_contents())->GetWebContentsAndroid());
}
void RenderWidgetHostConnector::Observer::DoDestroy(
WebContentsAndroid* web_contents_android) {
web_contents_android->RemoveDestructionObserver(this); web_contents_android->RemoveDestructionObserver(this);
DCHECK_EQ(active_rwhva_, GetRenderWidgetHostViewAndroid()); DCHECK_EQ(active_rwhva_, GetRenderWidgetHostViewAndroid());
UpdateRenderWidgetHostView(nullptr); UpdateRenderWidgetHostView(nullptr);
...@@ -153,6 +166,10 @@ RenderWidgetHostViewAndroid* RenderWidgetHostConnector::GetRWHVAForTesting() ...@@ -153,6 +166,10 @@ RenderWidgetHostViewAndroid* RenderWidgetHostConnector::GetRWHVAForTesting()
return render_widget_observer_->active_rwhva(); return render_widget_observer_->active_rwhva();
} }
void RenderWidgetHostConnector::DestroyEarly() {
render_widget_observer_->DestroyEarly();
}
WebContents* RenderWidgetHostConnector::web_contents() const { WebContents* RenderWidgetHostConnector::web_contents() const {
return render_widget_observer_->web_contents(); return render_widget_observer_->web_contents();
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#ifndef CONTENT_BROWSER_ANDROID_RENDER_WIDGET_HOST_CONNECTOR_H_ #ifndef CONTENT_BROWSER_ANDROID_RENDER_WIDGET_HOST_CONNECTOR_H_
#define CONTENT_BROWSER_ANDROID_RENDER_WIDGET_HOST_CONNECTOR_H_ #define CONTENT_BROWSER_ANDROID_RENDER_WIDGET_HOST_CONNECTOR_H_
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h" #include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
...@@ -16,7 +17,9 @@ namespace content { ...@@ -16,7 +17,9 @@ namespace content {
// override |UpdateRenderProcessConnection| to set itself to the RWHVA // override |UpdateRenderProcessConnection| to set itself to the RWHVA
// brought up foreground, and null out its reference in the RWHVA going // brought up foreground, and null out its reference in the RWHVA going
// away so it won't access the object any more. // away so it won't access the object any more.
// This class owns itself and gets deleted when the Java WebContents is deleted. // This class owns itself and gets deleted when the WebContents is deleted.
// Can also delete this object early (before the WebContents is destroyed)
// by calling Destroy directly.
class RenderWidgetHostConnector { class RenderWidgetHostConnector {
public: public:
explicit RenderWidgetHostConnector(WebContents* web_contents); explicit RenderWidgetHostConnector(WebContents* web_contents);
...@@ -39,8 +42,14 @@ class RenderWidgetHostConnector { ...@@ -39,8 +42,14 @@ class RenderWidgetHostConnector {
RenderWidgetHostViewAndroid* GetRWHVAForTesting() const; RenderWidgetHostViewAndroid* GetRWHVAForTesting() const;
protected: protected:
FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostConnectorTest, DestroyEarly);
WebContents* web_contents() const; WebContents* web_contents() const;
// Deletes this now. Note this is usually not required as this is
// deleted when the corresponding WebContents is destroyed.
void DestroyEarly();
private: private:
class Observer; class Observer;
std::unique_ptr<Observer> render_widget_observer_; std::unique_ptr<Observer> render_widget_observer_;
......
...@@ -119,4 +119,16 @@ IN_PROC_BROWSER_TEST_F(RenderWidgetHostConnectorTest, ...@@ -119,4 +119,16 @@ IN_PROC_BROWSER_TEST_F(RenderWidgetHostConnectorTest,
EXPECT_EQ(nullptr, connector->GetRWHVAForTesting()); EXPECT_EQ(nullptr, connector->GetRWHVAForTesting());
} }
IN_PROC_BROWSER_TEST_F(RenderWidgetHostConnectorTest, DestroyEarly) {
EXPECT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
RenderWidgetHostViewAndroid* main_rwhva = render_widget_host_view_android();
RenderWidgetHostConnector* connector = render_widget_host_connector();
EXPECT_EQ(main_rwhva, connector->GetRWHVAForTesting());
connector->DestroyEarly();
EXPECT_EQ(nullptr, render_widget_host_connector());
}
} // namespace content } // namespace content
...@@ -43,6 +43,7 @@ import org.chromium.content_public.browser.AccessibilitySnapshotCallback; ...@@ -43,6 +43,7 @@ import org.chromium.content_public.browser.AccessibilitySnapshotCallback;
import org.chromium.content_public.browser.AccessibilitySnapshotNode; import org.chromium.content_public.browser.AccessibilitySnapshotNode;
import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsAccessibility; import org.chromium.content_public.browser.WebContentsAccessibility;
import org.chromium.ui.base.WindowAndroid;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -215,6 +216,17 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider ...@@ -215,6 +216,17 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
mCaptioningController.startListening(); mCaptioningController.startListening();
} }
@Override
public void onWindowAndroidChanged(WindowAndroid windowAndroid) {
// Delete this object when switching between WindowAndroids/Activities.
WindowEventObserverManager.from(mWebContents).removeObserver(this);
mWebContents.removeUserData(WebContentsAccessibilityImpl.class);
if (mNativeObj != 0) {
WebContentsAccessibilityImplJni.get().deleteEarly(mNativeObj);
assert mNativeObj == 0;
}
}
/** /**
* Refresh a11y state with that of {@link AccessibilityManager}. * Refresh a11y state with that of {@link AccessibilityManager}.
*/ */
...@@ -1533,6 +1545,7 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider ...@@ -1533,6 +1545,7 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
@NativeMethods @NativeMethods
interface Natives { interface Natives {
long init(WebContentsAccessibilityImpl caller, WebContents webContents); long init(WebContentsAccessibilityImpl caller, WebContents webContents);
void deleteEarly(long nativeWebContentsAccessibilityAndroid);
void onAutofillPopupDisplayed( void onAutofillPopupDisplayed(
long nativeWebContentsAccessibilityAndroid, WebContentsAccessibilityImpl caller); long nativeWebContentsAccessibilityAndroid, WebContentsAccessibilityImpl caller);
void onAutofillPopupDismissed( void onAutofillPopupDismissed(
......
...@@ -17,6 +17,7 @@ import android.content.pm.ResolveInfo; ...@@ -17,6 +17,7 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.provider.Browser; import android.provider.Browser;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
...@@ -110,6 +111,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -110,6 +111,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
SelectionPopupControllerImpl::new; SelectionPopupControllerImpl::new;
} }
private final Handler mHandler;
private Context mContext; private Context mContext;
private WindowAndroid mWindowAndroid; private WindowAndroid mWindowAndroid;
private WebContentsImpl mWebContents; private WebContentsImpl mWebContents;
...@@ -128,6 +130,8 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -128,6 +130,8 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
// required because ActionMode only exposes a temporary hide routine. // required because ActionMode only exposes a temporary hide routine.
private Runnable mRepeatingHideRunnable; private Runnable mRepeatingHideRunnable;
// Can be null temporarily when switching between WindowAndroid.
@Nullable
private View mView; private View mView;
private ActionMode mActionMode; private ActionMode mActionMode;
...@@ -236,6 +240,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -236,6 +240,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
private SelectionPopupControllerImpl( private SelectionPopupControllerImpl(
WebContents webContents, PopupController popupController, boolean initializeNative) { WebContents webContents, PopupController popupController, boolean initializeNative) {
mHandler = new Handler();
mWebContents = (WebContentsImpl) webContents; mWebContents = (WebContentsImpl) webContents;
mPopupController = popupController; mPopupController = popupController;
mContext = mWebContents.getContext(); mContext = mWebContents.getContext();
...@@ -254,7 +259,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -254,7 +259,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
assert mHidden; assert mHidden;
final long hideDuration = getDefaultHideDuration(); final long hideDuration = getDefaultHideDuration();
// Ensure the next hide call occurs before the ActionMode reappears. // Ensure the next hide call occurs before the ActionMode reappears.
mView.postDelayed(mRepeatingHideRunnable, hideDuration - 1); mHandler.postDelayed(mRepeatingHideRunnable, hideDuration - 1);
hideActionModeTemporarily(hideDuration); hideActionModeTemporarily(hideDuration);
} }
}; };
...@@ -287,14 +292,12 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -287,14 +292,12 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
@Override @Override
public void onUpdateContainerView(ViewGroup view) { public void onUpdateContainerView(ViewGroup view) {
assert view != null;
// Cleans up action mode before switching to a new container view. // Cleans up action mode before switching to a new container view.
if (isActionModeValid()) finishActionMode(); if (isActionModeValid()) finishActionMode();
mUnselectAllOnDismiss = true; mUnselectAllOnDismiss = true;
destroyPastePopup(); destroyPastePopup();
view.setClickable(true); if (view != null) view.setClickable(true);
mView = view; mView = view;
initHandleObserver(); initHandleObserver();
} }
...@@ -411,7 +414,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -411,7 +414,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
* <p> If the action mode cannot be created the selection is cleared. * <p> If the action mode cannot be created the selection is cleared.
*/ */
public void showActionModeOrClearOnFailure() { public void showActionModeOrClearOnFailure() {
if (!isActionModeSupported() || !hasSelection()) return; if (!isActionModeSupported() || !hasSelection() || mView == null) return;
// Just refresh non-floating action mode if it already exists to avoid blinking. // Just refresh non-floating action mode if it already exists to avoid blinking.
if (isActionModeValid() && !isFloatingActionMode()) { if (isActionModeValid() && !isFloatingActionMode()) {
...@@ -442,6 +445,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -442,6 +445,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
} }
private ActionMode startFloatingActionMode() { private ActionMode startFloatingActionMode() {
assert mView != null;
assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
ActionMode actionMode = ContentApiHelperForM.startActionMode(mView, this, mCallback); ActionMode actionMode = ContentApiHelperForM.startActionMode(mView, this, mCallback);
return actionMode; return actionMode;
...@@ -460,7 +464,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -460,7 +464,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
} }
private void createAndShowPastePopup() { private void createAndShowPastePopup() {
if (mView.getParent() == null || mView.getVisibility() != View.VISIBLE) { if (mView == null || mView.getParent() == null || mView.getVisibility() != View.VISIBLE) {
return; return;
} }
...@@ -595,6 +599,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -595,6 +599,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
@Override @Override
public void onWindowAndroidChanged(WindowAndroid newWindowAndroid) { public void onWindowAndroidChanged(WindowAndroid newWindowAndroid) {
mWindowAndroid = newWindowAndroid; mWindowAndroid = newWindowAndroid;
mContext = mWebContents.getContext();
initHandleObserver(); initHandleObserver();
destroyPastePopup(); destroyPastePopup();
} }
...@@ -653,7 +658,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -653,7 +658,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
if (mHidden) { if (mHidden) {
mRepeatingHideRunnable.run(); mRepeatingHideRunnable.run();
} else { } else {
mView.removeCallbacks(mRepeatingHideRunnable); mHandler.removeCallbacks(mRepeatingHideRunnable);
// To show the action mode that is being hidden call hide() again with a short delay. // To show the action mode that is being hidden call hide() again with a short delay.
hideActionModeTemporarily(SHOW_DELAY_MS); hideActionModeTemporarily(SHOW_DELAY_MS);
} }
...@@ -880,6 +885,8 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -880,6 +885,8 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
@Override @Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Actions should only happen when there is a WindowAndroid so mView should not be null.
assert mView != null;
if (!isActionModeValid()) return true; if (!isActionModeValid()) return true;
int id = item.getItemId(); int id = item.getItemId();
...@@ -1001,6 +1008,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -1001,6 +1008,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
*/ */
@VisibleForTesting @VisibleForTesting
void doAssistAction() { void doAssistAction() {
assert mView != null;
if (mClassificationResult == null || !mClassificationResult.hasNamedAction()) return; if (mClassificationResult == null || !mClassificationResult.hasNamedAction()) return;
assert mClassificationResult.onClickListener != null assert mClassificationResult.onClickListener != null
...@@ -1379,7 +1387,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper ...@@ -1379,7 +1387,7 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
@VisibleForTesting @VisibleForTesting
/* package */ void performHapticFeedback() { /* package */ void performHapticFeedback() {
if (BuildInfo.isAtLeastQ()) { if (BuildInfo.isAtLeastQ() && mView != null) {
mView.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE); mView.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE);
} }
} }
......
...@@ -922,6 +922,12 @@ public class WebContentsImpl implements WebContents, RenderFrameHostDelegate, Wi ...@@ -922,6 +922,12 @@ public class WebContentsImpl implements WebContents, RenderFrameHostDelegate, Wi
return key.cast(data); return key.cast(data);
} }
public <T extends UserData> void removeUserData(Class<T> key) {
UserDataHost userDataHost = getUserDataHost();
if (userDataHost == null) return;
userDataHost.removeUserData(key);
}
/** /**
* @return {@code UserDataHost} that contains internal user data. {@code null} if * @return {@code UserDataHost} that contains internal user data. {@code null} if
* it is already gc'ed. * it is already gc'ed.
......
...@@ -12,6 +12,7 @@ import android.content.Intent; ...@@ -12,6 +12,7 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule; import android.support.test.rule.ActivityTestRule;
import android.support.v4.app.Fragment;
import android.text.TextUtils; import android.text.TextUtils;
import org.json.JSONException; import org.json.JSONException;
...@@ -251,4 +252,12 @@ public class InstrumentationActivityTestRule extends ActivityTestRule<Instrument ...@@ -251,4 +252,12 @@ public class InstrumentationActivityTestRule extends ActivityTestRule<Instrument
public String getTestDataURL(String path) { public String getTestDataURL(String path) {
return getTestServer().getURL("/weblayer/test/data/" + path); return getTestServer().getURL("/weblayer/test/data/" + path);
} }
public void setRetainInstance(boolean retain) {
TestThreadUtils.runOnUiThreadBlocking(() -> getActivity().setRetainInstance(retain));
}
public Fragment getFragment() {
return TestThreadUtils.runOnUiThreadBlockingNoException(() -> getActivity().getFragment());
}
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package org.chromium.weblayer.test; package org.chromium.weblayer.test;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.v4.app.Fragment;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
...@@ -86,4 +87,38 @@ public class SmokeTest { ...@@ -86,4 +87,38 @@ public class SmokeTest {
} }
}); });
} }
@Test
@SmallTest
public void testSetRetainInstance() {
ReferenceQueue<InstrumentationActivity> referenceQueue = new ReferenceQueue<>();
PhantomReference<InstrumentationActivity> reference;
{
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
mActivityTestRule.setRetainInstance(true);
Fragment firstFragment = mActivityTestRule.getFragment();
mActivityTestRule.recreateActivity();
Fragment secondFragment = mActivityTestRule.getFragment();
Assert.assertEquals(firstFragment, secondFragment);
boolean destroyed =
TestThreadUtils.runOnUiThreadBlockingNoException(() -> activity.isDestroyed());
Assert.assertTrue(destroyed);
reference = new PhantomReference<>(activity, referenceQueue);
}
CriteriaHelper.pollInstrumentationThread(new Criteria() {
@Override
public boolean isSatisfied() {
Reference enqueuedReference = referenceQueue.poll();
if (enqueuedReference == null) {
Runtime.getRuntime().gc();
return false;
}
Assert.assertEquals(reference, enqueuedReference);
return true;
}
});
}
} }
...@@ -33,6 +33,7 @@ public class BrowserImpl extends IBrowser.Stub { ...@@ -33,6 +33,7 @@ public class BrowserImpl extends IBrowser.Stub {
private BrowserViewController mViewController; private BrowserViewController mViewController;
private FragmentWindowAndroid mWindowAndroid; private FragmentWindowAndroid mWindowAndroid;
private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>(); private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
private TabImpl mActiveTab;
private IBrowserClient mClient; private IBrowserClient mClient;
private LocaleChangedBroadcastReceiver mLocaleReceiver; private LocaleChangedBroadcastReceiver mLocaleReceiver;
...@@ -46,22 +47,31 @@ public class BrowserImpl extends IBrowser.Stub { ...@@ -46,22 +47,31 @@ public class BrowserImpl extends IBrowser.Stub {
} }
public ViewGroup getViewAndroidDelegateContainerView() { public ViewGroup getViewAndroidDelegateContainerView() {
if (mViewController == null) return null;
return mViewController.getContentView(); return mViewController.getContentView();
} }
public void onFragmentAttached(Context context, FragmentWindowAndroid windowAndroid) { public void onFragmentAttached(Context context, FragmentWindowAndroid windowAndroid) {
assert mWindowAndroid == null;
assert mViewController == null;
mWindowAndroid = windowAndroid; mWindowAndroid = windowAndroid;
mViewController = new BrowserViewController(context, windowAndroid); mViewController = new BrowserViewController(context, windowAndroid);
TabImpl tab = new TabImpl(mProfile, windowAndroid);
addTab(tab);
boolean set_active_result = setActiveTab(tab);
assert set_active_result;
mLocaleReceiver = new LocaleChangedBroadcastReceiver(context); mLocaleReceiver = new LocaleChangedBroadcastReceiver(context);
if (mTabs.isEmpty()) {
TabImpl tab = new TabImpl(mProfile, windowAndroid);
addTab(tab);
boolean set_active_result = setActiveTab(tab);
assert set_active_result;
} else {
updateAllTabs();
mViewController.setActiveTab(mActiveTab);
}
} }
public void onFragmentDetached() { public void onFragmentDetached() {
destroy(); // For now we don't retain anything between detach and attach. destroyAttachmentState();
updateAllTabs();
} }
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
...@@ -144,6 +154,7 @@ public class BrowserImpl extends IBrowser.Stub { ...@@ -144,6 +154,7 @@ public class BrowserImpl extends IBrowser.Stub {
public boolean setActiveTab(ITab controller) { public boolean setActiveTab(ITab controller) {
StrictModeWorkaround.apply(); StrictModeWorkaround.apply();
TabImpl tab = (TabImpl) controller; TabImpl tab = (TabImpl) controller;
mActiveTab = tab;
if (tab != null && tab.getBrowser() != this) return false; if (tab != null && tab.getBrowser() != this) return false;
mViewController.setActiveTab(tab); mViewController.setActiveTab(tab);
try { try {
...@@ -157,7 +168,7 @@ public class BrowserImpl extends IBrowser.Stub { ...@@ -157,7 +168,7 @@ public class BrowserImpl extends IBrowser.Stub {
} }
public TabImpl getActiveTab() { public TabImpl getActiveTab() {
return mViewController.getTab(); return mActiveTab;
} }
@Override @Override
...@@ -190,15 +201,21 @@ public class BrowserImpl extends IBrowser.Stub { ...@@ -190,15 +201,21 @@ public class BrowserImpl extends IBrowser.Stub {
} }
public void destroy() { public void destroy() {
setActiveTab(null);
for (TabImpl tab : mTabs) {
tab.destroy();
}
mTabs.clear();
destroyAttachmentState();
}
private void destroyAttachmentState() {
if (mLocaleReceiver != null) { if (mLocaleReceiver != null) {
mLocaleReceiver.destroy(); mLocaleReceiver.destroy();
mLocaleReceiver = null; mLocaleReceiver = null;
} }
if (mViewController != null) { if (mViewController != null) {
mViewController.destroy(); mViewController.destroy();
for (TabImpl tab : mTabs) {
tab.destroy();
}
mViewController = null; mViewController = null;
} }
if (mWindowAndroid != null) { if (mWindowAndroid != null) {
...@@ -206,4 +223,10 @@ public class BrowserImpl extends IBrowser.Stub { ...@@ -206,4 +223,10 @@ public class BrowserImpl extends IBrowser.Stub {
mWindowAndroid = null; mWindowAndroid = null;
} }
} }
private void updateAllTabs() {
for (TabImpl tab : mTabs) {
tab.updateFromBrowser();
}
}
} }
...@@ -115,12 +115,16 @@ public final class TabImpl extends ITab.Stub { ...@@ -115,12 +115,16 @@ public final class TabImpl extends ITab.Stub {
*/ */
public void attachToBrowser(BrowserImpl browser) { public void attachToBrowser(BrowserImpl browser) {
mBrowser = browser; mBrowser = browser;
mWebContents.setTopLevelNativeWindow(browser.getWindowAndroid()); updateFromBrowser();
mViewAndroidDelegate.setContainerView(browser.getViewAndroidDelegateContainerView());
SelectionPopupController.fromWebContents(mWebContents) SelectionPopupController.fromWebContents(mWebContents)
.setActionModeCallback(new ActionModeCallback(mWebContents)); .setActionModeCallback(new ActionModeCallback(mWebContents));
} }
public void updateFromBrowser() {
mWebContents.setTopLevelNativeWindow(mBrowser.getWindowAndroid());
mViewAndroidDelegate.setContainerView(mBrowser.getViewAndroidDelegateContainerView());
}
public BrowserImpl getBrowser() { public BrowserImpl getBrowser() {
return mBrowser; return mBrowser;
} }
......
...@@ -35,6 +35,7 @@ import java.util.List; ...@@ -35,6 +35,7 @@ import java.util.List;
*/ */
public class InstrumentationActivity extends FragmentActivity { public class InstrumentationActivity extends FragmentActivity {
private static final String TAG = "WLInstrumentation"; private static final String TAG = "WLInstrumentation";
private static final String KEY_MAIN_VIEW_ID = "mainViewId";
public static final String EXTRA_PROFILE_NAME = "EXTRA_PROFILE_NAME"; public static final String EXTRA_PROFILE_NAME = "EXTRA_PROFILE_NAME";
...@@ -43,6 +44,7 @@ public class InstrumentationActivity extends FragmentActivity { ...@@ -43,6 +44,7 @@ public class InstrumentationActivity extends FragmentActivity {
public static final String EXTRA_CREATE_WEBLAYER = "EXTRA_CREATE_WEBLAYER"; public static final String EXTRA_CREATE_WEBLAYER = "EXTRA_CREATE_WEBLAYER";
private Profile mProfile; private Profile mProfile;
private Fragment mFragment;
private Browser mBrowser; private Browser mBrowser;
private Tab mTab; private Tab mTab;
private EditText mUrlView; private EditText mUrlView;
...@@ -51,11 +53,16 @@ public class InstrumentationActivity extends FragmentActivity { ...@@ -51,11 +53,16 @@ public class InstrumentationActivity extends FragmentActivity {
private ViewGroup mTopContentsContainer; private ViewGroup mTopContentsContainer;
private IntentInterceptor mIntentInterceptor; private IntentInterceptor mIntentInterceptor;
private Bundle mSavedInstanceState; private Bundle mSavedInstanceState;
private TabCallback mTabCallback;
public Tab getTab() { public Tab getTab() {
return mTab; return mTab;
} }
public Fragment getFragment() {
return mFragment;
}
public Browser getBrowser() { public Browser getBrowser() {
return mBrowser; return mBrowser;
} }
...@@ -88,7 +95,11 @@ public class InstrumentationActivity extends FragmentActivity { ...@@ -88,7 +95,11 @@ public class InstrumentationActivity extends FragmentActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mSavedInstanceState = savedInstanceState; mSavedInstanceState = savedInstanceState;
LinearLayout mainView = new LinearLayout(this); LinearLayout mainView = new LinearLayout(this);
mMainViewId = View.generateViewId(); if (savedInstanceState == null) {
mMainViewId = View.generateViewId();
} else {
mMainViewId = savedInstanceState.getInt(KEY_MAIN_VIEW_ID);
}
mainView.setId(mMainViewId); mainView.setId(mMainViewId);
mMainView = mainView; mMainView = mainView;
setContentView(mainView); setContentView(mainView);
...@@ -117,6 +128,23 @@ public class InstrumentationActivity extends FragmentActivity { ...@@ -117,6 +128,23 @@ public class InstrumentationActivity extends FragmentActivity {
} }
} }
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// When restoring Fragments, FragmentManager tries to put them in the containers with same
// ids as before.
outState.putInt(KEY_MAIN_VIEW_ID, mMainViewId);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mTabCallback != null) {
mTab.unregisterTabCallback(mTabCallback);
mTabCallback = null;
}
}
private void createWebLayerAsync() { private void createWebLayerAsync() {
try { try {
WebLayer.loadAsync(getApplicationContext(), webLayer -> onWebLayerReady()); WebLayer.loadAsync(getApplicationContext(), webLayer -> onWebLayerReady());
...@@ -138,19 +166,20 @@ public class InstrumentationActivity extends FragmentActivity { ...@@ -138,19 +166,20 @@ public class InstrumentationActivity extends FragmentActivity {
private void onWebLayerReady() { private void onWebLayerReady() {
if (mBrowser != null || isFinishing() || isDestroyed()) return; if (mBrowser != null || isFinishing() || isDestroyed()) return;
Fragment fragment = getOrCreateBrowserFragment(); mFragment = getOrCreateBrowserFragment();
mBrowser = Browser.fromFragment(fragment); mBrowser = Browser.fromFragment(mFragment);
mProfile = mBrowser.getProfile(); mProfile = mBrowser.getProfile();
mBrowser.setTopView(mTopContentsContainer); mBrowser.setTopView(mTopContentsContainer);
mTab = mBrowser.getActiveTab(); mTab = mBrowser.getActiveTab();
mTab.registerTabCallback(new TabCallback() { mTabCallback = new TabCallback() {
@Override @Override
public void onVisibleUriChanged(Uri uri) { public void onVisibleUriChanged(Uri uri) {
mUrlView.setText(uri.toString()); mUrlView.setText(uri.toString());
} }
}); };
mTab.registerTabCallback(mTabCallback);
} }
private Fragment getOrCreateBrowserFragment() { private Fragment getOrCreateBrowserFragment() {
...@@ -189,6 +218,10 @@ public class InstrumentationActivity extends FragmentActivity { ...@@ -189,6 +218,10 @@ public class InstrumentationActivity extends FragmentActivity {
mUrlView.clearFocus(); mUrlView.clearFocus();
} }
public void setRetainInstance(boolean retain) {
mFragment.setRetainInstance(retain);
}
private static String getUrlFromIntent(Intent intent) { private static String getUrlFromIntent(Intent intent) {
return intent != null ? intent.getDataString() : null; return intent != null ? intent.getDataString() : null;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment