Commit 47d9188e authored by Pavel Shmakov's avatar Pavel Shmakov Committed by Commit Bot

Handle BrowserFragment restoration

When Activity is restarted (either after configuration change, unless
handled manually, or after being killed due to memory pressure), the
system FragmentManager restores all the Fragments during Activity's
onCreate event. The current APIs make it impossible to properly
create a BrowserFragment without an instance of WebLayer, and that
prohibits asynchronous WebLayer initialization.

There are two ways to deal with it:
- Disable it in a hacky way: the embedder runs
savedInstanceState.remove("android:support:fragments");
in their activity prior to super.onCreate(). The
embedder should then manually save and restore the parameters of
each fragment they have.
- Handle the restoration the "correct" way.

More detailed discussion:

This CL enables the second path. This means putting BrowserFragment
on the top of dependency tree, which is opposite to what we have
now (getWebLayer().createProfile().createBrowserFragment()). In
this CL, BrowserFragment is created independently, and later
attached to WebLayer instance.

I suggest making BrowserFragment the only client-side object that
can temporarily exist without the weblayer counterpart.
All other objects should have the same lifecycle on both sides.

This is achieved by making all browser APIs accessible only through
BrowserFragmentController, which in turn can be accessed only after
the embedder attaches BrowserFragment to WebLayer.

More detailed discussion:
https://docs.google.com/document/d/15xCcdO8V1tBYH-UJStmi0aEzLXcUWDkc-KWI_Wb7rdQ

This CL doesn't enable async init of WebLayer after process restart: trying to
retrieve BrowserFragmentController or attach BrowserFragment blocks the thread
if WebLayer hasn't been already initialized, and in case of process restart it
can't have been initialized.

Change-Id: Ibe6fd369e8d60af5cedc256d3ebf1db210b579bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1829082Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Pavel Shmakov <pshmakov@chromium.org>
Cr-Commit-Position: refs/heads/master@{#704695}
parent b49dfeb2
...@@ -9,12 +9,14 @@ android_library("java") { ...@@ -9,12 +9,14 @@ android_library("java") {
java_files = [ java_files = [
"org/chromium/weblayer_private/BrowserControllerImpl.java", "org/chromium/weblayer_private/BrowserControllerImpl.java",
"org/chromium/weblayer_private/BrowserFragmentControllerImpl.java", "org/chromium/weblayer_private/BrowserFragmentControllerImpl.java",
"org/chromium/weblayer_private/BrowserFragmentImpl.java",
"org/chromium/weblayer_private/BrowserObserverProxy.java", "org/chromium/weblayer_private/BrowserObserverProxy.java",
"org/chromium/weblayer_private/ContentView.java", "org/chromium/weblayer_private/ContentView.java",
"org/chromium/weblayer_private/ContentViewRenderView.java", "org/chromium/weblayer_private/ContentViewRenderView.java",
"org/chromium/weblayer_private/NavigationControllerImpl.java", "org/chromium/weblayer_private/NavigationControllerImpl.java",
"org/chromium/weblayer_private/NavigationImpl.java", "org/chromium/weblayer_private/NavigationImpl.java",
"org/chromium/weblayer_private/ProfileImpl.java", "org/chromium/weblayer_private/ProfileImpl.java",
"org/chromium/weblayer_private/ProfileManager.java",
"org/chromium/weblayer_private/TopControlsContainerView.java", "org/chromium/weblayer_private/TopControlsContainerView.java",
"org/chromium/weblayer_private/WebLayerImpl.java", "org/chromium/weblayer_private/WebLayerImpl.java",
"org/chromium/weblayer_private/ChildProcessServiceImpl.java", "org/chromium/weblayer_private/ChildProcessServiceImpl.java",
...@@ -48,6 +50,7 @@ android_library("client_java") { ...@@ -48,6 +50,7 @@ android_library("client_java") {
"org/chromium/weblayer_private/aidl/ObjectWrapper.java", "org/chromium/weblayer_private/aidl/ObjectWrapper.java",
"org/chromium/weblayer_private/aidl/WebLayerVersion.java", "org/chromium/weblayer_private/aidl/WebLayerVersion.java",
"org/chromium/weblayer_private/aidl/APICallException.java", "org/chromium/weblayer_private/aidl/APICallException.java",
"org/chromium/weblayer_private/aidl/BrowserFragmentArgs.java",
] ]
srcjar_deps = [ ":aidl" ] srcjar_deps = [ ":aidl" ]
...@@ -58,6 +61,7 @@ android_aidl("aidl") { ...@@ -58,6 +61,7 @@ android_aidl("aidl") {
sources = [ sources = [
"org/chromium/weblayer_private/aidl/IBrowserController.aidl", "org/chromium/weblayer_private/aidl/IBrowserController.aidl",
"org/chromium/weblayer_private/aidl/IBrowserControllerClient.aidl", "org/chromium/weblayer_private/aidl/IBrowserControllerClient.aidl",
"org/chromium/weblayer_private/aidl/IBrowserFragment.aidl",
"org/chromium/weblayer_private/aidl/IBrowserFragmentController.aidl", "org/chromium/weblayer_private/aidl/IBrowserFragmentController.aidl",
"org/chromium/weblayer_private/aidl/IChildProcessService.aidl", "org/chromium/weblayer_private/aidl/IChildProcessService.aidl",
"org/chromium/weblayer_private/aidl/IClientNavigation.aidl", "org/chromium/weblayer_private/aidl/IClientNavigation.aidl",
......
...@@ -130,8 +130,8 @@ public final class BrowserControllerImpl extends IBrowserController.Stub { ...@@ -130,8 +130,8 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
public void destroy() { public void destroy() {
BrowserControllerImplJni.get().setTopControlsContainerView( BrowserControllerImplJni.get().setTopControlsContainerView(
mNativeBrowserController, BrowserControllerImpl.this, 0); mNativeBrowserController, BrowserControllerImpl.this, 0);
mContentViewRenderView.destroy();
mTopControlsContainerView.destroy(); mTopControlsContainerView.destroy();
mContentViewRenderView.destroy();
if (mBrowserObserverProxy != null) mBrowserObserverProxy.destroy(); if (mBrowserObserverProxy != null) mBrowserObserverProxy.destroy();
mBrowserObserverProxy = null; mBrowserObserverProxy = null;
mNavigationController = null; mNavigationController = null;
......
...@@ -4,60 +4,62 @@ ...@@ -4,60 +4,62 @@
package org.chromium.weblayer_private; package org.chromium.weblayer_private;
import android.content.Context;
import android.os.Bundle;
import android.view.View; import android.view.View;
import org.chromium.weblayer_private.aidl.IBrowserController;
import org.chromium.weblayer_private.aidl.IBrowserFragmentController; import org.chromium.weblayer_private.aidl.IBrowserFragmentController;
import org.chromium.weblayer_private.aidl.IObjectWrapper; import org.chromium.weblayer_private.aidl.IObjectWrapper;
import org.chromium.weblayer_private.aidl.IRemoteFragment; import org.chromium.weblayer_private.aidl.IProfile;
import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
/** /**
* Implementation of {@link IBrowserFragmentController}. * Implementation of {@link IBrowserFragmentController}.
*/ */
public class BrowserFragmentControllerImpl extends IBrowserFragmentController.Stub { public class BrowserFragmentControllerImpl extends IBrowserFragmentController.Stub {
private final BrowserControllerImpl mController; private final ProfileImpl mProfile;
private final BrowserRemoteFragmentImpl mRemoteFragmentImpl; private BrowserControllerImpl mTabController;
public BrowserFragmentControllerImpl(BrowserControllerImpl controller, public BrowserFragmentControllerImpl(ProfileImpl profile, Bundle savedInstanceState) {
IRemoteFragmentClient fragmentClient) { mProfile = profile;
mController = controller; // Restore tabs etc from savedInstanceState here.
mRemoteFragmentImpl = new BrowserRemoteFragmentImpl(fragmentClient); }
public void onFragmentAttached(Context context) {
mTabController = new BrowserControllerImpl(context, mProfile);
}
public void onFragmentDetached() {
mTabController.destroy();
mTabController = null;
} }
@Override @Override
public void setTopView(IObjectWrapper view) { public void setTopView(IObjectWrapper view) {
mController.setTopView(view); getBrowserController().setTopView(view);
} }
@Override @Override
public void setSupportsEmbedding(boolean enable, IObjectWrapper valueCallback) { public void setSupportsEmbedding(boolean enable, IObjectWrapper valueCallback) {
mController.setSupportsEmbedding(enable, valueCallback); getBrowserController().setSupportsEmbedding(enable, valueCallback);
} }
@Override @Override
public IRemoteFragment getRemoteFragment() { public BrowserControllerImpl getBrowserController() {
return mRemoteFragmentImpl; if (mTabController == null) {
throw new RuntimeException("Currently BrowserController requires Activity context, so "
+ "it exists only while BrowserFragment is attached to an Activity");
}
return mTabController;
} }
@Override @Override
public IBrowserController getBrowserController() { public IProfile getProfile() {
return mController; return mProfile;
} }
@Override public View getFragmentView() {
public void destroy() { return mTabController.getView();
mController.destroy();
} }
private class BrowserRemoteFragmentImpl extends RemoteFragmentImpl { public void destroy() {}
public BrowserRemoteFragmentImpl(IRemoteFragmentClient client) {
super(client);
}
@Override
public View onCreateView() {
return mController.getView();
}
}
} }
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import org.chromium.weblayer_private.aidl.BrowserFragmentArgs;
import org.chromium.weblayer_private.aidl.IBrowserFragment;
import org.chromium.weblayer_private.aidl.IBrowserFragmentController;
import org.chromium.weblayer_private.aidl.IRemoteFragment;
import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
public class BrowserFragmentImpl extends RemoteFragmentImpl {
private final ProfileImpl mProfile;
private BrowserFragmentControllerImpl mController;
private Context mContext;
public BrowserFragmentImpl(ProfileManager profileManager, IRemoteFragmentClient client,
Bundle fragmentArgs) {
super(client);
mProfile = profileManager.getProfile(fragmentArgs.getString(
BrowserFragmentArgs.PROFILE_PATH));
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
if (mController != null) { // On first creation, onAttach is called before onCreate
mController.onFragmentAttached(context);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mController = new BrowserFragmentControllerImpl(mProfile, savedInstanceState);
if (mContext != null) {
mController.onFragmentAttached(mContext);
}
}
@Override
public View onCreateView() {
return mController.getFragmentView();
}
@Override
public void onDestroy() {
super.onDestroy();
mController.destroy();
mController = null;
}
@Override
public void onDetach() {
super.onDetach();
// mController != null if fragment is retained, otherwise onDestroy is called first.
if (mController != null) {
mController.onFragmentDetached();
}
mContext = null;
}
public IBrowserFragment asIBrowserFragment() {
return new IBrowserFragment.Stub() {
@Override
public IRemoteFragment asRemoteFragment() {
return BrowserFragmentImpl.this;
}
@Override
public IBrowserFragmentController getController() {
if (mController == null) {
throw new RuntimeException("BrowserFragmentController is available only between"
+ " BrowserFragment's onCreate() and onDestroy().");
}
return mController;
}
};
}
}
...@@ -4,28 +4,26 @@ ...@@ -4,28 +4,26 @@
package org.chromium.weblayer_private; package org.chromium.weblayer_private;
import android.content.Context;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods; import org.chromium.base.annotations.NativeMethods;
import org.chromium.weblayer_private.aidl.IBrowserFragmentController;
import org.chromium.weblayer_private.aidl.IObjectWrapper;
import org.chromium.weblayer_private.aidl.IProfile; import org.chromium.weblayer_private.aidl.IProfile;
import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
import org.chromium.weblayer_private.aidl.ObjectWrapper;
@JNINamespace("weblayer") @JNINamespace("weblayer")
public final class ProfileImpl extends IProfile.Stub { public final class ProfileImpl extends IProfile.Stub {
private long mNativeProfile; private long mNativeProfile;
private Runnable mOnDestroyCallback;
public ProfileImpl(String path) { ProfileImpl(String path, Runnable onDestroyCallback) {
mNativeProfile = ProfileImplJni.get().createProfile(path); mNativeProfile = ProfileImplJni.get().createProfile(path);
mOnDestroyCallback = onDestroyCallback;
} }
@Override @Override
public void destroy() { public void destroy() {
ProfileImplJni.get().deleteProfile(mNativeProfile); ProfileImplJni.get().deleteProfile(mNativeProfile);
mNativeProfile = 0; mNativeProfile = 0;
mOnDestroyCallback.run();
mOnDestroyCallback = null;
} }
@Override @Override
...@@ -33,15 +31,6 @@ public final class ProfileImpl extends IProfile.Stub { ...@@ -33,15 +31,6 @@ public final class ProfileImpl extends IProfile.Stub {
ProfileImplJni.get().clearBrowsingData(mNativeProfile); ProfileImplJni.get().clearBrowsingData(mNativeProfile);
} }
@Override
public IBrowserFragmentController createBrowserFragmentController(IRemoteFragmentClient
fragmentClient,
IObjectWrapper context) {
BrowserControllerImpl browserController = new BrowserControllerImpl(
ObjectWrapper.unwrap(context, Context.class), this);
return new BrowserFragmentControllerImpl(browserController, fragmentClient);
}
long getNativeProfile() { long getNativeProfile() {
return mNativeProfile; return mNativeProfile;
} }
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private;
import java.util.HashMap;
import java.util.Map;
/**
* Creates and maintains the active Profiles.
*/
public class ProfileManager {
private final Map<String, ProfileImpl> mProfiles = new HashMap<>();
/** Returns existing or new Profile associated with the given path */
public ProfileImpl getProfile(String path) {
if (path == null) throw new IllegalArgumentException("Path shouldn't be null");
ProfileImpl existingProfile = mProfiles.get(path);
if (existingProfile != null) {
return existingProfile;
}
ProfileImpl profile = new ProfileImpl(path, () -> mProfiles.remove(path));
mProfiles.put(path, profile);
return profile;
}
}
...@@ -52,6 +52,7 @@ class TopControlsContainerView extends FrameLayout { ...@@ -52,6 +52,7 @@ class TopControlsContainerView extends FrameLayout {
// Used to delay updating the image for the layer. // Used to delay updating the image for the layer.
private final Runnable mRefreshResourceIdRunnable = () -> { private final Runnable mRefreshResourceIdRunnable = () -> {
if (mView == null) return;
TopControlsContainerViewJni.get().updateTopControlsResource( TopControlsContainerViewJni.get().updateTopControlsResource(
mNativeTopControlsContainerView, TopControlsContainerView.this); mNativeTopControlsContainerView, TopControlsContainerView.this);
}; };
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package org.chromium.weblayer_private; package org.chromium.weblayer_private;
import android.content.Context; import android.content.Context;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
...@@ -18,8 +19,9 @@ import org.chromium.content_public.browser.BrowserStartupController; ...@@ -18,8 +19,9 @@ import org.chromium.content_public.browser.BrowserStartupController;
import org.chromium.content_public.browser.ChildProcessCreationParams; import org.chromium.content_public.browser.ChildProcessCreationParams;
import org.chromium.content_public.browser.DeviceUtils; import org.chromium.content_public.browser.DeviceUtils;
import org.chromium.ui.base.ResourceBundle; import org.chromium.ui.base.ResourceBundle;
import org.chromium.weblayer_private.aidl.IBrowserFragment;
import org.chromium.weblayer_private.aidl.IObjectWrapper; import org.chromium.weblayer_private.aidl.IObjectWrapper;
import org.chromium.weblayer_private.aidl.IProfile; import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
import org.chromium.weblayer_private.aidl.IWebLayer; import org.chromium.weblayer_private.aidl.IWebLayer;
import org.chromium.weblayer_private.aidl.ObjectWrapper; import org.chromium.weblayer_private.aidl.ObjectWrapper;
import org.chromium.weblayer_private.aidl.WebLayerVersion; import org.chromium.weblayer_private.aidl.WebLayerVersion;
...@@ -32,6 +34,8 @@ public final class WebLayerImpl extends IWebLayer.Stub { ...@@ -32,6 +34,8 @@ public final class WebLayerImpl extends IWebLayer.Stub {
// TODO: Configure this from the client. // TODO: Configure this from the client.
private static final String COMMAND_LINE_FILE = "/data/local/tmp/weblayer-command-line"; private static final String COMMAND_LINE_FILE = "/data/local/tmp/weblayer-command-line";
private final ProfileManager mProfileManager = new ProfileManager();
@UsedByReflection("WebLayer") @UsedByReflection("WebLayer")
public static IBinder create() { public static IBinder create() {
return new WebLayerImpl(); return new WebLayerImpl();
...@@ -47,11 +51,6 @@ public final class WebLayerImpl extends IWebLayer.Stub { ...@@ -47,11 +51,6 @@ public final class WebLayerImpl extends IWebLayer.Stub {
return clientVersion == WebLayerVersion.sVersionNumber; return clientVersion == WebLayerVersion.sVersionNumber;
} }
@Override
public IProfile createProfile(String path) {
return new ProfileImpl(path);
}
@Override @Override
public void initAndLoadAsync( public void initAndLoadAsync(
IObjectWrapper webLayerContextWrapper, IObjectWrapper loadedCallbackWrapper) { IObjectWrapper webLayerContextWrapper, IObjectWrapper loadedCallbackWrapper) {
...@@ -97,4 +96,13 @@ public final class WebLayerImpl extends IWebLayer.Stub { ...@@ -97,4 +96,13 @@ public final class WebLayerImpl extends IWebLayer.Stub {
.startBrowserProcessesSync( .startBrowserProcessesSync(
/* singleProcess*/ false); /* singleProcess*/ false);
} }
@Override
public IBrowserFragment createBrowserFragmentImpl(IRemoteFragmentClient fragmentClient,
IObjectWrapper fragmentArgs) {
Bundle unwrappedArgs = ObjectWrapper.unwrap(fragmentArgs, Bundle.class);
BrowserFragmentImpl fragment = new BrowserFragmentImpl(mProfileManager, fragmentClient,
unwrappedArgs);
return fragment.asIBrowserFragment();
}
} }
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private.aidl;
/** Keys for the Bundle of arguments with which BrowserFragments are created. */
public interface BrowserFragmentArgs {
String PROFILE_PATH = "profile_path";
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private.aidl;
import org.chromium.weblayer_private.aidl.IBrowserFragmentController;
import org.chromium.weblayer_private.aidl.IRemoteFragment;
interface IBrowserFragment {
IRemoteFragment asRemoteFragment() = 0;
IBrowserFragmentController getController() = 1;
}
...@@ -6,12 +6,10 @@ package org.chromium.weblayer_private.aidl; ...@@ -6,12 +6,10 @@ package org.chromium.weblayer_private.aidl;
import org.chromium.weblayer_private.aidl.IBrowserController; import org.chromium.weblayer_private.aidl.IBrowserController;
import org.chromium.weblayer_private.aidl.IObjectWrapper; import org.chromium.weblayer_private.aidl.IObjectWrapper;
import org.chromium.weblayer_private.aidl.IRemoteFragment;
interface IBrowserFragmentController { interface IBrowserFragmentController {
void destroy() = 0; IProfile getProfile() = 0;
IBrowserController getBrowserController() = 1; IBrowserController getBrowserController() = 1;
IRemoteFragment getRemoteFragment() = 2;
void setTopView(IObjectWrapper view) = 3; void setTopView(IObjectWrapper view) = 3;
// |valueCallback| is a wrapped ValueCallback<Boolean> instead. The bool value in |valueCallback| // |valueCallback| is a wrapped ValueCallback<Boolean> instead. The bool value in |valueCallback|
......
...@@ -4,21 +4,8 @@ ...@@ -4,21 +4,8 @@
package org.chromium.weblayer_private.aidl; package org.chromium.weblayer_private.aidl;
import org.chromium.weblayer_private.aidl.IBrowserFragmentController;
import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
import org.chromium.weblayer_private.aidl.IObjectWrapper;
interface IProfile { interface IProfile {
void destroy() = 0; void destroy() = 0;
void clearBrowsingData() = 1; void clearBrowsingData() = 1;
/**
* Creates a new IBrowserFragmentController.
* @param fragmentClient IRemoteFragmentClient that will host the Fragment implemented on the
* weblayer side.
* @param context Context that refers the the weblayer implementation
*/
IBrowserFragmentController createBrowserFragmentController(
in IRemoteFragmentClient fragmentClient, in IObjectWrapper context) = 2;
} }
...@@ -4,12 +4,13 @@ ...@@ -4,12 +4,13 @@
package org.chromium.weblayer_private.aidl; package org.chromium.weblayer_private.aidl;
import android.os.Bundle;
import org.chromium.weblayer_private.aidl.IObjectWrapper; import org.chromium.weblayer_private.aidl.IObjectWrapper;
import org.chromium.weblayer_private.aidl.IProfile; import org.chromium.weblayer_private.aidl.IBrowserFragment;
import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
interface IWebLayer { interface IWebLayer {
IProfile createProfile(in String path) = 0;
// Initializes WebLayer and starts loading. It is expected that is called // Initializes WebLayer and starts loading. It is expected that is called
// before anything else. |loadedCallback| is a ValueCallback that is called // before anything else. |loadedCallback| is a ValueCallback that is called
// when load completes. |webLayerContext| is a Context that refers to the // when load completes. |webLayerContext| is a Context that refers to the
...@@ -19,4 +20,13 @@ interface IWebLayer { ...@@ -19,4 +20,13 @@ interface IWebLayer {
// Blocks until loading has completed. // Blocks until loading has completed.
void loadSync() = 2; void loadSync() = 2;
// Creates the WebLayer counterpart to a BrowserFragment - a BrowserFragmentImpl
//
// @param fragmentClient Representative of the Fragment on the client side through which
// WebLayer can call methods on Fragment.
// @param fragmentArgs Bundle of arguments with which the Fragment was created on the client side
// (see Fragment#setArguments).
IBrowserFragment createBrowserFragmentImpl(in IRemoteFragmentClient fragmentClient,
in IObjectWrapper fragmentArgs) = 3;
} }
...@@ -34,6 +34,7 @@ template("weblayer_java") { ...@@ -34,6 +34,7 @@ template("weblayer_java") {
"org/chromium/weblayer/NavigationObserver.java", "org/chromium/weblayer/NavigationObserver.java",
"org/chromium/weblayer/ObserverList.java", "org/chromium/weblayer/ObserverList.java",
"org/chromium/weblayer/Profile.java", "org/chromium/weblayer/Profile.java",
"org/chromium/weblayer/ProfileManager.java",
"org/chromium/weblayer/WebLayer.java", "org/chromium/weblayer/WebLayer.java",
"org/chromium/weblayer/ChildProcessService.java", "org/chromium/weblayer/ChildProcessService.java",
"org/chromium/weblayer/UnsupportedVersionException.java", "org/chromium/weblayer/UnsupportedVersionException.java",
......
...@@ -13,13 +13,27 @@ import android.view.View; ...@@ -13,13 +13,27 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.chromium.weblayer_private.aidl.APICallException; import org.chromium.weblayer_private.aidl.APICallException;
import org.chromium.weblayer_private.aidl.IBrowserFragment;
import org.chromium.weblayer_private.aidl.IObjectWrapper; import org.chromium.weblayer_private.aidl.IObjectWrapper;
import org.chromium.weblayer_private.aidl.IRemoteFragment;
import org.chromium.weblayer_private.aidl.IRemoteFragmentClient; import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
import org.chromium.weblayer_private.aidl.ObjectWrapper; import org.chromium.weblayer_private.aidl.ObjectWrapper;
import java.util.concurrent.Future;
/** /**
* WebLayer's fragment implementation. * WebLayer's fragment implementation.
*
* All the browser APIs, such as loading pages can be accessed via
* {@link BrowserFragmentController}, which can be retrieved with {@link #getController} after
* the fragment received onCreate the call.
*
* Attaching a BrowserFragment to an Activity requires WebLayer to be initialized, so
* BrowserFragment will block the thread in onAttach until it's done. To prevent this,
* asynchronously "pre-warm" WebLayer using {@link WebLayer#create} prior to using BrowserFragments.
*
* Unfortunately, when the system restores the BrowserFragment after killing the process, it
* attaches the fragment immediately on activity's onCreate event, so there is currently no way to
* asynchronously init WebLayer in that case.
*/ */
public final class BrowserFragment extends Fragment { public final class BrowserFragment extends Fragment {
private final IRemoteFragmentClient mClientImpl = new IRemoteFragmentClient.Stub() { private final IRemoteFragmentClient mClientImpl = new IRemoteFragmentClient.Stub() {
...@@ -84,42 +98,83 @@ public final class BrowserFragment extends Fragment { ...@@ -84,42 +98,83 @@ public final class BrowserFragment extends Fragment {
} }
}; };
private IRemoteFragment mRemoteFragment; // Nonnull after first onAttach().
private IBrowserFragment mImpl;
private WebLayer mWebLayer;
// Nonnull between onCreate() and onDestroy().
private BrowserFragmentController mBrowserFragmentController;
// TODO(pshmakov): how do we deal with FragmentManager restoring this Fragment on its own? /**
/* package */ void setRemoteFragment(IRemoteFragment remoteFragment) { * This constructor is for the system FragmentManager only. Please use
mRemoteFragment = remoteFragment; * {@link WebLayer#createBrowserFragment}.
*/
public BrowserFragment() {
super();
} }
/* package */ IRemoteFragmentClient asIRemoteFragmentClient() { /**
return mClientImpl; * Returns the {@link BrowserFragmentController} associated with this fragment.
* The controller is available only between BrowserFragment's onCreate() and onDestroy().
*/
public BrowserFragmentController getController() {
if (mBrowserFragmentController == null) {
throw new RuntimeException("BrowserFragmentController is available only between "
+ "BrowserFragment's onCreate() and onDestroy().");
}
return mBrowserFragmentController;
} }
@SuppressWarnings("MissingSuperCall")
@Override @Override
public View onCreateView( public void onAttach(Context context) {
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // This is the first lifecycle event and also the first time we can get app context (unless
// the embedder has already called getController). So it's the latest and at the same time
// the earliest moment when we can initialize WebLayer without missing any lifecycle events.
ensureConnectedToWebLayer(context.getApplicationContext());
try { try {
return ObjectWrapper.unwrap(mRemoteFragment.handleOnCreateView(), View.class); mImpl.asRemoteFragment().handleOnAttach(ObjectWrapper.wrap(context));
// handleOnAttach results in creating BrowserFragmentControllerImpl on the other side.
// Now we retrieve it, and build BrowserFragmentController on this side.
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
} }
private void ensureConnectedToWebLayer(Context appContext) {
if (mImpl != null) {
return; // Already initialized.
}
Bundle args = getArguments();
if (args == null) {
throw new RuntimeException("BrowserFragment was created without arguments.");
}
try {
Future<WebLayer> future = WebLayer.create(appContext);
mWebLayer = future.get();
mImpl = mWebLayer.connectFragment(mClientImpl, args);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize WebLayer", e);
}
}
@SuppressWarnings("MissingSuperCall") @SuppressWarnings("MissingSuperCall")
@Override @Override
public void onAttach(Context context) { public void onCreate(Bundle savedInstanceState) {
try { try {
mRemoteFragment.handleOnAttach(ObjectWrapper.wrap(context)); mImpl.asRemoteFragment().handleOnCreate(ObjectWrapper.wrap(savedInstanceState));
mBrowserFragmentController = new BrowserFragmentController(mImpl.getController(),
mWebLayer.getProfileManager());
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
} }
@SuppressWarnings("MissingSuperCall")
@Override @Override
public void onCreate(Bundle savedInstanceState) { public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
try { try {
mRemoteFragment.handleOnCreate(ObjectWrapper.wrap(savedInstanceState)); return ObjectWrapper.unwrap(mImpl.asRemoteFragment().handleOnCreateView(), View.class);
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -129,7 +184,8 @@ public final class BrowserFragment extends Fragment { ...@@ -129,7 +184,8 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
try { try {
mRemoteFragment.handleOnActivityCreated(ObjectWrapper.wrap(savedInstanceState)); mImpl.asRemoteFragment().handleOnActivityCreated(
ObjectWrapper.wrap(savedInstanceState));
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -139,7 +195,7 @@ public final class BrowserFragment extends Fragment { ...@@ -139,7 +195,7 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onStart() { public void onStart() {
try { try {
mRemoteFragment.handleOnStart(); mImpl.asRemoteFragment().handleOnStart();
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -149,7 +205,7 @@ public final class BrowserFragment extends Fragment { ...@@ -149,7 +205,7 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onResume() { public void onResume() {
try { try {
mRemoteFragment.handleOnResume(); mImpl.asRemoteFragment().handleOnResume();
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -159,7 +215,7 @@ public final class BrowserFragment extends Fragment { ...@@ -159,7 +215,7 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
try { try {
mRemoteFragment.handleOnSaveInstanceState(ObjectWrapper.wrap(outState)); mImpl.asRemoteFragment().handleOnSaveInstanceState(ObjectWrapper.wrap(outState));
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -169,7 +225,7 @@ public final class BrowserFragment extends Fragment { ...@@ -169,7 +225,7 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onPause() { public void onPause() {
try { try {
mRemoteFragment.handleOnPause(); mImpl.asRemoteFragment().handleOnPause();
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -179,7 +235,7 @@ public final class BrowserFragment extends Fragment { ...@@ -179,7 +235,7 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onStop() { public void onStop() {
try { try {
mRemoteFragment.handleOnStop(); mImpl.asRemoteFragment().handleOnStop();
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -189,7 +245,7 @@ public final class BrowserFragment extends Fragment { ...@@ -189,7 +245,7 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onDestroyView() { public void onDestroyView() {
try { try {
mRemoteFragment.handleOnDestroyView(); mImpl.asRemoteFragment().handleOnDestroyView();
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
...@@ -199,17 +255,19 @@ public final class BrowserFragment extends Fragment { ...@@ -199,17 +255,19 @@ public final class BrowserFragment extends Fragment {
@Override @Override
public void onDestroy() { public void onDestroy() {
try { try {
mRemoteFragment.handleOnDestroy(); mImpl.asRemoteFragment().handleOnDestroy();
// The other side does the clean up automatically in handleOnDestroy()
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
mBrowserFragmentController = null;
} }
@SuppressWarnings("MissingSuperCall") @SuppressWarnings("MissingSuperCall")
@Override @Override
public void onDetach() { public void onDetach() {
try { try {
mRemoteFragment.handleOnDetach(); mImpl.asRemoteFragment().handleOnDetach();
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package org.chromium.weblayer; package org.chromium.weblayer;
import android.os.RemoteException; import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.view.View; import android.view.View;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
...@@ -18,20 +17,13 @@ import org.chromium.weblayer_private.aidl.ObjectWrapper; ...@@ -18,20 +17,13 @@ import org.chromium.weblayer_private.aidl.ObjectWrapper;
*/ */
public final class BrowserFragmentController { public final class BrowserFragmentController {
private final IBrowserFragmentController mImpl; private final IBrowserFragmentController mImpl;
private final BrowserFragment mFragment; private final ProfileManager mProfileManager;
private BrowserController mController; private BrowserController mController;
BrowserFragmentController(IBrowserFragmentController impl, BrowserFragment fragment) {
mImpl = impl;
mFragment = fragment;
}
public void destroy() { BrowserFragmentController(IBrowserFragmentController impl, ProfileManager profileManager) {
try { mImpl = impl;
mImpl.destroy(); mProfileManager = profileManager;
} catch (RemoteException e) {
throw new APICallException(e);
}
} }
// TODO(pshmakov): rename this to BrowserTabController. // TODO(pshmakov): rename this to BrowserTabController.
...@@ -46,10 +38,6 @@ public final class BrowserFragmentController { ...@@ -46,10 +38,6 @@ public final class BrowserFragmentController {
return mController; return mController;
} }
public Fragment getFragment() {
return mFragment;
}
public void setTopView(View view) { public void setTopView(View view) {
try { try {
mImpl.setTopView(ObjectWrapper.wrap(view)); mImpl.setTopView(ObjectWrapper.wrap(view));
...@@ -82,4 +70,16 @@ public final class BrowserFragmentController { ...@@ -82,4 +70,16 @@ public final class BrowserFragmentController {
throw new APICallException(e); throw new APICallException(e);
} }
} }
/**
* Returns {@link Profile} associated with this Browser Fragment. Multiple fragments can share
* the same Profile.
*/
public Profile getProfile() {
try {
return mProfileManager.getProfileFor(mImpl.getProfile());
} catch (RemoteException e) {
throw new APICallException(e);
}
}
} }
...@@ -4,13 +4,10 @@ ...@@ -4,13 +4,10 @@
package org.chromium.weblayer; package org.chromium.weblayer;
import android.content.Context;
import android.os.RemoteException; import android.os.RemoteException;
import org.chromium.weblayer_private.aidl.APICallException; import org.chromium.weblayer_private.aidl.APICallException;
import org.chromium.weblayer_private.aidl.IBrowserFragmentController;
import org.chromium.weblayer_private.aidl.IProfile; import org.chromium.weblayer_private.aidl.IProfile;
import org.chromium.weblayer_private.aidl.ObjectWrapper;
/** /**
* Profile holds state (typically on disk) needed for browsing. Create a * Profile holds state (typically on disk) needed for browsing. Create a
...@@ -18,9 +15,12 @@ import org.chromium.weblayer_private.aidl.ObjectWrapper; ...@@ -18,9 +15,12 @@ import org.chromium.weblayer_private.aidl.ObjectWrapper;
*/ */
public final class Profile { public final class Profile {
private IProfile mImpl; private IProfile mImpl;
private Runnable mOnDestroyRunnable;
Profile(IProfile impl) {
/* package */ Profile(IProfile impl, Runnable onDestroyRunnable) {
mImpl = impl; mImpl = impl;
mOnDestroyRunnable = onDestroyRunnable;
} }
@Override @Override
...@@ -28,14 +28,6 @@ public final class Profile { ...@@ -28,14 +28,6 @@ public final class Profile {
// TODO(sky): figure out right assertion here if mImpl is non-null. // TODO(sky): figure out right assertion here if mImpl is non-null.
} }
public void destroy() {
try {
mImpl.destroy();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
public void clearBrowsingData() { public void clearBrowsingData() {
try { try {
mImpl.clearBrowsingData(); mImpl.clearBrowsingData();
...@@ -44,16 +36,14 @@ public final class Profile { ...@@ -44,16 +36,14 @@ public final class Profile {
} }
} }
public BrowserFragmentController createBrowserFragmentController(Context context) { public void destroy() {
try { try {
BrowserFragment fragment = new BrowserFragment(); mImpl.destroy();
IBrowserFragmentController browserFragmentImpl =
mImpl.createBrowserFragmentController(fragment.asIRemoteFragmentClient(),
ObjectWrapper.wrap(WebLayer.createRemoteContext(context)));
fragment.setRemoteFragment(browserFragmentImpl.getRemoteFragment());
return new BrowserFragmentController(browserFragmentImpl, fragment);
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
mImpl = null;
mOnDestroyRunnable.run();
mOnDestroyRunnable = null;
} }
} }
package org.chromium.weblayer;
import org.chromium.weblayer_private.aidl.IProfile;
import java.util.HashMap;
import java.util.Map;
/* package */ class ProfileManager {
private final Map<IProfile, Profile> mProfiles = new HashMap<>();
/** Returns existing or new Profile associated with the given implementation on WebLayer side */
Profile getProfileFor(IProfile impl) {
Profile profile = mProfiles.get(impl);
if (profile != null) {
return profile;
}
profile = new Profile(impl, () -> mProfiles.remove(impl));
mProfiles.put(impl, profile);
return profile;
}
/** Destroys all the Profiles. */
void destroy() {
for (Profile profile : mProfiles.values()) {
profile.destroy();
}
mProfiles.clear();
}
}
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
package org.chromium.weblayer; package org.chromium.weblayer;
import android.app.Application;
import android.content.ComponentCallbacks; import android.content.ComponentCallbacks;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
...@@ -21,11 +20,13 @@ import android.webkit.WebViewDelegate; ...@@ -21,11 +20,13 @@ import android.webkit.WebViewDelegate;
import android.webkit.WebViewFactory; import android.webkit.WebViewFactory;
import org.chromium.weblayer_private.aidl.APICallException; import org.chromium.weblayer_private.aidl.APICallException;
import org.chromium.weblayer_private.aidl.BrowserFragmentArgs;
import org.chromium.weblayer_private.aidl.IBrowserFragment;
import org.chromium.weblayer_private.aidl.IRemoteFragmentClient;
import org.chromium.weblayer_private.aidl.IWebLayer; import org.chromium.weblayer_private.aidl.IWebLayer;
import org.chromium.weblayer_private.aidl.ObjectWrapper; import org.chromium.weblayer_private.aidl.ObjectWrapper;
import org.chromium.weblayer_private.aidl.WebLayerVersion; import org.chromium.weblayer_private.aidl.WebLayerVersion;
import java.io.File;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -40,13 +41,14 @@ public final class WebLayer { ...@@ -40,13 +41,14 @@ public final class WebLayer {
private static ListenableFuture<WebLayer> sFuture; private static ListenableFuture<WebLayer> sFuture;
private IWebLayer mImpl; private final IWebLayer mImpl;
private final ProfileManager mProfileManager = new ProfileManager();
/** /**
* Loads the WebLayer implementation and returns the IWebLayer. This does *not* trigger the * Loads the WebLayer implementation and returns the IWebLayer. This does *not* trigger the
* implementation to start. * implementation to start.
*/ */
private static IWebLayer connectToWebLayerImplementation(Application application) private static IWebLayer connectToWebLayerImplementation(Context application)
throws UnsupportedVersionException { throws UnsupportedVersionException {
try { try {
// TODO: Make asset loading work on L, where WebViewDelegate doesn't exist. // TODO: Make asset loading work on L, where WebViewDelegate doesn't exist.
...@@ -88,15 +90,16 @@ public final class WebLayer { ...@@ -88,15 +90,16 @@ public final class WebLayer {
* Asynchronously creates and initializes WebLayer. Calling this more than once returns the same * Asynchronously creates and initializes WebLayer. Calling this more than once returns the same
* object. * object.
* *
* @param application The hosting Application * @param appContext The hosting application's Context.
* @return a ListenableFuture whose value will contain the WebLayer once initialization * @return a ListenableFuture whose value will contain the WebLayer once initialization
* completes * completes
*/ */
public static ListenableFuture<WebLayer> create(Application application) public static ListenableFuture<WebLayer> create(Context appContext)
throws UnsupportedVersionException { throws UnsupportedVersionException {
if (sFuture == null) { if (sFuture == null) {
IWebLayer iWebLayer = connectToWebLayerImplementation(application); IWebLayer iWebLayer = connectToWebLayerImplementation(
sFuture = new WebLayerLoadFuture(iWebLayer, application); appContext.getApplicationContext());
sFuture = new WebLayerLoadFuture(iWebLayer, appContext);
} }
return sFuture; return sFuture;
} }
...@@ -107,7 +110,7 @@ public final class WebLayer { ...@@ -107,7 +110,7 @@ public final class WebLayer {
private static final class WebLayerLoadFuture extends ListenableFuture<WebLayer> { private static final class WebLayerLoadFuture extends ListenableFuture<WebLayer> {
private final IWebLayer mIWebLayer; private final IWebLayer mIWebLayer;
WebLayerLoadFuture(IWebLayer iWebLayer, Application application) { WebLayerLoadFuture(IWebLayer iWebLayer, Context application) {
mIWebLayer = iWebLayer; mIWebLayer = iWebLayer;
ValueCallback<Boolean> loadCallback = new ValueCallback<Boolean>() { ValueCallback<Boolean> loadCallback = new ValueCallback<Boolean>() {
@Override @Override
...@@ -159,23 +162,39 @@ public final class WebLayer { ...@@ -159,23 +162,39 @@ public final class WebLayer {
public void destroy() { public void destroy() {
// TODO: implement me. // TODO: implement me.
mProfileManager.destroy();
} }
private WebLayer(IWebLayer iWebLayer) { private WebLayer(IWebLayer iWebLayer) {
mImpl = iWebLayer; mImpl = iWebLayer;
} }
public static BrowserFragment createBrowserFragment(String profilePath) {
// TODO: use a profile id instead of the path to the actual file.
Bundle args = new Bundle();
args.putString(BrowserFragmentArgs.PROFILE_PATH, profilePath == null ? "" : profilePath);
BrowserFragment fragment = new BrowserFragment();
fragment.setArguments(args);
return fragment;
}
/** /**
* Creates a new Profile with the given path. Pass in an null path for an in-memory profile. * Returns remote counterpart for the BrowserFragment: an {@link IBrowserFragment}.
*/ */
public Profile createProfile(File path) { /* package */ IBrowserFragment connectFragment(
IRemoteFragmentClient remoteFragmentClient, Bundle fragmentArgs) {
try { try {
return new Profile(mImpl.createProfile(path == null ? "" : path.getPath())); return mImpl.createBrowserFragmentImpl(remoteFragmentClient,
ObjectWrapper.wrap(fragmentArgs));
} catch (RemoteException e) { } catch (RemoteException e) {
throw new APICallException(e); throw new APICallException(e);
} }
} }
/* package */ ProfileManager getProfileManager() {
return mProfileManager;
}
/** /**
* Creates a Context for the remote (weblayer implementation) side. * Creates a Context for the remote (weblayer implementation) side.
*/ */
......
...@@ -255,6 +255,7 @@ instrumentation_test_apk("weblayer_instrumentation_test_apk") { ...@@ -255,6 +255,7 @@ instrumentation_test_apk("weblayer_instrumentation_test_apk") {
"javatests/src/org/chromium/weblayer/test/SmokeTest.java", "javatests/src/org/chromium/weblayer/test/SmokeTest.java",
"javatests/src/org/chromium/weblayer/test/RenderingTest.java", "javatests/src/org/chromium/weblayer/test/RenderingTest.java",
"javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java", "javatests/src/org/chromium/weblayer/test/WebLayerShellActivityTestRule.java",
"javatests/src/org/chromium/weblayer/test/FragmentRestoreTest.java",
"shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java", "shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java",
] ]
additional_apks = [ "//weblayer/shell/android:weblayer_support_apk" ] additional_apks = [ "//weblayer/shell/android:weblayer_support_apk" ]
......
...@@ -18,6 +18,7 @@ import org.chromium.content_public.browser.BrowserStartupController; ...@@ -18,6 +18,7 @@ import org.chromium.content_public.browser.BrowserStartupController;
import org.chromium.native_test.NativeBrowserTest; import org.chromium.native_test.NativeBrowserTest;
import org.chromium.native_test.NativeBrowserTestActivity; import org.chromium.native_test.NativeBrowserTestActivity;
import org.chromium.weblayer.BrowserController; import org.chromium.weblayer.BrowserController;
import org.chromium.weblayer.BrowserFragment;
import org.chromium.weblayer.BrowserFragmentController; import org.chromium.weblayer.BrowserFragmentController;
import org.chromium.weblayer.BrowserObserver; import org.chromium.weblayer.BrowserObserver;
import org.chromium.weblayer.ListenableFuture; import org.chromium.weblayer.ListenableFuture;
...@@ -37,13 +38,6 @@ public class WebLayerBrowserTestsActivity extends NativeBrowserTestActivity { ...@@ -37,13 +38,6 @@ public class WebLayerBrowserTestsActivity extends NativeBrowserTestActivity {
private EditText mUrlView; private EditText mUrlView;
private View mMainView; private View mMainView;
@Override
protected void onDestroy() {
if (mProfile != null) mProfile.destroy();
if (mBrowserFragmentController != null) mBrowserFragmentController.destroy();
super.onDestroy();
}
@Override @Override
protected void initializeBrowserProcess() { protected void initializeBrowserProcess() {
BrowserStartupController.get(LibraryProcessType.PROCESS_WEBLAYER) BrowserStartupController.get(LibraryProcessType.PROCESS_WEBLAYER)
...@@ -85,12 +79,12 @@ public class WebLayerBrowserTestsActivity extends NativeBrowserTestActivity { ...@@ -85,12 +79,12 @@ public class WebLayerBrowserTestsActivity extends NativeBrowserTestActivity {
new RelativeLayout.LayoutParams( new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mProfile = mWebLayer.createProfile(null); BrowserFragment fragment = WebLayer.createBrowserFragment(null);
mBrowserFragmentController = fragment.getController();
mBrowserFragmentController = mProfile.createBrowserFragmentController(this); mProfile = mBrowserFragmentController.getProfile();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(viewId, mBrowserFragmentController.getFragment()); transaction.add(viewId, fragment);
transaction.commit(); transaction.commit();
mBrowserFragmentController.setTopView(topContentsContainer); mBrowserFragmentController.setTopView(topContentsContainer);
......
...@@ -23,8 +23,10 @@ import android.widget.TextView; ...@@ -23,8 +23,10 @@ import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
import org.chromium.weblayer.BrowserController; import org.chromium.weblayer.BrowserController;
import org.chromium.weblayer.BrowserFragment;
import org.chromium.weblayer.BrowserFragmentController; import org.chromium.weblayer.BrowserFragmentController;
import org.chromium.weblayer.BrowserObserver; import org.chromium.weblayer.BrowserObserver;
import org.chromium.weblayer.NavigationController;
import org.chromium.weblayer.Profile; import org.chromium.weblayer.Profile;
import org.chromium.weblayer.WebLayer; import org.chromium.weblayer.WebLayer;
...@@ -37,11 +39,11 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -37,11 +39,11 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
private static final boolean USE_WEBVIEW = false; private static final boolean USE_WEBVIEW = false;
private Profile mProfile; private Profile mProfile;
private final BrowserFragmentController mBrowserFragmentControllers[] = private final BrowserFragment mBrowserFragments[] = new BrowserFragment[4];
new BrowserFragmentController[4];
private final ContainerFrameLayout mContainerViews[] = new ContainerFrameLayout[4]; private final ContainerFrameLayout mContainerViews[] = new ContainerFrameLayout[4];
private final MyWebView mWebViews[] = new MyWebView[4]; private final MyWebView mWebViews[] = new MyWebView[4];
private FrameLayout mMainView; private FrameLayout mMainView;
private WebLayer mWebLayer;
public static class ContainerFrameLayout extends FrameLayout { public static class ContainerFrameLayout extends FrameLayout {
private final BrowserFragmentController mFragmentController; private final BrowserFragmentController mFragmentController;
...@@ -115,21 +117,22 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -115,21 +117,22 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
} }
private void createNewFragment(int index) { private void createNewFragment(int index) {
mBrowserFragmentControllers[index] = mProfile.createBrowserFragmentController(this); int viewId = View.generateViewId();
final BrowserController controller = mBrowserFragmentControllers[index]
.getBrowserController(); mBrowserFragments[index] = WebLayer.createBrowserFragment(null);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(viewId, mBrowserFragments[index]);
transaction.commitNow();
final BrowserController controller = getFragmentController(index).getBrowserController();
ContainerFrameLayout container = ContainerFrameLayout container =
new ContainerFrameLayout(this, mBrowserFragmentControllers[index], new ContainerFrameLayout(this, getFragmentController(index), index);
index);
mContainerViews[index] = container; mContainerViews[index] = container;
int viewId = View.generateViewId();
container.setId(viewId); container.setId(viewId);
mMainView.addView(container); mMainView.addView(container);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(viewId, mBrowserFragmentControllers[index].getFragment());
transaction.commit();
EditText urlView = new EditText(this); EditText urlView = new EditText(this);
urlView.setSelectAllOnFocus(true); urlView.setSelectAllOnFocus(true);
...@@ -151,7 +154,7 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -151,7 +154,7 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
return true; return true;
} }
}); });
mBrowserFragmentControllers[index].setTopView(urlView); getFragmentController(index).setTopView(urlView);
controller.addObserver(new BrowserObserver() { controller.addObserver(new BrowserObserver() {
@Override @Override
...@@ -189,12 +192,16 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -189,12 +192,16 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
} }
} else { } else {
// Only call init for main process. // Only call init for main process.
WebLayer webLayer = null;
try { try {
webLayer = WebLayer.create(getApplication()).get(); mWebLayer = WebLayer.create(getApplication()).get();
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("failed loading WebLayer", e); throw new RuntimeException("failed loading WebLayer", e);
} }
if (savedInstanceState != null) {
// This prevents FragmentManager from restoring fragments.
// TODO(pshmakov): restore fragments properly, as in WebLayerShellActivity.
savedInstanceState.remove("android:support:fragments");
}
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
...@@ -202,32 +209,17 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -202,32 +209,17 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
mMainView = mainView; mMainView = mainView;
setContentView(mainView); setContentView(mainView);
mProfile = webLayer.createProfile(null);
createNewFragment(0); createNewFragment(0);
createNewFragment(1); createNewFragment(1);
createNewFragment(2); createNewFragment(2);
mProfile = getFragmentController(0).getProfile();
mBrowserFragmentControllers[0].getBrowserController().getNavigationController() getNavigationController(0).navigate(Uri.parse(sanitizeUrl("https://www.google.com")));
.navigate(Uri.parse(sanitizeUrl("https://www.google.com"))); getNavigationController(1).navigate(Uri.parse(sanitizeUrl("https://en.wikipedia.org")));
mBrowserFragmentControllers[1].getBrowserController().getNavigationController() getNavigationController(2).navigate(Uri.parse(sanitizeUrl("https://www.chromium.org")));
.navigate(Uri.parse(sanitizeUrl("https://en.wikipedia.org")));
mBrowserFragmentControllers[2].getBrowserController().getNavigationController()
.navigate(Uri.parse(sanitizeUrl("https://www.chromium.org")));
} }
} }
@Override
protected void onDestroy() {
for (int i = 0; i < mBrowserFragmentControllers.length; ++i) {
BrowserFragmentController fragment = mBrowserFragmentControllers[i];
if (fragment != null) fragment.destroy();
mBrowserFragmentControllers[i] = null;
}
if (mProfile != null) mProfile.destroy();
super.onDestroy();
}
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
...@@ -252,16 +244,24 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity { ...@@ -252,16 +244,24 @@ public class WebLayerAnimationDemoActivity extends FragmentActivity {
mContainerViews[1].setOnClickListener(new OnClickImpl()); mContainerViews[1].setOnClickListener(new OnClickImpl());
mContainerViews[2].setOnClickListener(new OnClickImpl()); mContainerViews[2].setOnClickListener(new OnClickImpl());
mContainerViews[0].post(() -> { mContainerViews[0].post(() -> {
mBrowserFragmentControllers[0].setSupportsEmbedding(true).addCallback( getFragmentController(0).setSupportsEmbedding(true).addCallback(
(Boolean result) -> animateDown(mContainerViews[0])); (Boolean result) -> animateDown(mContainerViews[0]));
mBrowserFragmentControllers[1].setSupportsEmbedding(true).addCallback( getFragmentController(1).setSupportsEmbedding(true).addCallback(
(Boolean result) -> animateDown(mContainerViews[1])); (Boolean result) -> animateDown(mContainerViews[1]));
mBrowserFragmentControllers[2].setSupportsEmbedding(true).addCallback( getFragmentController(2).setSupportsEmbedding(true).addCallback(
(Boolean result) -> animateDown(mContainerViews[2])); (Boolean result) -> animateDown(mContainerViews[2]));
}); });
} }
} }
private BrowserFragmentController getFragmentController(int index) {
return mBrowserFragments[index].getController();
}
private NavigationController getNavigationController(int index) {
return getFragmentController(index).getBrowserController().getNavigationController();
}
private ContainerFrameLayout mFullscreenContainer; private ContainerFrameLayout mFullscreenContainer;
private MyWebView mFullscreenWebView; private MyWebView mFullscreenWebView;
public class OnClickImpl implements View.OnClickListener { public class OnClickImpl implements View.OnClickListener {
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer.test;
import android.support.test.filters.SmallTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
@RunWith(BaseJUnit4ClassRunner.class)
public class FragmentRestoreTest {
@Rule
public WebLayerShellActivityTestRule mActivityTestRule = new WebLayerShellActivityTestRule();
@Test
@SmallTest
public void successfullyLoadsUrlAfterRotation() {
mActivityTestRule.launchShellWithUrl("about:blank");
String url = "data:text,foo";
mActivityTestRule.loadUrl(url);
mActivityTestRule.waitForNavigation(url);
mActivityTestRule.rotateActivity();
url = "data:text,bar";
mActivityTestRule.loadUrl(url);
mActivityTestRule.waitForNavigation(url);
}
}
...@@ -4,8 +4,12 @@ ...@@ -4,8 +4,12 @@
package org.chromium.weblayer.test; package org.chromium.weblayer.test;
import android.app.Activity;
import android.app.Instrumentation.ActivityMonitor;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.net.Uri; import android.net.Uri;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule; import android.support.test.rule.ActivityTestRule;
...@@ -16,6 +20,8 @@ import org.chromium.content_public.browser.test.util.TestThreadUtils; ...@@ -16,6 +20,8 @@ import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.NavigationController; import org.chromium.weblayer.NavigationController;
import org.chromium.weblayer.shell.WebLayerShellActivity; import org.chromium.weblayer.shell.WebLayerShellActivity;
import java.lang.reflect.Field;
/** /**
* ActivityTestRule for WebLayerShellActivity. * ActivityTestRule for WebLayerShellActivity.
* *
...@@ -64,4 +70,36 @@ public class WebLayerShellActivityTestRule extends ActivityTestRule<WebLayerShel ...@@ -64,4 +70,36 @@ public class WebLayerShellActivityTestRule extends ActivityTestRule<WebLayerShel
public void loadUrl(String url) { public void loadUrl(String url) {
TestThreadUtils.runOnUiThreadBlocking(() -> { getActivity().loadUrl(url); }); TestThreadUtils.runOnUiThreadBlocking(() -> { getActivity().loadUrl(url); });
} }
/**
* Rotates the Activity, blocking until the Activity is re-created.
* After calling this, getActivity() returns the new Activity.
*/
public void rotateActivity() {
Activity activity = getActivity();
ActivityMonitor monitor = new ActivityMonitor(WebLayerShellActivity.class.getName(),
null, false);
InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
int current = activity.getResources().getConfiguration().orientation;
int requested = current == Configuration.ORIENTATION_LANDSCAPE ?
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT :
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
activity.setRequestedOrientation(requested);
CriteriaHelper.pollUiThread(() ->
monitor.getLastActivity() != null && monitor.getLastActivity() != activity
);
InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
// There is no way to rotate the activity using ActivityTestRule or even notify it.
// So we have to hack...
try {
Field field = ActivityTestRule.class.getDeclaredField("mActivity");
field.setAccessible(true);
field.set(this, monitor.getLastActivity());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
} }
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
<activity android:name="WebLayerShellActivity" <activity android:name="WebLayerShellActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@android:style/Theme.Holo.Light.NoActionBar" android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
android:windowSoftInputMode="adjustPan|stateUnspecified"> android:windowSoftInputMode="adjustPan|stateUnspecified">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
......
...@@ -7,12 +7,15 @@ package org.chromium.weblayer.shell; ...@@ -7,12 +7,15 @@ package org.chromium.weblayer.shell;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.EditText; import android.widget.EditText;
...@@ -23,16 +26,21 @@ import android.widget.TextView; ...@@ -23,16 +26,21 @@ import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
import org.chromium.weblayer.BrowserController; import org.chromium.weblayer.BrowserController;
import org.chromium.weblayer.BrowserFragment;
import org.chromium.weblayer.BrowserFragmentController; import org.chromium.weblayer.BrowserFragmentController;
import org.chromium.weblayer.BrowserObserver; import org.chromium.weblayer.BrowserObserver;
import org.chromium.weblayer.Profile; import org.chromium.weblayer.Profile;
import org.chromium.weblayer.UnsupportedVersionException;
import org.chromium.weblayer.WebLayer; import org.chromium.weblayer.WebLayer;
import java.util.List;
/** /**
* Activity for managing the Demo Shell. * Activity for managing the Demo Shell.
*/ */
public class WebLayerShellActivity extends FragmentActivity { public class WebLayerShellActivity extends FragmentActivity {
private static final String TAG = "WebLayerShell"; private static final String TAG = "WebLayerShell";
private static final String KEY_MAIN_VIEW_ID = "mainViewId";
private Profile mProfile; private Profile mProfile;
private BrowserFragmentController mBrowserFragmentController; private BrowserFragmentController mBrowserFragmentController;
...@@ -40,6 +48,9 @@ public class WebLayerShellActivity extends FragmentActivity { ...@@ -40,6 +48,9 @@ public class WebLayerShellActivity extends FragmentActivity {
private EditText mUrlView; private EditText mUrlView;
private ProgressBar mLoadProgressBar; private ProgressBar mLoadProgressBar;
private View mMainView; private View mMainView;
private int mMainViewId;
private ViewGroup mTopContentsContainer;
private BrowserFragment mFragment;
public BrowserController getBrowserController() { public BrowserController getBrowserController() {
return mBrowserController; return mBrowserController;
...@@ -52,17 +63,13 @@ public class WebLayerShellActivity extends FragmentActivity { ...@@ -52,17 +63,13 @@ public class WebLayerShellActivity extends FragmentActivity {
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
WebLayer webLayer = null;
try {
webLayer = WebLayer.create(getApplication()).get();
} catch (Exception e) {
throw new RuntimeException("failed loading WebLayer", e);
}
LinearLayout mainView = new LinearLayout(this); LinearLayout mainView = new LinearLayout(this);
int viewId = View.generateViewId(); if (savedInstanceState == null) {
mainView.setId(viewId); mMainViewId = View.generateViewId();
} else {
mMainViewId = savedInstanceState.getInt(KEY_MAIN_VIEW_ID);
}
mainView.setId(mMainViewId);
mMainView = mainView; mMainView = mainView;
setContentView(mainView); setContentView(mainView);
...@@ -93,8 +100,8 @@ public class WebLayerShellActivity extends FragmentActivity { ...@@ -93,8 +100,8 @@ public class WebLayerShellActivity extends FragmentActivity {
mLoadProgressBar.setVisibility(View.INVISIBLE); mLoadProgressBar.setVisibility(View.INVISIBLE);
// The progress bar sits above the URL bar in Z order and at its bottom in Y. // The progress bar sits above the URL bar in Z order and at its bottom in Y.
RelativeLayout topContentsContainer = new RelativeLayout(this); mTopContentsContainer = new RelativeLayout(this);
topContentsContainer.addView(mUrlView, mTopContentsContainer.addView(mUrlView,
new RelativeLayout.LayoutParams( new RelativeLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
...@@ -102,17 +109,28 @@ public class WebLayerShellActivity extends FragmentActivity { ...@@ -102,17 +109,28 @@ public class WebLayerShellActivity extends FragmentActivity {
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
progressLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mUrlView.getId()); progressLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mUrlView.getId());
progressLayoutParams.setMargins(0, 0, 0, -10); progressLayoutParams.setMargins(0, 0, 0, -10);
topContentsContainer.addView(mLoadProgressBar, progressLayoutParams); mTopContentsContainer.addView(mLoadProgressBar, progressLayoutParams);
mProfile = webLayer.createProfile(null); try {
// This ensures asynchronous initialization of WebLayer on first start of activity.
// If activity is re-created during process restart, FragmentManager attaches
// BrowserFragment immediately, resulting in synchronous init. By the time this line
// executes, the synchronous init has already happened.
WebLayer.create(getApplication()).addCallback(webLayer -> onWebLayerReady(
savedInstanceState));
} catch (UnsupportedVersionException e) {
throw new RuntimeException("Failed to initialize WebLayer", e);
}
}
mBrowserFragmentController = mProfile.createBrowserFragmentController(this); private void onWebLayerReady(Bundle savedInstanceState) {
if (isFinishing() || isDestroyed()) return;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); mFragment = getOrCreateBrowserFragment(savedInstanceState);
transaction.add(viewId, mBrowserFragmentController.getFragment()); mBrowserFragmentController = mFragment.getController();
transaction.commit(); mProfile = mBrowserFragmentController.getProfile();
mBrowserFragmentController.setTopView(topContentsContainer); mBrowserFragmentController.setTopView(mTopContentsContainer);
mBrowserController = mBrowserFragmentController.getBrowserController(); mBrowserController = mBrowserFragmentController.getBrowserController();
String startupUrl = getUrlFromIntent(getIntent()); String startupUrl = getUrlFromIntent(getIntent());
...@@ -139,11 +157,28 @@ public class WebLayerShellActivity extends FragmentActivity { ...@@ -139,11 +157,28 @@ public class WebLayerShellActivity extends FragmentActivity {
}); });
} }
@Override private BrowserFragment getOrCreateBrowserFragment(Bundle savedInstanceState) {
protected void onDestroy() { FragmentManager fragmentManager = getSupportFragmentManager();
if (mProfile != null) mProfile.destroy(); if (savedInstanceState != null) {
if (mBrowserFragmentController != null) mBrowserFragmentController.destroy(); // FragmentManager could have re-created the fragment.
super.onDestroy(); List<Fragment> fragments = fragmentManager.getFragments();
if (fragments.size() > 1) {
throw new IllegalStateException("More than one fragment added, shouldn't happen");
}
if (fragments.size() == 1) {
return (BrowserFragment) fragments.get(0);
}
}
BrowserFragment fragment = WebLayer.createBrowserFragment(null);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(mMainViewId, fragment);
// Note the commitNow() instead of commit(). We want the fragment to get attached to
// activity synchronously, so we can use all the functionality immediately. Otherwise we'd
// have to wait until the commit is executed.
transaction.commitNow();
return fragment;
} }
@Override @Override
...@@ -170,4 +205,12 @@ public class WebLayerShellActivity extends FragmentActivity { ...@@ -170,4 +205,12 @@ public class WebLayerShellActivity extends FragmentActivity {
if (url.startsWith("www.") || url.indexOf(":") == -1) url = "http://" + url; if (url.startsWith("www.") || url.indexOf(":") == -1) url = "http://" + url;
return url; return url;
} }
@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);
}
} }
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