From a4b447110c9f5801126d228d0249bfa1695fba9c Mon Sep 17 00:00:00 2001 From: Imanol Fernandez Date: Fri, 18 Mar 2022 18:01:54 +0100 Subject: [PATCH] Baisc WPE backend implementation --- .../wolvic/browser/api/impl/DisplayImpl.java | 161 ++++++++ .../api/impl/PanZoomCrontrollerImpl.java | 42 ++ .../wolvic/browser/api/impl/ResultImpl.java | 263 ++++++++++++ .../wolvic/browser/api/impl/RuntimeImpl.java | 122 ++++++ .../wolvic/browser/api/impl/SessionImpl.java | 374 ++++++++++++++++++ .../browser/api/impl/SessionStateImpl.java | 23 ++ .../wolvic/browser/api/impl/SettingsImpl.java | 115 ++++++ .../browser/api/impl/SurfaceClientImpl.java | 37 ++ .../browser/api/impl/TextInputImpl.java | 112 ++++++ .../api/impl/WPEWebViewClientImpl.java | 61 +++ .../browser/api/impl/WebChromeClientImpl.java | 32 ++ .../api/impl/WebExtensionControllerImpl.java | 103 +++++ .../browser/api/impl/WebExtensionImpl.java | 99 +++++ 13 files changed, 1544 insertions(+) create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/DisplayImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/PanZoomCrontrollerImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/ResultImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/RuntimeImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionStateImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SettingsImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SurfaceClientImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/TextInputImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WPEWebViewClientImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebChromeClientImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionControllerImpl.java create mode 100644 app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionImpl.java diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/DisplayImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/DisplayImpl.java new file mode 100644 index 0000000000..44b18bebd3 --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/DisplayImpl.java @@ -0,0 +1,161 @@ +package com.igalia.wolvic.browser.api.impl; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; + +import com.igalia.wolvic.browser.SettingsStore; +import com.igalia.wolvic.browser.api.WDisplay; +import com.igalia.wolvic.browser.api.WResult; +import com.wpe.wpeview.SurfaceClient; +import com.wpe.wpeview.WPEView; + +import kotlin.NotImplementedError; + +public class DisplayImpl implements WDisplay, SurfaceHolder { + @NonNull ViewGroup mContainer; + @NonNull SessionImpl mSession; + Surface mSurface; + private int mWidth = 1; + private int mHeight = 1; + + public DisplayImpl(@NonNull ViewGroup container, @NonNull SessionImpl session) { + mContainer = container; + mSession = session; + } + + public void acquire() { + assert mSession.mWPEView.getParent() == null; + SettingsStore settings = SettingsStore.getInstance(mSession.mWPEView.getContext()); + mContainer.addView(mSession.mWPEView, new ViewGroup.LayoutParams(settings.getWindowWidth(), settings.getWindowHeight())); + mSession.mSurfaceClient.setProxy(new SurfaceClient() { + @Override + public void addCallback(WPEView wpeView, Callback2 callback) { + if (mSurface != null) { + mSession.mWPEView.postDelayed(() -> { + callback.surfaceCreated(DisplayImpl.this); + callback.surfaceChanged(DisplayImpl.this, PixelFormat.RGBA_8888, mWidth, mHeight); + }, 0); + } + } + + @Override + public void removeCallback(WPEView wpeView, Callback2 callback2) { + + } + }); + } + + public void release() { + mContainer.removeView(mSession.mWPEView); + mSession.mSurfaceClient.setProxy(null); + } + + @Override + public void surfaceChanged(@NonNull Surface surface, int width, int height) { + mSurface = surface; + mWidth = width; + mHeight = height; + for (SurfaceHolder.Callback2 callback: mSession.mSurfaceClient.mCallbacks) { + callback.surfaceCreated(this); + callback.surfaceChanged(this, PixelFormat.RGBA_8888, width, height); + } + } + + @Override + public void surfaceChanged(@NonNull Surface surface, int left, int top, int width, int height) { + surfaceChanged(surface, width, height); + } + + @Override + public void surfaceDestroyed() { + mSurface = null; + for (SurfaceHolder.Callback2 callback: mSession.mSurfaceClient.mCallbacks) { + callback.surfaceDestroyed(this); + } + } + + @NonNull + @Override + public WResult capturePixels() { + return capturePixelsWithAspectPreservingSize(mWidth); + } + + @NonNull + @Override + public WResult capturePixelsWithAspectPreservingSize(int width) { + return WResult.fromException(new NotImplementedError()); + } + + // SurfaceHolder interface, only getSurface() is used in WPE so far. + @Override + public void addCallback(Callback callback) { + + } + + @Override + public void removeCallback(Callback callback) { + + } + + @Override + public boolean isCreating() { + return false; + } + + @Override + public void setType(int type) { + + } + + @Override + public void setFixedSize(int width, int height) { + + } + + @Override + public void setSizeFromLayout() { + + } + + @Override + public void setFormat(int format) { + + } + + @Override + public void setKeepScreenOn(boolean screenOn) { + + } + + @Override + public Canvas lockCanvas() { + return null; + } + + @Override + public Canvas lockCanvas(Rect dirty) { + return null; + } + + @Override + public void unlockCanvasAndPost(Canvas canvas) { + + } + + @Override + public Rect getSurfaceFrame() { + return null; + } + + @Override + public Surface getSurface() { + return mSurface; + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/PanZoomCrontrollerImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/PanZoomCrontrollerImpl.java new file mode 100644 index 0000000000..6710e2b71a --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/PanZoomCrontrollerImpl.java @@ -0,0 +1,42 @@ +package com.igalia.wolvic.browser.api.impl; + +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.igalia.wolvic.browser.api.WPanZoomController; +import com.wpe.wpeview.WPEView; + +public class PanZoomCrontrollerImpl implements WPanZoomController { + SessionImpl mSession; + + public PanZoomCrontrollerImpl(SessionImpl session) { + mSession = session; + } + + @Override + public void onTouchEvent(@NonNull MotionEvent event) { + @Nullable View view = getViewForEvents(); + if (view != null) { + view.dispatchTouchEvent(event); + } + } + + @Override + public void onMotionEvent(@NonNull MotionEvent event) { + @Nullable View view = getViewForEvents(); + if (view != null) { + view.dispatchGenericMotionEvent(event); + } + } + + private @Nullable + View getViewForEvents() { + if (mSession == null) { + return null; + } + return mSession.mWPEView.getGfxView(); + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/ResultImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/ResultImpl.java new file mode 100644 index 0000000000..e6a771d8b1 --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/ResultImpl.java @@ -0,0 +1,263 @@ +package com.igalia.wolvic.browser.api.impl; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.SimpleArrayMap; + +import com.igalia.wolvic.browser.api.WResult; + +import java.util.ArrayList; +import java.util.concurrent.CancellationException; + +public class ResultImpl implements WResult { + private ResultImpl.Dispatcher mDispatcher; + private boolean mComplete; + private T mValue; + private Throwable mError; + private boolean mIsUncaughtError; + private SimpleArrayMap> mListeners = new SimpleArrayMap<>(); + private CancellationDelegate mCancellationDelegate; + private WResult mParent; + + + public ResultImpl() { + if (Looper.getMainLooper().getThread() == Thread.currentThread()) { + mDispatcher = new HandlerDispatcher(new Handler(Looper.getMainLooper())); + } else if (Looper.myLooper() != null) { + mDispatcher = new HandlerDispatcher(new Handler()); + } else { + mDispatcher = new DirectDispatcher(); + } + } + + @Override + public synchronized void complete(@Nullable T value) { + mValue = value; + mComplete = true; + + dispatch(); + notifyAll(); + } + + @Override + public synchronized void completeExceptionally(@NonNull Throwable exception) { + mError = exception; + mComplete = true; + + dispatch(); + notifyAll(); + } + + @NonNull + @Override + public WResult cancel() { + if (haveValue() || haveError()) { + return WResult.fromValue(false); + } + + if (mCancellationDelegate != null) { + return mCancellationDelegate + .cancel() + .then( + value -> { + if (value) { + try { + this.completeExceptionally(new CancellationException()); + } catch (final IllegalStateException e) { + e.printStackTrace(); + } + } + return WResult.fromValue(value); + }); + } + + if (mParent != null) { + return mParent.cancel(); + } + + return WResult.fromValue(false); + } + + @Override + public void setCancellationDelegate(@Nullable CancellationDelegate delegate) { + mCancellationDelegate = delegate; + } + + @NonNull + @Override + public synchronized WResult then(@Nullable OnValueListener valueListener, @Nullable OnExceptionListener exceptionListener) { + return thenInternal(mDispatcher, valueListener, exceptionListener); + } + + @NonNull + @Override + public synchronized WResult exceptionally(@NonNull OnExceptionListener exceptionListener) { + return then(null, exceptionListener); + } + + @NonNull + @Override + public synchronized WResult then(@NonNull OnValueListener valueListener) { + return then(valueListener, null); + } + + private void completeFrom(final @Nullable WResult aOther) { + if (aOther == null) { + complete(null); + return; + } + + ResultImpl other = (ResultImpl)aOther; + this.mCancellationDelegate = other.mCancellationDelegate; + other.thenInternal( + ResultImpl.DirectDispatcher.sInstance, + () -> { + if (other.haveValue()) { + complete(other.mValue); + } else { + mIsUncaughtError = other.mIsUncaughtError; + completeExceptionally(other.mError); + } + }); + } + + + private @NonNull WResult thenInternal( + @NonNull final ResultImpl.Dispatcher dispatcher, + @Nullable final WResult.OnValueListener valueListener, + @Nullable final WResult.OnExceptionListener exceptionListener) { + if (valueListener == null && exceptionListener == null) { + throw new IllegalArgumentException("At least one listener should be non-null"); + } + + final ResultImpl result = new ResultImpl(); + result.mParent = this; + thenInternal( + dispatcher, + () -> { + try { + if (haveValue()) { + result.completeFrom(valueListener != null ? valueListener.onValue(mValue) : null); + } else if (!haveError()) { + // Listener called without completion? + throw new AssertionError(); + } else if (exceptionListener != null) { + result.completeFrom(exceptionListener.onException(mError)); + } else { + result.mIsUncaughtError = mIsUncaughtError; + result.completeExceptionally(mError); + } + } catch (final Throwable e) { + if (!result.mComplete) { + result.mIsUncaughtError = true; + result.completeExceptionally(e); + } else if (e instanceof RuntimeException) { + // This should only be UncaughtException, but we rethrow all RuntimeExceptions + // to avoid squelching logic errors in GeckoResult itself. + throw (RuntimeException) e; + } + } + }); + return result; + } + + private synchronized void thenInternal(@NonNull final ResultImpl.Dispatcher dispatcher, @NonNull final Runnable listener) { + if (mComplete) { + dispatcher.dispatch(listener); + } else { + if (!mListeners.containsKey(dispatcher)) { + mListeners.put(dispatcher, new ArrayList<>(1)); + } + mListeners.get(dispatcher).add(listener); + } + } + + private void dispatch() { + if (!mComplete) { + throw new IllegalStateException("Cannot dispatch unless result is complete"); + } + + if (mListeners.isEmpty()) { + if (mIsUncaughtError) { + // We have no listeners to forward the uncaught exception to; + // rethrow the exception to make it visible. + throw new ResultImpl.UncaughtException(mError); + } + return; + } + + if (mDispatcher == null) { + throw new AssertionError("Shouldn't have listeners with null dispatcher"); + } + + for (int i = 0; i < mListeners.size(); ++i) { + final ResultImpl.Dispatcher dispatcher = mListeners.keyAt(i); + final ArrayList jobs = mListeners.valueAt(i); + dispatcher.dispatch( + () -> { + for (final Runnable job : jobs) { + job.run(); + } + }); + } + mListeners.clear(); + } + + private boolean haveValue() { + return mComplete && mError == null; + } + + private boolean haveError() { + return mComplete && mError != null; + } + + + private interface Dispatcher { + void dispatch(Runnable r); + } + + private static class HandlerDispatcher implements ResultImpl.Dispatcher { + HandlerDispatcher(final Handler h) { + mHandler = h; + } + + public void dispatch(final Runnable r) { + mHandler.post(r); + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof ResultImpl.HandlerDispatcher)) { + return false; + } + return mHandler.equals(((ResultImpl.HandlerDispatcher) other).mHandler); + } + + @Override + public int hashCode() { + return mHandler.hashCode(); + } + + Handler mHandler; + } + + private static class DirectDispatcher implements ResultImpl.Dispatcher { + public void dispatch(final Runnable r) { + r.run(); + } + + static ResultImpl.DirectDispatcher sInstance = new ResultImpl.DirectDispatcher(); + + private DirectDispatcher() {} + } + + public static final class UncaughtException extends RuntimeException { + @SuppressWarnings("checkstyle:javadocmethod") + public UncaughtException(final Throwable cause) { + super(cause); + } + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/RuntimeImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/RuntimeImpl.java new file mode 100644 index 0000000000..c8338cf0f4 --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/RuntimeImpl.java @@ -0,0 +1,122 @@ +package com.igalia.wolvic.browser.api.impl; + +import android.app.Service; +import android.content.Context; +import android.content.res.Configuration; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentManager; + +import com.igalia.wolvic.browser.api.WResult; +import com.igalia.wolvic.browser.api.WRuntime; +import com.igalia.wolvic.browser.api.WRuntimeSettings; +import com.igalia.wolvic.browser.api.WWebExtensionController; + + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + + +import kotlin.Lazy; +import mozilla.components.concept.fetch.Client; +import mozilla.components.concept.storage.LoginsStorage; +import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient; + +public class RuntimeImpl implements WRuntime { + private Context mContext; + private WRuntimeSettings mRuntimeSettings; + private WebExtensionControllerImpl mWebExtensionController; + private ViewGroup mContainer; + + public RuntimeImpl(@NonNull Context ctx, @NonNull WRuntimeSettings settings) { + mContext = ctx; + mRuntimeSettings = settings; + mWebExtensionController = new WebExtensionControllerImpl(); + } + + Context getContext() { + return mContext; + } + + ViewGroup getContainer() { + return mContainer; + } + + + @Override + public WRuntimeSettings getSettings() { + return mRuntimeSettings; + } + + @NonNull + @Override + public WResult clearData(long flags) { + // TODO: Implement + return WResult.fromValue(null); + } + + @NonNull + @Override + public WWebExtensionController getWebExtensionController() { + return mWebExtensionController; + } + + @NonNull + @Override + public void setUpLoginPersistence(Lazy storage) { + // TODO: Implement + } + + @NonNull + @Override + public Client createFetchClient(Context context) { + return new HttpURLConnectionClient(); + } + + @Override + public void setExternalVRContext(long externalContext) { + // TODO: Implement + } + + @Override + public void setFragmentManager(@NonNull FragmentManager fragmentManager, @NonNull ViewGroup container) { + mContainer = container; + } + + @Override + public float getDensity() { + return mContext.getResources().getDisplayMetrics().density; + } + + @Override + public void configurationChanged(@NonNull Configuration newConfig) { + } + + @Override + public void appendAppNotesToCrashReport(@NonNull String notes) { + // TODO: Implement + } + + @NonNull + @Override + public WResult sendCrashReport(@NonNull Context context, @NonNull File minidumpFile, @NonNull File extrasFile, @NonNull String appName) throws IOException, URISyntaxException { + // TODO: Implement correctly + return WResult.fromValue(""); + } + + @Override + public Thread.UncaughtExceptionHandler createCrashHandler(Context appContext, Class handlerService) { + // TODO: Implement correctly + return (t, e) -> e.printStackTrace(); + } + + @NonNull + @Override + public CrashReportIntent getCrashReportIntent() { + // TODO: Implement correctly + return new CrashReportIntent("", "", "", ""); + } +} + diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionImpl.java new file mode 100644 index 0000000000..b096998b3e --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionImpl.java @@ -0,0 +1,374 @@ +package com.igalia.wolvic.browser.api.impl; + +import static android.util.Patterns.WEB_URL; + +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.icu.number.UnlocalizedNumberFormatter; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + +import com.igalia.wolvic.browser.api.WContentBlocking; +import com.igalia.wolvic.browser.api.WDisplay; +import com.igalia.wolvic.browser.api.WMediaSession; +import com.igalia.wolvic.browser.api.WPanZoomController; +import com.igalia.wolvic.browser.api.WRuntime; +import com.igalia.wolvic.browser.api.WSession; +import com.igalia.wolvic.browser.api.WSessionSettings; +import com.igalia.wolvic.browser.api.WSessionState; +import com.igalia.wolvic.browser.api.WTextInput; +import com.wpe.wpeview.WPEView; + +public class SessionImpl implements WSession { + WPEView mWPEView; + RuntimeImpl mRuntime; + SettingsImpl mSettings; + ContentDelegate mContentDelegate; + ProgressDelegate mProgressDelegate; + PermissionDelegate mPermissionDelegate; + NavigationDelegate mNavigationDelegate; + ScrollDelegate mScrollDelegate; + HistoryDelegate mHistoryDelegate; + WContentBlocking.Delegate mContentBlockingDelegate; + PromptDelegate mPromptDelegate; + SelectionActionDelegate mSelectionActionDelegate; + WMediaSession.Delegate mMediaSessionDelegate; + DisplayImpl mDisplay; + TextInputImpl mTextInput; + PanZoomCrontrollerImpl mPanZoomCrontroller; + SurfaceClientImpl mSurfaceClient; + + public SessionImpl(@Nullable WSessionSettings settings) { + mSettings = settings != null ? (SettingsImpl) settings : new SettingsImpl(false); + init(); + } + + private void init() { + mTextInput = new TextInputImpl(this); + mPanZoomCrontroller = new PanZoomCrontrollerImpl(this); + mSurfaceClient = new SurfaceClientImpl(); + } + + @UiThread + @Override + public void loadUri(@NonNull String uri, int flags) { + assertSessionOpened(); + mWPEView.loadUrl(getUriFromString(uri).toString()); + } + + @UiThread + @Override + public void loadData(@NonNull byte[] bytes, String mimeType) { + assertSessionOpened(); + + String dataUri = String.format("data:%s;base64,%s", + mimeType != null ? mimeType : "", Base64.encodeToString(bytes, Base64.NO_WRAP)); + + mWPEView.loadUrl(dataUri); + } + + @UiThread + @Override + public void reload(int flags) { + assertSessionOpened(); + mWPEView.reload(); + } + + @UiThread + @Override + public void stop() { + assertSessionOpened(); + mWPEView.stopLoading(); + } + + @Override + public void setActive(boolean active) { + assertSessionOpened(); + mWPEView.setVisibility(active ? View.VISIBLE : View.INVISIBLE); + } + + @Override + public void setFocused(boolean focused) { + assertSessionOpened(); + mWPEView.requestFocus(); + } + + @Override + public void open(@NonNull WRuntime runtime) { + assert mWPEView == null; + mRuntime = (RuntimeImpl)runtime; + mWPEView = new WPEView(((RuntimeImpl)runtime).getContext()); + registerCallbacks(); + } + + private void registerCallbacks() { + mWPEView.setSurfaceClient(mSurfaceClient); + mWPEView.setWPEViewClient(new WPEWebViewClientImpl(this)); + mWPEView.setWebChromeClient(new WebChromeClientImpl(this)); + } + + @Override + public boolean isOpen() { + return mWPEView != null; + } + + @Override + public void close() { + assertSessionOpened(); + mWPEView.setSurfaceClient(null); + mWPEView.setWPEViewClient(null); + mWPEView.setWebChromeClient(null); + mWPEView.stopLoading(); + } + + @UiThread + @Override + public void goBack(boolean userInteraction) { + mWPEView.goBack(); + } + + @UiThread + @Override + public void goForward(boolean userInteraction) { + mWPEView.goForward(); + } + + @UiThread + @Override + public void gotoHistoryIndex(int index) { + mWPEView.goBack(); + } + + @Override + public void purgeHistory() { + // TODO: Implement + } + + @NonNull + @Override + public WSessionSettings getSettings() { + return mSettings; + } + + @Override + public void exitFullScreen() { + + } + + @NonNull + @Override + public WDisplay acquireDisplay() { + assert mDisplay == null; + assert mWPEView != null; + + mDisplay = new DisplayImpl(mRuntime.getContainer(), this); + mDisplay.acquire(); + return mDisplay; + } + + @Override + public void releaseDisplay(@NonNull WDisplay display) { + assert mDisplay != null; + mDisplay.release(); + mDisplay = null; + } + + @Override + public void restoreState(@NonNull WSessionState state) { + + } + + @Override + public void getClientToSurfaceMatrix(@NonNull Matrix matrix) { + + } + + @Override + public void getClientToScreenMatrix(@NonNull Matrix matrix) { + + } + + @Override + public void getPageToScreenMatrix(@NonNull Matrix matrix) { + + } + + @Override + public void getPageToSurfaceMatrix(@NonNull Matrix matrix) { + + } + + @Override + public void dispatchLocation(double latitude, double longitude, double altitude, float accuracy, float altitudeAccuracy, float heading, float speed, float time) { + + } + + @NonNull + @Override + public WTextInput getTextInput() { + return mTextInput; + } + + @NonNull + @Override + public WPanZoomController getPanZoomController() { + return mPanZoomCrontroller; + } + + @Override + public void setContentDelegate(@Nullable ContentDelegate delegate) { + // TODO: Implement bridge + mContentDelegate = delegate; + } + + @Nullable + @Override + public ContentDelegate getContentDelegate() { + return mContentDelegate; + } + + @Override + public void setPermissionDelegate(@Nullable PermissionDelegate delegate) { + // TODO: Implement bridge + mPermissionDelegate = delegate; + } + + @Nullable + @Override + public PermissionDelegate getPermissionDelegate() { + return mPermissionDelegate; + } + + @Override + public void setProgressDelegate(@Nullable ProgressDelegate delegate) { + // TODO: Implement bridge + mProgressDelegate = delegate; + } + + @Nullable + @Override + public ProgressDelegate getProgressDelegate() { + return mProgressDelegate; + } + + @Override + public void setNavigationDelegate(@Nullable NavigationDelegate delegate) { + // TODO: Implement bridge + mNavigationDelegate = delegate; + } + + @Nullable + @Override + public NavigationDelegate getNavigationDelegate() { + return mNavigationDelegate; + } + + @Override + public void setScrollDelegate(@Nullable ScrollDelegate delegate) { + // TODO: Implement bridge + mScrollDelegate = delegate; + } + + @Nullable + @Override + public ScrollDelegate getScrollDelegate() { + return mScrollDelegate; + } + + @Override + public void setHistoryDelegate(@Nullable HistoryDelegate delegate) { + // TODO: Implement bridge + mHistoryDelegate = delegate; + } + + @Nullable + @Override + public HistoryDelegate getHistoryDelegate() { + return mHistoryDelegate; + } + + @Override + public void setContentBlockingDelegate(@Nullable WContentBlocking.Delegate delegate) { + // TODO: Implement bridge + mContentBlockingDelegate = delegate; + } + + @Nullable + @Override + public WContentBlocking.Delegate getContentBlockingDelegate() { + return mContentBlockingDelegate; + } + + @Override + public void setPromptDelegate(@Nullable PromptDelegate delegate) { + // TODO: Implement bridge + mPromptDelegate = delegate; + } + + @Nullable + @Override + public PromptDelegate getPromptDelegate() { + return mPromptDelegate; + } + + @Override + public void setSelectionActionDelegate(@Nullable SelectionActionDelegate delegate) { + // TODO: Implement bridge + mSelectionActionDelegate = delegate; + } + + @Override + public void setMediaSessionDelegate(@Nullable WMediaSession.Delegate delegate) { + // TODO: Implement bridge + mMediaSessionDelegate = delegate; + } + + @Nullable + @Override + public WMediaSession.Delegate getMediaSessionDelegate() { + return mMediaSessionDelegate; + } + + @Nullable + @Override + public SelectionActionDelegate getSelectionActionDelegate() { + return mSelectionActionDelegate; + } + + private void assertSessionOpened() { + assert mWPEView != null; + } + + private Uri getUriFromString(@NonNull String str) { + // WEB_URL doesn't match port numbers. Special case "localhost:" to aid + // testing where a port is remapped. + // Use WEB_URL first to ensure this matches urls such as 'https.' + if (WEB_URL.matcher(str).matches() || str.startsWith("http://localhost:")) { + // WEB_URL matches relative urls (relative meaning no scheme), but this branch is only + // interested in absolute urls. Fall through if no scheme is supplied. + Uri uri = Uri.parse(str); + if (!uri.isRelative()) return uri; + } + + if (str.startsWith("www.") || str.indexOf(":") == -1) { + String url = "http://" + str; + if (WEB_URL.matcher(url).matches()) { + return Uri.parse(url); + } + } + + if (str.startsWith("chrome://")) return Uri.parse(str); + + return Uri.parse("https://google.com/search") + .buildUpon() + .appendQueryParameter("q", str) + .build(); + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionStateImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionStateImpl.java new file mode 100644 index 0000000000..f4f5ea4a4a --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SessionStateImpl.java @@ -0,0 +1,23 @@ +package com.igalia.wolvic.browser.api.impl; + +import com.igalia.wolvic.browser.api.WSessionState; + +public class SessionStateImpl implements WSessionState { + + public SessionStateImpl() {} + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toJson() { + return "{}"; + } + + public static SessionStateImpl fromJson(String json) { + // TODO + return new SessionStateImpl(); + } +} \ No newline at end of file diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SettingsImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SettingsImpl.java new file mode 100644 index 0000000000..7ea48dac72 --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SettingsImpl.java @@ -0,0 +1,115 @@ +package com.igalia.wolvic.browser.api.impl; + +import androidx.annotation.Nullable; + +import com.igalia.wolvic.browser.api.WSessionSettings; + + +public class SettingsImpl implements WSessionSettings { + boolean mPrivateMode; + boolean mUseTrackingProtection = true; + boolean mSuspendMediaWhenInactive = false; + boolean mAllowJavaScript = true; + boolean mFullAccesibilityTree = false; + String mUserAgentOverride; + int mDisplayMode = WSessionSettings.DISPLAY_MODE_BROWSER; + int mViewportMode = WSessionSettings.VIEWPORT_MODE_MOBILE; + int mUserAgentMode = WSessionSettings.USER_AGENT_MODE_VR; + + public SettingsImpl(boolean aPrivateMode) { + mPrivateMode = aPrivateMode; + } + + @Override + public void setUseTrackingProtection(boolean value) { + mUseTrackingProtection = value; + } + + @Override + public void setSuspendMediaWhenInactive(boolean value) { + mSuspendMediaWhenInactive = value; + } + + @Override + public void setAllowJavascript(boolean value) { + mAllowJavaScript = value; + } + + @Override + public void setFullAccessibilityTree(boolean value) { + mFullAccesibilityTree = value; + } + + @Override + public boolean getUseTrackingProtection() { + return mUseTrackingProtection; + } + + @Override + public boolean getUsePrivateMode() { + return mPrivateMode; + } + + @Nullable + @Override + public String getContextId() { + return null; + } + + @Override + public boolean getSuspendMediaWhenInactive() { + return mSuspendMediaWhenInactive; + } + + @Override + public boolean getAllowJavascript() { + return mAllowJavaScript; + } + + @Override + public boolean getFullAccessibilityTree() { + return mFullAccesibilityTree; + } + + @Override + public void setUserAgentMode(int value) { + mUserAgentMode = value; + } + + @Override + public void setDisplayMode(int value) { + mDisplayMode = value; + } + + @Override + public void setViewportMode(int value) { + mViewportMode = value; + } + + @Override + public int getUserAgentMode() { + return mUserAgentMode; + } + + @Override + public int getDisplayMode() { + return mDisplayMode; + } + + @Override + public int getViewportMode() { + return mViewportMode; + } + + @Override + public void setUserAgentOverride(@Nullable String value) { + // TODO: override in browser engine + mUserAgentOverride = value; + } + + @Nullable + @Override + public String getUserAgentOverride() { + return mUserAgentOverride; + } +} \ No newline at end of file diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SurfaceClientImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SurfaceClientImpl.java new file mode 100644 index 0000000000..6c8c6feccf --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/SurfaceClientImpl.java @@ -0,0 +1,37 @@ +package com.igalia.wolvic.browser.api.impl; + +import android.view.SurfaceHolder; + +import androidx.annotation.Nullable; + +import com.wpe.wpeview.SurfaceClient; +import com.wpe.wpeview.WPEView; + +import java.util.concurrent.CopyOnWriteArrayList; + +class SurfaceClientImpl implements SurfaceClient { + SurfaceClient mProxy; + CopyOnWriteArrayList mCallbacks = new CopyOnWriteArrayList<>(); + + @Override + public void addCallback(WPEView wpeView, SurfaceHolder.Callback2 callback) { + if (!mCallbacks.contains(callback)) { + mCallbacks.add(callback); + } + if (mProxy != null) { + mProxy.addCallback(wpeView, callback); + } + } + + @Override + public void removeCallback(WPEView wpeView, SurfaceHolder.Callback2 callback) { + mCallbacks.remove(callback); + if (mProxy != null) { + mProxy.removeCallback(wpeView, callback); + } + } + + public void setProxy(@Nullable SurfaceClient proxy) { + mProxy = proxy; + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/TextInputImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/TextInputImpl.java new file mode 100644 index 0000000000..aa40dd7e58 --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/TextInputImpl.java @@ -0,0 +1,112 @@ +package com.igalia.wolvic.browser.api.impl; + +import android.os.Handler; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.igalia.wolvic.browser.api.WSession; +import com.igalia.wolvic.browser.api.WTextInput; +import com.wpe.wpeview.WPEView; + +public class TextInputImpl implements WTextInput { + SessionImpl mSession; + View mView; + WSession.TextInputDelegate mDelegate; + + public TextInputImpl(SessionImpl session) { + mSession = session; + } + + @NonNull + @Override + public Handler getHandler(@NonNull Handler defHandler) { + return null; + } + + @Nullable + @Override + public View getView() { + return null; + } + + @Override + public void setView(@Nullable View view) { + mView = view; + } + + @Nullable + @Override + public InputConnection onCreateInputConnection(@NonNull EditorInfo attrs) { + return mView.onCreateInputConnection(attrs); + } + + @Override + public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) { + @Nullable View view = getViewForEvents(); + if (view == null) { + return false; + } + return view.onKeyPreIme(keyCode, event); + } + + @Override + public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { + @Nullable View view = getViewForEvents(); + if (view == null) { + return false; + } + return view.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { + @Nullable View view = getViewForEvents(); + if (view == null) { + return false; + } + return view.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) { + @Nullable View view = getViewForEvents(); + if (view == null) { + return false; + } + return view.onKeyLongPress(keyCode, event); + } + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, @NonNull KeyEvent event) { + @Nullable View view = getViewForEvents(); + if (view == null) { + return false; + } + return view.onKeyMultiple(keyCode, repeatCount, event); + } + + @Override + public void setDelegate(@Nullable WSession.TextInputDelegate delegate) { + // TODO: Implement + mDelegate = delegate; + } + + @NonNull + @Override + public WSession.TextInputDelegate getDelegate() { + return mDelegate; + } + + private @Nullable + View getViewForEvents() { + if (mSession == null) { + return null; + } + return mSession.mWPEView.getGfxView(); + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WPEWebViewClientImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WPEWebViewClientImpl.java new file mode 100644 index 0000000000..f115d7d94b --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WPEWebViewClientImpl.java @@ -0,0 +1,61 @@ +package com.igalia.wolvic.browser.api.impl; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.igalia.wolvic.browser.api.WSession; +import com.wpe.wpeview.WPEView; +import com.wpe.wpeview.WPEViewClient; + +class WPEWebViewClientImpl implements WPEViewClient { + @NonNull SessionImpl mSession; + + public WPEWebViewClientImpl(@NonNull SessionImpl session) { + mSession = session; + } + + + @Override + public void onPageStarted(WPEView view, String url) { + @Nullable WSession.NavigationDelegate delegate = mSession.getNavigationDelegate(); + if (delegate != null) { + delegate.onLocationChange(mSession, url); + } + + @Nullable WSession.ProgressDelegate progress = mSession.getProgressDelegate(); + if (progress != null) { + progress.onPageStart(mSession, url); + } + dispatchCanGoBackForward(); + } + + @Override + public void onPageFinished(WPEView view, String url) { + @Nullable WSession.ProgressDelegate progress = mSession.getProgressDelegate(); + if (progress != null) { + progress.onPageStop(mSession, true); + } + + @Nullable WSession.ContentDelegate delegate = mSession.getContentDelegate(); + if (delegate != null) { + delegate.onFirstContentfulPaint(mSession); + } + dispatchCanGoBackForward(); + } + + private void dispatchCanGoBackForward() { + @Nullable WSession.NavigationDelegate delegate = mSession.getNavigationDelegate(); + if (delegate != null) { + delegate.onCanGoBack(mSession, mSession.mWPEView.canGoBack()); + delegate.onCanGoForward(mSession, mSession.mWPEView.canGoForward()); + } + } + + @Override + public void onViewReady(WPEView view) { + @Nullable WSession.ContentDelegate delegate = mSession.getContentDelegate(); + if (delegate != null) { + delegate.onFirstComposite(mSession); + } + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebChromeClientImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebChromeClientImpl.java new file mode 100644 index 0000000000..ad51c60fdc --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebChromeClientImpl.java @@ -0,0 +1,32 @@ +package com.igalia.wolvic.browser.api.impl; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.igalia.wolvic.browser.api.WSession; +import com.wpe.wpeview.WPEView; +import com.wpe.wpeview.WebChromeClient; + +class WebChromeClientImpl implements WebChromeClient { + @NonNull SessionImpl mSession; + + public WebChromeClientImpl(@NonNull SessionImpl session) { + mSession = session; + } + + @Override + public void onProgressChanged(WPEView view, int progress) { + @Nullable WSession.ProgressDelegate delegate = mSession.getProgressDelegate(); + if (delegate != null) { + delegate.onProgressChange(mSession, progress); + } + } + + @Override + public void onReceivedTitle(WPEView view, String title) { + @Nullable WSession.ContentDelegate delegate = mSession.getContentDelegate(); + if (delegate != null) { + delegate.onTitleChange(mSession, title); + } + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionControllerImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionControllerImpl.java new file mode 100644 index 0000000000..1e55a8a94a --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionControllerImpl.java @@ -0,0 +1,103 @@ +package com.igalia.wolvic.browser.api.impl; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.igalia.wolvic.browser.api.WResult; +import com.igalia.wolvic.browser.api.WSession; +import com.igalia.wolvic.browser.api.WWebExtensionController; + +import java.util.ArrayList; +import java.util.List; + +import kotlin.NotImplementedError; +import mozilla.components.concept.engine.webextension.WebExtension; + +public class WebExtensionControllerImpl implements WWebExtensionController { + private PromptDelegate mPromptDelegate; + + @Nullable + @Override + public PromptDelegate getPromptDelegate() { + return mPromptDelegate; + } + + @Override + public void setPromptDelegate(@Nullable PromptDelegate delegate) { + // TODO: Implement + mPromptDelegate = delegate; + } + + @Override + public void setDebuggerDelegate(@NonNull DebuggerDelegate delegate) { + // TODO: Implement + } + + @NonNull + @Override + public WResult install(@NonNull String uri) { + // TODO: Implement + return WResult.fromValue(new WebExtensionImpl(uri, uri, false)); + } + + @NonNull + @Override + public WResult setAllowedInPrivateBrowsing(@NonNull WebExtension extension, boolean allowed) { + // TODO: Implement + return WResult.fromValue(extension); + } + + @NonNull + @Override + public WResult installBuiltIn(@NonNull String uri) { + // TODO: Implement + return WResult.fromValue(new WebExtensionImpl(uri, uri, false)); + } + + @NonNull + @Override + public WResult ensureBuiltIn(@NonNull String uri, @Nullable String id) { + // TODO: Implement + return WResult.fromValue(new WebExtensionImpl(id == null ? uri : id, uri, false)); + } + + @NonNull + @Override + public WResult uninstall(@NonNull WebExtension extension) { + // TODO: Implement + return WResult.fromValue(null); + } + + @NonNull + @Override + public WResult enable(@NonNull WebExtension extension, int source) { + // TODO: Implement + return WResult.fromValue(extension); + } + + @NonNull + @Override + public WResult disable(@NonNull WebExtension extension, int source) { + // TODO: Implement + return WResult.fromValue(extension); + } + + @NonNull + @Override + public WResult> list() { + // TODO: Implement + return WResult.fromValue(new ArrayList<>()); + } + + @NonNull + @Override + public WResult update(@NonNull WebExtension extension) { + // TODO: Implement + return WResult.fromValue(extension); + } + + @Override + public void setTabActive(@NonNull WSession session, boolean active) { + // TODO: Implement + } +} diff --git a/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionImpl.java b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionImpl.java new file mode 100644 index 0000000000..daad011a83 --- /dev/null +++ b/app/src/common/webkit/com/igalia/wolvic/browser/api/impl/WebExtensionImpl.java @@ -0,0 +1,99 @@ +package com.igalia.wolvic.browser.api.impl; + +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import kotlin.coroutines.Continuation; +import mozilla.components.concept.engine.EngineSession; +import mozilla.components.concept.engine.webextension.ActionHandler; +import mozilla.components.concept.engine.webextension.MessageHandler; +import mozilla.components.concept.engine.webextension.Metadata; +import mozilla.components.concept.engine.webextension.Port; +import mozilla.components.concept.engine.webextension.TabHandler; +import mozilla.components.concept.engine.webextension.WebExtension; + +public class WebExtensionImpl extends WebExtension { + public WebExtensionImpl(@NonNull String id, @NonNull String url, boolean supportActions) { + super(id, url, supportActions); + } + + @Override + public void disconnectPort(@NonNull String s, @Nullable EngineSession engineSession) { + + } + + @Nullable + @Override + public Port getConnectedPort(@NonNull String s, @Nullable EngineSession engineSession) { + return null; + } + + @Nullable + @Override + public Metadata getMetadata() { + return null; + } + + @Override + public boolean hasActionHandler(@NonNull EngineSession engineSession) { + return false; + } + + @Override + public boolean hasContentMessageHandler(@NonNull EngineSession engineSession, @NonNull String s) { + return false; + } + + @Override + public boolean hasTabHandler(@NonNull EngineSession engineSession) { + return false; + } + + @Override + public boolean isAllowedInPrivateBrowsing() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Nullable + @Override + public Object loadIcon(int i, @NonNull Continuation continuation) { + return null; + } + + @Override + public void registerActionHandler(@NonNull EngineSession engineSession, @NonNull ActionHandler actionHandler) { + + } + + @Override + public void registerActionHandler(@NonNull ActionHandler actionHandler) { + + } + + @Override + public void registerBackgroundMessageHandler(@NonNull String s, @NonNull MessageHandler messageHandler) { + + } + + @Override + public void registerContentMessageHandler(@NonNull EngineSession engineSession, @NonNull String s, @NonNull MessageHandler messageHandler) { + + } + + @Override + public void registerTabHandler(@NonNull EngineSession engineSession, @NonNull TabHandler tabHandler) { + + } + + @Override + public void registerTabHandler(@NonNull TabHandler tabHandler) { + + } +}