thorium_shell android fix

This commit is contained in:
Alexander Frick 2023-03-07 10:19:24 -06:00
parent eb8f19ccfc
commit 44ad3ce211
5 changed files with 639 additions and 2 deletions

View file

@ -38,6 +38,8 @@ enable_nacl = false
optimize_webui = true
treat_warnings_as_errors = false
system_webview_package_name = "com.android.webview"
trichrome_library_package = "org.thorium.trichromelibrary"
system_webview_shell_package_name = "org.thorium.webview_shell"
webview_includes_weblayer = true
use_lld = true
v8_symbol_level = 0
@ -63,7 +65,7 @@ use_webaudio_pffft = true
# enable_dav1d_decoder = true
enable_widevine = true
bundle_widevine_cdm = false
enable_widevine_cdm_component = true
# enable_widevine_cdm_component = true
enable_widevine_cdm_host_verification = false
ignore_missing_widevine_signing_cert = true
enable_media_drm_storage = true

View file

@ -38,6 +38,8 @@ enable_nacl = false
optimize_webui = true
treat_warnings_as_errors = false
system_webview_package_name = "com.android.webview"
trichrome_library_package = "org.thorium.trichromelibrary"
system_webview_shell_package_name = "org.thorium.webview_shell"
webview_includes_weblayer = true
use_lld = true
v8_symbol_level = 0
@ -63,7 +65,7 @@ use_webaudio_pffft = true
# enable_dav1d_decoder = true
enable_widevine = true
bundle_widevine_cdm = false
enable_widevine_cdm_component = true
# enable_widevine_cdm_component = true
enable_widevine_cdm_host_verification = false
ignore_missing_widevine_signing_cert = true
enable_media_drm_storage = true

View file

@ -0,0 +1,421 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.thorium_shell;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.ClipDrawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
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.components.embedder_support.view.ContentView;
import org.chromium.components.embedder_support.view.ContentViewRenderView;
import org.chromium.content_public.browser.ActionModeCallbackHelper;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.base.WindowAndroid;
/**
* Container for the various UI components that make up a shell window.
*/
@JNINamespace("content")
public class Shell extends LinearLayout {
private static final long COMPLETED_PROGRESS_TIMEOUT_MS = 200;
// Stylus handwriting: Setting this ime option instructs stylus writing service to restrict
// capturing writing events slightly outside the Url bar area. This is needed to prevent stylus
// handwriting in inputs in web content area that are very close to url bar area, from being
// committed to Url bar's Edit text. Ex: google.com search field.
private static final String IME_OPTION_RESTRICT_STYLUS_WRITING_AREA =
"restrictDirectWritingArea=true";
private final Runnable mClearProgressRunnable = new Runnable() {
@Override
public void run() {
mProgressDrawable.setLevel(0);
}
};
private WebContents mWebContents;
private NavigationController mNavigationController;
private EditText mUrlTextView;
private ImageButton mPrevButton;
private ImageButton mNextButton;
private ImageButton mStopReloadButton;
private ClipDrawable mProgressDrawable;
private long mNativeShell;
private ContentViewRenderView mContentViewRenderView;
private WindowAndroid mWindow;
private ShellViewAndroidDelegate mViewAndroidDelegate;
private boolean mLoading;
private boolean mIsFullscreen;
private Callback<Boolean> mOverlayModeChangedCallbackForTesting;
/**
* Constructor for inflating via XML.
*/
public Shell(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Set the SurfaceView being renderered to as soon as it is available.
*/
public void setContentViewRenderView(ContentViewRenderView contentViewRenderView) {
FrameLayout contentViewHolder = (FrameLayout) findViewById(R.id.contentview_holder);
if (contentViewRenderView == null) {
if (mContentViewRenderView != null) {
contentViewHolder.removeView(mContentViewRenderView);
}
} else {
contentViewHolder.addView(contentViewRenderView,
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
}
mContentViewRenderView = contentViewRenderView;
}
/**
* Initializes the Shell for use.
*
* @param nativeShell The pointer to the native Shell object.
* @param window The owning window for this shell.
*/
public void initialize(long nativeShell, WindowAndroid window) {
mNativeShell = nativeShell;
mWindow = window;
}
/**
* Closes the shell and cleans up the native instance, which will handle destroying all
* dependencies.
*/
public void close() {
if (mNativeShell == 0) return;
ShellJni.get().closeShell(mNativeShell);
}
@CalledByNative
private void onNativeDestroyed() {
mWindow = null;
mNativeShell = 0;
mWebContents = null;
}
/**
* @return Whether the Shell has been destroyed.
* @see #onNativeDestroyed()
*/
public boolean isDestroyed() {
return mNativeShell == 0;
}
/**
* @return Whether or not the Shell is loading content.
*/
public boolean isLoading() {
return mLoading;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
View toolbar = findViewById(R.id.toolbar);
mProgressDrawable = (ClipDrawable) toolbar.getBackground();
initializeUrlField();
initializeNavigationButtons();
}
private void initializeUrlField() {
mUrlTextView = (EditText) findViewById(R.id.url);
mUrlTextView.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((actionId != EditorInfo.IME_ACTION_GO) && (event == null
|| event.getKeyCode() != KeyEvent.KEYCODE_ENTER
|| event.getAction() != KeyEvent.ACTION_DOWN)) {
return false;
}
loadUrl(mUrlTextView.getText().toString());
setKeyboardVisibilityForUrl(false);
getContentView().requestFocus();
return true;
}
});
mUrlTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
setKeyboardVisibilityForUrl(hasFocus);
mNextButton.setVisibility(hasFocus ? GONE : VISIBLE);
mPrevButton.setVisibility(hasFocus ? GONE : VISIBLE);
mStopReloadButton.setVisibility(hasFocus ? GONE : VISIBLE);
if (!hasFocus) {
mUrlTextView.setText(mWebContents.getVisibleUrl().getSpec());
}
}
});
mUrlTextView.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
getContentView().requestFocus();
return true;
}
return false;
}
});
mUrlTextView.setPrivateImeOptions(IME_OPTION_RESTRICT_STYLUS_WRITING_AREA);
}
/**
* Loads an URL. This will perform minimal amounts of sanitizing of the URL to attempt to
* make it valid.
*
* @param url The URL to be loaded by the shell.
*/
public void loadUrl(String url) {
if (url == null) return;
if (TextUtils.equals(url, mWebContents.getLastCommittedUrl().getSpec())) {
mNavigationController.reload(true);
} else {
mNavigationController.loadUrl(new LoadUrlParams(sanitizeUrl(url)));
}
mUrlTextView.clearFocus();
// TODO(aurimas): Remove this when crbug.com/174541 is fixed.
getContentView().clearFocus();
getContentView().requestFocus();
}
/**
* Given an URL, this performs minimal sanitizing to ensure it will be valid.
* @param url The url to be sanitized.
* @return The sanitized URL.
*/
public static String sanitizeUrl(String url) {
if (url == null) return null;
if (url.startsWith("www.") || url.indexOf(":") == -1) url = "http://" + url;
return url;
}
private void initializeNavigationButtons() {
mPrevButton = (ImageButton) findViewById(R.id.prev);
mPrevButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mNavigationController.canGoBack()) mNavigationController.goBack();
}
});
mNextButton = (ImageButton) findViewById(R.id.next);
mNextButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mNavigationController.canGoForward()) mNavigationController.goForward();
}
});
mStopReloadButton = (ImageButton) findViewById(R.id.stop_reload_button);
mStopReloadButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mLoading) mWebContents.stop();
else mNavigationController.reload(true);
}
});
}
@SuppressWarnings("unused")
@CalledByNative
private void onUpdateUrl(String url) {
mUrlTextView.setText(url);
}
@SuppressWarnings("unused")
@CalledByNative
private void onLoadProgressChanged(double progress) {
removeCallbacks(mClearProgressRunnable);
mProgressDrawable.setLevel((int) (10000.0 * progress));
if (progress == 1.0) postDelayed(mClearProgressRunnable, COMPLETED_PROGRESS_TIMEOUT_MS);
}
@CalledByNative
private void toggleFullscreenModeForTab(boolean enterFullscreen) {
mIsFullscreen = enterFullscreen;
LinearLayout toolBar = (LinearLayout) findViewById(R.id.toolbar);
toolBar.setVisibility(enterFullscreen ? GONE : VISIBLE);
}
@CalledByNative
private boolean isFullscreenForTabOrPending() {
return mIsFullscreen;
}
@SuppressWarnings("unused")
@CalledByNative
private void setIsLoading(boolean loading) {
mLoading = loading;
if (mLoading) {
mStopReloadButton
.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
} else {
mStopReloadButton.setImageResource(R.drawable.ic_refresh);
}
}
public ShellViewAndroidDelegate getViewAndroidDelegate() {
return mViewAndroidDelegate;
}
/**
* Initializes the ContentView based on the native tab contents pointer passed in.
* @param webContents A {@link WebContents} object.
*/
@SuppressWarnings("unused")
@CalledByNative
private void initFromNativeTabContents(WebContents webContents) {
Context context = getContext();
ContentView cv =
ContentView.createContentView(context, null /* eventOffsetHandler */, webContents);
mViewAndroidDelegate = new ShellViewAndroidDelegate(cv);
assert (mWebContents != webContents);
if (mWebContents != null) mWebContents.clearNativeReference();
webContents.initialize(
"", mViewAndroidDelegate, cv, mWindow, WebContents.createDefaultInternalsHolder());
mWebContents = webContents;
SelectionPopupController.fromWebContents(webContents)
.setActionModeCallback(defaultActionCallback());
mNavigationController = mWebContents.getNavigationController();
if (getParent() != null) mWebContents.onShow();
mUrlTextView.setText(mWebContents.getVisibleUrl().getSpec());
((FrameLayout) findViewById(R.id.contentview_holder)).addView(cv,
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
cv.requestFocus();
mContentViewRenderView.setCurrentWebContents(mWebContents);
}
/**
* {link @ActionMode.Callback} that uses the default implementation in
* {@link SelectionPopupController}.
*/
private ActionMode.Callback2 defaultActionCallback() {
final ActionModeCallbackHelper helper =
SelectionPopupController.fromWebContents(mWebContents)
.getActionModeCallbackHelper();
return new ActionMode.Callback2() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
helper.onCreateActionMode(mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return helper.onPrepareActionMode(mode, menu);
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return helper.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
helper.onDestroyActionMode();
}
@Override
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
helper.onGetContentRect(mode, view, outRect);
}
};
}
@CalledByNative
public void setOverlayMode(boolean useOverlayMode) {
mContentViewRenderView.setOverlayVideoMode(useOverlayMode);
if (mOverlayModeChangedCallbackForTesting != null) {
mOverlayModeChangedCallbackForTesting.onResult(useOverlayMode);
}
}
public void setOverayModeChangedCallbackForTesting(Callback<Boolean> callback) {
mOverlayModeChangedCallbackForTesting = callback;
}
/**
* Enable/Disable navigation(Prev/Next) button if navigation is allowed/disallowed
* in respective direction.
* @param controlId Id of button to update
* @param enabled enable/disable value
*/
@CalledByNative
private void enableUiControl(int controlId, boolean enabled) {
if (controlId == 0) {
mPrevButton.setEnabled(enabled);
} else if (controlId == 1) {
mNextButton.setEnabled(enabled);
}
}
/**
* @return The {@link View} currently shown by this Shell.
*/
public View getContentView() {
ViewAndroidDelegate viewDelegate = mWebContents.getViewAndroidDelegate();
return viewDelegate != null ? viewDelegate.getContainerView() : null;
}
/**
* @return The {@link WebContents} currently managing the content shown by this Shell.
*/
public WebContents getWebContents() {
return mWebContents;
}
private void setKeyboardVisibilityForUrl(boolean visible) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (visible) {
imm.showSoftInput(mUrlTextView, InputMethodManager.SHOW_IMPLICIT);
} else {
imm.hideSoftInputFromWindow(mUrlTextView.getWindowToken(), 0);
}
}
@NativeMethods
interface Natives {
void closeShell(long shellPtr);
}
}

View file

@ -0,0 +1,153 @@
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.thorium_shell;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.components.embedder_support.view.ContentViewRenderView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
/**
* Container and generator of ShellViews.
*/
@JNINamespace("content")
public class ShellManager extends FrameLayout {
public static final String DEFAULT_SHELL_URL = "http://www.google.com";
private WindowAndroid mWindow;
private Shell mActiveShell;
private String mStartupUrl = DEFAULT_SHELL_URL;
// The target for all content rendering.
private ContentViewRenderView mContentViewRenderView;
/**
* Constructor for inflating via XML.
*/
public ShellManager(final Context context, AttributeSet attrs) {
super(context, attrs);
ShellManagerJni.get().init(this);
}
/**
* @param window The window used to generate all shells.
*/
public void setWindow(WindowAndroid window) {
assert window != null;
mWindow = window;
mContentViewRenderView = new ContentViewRenderView(getContext());
mContentViewRenderView.onNativeLibraryLoaded(window);
}
/**
* @return The window used to generate all shells.
*/
public WindowAndroid getWindow() {
return mWindow;
}
/**
* Get the ContentViewRenderView.
*/
public ContentViewRenderView getContentViewRenderView() {
return mContentViewRenderView;
}
/**
* Sets the startup URL for new shell windows.
*/
public void setStartupUrl(String url) {
mStartupUrl = url;
}
/**
* @return The currently visible shell view or null if one is not showing.
*/
public Shell getActiveShell() {
return mActiveShell;
}
/**
* Creates a new shell pointing to the specified URL.
* @param url The URL the shell should load upon creation.
*/
public void launchShell(String url) {
ThreadUtils.assertOnUiThread();
Shell previousShell = mActiveShell;
ShellManagerJni.get().launchShell(url);
if (previousShell != null) previousShell.close();
}
@SuppressWarnings("unused")
@CalledByNative
private Object createShell(long nativeShellPtr) {
if (mContentViewRenderView == null) {
mContentViewRenderView = new ContentViewRenderView(getContext());
mContentViewRenderView.onNativeLibraryLoaded(mWindow);
}
LayoutInflater inflater =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Shell shellView = (Shell) inflater.inflate(R.layout.shell_view, null);
shellView.initialize(nativeShellPtr, mWindow);
// TODO(tedchoc): Allow switching back to these inactive shells.
if (mActiveShell != null) removeShell(mActiveShell);
showShell(shellView);
return shellView;
}
private void showShell(Shell shellView) {
shellView.setContentViewRenderView(mContentViewRenderView);
addView(shellView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
mActiveShell = shellView;
WebContents webContents = mActiveShell.getWebContents();
if (webContents != null) {
mContentViewRenderView.setCurrentWebContents(webContents);
webContents.onShow();
}
}
@CalledByNative
private void removeShell(Shell shellView) {
if (shellView == mActiveShell) mActiveShell = null;
if (shellView.getParent() == null) return;
shellView.setContentViewRenderView(null);
removeView(shellView);
}
/**
* Destroys the Shell manager and associated components.
* Always called at activity exit, and potentially called by native in cases where we need to
* control the timing of mContentViewRenderView destruction. Must handle being called twice.
*/
@CalledByNative
public void destroy() {
// Remove active shell (Currently single shell support only available).
if (mActiveShell != null) {
removeShell(mActiveShell);
}
if (mContentViewRenderView != null) {
mContentViewRenderView.destroy();
mContentViewRenderView = null;
}
}
@NativeMethods
interface Natives {
void init(Object shellManagerInstance);
void launchShell(String url);
}
}

View file

@ -0,0 +1,59 @@
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.thorium_shell;
import android.graphics.Bitmap;
import android.view.ViewGroup;
import org.chromium.ui.base.ViewAndroidDelegate;
import org.chromium.ui.mojom.CursorType;
/**
* Implementation of the abstract class {@link ViewAndroidDelegate} for content shell.
* Extended for testing.
*/
public class ShellViewAndroidDelegate extends ViewAndroidDelegate {
/**
* An interface delegates a {@link CallbackHelper} for cursor update. see more in {@link
* ContentViewPointerTypeTest.OnCursorUpdateHelperImpl}.
*/
public interface OnCursorUpdateHelper {
/**
* Record the last notifyCalled pointer type, see more {@link CallbackHelper#notifyCalled}.
* @param type The pointer type of the notifyCalled.
*/
void notifyCalled(int type);
}
private OnCursorUpdateHelper mOnCursorUpdateHelper;
public ShellViewAndroidDelegate(ViewGroup containerView) {
super(containerView);
}
public void setOnCursorUpdateHelper(OnCursorUpdateHelper helper) {
mOnCursorUpdateHelper = helper;
}
public OnCursorUpdateHelper getOnCursorUpdateHelper() {
return mOnCursorUpdateHelper;
}
@Override
public void onCursorChangedToCustom(Bitmap customCursorBitmap, int hotspotX, int hotspotY) {
super.onCursorChangedToCustom(customCursorBitmap, hotspotX, hotspotY);
if (mOnCursorUpdateHelper != null) {
mOnCursorUpdateHelper.notifyCalled(CursorType.CUSTOM);
}
}
@Override
public void onCursorChanged(int cursorType) {
super.onCursorChanged(cursorType);
if (mOnCursorUpdateHelper != null) {
mOnCursorUpdateHelper.notifyCalled(cursorType);
}
}
}