diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-23 17:21:03 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-23 16:25:15 +0000 |
commit | c551f43206405019121bd2b2c93714319a0a3300 (patch) | |
tree | 1f48c30631c421fd4bbb3c36da20183c8a2ed7d7 /chromium/weblayer/browser/java | |
parent | 7961cea6d1041e3e454dae6a1da660b453efd238 (diff) | |
download | qtwebengine-chromium-c551f43206405019121bd2b2c93714319a0a3300.tar.gz |
BASELINE: Update Chromium to 79.0.3945.139
Change-Id: I336b7182fab9bca80b709682489c07db112eaca5
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/weblayer/browser/java')
67 files changed, 4963 insertions, 423 deletions
diff --git a/chromium/weblayer/browser/java/BUILD.gn b/chromium/weblayer/browser/java/BUILD.gn index 24403c5be58..6b5e736a49f 100644 --- a/chromium/weblayer/browser/java/BUILD.gn +++ b/chromium/weblayer/browser/java/BUILD.gn @@ -4,54 +4,170 @@ import("//build/config/android/config.gni") import("//build/config/android/rules.gni") +import("//weblayer/variables.gni") + +android_resources("weblayer_resources") { + resource_dirs = [] + custom_package = "org.chromium.weblayer_private" +} + +generate_locale_config_srcjar("weblayer_locale_config") { + java_package = weblayer_locale_config_java_package +} + +java_cpp_enum("generated_enums") { + sources = [ + "//weblayer/public/navigation.h", + "//weblayer/public/new_tab_delegate.h", + "//weblayer/public/profile.h", + ] +} android_library("java") { java_files = [ - "org/chromium/weblayer_private/BrowserControllerImpl.java", - "org/chromium/weblayer_private/BrowserObserverProxy.java", + "org/chromium/weblayer_private/BrowserImpl.java", + "org/chromium/weblayer_private/BrowserFragmentImpl.java", + "org/chromium/weblayer_private/BrowserViewController.java", + "org/chromium/weblayer_private/ChildProcessServiceImpl.java", + "org/chromium/weblayer_private/ContentView.java", + "org/chromium/weblayer_private/ContentViewRenderView.java", + "org/chromium/weblayer_private/CrashReporterControllerImpl.java", + "org/chromium/weblayer_private/DownloadCallbackProxy.java", + "org/chromium/weblayer_private/ErrorPageCallbackProxy.java", + "org/chromium/weblayer_private/ExternalNavigationHandler.java", + "org/chromium/weblayer_private/ActionModeCallback.java", + "org/chromium/weblayer_private/FullscreenCallbackProxy.java", + "org/chromium/weblayer_private/MinidumpUploader.java", "org/chromium/weblayer_private/NavigationControllerImpl.java", "org/chromium/weblayer_private/NavigationImpl.java", + "org/chromium/weblayer_private/NewTabCallbackProxy.java", "org/chromium/weblayer_private/ProfileImpl.java", + "org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java", + "org/chromium/weblayer_private/FragmentWindowAndroid.java", + "org/chromium/weblayer_private/ProfileManager.java", + "org/chromium/weblayer_private/RemoteFragmentImpl.java", + "org/chromium/weblayer_private/TabCallbackProxy.java", + "org/chromium/weblayer_private/TabImpl.java", + "org/chromium/weblayer_private/TopControlsContainerView.java", + "org/chromium/weblayer_private/WebContentsGestureStateTracker.java", + "org/chromium/weblayer_private/WebLayerFactoryImpl.java", "org/chromium/weblayer_private/WebLayerImpl.java", ] deps = [ - ":client_java", + ":gms_bridge_java", + ":interfaces_java", + ":weblayer_resources", "//base:base_java", - "//components/embedder_support/android:content_view_java", - "//components/embedder_support/android:view_java", + "//base:jni_java", + "//components/crash/android:handler_java", + "//components/crash/android:java", + "//components/embedder_support/android:application_java", + "//components/embedder_support/android:web_contents_delegate_java", + "//components/minidump_uploader:minidump_uploader_java", + "//components/version_info/android:version_constants_java", "//content/public/android:content_java", + "//third_party/android_deps:com_android_support_support_compat_java", "//ui/android:ui_java", ] + srcjar_deps = [ + ":generated_enums", + ":weblayer_locale_config", + ] + jar_excluded_patterns = [ "*/LocaleConfig.class" ] + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] + + # Needed for android.webkit.WebView(Delegate|Factory) + alternative_android_sdk_dep = + "//third_party/android_sdk:public_framework_system_java" } generate_jni("jni") { sources = [ - "org/chromium/weblayer_private/BrowserControllerImpl.java", - "org/chromium/weblayer_private/BrowserObserverProxy.java", + "org/chromium/weblayer_private/ContentViewRenderView.java", + "org/chromium/weblayer_private/DownloadCallbackProxy.java", + "org/chromium/weblayer_private/ErrorPageCallbackProxy.java", + "org/chromium/weblayer_private/ExternalNavigationHandler.java", + "org/chromium/weblayer_private/FullscreenCallbackProxy.java", "org/chromium/weblayer_private/NavigationControllerImpl.java", "org/chromium/weblayer_private/NavigationImpl.java", + "org/chromium/weblayer_private/NewTabCallbackProxy.java", "org/chromium/weblayer_private/ProfileImpl.java", + "org/chromium/weblayer_private/TabCallbackProxy.java", + "org/chromium/weblayer_private/TabImpl.java", + "org/chromium/weblayer_private/TopControlsContainerView.java", + "org/chromium/weblayer_private/WebLayerImpl.java", ] } -android_library("client_java") { - java_files = [ "org/chromium/weblayer_private/aidl/ObjectWrapper.java" ] +android_library("interfaces_java") { + java_files = [ + "org/chromium/weblayer_private/interfaces/APICallException.java", + "org/chromium/weblayer_private/interfaces/BrowserFragmentArgs.java", + "org/chromium/weblayer_private/interfaces/BrowsingDataType.java", + "org/chromium/weblayer_private/interfaces/LoadError.java", + "org/chromium/weblayer_private/interfaces/NavigationState.java", + "org/chromium/weblayer_private/interfaces/NewTabType.java", + "org/chromium/weblayer_private/interfaces/ObjectWrapper.java", + "org/chromium/weblayer_private/interfaces/WebLayerVersion.java", + ] + + deps = [ + "//third_party/android_deps:androidx_annotation_annotation_java", + ] srcjar_deps = [ ":aidl" ] } +# Separate target to allow for a dependency on GmsCore without pulling in all of +# java classes. It compiles the abstract class; implementations are compiled +# separately. +android_library("gms_bridge_java") { + java_files = [ + "org/chromium/weblayer_private/GmsBridge.java", + "org/chromium/weblayer_private/GmsBridgeImpl.java", + ] + + deps = [ + "//base:base_java", + ] + + # The appropriate .class file will be loaded via a dependency to a library + # like :gms_bridge_upstream_impl_java below. + jar_excluded_patterns = [ "*/GmsBridgeImpl.class" ] +} + +# This target compiles the implementation of GmsBridge for public targets. +android_library("gms_bridge_upstream_impl_java") { + java_files = [ "org/chromium/weblayer_private/GmsBridgeImpl.java" ] + deps = [ + ":gms_bridge_java", + ] +} + android_aidl("aidl") { - import_include = [ "org/chromium/weblayer_private/aidl" ] + import_include = [ "." ] sources = [ - "org/chromium/weblayer_private/aidl/IBrowserController.aidl", - "org/chromium/weblayer_private/aidl/IBrowserControllerClient.aidl", - "org/chromium/weblayer_private/aidl/IClientNavigation.aidl", - "org/chromium/weblayer_private/aidl/INavigation.aidl", - "org/chromium/weblayer_private/aidl/INavigationController.aidl", - "org/chromium/weblayer_private/aidl/INavigationControllerClient.aidl", - "org/chromium/weblayer_private/aidl/IObjectWrapper.aidl", - "org/chromium/weblayer_private/aidl/IProfile.aidl", - "org/chromium/weblayer_private/aidl/IWebLayer.aidl", + "org/chromium/weblayer_private/interfaces/IBrowser.aidl", + "org/chromium/weblayer_private/interfaces/IBrowserClient.aidl", + "org/chromium/weblayer_private/interfaces/IBrowserFragment.aidl", + "org/chromium/weblayer_private/interfaces/IChildProcessService.aidl", + "org/chromium/weblayer_private/interfaces/IClientNavigation.aidl", + "org/chromium/weblayer_private/interfaces/ICrashReporterController.aidl", + "org/chromium/weblayer_private/interfaces/ICrashReporterControllerClient.aidl", + "org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl", + "org/chromium/weblayer_private/interfaces/IErrorPageCallbackClient.aidl", + "org/chromium/weblayer_private/interfaces/IFullscreenCallbackClient.aidl", + "org/chromium/weblayer_private/interfaces/INavigation.aidl", + "org/chromium/weblayer_private/interfaces/INavigationController.aidl", + "org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl", + "org/chromium/weblayer_private/interfaces/IObjectWrapper.aidl", + "org/chromium/weblayer_private/interfaces/IProfile.aidl", + "org/chromium/weblayer_private/interfaces/IRemoteFragment.aidl", + "org/chromium/weblayer_private/interfaces/IRemoteFragmentClient.aidl", + "org/chromium/weblayer_private/interfaces/ITab.aidl", + "org/chromium/weblayer_private/interfaces/ITabClient.aidl", + "org/chromium/weblayer_private/interfaces/IWebLayer.aidl", + "org/chromium/weblayer_private/interfaces/IWebLayerFactory.aidl", ] } diff --git a/chromium/weblayer/browser/java/DEPS b/chromium/weblayer/browser/java/DEPS new file mode 100644 index 00000000000..0785845d9c6 --- /dev/null +++ b/chromium/weblayer/browser/java/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+components/crash/android/java", + "+components/minidump_uploader", +] diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ActionModeCallback.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ActionModeCallback.java new file mode 100644 index 00000000000..6b1103e7324 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ActionModeCallback.java @@ -0,0 +1,63 @@ +// 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.app.SearchManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; + +import org.chromium.base.PackageManagerUtils; +import org.chromium.content_public.browser.ActionModeCallbackHelper; +import org.chromium.content_public.browser.SelectionPopupController; +import org.chromium.content_public.browser.WebContents; + +/** + * A class that handles selection action mode for WebLayer. + */ +public final class ActionModeCallback implements ActionMode.Callback { + private final ActionModeCallbackHelper mHelper; + + public ActionModeCallback(WebContents webContents) { + mHelper = + SelectionPopupController.fromWebContents(webContents).getActionModeCallbackHelper(); + } + + @Override + public final boolean onCreateActionMode(ActionMode mode, Menu menu) { + int allowedActionModes = ActionModeCallbackHelper.MENU_ITEM_PROCESS_TEXT + | ActionModeCallbackHelper.MENU_ITEM_SHARE; + if (isWebSearchAvailable()) { + allowedActionModes |= ActionModeCallbackHelper.MENU_ITEM_WEB_SEARCH; + } + mHelper.setAllowedMenuItems(allowedActionModes); + mHelper.onCreateActionMode(mode, menu); + return true; + } + + private boolean isWebSearchAvailable() { + Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); + return !PackageManagerUtils.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) + .isEmpty(); + } + + @Override + public final boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return mHelper.onPrepareActionMode(mode, menu); + } + + @Override + public final boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return mHelper.onActionItemClicked(mode, item); + } + + @Override + public final void onDestroyActionMode(ActionMode mode) { + mHelper.onDestroyActionMode(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControllerImpl.java deleted file mode 100644 index cd9ee225041..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserControllerImpl.java +++ /dev/null @@ -1,143 +0,0 @@ -// 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.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.widget.LinearLayout; - -import org.chromium.base.annotations.JNINamespace; -import org.chromium.components.embedder_support.view.ContentView; -import org.chromium.components.embedder_support.view.ContentViewRenderView; -import org.chromium.content_public.browser.ViewEventSink; -import org.chromium.content_public.browser.WebContents; -import org.chromium.ui.base.ActivityWindowAndroid; -import org.chromium.ui.base.ViewAndroidDelegate; -import org.chromium.weblayer_private.aidl.IBrowserController; -import org.chromium.weblayer_private.aidl.IBrowserControllerClient; -import org.chromium.weblayer_private.aidl.INavigationControllerClient; -import org.chromium.weblayer_private.aidl.IObjectWrapper; -import org.chromium.weblayer_private.aidl.ObjectWrapper; - -@JNINamespace("weblayer") -public final class BrowserControllerImpl extends IBrowserController.Stub { - private long mNativeBrowserController; - - private ActivityWindowAndroid mWindowAndroid; - // This is set as the content view of the activity. It contains mContentViewRenderView. - private LinearLayout mLinearLayout; - // This is parented to mLinearLayout. - private ContentViewRenderView mContentViewRenderView; - // One of these is needed per WebContents. - private ContentView mContentView; - private ProfileImpl mProfile; - private WebContents mWebContents; - private BrowserObserverProxy mBrowserObserverProxy; - private NavigationControllerImpl mNavigationController; - private View mTopView; - - private static class InternalAccessDelegateImpl - implements ViewEventSink.InternalAccessDelegate { - @Override - public boolean super_onKeyUp(int keyCode, KeyEvent event) { - return false; - } - - @Override - public boolean super_dispatchKeyEvent(KeyEvent event) { - return false; - } - - @Override - public boolean super_onGenericMotionEvent(MotionEvent event) { - return false; - } - - @Override - public void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix) {} - } - - public BrowserControllerImpl(Context context, ProfileImpl profile) { - mProfile = profile; - - mLinearLayout = new LinearLayout(context); - mLinearLayout.setOrientation(LinearLayout.VERTICAL); - - mWindowAndroid = new ActivityWindowAndroid(context); - mContentViewRenderView = new ContentViewRenderView(context); - mWindowAndroid.setAnimationPlaceholderView(mContentViewRenderView.getSurfaceView()); - - mContentViewRenderView.onNativeLibraryLoaded(mWindowAndroid); - - mNativeBrowserController = nativeCreateBrowserController(profile.getNativeProfile()); - mWebContents = nativeGetWebContents(mNativeBrowserController); - mWebContents.initialize("", ViewAndroidDelegate.createBasicDelegate(mContentViewRenderView), - new InternalAccessDelegateImpl(), mWindowAndroid, - WebContents.createDefaultInternalsHolder()); - - mContentViewRenderView.setCurrentWebContents(mWebContents); - mLinearLayout.addView(mContentViewRenderView, - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, 1f)); - - mContentView = ContentView.createContentView(context, mWebContents); - mContentViewRenderView.addView(mContentView, - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1f)); - - mWebContents.onShow(); - } - - long getNativeBrowserController() { - return mNativeBrowserController; - } - - @Override - public NavigationControllerImpl createNavigationController(INavigationControllerClient client) { - // This should only be called once. - assert mNavigationController == null; - mNavigationController = new NavigationControllerImpl(this, client); - return mNavigationController; - } - - @Override - public void setClient(IBrowserControllerClient client) { - mBrowserObserverProxy = new BrowserObserverProxy(mNativeBrowserController, client); - } - - @Override - public void destroy() { - if (mBrowserObserverProxy != null) mBrowserObserverProxy.destroy(); - mBrowserObserverProxy = null; - mNavigationController = null; - nativeDeleteBrowserController(mNativeBrowserController); - mNativeBrowserController = 0; - } - - @Override - public void setTopView(IObjectWrapper viewWrapper) { - View view = ObjectWrapper.unwrap(viewWrapper, View.class); - if (mTopView == view) return; - if (mTopView != null) mLinearLayout.removeView(mTopView); - mTopView = view; - if (mTopView != null) { - mLinearLayout.addView(mTopView, 0, - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, 0f)); - } - } - - @Override - public IObjectWrapper onCreateView() { - return ObjectWrapper.wrap(mLinearLayout); - } - - private static native long nativeCreateBrowserController(long profile); - private static native void nativeDeleteBrowserController(long browserController); - private native WebContents nativeGetWebContents(long nativeBrowserControllerImpl); -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java new file mode 100644 index 00000000000..7e0e6477aa2 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserFragmentImpl.java @@ -0,0 +1,103 @@ +// 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.content.Intent; +import android.os.Bundle; +import android.view.View; + +import org.chromium.components.embedder_support.application.ClassLoaderContextWrapperFactory; +import org.chromium.weblayer_private.interfaces.BrowserFragmentArgs; +import org.chromium.weblayer_private.interfaces.IBrowser; +import org.chromium.weblayer_private.interfaces.IBrowserFragment; +import org.chromium.weblayer_private.interfaces.IRemoteFragment; +import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient; + +/** + * Implementation of RemoteFragmentImpl which forwards logic to BrowserImpl. + */ +public class BrowserFragmentImpl extends RemoteFragmentImpl { + private final ProfileImpl mProfile; + + private BrowserImpl mBrowser; + private Context mContext; + + public BrowserFragmentImpl( + ProfileManager profileManager, IRemoteFragmentClient client, Bundle fragmentArgs) { + super(client); + mProfile = + profileManager.getProfile(fragmentArgs.getString(BrowserFragmentArgs.PROFILE_NAME)); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mContext = ClassLoaderContextWrapperFactory.get(context); + if (mBrowser != null) { // On first creation, onAttach is called before onCreate + mBrowser.onFragmentAttached(mContext, new FragmentWindowAndroid(mContext, this)); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mBrowser = new BrowserImpl(mProfile, savedInstanceState); + if (mContext != null) { + mBrowser.onFragmentAttached(mContext, new FragmentWindowAndroid(mContext, this)); + } + } + + @Override + public View onCreateView() { + return mBrowser.getFragmentView(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mBrowser.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + mBrowser.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mBrowser.destroy(); + mBrowser = null; + } + + @Override + public void onDetach() { + super.onDetach(); + // mBrowser != null if fragment is retained, otherwise onDestroy is called first. + if (mBrowser != null) { + mBrowser.onFragmentDetached(); + } + mContext = null; + } + + public IBrowserFragment asIBrowserFragment() { + return new IBrowserFragment.Stub() { + @Override + public IRemoteFragment asRemoteFragment() { + return BrowserFragmentImpl.this; + } + + @Override + public IBrowser getBrowser() { + if (mBrowser == null) { + throw new RuntimeException("Browser is available only between" + + " BrowserFragment's onCreate() and onDestroy()."); + } + return mBrowser; + } + }; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java new file mode 100644 index 00000000000..08449086bee --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java @@ -0,0 +1,192 @@ +// 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.content.Intent; +import android.os.Bundle; +import android.os.RemoteException; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.ValueCallback; + +import org.chromium.ui.base.WindowAndroid; +import org.chromium.weblayer_private.interfaces.APICallException; +import org.chromium.weblayer_private.interfaces.IBrowser; +import org.chromium.weblayer_private.interfaces.IBrowserClient; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.IProfile; +import org.chromium.weblayer_private.interfaces.ITab; +import org.chromium.weblayer_private.interfaces.ObjectWrapper; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link IBrowser}. + */ +public class BrowserImpl extends IBrowser.Stub { + private final ProfileImpl mProfile; + private BrowserViewController mViewController; + private FragmentWindowAndroid mWindowAndroid; + private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>(); + private IBrowserClient mClient; + + public BrowserImpl(ProfileImpl profile, Bundle savedInstanceState) { + mProfile = profile; + // Restore tabs etc from savedInstanceState here. + } + + public WindowAndroid getWindowAndroid() { + return mWindowAndroid; + } + + public ViewGroup getViewAndroidDelegateContainerView() { + return mViewController.getContentView(); + } + + public void onFragmentAttached(Context context, FragmentWindowAndroid windowAndroid) { + mWindowAndroid = windowAndroid; + mViewController = new BrowserViewController(context, windowAndroid); + TabImpl tab = new TabImpl(mProfile, windowAndroid); + addTab(tab); + boolean set_active_result = setActiveTab(tab); + assert set_active_result; + } + + public void onFragmentDetached() { + destroy(); // For now we don't retain anything between detach and attach. + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mWindowAndroid != null) { + mWindowAndroid.onActivityResult(requestCode, resultCode, data); + } + } + + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + if (mWindowAndroid != null) { + mWindowAndroid.handlePermissionResult(requestCode, permissions, grantResults); + } + } + + @Override + public void setTopView(IObjectWrapper viewWrapper) { + getViewController().setTopView(ObjectWrapper.unwrap(viewWrapper, View.class)); + } + + @Override + public void setSupportsEmbedding(boolean enable, IObjectWrapper valueCallback) { + getViewController().setSupportsEmbedding(enable, + (ValueCallback<Boolean>) ObjectWrapper.unwrap(valueCallback, ValueCallback.class)); + } + + public BrowserViewController getViewController() { + if (mViewController == null) { + throw new RuntimeException("Currently Tab requires Activity context, so " + + "it exists only while BrowserFragment is attached to an Activity"); + } + return mViewController; + } + + @Override + public IProfile getProfile() { + return mProfile; + } + + @Override + public void addTab(ITab iTab) { + TabImpl tab = (TabImpl) iTab; + if (tab.getBrowser() == this) return; + addTabImpl(tab); + } + + private void addTabImpl(TabImpl tab) { + // Null case is only during initial creation. + if (tab.getBrowser() != this && tab.getBrowser() != null) { + tab.getBrowser().detachTab(tab); + } + mTabs.add(tab); + tab.attachToBrowser(this); + try { + if (mClient != null) mClient.onTabAdded(tab); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void detachTab(ITab iTab) { + TabImpl tab = (TabImpl) iTab; + if (tab.getBrowser() != this) return; + if (getActiveTab() == tab) setActiveTab(null); + mTabs.remove(tab); + try { + if (mClient != null) mClient.onTabRemoved(tab.getId()); + } catch (RemoteException e) { + throw new APICallException(e); + } + // This doesn't reset state on TabImpl as |browser| is either about to be + // destroyed, or switching to a different fragment. + } + + @Override + public boolean setActiveTab(ITab controller) { + TabImpl tab = (TabImpl) controller; + if (tab != null && tab.getBrowser() != this) return false; + mViewController.setActiveTab(tab); + try { + if (mClient != null) { + mClient.onActiveTabChanged(tab != null ? tab.getId() : 0); + } + } catch (RemoteException e) { + throw new APICallException(e); + } + return true; + } + + public TabImpl getActiveTab() { + return mViewController.getTab(); + } + + @Override + public List getTabs() { + return new ArrayList(mTabs); + } + + @Override + public int getActiveTabId() { + return getActiveTab() != null ? getActiveTab().getId() : 0; + } + + @Override + public void setClient(IBrowserClient client) { + mClient = client; + } + + @Override + public void destroyTab(ITab iTab) { + detachTab(iTab); + ((TabImpl) iTab).destroy(); + } + + public View getFragmentView() { + return getViewController().getView(); + } + + public void destroy() { + if (mViewController != null) { + mViewController.destroy(); + for (TabImpl tab : mTabs) { + tab.destroy(); + } + mViewController = null; + } + if (mWindowAndroid != null) { + mWindowAndroid.destroy(); + mWindowAndroid = null; + } + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserObserverProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserObserverProxy.java deleted file mode 100644 index 0cb684fce29..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserObserverProxy.java +++ /dev/null @@ -1,50 +0,0 @@ -// 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.os.RemoteException; -import android.util.AndroidRuntimeException; - -import org.chromium.base.Log; -import org.chromium.base.annotations.CalledByNative; -import org.chromium.base.annotations.JNINamespace; -import org.chromium.weblayer_private.aidl.IBrowserControllerClient; - -/** - * Owns the c++ BrowserObserverProxy class, which is responsible for forwarding all - * BrowserObserver calls to this class, which in turn forwards to the BrowserControllerClient. - * To avoid unnecessary IPC only one BrowserObserverProxy is created per BrowserController. - */ -@JNINamespace("weblayer") -public final class BrowserObserverProxy { - private static final String TAG = "WL_BObserverProxy"; - - private long mNativeBrowserObserverProxy; - private IBrowserControllerClient mClient; - - BrowserObserverProxy(long browserController, IBrowserControllerClient client) { - mClient = client; - mNativeBrowserObserverProxy = nativeCreateBrowserObsererProxy(this, browserController); - } - - public void destroy() { - nativeDeleteBrowserObserverProxy(mNativeBrowserObserverProxy); - mNativeBrowserObserverProxy = 0; - } - - @CalledByNative - private void displayURLChanged(String string) { - try { - mClient.displayURLChanged(string); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call displayURLChanged.", e); - throw new AndroidRuntimeException(e); - } - } - - private static native long nativeCreateBrowserObsererProxy( - BrowserObserverProxy proxy, long browserController); - private static native void nativeDeleteBrowserObserverProxy(long proxy); -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java new file mode 100644 index 00000000000..abda36312e4 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/BrowserViewController.java @@ -0,0 +1,155 @@ +// 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.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.webkit.ValueCallback; +import android.widget.FrameLayout; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.content_public.browser.WebContents; +import org.chromium.ui.base.ViewAndroidDelegate; +import org.chromium.ui.base.WindowAndroid; + +/** + * BrowserViewController controls the set of Views needed to show the WebContents. + */ +@JNINamespace("weblayer") +public final class BrowserViewController + implements TopControlsContainerView.Listener, + WebContentsGestureStateTracker.OnGestureStateChangedListener { + private final ContentViewRenderView mContentViewRenderView; + private final ContentView mContentView; + // Child of mContentViewRenderView, holds top-view from client. + private final TopControlsContainerView mTopControlsContainerView; + + private TabImpl mTab; + + private WebContentsGestureStateTracker mGestureStateTracker; + + /** + * The value of mCachedDoBrowserControlsShrinkRendererSize is set when + * WebContentsGestureStateTracker begins a gesture. This is necessary as the values should only + * change once a gesture is no longer under way. + */ + private boolean mCachedDoBrowserControlsShrinkRendererSize; + + public BrowserViewController(Context context, WindowAndroid windowAndroid) { + mContentViewRenderView = new ContentViewRenderView(context); + + mContentViewRenderView.onNativeLibraryLoaded( + windowAndroid, ContentViewRenderView.MODE_SURFACE_VIEW); + mTopControlsContainerView = + new TopControlsContainerView(context, mContentViewRenderView, this); + mContentView = ContentView.createContentView( + context, mTopControlsContainerView.getEventOffsetHandler()); + ViewAndroidDelegate viewAndroidDelegate = new ViewAndroidDelegate(mContentView) { + @Override + public void onTopControlsChanged(int topControlsOffsetY, int topContentOffsetY) { + mTopControlsContainerView.onTopControlsChanged( + topControlsOffsetY, topContentOffsetY); + } + }; + mContentViewRenderView.addView(mContentView, + new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.UNSPECIFIED_GRAVITY)); + mContentView.addView(mTopControlsContainerView, + new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, + Gravity.FILL_HORIZONTAL | Gravity.TOP)); + } + + public void destroy() { + setActiveTab(null); + mTopControlsContainerView.destroy(); + mContentViewRenderView.destroy(); + } + + /** Returns top-level View this Controller works with */ + public View getView() { + return mContentViewRenderView; + } + + public ViewGroup getContentView() { + return mContentView; + } + + public void setActiveTab(TabImpl tab) { + if (tab == mTab) return; + + if (mTab != null) { + mTab.onDidLoseActive(); + // WebContentsGestureStateTracker is relatively cheap, easier to destroy rather than + // update WebContents. + mGestureStateTracker.destroy(); + mGestureStateTracker = null; + } + mTab = tab; + WebContents webContents = mTab != null ? mTab.getWebContents() : null; + // Create the WebContentsGestureStateTracker before setting the WebContents on + // the views as they may call back to this class. + if (mTab != null) { + mGestureStateTracker = + new WebContentsGestureStateTracker(mContentView, webContents, this); + } + mContentView.setWebContents(webContents); + mContentViewRenderView.setWebContents(webContents); + mTopControlsContainerView.setWebContents(webContents); + if (mTab != null) { + mTab.onDidGainActive(mTopControlsContainerView.getNativeHandle()); + mContentView.requestFocus(); + } + } + + public TabImpl getTab() { + return mTab; + } + + public void setTopView(View view) { + mTopControlsContainerView.setView(view); + } + + @Override + public void onTopControlsCompletelyShownOrHidden() { + adjustWebContentsHeightIfNecessary(); + } + + @Override + public void onGestureStateChanged() { + if (mGestureStateTracker.isInGestureOrScroll()) { + mCachedDoBrowserControlsShrinkRendererSize = + mTopControlsContainerView.isTopControlVisible(); + } + adjustWebContentsHeightIfNecessary(); + } + + private void adjustWebContentsHeightIfNecessary() { + if (mGestureStateTracker.isInGestureOrScroll() + || !mTopControlsContainerView.isTopControlsCompletelyShownOrHidden()) { + return; + } + mContentViewRenderView.setWebContentsHeightDelta( + mTopControlsContainerView.getTopContentOffset()); + } + + public void setSupportsEmbedding(boolean enable, ValueCallback<Boolean> callback) { + mContentViewRenderView.requestMode(enable ? ContentViewRenderView.MODE_TEXTURE_VIEW + : ContentViewRenderView.MODE_SURFACE_VIEW, + callback); + } + + public void onTopControlsChanged(int topControlsOffsetY, int topContentOffsetY) { + mTopControlsContainerView.onTopControlsChanged(topControlsOffsetY, topContentOffsetY); + } + + public boolean doBrowserControlsShrinkRendererSize() { + return (mGestureStateTracker.isInGestureOrScroll()) + ? mCachedDoBrowserControlsShrinkRendererSize + : mTopControlsContainerView.isTopControlVisible(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ChildProcessServiceImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ChildProcessServiceImpl.java new file mode 100644 index 00000000000..ebfecb29e94 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ChildProcessServiceImpl.java @@ -0,0 +1,53 @@ +// 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.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; + +import org.chromium.base.annotations.UsedByReflection; +import org.chromium.base.process_launcher.ChildProcessService; +import org.chromium.components.embedder_support.application.ClassLoaderContextWrapperFactory; +import org.chromium.content_public.app.ChildProcessServiceFactory; +import org.chromium.weblayer_private.interfaces.IChildProcessService; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.ObjectWrapper; + +/** + * Implementation of IChildProcessService. + */ +@UsedByReflection("WebLayer") +public final class ChildProcessServiceImpl extends IChildProcessService.Stub { + private ChildProcessService mService; + + @UsedByReflection("WebLayer") + public static IBinder create(Service service, Context appContext) { + // Wrap the app context so that it can be used to load WebLayer implementation classes. + appContext = ClassLoaderContextWrapperFactory.get(appContext); + return new ChildProcessServiceImpl(service, appContext); + } + + @Override + public void onCreate() { + mService.onCreate(); + } + + @Override + public void onDestroy() { + mService.onDestroy(); + mService = null; + } + + @Override + public IObjectWrapper onBind(IObjectWrapper intent) { + return ObjectWrapper.wrap(mService.onBind(ObjectWrapper.unwrap(intent, Intent.class))); + } + + private ChildProcessServiceImpl(Service service, Context context) { + mService = ChildProcessServiceFactory.create(service, context); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java new file mode 100644 index 00000000000..65ff96fd0f8 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentView.java @@ -0,0 +1,490 @@ +// Copyright 2012 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.content.res.Configuration; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.view.DragEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnSystemUiVisibilityChangeListener; +import android.view.ViewGroup.OnHierarchyChangeListener; +import android.view.ViewStructure; +import android.view.accessibility.AccessibilityNodeProvider; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.widget.FrameLayout; + +import org.chromium.base.ObserverList; +import org.chromium.base.TraceEvent; +import org.chromium.base.compat.ApiHelperForO; +import org.chromium.content_public.browser.ImeAdapter; +import org.chromium.content_public.browser.RenderCoordinates; +import org.chromium.content_public.browser.SmartClipProvider; +import org.chromium.content_public.browser.ViewEventSink; +import org.chromium.content_public.browser.WebContents; +import org.chromium.content_public.browser.WebContentsAccessibility; +import org.chromium.ui.base.EventForwarder; +import org.chromium.ui.base.EventOffsetHandler; + +/** + * The containing view for {@link WebContents} that exists in the Android UI hierarchy and exposes + * the various {@link View} functionality to it. + */ +public class ContentView extends FrameLayout + implements ViewEventSink.InternalAccessDelegate, SmartClipProvider, + OnHierarchyChangeListener, OnSystemUiVisibilityChangeListener { + private static final String TAG = "ContentView"; + + // Default value to signal that the ContentView's size need not be overridden. + public static final int DEFAULT_MEASURE_SPEC = + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + + private WebContents mWebContents; + private final ObserverList<OnHierarchyChangeListener> mHierarchyChangeListeners = + new ObserverList<>(); + private final ObserverList<OnSystemUiVisibilityChangeListener> mSystemUiChangeListeners = + new ObserverList<>(); + + /** + * The desired size of this view in {@link MeasureSpec}. Set by the host + * when it should be different from that of the parent. + */ + private int mDesiredWidthMeasureSpec = DEFAULT_MEASURE_SPEC; + private int mDesiredHeightMeasureSpec = DEFAULT_MEASURE_SPEC; + + private EventOffsetHandler mEventOffsetHandler; + + /** + * Constructs a new ContentView for the appropriate Android version. + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param webContents The WebContents managing this content view. + * @return an instance of a ContentView. + */ + public static ContentView createContentView( + Context context, EventOffsetHandler eventOffsetHandler) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return new ContentViewApi23(context, eventOffsetHandler); + } + return new ContentView(context, eventOffsetHandler); + } + + /** + * Creates an instance of a ContentView. + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param webContents A pointer to the WebContents managing this content view. + */ + ContentView(Context context, EventOffsetHandler eventOffsetHandler) { + super(context, null, android.R.attr.webViewStyle); + + if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) { + setHorizontalScrollBarEnabled(false); + setVerticalScrollBarEnabled(false); + } + + mEventOffsetHandler = eventOffsetHandler; + + setFocusable(true); + setFocusableInTouchMode(true); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ApiHelperForO.setDefaultFocusHighlightEnabled(this, false); + } + + setOnHierarchyChangeListener(this); + setOnSystemUiVisibilityChangeListener(this); + } + + protected WebContentsAccessibility getWebContentsAccessibility() { + return mWebContents != null && !mWebContents.isDestroyed() + ? WebContentsAccessibility.fromWebContents(mWebContents) + : null; + } + + public void setWebContents(WebContents webContents) { + boolean wasFocused = isFocused(); + boolean wasWindowFocused = hasWindowFocus(); + boolean wasAttached = isAttachedToWindow(); + if (wasFocused) onFocusChanged(false, View.FOCUS_FORWARD, null); + if (wasWindowFocused) onWindowFocusChanged(false); + if (wasAttached) onDetachedFromWindow(); + mWebContents = webContents; + if (wasFocused) onFocusChanged(true, View.FOCUS_FORWARD, null); + if (wasWindowFocused) onWindowFocusChanged(true); + if (wasAttached) onAttachedToWindow(); + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + WebContentsAccessibility wcax = getWebContentsAccessibility(); + return wcax != null && wcax.supportsAction(action) + ? wcax.performAction(action, arguments) + : super.performAccessibilityAction(action, arguments); + } + + /** + * Set the desired size of the view. The values are in {@link MeasureSpec}. + * @param width The width of the content view. + * @param height The height of the content view. + */ + public void setDesiredMeasureSpec(int width, int height) { + mDesiredWidthMeasureSpec = width; + mDesiredHeightMeasureSpec = height; + } + + @Override + public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { + assert listener == this : "Use add/removeOnHierarchyChangeListener instead."; + super.setOnHierarchyChangeListener(listener); + } + + /** + * Registers the given listener to receive state changes for the content view hierarchy. + * @param listener Listener to receive view hierarchy state changes. + */ + public void addOnHierarchyChangeListener(OnHierarchyChangeListener listener) { + mHierarchyChangeListeners.addObserver(listener); + } + + /** + * Unregisters the given listener from receiving state changes for the content view hierarchy. + * @param listener Listener that doesn't want to receive view hierarchy state changes. + */ + public void removeOnHierarchyChangeListener(OnHierarchyChangeListener listener) { + mHierarchyChangeListeners.removeObserver(listener); + } + + @Override + public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) { + assert listener == this : "Use add/removeOnSystemUiVisibilityChangeListener instead."; + super.setOnSystemUiVisibilityChangeListener(listener); + } + + /** + * Registers the given listener to receive system UI visibility state changes. + * @param listener Listener to receive system UI visibility state changes. + */ + public void addOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener listener) { + mSystemUiChangeListeners.addObserver(listener); + } + + /** + * Unregisters the given listener from receiving system UI visibility state changes. + * @param listener Listener that doesn't want to receive state changes. + */ + public void removeOnSystemUiVisibilityChangeListener( + OnSystemUiVisibilityChangeListener listener) { + mSystemUiChangeListeners.removeObserver(listener); + } + + // View.OnHierarchyChangeListener implementation + + @Override + public void onChildViewRemoved(View parent, View child) { + for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) { + listener.onChildViewRemoved(parent, child); + } + } + + @Override + public void onChildViewAdded(View parent, View child) { + for (OnHierarchyChangeListener listener : mHierarchyChangeListeners) { + listener.onChildViewAdded(parent, child); + } + } + + // View.OnHierarchyChangeListener implementation + + @Override + public void onSystemUiVisibilityChange(int visibility) { + for (OnSystemUiVisibilityChangeListener listener : mSystemUiChangeListeners) { + listener.onSystemUiVisibilityChange(visibility); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mDesiredWidthMeasureSpec != DEFAULT_MEASURE_SPEC) { + widthMeasureSpec = mDesiredWidthMeasureSpec; + } + if (mDesiredHeightMeasureSpec != DEFAULT_MEASURE_SPEC) { + heightMeasureSpec = mDesiredHeightMeasureSpec; + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + public AccessibilityNodeProvider getAccessibilityNodeProvider() { + WebContentsAccessibility wcax = getWebContentsAccessibility(); + AccessibilityNodeProvider provider = + (wcax != null) ? wcax.getAccessibilityNodeProvider() : null; + return (provider != null) ? provider : super.getAccessibilityNodeProvider(); + } + + // Needed by ViewEventSink.InternalAccessDelegate + @Override + public void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + // Calls may come while/after WebContents is destroyed. See https://crbug.com/821750#c8. + if (mWebContents != null && mWebContents.isDestroyed()) return null; + return ImeAdapter.fromWebContents(mWebContents).onCreateInputConnection(outAttrs); + } + + @Override + public boolean onCheckIsTextEditor() { + if (mWebContents != null && mWebContents.isDestroyed()) return false; + return ImeAdapter.fromWebContents(mWebContents).onCheckIsTextEditor(); + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + try { + TraceEvent.begin("ContentView.onFocusChanged"); + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + if (mWebContents != null) { + getViewEventSink().setHideKeyboardOnBlur(true); + getViewEventSink().onViewFocusChanged(gainFocus); + } + } finally { + TraceEvent.end("ContentView.onFocusChanged"); + } + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (mWebContents != null) { + getViewEventSink().onWindowFocusChanged(hasWindowFocus); + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return getEventForwarder().onKeyUp(keyCode, event); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return isFocused() ? getEventForwarder().dispatchKeyEvent(event) + : super.dispatchKeyEvent(event); + } + + @Override + public boolean onDragEvent(DragEvent event) { + return getEventForwarder().onDragEvent(event, this); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + boolean ret = super.onInterceptTouchEvent(e); + mEventOffsetHandler.onInterceptTouchEvent(e); + return ret; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean ret = getEventForwarder().onTouchEvent(event); + mEventOffsetHandler.onTouchEvent(event); + return ret; + } + + @Override + public boolean onInterceptHoverEvent(MotionEvent e) { + mEventOffsetHandler.onInterceptHoverEvent(e); + return super.onInterceptHoverEvent(e); + } + + @Override + public boolean dispatchDragEvent(DragEvent e) { + mEventOffsetHandler.onPreDispatchDragEvent(e.getAction()); + boolean ret = super.dispatchDragEvent(e); + mEventOffsetHandler.onPostDispatchDragEvent(e.getAction()); + return ret; + } + + /** + * Mouse move events are sent on hover enter, hover move and hover exit. + * They are sent on hover exit because sometimes it acts as both a hover + * move and hover exit. + */ + @Override + public boolean onHoverEvent(MotionEvent event) { + boolean consumed = getEventForwarder().onHoverEvent(event); + WebContentsAccessibility wcax = getWebContentsAccessibility(); + if (wcax != null && !wcax.isTouchExplorationEnabled()) super.onHoverEvent(event); + return consumed; + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + return getEventForwarder().onGenericMotionEvent(event); + } + + private EventForwarder getEventForwarder() { + return mWebContents != null ? mWebContents.getEventForwarder() : null; + } + + private ViewEventSink getViewEventSink() { + return mWebContents != null ? ViewEventSink.from(mWebContents) : null; + } + + @Override + public boolean performLongClick() { + return false; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + if (mWebContents != null) { + getViewEventSink().onConfigurationChanged(newConfig); + } + super.onConfigurationChanged(newConfig); + } + + /** + * Currently the ContentView scrolling happens in the native side. In + * the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo() + * are overridden, so that View's mScrollX and mScrollY will be unchanged at + * (0, 0). This is critical for drawing ContentView correctly. + */ + @Override + public void scrollBy(int x, int y) { + getEventForwarder().scrollBy(x, y); + } + + @Override + public void scrollTo(int x, int y) { + getEventForwarder().scrollTo(x, y); + } + + @Override + protected int computeHorizontalScrollExtent() { + RenderCoordinates rc = getRenderCoordinates(); + return rc != null ? rc.getLastFrameViewportWidthPixInt() : 0; + } + + @Override + protected int computeHorizontalScrollOffset() { + RenderCoordinates rc = getRenderCoordinates(); + return rc != null ? rc.getScrollXPixInt() : 0; + } + + @Override + protected int computeHorizontalScrollRange() { + RenderCoordinates rc = getRenderCoordinates(); + return rc != null ? rc.getContentWidthPixInt() : 0; + } + + @Override + protected int computeVerticalScrollExtent() { + RenderCoordinates rc = getRenderCoordinates(); + return rc != null ? rc.getLastFrameViewportHeightPixInt() : 0; + } + + @Override + protected int computeVerticalScrollOffset() { + RenderCoordinates rc = getRenderCoordinates(); + return rc != null ? rc.getScrollYPixInt() : 0; + } + + @Override + protected int computeVerticalScrollRange() { + RenderCoordinates rc = getRenderCoordinates(); + return rc != null ? rc.getContentHeightPixInt() : 0; + } + + private RenderCoordinates getRenderCoordinates() { + return mWebContents != null ? RenderCoordinates.fromWebContents(mWebContents) : null; + } + + // End FrameLayout overrides. + + @Override + public boolean awakenScrollBars(int startDelay, boolean invalidate) { + // For the default implementation of ContentView which draws the scrollBars on the native + // side, calling this function may get us into a bad state where we keep drawing the + // scrollBars, so disable it by always returning false. + if (getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) return false; + return super.awakenScrollBars(startDelay, invalidate); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mWebContents != null) { + getViewEventSink().onAttachedToWindow(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mWebContents != null) { + getViewEventSink().onDetachedFromWindow(); + } + } + + // Implements SmartClipProvider + @Override + public void extractSmartClipData(int x, int y, int width, int height) { + if (mWebContents != null) { + mWebContents.requestSmartClipExtract(x, y, width, height); + } + } + + // Implements SmartClipProvider + @Override + public void setSmartClipResultHandler(final Handler resultHandler) { + if (mWebContents != null) { + mWebContents.setSmartClipResultHandler(resultHandler); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Start Implementation of ViewEventSink.InternalAccessDelegate // + /////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean super_onKeyUp(int keyCode, KeyEvent event) { + return super.onKeyUp(keyCode, event); + } + + @Override + public boolean super_dispatchKeyEvent(KeyEvent event) { + return super.dispatchKeyEvent(event); + } + + @Override + public boolean super_onGenericMotionEvent(MotionEvent event) { + return super.onGenericMotionEvent(event); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // End Implementation of ViewEventSink.InternalAccessDelegate // + /////////////////////////////////////////////////////////////////////////////////////////////// + + private static class ContentViewApi23 extends ContentView { + public ContentViewApi23(Context context, EventOffsetHandler eventOffsetHandler) { + super(context, eventOffsetHandler); + } + + @Override + public void onProvideVirtualStructure(final ViewStructure structure) { + WebContentsAccessibility wcax = getWebContentsAccessibility(); + if (wcax != null) wcax.onProvideVirtualStructure(structure, false); + } + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java new file mode 100644 index 00000000000..ca9f78ff358 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ContentViewRenderView.java @@ -0,0 +1,695 @@ +// Copyright 2012 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.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.SurfaceTexture; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.ValueCallback; +import android.widget.FrameLayout; + +import androidx.annotation.IntDef; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.base.task.PostTask; +import org.chromium.content_public.browser.UiThreadTaskTraits; +import org.chromium.content_public.browser.WebContents; +import org.chromium.ui.base.WindowAndroid; +import org.chromium.ui.resources.ResourceManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * This class manages the chromium compositor and the Surface that is used by + * the chromium compositor. Note it can be used to display only one WebContents. + * This allows switching between SurfaceView and TextureView as the source of + * the Surface used by chromium compositor, and attempts to make the switch + * visually seamless. + */ +@JNINamespace("weblayer") +public class ContentViewRenderView extends FrameLayout { + @Retention(RetentionPolicy.SOURCE) + @IntDef({MODE_SURFACE_VIEW, MODE_SURFACE_VIEW}) + public @interface Mode {} + public static final int MODE_SURFACE_VIEW = 0; + public static final int MODE_TEXTURE_VIEW = 1; + + // This is mode that is requested by client. + private SurfaceData mRequested; + // This is the mode that last supplied the Surface to the compositor. + // This should generally be equal to |mRequested| except during transitions. + private SurfaceData mCurrent; + + // The native side of this object. + private long mNativeContentViewRenderView; + + private WindowAndroid mWindowAndroid; + private WebContents mWebContents; + + private int mBackgroundColor; + private int mWidth; + private int mHeight; + + private int mWebContentsHeightDelta; + + // Common interface to listen to surface related events. + private interface SurfaceEventListener { + void surfaceCreated(); + void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, + int width, int height); + // |cacheBackBuffer| will delay destroying the EGLSurface until after the next swap. + void surfaceDestroyed(boolean cacheBackBuffer); + } + + private final ArrayList<TrackedRunnable> mPendingRunnables = new ArrayList<>(); + + // Runnables posted via View.postOnAnimation may not run after the view is detached, + // if nothing else causes animation. However a pending runnable may held by a GC root + // from the thread itself, and thus can cause leaks. This class here is so ensure that + // on destroy, all pending tasks are run immediately so they do not lead to leaks. + private abstract class TrackedRunnable implements Runnable { + private boolean mHasRun; + public TrackedRunnable() { + mPendingRunnables.add(this); + } + + @Override + public final void run() { + // View.removeCallbacks is not always reliable, and may run the callback even + // after it has been removed. + if (mHasRun) return; + assert mPendingRunnables.contains(this); + mPendingRunnables.remove(this); + mHasRun = true; + doRun(); + } + + protected abstract void doRun(); + } + + // Non-static implementation of SurfaceEventListener that forward calls to native Compositor. + // It is also responsible for updating |mRequested| and |mCurrent|. + private class SurfaceEventListenerImpl implements SurfaceEventListener { + private SurfaceData mSurfaceData; + + public void setRequestData(SurfaceData surfaceData) { + assert mSurfaceData == null; + mSurfaceData = surfaceData; + } + + @Override + public void surfaceCreated() { + assert mNativeContentViewRenderView != 0; + assert mSurfaceData == ContentViewRenderView.this.mRequested + || mSurfaceData == ContentViewRenderView.this.mCurrent; + if (ContentViewRenderView.this.mCurrent != null + && ContentViewRenderView.this.mCurrent != mSurfaceData) { + ContentViewRenderView.this.mCurrent.markForDestroy(true /* hasNextSurface */); + mSurfaceData.setSurfaceDataNeedsDestroy(ContentViewRenderView.this.mCurrent); + } + ContentViewRenderView.this.mCurrent = mSurfaceData; + ContentViewRenderViewJni.get().surfaceCreated(mNativeContentViewRenderView); + } + + @Override + public void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, + int width, int height) { + assert mNativeContentViewRenderView != 0; + assert mSurfaceData == ContentViewRenderView.this.mCurrent; + ContentViewRenderViewJni.get().surfaceChanged(mNativeContentViewRenderView, + canBeUsedWithSurfaceControl, format, width, height, surface); + if (mWebContents != null) { + ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged( + mNativeContentViewRenderView, mWebContents, width, height); + } + } + + @Override + public void surfaceDestroyed(boolean cacheBackBuffer) { + assert mNativeContentViewRenderView != 0; + assert mSurfaceData == ContentViewRenderView.this.mCurrent; + ContentViewRenderViewJni.get().surfaceDestroyed( + mNativeContentViewRenderView, cacheBackBuffer); + } + } + + // Abstract differences between SurfaceView and TextureView behind this class. + // Also responsible for holding and calling callbacks. + private class SurfaceData implements SurfaceEventListener { + private class TextureViewWithInvalidate extends TextureView { + public TextureViewWithInvalidate(Context context) { + super(context); + } + + @Override + public void invalidate() { + // TextureView is invalidated when it receives a new frame from its SurfaceTexture. + // This is a safe place to indicate that this TextureView now has content and is + // ready to be shown. + super.invalidate(); + destroyPreviousData(); + } + } + + @Mode + private final int mMode; + private final SurfaceEventListener mListener; + private final FrameLayout mParent; + private final Runnable mEvict; + + private boolean mRanCallbacks; + private boolean mMarkedForDestroy; + private boolean mCachedSurfaceNeedsEviction; + + private boolean mNeedsOnSurfaceDestroyed; + + // During transitioning between two SurfaceData, there is a complicated series of calls to + // avoid visual artifacts. + // 1) Allocate new SurfaceData, and insert it into view hierarchy below the existing + // SurfaceData, so it is not yet showing. + // 2) When Surface is allocated by new View, swap chromium compositor to the + // new Surface. |markForDestroy| is called on the previous SurfaceData, and the two + // SurfaceDatas are linked through these two variables. + // Note at this point the existing view is still visible. + // 3) Wait until new SurfaceData decides that it has content and is ready to be shown + // * For TextureView, wait until TextureView.invalidate is called + // * For SurfaceView, wait for two swaps from the chromium compositor + // 4) New SurfaceData calls |destroy| on previous SurfaceData. + // * For TextureView, it is simply detached immediately from the view tree + // * For SurfaceView, to avoid flicker, move it to the back first before and wait + // two frames before detaching. + // 5) Previous SurfaceData runs callbacks on the new SurfaceData to signal the completion + // of the transition. + private SurfaceData mPrevSurfaceDataNeedsDestroy; + private SurfaceData mNextSurfaceDataNeedsRunCallback; + + private final SurfaceHolderCallback mSurfaceCallback; + private final SurfaceView mSurfaceView; + private int mNumSurfaceViewSwapsUntilVisible; + + private final TextureView mTextureView; + private final TextureViewSurfaceTextureListener mSurfaceTextureListener; + + private final ArrayList<ValueCallback<Boolean>> mModeCallbacks = new ArrayList<>(); + + public SurfaceData(@Mode int mode, FrameLayout parent, SurfaceEventListener listener, + int backgroundColor, Runnable evict) { + mMode = mode; + mListener = listener; + mParent = parent; + mEvict = evict; + if (mode == MODE_SURFACE_VIEW) { + mSurfaceView = new SurfaceView(parent.getContext()); + mSurfaceView.setZOrderMediaOverlay(true); + mSurfaceView.setBackgroundColor(backgroundColor); + + mSurfaceCallback = new SurfaceHolderCallback(this); + mSurfaceView.getHolder().addCallback(mSurfaceCallback); + mSurfaceView.setVisibility(View.VISIBLE); + + // TODO(boliu): This is only needed when video is lifted into a separate surface. + // Keeping this constantly will use one more byte per pixel constantly. + mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); + + mTextureView = null; + mSurfaceTextureListener = null; + } else if (mode == MODE_TEXTURE_VIEW) { + mTextureView = new TextureViewWithInvalidate(parent.getContext()); + mSurfaceTextureListener = new TextureViewSurfaceTextureListener(this); + mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); + mTextureView.setVisibility(VISIBLE); + + mSurfaceView = null; + mSurfaceCallback = null; + } else { + throw new RuntimeException("Illegal mode: " + mode); + } + + // This postOnAnimation is to avoid manipulating the view tree inside layout or draw. + parent.postOnAnimation(new TrackedRunnable() { + @Override + protected void doRun() { + if (mMarkedForDestroy) return; + View view = (mMode == MODE_SURFACE_VIEW) ? mSurfaceView : mTextureView; + assert view != null; + // Always insert view for new surface below the existing view to avoid artifacts + // during surface swaps. Index 0 is the lowest child. + mParent.addView(view, 0, + new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + mParent.invalidate(); + } + }); + } + + public void setSurfaceDataNeedsDestroy(SurfaceData surfaceData) { + assert !mMarkedForDestroy; + assert mPrevSurfaceDataNeedsDestroy == null; + mPrevSurfaceDataNeedsDestroy = surfaceData; + mPrevSurfaceDataNeedsDestroy.mNextSurfaceDataNeedsRunCallback = this; + } + + public @Mode int getMode() { + return mMode; + } + + public void addCallback(ValueCallback<Boolean> callback) { + assert !mMarkedForDestroy; + mModeCallbacks.add(callback); + if (mRanCallbacks) runCallbacks(); + } + + // Tearing down is separated into markForDestroy and destroy. After markForDestroy + // this class will is guaranteed to not issue any calls to its SurfaceEventListener. + public void markForDestroy(boolean hasNextSurface) { + if (mMarkedForDestroy) return; + mMarkedForDestroy = true; + + if (mNeedsOnSurfaceDestroyed) { + // SurfaceView being used with SurfaceControl need to cache the back buffer + // (EGLSurface). Otherwise the surface is destroyed immediate before the + // SurfaceView is detached. + mCachedSurfaceNeedsEviction = hasNextSurface && mMode == MODE_SURFACE_VIEW; + mListener.surfaceDestroyed(mCachedSurfaceNeedsEviction); + mNeedsOnSurfaceDestroyed = false; + } + + if (mMode == MODE_SURFACE_VIEW) { + mSurfaceView.getHolder().removeCallback(mSurfaceCallback); + } else if (mMode == MODE_TEXTURE_VIEW) { + mTextureView.setSurfaceTextureListener(null); + } else { + assert false; + } + } + + // Remove view from parent hierarchy. + public void destroy() { + assert mMarkedForDestroy; + runCallbacks(); + // This postOnAnimation is to avoid manipulating the view tree inside layout or draw. + mParent.postOnAnimation(new TrackedRunnable() { + @Override + protected void doRun() { + if (mMode == MODE_SURFACE_VIEW) { + // Detaching a SurfaceView causes a flicker because the SurfaceView tears + // down the Surface in SurfaceFlinger before removing its hole in the view + // tree. This is a complicated heuristics to avoid this. It first moves the + // SurfaceView behind the new View. Then wait two frames before detaching + // the SurfaceView. Waiting for a single frame still causes flickers on + // high end devices like Pixel 3. + moveChildToBackWithoutDetach(mParent, mSurfaceView); + TrackedRunnable inner = new TrackedRunnable() { + @Override + public void doRun() { + mParent.removeView(mSurfaceView); + mParent.invalidate(); + if (mCachedSurfaceNeedsEviction) { + mEvict.run(); + mCachedSurfaceNeedsEviction = false; + } + runCallbackOnNextSurfaceData(); + } + }; + TrackedRunnable outer = new TrackedRunnable() { + @Override + public void doRun() { + mParent.postOnAnimation(inner); + } + }; + mParent.postOnAnimation(outer); + } else if (mMode == MODE_TEXTURE_VIEW) { + mParent.removeView(mTextureView); + runCallbackOnNextSurfaceData(); + } else { + assert false; + } + } + }); + } + + private void moveChildToBackWithoutDetach(ViewGroup parent, View child) { + final int numberOfChildren = parent.getChildCount(); + final int childIndex = parent.indexOfChild(child); + if (childIndex <= 0) return; + for (int i = 0; i < childIndex; ++i) { + parent.bringChildToFront(parent.getChildAt(0)); + } + assert parent.indexOfChild(child) == 0; + for (int i = 0; i < numberOfChildren - childIndex - 1; ++i) { + parent.bringChildToFront(parent.getChildAt(1)); + } + parent.invalidate(); + } + + public void setBackgroundColor(int color) { + assert !mMarkedForDestroy; + if (mMode == MODE_SURFACE_VIEW) { + mSurfaceView.setBackgroundColor(color); + } + } + + /** @return true if should keep swapping frames */ + public boolean didSwapFrame() { + if (mSurfaceView != null && mSurfaceView.getBackground() != null) { + mSurfaceView.post(new Runnable() { + @Override + public void run() { + if (mSurfaceView != null) mSurfaceView.setBackgroundResource(0); + } + }); + } + if (mMode == MODE_SURFACE_VIEW) { + // We have no reliable signal for when to show a SurfaceView. This is a heuristic + // (used by chrome as well) is to wait for 2 swaps from the chromium comopsitor + // as a signal that the SurfaceView has content and is ready to be displayed. + if (mNumSurfaceViewSwapsUntilVisible > 0) { + mNumSurfaceViewSwapsUntilVisible--; + } + if (mNumSurfaceViewSwapsUntilVisible == 0) { + destroyPreviousData(); + } + return mNumSurfaceViewSwapsUntilVisible > 0; + } + return false; + } + + private void destroyPreviousData() { + if (mPrevSurfaceDataNeedsDestroy != null) { + mPrevSurfaceDataNeedsDestroy.destroy(); + mPrevSurfaceDataNeedsDestroy = null; + } + } + + @Override + public void surfaceCreated() { + if (mMarkedForDestroy) return; + + // On pre-M Android, layers start in the hidden state until a relayout happens. + // There is a bug that manifests itself when entering overlay mode on pre-M devices, + // where a relayout never happens. This bug is out of Chromium's control, but can be + // worked around by forcibly re-setting the visibility of the surface view. + // Otherwise, the screen stays black, and some tests fail. + if (mSurfaceView != null) { + mSurfaceView.setVisibility(mSurfaceView.getVisibility()); + } + mListener.surfaceCreated(); + + if (!mRanCallbacks && mPrevSurfaceDataNeedsDestroy == null) { + runCallbacks(); + } + + mNeedsOnSurfaceDestroyed = true; + } + + @Override + public void surfaceChanged(Surface surface, boolean canBeUsedWithSurfaceControl, int format, + int width, int height) { + if (mMarkedForDestroy) return; + mListener.surfaceChanged(surface, canBeUsedWithSurfaceControl, format, width, height); + mNumSurfaceViewSwapsUntilVisible = 2; + } + + @Override + public void surfaceDestroyed(boolean cacheBackBuffer) { + if (mMarkedForDestroy) return; + assert mNeedsOnSurfaceDestroyed; + mListener.surfaceDestroyed(cacheBackBuffer); + mNeedsOnSurfaceDestroyed = false; + } + + private void runCallbacks() { + mRanCallbacks = true; + if (mModeCallbacks.isEmpty()) return; + // PostTask to avoid possible reentrancy problems with embedder code. + PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> { + ArrayList<ValueCallback<Boolean>> clone = + (ArrayList<ValueCallback<Boolean>>) mModeCallbacks.clone(); + mModeCallbacks.clear(); + for (ValueCallback<Boolean> run : clone) { + run.onReceiveValue(!mMarkedForDestroy); + } + }); + } + + private void runCallbackOnNextSurfaceData() { + if (mNextSurfaceDataNeedsRunCallback != null) { + mNextSurfaceDataNeedsRunCallback.runCallbacks(); + mNextSurfaceDataNeedsRunCallback = null; + } + } + } + + // Adapter for SurfaceHoolder.Callback. + private static class SurfaceHolderCallback implements SurfaceHolder.Callback { + private final SurfaceEventListener mListener; + + public SurfaceHolderCallback(SurfaceEventListener listener) { + mListener = listener; + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mListener.surfaceCreated(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + mListener.surfaceChanged(holder.getSurface(), true, format, width, height); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mListener.surfaceDestroyed(false /* cacheBackBuffer */); + } + } + + // Adapter for TextureView.SurfaceTextureListener. + private static class TextureViewSurfaceTextureListener + implements TextureView.SurfaceTextureListener { + private final SurfaceEventListener mListener; + + private SurfaceTexture mCurrentSurfaceTexture; + private Surface mCurrentSurface; + + public TextureViewSurfaceTextureListener(SurfaceEventListener listener) { + mListener = listener; + } + + @Override + public void onSurfaceTextureAvailable( + SurfaceTexture surfaceTexture, int width, int height) { + mListener.surfaceCreated(); + onSurfaceTextureSizeChanged(surfaceTexture, width, height); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + mListener.surfaceDestroyed(false /* cacheBackBuffer */); + return true; + } + + @Override + public void onSurfaceTextureSizeChanged( + SurfaceTexture surfaceTexture, int width, int height) { + if (mCurrentSurfaceTexture != surfaceTexture) { + mCurrentSurfaceTexture = surfaceTexture; + mCurrentSurface = new Surface(mCurrentSurfaceTexture); + } + mListener.surfaceChanged(mCurrentSurface, false, PixelFormat.OPAQUE, width, height); + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {} + } + + /** + * Constructs a new ContentViewRenderView. + * This should be called and the {@link ContentViewRenderView} should be added to the view + * hierarchy before the first draw to avoid a black flash that is seen every time a + * {@link SurfaceView} is added. + * @param context The context used to create this. + */ + public ContentViewRenderView(Context context) { + super(context); + setBackgroundColor(Color.WHITE); + } + + /** + * Initialization that requires native libraries should be done here. + * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer. + * @param rootWindow The {@link WindowAndroid} this render view should be linked to. + */ + public void onNativeLibraryLoaded(WindowAndroid rootWindow, @Mode int mode) { + assert rootWindow != null; + mNativeContentViewRenderView = + ContentViewRenderViewJni.get().init(ContentViewRenderView.this, rootWindow); + assert mNativeContentViewRenderView != 0; + mWindowAndroid = rootWindow; + requestMode(mode, (Boolean result) -> {}); + } + + public void requestMode(@Mode int mode, ValueCallback<Boolean> callback) { + assert mode == MODE_SURFACE_VIEW || mode == MODE_TEXTURE_VIEW; + assert callback != null; + if (mRequested != null && mRequested.getMode() != mode) { + if (mRequested != mCurrent) { + mRequested.markForDestroy(false /* hasNextSurface */); + mRequested.destroy(); + } + mRequested = null; + } + + if (mRequested == null) { + SurfaceEventListenerImpl listener = new SurfaceEventListenerImpl(); + mRequested = new SurfaceData( + mode, this, listener, mBackgroundColor, this::evictCachedSurface); + listener.setRequestData(mRequested); + } + assert mRequested.getMode() == mode; + mRequested.addCallback(callback); + } + + /** + * Sets how much to decrease the height of the WebContents by. + */ + public void setWebContentsHeightDelta(int delta) { + if (delta == mWebContentsHeightDelta) return; + mWebContentsHeightDelta = delta; + updateWebContentsSize(); + } + + private void updateWebContentsSize() { + if (mWebContents == null) return; + mWebContents.setSize(mWidth, mHeight - mWebContentsHeightDelta); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mWidth = w; + mHeight = h; + updateWebContentsSize(); + } + + /** + * View's method override to notify WindowAndroid about changes in its visibility. + */ + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + + if (mWindowAndroid == null) return; + + if (visibility == View.GONE) { + mWindowAndroid.onVisibilityChanged(false); + } else if (visibility == View.VISIBLE) { + mWindowAndroid.onVisibilityChanged(true); + } + } + + /** + * Sets the background color of the surface / texture view. This method is necessary because + * the background color of ContentViewRenderView itself is covered by the background of + * SurfaceView. + * @param color The color of the background. + */ + @Override + public void setBackgroundColor(int color) { + super.setBackgroundColor(color); + mBackgroundColor = color; + if (mRequested != null) { + mRequested.setBackgroundColor(color); + } + if (mCurrent != null) { + mCurrent.setBackgroundColor(color); + } + } + + /** + * Should be called when the ContentViewRenderView is not needed anymore so its associated + * native resource can be freed. + */ + public void destroy() { + if (mRequested != null) { + mRequested.markForDestroy(false /* hasNextSurface */); + mRequested.destroy(); + if (mCurrent != null && mCurrent != mRequested) { + mCurrent.markForDestroy(false /* hasNextSurface */); + mCurrent.destroy(); + } + } + mRequested = null; + mCurrent = null; + + mWindowAndroid = null; + + while (!mPendingRunnables.isEmpty()) { + TrackedRunnable runnable = mPendingRunnables.get(0); + removeCallbacks(runnable); + runnable.run(); + assert !mPendingRunnables.contains(runnable); + } + ContentViewRenderViewJni.get().destroy(mNativeContentViewRenderView); + mNativeContentViewRenderView = 0; + } + + public void setWebContents(WebContents webContents) { + assert mNativeContentViewRenderView != 0; + mWebContents = webContents; + + if (webContents != null) { + updateWebContentsSize(); + ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged( + mNativeContentViewRenderView, webContents, mWidth, mHeight); + } + ContentViewRenderViewJni.get().setCurrentWebContents( + mNativeContentViewRenderView, webContents); + } + + public ResourceManager getResourceManager() { + return ContentViewRenderViewJni.get().getResourceManager(mNativeContentViewRenderView); + } + + @CalledByNative + private boolean didSwapFrame() { + assert mCurrent != null; + return mCurrent.didSwapFrame(); + } + + private void evictCachedSurface() { + if (mNativeContentViewRenderView == 0) return; + ContentViewRenderViewJni.get().evictCachedSurface(mNativeContentViewRenderView); + } + + public long getNativeHandle() { + return mNativeContentViewRenderView; + } + + @NativeMethods + interface Natives { + long init(ContentViewRenderView caller, WindowAndroid rootWindow); + void destroy(long nativeContentViewRenderView); + void setCurrentWebContents(long nativeContentViewRenderView, WebContents webContents); + void onPhysicalBackingSizeChanged( + long nativeContentViewRenderView, WebContents webContents, int width, int height); + void surfaceCreated(long nativeContentViewRenderView); + void surfaceDestroyed(long nativeContentViewRenderView, boolean cacheBackBuffer); + void surfaceChanged(long nativeContentViewRenderView, boolean canBeUsedWithSurfaceControl, + int format, int width, int height, Surface surface); + void evictCachedSurface(long nativeContentViewRenderView); + ResourceManager getResourceManager(long nativeContentViewRenderView); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java new file mode 100644 index 00000000000..ce39d8ef129 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/CrashReporterControllerImpl.java @@ -0,0 +1,276 @@ +// 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.os.Bundle; +import android.os.RemoteException; +import android.util.AndroidRuntimeException; + +import androidx.annotation.Nullable; + +import org.json.JSONException; +import org.json.JSONObject; + +import org.chromium.base.Log; +import org.chromium.base.PathUtils; +import org.chromium.base.task.AsyncTask; +import org.chromium.components.crash.browser.ChildProcessCrashObserver; +import org.chromium.components.minidump_uploader.CrashFileManager; +import org.chromium.weblayer_private.interfaces.ICrashReporterController; +import org.chromium.weblayer_private.interfaces.ICrashReporterControllerClient; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; + +/** + * Provides the implementation of the API for managing captured crash reports. + * + * @see org.chromium.weblayer.CrashReporterController + */ +public final class CrashReporterControllerImpl extends ICrashReporterController.Stub { + private static final String TAG = "CrashReporter"; + private static final int MAX_UPLOAD_RETRIES = 3; + + @Nullable + private ICrashReporterControllerClient mClient; + private CrashFileManager mCrashFileManager; + private boolean mIsNativeInitialized; + + private static class Holder { + static CrashReporterControllerImpl sInstance = new CrashReporterControllerImpl(); + } + + private CrashReporterControllerImpl() { + ChildProcessCrashObserver.registerCrashCallback( + new ChildProcessCrashObserver.ChildCrashedCallback() { + @Override + public void childCrashed(int pid) { + processNewMinidumps(); + } + }); + } + + public static CrashReporterControllerImpl getInstance(IObjectWrapper appContextWrapper) { + // This is a no-op if init has already happened. + WebLayerImpl.minimalInitForContext(appContextWrapper); + return Holder.sInstance; + } + + public void notifyNativeInitialized() { + mIsNativeInitialized = true; + if (mClient != null) { + processNewMinidumps(); + } + } + + @Override + public void deleteCrash(String localId) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + deleteCrashOnBackgroundThread(localId); + try { + mClient.onCrashDeleted(localId); + } catch (RemoteException e) { + throw new AndroidRuntimeException(e); + } + }); + } + + @Override + public void uploadCrash(String localId) { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + File minidumpFile = getCrashFileManager().getCrashFileWithLocalId(localId); + MinidumpUploader.Result result = new MinidumpUploader().upload(minidumpFile); + if (result.mSuccess) { + CrashFileManager.markUploadSuccess(minidumpFile); + } else { + CrashFileManager.tryIncrementAttemptNumber(minidumpFile); + } + try { + if (result.mSuccess) { + mClient.onCrashUploadSucceeded(localId, result.mResult); + } else { + mClient.onCrashUploadFailed(localId, result.mResult); + } + } catch (RemoteException e) { + throw new AndroidRuntimeException(e); + } + }); + } + + @Override + public @Nullable Bundle getCrashKeys(String localId) { + JSONObject data = readSidecar(localId); + if (data == null) { + return null; + } + Bundle result = new Bundle(); + Iterator<String> iter = data.keys(); + while (iter.hasNext()) { + String key = iter.next(); + try { + result.putCharSequence(key, data.getString(key)); + } catch (JSONException e) { + // Skip non-string values. + } + } + return result; + } + + @Override + public void checkForPendingCrashReports() { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + try { + mClient.onPendingCrashReports(getPendingMinidumpsOnBackgroundThread()); + } catch (RemoteException e) { + throw new AndroidRuntimeException(e); + } + }); + } + + @Override + public void setClient(ICrashReporterControllerClient client) { + mClient = client; + if (mIsNativeInitialized) { + processNewMinidumps(); + } + } + + /** Start an async task to import crashes, and notify if any are found. */ + private void processNewMinidumps() { + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + String[] localIds = processNewMinidumpsOnBackgroundThread(); + if (localIds.length > 0) { + try { + mClient.onPendingCrashReports(localIds); + } catch (RemoteException e) { + throw new AndroidRuntimeException(e); + } + } + }); + } + + /** Delete a crash report (and any sidecar file) given its local ID. */ + private void deleteCrashOnBackgroundThread(String localId) { + File minidumpFile = getCrashFileManager().getCrashFileWithLocalId(localId); + File sidecarFile = sidecarFile(localId); + if (minidumpFile != null) { + CrashFileManager.deleteFile(minidumpFile); + } + if (sidecarFile != null) { + CrashFileManager.deleteFile(sidecarFile); + } + } + + /** + * Determine the set of crashes that are currently ready to be uploaded. + * + * Clean out crashes that are too old, and return the any remaining crashes that have not + * exceeded their upload retry limit. + * + * @return An array of local IDs for crashes that are ready to be uploaded. + */ + private String[] getPendingMinidumpsOnBackgroundThread() { + getCrashFileManager().cleanOutAllNonFreshMinidumpFiles(); + File[] pendingMinidumps = + getCrashFileManager().getMinidumpsReadyForUpload(MAX_UPLOAD_RETRIES); + ArrayList<String> localIds = new ArrayList<>(pendingMinidumps.length); + for (File minidump : pendingMinidumps) { + localIds.add(CrashFileManager.getCrashLocalIdFromFileName(minidump.getName())); + } + return localIds.toArray(new String[0]); + } + + /** + * Use the CrashFileManager to import crashes from crashpad. + * + * For each imported crash, a sidecar file (in JSON format) is written, containing the + * crash keys that were recorded at the time of the crash. + * + * @return An array of local IDs of the new crashes (may be empty). + */ + private String[] processNewMinidumpsOnBackgroundThread() { + Map<String, Map<String, String>> crashesInfoMap = + getCrashFileManager().importMinidumpsCrashKeys(); + ArrayList<String> localIds = new ArrayList<>(crashesInfoMap.size()); + for (Map.Entry<String, Map<String, String>> entry : crashesInfoMap.entrySet()) { + JSONObject crashKeysJson = new JSONObject(entry.getValue()); + String uuid = entry.getKey(); + // TODO(tobiasjs): the minidump uploader uses the last component of the uuid as + // the local ID. The ergonomics of this should be improved. + localIds.add(CrashFileManager.getCrashLocalIdFromFileName(uuid + ".dmp")); + writeSidecar(uuid, crashKeysJson); + } + for (File minidump : getCrashFileManager().getMinidumpsSansLogcat()) { + CrashFileManager.trySetReadyForUpload(minidump); + } + return localIds.toArray(new String[0]); + } + + /** + * Generate a sidecar file path given a crash local ID. + * + * The sidecar file holds a JSON representation of the crash keys associated + * with the crash. All crash keys and values are strings. + */ + private @Nullable File sidecarFile(String localId) { + File minidumpFile = getCrashFileManager().getCrashFileWithLocalId(localId); + if (minidumpFile == null) { + return null; + } + String uuid = minidumpFile.getName().split("\\.")[0]; + return new File(minidumpFile.getParent(), uuid + ".json"); + } + + /** Write JSON formatted crash key data to the sidecar file for a crash. */ + private void writeSidecar(String localId, JSONObject data) { + File sidecar = sidecarFile(localId); + if (sidecar == null) { + return; + } + try (FileOutputStream out = new FileOutputStream(sidecar)) { + out.write(data.toString().getBytes("UTF-8")); + } catch (IOException e) { + Log.w(TAG, "Failed to write crash keys JSON for crash " + localId); + sidecar.delete(); + } + } + + /** Read JSON formatted crash key data previously written to a crash sidecar file. */ + private @Nullable JSONObject readSidecar(String localId) { + File sidecar = sidecarFile(localId); + if (sidecar == null) { + return null; + } + try (FileInputStream in = new FileInputStream(sidecar)) { + byte[] data = new byte[(int) sidecar.length()]; + int offset = 0; + while (offset < data.length) { + int count = in.read(data, offset, data.length - offset); + if (count <= 0) break; + offset += count; + } + return new JSONObject(new String(data, "UTF-8")); + } catch (IOException | JSONException e) { + return null; + } + } + + private CrashFileManager getCrashFileManager() { + if (mCrashFileManager == null) { + File cacheDir = new File(PathUtils.getCacheDirectory()); + // Make sure the cache dir has been created, since this may be called before WebLayer + // has been initialized. + cacheDir.mkdir(); + mCrashFileManager = new CrashFileManager(cacheDir); + } + return mCrashFileManager; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadCallbackProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadCallbackProxy.java new file mode 100644 index 00000000000..ad33b663a44 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/DownloadCallbackProxy.java @@ -0,0 +1,53 @@ +// 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.os.RemoteException; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient; + +/** + * Owns the c++ DownloadCallbackProxy class, which is responsible for forwarding all + * DownloadDelegate delegate calls to this class, which in turn forwards to the + * DownloadCallbackClient. + */ +@JNINamespace("weblayer") +public final class DownloadCallbackProxy { + private long mNativeDownloadCallbackProxy; + private IDownloadCallbackClient mClient; + + DownloadCallbackProxy(long tab, IDownloadCallbackClient client) { + assert client != null; + mClient = client; + mNativeDownloadCallbackProxy = + DownloadCallbackProxyJni.get().createDownloadCallbackProxy(this, tab); + } + + public void setClient(IDownloadCallbackClient client) { + assert client != null; + mClient = client; + } + + public void destroy() { + DownloadCallbackProxyJni.get().deleteDownloadCallbackProxy(mNativeDownloadCallbackProxy); + mNativeDownloadCallbackProxy = 0; + } + + @CalledByNative + private boolean interceptDownload(String url, String userAgent, String contentDisposition, + String mimetype, long contentLength) throws RemoteException { + return mClient.interceptDownload( + url, userAgent, contentDisposition, mimetype, contentLength); + } + + @NativeMethods + interface Natives { + long createDownloadCallbackProxy(DownloadCallbackProxy proxy, long tab); + void deleteDownloadCallbackProxy(long proxy); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ErrorPageCallbackProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ErrorPageCallbackProxy.java new file mode 100644 index 00000000000..639a2003568 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ErrorPageCallbackProxy.java @@ -0,0 +1,51 @@ +// 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.os.RemoteException; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient; + +/** + * Owns the c++ ErrorPageCallbackProxy class, which is responsible for forwarding all + * ErrorPageDelegate calls to this class, which in turn forwards to the + * ErrorPageCallbackClient. + */ +@JNINamespace("weblayer") +public final class ErrorPageCallbackProxy { + private long mNativeErrorPageCallbackProxy; + private IErrorPageCallbackClient mClient; + + ErrorPageCallbackProxy(long tab, IErrorPageCallbackClient client) { + assert client != null; + mClient = client; + mNativeErrorPageCallbackProxy = + ErrorPageCallbackProxyJni.get().createErrorPageCallbackProxy(this, tab); + } + + public void setClient(IErrorPageCallbackClient client) { + assert client != null; + mClient = client; + } + + public void destroy() { + ErrorPageCallbackProxyJni.get().deleteErrorPageCallbackProxy(mNativeErrorPageCallbackProxy); + mNativeErrorPageCallbackProxy = 0; + } + + @CalledByNative + private boolean onBackToSafety() throws RemoteException { + return mClient.onBackToSafety(); + } + + @NativeMethods + interface Natives { + long createErrorPageCallbackProxy(ErrorPageCallbackProxy proxy, long tab); + void deleteErrorPageCallbackProxy(long proxy); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationHandler.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationHandler.java new file mode 100644 index 00000000000..dc976a683e2 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationHandler.java @@ -0,0 +1,99 @@ +// 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.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.provider.Browser; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A class that handles navigations that should be transformed to intents. Logic taken primarly from + * //android_webview's AwContentsClient.java:sendBrowsingIntent(), with some additional logic + * from //android_webview's WebViewBrowserActivity.java:startBrowsingIntent(). + */ +@JNINamespace("weblayer") +public class ExternalNavigationHandler { + private static final String TAG = "ExternalNavHandler"; + + static final Pattern BROWSER_URI_SCHEMA = + Pattern.compile("(?i)" // switch on case insensitive matching + + "(" // begin group for schema + + "(?:http|https|file)://" + + "|(?:inline|data|about|chrome|javascript):" + + ")" + + ".*"); + + @CalledByNative + private static boolean shouldOverrideUrlLoading( + String url, boolean hasUserGesture, boolean isRedirect, boolean isMainFrame) { + // Check for regular URIs that WebLayer supports by itself. + // TODO(blundell): Port over WebViewBrowserActivity's + // isSpecializedHandlerAvailable() check that checks whether there's an app for handling + // the scheme? + Matcher m = BROWSER_URI_SCHEMA.matcher(url); + if (m.matches()) { + return false; + } + + if (!hasUserGesture && !isRedirect) { + Log.w(TAG, "Denied starting an intent without a user gesture, URI %s", url); + return true; + } + + Intent intent; + // Perform generic parsing of the URI to turn it into an Intent. + try { + intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); + } catch (Exception ex) { + Log.w(TAG, "Bad URI %s", url, ex); + return false; + } + // Sanitize the Intent, ensuring web pages can not bypass browser + // security (only access to BROWSABLE activities). + intent.addCategory(Intent.CATEGORY_BROWSABLE); + + // Ensure that startActivity() succeeds even if the application context + // isn't an Activity. This also matches Chrome's behavior (see + // //chrome's ExternalNavigationHandler.java:PrepareExternalIntent()). + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + intent.setComponent(null); + Intent selector = intent.getSelector(); + if (selector != null) { + selector.addCategory(Intent.CATEGORY_BROWSABLE); + selector.setComponent(null); + } + + Context context = ContextUtils.getApplicationContext(); + + // Pass the package name as application ID so that the intent from the + // same application can be opened in the same tab. + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + + try { + context.startActivity(intent); + return true; + } catch (ActivityNotFoundException ex) { + Log.w(TAG, "No application can handle %s", url); + } catch (SecurityException ex) { + // This can happen if the Activity is exported="true", guarded by a permission, and sets + // up an intent filter matching this intent. This is a valid configuration for an + // Activity, so instead of crashing, we catch the exception and do nothing. See + // https://crbug.com/808494 and https://crbug.com/889300. + Log.w(TAG, "SecurityException when starting intent for %s", url); + } + + return false; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java new file mode 100644 index 00000000000..bf19c8e96b2 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java @@ -0,0 +1,38 @@ +// 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 org.chromium.base.compat.ApiHelperForM; +import org.chromium.ui.base.AndroidPermissionDelegateWithRequester; + +/** + * AndroidPermissionDelegate implementation for BrowserFragment. + */ +public class FragmentAndroidPermissionDelegate extends AndroidPermissionDelegateWithRequester { + private BrowserFragmentImpl mFragment; + + public FragmentAndroidPermissionDelegate(BrowserFragmentImpl fragment) { + mFragment = fragment; + } + + @Override + protected final boolean shouldShowRequestPermissionRationale(String permission) { + if (mFragment.getActivity() == null) return false; + return mFragment.shouldShowRequestPermissionRationale(permission); + } + + @Override + protected final boolean isPermissionRevokedByPolicyInternal(String permission) { + if (mFragment.getActivity() == null) return false; + return ApiHelperForM.isPermissionRevokedByPolicy(mFragment.getActivity(), permission); + } + + @Override + protected final boolean requestPermissionsFromRequester(String[] permissions, int requestCode) { + if (mFragment.getActivity() == null) return false; + mFragment.requestPermissions(permissions, requestCode); + return true; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/FragmentWindowAndroid.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/FragmentWindowAndroid.java new file mode 100644 index 00000000000..7b6ba3392c2 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/FragmentWindowAndroid.java @@ -0,0 +1,47 @@ +// 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.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; + +import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate; +import org.chromium.ui.base.IntentWindowAndroid; + +import java.lang.ref.WeakReference; + +/** + * Implements intent sending for a fragment based window. This should be created when + * onAttach() is called on the fragment, and destroyed when onDetach() is called. + */ +public class FragmentWindowAndroid extends IntentWindowAndroid { + private BrowserFragmentImpl mFragment; + + FragmentWindowAndroid(Context context, BrowserFragmentImpl fragment) { + super(context); + mFragment = fragment; + + setKeyboardDelegate(new ActivityKeyboardVisibilityDelegate(getActivity())); + setAndroidPermissionDelegate(new FragmentAndroidPermissionDelegate(mFragment)); + } + + @Override + protected final boolean startIntentSenderForResult(IntentSender intentSender, int requestCode) { + return mFragment.startIntentSenderForResult( + intentSender, requestCode, new Intent(), 0, 0, 0, null); + } + + @Override + protected final boolean startActivityForResult(Intent intent, int requestCode) { + return mFragment.startActivityForResult(intent, requestCode, null); + } + + @Override + public final WeakReference<Activity> getActivity() { + return new WeakReference<>(mFragment.getActivity()); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/FullscreenCallbackProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/FullscreenCallbackProxy.java new file mode 100644 index 00000000000..fb6b8a31154 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/FullscreenCallbackProxy.java @@ -0,0 +1,70 @@ +// 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.os.RemoteException; +import android.webkit.ValueCallback; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.IFullscreenCallbackClient; +import org.chromium.weblayer_private.interfaces.ObjectWrapper; + +/** + * Owns the c++ FullscreenCallbackProxy class, which is responsible for forwarding all + * FullscreenDelegate delegate calls to this class, which in turn forwards to the + * FullscreenCallbackClient. + */ +@JNINamespace("weblayer") +public final class FullscreenCallbackProxy { + private long mNativeFullscreenCallbackProxy; + private IFullscreenCallbackClient mClient; + + FullscreenCallbackProxy(long tab, IFullscreenCallbackClient client) { + assert client != null; + mClient = client; + mNativeFullscreenCallbackProxy = + FullscreenCallbackProxyJni.get().createFullscreenCallbackProxy(this, tab); + } + + public void setClient(IFullscreenCallbackClient client) { + assert client != null; + mClient = client; + } + + public void destroy() { + FullscreenCallbackProxyJni.get().deleteFullscreenCallbackProxy( + mNativeFullscreenCallbackProxy); + mNativeFullscreenCallbackProxy = 0; + } + + @CalledByNative + private void enterFullscreen() throws RemoteException { + ValueCallback<Void> exitFullscreenCallback = new ValueCallback<Void>() { + @Override + public void onReceiveValue(Void result) { + if (mNativeFullscreenCallbackProxy == 0) { + throw new IllegalStateException("Called after destroy()"); + } + FullscreenCallbackProxyJni.get().doExitFullscreen( + mNativeFullscreenCallbackProxy, FullscreenCallbackProxy.this); + } + }; + mClient.enterFullscreen(ObjectWrapper.wrap(exitFullscreenCallback)); + } + + @CalledByNative + private void exitFullscreen() throws RemoteException { + mClient.exitFullscreen(); + } + + @NativeMethods + interface Natives { + long createFullscreenCallbackProxy(FullscreenCallbackProxy proxy, long tab); + void deleteFullscreenCallbackProxy(long proxy); + void doExitFullscreen(long nativeFullscreenCallbackProxy, FullscreenCallbackProxy proxy); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/GmsBridge.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/GmsBridge.java new file mode 100644 index 00000000000..21b5a59c405 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/GmsBridge.java @@ -0,0 +1,55 @@ +// 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.os.Handler; +import android.os.HandlerThread; + +/** + * This class manages functionality related to Google Mobile Services (i.e. GMS). + * Platform specific implementations are provided in GmsBridgeImpl.java. + */ +public abstract class GmsBridge { + private static GmsBridge sInstance; + private static final Object sInstanceLock = new Object(); + + private static HandlerThread sHandlerThread; + private static Handler sHandler; + private static final Object sHandlerLock = new Object(); + + protected GmsBridge() {} + + public static GmsBridge getInstance() { + synchronized (sInstanceLock) { + if (sInstance == null) { + // Load an instance of GmsBridgeImpl. Because this can change depending on + // the GN configuration, this may not be the GmsBridgeImpl defined upstream. + sInstance = new GmsBridgeImpl(); + } + return sInstance; + } + } + + // Return a handler appropriate for executing blocking Platform Service tasks. + public static Handler getHandler() { + synchronized (sHandlerLock) { + if (sHandler == null) { + sHandlerThread = new HandlerThread("GmsBridgeHandlerThread"); + sHandlerThread.start(); + sHandler = new Handler(sHandlerThread.getLooper()); + } + } + return sHandler; + } + + // Returns true if the WebLayer can use Google Mobile Services (GMS). + public boolean canUseGms() { + return false; + } + + public void setSafeBrowsingHandler() { + // We don't have this specialized service here. + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/GmsBridgeImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/GmsBridgeImpl.java new file mode 100644 index 00000000000..315f34a90be --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/GmsBridgeImpl.java @@ -0,0 +1,11 @@ +// 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; + +/** + * Instantiable version of {@link GmsBridge}, don't add anything to this class! + * Downstream targets may provide a different implementation. + */ +public class GmsBridgeImpl extends GmsBridge {} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/MinidumpUploader.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MinidumpUploader.java new file mode 100644 index 00000000000..f84ccfa462a --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/MinidumpUploader.java @@ -0,0 +1,188 @@ +// 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 org.chromium.components.minidump_uploader.util.HttpURLConnectionFactory; +import org.chromium.components.minidump_uploader.util.HttpURLConnectionFactoryImpl; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.util.zip.GZIPOutputStream; + +/** + * This class tries to upload a minidump to the crash server. + * + * Minidumps are stored in multipart MIME format ready to form the body of a POST request. The MIME + * boundary forms the first line of the file. + */ +public class MinidumpUploader { + // TODO(crbug.com/1029724) unfork this class back to //components/minidump_uploader + private static final String CRASH_URL_STRING = "https://clients2.google.com/cr/report"; + private static final String CONTENT_TYPE_TMPL = "multipart/form-data; boundary=%s"; + + private final HttpURLConnectionFactory mHttpURLConnectionFactory; + + /* package */ static final class Result { + final boolean mSuccess; + final int mStatus; + final String mResult; + + private Result(boolean success, int status, String result) { + mSuccess = success; + mStatus = status; + mResult = result; + } + + static Result failure(String result) { + return new Result(false, -1, result); + } + + static Result failure(int status, String result) { + return new Result(false, status, result); + } + + static Result success(String result) { + return new Result(true, 0, result); + } + } + + public MinidumpUploader() { + this(new HttpURLConnectionFactoryImpl()); + } + + public MinidumpUploader(HttpURLConnectionFactory httpURLConnectionFactory) { + mHttpURLConnectionFactory = httpURLConnectionFactory; + } + + public Result upload(File fileToUpload) { + try { + if (fileToUpload == null || !fileToUpload.exists()) { + return Result.failure("Crash report does not exist"); + } + HttpURLConnection connection = + mHttpURLConnectionFactory.createHttpURLConnection(CRASH_URL_STRING); + if (connection == null) { + return Result.failure("Failed to create connection"); + } + configureConnectionForHttpPost(connection, readBoundary(fileToUpload)); + + try (InputStream minidumpInputStream = new FileInputStream(fileToUpload); + OutputStream requestBodyStream = + new GZIPOutputStream(connection.getOutputStream())) { + streamCopy(minidumpInputStream, requestBodyStream); + int responseCode = connection.getResponseCode(); + if (isSuccessful(responseCode)) { + // The crash server returns the crash ID in the response body. + String responseContent = getResponseContentAsString(connection); + String uploadId = responseContent != null ? responseContent : "unknown"; + return Result.success(uploadId); + } else { + // Return the remote error code and message. + return Result.failure(responseCode, connection.getResponseMessage()); + } + } finally { + connection.disconnect(); + } + } catch (IOException | RuntimeException e) { + return Result.failure(e.getMessage()); + } + } + + /** + * Configures a HttpURLConnection to send a HTTP POST request for uploading the minidump. + * + * This also reads the content-type from the minidump file. + * + * @param connection the HttpURLConnection to configure + * @param boundary the MIME boundary used in the request body + */ + private void configureConnectionForHttpPost(HttpURLConnection connection, String boundary) { + connection.setDoOutput(true); + connection.setRequestProperty("Connection", "Keep-Alive"); + connection.setRequestProperty("Content-Encoding", "gzip"); + connection.setRequestProperty("Content-Type", String.format(CONTENT_TYPE_TMPL, boundary)); + } + + /** + * Get the boundary from the file, we need it for the content-type. + * + * @return the boundary if found, else null. + * @throws IOException + */ + private String readBoundary(File fileToUpload) throws IOException { + try (FileReader fileReader = new FileReader(fileToUpload); + BufferedReader reader = new BufferedReader(fileReader)) { + String boundary = reader.readLine(); + if (boundary == null || boundary.trim().isEmpty()) { + throw new RuntimeException(fileToUpload + " does not have a MIME boundary"); + } + boundary = boundary.trim(); + if (!boundary.startsWith("--") || boundary.length() < 10) { + throw new RuntimeException(fileToUpload + " does not have a MIME boundary"); + } + // Note: The regex allows all alphanumeric characters, as well as dashes. + // This matches the code that generates minidumps boundaries: + // https://chromium.googlesource.com/crashpad/crashpad/+/0c322ecc3f711c34fbf85b2cbe69f38b8dbccf05/util/net/http_multipart_builder.cc#36 + if (!boundary.matches("^[a-zA-Z0-9-]*$")) { + throw new RuntimeException( + fileToUpload.getName() + " has an illegal MIME boundary: " + boundary); + } + boundary = boundary.substring(2); // Remove the initial -- + return boundary; + } + } + + /** + * Returns whether the response code indicates a successful HTTP request. + * + * @param responseCode the response code + * @return true if response code indicates success, false otherwise. + */ + private boolean isSuccessful(int responseCode) { + return responseCode == 200 || responseCode == 201 || responseCode == 202; + } + + /** + * Reads the response from |connection| as a String. + * + * @param connection the connection to read the response from. + * @return the content of the response. + * @throws IOException + */ + private String getResponseContentAsString(HttpURLConnection connection) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + streamCopy(connection.getInputStream(), baos); + if (baos.size() == 0) { + return null; + } + return baos.toString(); + } + + /** + * Copies all available data from |inStream| to |outStream|. Closes both + * streams when done. + * + * @param inStream the stream to read + * @param outStream the stream to write to + * @throws IOException + */ + private void streamCopy(InputStream inStream, OutputStream outStream) throws IOException { + byte[] temp = new byte[4096]; + int bytesRead = inStream.read(temp); + while (bytesRead >= 0) { + outStream.write(temp, 0, bytesRead); + bytesRead = inStream.read(temp); + } + inStream.close(); + outStream.close(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java index d9161e3374c..3fb45d58fb1 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationControllerImpl.java @@ -5,72 +5,89 @@ package org.chromium.weblayer_private; import android.os.RemoteException; -import android.util.AndroidRuntimeException; -import org.chromium.base.Log; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; -import org.chromium.weblayer_private.aidl.INavigationController; -import org.chromium.weblayer_private.aidl.INavigationControllerClient; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.INavigationController; +import org.chromium.weblayer_private.interfaces.INavigationControllerClient; /** * Acts as the bridge between java and the C++ implementation of of NavigationController. */ @JNINamespace("weblayer") public final class NavigationControllerImpl extends INavigationController.Stub { - private static final String TAG = "WebLayer"; - private long mNativeNavigationController; - private BrowserControllerImpl mBrowserController; + private TabImpl mTab; private INavigationControllerClient mNavigationControllerClient; - public NavigationControllerImpl( - BrowserControllerImpl browserController, INavigationControllerClient client) { + public NavigationControllerImpl(TabImpl tab, INavigationControllerClient client) { mNavigationControllerClient = client; - mBrowserController = browserController; + mTab = tab; mNativeNavigationController = - nativeGetNavigationController(browserController.getNativeBrowserController()); - nativeSetNavigationControllerImpl(mNativeNavigationController); + NavigationControllerImplJni.get().getNavigationController(tab.getNativeTab()); + NavigationControllerImplJni.get().setNavigationControllerImpl( + mNativeNavigationController, NavigationControllerImpl.this); } @Override public void navigate(String uri) { - nativeNavigate(mNativeNavigationController, uri); + NavigationControllerImplJni.get().navigate( + mNativeNavigationController, NavigationControllerImpl.this, uri); } @Override public void goBack() { - nativeGoBack(mNativeNavigationController); + NavigationControllerImplJni.get().goBack( + mNativeNavigationController, NavigationControllerImpl.this); } @Override public void goForward() { - nativeGoForward(mNativeNavigationController); + NavigationControllerImplJni.get().goForward( + mNativeNavigationController, NavigationControllerImpl.this); + } + + @Override + public boolean canGoBack() { + return NavigationControllerImplJni.get().canGoBack( + mNativeNavigationController, NavigationControllerImpl.this); + } + + @Override + public boolean canGoForward() { + return NavigationControllerImplJni.get().canGoForward( + mNativeNavigationController, NavigationControllerImpl.this); } @Override public void reload() { - nativeReload(mNativeNavigationController); + NavigationControllerImplJni.get().reload( + mNativeNavigationController, NavigationControllerImpl.this); } @Override public void stop() { - nativeStop(mNativeNavigationController); + NavigationControllerImplJni.get().stop( + mNativeNavigationController, NavigationControllerImpl.this); } @Override public int getNavigationListSize() { - return nativeGetNavigationListSize(mNativeNavigationController); + return NavigationControllerImplJni.get().getNavigationListSize( + mNativeNavigationController, NavigationControllerImpl.this); } @Override public int getNavigationListCurrentIndex() { - return nativeGetNavigationListCurrentIndex(mNativeNavigationController); + return NavigationControllerImplJni.get().getNavigationListCurrentIndex( + mNativeNavigationController, NavigationControllerImpl.this); } @Override public String getNavigationEntryDisplayUri(int index) { - return nativeGetNavigationEntryDisplayUri(mNativeNavigationController, index); + return NavigationControllerImplJni.get().getNavigationEntryDisplayUri( + mNativeNavigationController, NavigationControllerImpl.this, index); } @CalledByNative @@ -79,65 +96,64 @@ public final class NavigationControllerImpl extends INavigationController.Stub { } @CalledByNative - private void navigationStarted(NavigationImpl navigation) { - try { - mNavigationControllerClient.navigationStarted(navigation.getClientNavigation()); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call navigationStarted.", e); - throw new AndroidRuntimeException(e); - } + private void navigationStarted(NavigationImpl navigation) throws RemoteException { + mNavigationControllerClient.navigationStarted(navigation.getClientNavigation()); + } + + @CalledByNative + private void navigationRedirected(NavigationImpl navigation) throws RemoteException { + mNavigationControllerClient.navigationRedirected(navigation.getClientNavigation()); } @CalledByNative - private void navigationRedirected(NavigationImpl navigation) { - try { - mNavigationControllerClient.navigationRedirected(navigation.getClientNavigation()); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call navigationRedirected.", e); - throw new AndroidRuntimeException(e); - } + private void readyToCommitNavigation(NavigationImpl navigation) throws RemoteException { + mNavigationControllerClient.readyToCommitNavigation(navigation.getClientNavigation()); } @CalledByNative - private void navigationCommitted(NavigationImpl navigation) { - try { - mNavigationControllerClient.navigationCommitted(navigation.getClientNavigation()); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call navigationCommitted.", e); - throw new AndroidRuntimeException(e); - } + private void navigationCompleted(NavigationImpl navigation) throws RemoteException { + mNavigationControllerClient.navigationCompleted(navigation.getClientNavigation()); } @CalledByNative - private void navigationCompleted(NavigationImpl navigation) { - try { - mNavigationControllerClient.navigationCompleted(navigation.getClientNavigation()); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call navigationCompleted.", e); - throw new AndroidRuntimeException(e); - } + private void navigationFailed(NavigationImpl navigation) throws RemoteException { + mNavigationControllerClient.navigationFailed(navigation.getClientNavigation()); } @CalledByNative - private void navigationFailed(NavigationImpl navigation) { - try { - mNavigationControllerClient.navigationFailed(navigation.getClientNavigation()); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call navigationFailed.", e); - throw new AndroidRuntimeException(e); - } - } - - private native void nativeSetNavigationControllerImpl(long nativeNavigationControllerImpl); - - private static native long nativeGetNavigationController(long browserController); - private native void nativeNavigate(long nativeNavigationControllerImpl, String uri); - private native void nativeGoBack(long nativeNavigationControllerImpl); - private native void nativeGoForward(long nativeNavigationControllerImpl); - private native void nativeReload(long nativeNavigationControllerImpl); - private native void nativeStop(long nativeNavigationControllerImpl); - private native int nativeGetNavigationListSize(long nativeNavigationControllerImpl); - private native int nativeGetNavigationListCurrentIndex(long nativeNavigationControllerImpl); - private native String nativeGetNavigationEntryDisplayUri( - long nativeNavigationControllerImpl, int index); + private void loadStateChanged(boolean isLoading, boolean toDifferentDocument) + throws RemoteException { + mNavigationControllerClient.loadStateChanged(isLoading, toDifferentDocument); + } + + @CalledByNative + private void loadProgressChanged(double progress) throws RemoteException { + mNavigationControllerClient.loadProgressChanged(progress); + } + + @CalledByNative + private void onFirstContentfulPaint() throws RemoteException { + mNavigationControllerClient.onFirstContentfulPaint(); + } + + @NativeMethods + interface Natives { + void setNavigationControllerImpl( + long nativeNavigationControllerImpl, NavigationControllerImpl caller); + long getNavigationController(long tab); + void navigate( + long nativeNavigationControllerImpl, NavigationControllerImpl caller, String uri); + void goBack(long nativeNavigationControllerImpl, NavigationControllerImpl caller); + void goForward(long nativeNavigationControllerImpl, NavigationControllerImpl caller); + boolean canGoBack(long nativeNavigationControllerImpl, NavigationControllerImpl caller); + boolean canGoForward(long nativeNavigationControllerImpl, NavigationControllerImpl caller); + void reload(long nativeNavigationControllerImpl, NavigationControllerImpl caller); + void stop(long nativeNavigationControllerImpl, NavigationControllerImpl caller); + int getNavigationListSize( + long nativeNavigationControllerImpl, NavigationControllerImpl caller); + int getNavigationListCurrentIndex( + long nativeNavigationControllerImpl, NavigationControllerImpl caller); + String getNavigationEntryDisplayUri( + long nativeNavigationControllerImpl, NavigationControllerImpl caller, int index); + } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java index 99d6d170240..dca8e88bf87 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NavigationImpl.java @@ -5,20 +5,25 @@ package org.chromium.weblayer_private; import android.os.RemoteException; -import android.util.AndroidRuntimeException; -import org.chromium.base.Log; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; -import org.chromium.weblayer_private.aidl.IClientNavigation; -import org.chromium.weblayer_private.aidl.INavigation; -import org.chromium.weblayer_private.aidl.INavigationControllerClient; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.APICallException; +import org.chromium.weblayer_private.interfaces.IClientNavigation; +import org.chromium.weblayer_private.interfaces.INavigation; +import org.chromium.weblayer_private.interfaces.INavigationControllerClient; +import org.chromium.weblayer_private.interfaces.LoadError; +import org.chromium.weblayer_private.interfaces.NavigationState; +import java.util.Arrays; import java.util.List; +/** + * Implementation of INavigation. + */ @JNINamespace("weblayer") public final class NavigationImpl extends INavigation.Stub { - private static final String TAG = "WebLayer"; private final IClientNavigation mClientNavigation; // WARNING: NavigationImpl may outlive the native side, in which case this member is set to 0. private long mNativeNavigationImpl; @@ -28,32 +33,76 @@ public final class NavigationImpl extends INavigation.Stub { try { mClientNavigation = client.createClientNavigation(this); } catch (RemoteException e) { - Log.e(TAG, "Failed to call createClientNavigation.", e); - throw new AndroidRuntimeException(e); + throw new APICallException(e); } - nativeSetJavaNavigation(mNativeNavigationImpl); + NavigationImplJni.get().setJavaNavigation(mNativeNavigationImpl, NavigationImpl.this); } public IClientNavigation getClientNavigation() { return mClientNavigation; } + @NavigationState + private static int implTypeToJavaType(@ImplNavigationState int type) { + switch (type) { + case ImplNavigationState.WAITING_RESPONSE: + return NavigationState.WAITING_RESPONSE; + case ImplNavigationState.RECEIVING_BYTES: + return NavigationState.RECEIVING_BYTES; + case ImplNavigationState.COMPLETE: + return NavigationState.COMPLETE; + case ImplNavigationState.FAILED: + return NavigationState.FAILED; + } + assert false; + return NavigationState.FAILED; + } + @Override + @NavigationState public int getState() { throwIfNativeDestroyed(); - return nativeGetState(mNativeNavigationImpl); + return implTypeToJavaType( + NavigationImplJni.get().getState(mNativeNavigationImpl, NavigationImpl.this)); } @Override public String getUri() { throwIfNativeDestroyed(); - return nativeGetUri(mNativeNavigationImpl); + return NavigationImplJni.get().getUri(mNativeNavigationImpl, NavigationImpl.this); } @Override public List<String> getRedirectChain() { throwIfNativeDestroyed(); - return nativeGetRedirectChain(mNativeNavigationImpl); + return Arrays.asList(NavigationImplJni.get().getRedirectChain( + mNativeNavigationImpl, NavigationImpl.this)); + } + + @Override + public int getHttpStatusCode() { + throwIfNativeDestroyed(); + return NavigationImplJni.get().getHttpStatusCode( + mNativeNavigationImpl, NavigationImpl.this); + } + + @Override + public boolean isSameDocument() { + throwIfNativeDestroyed(); + return NavigationImplJni.get().isSameDocument(mNativeNavigationImpl, NavigationImpl.this); + } + + @Override + public boolean isErrorPage() { + throwIfNativeDestroyed(); + return NavigationImplJni.get().isErrorPage(mNativeNavigationImpl, NavigationImpl.this); + } + + @Override + public int getLoadError() { + throwIfNativeDestroyed(); + return implLoadErrorToLoadError( + NavigationImplJni.get().getLoadError(mNativeNavigationImpl, NavigationImpl.this)); } private void throwIfNativeDestroyed() { @@ -62,14 +111,41 @@ public final class NavigationImpl extends INavigation.Stub { } } + @LoadError + private static int implLoadErrorToLoadError(@ImplLoadError int loadError) { + switch (loadError) { + case ImplLoadError.NO_ERROR: + return LoadError.NO_ERROR; + case ImplLoadError.HTTP_CLIENT_ERROR: + return LoadError.HTTP_CLIENT_ERROR; + case ImplLoadError.HTTP_SERVER_ERROR: + return LoadError.HTTP_SERVER_ERROR; + case ImplLoadError.SSL_ERROR: + return LoadError.SSL_ERROR; + case ImplLoadError.CONNECTIVITY_ERROR: + return LoadError.CONNECTIVITY_ERROR; + case ImplLoadError.OTHER_ERROR: + return LoadError.OTHER_ERROR; + default: + throw new IllegalArgumentException("Unexpected load error " + loadError); + } + } + @CalledByNative private void onNativeDestroyed() { mNativeNavigationImpl = 0; // TODO: this should likely notify delegate in some way. } - private native void nativeSetJavaNavigation(long nativeNavigationImpl); - private native int nativeGetState(long nativeNavigationImpl); - private native String nativeGetUri(long nativeNavigationImpl); - private native List<String> nativeGetRedirectChain(long nativeNavigationImpl); + @NativeMethods + interface Natives { + void setJavaNavigation(long nativeNavigationImpl, NavigationImpl caller); + int getState(long nativeNavigationImpl, NavigationImpl caller); + String getUri(long nativeNavigationImpl, NavigationImpl caller); + String[] getRedirectChain(long nativeNavigationImpl, NavigationImpl caller); + int getHttpStatusCode(long nativeNavigationImpl, NavigationImpl caller); + boolean isSameDocument(long nativeNavigationImpl, NavigationImpl caller); + boolean isErrorPage(long nativeNavigationImpl, NavigationImpl caller); + int getLoadError(long nativeNavigationImpl, NavigationImpl caller); + } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java new file mode 100644 index 00000000000..c5d665b7c08 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/NewTabCallbackProxy.java @@ -0,0 +1,70 @@ +// 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.os.RemoteException; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.NewTabType; + +/** + * Owns the c++ NewTabCallback class, which is responsible for forwarding all + * NewTabCallback calls to this class, which in turn forwards to ITabClient. + */ +@JNINamespace("weblayer") +public final class NewTabCallbackProxy { + private long mNativeNewTabCallbackProxy; + private final TabImpl mTab; + + public NewTabCallbackProxy(TabImpl tab) { + mTab = tab; + mNativeNewTabCallbackProxy = + NewTabCallbackProxyJni.get().createNewTabCallbackProxy(this, tab.getNativeTab()); + } + + public void destroy() { + NewTabCallbackProxyJni.get().deleteNewTabCallbackProxy(mNativeNewTabCallbackProxy); + mNativeNewTabCallbackProxy = 0; + } + + @NewTabType + private static int implTypeToJavaType(@ImplNewTabType int type) { + switch (type) { + case ImplNewTabType.FOREGROUND: + return NewTabType.FOREGROUND_TAB; + case ImplNewTabType.BACKGROUND: + return NewTabType.BACKGROUND_TAB; + case ImplNewTabType.NEW_POPUP: + return NewTabType.NEW_POPUP; + case ImplNewTabType.NEW_WINDOW: + return NewTabType.NEW_WINDOW; + } + assert false; + return NewTabType.FOREGROUND_TAB; + } + + @CalledByNative + public void onNewTab(long nativeTab, @ImplNewTabType int mode) throws RemoteException { + // This class should only be created while the tab is attached to a fragment. + assert mTab.getBrowser() != null; + TabImpl tab = + new TabImpl(mTab.getProfile(), mTab.getBrowser().getWindowAndroid(), nativeTab); + mTab.getBrowser().addTab(tab); + mTab.getClient().onNewTab(tab.getId(), mode); + } + + @CalledByNative + private void onCloseTab() throws RemoteException { + mTab.getClient().onCloseTab(); + } + + @NativeMethods + interface Natives { + long createNewTabCallbackProxy(NewTabCallbackProxy proxy, long tab); + void deleteNewTabCallbackProxy(long proxy); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/OWNERS b/chromium/weblayer/browser/java/org/chromium/weblayer_private/OWNERS new file mode 100644 index 00000000000..49cabda246c --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/OWNERS @@ -0,0 +1,2 @@ +# For safebrowsing related files +per-file *GmsBridge*=timvolodine@chromium.org diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java index 857777080f0..d734d7bb0c3 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileImpl.java @@ -4,45 +4,87 @@ package org.chromium.weblayer_private; -import android.content.Context; +import androidx.annotation.NonNull; +import org.chromium.base.CollectionUtil; import org.chromium.base.annotations.JNINamespace; -import org.chromium.weblayer_private.aidl.IBrowserController; -import org.chromium.weblayer_private.aidl.IObjectWrapper; -import org.chromium.weblayer_private.aidl.IProfile; -import org.chromium.weblayer_private.aidl.ObjectWrapper; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.BrowsingDataType; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.IProfile; +import org.chromium.weblayer_private.interfaces.ObjectWrapper; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of IProfile. + */ @JNINamespace("weblayer") public final class ProfileImpl extends IProfile.Stub { + private final String mName; private long mNativeProfile; + private Runnable mOnDestroyCallback; - public ProfileImpl(String path) { - mNativeProfile = nativeCreateProfile(path); + ProfileImpl(String name, Runnable onDestroyCallback) { + if (!name.matches("^\\w*$")) { + throw new IllegalArgumentException("Name can only contain words: " + name); + } + mName = name; + mNativeProfile = ProfileImplJni.get().createProfile(name); + mOnDestroyCallback = onDestroyCallback; } @Override public void destroy() { - nativeDeleteProfile(mNativeProfile); + ProfileImplJni.get().deleteProfile(mNativeProfile); mNativeProfile = 0; + mOnDestroyCallback.run(); + mOnDestroyCallback = null; } @Override - public void clearBrowsingData() { - nativeClearBrowsingData(mNativeProfile); + public String getName() { + return mName; } @Override - public IBrowserController createBrowserController(IObjectWrapper context) { - return new BrowserControllerImpl(ObjectWrapper.unwrap(context, Context.class), this); + public void clearBrowsingData(@NonNull @BrowsingDataType int[] dataTypes, long fromMillis, + long toMillis, @NonNull IObjectWrapper completionCallback) { + Runnable callback = ObjectWrapper.unwrap(completionCallback, Runnable.class); + ProfileImplJni.get().clearBrowsingData( + mNativeProfile, mapBrowsingDataTypes(dataTypes), fromMillis, toMillis, callback); + } + + private static @ImplBrowsingDataType int[] mapBrowsingDataTypes( + @NonNull @BrowsingDataType int[] dataTypes) { + // Convert data types coming from aidl to the ones accepted by C++ (ImplBrowsingDataType is + // generated from a C++ enum). + List<Integer> convertedTypes = new ArrayList<>(); + for (int aidlType : dataTypes) { + switch (aidlType) { + case BrowsingDataType.COOKIES_AND_SITE_DATA: + convertedTypes.add(ImplBrowsingDataType.COOKIES_AND_SITE_DATA); + break; + case BrowsingDataType.CACHE: + convertedTypes.add(ImplBrowsingDataType.CACHE); + break; + default: + break; // Skip unrecognized values for forward compatibility. + } + } + return CollectionUtil.integerListToIntArray(convertedTypes); } long getNativeProfile() { return mNativeProfile; } - private static native long nativeCreateProfile(String path); - - private static native void nativeDeleteProfile(long profile); - - private static native void nativeClearBrowsingData(long nativeProfileImpl); + @NativeMethods + interface Natives { + long createProfile(String name); + void deleteProfile(long profile); + void clearBrowsingData(long nativeProfileImpl, @ImplBrowsingDataType int[] dataTypes, + long fromMillis, long toMillis, Runnable callback); + } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileManager.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileManager.java new file mode 100644 index 00000000000..45903d5c53c --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/ProfileManager.java @@ -0,0 +1,28 @@ +// 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 name. */ + public ProfileImpl getProfile(String name) { + if (name == null) throw new IllegalArgumentException("Name shouldn't be null"); + ProfileImpl existingProfile = mProfiles.get(name); + if (existingProfile != null) { + return existingProfile; + } + + ProfileImpl profile = new ProfileImpl(name, () -> mProfiles.remove(name)); + mProfiles.put(name, profile); + return profile; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/RemoteFragmentImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/RemoteFragmentImpl.java new file mode 100644 index 00000000000..1b4a6b9c85d --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/RemoteFragmentImpl.java @@ -0,0 +1,247 @@ +// 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.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.RemoteException; +import android.view.View; + +import org.chromium.weblayer_private.interfaces.APICallException; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.IRemoteFragment; +import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient; +import org.chromium.weblayer_private.interfaces.ObjectWrapper; + +/** + * Base for the classes controlling a Fragment that exists in another ClassLoader. Extending this + * class is similar to extending Fragment: e.g. one can override lifecycle methods, not forgetting + * to call super, etc. + */ +public abstract class RemoteFragmentImpl extends IRemoteFragment.Stub { + private final IRemoteFragmentClient mClient; + + protected RemoteFragmentImpl(IRemoteFragmentClient client) { + mClient = client; + } + + public View onCreateView() { + return null; + } + + public final Activity getActivity() { + try { + return ObjectWrapper.unwrap(mClient.getActivity(), Activity.class); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + // TODO(pshmakov): add dependency to androidx.annotation and put @CallSuper here. + public void onCreate(Bundle savedInstanceState) { + try { + mClient.superOnCreate(ObjectWrapper.wrap(savedInstanceState)); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onAttach(Context context) { + try { + mClient.superOnAttach(ObjectWrapper.wrap(context)); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onActivityCreated(Bundle savedInstanceState) { + try { + mClient.superOnActivityCreated(ObjectWrapper.wrap(savedInstanceState)); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) {} + + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) {} + + public void onStart() { + try { + mClient.superOnStart(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onDestroy() { + try { + mClient.superOnDestroy(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onDetach() { + try { + mClient.superOnDetach(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onResume() { + try { + mClient.superOnResume(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onDestroyView() { + try { + mClient.superOnDestroyView(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onStop() { + try { + mClient.superOnStop(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onPause() { + try { + mClient.superOnPause(); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void onSaveInstaceState(Bundle outState) { + try { + mClient.superOnSaveInstanceState(ObjectWrapper.wrap(outState)); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public boolean startActivityForResult(Intent intent, int requestCode, Bundle options) { + try { + return mClient.startActivityForResult( + ObjectWrapper.wrap(intent), requestCode, ObjectWrapper.wrap(options)); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public boolean startIntentSenderForResult(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { + try { + return mClient.startIntentSenderForResult(ObjectWrapper.wrap(intent), requestCode, + ObjectWrapper.wrap(fillInIntent), flagsMask, flagsValues, extraFlags, + ObjectWrapper.wrap(options)); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public boolean shouldShowRequestPermissionRationale(String permission) { + try { + return mClient.shouldShowRequestPermissionRationale(permission); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + public void requestPermissions(String[] permissions, int requestCode) { + try { + mClient.requestPermissions(permissions, requestCode); + } catch (RemoteException e) { + throw new APICallException(e); + } + } + + // IRemoteFragment implementation below. + + @Override + public final IObjectWrapper handleOnCreateView() { + return ObjectWrapper.wrap(onCreateView()); + } + + @Override + public final void handleOnStart() { + onStart(); + } + + @Override + public final void handleOnCreate(IObjectWrapper savedInstanceState) { + onCreate(ObjectWrapper.unwrap(savedInstanceState, Bundle.class)); + } + + @Override + public final void handleOnAttach(IObjectWrapper context) { + onAttach(ObjectWrapper.unwrap(context, Context.class)); + } + + @Override + public final void handleOnActivityCreated(IObjectWrapper savedInstanceState) { + onActivityCreated(ObjectWrapper.unwrap(savedInstanceState, Bundle.class)); + } + + @Override + public final void handleOnResume() { + onResume(); + } + + @Override + public final void handleOnPause() { + onPause(); + } + + @Override + public final void handleOnStop() { + onStop(); + } + + @Override + public final void handleOnDestroyView() { + onDestroyView(); + } + + @Override + public final void handleOnDetach() { + onDetach(); + } + + @Override + public final void handleOnDestroy() { + onDestroy(); + } + + @Override + public final void handleOnSaveInstanceState(IObjectWrapper outState) { + onSaveInstaceState(ObjectWrapper.unwrap(outState, Bundle.class)); + } + + @Override + public final void handleOnActivityResult(int requestCode, int resultCode, IObjectWrapper data) { + onActivityResult(requestCode, resultCode, ObjectWrapper.unwrap(data, Intent.class)); + } + + @Override + public final void handleOnRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + onRequestPermissionsResult(requestCode, permissions, grantResults); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabCallbackProxy.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabCallbackProxy.java new file mode 100644 index 00000000000..4e63f53e96a --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabCallbackProxy.java @@ -0,0 +1,49 @@ +// 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.os.RemoteException; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.weblayer_private.interfaces.ITabClient; + +/** + * Owns the C++ TabCallbackProxy class, which is responsible for forwarding all + * BrowserObserver calls to this class, which in turn forwards to the TabClient. + * To avoid unnecessary IPC only one TabCallbackProxy is created per Tab. + */ +@JNINamespace("weblayer") +public final class TabCallbackProxy { + private long mNativeTabCallbackProxy; + private ITabClient mClient; + + TabCallbackProxy(long tab, ITabClient client) { + mClient = client; + mNativeTabCallbackProxy = TabCallbackProxyJni.get().createTabCallbackProxy(this, tab); + } + + public void destroy() { + TabCallbackProxyJni.get().deleteTabCallbackProxy(mNativeTabCallbackProxy); + mNativeTabCallbackProxy = 0; + } + + @CalledByNative + private void visibleUriChanged(String string) throws RemoteException { + mClient.visibleUriChanged(string); + } + + @CalledByNative + private void onRenderProcessGone() throws RemoteException { + mClient.onRenderProcessGone(); + } + + @NativeMethods + interface Natives { + long createTabCallbackProxy(TabCallbackProxy proxy, long tab); + void deleteTabCallbackProxy(long proxy); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java new file mode 100644 index 00000000000..ae439d4b688 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TabImpl.java @@ -0,0 +1,290 @@ +// 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.view.KeyEvent; +import android.view.MotionEvent; +import android.webkit.ValueCallback; + +import org.chromium.base.Callback; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.content_public.browser.SelectionPopupController; +import org.chromium.content_public.browser.ViewEventSink; +import org.chromium.content_public.browser.WebContents; +import org.chromium.ui.base.ViewAndroidDelegate; +import org.chromium.ui.base.WindowAndroid; +import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient; +import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient; +import org.chromium.weblayer_private.interfaces.IFullscreenCallbackClient; +import org.chromium.weblayer_private.interfaces.INavigationControllerClient; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.ITab; +import org.chromium.weblayer_private.interfaces.ITabClient; +import org.chromium.weblayer_private.interfaces.ObjectWrapper; + +/** + * Implementation of ITab. + */ +@JNINamespace("weblayer") +public final class TabImpl extends ITab.Stub { + private static int sNextId = 1; + private long mNativeTab; + + private ProfileImpl mProfile; + private WebContents mWebContents; + private TabCallbackProxy mTabCallbackProxy; + private NavigationControllerImpl mNavigationController; + private DownloadCallbackProxy mDownloadCallbackProxy; + private ErrorPageCallbackProxy mErrorPageCallbackProxy; + private FullscreenCallbackProxy mFullscreenCallbackProxy; + private ViewAndroidDelegate mViewAndroidDelegate; + // BrowserImpl this TabImpl is in. This is only null during creation. + private BrowserImpl mBrowser; + private NewTabCallbackProxy mNewTabCallbackProxy; + private ITabClient mClient; + private final int mId; + + private static class InternalAccessDelegateImpl + implements ViewEventSink.InternalAccessDelegate { + @Override + public boolean super_onKeyUp(int keyCode, KeyEvent event) { + return false; + } + + @Override + public boolean super_dispatchKeyEvent(KeyEvent event) { + return false; + } + + @Override + public boolean super_onGenericMotionEvent(MotionEvent event) { + return false; + } + + @Override + public void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix) {} + } + + public TabImpl(ProfileImpl profile, WindowAndroid windowAndroid) { + mId = ++sNextId; + init(profile, windowAndroid, TabImplJni.get().createTab(profile.getNativeProfile(), this)); + } + + /** + * This constructor is called when the native side triggers creation of a TabImpl + * (as happens with popups). + */ + public TabImpl(ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab) { + mId = ++sNextId; + TabImplJni.get().setJavaImpl(nativeTab, TabImpl.this); + init(profile, windowAndroid, nativeTab); + } + + private void init(ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab) { + mProfile = profile; + mNativeTab = nativeTab; + mWebContents = TabImplJni.get().getWebContents(mNativeTab, TabImpl.this); + mViewAndroidDelegate = new ViewAndroidDelegate(null) { + @Override + public void onTopControlsChanged(int topControlsOffsetY, int topContentOffsetY) { + BrowserViewController viewController = getViewController(); + if (viewController != null) { + viewController.onTopControlsChanged(topControlsOffsetY, topContentOffsetY); + } + } + }; + mWebContents.initialize("", mViewAndroidDelegate, new InternalAccessDelegateImpl(), + windowAndroid, WebContents.createDefaultInternalsHolder()); + } + + public ProfileImpl getProfile() { + return mProfile; + } + + public ITabClient getClient() { + return mClient; + } + + /** + * Sets the BrowserImpl this TabImpl is contained in. + */ + public void attachToBrowser(BrowserImpl browser) { + mBrowser = browser; + mWebContents.setTopLevelNativeWindow(browser.getWindowAndroid()); + mViewAndroidDelegate.setContainerView(browser.getViewAndroidDelegateContainerView()); + SelectionPopupController.fromWebContents(mWebContents) + .setActionModeCallback(new ActionModeCallback(mWebContents)); + } + + public BrowserImpl getBrowser() { + return mBrowser; + } + + @Override + public void setNewTabsEnabled(boolean enable) { + if (enable && mNewTabCallbackProxy == null) { + mNewTabCallbackProxy = new NewTabCallbackProxy(this); + } else if (!enable && mNewTabCallbackProxy != null) { + mNewTabCallbackProxy.destroy(); + mNewTabCallbackProxy = null; + } + } + + @Override + public int getId() { + return mId; + } + + /** + * Called when this TabImpl becomes the active TabImpl. + */ + public void onDidGainActive(long topControlsContainerViewHandle) { + // attachToFragment() must be called before activate(). + assert mBrowser != null; + TabImplJni.get().setTopControlsContainerView( + mNativeTab, TabImpl.this, topControlsContainerViewHandle); + mWebContents.onShow(); + } + /** + * Called when this TabImpl is no longer the active TabImpl. + */ + public void onDidLoseActive() { + mWebContents.onHide(); + TabImplJni.get().setTopControlsContainerView(mNativeTab, TabImpl.this, 0); + } + + public WebContents getWebContents() { + return mWebContents; + } + + long getNativeTab() { + return mNativeTab; + } + + @Override + public NavigationControllerImpl createNavigationController(INavigationControllerClient client) { + // This should only be called once. + assert mNavigationController == null; + mNavigationController = new NavigationControllerImpl(this, client); + return mNavigationController; + } + + @Override + public void setClient(ITabClient client) { + mClient = client; + mTabCallbackProxy = new TabCallbackProxy(mNativeTab, client); + } + + @Override + public void setDownloadCallbackClient(IDownloadCallbackClient client) { + if (client != null) { + if (mDownloadCallbackProxy == null) { + mDownloadCallbackProxy = new DownloadCallbackProxy(mNativeTab, client); + } else { + mDownloadCallbackProxy.setClient(client); + } + } else if (mDownloadCallbackProxy != null) { + mDownloadCallbackProxy.destroy(); + mDownloadCallbackProxy = null; + } + } + + @Override + public void setErrorPageCallbackClient(IErrorPageCallbackClient client) { + if (client != null) { + if (mErrorPageCallbackProxy == null) { + mErrorPageCallbackProxy = new ErrorPageCallbackProxy(mNativeTab, client); + } else { + mErrorPageCallbackProxy.setClient(client); + } + } else if (mErrorPageCallbackProxy != null) { + mErrorPageCallbackProxy.destroy(); + mErrorPageCallbackProxy = null; + } + } + + @Override + public void setFullscreenCallbackClient(IFullscreenCallbackClient client) { + if (client != null) { + if (mFullscreenCallbackProxy == null) { + mFullscreenCallbackProxy = new FullscreenCallbackProxy(mNativeTab, client); + } else { + mFullscreenCallbackProxy.setClient(client); + } + } else if (mFullscreenCallbackProxy != null) { + mFullscreenCallbackProxy.destroy(); + mFullscreenCallbackProxy = null; + } + } + + @Override + public void executeScript(String script, boolean useSeparateIsolate, IObjectWrapper callback) { + Callback<String> nativeCallback = new Callback<String>() { + @Override + public void onResult(String result) { + ValueCallback<String> unwrappedCallback = + (ValueCallback<String>) ObjectWrapper.unwrap(callback, ValueCallback.class); + if (unwrappedCallback != null) { + unwrappedCallback.onReceiveValue(result); + } + } + }; + TabImplJni.get().executeScript(mNativeTab, script, useSeparateIsolate, nativeCallback); + } + + public void destroy() { + if (mTabCallbackProxy != null) { + mTabCallbackProxy.destroy(); + mTabCallbackProxy = null; + } + if (mDownloadCallbackProxy != null) { + mDownloadCallbackProxy.destroy(); + mDownloadCallbackProxy = null; + } + if (mErrorPageCallbackProxy != null) { + mErrorPageCallbackProxy.destroy(); + mErrorPageCallbackProxy = null; + } + if (mFullscreenCallbackProxy != null) { + mFullscreenCallbackProxy.destroy(); + mFullscreenCallbackProxy = null; + } + if (mNewTabCallbackProxy != null) { + mNewTabCallbackProxy.destroy(); + mNewTabCallbackProxy = null; + } + mNavigationController = null; + TabImplJni.get().deleteTab(mNativeTab); + mNativeTab = 0; + } + + @CalledByNative + private boolean doBrowserControlsShrinkRendererSize() { + BrowserViewController viewController = getViewController(); + return viewController != null && viewController.doBrowserControlsShrinkRendererSize(); + } + + /** + * Returns the BrowserViewController for this TabImpl, but only if this + * is the active TabImpl. + */ + private BrowserViewController getViewController() { + return (mBrowser.getActiveTab() == this) ? mBrowser.getViewController() : null; + } + + @NativeMethods + interface Natives { + long createTab(long profile, TabImpl caller); + void setJavaImpl(long nativeTabImpl, TabImpl impl); + void setTopControlsContainerView( + long nativeTabImpl, TabImpl caller, long nativeTopControlsContainerView); + void deleteTab(long tab); + WebContents getWebContents(long nativeTabImpl, TabImpl caller); + void executeScript(long nativeTabImpl, String script, boolean useSeparateIsolate, + Callback<String> callback); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/TopControlsContainerView.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TopControlsContainerView.java new file mode 100644 index 00000000000..c2c1f9d5d25 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/TopControlsContainerView.java @@ -0,0 +1,351 @@ +// 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.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewParent; +import android.widget.FrameLayout; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; +import org.chromium.content_public.browser.WebContents; +import org.chromium.ui.base.EventOffsetHandler; +import org.chromium.ui.resources.dynamics.ViewResourceAdapter; + +/** + * TopControlsContainerView is responsible for holding the top-view from the client. Further, it + * has a ViewResourceAdapter that is kept in sync with the contents of the top-view. + * ViewResourceAdapter is used to keep a bitmap in sync with the contents of the top-view. The + * bitmap is placed in a cc::Layer and the layer is shown while scrolling the top-view. + * ViewResourceAdapter is always kept in sync, as to do otherwise results in a noticeable delay + * between when the scroll starts the content is available. + * + * There are many parts involved in orchestrating top-controls scrolling. The key things to know + * are: + * . TopControlsContainerView (in native code) keeps a cc::Layer that shows a bitmap rendered by + * the top-view. The bitmap is updated anytime the top-view changes. This is done as otherwise + * there is a noticable delay between when the scroll starts and the bitmap is available. + * . When scrolling, the cc::Layer for the WebContents and TopControlsContainerView is moved. + * . The size of the WebContents is only changed after the user releases a touch point. Otherwise + * the scrollbar bounces around. + * . WebContentsDelegate::DoBrowserControlsShrinkRendererSize() only changes when the WebContents + * size change. + * . WebContentsGestureStateTracker is responsible for determining when a scroll/touch is underway. + * . ContentViewRenderView.Delegate is used to adjust the size of the webcontents when the + * top-controls are fully visible (and a scroll is not underway). + * + * The flow of this code is roughly: + * . WebContentsGestureStateTracker generally detects a touch first + * . TabImpl is notified and caches state. + * . onTopControlsChanged() is called. This triggers hiding the real view and calling to native code + * to move the cc::Layers. + * . the move continues. + * . when the move completes and both WebContentsGestureStateTracker and TopControlsContainerView + * no longer believe a move/gesture/scroll is underway the size of the WebContents is adjusted + * (if necessary). + */ +@JNINamespace("weblayer") +class TopControlsContainerView extends FrameLayout { + // ID used with ViewResourceAdapter. + private static final int TOP_CONTROLS_ID = 1001; + + private static final long SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS = 500; + + private long mNativeTopControlsContainerView; + + private ViewResourceAdapter mViewResourceAdapter; + + // Last width/height of mView as sent to the native side. + private int mLastWidth; + private int mLastHeight; + + // view from the client. + private View mView; + + private ContentViewRenderView mContentViewRenderView; + private WebContents mWebContents; + private EventOffsetHandler mEventOffsetHandler; + private int mTopContentOffset; + + // Set to true if |mView| is hidden because the user has scrolled or triggered some action such + // that mView is not visible. While |mView| is not visible if this is true, the bitmap from + // |mView| may be partially visible. + private boolean mInTopControlsScroll; + + private boolean mIsFullscreen; + + // Used to delay processing fullscreen requests. + private Runnable mSystemUiFullscreenResizeRunnable; + + private final Listener mListener; + + public interface Listener { + /** + * Called when the top-controls are either completely showing, or completely hiding. + */ + public void onTopControlsCompletelyShownOrHidden(); + } + + // Used to delay updating the image for the layer. + private final Runnable mRefreshResourceIdRunnable = () -> { + if (mView == null) return; + TopControlsContainerViewJni.get().updateTopControlsResource( + mNativeTopControlsContainerView, TopControlsContainerView.this); + }; + + TopControlsContainerView( + Context context, ContentViewRenderView contentViewRenderView, Listener listener) { + super(context); + mContentViewRenderView = contentViewRenderView; + mEventOffsetHandler = + new EventOffsetHandler(new EventOffsetHandler.EventOffsetHandlerDelegate() { + @Override + public float getTop() { + return mTopContentOffset; + } + + @Override + public void setCurrentTouchEventOffsets(float top) { + if (mWebContents != null) { + mWebContents.getEventForwarder().setCurrentTouchEventOffsets(0, top); + } + } + }); + mNativeTopControlsContainerView = + TopControlsContainerViewJni.get().createTopControlsContainerView( + this, contentViewRenderView.getNativeHandle()); + mListener = listener; + } + + public void setWebContents(WebContents webContents) { + mWebContents = webContents; + TopControlsContainerViewJni.get().setWebContents( + mNativeTopControlsContainerView, TopControlsContainerView.this, webContents); + } + + public void destroy() { + setView(null); + TopControlsContainerViewJni.get().deleteTopControlsContainerView( + mNativeTopControlsContainerView, TopControlsContainerView.this); + } + + public long getNativeHandle() { + return mNativeTopControlsContainerView; + } + + public EventOffsetHandler getEventOffsetHandler() { + return mEventOffsetHandler; + } + + /** + * Returns the vertical offset for the WebContents. + */ + public int getTopContentOffset() { + return mView == null ? 0 : mTopContentOffset; + } + + /** + * Returns true if the top control is visible to the user. + */ + public boolean isTopControlVisible() { + // Don't check the visibility of the View itself as it's hidden while scrolling. + return mView != null && mTopContentOffset != 0; + } + + /** + * Sets the view from the client. + */ + public void setView(View view) { + if (mView == view) return; + if (mView != null) { + if (mView.getParent() == this) removeView(mView); + // TODO: need some sort of destroy to drop reference. + mViewResourceAdapter = null; + TopControlsContainerViewJni.get().deleteTopControlsLayer( + mNativeTopControlsContainerView, TopControlsContainerView.this); + mContentViewRenderView.getResourceManager() + .getDynamicResourceLoader() + .unregisterResource(TOP_CONTROLS_ID); + } + mView = view; + if (mView == null) return; + addView(view, + new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.UNSPECIFIED_GRAVITY)); + if (getWidth() > 0 && getHeight() > 0) { + view.layout(0, 0, getWidth(), getHeight()); + createAdapterAndLayer(); + } + if (mIsFullscreen) hideTopControls(); + } + + public View getView() { + return mView; + } + + /** + * Called from ViewAndroidDelegate, see it for details. + */ + public void onTopControlsChanged(int topControlsOffsetY, int topContentOffsetY) { + if (mView == null) return; + if (mIsFullscreen) return; + if (topContentOffsetY == getHeight()) { + finishTopControlsScroll(topContentOffsetY); + return; + } + if (!mInTopControlsScroll) prepareForTopControlsScroll(); + setTopControlsOffset(topControlsOffsetY, topContentOffsetY); + } + + @SuppressLint("NewApi") // Used on O+, invalidateChildInParent used for previous versions. + @Override + public void onDescendantInvalidated(View child, View target) { + super.onDescendantInvalidated(child, target); + invalidateViewResourceAdapter(); + } + + @Override + public ViewParent invalidateChildInParent(int[] location, Rect dirty) { + invalidateViewResourceAdapter(); + return super.invalidateChildInParent(location, dirty); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mView == null) return; + int width = right - left; + int height = bottom - top; + if (height != mLastHeight || width != mLastWidth) { + mLastWidth = width; + mLastHeight = height; + if (mLastWidth > 0 && mLastHeight > 0) { + if (mViewResourceAdapter == null) { + createAdapterAndLayer(); + } else { + TopControlsContainerViewJni.get().setTopControlsSize( + mNativeTopControlsContainerView, TopControlsContainerView.this, + mLastWidth, mLastHeight); + } + } + } + } + + /** + * Triggers copying the contents of mView to the offscreen buffer. + */ + private void invalidateViewResourceAdapter() { + if (mViewResourceAdapter == null || mView.getVisibility() != View.VISIBLE) return; + mViewResourceAdapter.invalidate(null); + removeCallbacks(mRefreshResourceIdRunnable); + postOnAnimation(mRefreshResourceIdRunnable); + } + + /** + * Creates mViewResourceAdapter and the layer showing a copy of mView. + */ + private void createAdapterAndLayer() { + assert mViewResourceAdapter == null; + assert mView != null; + mViewResourceAdapter = new ViewResourceAdapter(mView); + mContentViewRenderView.getResourceManager().getDynamicResourceLoader().registerResource( + TOP_CONTROLS_ID, mViewResourceAdapter); + // It's important that the layer is created immediately and always kept in sync with the + // View. Creating the layer only when needed results in a noticeable delay between when + // the layer is created and actually shown. Chrome for Android does the same thing. + TopControlsContainerViewJni.get().createTopControlsLayer( + mNativeTopControlsContainerView, TopControlsContainerView.this, TOP_CONTROLS_ID); + mLastWidth = getWidth(); + mLastHeight = getHeight(); + TopControlsContainerViewJni.get().setTopControlsSize(mNativeTopControlsContainerView, + TopControlsContainerView.this, mLastWidth, mLastHeight); + } + + private void finishTopControlsScroll(int topContentOffsetY) { + mInTopControlsScroll = false; + setTopControlsOffset(0, topContentOffsetY); + mContentViewRenderView.postOnAnimation(() -> showTopControls()); + } + + /** + * Returns true if the top-controls are completely shown or completely hidden. A return value + * of false indicates the top-controls are being moved. + */ + public boolean isTopControlsCompletelyShownOrHidden() { + return mTopContentOffset == 0 || mTopContentOffset == getHeight(); + } + + private void setTopControlsOffset(int topControlsOffsetY, int topContentOffsetY) { + mTopContentOffset = topContentOffsetY; + if (isTopControlsCompletelyShownOrHidden()) { + mListener.onTopControlsCompletelyShownOrHidden(); + } + TopControlsContainerViewJni.get().setTopControlsOffset(mNativeTopControlsContainerView, + TopControlsContainerView.this, topControlsOffsetY, topContentOffsetY); + } + + private void prepareForTopControlsScroll() { + mInTopControlsScroll = true; + mContentViewRenderView.postOnAnimation(() -> hideTopControls()); + } + + private void hideTopControls() { + if (mView != null) mView.setVisibility(View.INVISIBLE); + } + + private void showTopControls() { + if (mView != null) mView.setVisibility(View.VISIBLE); + } + + @CalledByNative + private void didToggleFullscreenModeForTab(final boolean isFullscreen) { + // Delay hiding until after the animation. This comes from Chrome code. + if (mSystemUiFullscreenResizeRunnable != null) { + getHandler().removeCallbacks(mSystemUiFullscreenResizeRunnable); + } + mSystemUiFullscreenResizeRunnable = () -> processFullscreenChanged(isFullscreen); + long delay = isFullscreen ? SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS : 0; + postDelayed(mSystemUiFullscreenResizeRunnable, delay); + } + + private void processFullscreenChanged(boolean isFullscreen) { + mSystemUiFullscreenResizeRunnable = null; + if (mIsFullscreen == isFullscreen) return; + mIsFullscreen = isFullscreen; + if (mView == null) return; + if (mIsFullscreen) { + hideTopControls(); + setTopControlsOffset(-mLastHeight, 0); + } else { + showTopControls(); + setTopControlsOffset(0, mLastHeight); + } + } + + @NativeMethods + interface Natives { + long createTopControlsContainerView( + TopControlsContainerView view, long nativeContentViewRenderView); + void deleteTopControlsContainerView( + long nativeTopControlsContainerView, TopControlsContainerView caller); + void createTopControlsLayer( + long nativeTopControlsContainerView, TopControlsContainerView caller, int id); + void deleteTopControlsLayer( + long nativeTopControlsContainerView, TopControlsContainerView caller); + void setTopControlsOffset(long nativeTopControlsContainerView, + TopControlsContainerView caller, int topControlsOffsetY, int topContentOffsetY); + void setTopControlsSize(long nativeTopControlsContainerView, + TopControlsContainerView caller, int width, int height); + void updateTopControlsResource( + long nativeTopControlsContainerView, TopControlsContainerView caller); + void setWebContents(long nativeTopControlsContainerView, TopControlsContainerView caller, + WebContents webContents); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebContentsGestureStateTracker.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebContentsGestureStateTracker.java new file mode 100644 index 00000000000..eb9ca9b1ddd --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebContentsGestureStateTracker.java @@ -0,0 +1,110 @@ +// 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.view.MotionEvent; +import android.view.View; + +import org.chromium.content_public.browser.GestureListenerManager; +import org.chromium.content_public.browser.GestureStateListener; +import org.chromium.content_public.browser.WebContents; + +/** + * WebContentsGestureStateTracker is responsible for tracking when a scroll/gesture is in progress + * and notifying when the state changes. + */ +// TODO(sky): refactor TabGestureStateListener and this to a common place. +public final class WebContentsGestureStateTracker { + private GestureListenerManager mGestureListenerManager; + private GestureStateListener mGestureListener; + private final OnGestureStateChangedListener mListener; + private boolean mScrolling; + private boolean mIsInGesture; + + /** + * The View events are tracked on. + */ + private View mContentView; + + /** + * Notified when the gesture state changes. + */ + public interface OnGestureStateChangedListener { + /** + * Called when the value of isInGestureOrScroll() changes. + */ + public void onGestureStateChanged(); + } + + public WebContentsGestureStateTracker( + View contentView, WebContents webContents, OnGestureStateChangedListener listener) { + mListener = listener; + mGestureListenerManager = GestureListenerManager.fromWebContents(webContents); + mContentView = contentView; + mContentView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + final int eventAction = event.getActionMasked(); + final boolean oldState = isInGestureOrScroll(); + if (eventAction == MotionEvent.ACTION_DOWN + || eventAction == MotionEvent.ACTION_POINTER_DOWN) { + mIsInGesture = true; + } else if (eventAction == MotionEvent.ACTION_CANCEL + || eventAction == MotionEvent.ACTION_UP) { + mIsInGesture = false; + } + if (isInGestureOrScroll() != oldState) { + mListener.onGestureStateChanged(); + } + return false; + } + }); + + mGestureListener = new GestureStateListener() { + @Override + public void onFlingStartGesture(int scrollOffsetY, int scrollExtentY) { + onScrollingStateChanged(); + } + + @Override + public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) { + onScrollingStateChanged(); + } + + @Override + public void onScrollStarted(int scrollOffsetY, int scrollExtentY) { + onScrollingStateChanged(); + } + + @Override + public void onScrollEnded(int scrollOffsetY, int scrollExtentY) { + onScrollingStateChanged(); + } + + private void onScrollingStateChanged() { + final boolean oldState = isInGestureOrScroll(); + mScrolling = mGestureListenerManager.isScrollInProgress(); + if (oldState != isInGestureOrScroll()) { + mListener.onGestureStateChanged(); + } + } + }; + mGestureListenerManager.addListener(mGestureListener); + } + + public void destroy() { + mGestureListenerManager.removeListener(mGestureListener); + mGestureListener = null; + mGestureListenerManager = null; + mContentView.setOnTouchListener(null); + } + + /** + * Returns true if the user has touched the target view, or is scrolling. + */ + public boolean isInGestureOrScroll() { + return mIsInGesture || mScrolling; + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java new file mode 100644 index 00000000000..68b11bcb5f2 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerFactoryImpl.java @@ -0,0 +1,77 @@ +// 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.os.IBinder; + +import org.chromium.base.annotations.UsedByReflection; +import org.chromium.components.version_info.VersionConstants; +import org.chromium.weblayer_private.interfaces.IWebLayer; +import org.chromium.weblayer_private.interfaces.IWebLayerFactory; +import org.chromium.weblayer_private.interfaces.WebLayerVersion; + +/** + * Factory used to create WebLayer as well as verify compatibility. + * This is constructed by the client library using reflection. + */ +@UsedByReflection("WebLayer") +public final class WebLayerFactoryImpl extends IWebLayerFactory.Stub { + private final int mClientMajorVersion; + private final String mClientVersion; + private final int mClientWeblayerVersion; + + /** + * This function is called by the client using reflection. + * + * @param clientVersion The full version string the client was compiled from. + * @param clientMajorVersion The major version number the client was compiled from. This is also + * contained in clientVersion. + * @param clientWebLayerVersion The version from interfaces.WebLayerVersion the client was + * compiled with. + */ + @UsedByReflection("WebLayer") + public static IBinder create( + String clientVersion, int clientMajorVersion, int clientWebLayerVersion) { + return new WebLayerFactoryImpl(clientVersion, clientMajorVersion, clientWebLayerVersion); + } + + private WebLayerFactoryImpl( + String clientVersion, int clientMajorVersion, int clientWeblayerVersion) { + mClientMajorVersion = clientMajorVersion; + mClientVersion = clientVersion; + mClientWeblayerVersion = clientWeblayerVersion; + } + + /** + * Returns true if the client compiled with the specific version is compatible with this + * implementation. The client library calls this exactly once. + */ + @Override + public boolean isClientSupported() { + return mClientWeblayerVersion == WebLayerVersion.sVersionNumber; + } + + /** + * Returns the major version of the implementation. + */ + @Override + public int getImplementationMajorVersion() { + return VersionConstants.PRODUCT_MAJOR_VERSION; + } + + /** + * Returns the full version string of the implementation. + */ + @Override + public String getImplementationVersion() { + return VersionConstants.PRODUCT_VERSION; + } + + @Override + public IWebLayer createWebLayer() { + assert isClientSupported(); + return new WebLayerImpl(); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java index 7f6069e8eda..5ba843f2e25 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java @@ -4,80 +4,253 @@ package org.chromium.weblayer_private; -import android.app.Application; import android.content.Context; -import android.content.ContextWrapper; -import android.content.res.Resources; -import android.os.IBinder; -import android.util.AndroidRuntimeException; +import android.content.pm.PackageInfo; +import android.content.res.AssetManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.content.FileProvider; +import android.util.SparseArray; +import android.webkit.ValueCallback; +import android.webkit.WebViewDelegate; +import android.webkit.WebViewFactory; -import org.chromium.base.ApplicationStatus; +import org.chromium.base.BuildInfo; import org.chromium.base.CommandLine; +import org.chromium.base.ContentUriUtils; import org.chromium.base.ContextUtils; -import org.chromium.base.Log; import org.chromium.base.PathUtils; +import org.chromium.base.StrictModeContext; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; import org.chromium.base.library_loader.LibraryLoader; import org.chromium.base.library_loader.LibraryProcessType; -import org.chromium.base.library_loader.ProcessInitException; +import org.chromium.components.embedder_support.application.ClassLoaderContextWrapperFactory; import org.chromium.content_public.browser.BrowserStartupController; import org.chromium.content_public.browser.ChildProcessCreationParams; import org.chromium.content_public.browser.DeviceUtils; import org.chromium.ui.base.ResourceBundle; -import org.chromium.weblayer_private.aidl.IProfile; -import org.chromium.weblayer_private.aidl.IWebLayer; +import org.chromium.weblayer_private.interfaces.IBrowserFragment; +import org.chromium.weblayer_private.interfaces.ICrashReporterController; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.IProfile; +import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient; +import org.chromium.weblayer_private.interfaces.IWebLayer; +import org.chromium.weblayer_private.interfaces.ObjectWrapper; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * Root implementation class for WebLayer. + */ +@JNINamespace("weblayer") public final class WebLayerImpl extends IWebLayer.Stub { // TODO: should there be one tag for all this code? private static final String TAG = "WebLayer"; - private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "weblayer"; + private static final String PRIVATE_DIRECTORY_SUFFIX = "weblayer"; // TODO: Configure this from the client. private static final String COMMAND_LINE_FILE = "/data/local/tmp/weblayer-command-line"; - public static IBinder create(Application application, Context implContext) { - return new WebLayerImpl(application, implContext); + private final ProfileManager mProfileManager = new ProfileManager(); + + private boolean mInited; + + private static class FileProviderHelper implements ContentUriUtils.FileProviderUtil { + // Keep this variable in sync with the value defined in AndroidManifest.xml. + private static final String API_AUTHORITY_SUFFIX = + ".org.chromium.weblayer.client.FileProvider"; + + @Override + public Uri getContentUriFromFile(File file) { + Context appContext = ContextUtils.getApplicationContext(); + return FileProvider.getUriForFile( + appContext, appContext.getPackageName() + API_AUTHORITY_SUFFIX, file); + } + } + + WebLayerImpl() {} + + /** + * Performs the minimal initialization needed for a context. This is used for example in + * CrashReporterControllerImpl, so it can be used before full WebLayer initialization. + */ + public static Context minimalInitForContext(IObjectWrapper appContextWrapper) { + if (ContextUtils.getApplicationContext() != null) { + return ContextUtils.getApplicationContext(); + } + // Wrap the app context so that it can be used to load WebLayer implementation classes. + Context appContext = ClassLoaderContextWrapperFactory.get( + ObjectWrapper.unwrap(appContextWrapper, Context.class)); + ContextUtils.initApplicationContext(appContext); + PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DIRECTORY_SUFFIX, PRIVATE_DIRECTORY_SUFFIX); + return appContext; } @Override - public IProfile createProfile(String path) { - return new ProfileImpl(path); + public void loadAsync( + IObjectWrapper appContextWrapper, IObjectWrapper loadedCallbackWrapper) { + init(appContextWrapper); + + final ValueCallback<Boolean> loadedCallback = (ValueCallback<Boolean>) ObjectWrapper.unwrap( + loadedCallbackWrapper, ValueCallback.class); + BrowserStartupController.get(LibraryProcessType.PROCESS_WEBLAYER) + .startBrowserProcessesAsync(/* startGpu */ false, + /* startServiceManagerOnly */ false, + new BrowserStartupController.StartupCallback() { + @Override + public void onSuccess() { + CrashReporterControllerImpl.getInstance(appContextWrapper) + .notifyNativeInitialized(); + loadedCallback.onReceiveValue(true); + } + @Override + public void onFailure() { + loadedCallback.onReceiveValue(false); + } + }); } - private WebLayerImpl(Application application, Context implContext) { - ContextUtils.initApplicationContext(new ContextWrapper(application) { - @Override - public Resources getResources() { - // Always use resources from the implementation APK. - return implContext.getResources(); - } - }); - ResourceBundle.setNoAvailableLocalePaks(); - PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX); - ApplicationStatus.initialize(application); + @Override + public void loadSync(IObjectWrapper appContextWrapper) { + init(appContextWrapper); - ChildProcessCreationParams.set(implContext.getPackageName(), true /* isExternalService */, - LibraryProcessType.PROCESS_CHILD, true /* bindToCaller */, - false /* ignoreVisibilityForImportance */); + BrowserStartupController.get(LibraryProcessType.PROCESS_WEBLAYER) + .startBrowserProcessesSync( + /* singleProcess*/ false); + CrashReporterControllerImpl.getInstance(appContextWrapper).notifyNativeInitialized(); + } + + private void init(IObjectWrapper appContextWrapper) { + if (mInited) { + return; + } + mInited = true; + + Context appContext = minimalInitForContext(appContextWrapper); + PackageInfo packageInfo = WebViewFactory.getLoadedPackageInfo(); + + // TODO: This can break some functionality of apps that are doing interesting things with + // Contexts, ideally we would find a better way to do this. + addWebViewAssetPath(appContext, packageInfo); + + BuildInfo.setBrowserPackageInfo(packageInfo); + int resourcesPackageId = getPackageId(appContext, packageInfo.packageName); + // TODO: The call to onResourcesLoaded() can be slow, we may need to parallelize this with + // other expensive startup tasks. + R.onResourcesLoaded(resourcesPackageId); + + ResourceBundle.setAvailablePakLocales(new String[] {}, LocaleConfig.UNCOMPRESSED_LOCALES); + + ChildProcessCreationParams.set(appContext.getPackageName(), false /* isExternalService */, + LibraryProcessType.PROCESS_WEBLAYER_CHILD, true /* bindToCaller */, + false /* ignoreVisibilityForImportance */, + "org.chromium.weblayer.ChildProcessService$Privileged", + "org.chromium.weblayer.ChildProcessService$Sandboxed"); if (!CommandLine.isInitialized()) { - CommandLine.initFromFile(COMMAND_LINE_FILE); + // This disk read in the critical path is for development purposes only. + // TODO: Move it to debug-only (similar to WebView), or allow clients to configure the + // command line. + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + CommandLine.initFromFile(COMMAND_LINE_FILE); + } } DeviceUtils.addDeviceSpecificUserAgentSwitch(); + ContentUriUtils.setFileProviderUtil(new FileProviderHelper()); + // TODO: Validate that doing this disk IO on the main thread is necessary. + try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { + LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_WEBLAYER); + } + GmsBridge.getInstance().setSafeBrowsingHandler(); + } + + @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(); + } + + @Override + public IProfile getProfile(String profileName) { + return mProfileManager.getProfile(profileName); + } + + @Override + public void setRemoteDebuggingEnabled(boolean enabled) { + WebLayerImplJni.get().setRemoteDebuggingEnabled(enabled); + } + + @Override + public boolean isRemoteDebuggingEnabled() { + return WebLayerImplJni.get().isRemoteDebuggingEnabled(); + } + + @Override + public ICrashReporterController getCrashReporterController(IObjectWrapper appContext) { + return CrashReporterControllerImpl.getInstance(appContext); + } + + private static void addWebViewAssetPath(Context appContext, PackageInfo packageInfo) { try { - LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); - } catch (ProcessInitException e) { - Log.e(TAG, "ContentView initialization failed.", e); - throw new AndroidRuntimeException(e); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + Constructor constructor = WebViewDelegate.class.getDeclaredConstructor(); + constructor.setAccessible(true); + WebViewDelegate delegate = (WebViewDelegate) constructor.newInstance(); + delegate.addWebViewAssetPath(appContext); + } else { + // In L WebViewDelegate did not yet exist, so we have to poke AssetManager directly. + // Note: like the implementation in WebView's Api21CompatibilityDelegate this does + // not support split APKs. + Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class); + addAssetPath.invoke(appContext.getResources().getAssets(), + packageInfo.applicationInfo.sourceDir); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); } + } + /** + * Returns the package ID to use when calling R.onResourcesLoaded(). + */ + private static int getPackageId(Context appContext, String implPackageName) { try { - BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER) - .startBrowserProcessesSync( - /* singleProcess*/ false); - } catch (ProcessInitException e) { - Log.e(TAG, "Unable to load native library.", e); - throw new AndroidRuntimeException(e); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + Constructor constructor = WebViewDelegate.class.getDeclaredConstructor(); + constructor.setAccessible(true); + WebViewDelegate delegate = (WebViewDelegate) constructor.newInstance(); + return delegate.getPackageId(appContext.getResources(), implPackageName); + } else { + // In L WebViewDelegate did not yet exist, so we have to look inside AssetManager. + Method getAssignedPackageIdentifiers = + AssetManager.class.getMethod("getAssignedPackageIdentifiers"); + SparseArray packageIdentifiers = (SparseArray) getAssignedPackageIdentifiers.invoke( + appContext.getResources().getAssets()); + for (int i = 0; i < packageIdentifiers.size(); i++) { + final String name = (String) packageIdentifiers.valueAt(i); + + if (implPackageName.equals(name)) { + return packageIdentifiers.keyAt(i); + } + } + throw new RuntimeException("Package not found: " + implPackageName); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); } } + + @NativeMethods + interface Natives { + void setRemoteDebuggingEnabled(boolean enabled); + boolean isRemoteDebuggingEnabled(); + } } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IBrowserController.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IBrowserController.aidl deleted file mode 100644 index 778e0445d60..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IBrowserController.aidl +++ /dev/null @@ -1,20 +0,0 @@ -// 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.IBrowserControllerClient; -import org.chromium.weblayer_private.aidl.IObjectWrapper; - -interface IBrowserController { - void setClient(in IBrowserControllerClient client) = 0; - - INavigationController createNavigationController(in INavigationControllerClient client) = 1; - - void setTopView(in IObjectWrapper view) = 2; - - void destroy() = 3; - - IObjectWrapper onCreateView() = 4; -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IBrowserControllerClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IBrowserControllerClient.aidl deleted file mode 100644 index 11f52522106..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IBrowserControllerClient.aidl +++ /dev/null @@ -1,14 +0,0 @@ -// 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; - -/** - * Interface used by BrowserController to inform the client of changes. This largely duplicates the - * BrowserObserver interface, but is a singleton to avoid unnecessary IPC. - */ -interface IBrowserControllerClient { - /** The Uri that should be displayed in the url-bar has updated. */ - void displayURLChanged(in String url) = 0; -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IProfile.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IProfile.aidl deleted file mode 100644 index d68789343ad..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IProfile.aidl +++ /dev/null @@ -1,16 +0,0 @@ -// 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.IBrowserController; -import org.chromium.weblayer_private.aidl.IObjectWrapper; - -interface IProfile { - void destroy() = 0; - - void clearBrowsingData() = 1; - - IBrowserController createBrowserController(in IObjectWrapper context) = 2; -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IWebLayer.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IWebLayer.aidl deleted file mode 100644 index 7625d7b6bf7..00000000000 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IWebLayer.aidl +++ /dev/null @@ -1,11 +0,0 @@ -// 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.IProfile; - -interface IWebLayer { - IProfile createProfile(in String path) = 0; -} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/APICallException.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/APICallException.java new file mode 100644 index 00000000000..59366a751c7 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/APICallException.java @@ -0,0 +1,19 @@ +// 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.interfaces; + +import android.util.AndroidRuntimeException; + +/** + * Error thrown if there is an error communicating over the AIDL boundary. + */ +public class APICallException extends AndroidRuntimeException { + /** + * Constructs a new exception with the specified cause. + */ + public APICallException(Exception cause) { + super(cause); + } +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/BrowserFragmentArgs.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/BrowserFragmentArgs.java new file mode 100644 index 00000000000..cefc545ded7 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/BrowserFragmentArgs.java @@ -0,0 +1,8 @@ +// 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.interfaces; + +/** Keys for the Bundle of arguments with which BrowserFragments are created. */ +public interface BrowserFragmentArgs { String PROFILE_NAME = "profile_name"; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/BrowsingDataType.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/BrowsingDataType.java new file mode 100644 index 00000000000..840bfae909c --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/BrowsingDataType.java @@ -0,0 +1,17 @@ +// 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.interfaces; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({BrowsingDataType.COOKIES_AND_SITE_DATA, BrowsingDataType.CACHE}) +@Retention(RetentionPolicy.SOURCE) +public @interface BrowsingDataType { + int COOKIES_AND_SITE_DATA = 0; + int CACHE = 1; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl new file mode 100644 index 00000000000..c262dee3c34 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowser.aidl @@ -0,0 +1,33 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IBrowserClient; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.ITab; +import org.chromium.weblayer_private.interfaces.IProfile; + +import java.util.List; + +interface IBrowser { + IProfile getProfile() = 0; + void setTopView(in IObjectWrapper view) = 1; + + // |valueCallback| is a wrapped ValueCallback<Boolean> instead. The bool value in |valueCallback| + // indicates is whether the request was successful. Request might fail if it is subsumed by a + // following request, or if this object is destroyed. + void setSupportsEmbedding(in boolean enable, in IObjectWrapper valueCallback) = 2; + + // Sets the active tab, returns false if tab is not attached to this fragment. + boolean setActiveTab(in ITab tab) = 3; + + int getActiveTabId() = 4; + List getTabs() = 5; + + void setClient(in IBrowserClient client) = 6; + + void addTab(in ITab tab) = 7; + void destroyTab(in ITab tab) = 8; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowserClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowserClient.aidl new file mode 100644 index 00000000000..c23221efd1d --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowserClient.aidl @@ -0,0 +1,13 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.ITab; + +interface IBrowserClient { + void onActiveTabChanged(in int activeTabId) = 0; + void onTabAdded(in ITab tab) = 1; + void onTabRemoved(in int tabId) = 2; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowserFragment.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowserFragment.aidl new file mode 100644 index 00000000000..7b7d3b218e8 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IBrowserFragment.aidl @@ -0,0 +1,13 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IBrowser; +import org.chromium.weblayer_private.interfaces.IRemoteFragment; + +interface IBrowserFragment { + IRemoteFragment asRemoteFragment() = 0; + IBrowser getBrowser() = 1; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IChildProcessService.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IChildProcessService.aidl new file mode 100644 index 00000000000..87402311731 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IChildProcessService.aidl @@ -0,0 +1,16 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +/** Interface to forward service calls to the service implementation. */ +interface IChildProcessService { + void onCreate() = 0; + + void onDestroy() = 1; + + IObjectWrapper onBind(IObjectWrapper intent) = 2; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IClientNavigation.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IClientNavigation.aidl index 442060c0335..2adeaa34148 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IClientNavigation.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IClientNavigation.aidl @@ -2,7 +2,7 @@ // 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; +package org.chromium.weblayer_private.interfaces; /** * Represents a navigation on the *client* side. diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ICrashReporterController.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ICrashReporterController.aidl new file mode 100644 index 00000000000..04e963521b3 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ICrashReporterController.aidl @@ -0,0 +1,18 @@ +// 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.interfaces; + +import android.os.Bundle; + +import org.chromium.weblayer_private.interfaces.ICrashReporterControllerClient; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +interface ICrashReporterController { + void setClient(in ICrashReporterControllerClient client) = 0; + void checkForPendingCrashReports() = 1; + Bundle getCrashKeys(in String localId) = 2; + void deleteCrash(in String localId) = 3; + void uploadCrash(in String localId) = 4; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ICrashReporterControllerClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ICrashReporterControllerClient.aidl new file mode 100644 index 00000000000..caf744e4eb4 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ICrashReporterControllerClient.aidl @@ -0,0 +1,16 @@ +// 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.interfaces; + +import android.os.Bundle; + +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +interface ICrashReporterControllerClient { + void onPendingCrashReports(in String[] localIds) = 0; + void onCrashDeleted(in String localId) = 1; + void onCrashUploadSucceeded(in String localId, in String reportId) = 2; + void onCrashUploadFailed(in String localId, in String failureMessage) = 3; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl new file mode 100644 index 00000000000..830f709b45f --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IDownloadCallbackClient.aidl @@ -0,0 +1,12 @@ +// 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.interfaces; + +/** + * Used to forward download requests to the client. + */ +interface IDownloadCallbackClient { + boolean interceptDownload(in String uriString, in String userAgent, in String contentDisposition, in String mimetype, long contentLength) = 0; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IErrorPageCallbackClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IErrorPageCallbackClient.aidl new file mode 100644 index 00000000000..1dfc22b1a2e --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IErrorPageCallbackClient.aidl @@ -0,0 +1,13 @@ +// 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.interfaces; + +/** + * Allows the client to override the default way of handling user interactions + * with error pages (such as SSL interstitials). + */ +interface IErrorPageCallbackClient { + boolean onBackToSafety() = 0; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IFullscreenCallbackClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IFullscreenCallbackClient.aidl new file mode 100644 index 00000000000..fff55334c24 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IFullscreenCallbackClient.aidl @@ -0,0 +1,17 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +/** + * Used to forward FullscreenCallback calls to the client. + */ +interface IFullscreenCallbackClient { + // exitFullscreenWrapper is a ValueCallback<Void> that when run exits + // fullscreen. + void enterFullscreen(in IObjectWrapper exitFullscreenWrapper) = 0; + void exitFullscreen() = 1; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/INavigation.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl index b0194de42dc..7a7a8969502 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/INavigation.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigation.aidl @@ -2,7 +2,7 @@ // 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; +package org.chromium.weblayer_private.interfaces; /** * Provides information about a navigation. @@ -13,4 +13,12 @@ interface INavigation { String getUri() = 1; List<String> getRedirectChain() = 2; + + int getHttpStatusCode() = 3; + + boolean isSameDocument() = 4; + + boolean isErrorPage() = 5; + + int getLoadError() = 6; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/INavigationController.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl index fe744bfc227..08467c4bba1 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/INavigationController.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationController.aidl @@ -2,7 +2,7 @@ // 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; +package org.chromium.weblayer_private.interfaces; interface INavigationController { void navigate(in String uri) = 0; @@ -20,4 +20,8 @@ interface INavigationController { int getNavigationListCurrentIndex() = 6; String getNavigationEntryDisplayUri(in int index) = 7; + + boolean canGoBack() = 8; + + boolean canGoForward() = 9; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/INavigationControllerClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl index d5571424afc..60132f87c71 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/INavigationControllerClient.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/INavigationControllerClient.aidl @@ -2,11 +2,14 @@ // 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; +package org.chromium.weblayer_private.interfaces; + +import org.chromium.weblayer_private.interfaces.IClientNavigation; +import org.chromium.weblayer_private.interfaces.INavigation; /** * Interface used by NavigationController to inform the client of changes. This largely duplicates - * the NavigationObserver interface, but is a singleton to avoid unnecessary IPC. + * the NavigationCallback interface, but is a singleton to avoid unnecessary IPC. */ interface INavigationControllerClient { IClientNavigation createClientNavigation(in INavigation impl) = 0; @@ -15,9 +18,15 @@ interface INavigationControllerClient { void navigationRedirected(IClientNavigation navigation) = 2; - void navigationCommitted(IClientNavigation navigation) = 3; + void readyToCommitNavigation(IClientNavigation navigation) = 3; void navigationCompleted(IClientNavigation navigation) = 4; void navigationFailed(IClientNavigation navigation) = 5; + + void loadStateChanged(boolean isLoading, boolean toDifferentDocument) = 6; + + void loadProgressChanged(double progress) = 7; + + void onFirstContentfulPaint() = 8; } diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IObjectWrapper.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IObjectWrapper.aidl index 34f8d76df5b..91746ddce47 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/IObjectWrapper.aidl +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IObjectWrapper.aidl @@ -2,7 +2,7 @@ // 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; +package org.chromium.weblayer_private.interfaces; /** * This interface intentionally has no methods, and instances of this should diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl new file mode 100644 index 00000000000..ef92f749a96 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IProfile.aidl @@ -0,0 +1,16 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +interface IProfile { + void destroy() = 0; + + void clearBrowsingData(in int[] dataTypes, long fromMillis, long toMillis, + in IObjectWrapper completionCallback) = 1; + + String getName() = 2; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IRemoteFragment.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IRemoteFragment.aidl new file mode 100644 index 00000000000..65b22e106e6 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IRemoteFragment.aidl @@ -0,0 +1,30 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +interface IRemoteFragment { + // Using IObjectWrapper instead of Bundle to pass by reference instead of by value. + void handleOnCreate(in IObjectWrapper savedInstanceState) = 0; + void handleOnAttach(in IObjectWrapper context) = 1; + void handleOnActivityCreated(in IObjectWrapper savedInstanceState) = 2; + IObjectWrapper handleOnCreateView() = 3; + void handleOnStart() = 4; + void handleOnResume() = 5; + void handleOnPause() = 6; + void handleOnStop() = 7; + void handleOnDestroyView() = 8; + void handleOnDetach() = 9; + void handleOnDestroy() = 10; + void handleOnSaveInstanceState(in IObjectWrapper outState) = 11; + // |data| is an Intent with the result returned from the activity. + void handleOnActivityResult(int requestCode, + int resultCode, + in IObjectWrapper data) = 12; + void handleOnRequestPermissionsResult(int requestCode, + in String[] permissions, + in int[] grantResults) = 13; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IRemoteFragmentClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IRemoteFragmentClient.aidl new file mode 100644 index 00000000000..34f41b90c39 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IRemoteFragmentClient.aidl @@ -0,0 +1,35 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IObjectWrapper; + +interface IRemoteFragmentClient { + void superOnCreate(in IObjectWrapper savedInstanceState) = 0; + void superOnAttach(in IObjectWrapper context) = 1; + void superOnActivityCreated(in IObjectWrapper savedInstanceState) = 2; + void superOnStart() = 3; + void superOnResume() = 4; + void superOnPause() = 5; + void superOnStop() = 6; + void superOnDestroyView() = 7; + void superOnDetach() = 8; + void superOnDestroy() = 9; + void superOnSaveInstanceState(in IObjectWrapper outState) = 10; + + IObjectWrapper getActivity() = 11; + boolean startActivityForResult(in IObjectWrapper intent, + int requestCode, + in IObjectWrapper options) = 12; + boolean startIntentSenderForResult(in IObjectWrapper intent, + int requestCode, + in IObjectWrapper fillInIntent, + int flagsMask, + int flagsValues, + int extraFlags, + in IObjectWrapper options) = 13; + boolean shouldShowRequestPermissionRationale(String permission) = 14; + void requestPermissions(in String[] permissions, int requestCode) = 15; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl new file mode 100644 index 00000000000..b153adcad09 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITab.aidl @@ -0,0 +1,34 @@ +// 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.interfaces; + +import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient; +import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient; +import org.chromium.weblayer_private.interfaces.IFullscreenCallbackClient; +import org.chromium.weblayer_private.interfaces.INavigationController; +import org.chromium.weblayer_private.interfaces.INavigationControllerClient; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.ITabClient; + +interface ITab { + void setClient(in ITabClient client) = 0; + + INavigationController createNavigationController(in INavigationControllerClient client) = 1; + + void setDownloadCallbackClient(IDownloadCallbackClient client) = 2; + + void setErrorPageCallbackClient(IErrorPageCallbackClient client) = 3; + + void setFullscreenCallbackClient(in IFullscreenCallbackClient client) = 4; + + void executeScript(in String script, boolean useSeparateIsolate, in IObjectWrapper callback) = 5; + + void setNewTabsEnabled(in boolean enabled) = 6; + + // Returns a unique identifier for this Tab. The id is *not* unique across + // restores. The id is intended for the client library to avoid creating duplicate client objects + // for the same ITab. + int getId() = 7; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl new file mode 100644 index 00000000000..92bdac6c5e8 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ITabClient.aidl @@ -0,0 +1,19 @@ +// 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.interfaces; + +/** + * Interface used by Tab to inform the client of changes. This largely duplicates the + * TabCallback interface, but is a singleton to avoid unnecessary IPC. + */ +interface ITabClient { + void visibleUriChanged(in String uriString) = 0; + + void onNewTab(in int tabId, in int mode) = 1; + + void onRenderProcessGone() = 2; + + void onCloseTab() = 3; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl new file mode 100644 index 00000000000..7e4be7f0c53 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayer.aidl @@ -0,0 +1,54 @@ +// 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.interfaces; + +import android.os.Bundle; + +import org.chromium.weblayer_private.interfaces.IBrowserFragment; +import org.chromium.weblayer_private.interfaces.ICrashReporterController; +import org.chromium.weblayer_private.interfaces.IObjectWrapper; +import org.chromium.weblayer_private.interfaces.IProfile; +import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient; + +interface IWebLayer { + // Initializes WebLayer and starts loading. + // + // It is expected that either loadAsync or loadSync is called before anything else. + // + // @param appContext A Context that refers to the Application using WebLayer. + // @param loadedCallback A ValueCallback that will be called when load completes. + void loadAsync(in IObjectWrapper appContext, + in IObjectWrapper loadedCallback) = 1; + + // Initializes WebLayer, starts loading and blocks until loading has completed. + // + // It is expected that either loadAsync or loadSync is called before anything else. + // + // @param appContext A Context that refers to the Application using WebLayer. + void loadSync(in IObjectWrapper appContext) = 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; + + // Create or get the profile matching profileName. + IProfile getProfile(in String profileName) = 4; + + // Enable or disable DevTools remote debugging server. + void setRemoteDebuggingEnabled(boolean enabled) = 5; + + // Returns whether or not the DevTools remote debugging server is enabled. + boolean isRemoteDebuggingEnabled() = 6; + + // Returns the singleton crash reporter controller. If WebLayer has not been + // initialized, does the minimum initialization needed for the crash reporter. + ICrashReporterController getCrashReporterController( + in IObjectWrapper appContext) = 7; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerFactory.aidl b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerFactory.aidl new file mode 100644 index 00000000000..214a966394a --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerFactory.aidl @@ -0,0 +1,26 @@ +// 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.interfaces; + +import android.os.Bundle; + +import org.chromium.weblayer_private.interfaces.IWebLayer; + +// Factory for creating IWebLayer as well as determining if a particular version +// of a client is supported. +interface IWebLayerFactory { + // Returns true if a client with the specified version is supported. + boolean isClientSupported() = 0; + + // Creates a new IWebLayer. It is expected that a client has a single + // IWebLayer. Further, at this time, only a single client is supported. + IWebLayer createWebLayer() = 1; + + // Returns the full version string of the implementation. + String getImplementationVersion() = 2; + + // Returns the major version of the implementation. + int getImplementationMajorVersion() = 3; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/LoadError.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/LoadError.java new file mode 100644 index 00000000000..eba5fbf0c51 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/LoadError.java @@ -0,0 +1,22 @@ +// 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.interfaces; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({LoadError.NO_ERROR, LoadError.HTTP_CLIENT_ERROR, LoadError.HTTP_SERVER_ERROR, + LoadError.SSL_ERROR, LoadError.CONNECTIVITY_ERROR, LoadError.OTHER_ERROR}) +@Retention(RetentionPolicy.SOURCE) +public @interface LoadError { + int NO_ERROR = 0; + int HTTP_CLIENT_ERROR = 1; + int HTTP_SERVER_ERROR = 2; + int SSL_ERROR = 3; + int CONNECTIVITY_ERROR = 4; + int OTHER_ERROR = 5; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/NavigationState.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/NavigationState.java new file mode 100644 index 00000000000..d0c0f608a8a --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/NavigationState.java @@ -0,0 +1,20 @@ +// 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.interfaces; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({NavigationState.WAITING_RESPONSE, NavigationState.RECEIVING_BYTES, + NavigationState.COMPLETE, NavigationState.FAILED}) +@Retention(RetentionPolicy.SOURCE) +public @interface NavigationState { + int WAITING_RESPONSE = 0; + int RECEIVING_BYTES = 1; + int COMPLETE = 2; + int FAILED = 3; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/NewTabType.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/NewTabType.java new file mode 100644 index 00000000000..dbd83525e84 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/NewTabType.java @@ -0,0 +1,20 @@ +// 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.interfaces; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@IntDef({NewTabType.FOREGROUND_TAB, NewTabType.BACKGROUND_TAB, NewTabType.NEW_POPUP, + NewTabType.NEW_WINDOW}) +@Retention(RetentionPolicy.SOURCE) +public @interface NewTabType { + int FOREGROUND_TAB = 0; + int BACKGROUND_TAB = 1; + int NEW_POPUP = 2; + int NEW_WINDOW = 3; +} diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/ObjectWrapper.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ObjectWrapper.java index 2a510722d75..41beb409765 100644 --- a/chromium/weblayer/browser/java/org/chromium/weblayer_private/aidl/ObjectWrapper.java +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/ObjectWrapper.java @@ -2,7 +2,7 @@ // 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; +package org.chromium.weblayer_private.interfaces; import android.os.IBinder; diff --git a/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersion.java b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersion.java new file mode 100644 index 00000000000..381431a1019 --- /dev/null +++ b/chromium/weblayer/browser/java/org/chromium/weblayer_private/interfaces/WebLayerVersion.java @@ -0,0 +1,12 @@ +// 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.interfaces; + +/** + * Holds the current version number of WebLayer. + * + * Whenever any AIDL file is changed, sVersionNumber must be incremented. + * */ +public final class WebLayerVersion { public static final int sVersionNumber = 20; } |