Bug 1776829 - Implement "Paste" permission action for clipboard.readText. r=geckoview-reviewers,owlish

When calling `clipboard.readText` on content script, Gecko dispatches
`MozClipboardReadPaste` event.  On Desktop Firefox uses XUL pop up window
to handle it, then it shows "Paste" button whether user can allow to read
clipboard data.

But GeckoView doesn't have XUL pop up. To implement this feature, we show
"Paste" pop up using action mode as default. Also, browser side can override
delegated methods if it wants another permission pop up or to support Android L
(Android L doesn't have action mode).

Differential Revision: https://phabricator.services.mozilla.com/D151102
This commit is contained in:
Makoto Kato 2022-09-12 07:36:52 +00:00
parent 27d6b966c4
commit e73b7956db
12 changed files with 598 additions and 1 deletions

View file

@ -0,0 +1,100 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { GeckoViewActorChild } = ChromeUtils.import(
"resource://gre/modules/GeckoViewActorChild.jsm"
);
const EXPORTED_SYMBOLS = ["GeckoViewClipboardPermissionChild"];
class GeckoViewClipboardPermissionChild extends GeckoViewActorChild {
constructor() {
super();
this._pendingPromise = null;
}
async promptPermissionForClipboardRead() {
const uri = this.contentWindow.location.href;
const { x, y } = await this.sendQuery(
"ClipboardReadTextPaste:GetLastPointerLocation"
);
const promise = this.eventDispatcher.sendRequestForResult({
type: "GeckoView:ClipboardPermissionRequest",
uri,
screenPoint: {
x,
y,
},
});
this._pendingPromise = promise;
try {
const allowOrDeny = await promise;
if (this._pendingPromise !== promise) {
// Current pending promise is newer. So it means that this promise
// is already resolved or rejected. Do nothing.
return;
}
this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup(
allowOrDeny
);
this._pendingPromise = null;
} catch (error) {
debug`Permission error: ${error}`;
if (this._pendingPromise !== promise) {
// Current pending promise is newer. So it means that this promise
// is already resolved or rejected. Do nothing.
return;
}
this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup(
false
);
this._pendingPromise = null;
}
}
handleEvent(aEvent) {
debug`handleEvent: ${aEvent.type}`;
switch (aEvent.type) {
case "MozClipboardReadPaste":
if (aEvent.isTrusted) {
this.promptPermissionForClipboardRead();
}
break;
// page hide or deactivate cancel clipboard permission.
case "pagehide":
// fallthrough for the next three events.
case "deactivate":
case "mousedown":
case "mozvisualscroll":
// Gecko desktop uses XUL popup to show clipboard permission prompt.
// So it will be closed automatically by scroll and other user
// activation. So GeckoView has to close permission prompt by some user
// activations, too.
this.eventDispatcher.sendRequest({
type: "GeckoView:DismissClipboardPermissionRequest",
});
if (this._pendingPromise) {
this.contentWindow.navigator.clipboard.onUserReactedToPasteMenuPopup(
false
);
this._pendingPromise = null;
}
break;
}
}
}
const { debug, warn } = GeckoViewClipboardPermissionChild.initLogging(
"GeckoViewClipboardPermissionChild"
);

View file

@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { GeckoViewActorParent } = ChromeUtils.import(
"resource://gre/modules/GeckoViewActorParent.jsm"
);
const EXPORTED_SYMBOLS = ["GeckoViewClipboardPermissionParent"];
class GeckoViewClipboardPermissionParent extends GeckoViewActorParent {
getLastOverWindowPointerLocation() {
const mouseXInCSSPixels = {};
const mouseYInCSSPixels = {};
const windowUtils = this.window.windowUtils;
windowUtils.getLastOverWindowPointerLocationInCSSPixels(
mouseXInCSSPixels,
mouseYInCSSPixels
);
const screenRect = windowUtils.toScreenRect(
mouseXInCSSPixels.value,
mouseYInCSSPixels.value,
0,
0
);
return {
x: screenRect.x,
y: screenRect.y,
};
}
receiveMessage(aMessage) {
debug`receiveMessage: ${aMessage.name}`;
switch (aMessage.name) {
case "ClipboardReadTextPaste:GetLastPointerLocation":
return this.getLastOverWindowPointerLocation();
default:
return super.receiveMessage(aMessage);
}
}
}
const { debug, warn } = GeckoViewClipboardPermissionParent.initLogging(
"GeckoViewClipboardPermissionParent"
);

View file

@ -10,6 +10,8 @@ FINAL_TARGET_FILES.actors += [
"ContentDelegateParent.jsm",
"GeckoViewAutoFillChild.jsm",
"GeckoViewAutoFillParent.jsm",
"GeckoViewClipboardPermissionChild.jsm",
"GeckoViewClipboardPermissionParent.jsm",
"GeckoViewContentChild.jsm",
"GeckoViewContentParent.jsm",
"GeckoViewFormValidationChild.jsm",

View file

@ -92,6 +92,22 @@ const JSWINDOWACTORS = {
allFrames: true,
messageManagerGroups: ["browsers"],
},
GeckoViewClipboardPermission: {
parent: {
moduleURI: "resource:///actors/GeckoViewClipboardPermissionParent.jsm",
},
child: {
moduleURI: "resource:///actors/GeckoViewClipboardPermissionChild.jsm",
events: {
MozClipboardReadPaste: {},
deactivate: { mozSystemGroup: true },
mousedown: { capture: true, mozSystemGroup: true },
mozvisualscroll: { mozSystemGroup: true },
pagehide: { capture: true, mozSystemGroup: true },
},
},
allFrames: true,
},
};
class GeckoViewStartup {

View file

@ -6,6 +6,7 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
@ -952,6 +953,9 @@ package org.mozilla.geckoview {
field @Nullable protected GeckoSession.Window mWindow;
}
@Retention(value=RetentionPolicy.SOURCE) public static interface GeckoSession.ClipboardPermissionType {
}
public static interface GeckoSession.ContentDelegate {
method @UiThread default public void onCloseRequest(@NonNull GeckoSession);
method @UiThread default public void onContextMenu(@NonNull GeckoSession, int, int, @NonNull GeckoSession.ContentDelegate.ContextElement);
@ -1446,8 +1450,10 @@ package org.mozilla.geckoview {
}
public static interface GeckoSession.SelectionActionDelegate {
method @UiThread default public void onDismissClipboardPermissionRequest(@NonNull GeckoSession);
method @UiThread default public void onHideAction(@NonNull GeckoSession, int);
method @UiThread default public void onShowActionRequest(@NonNull GeckoSession, @NonNull GeckoSession.SelectionActionDelegate.Selection);
method @Nullable @UiThread default public GeckoResult<AllowOrDeny> onShowClipboardPermissionRequest(@NonNull GeckoSession, @NonNull GeckoSession.SelectionActionDelegate.ClipboardPermission);
field public static final String ACTION_COLLAPSE_TO_END = "org.mozilla.geckoview.COLLAPSE_TO_END";
field public static final String ACTION_COLLAPSE_TO_START = "org.mozilla.geckoview.COLLAPSE_TO_START";
field public static final String ACTION_COPY = "org.mozilla.geckoview.COPY";
@ -1465,6 +1471,14 @@ package org.mozilla.geckoview {
field public static final int HIDE_REASON_ACTIVE_SELECTION = 2;
field public static final int HIDE_REASON_INVISIBLE_SELECTION = 1;
field public static final int HIDE_REASON_NO_SELECTION = 0;
field public static final int PERMISSION_CLIPBOARD_READ = 1;
}
public static class GeckoSession.SelectionActionDelegate.ClipboardPermission {
ctor protected ClipboardPermission();
field @Nullable public final Point screenPoint;
field public final int type;
field @NonNull public final String uri;
}
public static class GeckoSession.SelectionActionDelegate.Selection {

View file

@ -0,0 +1,19 @@
<html>
<head>
<meta charset="utf-8">
<title>Hello, world!</title>
<meta name="viewport" content="initial-scale=1.0">
</head>
<body style="height: 100%">
<p>Hello, world!</p>
<script>
document.body.addEventListener("click", () => {
navigator.clipboard.readText().then(() => {
window.alert("allow");
}).catch(() => {
window.alert("deny");
});
});
</script>
</body>
</html>

View file

@ -35,6 +35,7 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
const val RESUBMIT_CONFIRM = "/assets/www/resubmit.html"
const val BEFORE_UNLOAD = "/assets/www/beforeunload.html"
const val CLICK_TO_RELOAD_HTML_PATH = "/assets/www/clickToReload.html"
const val CLIPBOARD_READ_HTML_PATH = "/assets/www/clipboard_read.html"
const val CONTENT_CRASH_URL = "about:crashcontent"
const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"
const val FORM_BLANK_HTML_PATH = "/assets/www/form_blank.html"

View file

@ -4,6 +4,9 @@
package org.mozilla.geckoview.test
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession.PromptDelegate
import org.mozilla.geckoview.GeckoSession.SelectionActionDelegate
import org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.*
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
@ -13,6 +16,7 @@ import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Point;
import android.graphics.RectF;
import android.os.Build
import androidx.test.ext.junit.rules.ActivityScenarioRule
@ -254,6 +258,117 @@ class SelectionActionDelegateTest : BaseSessionTest() {
testClientRect(selectedContent, jsCssReset, jsBorder10pxPadding10px, expectedDiff)
}
@WithDisplay(width = 100, height = 100)
@Test fun clipboardReadAllow() {
assumeThat("Unnecessary to run multiple times", id, equalTo("#text"));
sessionRule.setPrefsUntilTestEnd(mapOf("dom.events.asyncClipboard.readText" to true))
val url = createTestUrl(CLIPBOARD_READ_HTML_PATH)
mainSession.loadUri(url)
mainSession.waitForPageStop()
// Select allow
val result = GeckoResult<Void>()
mainSession.delegateDuringNextWait(object : SelectionActionDelegate, PromptDelegate {
@AssertCalled(count = 1)
override fun onShowClipboardPermissionRequest(
session: GeckoSession, perm: ClipboardPermission):
GeckoResult<AllowOrDeny> {
assertThat("URI should match", perm.uri, startsWith(url))
assertThat("Type should match", perm.type,
equalTo(SelectionActionDelegate.PERMISSION_CLIPBOARD_READ))
assertThat("screenPoint should match", perm.screenPoint, equalTo(Point(50, 50)))
return GeckoResult.allow()
}
@AssertCalled(count = 1, order = [2])
override fun onAlertPrompt(
session: GeckoSession, prompt: PromptDelegate.AlertPrompt):
GeckoResult<PromptDelegate.PromptResponse> {
assertThat("Message should match", "allow", equalTo(prompt.message))
result.complete(null)
return GeckoResult.fromValue(prompt.dismiss())
}
})
mainSession.synthesizeTap(50, 50) // Provides user activation.
sessionRule.waitForResult(result)
}
@WithDisplay(width = 100, height = 100)
@Test fun clipboardReadDeny() {
assumeThat("Unnecessary to run multiple times", id, equalTo("#text"));
sessionRule.setPrefsUntilTestEnd(mapOf("dom.events.asyncClipboard.readText" to true))
val url = createTestUrl(CLIPBOARD_READ_HTML_PATH)
mainSession.loadUri(url)
mainSession.waitForPageStop()
// Select deny
val result = GeckoResult<Void>()
mainSession.delegateDuringNextWait(object : SelectionActionDelegate, PromptDelegate {
@AssertCalled(count = 1, order = [1])
override fun onShowClipboardPermissionRequest(
session: GeckoSession, perm: ClipboardPermission):
GeckoResult<AllowOrDeny>? {
assertThat("URI should match", perm.uri, startsWith(url))
assertThat("Type should match", perm.type,
equalTo(SelectionActionDelegate.PERMISSION_CLIPBOARD_READ))
return GeckoResult.deny()
}
@AssertCalled(count = 1, order = [2])
override fun onAlertPrompt(
session: GeckoSession, prompt: PromptDelegate.AlertPrompt):
GeckoResult<PromptDelegate.PromptResponse> {
assertThat("Message should match", "deny", equalTo(prompt.message))
result.complete(null)
return GeckoResult.fromValue(prompt.dismiss())
}
})
mainSession.synthesizeTap(50, 50) // Provides user activation.
sessionRule.waitForResult(result)
}
@WithDisplay(width = 100, height = 100)
@Test fun clipboardReadDeactivate() {
assumeThat("Unnecessary to run multiple times", id, equalTo("#text"));
sessionRule.setPrefsUntilTestEnd(mapOf("dom.events.asyncClipboard.readText" to true))
val url = createTestUrl(CLIPBOARD_READ_HTML_PATH)
mainSession.loadUri(url)
mainSession.waitForPageStop()
val result = GeckoResult<Void>()
mainSession.delegateDuringNextWait(object : SelectionActionDelegate {
@AssertCalled(count = 1)
override fun onShowClipboardPermissionRequest(
session: GeckoSession, perm: ClipboardPermission):
GeckoResult<AllowOrDeny>? {
assertThat("Type should match", perm.type,
equalTo(SelectionActionDelegate.PERMISSION_CLIPBOARD_READ))
result.complete(null)
return GeckoResult()
}
});
mainSession.synthesizeTap(50, 50) // Provides user activation.
sessionRule.waitForResult(result)
mainSession.delegateDuringNextWait(object : SelectionActionDelegate {
@AssertCalled
override fun onDismissClipboardPermissionRequest(session: GeckoSession) {
}
});
mainSession.loadTestPath(HELLO_HTML_PATH)
sessionRule.waitForPageStop()
}
/** Interface that defines behavior for a particular type of content */
private interface SelectedContent {
fun focus() {}

View file

@ -5,6 +5,7 @@
package org.mozilla.gecko.util;
import android.graphics.Point;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
@ -398,6 +399,21 @@ public final class GeckoBundle implements Parcelable {
(float) rectBundle.getDouble("bottom"));
}
/**
* Returns the value associated with a Point mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Point value
*/
public Point getPoint(final String key) {
final GeckoBundle ptBundle = getBundle(key);
if (ptBundle == null) {
return null;
}
return new Point(ptBundle.getInt("x"), ptBundle.getInt("y"));
}
/**
* Returns the value associated with a GeckoBundle mapping, or null if the mapping does not exist.
*

View file

@ -12,6 +12,7 @@ import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
@ -77,6 +78,8 @@ public class BasicSelectionActionDelegate
protected @Nullable Selection mSelection;
protected boolean mRepopulatedMenu;
private @Nullable ActionMode mActionModeForClipboardPermission;
@TargetApi(Build.VERSION_CODES.M)
private class Callback2Wrapper extends ActionMode.Callback2 {
@Override
@ -444,6 +447,11 @@ public class BasicSelectionActionDelegate
return;
}
if (mActionModeForClipboardPermission != null) {
mActionModeForClipboardPermission.finish();
return;
}
if (mUseFloatingToolbar) {
mActionMode = mActivity.startActionMode(new Callback2Wrapper(), ActionMode.TYPE_FLOATING);
} else {
@ -473,4 +481,159 @@ public class BasicSelectionActionDelegate
break;
}
}
/** Callback class of clipboard permission. This is used on pre-M only */
private class ClipboardPermissionCallback implements ActionMode.Callback {
private GeckoResult<AllowOrDeny> mResult;
public ClipboardPermissionCallback(final GeckoResult<AllowOrDeny> result) {
mResult = result;
}
@Override
public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
return BasicSelectionActionDelegate.this.onCreateActionModeForClipboardPermission(
actionMode, menu);
}
@Override
public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
mResult.complete(AllowOrDeny.ALLOW);
mResult = null;
actionMode.finish();
return true;
}
@Override
public void onDestroyActionMode(final ActionMode actionMode) {
if (mResult != null) {
mResult.complete(AllowOrDeny.DENY);
}
BasicSelectionActionDelegate.this.onDestroyActionModeForClipboardPermission(actionMode);
}
}
/** Callback class of clipboard permission for Android M+ */
@TargetApi(Build.VERSION_CODES.M)
private class ClipboardPermissionCallbackM extends ActionMode.Callback2 {
private @Nullable GeckoResult<AllowOrDeny> mResult;
private final @NonNull GeckoSession mSession;
private final @Nullable Point mPoint;
public ClipboardPermissionCallbackM(
final @NonNull GeckoSession session,
final @Nullable Point screenPoint,
final @NonNull GeckoResult<AllowOrDeny> result) {
mSession = session;
mPoint = screenPoint;
mResult = result;
}
@Override
public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
return BasicSelectionActionDelegate.this.onCreateActionModeForClipboardPermission(
actionMode, menu);
}
@Override
public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
mResult.complete(AllowOrDeny.ALLOW);
mResult = null;
actionMode.finish();
return true;
}
@Override
public void onDestroyActionMode(final ActionMode actionMode) {
if (mResult != null) {
mResult.complete(AllowOrDeny.DENY);
}
BasicSelectionActionDelegate.this.onDestroyActionModeForClipboardPermission(actionMode);
}
@Override
public void onGetContentRect(final ActionMode mode, final View view, final Rect outRect) {
super.onGetContentRect(mode, view, outRect);
if (mPoint == null) {
return;
}
outRect.set(mPoint.x, mPoint.y, mPoint.x + 1, mPoint.y + 1);
}
}
/**
* Show action mode bar to request clipboard permission
*
* @param session The GeckoSession that initiated the callback.
* @param permission An {@link ClipboardPermission} describing the permission being requested.
* @return A {@link GeckoResult} with {@link AllowOrDeny}, determining the response to the
* permission request for this site.
*/
@TargetApi(Build.VERSION_CODES.M)
@Override
public GeckoResult<AllowOrDeny> onShowClipboardPermissionRequest(
final GeckoSession session, final ClipboardPermission permission) {
ThreadUtils.assertOnUiThread();
final GeckoResult<AllowOrDeny> result = new GeckoResult<>();
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
}
if (mActionModeForClipboardPermission != null) {
mActionModeForClipboardPermission.finish();
mActionModeForClipboardPermission = null;
}
if (mUseFloatingToolbar) {
mActionModeForClipboardPermission =
mActivity.startActionMode(
new ClipboardPermissionCallbackM(session, permission.screenPoint, result),
ActionMode.TYPE_FLOATING);
} else {
mActionModeForClipboardPermission =
mActivity.startActionMode(new ClipboardPermissionCallback(result));
}
return result;
}
/**
* Dismiss action mode for requesting clipboard permission popup or model.
*
* @param session The GeckoSession that initiated the callback.
*/
@Override
public void onDismissClipboardPermissionRequest(final GeckoSession session) {
ThreadUtils.assertOnUiThread();
if (mActionModeForClipboardPermission != null) {
mActionModeForClipboardPermission.finish();
mActionModeForClipboardPermission = null;
}
}
/* package */ boolean onCreateActionModeForClipboardPermission(
final ActionMode actionMode, final Menu menu) {
final MenuItem item = menu.add(/* group */ Menu.NONE, Menu.FIRST, Menu.FIRST, /* title */ "");
item.setTitle(android.R.string.paste);
return true;
}
/* package */ void onDestroyActionModeForClipboardPermission(final ActionMode actionMode) {
mActionModeForClipboardPermission = null;
}
}

View file

@ -13,6 +13,7 @@ import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@ -895,6 +896,8 @@ public class GeckoSession {
"GeckoView:ShowSelectionAction",
"GeckoView:HideMagnifier",
"GeckoView:ShowMagnifier",
"GeckoView:ClipboardPermissionRequest",
"GeckoView:DismissClipboardPermissionRequest",
}) {
@Override
public void handleMessage(
@ -902,6 +905,7 @@ public class GeckoSession {
final String event,
final GeckoBundle message,
final EventCallback callback) {
Log.d(LOGTAG, "handleMessage: " + event);
if ("GeckoView:ShowSelectionAction".equals(event)) {
final @SelectionActionDelegateAction HashSet<String> actionsSet =
new HashSet<>(Arrays.asList(message.getStringArray("actions")));
@ -941,6 +945,25 @@ public class GeckoSession {
GeckoSession.this.getMagnifier().show(new PointF(origin[0], origin[1]));
} else if ("GeckoView:HideMagnifier".equals(event)) {
GeckoSession.this.getMagnifier().dismiss();
} else if ("GeckoView:ClipboardPermissionRequest".equals(event)) {
final SelectionActionDelegate.ClipboardPermission permission =
new SelectionActionDelegate.ClipboardPermission(message);
final GeckoResult<AllowOrDeny> result =
delegate.onShowClipboardPermissionRequest(GeckoSession.this, permission);
callback.resolveTo(
result.map(
value -> {
if (value == AllowOrDeny.ALLOW) {
return true;
}
if (value == AllowOrDeny.DENY) {
return false;
}
throw new IllegalArgumentException("Invalid response");
}));
} else if ("GeckoView:DismissClipboardPermissionRequest".equals(event)) {
delegate.onDismissClipboardPermissionRequest(GeckoSession.this);
}
}
};
@ -3671,6 +3694,63 @@ public class GeckoSession {
@UiThread
default void onHideAction(
@NonNull final GeckoSession session, @SelectionActionDelegateHideReason final int reason) {}
/**
* Permission for reading clipboard data. See: <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText">Clipboard.readText()</a>
*/
int PERMISSION_CLIPBOARD_READ = 1;
/** Represents attributes of a clipboard permission. */
public class ClipboardPermission {
/** The URI associated with this content permission. */
public final @NonNull String uri;
/**
* The type of this permission; one of {@link #PERMISSION_CLIPBOARD_READ
* PERMISSION_CLIPBOARD_*}.
*/
public final @ClipboardPermissionType int type;
/**
* The last mouse or touch location in screen coordinates when the permission is requested.
*/
public final @Nullable Point screenPoint;
/** Empty constructor for tests */
protected ClipboardPermission() {
this.uri = "";
this.type = PERMISSION_CLIPBOARD_READ;
this.screenPoint = null;
}
private ClipboardPermission(final @NonNull GeckoBundle bundle) {
this.uri = bundle.getString("uri");
this.type = PERMISSION_CLIPBOARD_READ;
this.screenPoint = bundle.getPoint("screenPoint");
}
}
/**
* Request clipboard permission.
*
* @param session The GeckoSession that initiated the callback.
* @param permission An {@link ClipboardPermission} describing the permission being requested.
* @return A {@link GeckoResult} with {@link AllowOrDeny}, determining the response to the
* permission request for this site.
*/
@UiThread
default @Nullable GeckoResult<AllowOrDeny> onShowClipboardPermissionRequest(
@NonNull final GeckoSession session, @NonNull ClipboardPermission permission) {
return GeckoResult.deny();
}
/**
* Dismiss requesting clipboard permission popup or model.
*
* @param session The GeckoSession that initiated the callback.
*/
@UiThread
default void onDismissClipboardPermissionRequest(@NonNull final GeckoSession session) {}
}
@Retention(RetentionPolicy.SOURCE)
@ -3707,6 +3787,12 @@ public class GeckoSession {
})
public @interface SelectionActionDelegateHideReason {}
@Retention(RetentionPolicy.SOURCE)
@IntDef({
SelectionActionDelegate.PERMISSION_CLIPBOARD_READ,
})
public @interface ClipboardPermissionType {}
public interface NavigationDelegate {
/**
* A view has started loading content from the network.

View file

@ -13,6 +13,22 @@ exclude: true
⚠️ breaking change and deprecation notices
## v106
- Added [`SelectionActionDelegate.onShowClipboardPermissionRequest`][106.1],
[`SelectionActionDelegate.onDismissClipboardPermissionRequest`][106.2],
[`BasicSelectionActionDelegate.onShowClipboardPermissionRequest`][106.3],
[`BasicSelectionActionDelegate.onDismissCancelClipboardPermissionRequest`][106.4] and
[`SelectionActionDelegate.ClipboardPermission`][106.5] to handle permission
request for reading clipboard data by [`clipboard.readText`][106.6].
([bug 1776829]({{bugzilla}}1776829))
[106.1]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.html#onShowClipboardPermissionRequest(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.ClipboardPermission)
[106.2]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.html#onDismissClipboardPermissionRequest(org.mozilla.geckoview.GeckoSession)
[106.3]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#onShowClipboardPermissionRequest(org.mozilla.geckoview.GeckoSession,org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.ClipboardPermission)
[106.4]: {{javadoc_uri}}/BasicSelectionActionDelegate.html#onDismissClipboardPermission(org.mozilla.geckoview.GeckoSession)
[106.5]: {{javadoc_uri}}/GeckoSession.SelectionActionDelegate.ClipboardPermission.html
[106.6]: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/readText
## v104
- Removed deprecated Autofill.Delegate `onAutofill`, Autofill.Node `fillViewStructure`, `getFocused`, `getId`, `getValue`, `getVisible`, Autofill.NodeData `Autofill.Notify`, Autofill.Session `surfaceChanged`.
([bug 1781180]({{bugzilla}}1781180))
@ -1224,4 +1240,4 @@ to allow adding gecko profiler markers.
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String)
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 771802b68452c32d672df605e3a26d0eebd89b84
[api-version]: 5dd4f92b49dec51709788671173c5a03b303287b