tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 78fe31c4173265f42a2dc00cfa085f20a6c7203e
parent 7d90789b9485e7ec2ec02559ff177ff97b50de82
Author: Segun Famisa <sfamisa@mozilla.com>
Date:   Mon, 15 Dec 2025 15:05:12 +0000

Bug 1809303 - Add "Copy link text" context menu item r=geckoview-reviewers,android-reviewers,android-l10n-reviewers,geckoview-api-reviewers,flod,ohall,tcampbell

This patch introduces a "Copy link text" option to the context menu. It adds the necessary `innerText` property from Gecko to the Android components layer, allowing the context menu feature to access and copy the visible text of a link to the clipboard.

Differential Revision: https://phabricator.services.mozilla.com/D275482

Diffstat:
Mmobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt | 27++++++++++++++++++++++++---
Mmobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt | 9+++++++++
Mmobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/HitResult.kt | 5++++-
Mmobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt | 39+++++++++++++++++++++++++++++++++++++++
Mmobile/android/android-components/components/feature/contextmenu/src/main/res/values/strings.xml | 4++++
Mmobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mmobile/android/android-components/config/detekt-baseline.xml | 1-
Mmobile/android/android-components/docs/changelog.md | 4++++
Mmobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt | 15+++++++++++++++
Mmobile/android/geckoview/api.txt | 9++++++---
Amobile/android/geckoview/src/androidTest/assets/www/context_menu_normal_link_text.html | 22++++++++++++++++++++++
Mmobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt | 1+
Mmobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mmobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java | 26++++++++++++++++++++++----
Mmobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md | 10+++++++++-
Mmobile/shared/actors/ContentDelegateChild.sys.mjs | 4++++
16 files changed, 302 insertions(+), 16 deletions(-)

diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt @@ -1261,7 +1261,13 @@ class GeckoEngineSession( screenY: Int, element: GeckoSession.ContentDelegate.ContextElement, ) { - val hitResult = handleLongClick(element.srcUri, element.type, element.linkUri, element.title) + val hitResult = handleLongClick( + elementSrc = element.srcUri, + elementType = element.type, + uri = element.linkUri, + title = element.title, + linkText = element.linkText, + ) hitResult?.let { notifyObservers { onLongPress(it) } } @@ -1517,7 +1523,22 @@ class GeckoEngineSession( } } - fun handleLongClick(elementSrc: String?, elementType: Int, uri: String? = null, title: String? = null): HitResult? { + /** + * Handles long click events. + * + * @param elementSrc The source of the element. + * @param elementType The type of the element. + * @param uri The (optional) URI of the element. + * @param title The (optional) title of the element. + * @param linkText The (optional) link text of the element. + */ + fun handleLongClick( + elementSrc: String?, + elementType: Int, + uri: String? = null, + title: String? = null, + linkText: String? = null, + ): HitResult? { return when (elementType) { GeckoSession.ContentDelegate.ContextElement.TYPE_AUDIO -> elementSrc?.let { @@ -1545,7 +1566,7 @@ class GeckoEngineSession( else -> HitResult.UNKNOWN(it) } } ?: uri?.let { - HitResult.UNKNOWN(it) + HitResult.UNKNOWN(src = it, linkText = linkText) } } else -> HitResult.UNKNOWN("") diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt @@ -2520,6 +2520,15 @@ class GeckoEngineSessionTest { result = engineSession.handleLongClick(null, TYPE_NONE, null) assertNull(result) + + result = engineSession.handleLongClick( + elementSrc = null, + elementType = TYPE_NONE, + uri = "https://mozilla.org", + linkText = "Mozilla", + ) + assertTrue(result is HitResult.UNKNOWN && result.src == "https://mozilla.org") + assertTrue(result is HitResult.UNKNOWN && result.linkText == "Mozilla") } @Test diff --git a/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/HitResult.kt b/mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/HitResult.kt @@ -12,8 +12,11 @@ package mozilla.components.concept.engine sealed class HitResult(open val src: String) { /** * Default type if we're unable to match the type to anything. It may or may not have a src. + * + * @param src The src of the element. + * @param linkText The (optional) link text of the element. */ - data class UNKNOWN(override val src: String) : HitResult(src) + data class UNKNOWN(override val src: String, val linkText: String? = null) : HitResult(src) /** * If the HTML element was of type 'HTMLImageElement'. diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt b/mobile/android/android-components/components/feature/contextmenu/src/main/java/mozilla/components/feature/contextmenu/ContextMenuCandidate.kt @@ -74,6 +74,7 @@ data class ContextMenuCandidate( snackbarDelegate, ), createCopyLinkCandidate(context, snackBarParentView, snackbarDelegate), + createCopyLinkTextCandidate(context, snackBarParentView, snackbarDelegate), createDownloadLinkCandidate(context, contextMenuUseCases), createShareLinkCandidate(context), createShareImageCandidate(context, contextMenuUseCases), @@ -581,6 +582,41 @@ data class ContextMenuCandidate( ) /** + * Context Menu item: "Copy link text". + * + * @param context [Context] used for various system interactions. + * @param snackBarParentView The view in which to find a suitable parent for displaying the `Snackbar`. + * @param snackbarDelegate [SnackbarDelegate] used to actually show a `Snackbar`. + * @param additionalValidation Callback for the final validation in deciding whether this menu option + * will be shown. Will only be called if all the intrinsic validations passed. + */ + fun createCopyLinkTextCandidate( + context: Context, + snackBarParentView: View, + snackbarDelegate: SnackbarDelegate = DefaultSnackbarDelegate(), + additionalValidation: (SessionState, HitResult) -> Boolean = { _, _ -> true }, + ) = ContextMenuCandidate( + id = "mozac.feature.contextmenu.copy_link_text", + label = context.getString(R.string.mozac_feature_contextmenu_copy_link_text), + showFor = { tab, hitResult -> + tab.isUrlSchemeAllowed(hitResult.getLink()) && + hitResult.isUri() && hitResult.hasLinkText() && + additionalValidation(tab, hitResult) + }, + action = { _, hitResult -> + val innerText = (hitResult as? HitResult.UNKNOWN)?.linkText ?: return@ContextMenuCandidate + clipPlainText( + context, + label = hitResult.getLink(), + plainText = innerText, + displayTextId = R.string.mozac_feature_contextmenu_snackbar_link_text_copied, + snackBarParentView, + snackbarDelegate, + ) + }, + ) + + /** * Context Menu item: "Copy Image Location". * * @param context [Context] used for various system interactions. @@ -648,6 +684,9 @@ private fun HitResult.isVideoAudio(): Boolean = private fun HitResult.isUri(): Boolean = ((this is HitResult.UNKNOWN && src.isNotEmpty()) || this is HitResult.IMAGE_SRC) +private fun HitResult.hasLinkText(): Boolean = + (!(this as? HitResult.UNKNOWN)?.linkText.isNullOrEmpty() && src.isNotEmpty()) + private fun HitResult.isHttpLink(): Boolean = isUri() && getLink().startsWith("http") diff --git a/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/strings.xml b/mobile/android/android-components/components/feature/contextmenu/src/main/res/values/strings.xml @@ -17,6 +17,8 @@ <string name="mozac_feature_contextmenu_share_image">Share image</string> <!-- Text for context menu item to copy the link to the clipboard. --> <string name="mozac_feature_contextmenu_copy_link">Copy link</string> + <!-- Text for context menu item to copy the link text to the clipboard. --> + <string name="mozac_feature_contextmenu_copy_link_text">Copy link text</string> <!-- Text for context menu item to copy the URL pointing to the image to the clipboard. --> <string name="mozac_feature_contextmenu_copy_image_location">Copy image location</string> <!-- Text for context menu item to save / download the image. --> @@ -31,6 +33,8 @@ <string name="mozac_feature_contextmenu_snackbar_new_private_tab_opened">New private tab opened</string> <!-- Text for confirmation "snackbar" shown after copying a link or image URL to the clipboard. --> <string name="mozac_feature_contextmenu_snackbar_link_copied">Link copied to clipboard</string> + <!-- Text for confirmation "snackbar" shown after copying a link text to the clipboard. --> + <string name="mozac_feature_contextmenu_snackbar_link_text_copied">Link text copied to clipboard</string> <!-- Action shown in a "snacbkar" after opening a new/private tab. Clicking this action will switch to the newly opened tab. --> <string name="mozac_feature_contextmenu_snackbar_action_switch">Switch</string> <!-- Text for context menu item to open the link in an external app. --> diff --git a/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt b/mobile/android/android-components/components/feature/contextmenu/src/test/java/mozilla/components/feature/contextmenu/ContextMenuCandidateTest.kt @@ -67,7 +67,26 @@ class ContextMenuCandidateTest { val candidates = ContextMenuCandidate.defaultCandidates(testContext, mock(), mock(), mock()) // Just a sanity check: When changing the list of default candidates be aware that this will affect all // consumers of this component using the default list. - assertEquals(14, candidates.size) + assertEquals( + listOf( + "mozac.feature.contextmenu.open_in_new_tab", + "mozac.feature.contextmenu.open_in_private_tab", + "mozac.feature.contextmenu.copy_link", + "mozac.feature.contextmenu.copy_link_text", + "mozac.feature.contextmenu.download_link", + "mozac.feature.contextmenu.share_link", + "mozac.feature.contextmenu.share_image", + "mozac.feature.contextmenu.open_image_in_new_tab", + "mozac.feature.contextmenu.copy_image", + "mozac.feature.contextmenu.save_image", + "mozac.feature.contextmenu.save_video", + "mozac.feature.contextmenu.copy_image_location", + "mozac.feature.contextmenu.add_to_contact", + "mozac.feature.contextmenu.share_email", + "mozac.feature.contextmenu.copy_email_address", + ), + candidates.map { it.id }, + ) } @Test @@ -1513,6 +1532,51 @@ class ContextMenuCandidateTest { } @Test + fun `Candidate 'Copy link text' is shown for UNKNOWN HitResult with link text`() { + val parentView = CoordinatorLayout(testContext) + + val copyLinkText = ContextMenuCandidate.createCopyLinkTextCandidate( + testContext, + parentView, + snackbarDelegate, + ) + + assertTrue( + "Copy link text is shown for HitResult.UNKNOWN with link text", + copyLinkText.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN(src = "https://www.mozilla.org", linkText = "Mozilla"), + ), + ) + } + + @Test + fun `Candidate 'Copy link text' not shown for UNKNOWN HitResult without link text`() { + val parentView = CoordinatorLayout(testContext) + + val copyLinkText = ContextMenuCandidate.createCopyLinkTextCandidate( + testContext, + parentView, + snackbarDelegate, + ) + + assertFalse( + "Copy link text is not shown for HitResult.UNKNOWN with empty link text", + copyLinkText.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN(src = "https://www.mozilla.org", linkText = ""), + ), + ) + assertFalse( + "Copy link text is not shown for HitResult.UNKNOWN with null link text", + copyLinkText.showFor( + createTab("https://www.mozilla.org"), + HitResult.UNKNOWN(src = "https://www.mozilla.org", linkText = null), + ), + ) + } + + @Test fun `Candidate 'Copy Link' for videos`() { val parentView = CoordinatorLayout(testContext) diff --git a/mobile/android/android-components/config/detekt-baseline.xml b/mobile/android/android-components/config/detekt-baseline.xml @@ -314,7 +314,6 @@ <ID>UndocumentedPublicFunction:FindInPageView.kt$FindInPageView.Listener$fun onFindAll(query: String)</ID> <ID>UndocumentedPublicFunction:FindInPageView.kt$FindInPageView.Listener$fun onNextResult()</ID> <ID>UndocumentedPublicFunction:FindInPageView.kt$FindInPageView.Listener$fun onPreviousResult()</ID> - <ID>UndocumentedPublicFunction:GeckoEngineSession.kt$GeckoEngineSession$fun handleLongClick(elementSrc: String?, elementType: Int, uri: String? = null, title: String? = null): HitResult?</ID> <ID>UndocumentedPublicFunction:GeckoEngineSessionState.kt$GeckoEngineSessionState.Companion$fun fromJSON(json: JSONObject): GeckoEngineSessionState</ID> <ID>UndocumentedPublicFunction:GeckoPermissionRequest.kt$GeckoPermissionRequest.Media.Companion$fun mapPermission(mediaSource: MediaSource): Permission</ID> <ID>UndocumentedPublicFunction:Headers.kt$fun List&lt;Header&gt;.toMutableHeaders()</ID> diff --git a/mobile/android/android-components/docs/changelog.md b/mobile/android/android-components/docs/changelog.md @@ -5,6 +5,10 @@ permalink: /changelog/ --- # 148.0 (In Development) +* **browser-engine-gecko** and **concept-engine** + * Add optional link text support to HitResult.UNKNOWN to allow getting the text associated with a link in response to a long click +* **feature-contextmenu** + * 🆕 New: "Copy link text" context menu candidate to allow for the ability to copy link text [Bug 1809303](https://bugzilla.mozilla.org/show_bug.cgi?id=1809303) # 147.0 * **browser-state**: diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/ContextMenusTest.kt @@ -125,6 +125,21 @@ class ContextMenusTest : TestSetup() { } } + @Test + fun verifyCopyLinkTextContextMenuOptionTest() { + val pageLinks = mockWebServer.getGenericAsset(4) + val genericURL = mockWebServer.getGenericAsset(3) + + navigationToolbar { + }.enterURLAndEnterToBrowser(pageLinks.url) { + mDevice.waitForIdle() + longClickPageObject(itemWithText("Link 3")) + verifyContextMenuForLocalHostLinks(genericURL.url) + clickContextMenuItem("Copy link text") + verifySnackBarText("Link text copied to clipboard") + } + } + // TestRail link: https://mozilla.testrail.io/index.php?/cases/view/243838 @Test fun verifyShareLinkContextMenuOptionTest() { diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt @@ -53,6 +53,7 @@ import java.lang.Boolean; import java.lang.CharSequence; import java.lang.Class; import java.lang.Comparable; +import java.lang.Deprecated; import java.lang.Double; import java.lang.Exception; import java.lang.Float; @@ -90,6 +91,7 @@ import org.mozilla.geckoview.ContentBlocking; import org.mozilla.geckoview.ContentBlockingController; import org.mozilla.geckoview.CrashHandler; import org.mozilla.geckoview.CrashPullController; +import org.mozilla.geckoview.DeprecationSchedule; import org.mozilla.geckoview.ExperimentDelegate; import org.mozilla.geckoview.GeckoDisplay; import org.mozilla.geckoview.GeckoPreferenceController; @@ -1347,17 +1349,18 @@ package org.mozilla.geckoview { } public static class GeckoSession.ContentDelegate.ContextElement { - ctor protected ContextElement(@Nullable String, @Nullable String, @Nullable String, @Nullable String, @NonNull String, @Nullable String, @Nullable String); - ctor protected ContextElement(@Nullable String, @Nullable String, @Nullable String, @Nullable String, @NonNull String, @Nullable String); + ctor protected ContextElement(@Nullable String, @Nullable String, @Nullable String, @Nullable String, @NonNull String, @Nullable String, @Nullable String, @Nullable String); + ctor @Deprecated @DeprecationSchedule(id="context-element-api-updates",version=151) protected ContextElement(@Nullable String, @Nullable String, @Nullable String, @Nullable String, @NonNull String, @Nullable String); field public static final int TYPE_AUDIO = 3; field public static final int TYPE_IMAGE = 1; field public static final int TYPE_NONE = 0; field public static final int TYPE_VIDEO = 2; field @Nullable public final String altText; field @Nullable public final String baseUri; + field @Nullable public final String linkText; field @Nullable public final String linkUri; field @Nullable public final String srcUri; - field @Nullable public final String textContent; + field @Deprecated @DeprecationSchedule(id="context-element-api-updates",version=151) @Nullable public final String textContent; field @Nullable public final String title; field public final int type; } diff --git a/mobile/android/geckoview/src/androidTest/assets/www/context_menu_normal_link_text.html b/mobile/android/geckoview/src/androidTest/assets/www/context_menu_normal_link_text.html @@ -0,0 +1,22 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" content="width=device-width, height=device-height" /> + <title>Context Menu Test Link</title> + </head> + <style> + #hello { + font-size: 20vw; + } + </style> + <body> + <a + href="hello.html" + title="Lorem ipsum dolor sit amet cillum amet minim." + alt="Lorem ipsum dolor sit amet cillum amet minim." + id="hello" + > + Lorem ipsum dolor sit amet cillum amet minim.</a + > + </body> +</html> diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt @@ -136,6 +136,7 @@ open class BaseSessionTest( const val CONTEXT_MENU_IMAGE_HTML_PATH = "/assets/www/context_menu_image.html" const val CONTEXT_MENU_LINK_HTML_PATH = "/assets/www/context_menu_link.html" const val CONTEXT_MENU_LINK_TEXT_HTML_PATH = "/assets/www/context_menu_link_text.html" + const val CONTEXT_MENU_LINK_TEXT_HTML_NORMAL_LENGTH_PATH = "/assets/www/context_menu_normal_link_text.html" const val CONTEXT_MENU_VIDEO_HTML_PATH = "/assets/www/context_menu_video.html" const val CONTEXT_MENU_BLOB_FULL_HTML_PATH = "/assets/www/context_menu_blob_full.html" const val CONTEXT_MENU_BLOB_BUFFERED_HTML_PATH = "/assets/www/context_menu_blob_buffered.html" diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateChildTest.kt @@ -21,7 +21,6 @@ import org.hamcrest.Matchers.endsWith import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.startsWith import org.junit.Assert.assertNull -import org.junit.Assume.assumeThat import org.junit.Test import org.junit.runner.RunWith import org.mozilla.geckoview.GeckoSession @@ -390,11 +389,17 @@ class ContentDelegateChildTest : BaseSessionTest() { element.linkUri, endsWith("hello.html"), ) + @Suppress("DEPRECATION") // remove when textContent is removed assertThat( "The element link text content should be the text content of the anchor.", element.textContent, equalTo("Hello World"), ) + assertThat( + "The element link text should be the link text of the anchor.", + element.linkText, + equalTo("Hello World"), + ) } }, ) @@ -402,7 +407,7 @@ class ContentDelegateChildTest : BaseSessionTest() { @WithDisplay(width = 100, height = 100) @Test - fun requestContextMenuOnLinkText() { + fun requestContextMenuOnLinkTextLimits() { mainSession.loadTestPath(CONTEXT_MENU_LINK_TEXT_HTML_PATH) mainSession.waitForPageStop() sendLongPress(50f, 50f) @@ -426,11 +431,66 @@ class ContentDelegateChildTest : BaseSessionTest() { element.altText?.length, equalTo(4096), ) + @Suppress("DEPRECATION") // remove when textContent is removed assertThat( "The element link text content should not exceed a maximum of 4096 chars.", element.textContent?.length, equalTo(4096), ) + assertThat( + "The element link text should not exceed a maximum of 4096 chars.", + element.linkText?.length, + equalTo(4096), + ) + } + }, + ) + } + + @WithDisplay(width = 100, height = 100) + @Test + fun requestContextMenuOnLinkText() { + mainSession.loadTestPath(CONTEXT_MENU_LINK_TEXT_HTML_NORMAL_LENGTH_PATH) + mainSession.waitForPageStop() + sendLongPress(50f, 50f) + + mainSession.waitUntilCalled( + object : ContentDelegate { + @AssertCalled(count = 1) + override fun onContextMenu( + session: GeckoSession, + screenX: Int, + screenY: Int, + element: ContextElement, + ) { + assertThat( + "Type should be none.", + element.type, + equalTo(ContextElement.TYPE_NONE), + ) + assertThat( + "The element link title should be the title of the anchor.", + element.title, + equalTo("Lorem ipsum dolor sit amet cillum amet minim."), + ) + assertThat( + "The element link URI should be the href of the anchor.", + element.linkUri, + endsWith("hello.html"), + ) + @Suppress("DEPRECATION") // remove when textContent is removed + assertThat( + "The element link text content should be the text content of the " + + "anchor including white spaces.", + element.textContent, + equalTo("\n Lorem ipsum dolor sit amet cillum amet minim."), + ) + assertThat( + "The element link text should be the link text of the " + + "anchor without white spaces.", + element.linkText, + equalTo("Lorem ipsum dolor sit amet cillum amet minim."), + ) } }, ) @@ -518,11 +578,17 @@ class ContentDelegateChildTest : BaseSessionTest() { element.linkUri, endsWith("hello.html"), ) + @Suppress("DEPRECATION") // remove when textContent is removed assertThat( "The element link text content should be the text content of the anchor.", element.textContent, equalTo("Hello World"), ) + assertThat( + "The element link text should be the link text of the anchor.", + element.linkText, + equalTo("Hello World"), + ) } }, ) @@ -565,11 +631,17 @@ class ContentDelegateChildTest : BaseSessionTest() { element.linkUri, endsWith("hello.html"), ) + @Suppress("DEPRECATION") // remove when textContent is removed assertThat( "The element link text content should be the text content of the anchor.", element.textContent, equalTo("Hello World"), ) + assertThat( + "The element link text should be the link text of the anchor.", + element.linkText, + equalTo("Hello World"), + ) } }, ) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -586,7 +586,8 @@ public class GeckoSession { message.getString("alt"), message.getString("elementType"), message.getString("elementSrc"), - message.getString("textContent")); + message.getString("textContent"), + message.getString("linkText")); delegate.onContextMenu( GeckoSession.this, message.getInt("screenX"), message.getInt("screenY"), elem); @@ -3937,9 +3938,19 @@ public class GeckoSession { /** The source URI (src) of the element. Set for (nested) media elements. */ public final @Nullable String srcUri; - /** The text content of the element */ + /** + * The text content of the element + * + * @deprecated This field is deprecated, please use {@link ContextElement#linkText} to + * retrieve the text associated with a link element. + */ + @Deprecated + @DeprecationSchedule(id = "context-element-api-updates", version = 151) public final @Nullable String textContent; + /** The link text of the element */ + public final @Nullable String linkText; + // TODO: Bug 1595822 make public final List<WebExtension.Menu> extensionMenus; @@ -3953,6 +3964,7 @@ public class GeckoSession { * @param typeStr The type of the element. * @param srcUri The source URI (src). * @param textContent The text content. + * @param linkText The link text content. */ protected ContextElement( final @Nullable String baseUri, @@ -3961,7 +3973,8 @@ public class GeckoSession { final @Nullable String altText, final @NonNull String typeStr, final @Nullable String srcUri, - final @Nullable String textContent) { + final @Nullable String textContent, + final @Nullable String linkText) { this.baseUri = baseUri; this.linkUri = linkUri; this.title = title; @@ -3970,6 +3983,7 @@ public class GeckoSession { this.srcUri = srcUri; this.textContent = textContent; this.extensionMenus = null; + this.linkText = linkText; } /** @@ -3981,7 +3995,11 @@ public class GeckoSession { * @param altText The alternative text (alt). * @param typeStr The type of the element. * @param srcUri The source URI (src). + * @deprecated This constructor has been deprecated and will be removed in a future version. + * Please use the other overloaded constructors. */ + @Deprecated + @DeprecationSchedule(id = "context-element-api-updates", version = 151) protected ContextElement( final @Nullable String baseUri, final @Nullable String linkUri, @@ -3989,7 +4007,7 @@ public class GeckoSession { final @Nullable String altText, final @NonNull String typeStr, final @Nullable String srcUri) { - this(baseUri, linkUri, title, altText, typeStr, srcUri, null); + this(baseUri, linkUri, title, altText, typeStr, srcUri, null, null); } private static int getType(final String name) { diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -17,8 +17,16 @@ exclude: true - Introduce the harmful-addon URL-Classifier feature - [`HARMFULADDON`][148.1] - ⚠️ Remove deprecated `GeckoRuntimeSettings.Builder.setLnaBlockingEnabled`, `GeckoRuntimeSettings.setLnaBlockingEnabled` and `GeckoRuntimeSettings.getLnaBlockingEnabled` APIs. Alternatives were introduced in v147. +- Added [`linkText`][148.2] to [`ContentDelegate.ContextElement`][65.21] and a new [`constructor`][148.3] to [`ContentDelegate.ContextElement`][65.21] +- ⚠️ Deprecated [`ContentDelegate.ContextElement`][148.4] constructor. +- ⚠️ Deprecated [`ContentDelegate.ContextElement.textContent`][148.5]. +- ⚠️ Removed superfluous constructor overload for [`ContentDelegate.ContextElement`] [148.1]: {{javadoc_uri}}/ContentBlocking.SafeBrowsing.html#HARMFULADDON +[148.2]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html#linkText +[148.3]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html#<init>(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String) +[148.4]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html#<init>(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String) +[148.5]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html#textContent ## v147 - Changed Local Network / Device Access APIs in `GeckoRuntimeSettings` & `GeckoRuntimeSettings.Builder` for more granularity @@ -1884,4 +1892,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]: d32e6e204686a16db974861a7ce2ed06bc8147fc +[api-version]: c3ca86b7e2f375d68aa9ce29504debdb17c6500f diff --git a/mobile/shared/actors/ContentDelegateChild.sys.mjs b/mobile/shared/actors/ContentDelegateChild.sys.mjs @@ -194,6 +194,10 @@ export class ContentDelegateChild extends GeckoViewActorChild { (node.textContent && node.textContent.substring(0, MAX_TEXT_LENGTH)) || null, + linkText: + (node.innerText && + node.innerText.substring(0, MAX_TEXT_LENGTH)) || + null, }; this.sendAsyncMessage("GeckoView:ContextMenu", msg);