зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1844521 - GeckoView Initial Session Translations API r=geckoview-reviewers,kaya,calu
This patch lands an initial GeckoView session API for using toolkit translations. This patch adds an initial session translate delegate, functions to translate, detect languages, and restore to an original page. The runtime API will follow in bug 1852313 and additional session API changes are planned. Differential Revision: https://phabricator.services.mozilla.com/D189228
This commit is contained in:
Родитель
9a4832dfe3
Коммит
3eceb9a32f
|
@ -83,3 +83,8 @@ pref("pdfjs.handleOctetStream", true);
|
|||
pref("browser.download.open_pdf_attachments_inline", true);
|
||||
pref("pdfjs.annotationEditorMode", -1);
|
||||
pref("pdfjs.enableFloatingToolbar", true);
|
||||
|
||||
// Bug 1809922 to enable translations
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("browser.translations.enable", true);
|
||||
#endif
|
|
@ -856,6 +856,12 @@ function startup() {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GeckoViewTranslations",
|
||||
onInit: {
|
||||
resource: "resource://gre/modules/GeckoViewTranslations.sys.mjs",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
if (!Services.appinfo.sessionHistoryInParent) {
|
||||
|
|
|
@ -108,6 +108,7 @@ import org.mozilla.geckoview.SessionPdfFileSaver;
|
|||
import org.mozilla.geckoview.SessionTextInput;
|
||||
import org.mozilla.geckoview.SlowScriptResponse;
|
||||
import org.mozilla.geckoview.StorageController;
|
||||
import org.mozilla.geckoview.TranslationsController;
|
||||
import org.mozilla.geckoview.WebExtension;
|
||||
import org.mozilla.geckoview.WebExtensionController;
|
||||
import org.mozilla.geckoview.WebMessage;
|
||||
|
@ -966,9 +967,11 @@ package org.mozilla.geckoview {
|
|||
method @AnyThread @Nullable public GeckoSession.PromptDelegate getPromptDelegate();
|
||||
method @Nullable @UiThread public GeckoSession.ScrollDelegate getScrollDelegate();
|
||||
method @AnyThread @Nullable public GeckoSession.SelectionActionDelegate getSelectionActionDelegate();
|
||||
method @AnyThread @Nullable public TranslationsController.SessionTranslation getSessionTranslation();
|
||||
method @AnyThread @NonNull public GeckoSessionSettings getSettings();
|
||||
method @UiThread public void getSurfaceBounds(@NonNull Rect);
|
||||
method @AnyThread @NonNull public SessionTextInput getTextInput();
|
||||
method @AnyThread @Nullable public TranslationsController.SessionTranslation.Delegate getTranslationsSessionDelegate();
|
||||
method @AnyThread @NonNull public GeckoResult<String> getUserAgent();
|
||||
method @NonNull @UiThread public WebExtension.SessionController getWebExtensionController();
|
||||
method @AnyThread public void goBack();
|
||||
|
@ -1011,6 +1014,7 @@ package org.mozilla.geckoview {
|
|||
method @AnyThread public void setPromptDelegate(@Nullable GeckoSession.PromptDelegate);
|
||||
method @UiThread public void setScrollDelegate(@Nullable GeckoSession.ScrollDelegate);
|
||||
method @UiThread public void setSelectionActionDelegate(@Nullable GeckoSession.SelectionActionDelegate);
|
||||
method @AnyThread public void setTranslationsSessionDelegate(@Nullable TranslationsController.SessionTranslation.Delegate);
|
||||
method @AnyThread public void stop();
|
||||
method @UiThread protected void setShouldPinOnScreen(boolean);
|
||||
field public static final int FINDER_DISPLAY_DIM_PAGE = 2;
|
||||
|
@ -2201,6 +2205,56 @@ package org.mozilla.geckoview {
|
|||
@Retention(value=RetentionPolicy.SOURCE) public static interface StorageController.StorageControllerClearFlags {
|
||||
}
|
||||
|
||||
public class TranslationsController {
|
||||
ctor public TranslationsController();
|
||||
}
|
||||
|
||||
public static class TranslationsController.SessionTranslation {
|
||||
ctor public SessionTranslation(GeckoSession);
|
||||
method @AnyThread @NonNull public TranslationsController.SessionTranslation.Handler getHandler();
|
||||
method @AnyThread @NonNull public GeckoResult<Void> restoreOriginalPage();
|
||||
method @AnyThread @NonNull public GeckoResult<Void> translate(@NonNull String, @NonNull String, @Nullable TranslationsController.SessionTranslation.TranslationOptions);
|
||||
method @AnyThread @NonNull public GeckoResult<Void> translate(@NonNull TranslationsController.SessionTranslation.TranslationPair, @Nullable TranslationsController.SessionTranslation.TranslationOptions);
|
||||
}
|
||||
|
||||
@AnyThread public static interface TranslationsController.SessionTranslation.Delegate {
|
||||
method default public void onExpectedTranslate(@NonNull GeckoSession);
|
||||
method default public void onOfferTranslate(@NonNull GeckoSession);
|
||||
method default public void onTranslationStateChange(@NonNull GeckoSession, @Nullable TranslationsController.SessionTranslation.TranslationState);
|
||||
}
|
||||
|
||||
public static class TranslationsController.SessionTranslation.DetectedLanguages {
|
||||
ctor public DetectedLanguages(@Nullable String, @NonNull Boolean, @Nullable String);
|
||||
field @Nullable public final String docLangTag;
|
||||
field @NonNull public final Boolean isDocLangTagSupported;
|
||||
field @Nullable public final String userLangTag;
|
||||
}
|
||||
|
||||
@AnyThread public static class TranslationsController.SessionTranslation.TranslationOptions {
|
||||
ctor protected TranslationOptions(@NonNull TranslationsController.SessionTranslation.TranslationOptions.Builder);
|
||||
field @NonNull public final boolean downloadModel;
|
||||
}
|
||||
|
||||
@AnyThread public static class TranslationsController.SessionTranslation.TranslationOptions.Builder {
|
||||
ctor public Builder();
|
||||
method @AnyThread @NonNull public TranslationsController.SessionTranslation.TranslationOptions build();
|
||||
method @NonNull public TranslationsController.SessionTranslation.TranslationOptions.Builder downloadModel(@NonNull boolean);
|
||||
}
|
||||
|
||||
public static class TranslationsController.SessionTranslation.TranslationPair {
|
||||
ctor public TranslationPair(@Nullable String, @Nullable String);
|
||||
field @Nullable public final String fromLanguage;
|
||||
field @Nullable public final String toLanguage;
|
||||
}
|
||||
|
||||
public static class TranslationsController.SessionTranslation.TranslationState {
|
||||
ctor public TranslationState(@Nullable TranslationsController.SessionTranslation.TranslationPair, @Nullable String, @Nullable TranslationsController.SessionTranslation.DetectedLanguages, @NonNull Boolean);
|
||||
field @Nullable public final TranslationsController.SessionTranslation.DetectedLanguages detectedLanguages;
|
||||
field @Nullable public final String error;
|
||||
field @NonNull public final Boolean isEngineReady;
|
||||
field @Nullable public final TranslationsController.SessionTranslation.TranslationPair requestedTranslationPair;
|
||||
}
|
||||
|
||||
public class WebExtension {
|
||||
method @Nullable @UiThread public WebExtension.BrowsingDataDelegate getBrowsingDataDelegate();
|
||||
method @Nullable @UiThread public WebExtension.DownloadDelegate getDownloadDelegate();
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!-- See toolkit for original test.-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Translations Test</title>
|
||||
<style>
|
||||
div {
|
||||
margin: 10px auto;
|
||||
width: 300px;
|
||||
}
|
||||
p {
|
||||
margin: 47px 0;
|
||||
font-size: 21px;
|
||||
line-height: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<!-- The following is an excerpt from The Wondeful Wizard of Oz, which is in the public domain -->
|
||||
<h1>"The Wonderful Wizard of Oz" by L. Frank Baum</h1>
|
||||
<p>
|
||||
The little girl, seeing she had lost one of her pretty shoes, grew
|
||||
angry, and said to the Witch, “Give me back my shoe!”
|
||||
</p>
|
||||
<p>
|
||||
“I will not,” retorted the Witch, “for it is now my shoe, and not
|
||||
yours.”
|
||||
</p>
|
||||
<p>
|
||||
“You are a wicked creature!” cried Dorothy. “You have no right to take
|
||||
my shoe from me.”
|
||||
</p>
|
||||
<p>
|
||||
“I shall keep it, just the same,” said the Witch, laughing at her, “and
|
||||
someday I shall get the other one from you, too.”
|
||||
</p>
|
||||
<p>
|
||||
This made Dorothy so very angry that she picked up the bucket of water
|
||||
that stood near and dashed it over the Witch, wetting her from head to
|
||||
foot.
|
||||
</p>
|
||||
<p>
|
||||
Instantly the wicked woman gave a loud cry of fear, and then, as Dorothy
|
||||
looked at her in wonder, the Witch began to shrink and fall away.
|
||||
</p>
|
||||
<p>
|
||||
“See what you have done!” she screamed. “In a minute I shall melt away.”
|
||||
</p>
|
||||
<p>
|
||||
“I’m very sorry, indeed,” said Dorothy, who was truly frightened to see
|
||||
the Witch actually melting away like brown sugar before her very eyes.
|
||||
</p>
|
||||
<p>
|
||||
“Didn’t you know water would be the end of me?” asked the Witch, in a
|
||||
wailing, despairing voice.
|
||||
</p>
|
||||
<p>“Of course not,” answered Dorothy. “How should I?”</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,83 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<!-- See toolkit for original test.-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Translations Test</title>
|
||||
<style>
|
||||
div {
|
||||
margin: 10px auto;
|
||||
width: 300px;
|
||||
}
|
||||
p {
|
||||
margin: 47px 0;
|
||||
font-size: 21px;
|
||||
line-height: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<header lang="en">
|
||||
The following is an excerpt from Don Quijote de la Mancha, which is in
|
||||
the public domain
|
||||
</header>
|
||||
<h1>Don Quijote de La Mancha</h1>
|
||||
<h2>Capítulo VIII.</h2>
|
||||
<p>
|
||||
Del buen suceso que el valeroso don Quijote tuvo en la espantable y
|
||||
jamás imaginada aventura de los molinos de viento, con otros sucesos
|
||||
dignos de felice recordación
|
||||
</p>
|
||||
<p>
|
||||
En esto, descubrieron treinta o cuarenta molinos de viento que hay en
|
||||
aquel campo; y, así como don Quijote los vio, dijo a su escudero:
|
||||
</p>
|
||||
<p>
|
||||
— La ventura va guiando nuestras cosas mejor de lo que acertáramos a
|
||||
desear, porque ves allí, amigo Sancho Panza, donde se descubren treinta,
|
||||
o pocos más, desaforados gigantes, con quien pienso hacer batalla y
|
||||
quitarles a todos las vidas, con cuyos despojos comenzaremos a
|
||||
enriquecer; que ésta es buena guerra, y es gran servicio de Dios quitar
|
||||
tan mala simiente de sobre la faz de la tierra.
|
||||
</p>
|
||||
<p>— ¿Qué gigantes? —dijo Sancho Panza.</p>
|
||||
<p>
|
||||
— Aquellos que allí ves —respondió su amo— de los brazos largos, que los
|
||||
suelen tener algunos de casi dos leguas.
|
||||
</p>
|
||||
<p>
|
||||
— Mire vuestra merced —respondió Sancho— que aquellos que allí se
|
||||
parecen no son gigantes, sino molinos de viento, y lo que en ellos
|
||||
parecen brazos son las aspas, que, volteadas del viento, hacen andar la
|
||||
piedra del molino.
|
||||
</p>
|
||||
<p>
|
||||
— Bien parece —respondió don Quijote— que no estás cursado en esto de
|
||||
las aventuras: ellos son gigantes; y si tienes miedo, quítate de ahí, y
|
||||
ponte en oración en el espacio que yo voy a entrar con ellos en fiera y
|
||||
desigual batalla.
|
||||
</p>
|
||||
<p>
|
||||
Y, diciendo esto, dio de espuelas a su caballo Rocinante, sin atender a
|
||||
las voces que su escudero Sancho le daba, advirtiéndole que, sin duda
|
||||
alguna, eran molinos de viento, y no gigantes, aquellos que iba a
|
||||
acometer. Pero él iba tan puesto en que eran gigantes, que ni oía las
|
||||
voces de su escudero Sancho ni echaba de ver, aunque estaba ya bien
|
||||
cerca, lo que eran; antes, iba diciendo en voces altas:
|
||||
</p>
|
||||
<p>
|
||||
— Non fuyades, cobardes y viles criaturas, que un solo caballero es el
|
||||
que os acomete.
|
||||
</p>
|
||||
<p>
|
||||
Levantóse en esto un poco de viento y las grandes aspas comenzaron a
|
||||
moverse, lo cual visto por don Quijote, dijo:
|
||||
</p>
|
||||
<p>
|
||||
— Pues, aunque mováis más brazos que los del gigante Briareo, me lo
|
||||
habéis de pagar.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -135,6 +135,8 @@ open class BaseSessionTest(
|
|||
const val HELLO_PDF_WORLD_PDF_PATH = "/assets/www/helloPDFWorld.pdf"
|
||||
const val ORANGE_PDF_PATH = "/assets/www/orange.pdf"
|
||||
const val NO_META_VIEWPORT_HTML_PATH = "/assets/www/no-meta-viewport.html"
|
||||
const val TRANSLATIONS_EN = "/assets/www/translations-tester-en.html"
|
||||
const val TRANSLATIONS_ES = "/assets/www/translations-tester-es.html"
|
||||
|
||||
const val TEST_ENDPOINT = GeckoSessionTestRule.TEST_ENDPOINT
|
||||
const val TEST_HOST = GeckoSessionTestRule.TEST_HOST
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.geckoview.test
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.MediumTest
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.geckoview.GeckoSession
|
||||
import org.mozilla.geckoview.TranslationsController.SessionTranslation.Delegate
|
||||
import org.mozilla.geckoview.TranslationsController.SessionTranslation.TranslationOptions
|
||||
import org.mozilla.geckoview.TranslationsController.SessionTranslation.TranslationState
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@MediumTest
|
||||
class TranslationsTest : BaseSessionTest() {
|
||||
@Before
|
||||
fun setup() {
|
||||
sessionRule.setPrefsUntilTestEnd(
|
||||
mapOf(
|
||||
"browser.translations.enable" to true,
|
||||
"browser.translations.automaticallyPopup" to true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onExpectedTranslateDelegateTest() {
|
||||
sessionRule.delegateDuringNextWait(object : Delegate {
|
||||
@AssertCalled(count = 0)
|
||||
override fun onExpectedTranslate(session: GeckoSession) {
|
||||
}
|
||||
})
|
||||
mainSession.loadTestPath(TRANSLATIONS_ES)
|
||||
mainSession.waitForPageStop()
|
||||
// ToDo: bug 1853469 is to research getting automation testing working fully.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onOfferTranslateDelegateTest() {
|
||||
sessionRule.delegateDuringNextWait(object : Delegate {
|
||||
@AssertCalled(count = 0)
|
||||
override fun onOfferTranslate(session: GeckoSession) {
|
||||
}
|
||||
})
|
||||
mainSession.loadTestPath(TRANSLATIONS_ES)
|
||||
mainSession.waitForPageStop()
|
||||
// ToDo: bug 1853469 is to research getting onOfferTranslate to work in automation.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onTranslationStateChangeDelegateTest() {
|
||||
sessionRule.delegateDuringNextWait(object : Delegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onTranslationStateChange(
|
||||
session: GeckoSession,
|
||||
translationState: TranslationState?,
|
||||
) {
|
||||
assertTrue(
|
||||
"Translations correctly does not have an engine ready.",
|
||||
translationState?.isEngineReady == false,
|
||||
)
|
||||
assertTrue("Translations correctly does not have a requested pair. ", translationState?.requestedTranslationPair == null)
|
||||
}
|
||||
})
|
||||
mainSession.loadTestPath(TRANSLATIONS_ES)
|
||||
mainSession.waitForPageStop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun translateTest() {
|
||||
sessionRule.delegateUntilTestEnd(object : Delegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onTranslationStateChange(
|
||||
session: GeckoSession,
|
||||
translationState: TranslationState?,
|
||||
) {
|
||||
assertTrue(
|
||||
"Translations correctly does not have an engine ready. ",
|
||||
translationState?.isEngineReady == false,
|
||||
)
|
||||
assertTrue("Translations correctly does not have a requested pair. ", translationState?.requestedTranslationPair == null)
|
||||
}
|
||||
})
|
||||
mainSession.loadTestPath(TRANSLATIONS_ES)
|
||||
mainSession.waitForPageStop()
|
||||
val translate = mainSession.sessionTranslation!!.translate("es", "en", null)
|
||||
try {
|
||||
sessionRule.waitForResult(translate)
|
||||
// ToDo: bug 1853469 models not available in automation
|
||||
assertTrue("Should not be able to translate.", false)
|
||||
} catch (e: Exception) {
|
||||
assertTrue("Should have an exception.", true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun restoreOriginalPageLanguageTest() {
|
||||
sessionRule.delegateUntilTestEnd(object : Delegate {
|
||||
@AssertCalled(count = 2)
|
||||
override fun onTranslationStateChange(
|
||||
session: GeckoSession,
|
||||
translationState: TranslationState?,
|
||||
) {
|
||||
assertTrue(
|
||||
"Translations correctly does not have an engine ready. ",
|
||||
translationState?.isEngineReady == false,
|
||||
)
|
||||
assertTrue("Translations correctly does not have a requested pair. ", translationState?.requestedTranslationPair == null)
|
||||
}
|
||||
})
|
||||
mainSession.loadTestPath(TRANSLATIONS_ES)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
val restore = mainSession.sessionTranslation!!.restoreOriginalPage()
|
||||
try {
|
||||
sessionRule.waitForResult(restore)
|
||||
assertTrue("Should be able to restore.", true)
|
||||
} catch (e: Exception) {
|
||||
assertTrue("Should not have an exception.", false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTranslationOptions() {
|
||||
var options = TranslationOptions.Builder().downloadModel(true).build()
|
||||
assertTrue("TranslationOptions builder options work as expected.", options.downloadModel)
|
||||
// ToDo: bug 1853055 will develop this further.
|
||||
}
|
||||
}
|
|
@ -91,6 +91,7 @@ import org.mozilla.geckoview.MediaSession;
|
|||
import org.mozilla.geckoview.OrientationController;
|
||||
import org.mozilla.geckoview.RuntimeTelemetry;
|
||||
import org.mozilla.geckoview.SessionTextInput;
|
||||
import org.mozilla.geckoview.TranslationsController;
|
||||
import org.mozilla.geckoview.WebExtension;
|
||||
import org.mozilla.geckoview.WebExtensionController;
|
||||
import org.mozilla.geckoview.WebNotificationDelegate;
|
||||
|
@ -776,6 +777,7 @@ public class GeckoSessionTestRule implements TestRule {
|
|||
DEFAULT_DELEGATES.add(ScrollDelegate.class);
|
||||
DEFAULT_DELEGATES.add(SelectionActionDelegate.class);
|
||||
DEFAULT_DELEGATES.add(TextInputDelegate.class);
|
||||
DEFAULT_DELEGATES.add(TranslationsController.SessionTranslation.Delegate.class);
|
||||
}
|
||||
|
||||
private static final Set<Class<?>> DEFAULT_RUNTIME_DELEGATES = new HashSet<>();
|
||||
|
@ -808,6 +810,7 @@ public class GeckoSessionTestRule implements TestRule {
|
|||
ScrollDelegate,
|
||||
SelectionActionDelegate,
|
||||
TextInputDelegate,
|
||||
TranslationsController.SessionTranslation.Delegate,
|
||||
// Runtime delegates
|
||||
ActivityDelegate,
|
||||
Autocomplete.StorageDelegate,
|
||||
|
@ -1010,6 +1013,9 @@ public class GeckoSessionTestRule implements TestRule {
|
|||
session.setAutofillDelegate((Autofill.Delegate) delegate);
|
||||
} else if (cls == MediaSession.Delegate.class) {
|
||||
session.setMediaSessionDelegate((MediaSession.Delegate) delegate);
|
||||
} else if (cls == TranslationsController.SessionTranslation.Delegate.class) {
|
||||
session.setTranslationsSessionDelegate(
|
||||
(TranslationsController.SessionTranslation.Delegate) delegate);
|
||||
} else {
|
||||
GeckoSession.class.getMethod("set" + cls.getSimpleName(), cls).invoke(session, delegate);
|
||||
}
|
||||
|
@ -1082,6 +1088,9 @@ public class GeckoSessionTestRule implements TestRule {
|
|||
if (cls == MediaSession.Delegate.class) {
|
||||
return GeckoSession.class.getMethod("getMediaSessionDelegate").invoke(session);
|
||||
}
|
||||
if (cls == TranslationsController.SessionTranslation.Delegate.class) {
|
||||
return GeckoSession.class.getMethod("getTranslationsSessionDelegate").invoke(session);
|
||||
}
|
||||
return GeckoSession.class.getMethod("get" + cls.getSimpleName()).invoke(session);
|
||||
}
|
||||
|
||||
|
|
|
@ -144,6 +144,8 @@ public class GeckoSession {
|
|||
private SessionAccessibility mAccessibility;
|
||||
private SessionFinder mFinder;
|
||||
private SessionPdfFileSaver mPdfFileSaver;
|
||||
private TranslationsController.SessionTranslation mTranslations =
|
||||
new TranslationsController.SessionTranslation(this);
|
||||
|
||||
/** {@code SessionMagnifier} handles magnifying glass. */
|
||||
/* package */ interface SessionMagnifier {
|
||||
|
@ -1219,6 +1221,8 @@ public class GeckoSession {
|
|||
};
|
||||
|
||||
private final MediaSession.Handler mMediaSessionHandler = new MediaSession.Handler(this);
|
||||
private final TranslationsController.SessionTranslation.Handler mTranslationsHandler =
|
||||
mTranslations.getHandler();
|
||||
|
||||
/* package */ int handlersCount;
|
||||
|
||||
|
@ -1234,6 +1238,7 @@ public class GeckoSession {
|
|||
mProgressHandler,
|
||||
mScrollHandler,
|
||||
mSelectionActionDelegate,
|
||||
mTranslationsHandler,
|
||||
mContentBlockingHandler,
|
||||
mMediaSessionHandler,
|
||||
mExperimentHandler
|
||||
|
@ -3355,6 +3360,40 @@ public class GeckoSession {
|
|||
return mMediaSessionHandler.getDelegate();
|
||||
}
|
||||
|
||||
/**
|
||||
* The session translation object coordinates receiving and sending session messages with the
|
||||
* translations toolkit. Notably, it can be used to request translations.
|
||||
*
|
||||
* @return The current translation session coordinator.
|
||||
*/
|
||||
@AnyThread
|
||||
public @Nullable TranslationsController.SessionTranslation getSessionTranslation() {
|
||||
return mTranslations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the translation delegate, which receives translations events.
|
||||
*
|
||||
* @param delegate An implementation of @link{TranslationsController.SessionTranslation.Delegate}.
|
||||
*/
|
||||
@AnyThread
|
||||
public void setTranslationsSessionDelegate(
|
||||
final @Nullable TranslationsController.SessionTranslation.Delegate delegate) {
|
||||
mTranslationsHandler.setDelegate(delegate, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translations delegate. The application embedder must initially set the translations
|
||||
* delegate for use.
|
||||
*
|
||||
* @return The current translations delegate.
|
||||
*/
|
||||
@AnyThread
|
||||
public @Nullable TranslationsController.SessionTranslation.Delegate
|
||||
getTranslationsSessionDelegate() {
|
||||
return mTranslationsHandler.getDelegate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current selection action delegate for this GeckoSession.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,451 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* vim: ts=4 sw=4 expandtab:
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.geckoview;
|
||||
|
||||
import android.util.Log;
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
|
||||
/**
|
||||
* The translations controller coordinates the session and runtime messaging between GeckoView and
|
||||
* the translations toolkit. Initial runtime component will be added in ToDo: bug 1852313.
|
||||
*/
|
||||
public class TranslationsController {
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String LOGTAG = "TranslationsController";
|
||||
|
||||
/**
|
||||
* Session translation coordinates session messaging between the translations toolkit actor and
|
||||
* GeckoView.
|
||||
*
|
||||
* <p>Performs translations actions that are dependent on the page.
|
||||
*/
|
||||
public static class SessionTranslation {
|
||||
|
||||
// Events Dispatched to Toolkit Translations
|
||||
private static final String TRANSLATE_EVENT = "GeckoView:Translations:Translate";
|
||||
private static final String RESTORE_PAGE_EVENT = "GeckoView:Translations:RestorePage";
|
||||
|
||||
// Events Dispatched from Toolkit Translations
|
||||
private static final String ON_OFFER_EVENT = "GeckoView:Translations:Offer";
|
||||
private static final String ON_STATE_CHANGE_EVENT = "GeckoView:Translations:StateChange";
|
||||
|
||||
private final GeckoSession mSession;
|
||||
private final SessionTranslation.Handler mHandler;
|
||||
|
||||
/**
|
||||
* Construct a new translations session.
|
||||
*
|
||||
* @param session that will be dispatching and receiving events.
|
||||
*/
|
||||
public SessionTranslation(final GeckoSession session) {
|
||||
mSession = session;
|
||||
mHandler = new SessionTranslation.Handler(mSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for receiving messages about translations.
|
||||
*
|
||||
* @return associated session handler
|
||||
*/
|
||||
@AnyThread
|
||||
public @NonNull Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the session's current page based on criteria.
|
||||
*
|
||||
* <p>Currently when translating, the necessary language models will be automatically
|
||||
* downloaded.
|
||||
*
|
||||
* <p>ToDo: bug 1853055 will adjust this flow to add an option for automatic/non-automatic
|
||||
* downloads.
|
||||
*
|
||||
* @param fromLanguage BCP 47 language tag that the page should be translated from. Usually will
|
||||
* be the suggested detected language or user specified.
|
||||
* @param toLanguage BCP 47 language tag that the page should be translated to. Usually will be
|
||||
* the suggested preference language (will be added in ToDo: bug 1852313) or user specified.
|
||||
* @param options no-op, ToDo: bug 1853055 will add options
|
||||
* @return if translate process begins or exceptionally if an issue occurs.
|
||||
*/
|
||||
@AnyThread
|
||||
public @NonNull GeckoResult<Void> translate(
|
||||
@NonNull final String fromLanguage,
|
||||
@NonNull final String toLanguage,
|
||||
@Nullable final TranslationOptions options) {
|
||||
if (DEBUG) {
|
||||
Log.d(
|
||||
LOGTAG,
|
||||
"Translate page requested - fromLanguage: "
|
||||
+ fromLanguage
|
||||
+ " toLanguage: "
|
||||
+ toLanguage
|
||||
+ " options: "
|
||||
+ options);
|
||||
}
|
||||
final GeckoBundle bundle = new GeckoBundle(2);
|
||||
bundle.putString("fromLanguage", fromLanguage);
|
||||
bundle.putString("toLanguage", toLanguage);
|
||||
// ToDo: bug 1853055 - Translate options will be configured in a later iteration.
|
||||
return mSession.getEventDispatcher().queryVoid(TRANSLATE_EVENT, bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for calling {@link #translate(String, String, TranslationOptions)} with a
|
||||
* translation pair.
|
||||
*
|
||||
* @param translationPair the object with a from and to language
|
||||
* @param options no-op, ToDo: bug 1853055 will add options
|
||||
* @return if translate process begins or exceptionally if an issue occurs.
|
||||
*/
|
||||
@AnyThread
|
||||
public @NonNull GeckoResult<Void> translate(
|
||||
@NonNull final TranslationPair translationPair,
|
||||
@Nullable final TranslationOptions options) {
|
||||
return translate(translationPair.fromLanguage, translationPair.toLanguage, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores a page to the original or pre-translated state.
|
||||
*
|
||||
* @return if page restoration process begins or exceptionally if an issue occurs.
|
||||
*/
|
||||
@AnyThread
|
||||
public @NonNull GeckoResult<Void> restoreOriginalPage() {
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "Restore translated page requested");
|
||||
}
|
||||
return mSession.getEventDispatcher().queryVoid(RESTORE_PAGE_EVENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options available for translating. The options available for translating. Will be developed
|
||||
* in ToDo: bug 1853055.
|
||||
*
|
||||
* <p>Options (default):
|
||||
*
|
||||
* <p>downloadModel (true) - Downloads any models automatically that are needed for translation.
|
||||
*/
|
||||
@AnyThread
|
||||
public static class TranslationOptions {
|
||||
/** If the model should be automatically downloaded or stopped. */
|
||||
public final @NonNull boolean downloadModel;
|
||||
|
||||
/**
|
||||
* Options for translation.
|
||||
*
|
||||
* @param builder that populated the translation options
|
||||
*/
|
||||
protected TranslationOptions(final @NonNull Builder builder) {
|
||||
this.downloadModel = builder.mDownloadModel;
|
||||
}
|
||||
|
||||
/** Builder for making translation options. */
|
||||
@AnyThread
|
||||
public static class Builder {
|
||||
/* package */ boolean mDownloadModel = true;
|
||||
|
||||
/**
|
||||
* Build setter for the option for downloading a model.
|
||||
*
|
||||
* @param downloadModel should the model be automatically download or not
|
||||
* @return the model to download for the translation options
|
||||
*/
|
||||
public @NonNull Builder downloadModel(final @NonNull boolean downloadModel) {
|
||||
mDownloadModel = downloadModel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final call to build the specified options.
|
||||
*
|
||||
* @return a constructed translation options
|
||||
*/
|
||||
@AnyThread
|
||||
public @NonNull TranslationOptions build() {
|
||||
return new TranslationOptions(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The translations session delegate is used for receiving translation events and information.
|
||||
*/
|
||||
@AnyThread
|
||||
public interface Delegate {
|
||||
/**
|
||||
* onOfferTranslate occurs when a page should be offered for translation.
|
||||
*
|
||||
* <p>An offer should occur when all conditions are met:
|
||||
*
|
||||
* <p>* The page is not in the user's preferred language
|
||||
*
|
||||
* <p>* The page language is eligible for translation
|
||||
*
|
||||
* <p>* The host hasn't been offered for translation in this session
|
||||
*
|
||||
* <p>* No user preferences indicate that translation shouldn't be offered
|
||||
*
|
||||
* <p>* It is possible to translate
|
||||
*
|
||||
* <p>Usual use-case is to show a pop-up recommending a translation.
|
||||
*
|
||||
* @param session The associated GeckoSession.
|
||||
*/
|
||||
default void onOfferTranslate(@NonNull final GeckoSession session) {}
|
||||
|
||||
/**
|
||||
* onExpectedTranslate occurs when it is likely the user will want to translate and it is
|
||||
* feasible. For example, if the page is in a different language than the user preferred
|
||||
* language or languages.
|
||||
*
|
||||
* <p>Usual use-case is to add a toolbar option for translate.
|
||||
*
|
||||
* @param session The associated GeckoSession.
|
||||
*/
|
||||
default void onExpectedTranslate(@NonNull final GeckoSession session) {}
|
||||
|
||||
/**
|
||||
* onTranslationStateChange occurs when new information about the translation state is
|
||||
* available. This includes information when first visiting the page and after calls to
|
||||
* translate.
|
||||
*
|
||||
* @param session The associated GeckoSession.
|
||||
* @param translationState The state of the translation as reported by the translation engine.
|
||||
*/
|
||||
default void onTranslationStateChange(
|
||||
@NonNull final GeckoSession session, @Nullable TranslationState translationState) {}
|
||||
}
|
||||
|
||||
/** Translation pair is the from language and to language set on the translation state. */
|
||||
public static class TranslationPair {
|
||||
/** Language the page is translated from originally. */
|
||||
public final @Nullable String fromLanguage;
|
||||
|
||||
/** Language the page is translated to. */
|
||||
public final @Nullable String toLanguage;
|
||||
|
||||
/**
|
||||
* Requested translation pair constructor.
|
||||
*
|
||||
* @param fromLanguage original language of page (detected or specified)
|
||||
* @param toLanguage translated to language of page (detected or specified)
|
||||
*/
|
||||
public TranslationPair(
|
||||
@Nullable final String fromLanguage, @Nullable final String toLanguage) {
|
||||
this.fromLanguage = fromLanguage;
|
||||
this.toLanguage = toLanguage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TranslationPair {"
|
||||
+ "fromLanguage='"
|
||||
+ fromLanguage
|
||||
+ '\''
|
||||
+ ", toLanguage='"
|
||||
+ toLanguage
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for deserializing translation state information.
|
||||
*
|
||||
* @param bundle contains translation pair information.
|
||||
* @return translation pair
|
||||
*/
|
||||
/* package */
|
||||
static @Nullable TranslationPair fromBundle(final GeckoBundle bundle) {
|
||||
if (bundle == null) {
|
||||
return null;
|
||||
}
|
||||
return new TranslationPair(
|
||||
bundle.getString("fromLanguage"), bundle.getString("toLanguage"));
|
||||
}
|
||||
}
|
||||
|
||||
/** DetectedLanguages is information that was detected about the page or user preferences. */
|
||||
public static class DetectedLanguages {
|
||||
|
||||
/** The user's preferred language tag */
|
||||
public final @Nullable String userLangTag;
|
||||
|
||||
/** If the engine supports the document language. */
|
||||
public final @NonNull Boolean isDocLangTagSupported;
|
||||
|
||||
/** Detected language tag of page. */
|
||||
public final @Nullable String docLangTag;
|
||||
|
||||
/**
|
||||
* DetectedLanguages constructor.
|
||||
*
|
||||
* @param userLangTag - the user's preferred language tag
|
||||
* @param isDocLangTagSupported - if the engine supports the document language for translation
|
||||
* @param docLangTag - the document's detected language tag
|
||||
*/
|
||||
public DetectedLanguages(
|
||||
@Nullable final String userLangTag,
|
||||
@NonNull final Boolean isDocLangTagSupported,
|
||||
@Nullable final String docLangTag) {
|
||||
this.userLangTag = userLangTag;
|
||||
this.isDocLangTagSupported = isDocLangTagSupported;
|
||||
this.docLangTag = docLangTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DetectedLanguages {"
|
||||
+ "userLangTag='"
|
||||
+ userLangTag
|
||||
+ '\''
|
||||
+ ", isDocLangTagSupported="
|
||||
+ isDocLangTagSupported
|
||||
+ ", docLangTag='"
|
||||
+ docLangTag
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for deserializing detected language state information.
|
||||
*
|
||||
* @param bundle contains detected language information.
|
||||
* @return detected language information
|
||||
*/
|
||||
/* package */
|
||||
static @Nullable DetectedLanguages fromBundle(final GeckoBundle bundle) {
|
||||
if (bundle == null) {
|
||||
return null;
|
||||
}
|
||||
return new DetectedLanguages(
|
||||
bundle.getString("userLangTag"),
|
||||
bundle.getBoolean("isDocLangTagSupported", false),
|
||||
bundle.getString("docLangTag"));
|
||||
}
|
||||
}
|
||||
|
||||
/** The representation of the translation state. */
|
||||
public static class TranslationState {
|
||||
/** The language pair to translate. */
|
||||
public final @Nullable TranslationPair requestedTranslationPair;
|
||||
|
||||
/** If an error state occurred. */
|
||||
public final @Nullable String error;
|
||||
|
||||
/** Detected information about preferences and page information. */
|
||||
public final @Nullable DetectedLanguages detectedLanguages;
|
||||
|
||||
/** If the translation engine is ready for use or will need to be loaded. */
|
||||
public final @NonNull Boolean isEngineReady;
|
||||
|
||||
/**
|
||||
* Translation State constructor.
|
||||
*
|
||||
* @param requestedTranslationPair the language pair to translate
|
||||
* @param error if an error occurred
|
||||
* @param detectedLanguages detected language
|
||||
* @param isEngineReady if the engine is ready for translations
|
||||
*/
|
||||
public TranslationState(
|
||||
final @Nullable TranslationPair requestedTranslationPair,
|
||||
final @Nullable String error,
|
||||
final @Nullable DetectedLanguages detectedLanguages,
|
||||
final @NonNull Boolean isEngineReady) {
|
||||
this.requestedTranslationPair = requestedTranslationPair;
|
||||
this.error = error;
|
||||
this.detectedLanguages = detectedLanguages;
|
||||
this.isEngineReady = isEngineReady;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TranslationState {"
|
||||
+ "requestedTranslationPair="
|
||||
+ requestedTranslationPair
|
||||
+ ", error='"
|
||||
+ error
|
||||
+ '\''
|
||||
+ ", detectedLanguages="
|
||||
+ detectedLanguages
|
||||
+ ", isEngineReady="
|
||||
+ isEngineReady
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for deserializing translation state information.
|
||||
*
|
||||
* @param bundle contains information about translation state.
|
||||
* @return translation state
|
||||
*/
|
||||
/* package */
|
||||
static @Nullable TranslationState fromBundle(final GeckoBundle bundle) {
|
||||
if (bundle == null) {
|
||||
return null;
|
||||
}
|
||||
return new TranslationState(
|
||||
TranslationPair.fromBundle(bundle.getBundle("requestedTranslationPair")),
|
||||
bundle.getString("error"),
|
||||
DetectedLanguages.fromBundle(bundle.getBundle("detectedLanguages")),
|
||||
bundle.getBoolean("isEngineReady", false));
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ static class Handler extends GeckoSessionHandler<SessionTranslation.Delegate> {
|
||||
|
||||
private final GeckoSession mSession;
|
||||
|
||||
private Handler(final GeckoSession session) {
|
||||
super(
|
||||
"GeckoViewTranslations",
|
||||
session,
|
||||
new String[] {
|
||||
ON_OFFER_EVENT, ON_STATE_CHANGE_EVENT,
|
||||
});
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(
|
||||
final Delegate delegate,
|
||||
final String event,
|
||||
final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOGTAG, "handleMessage " + event);
|
||||
}
|
||||
if (delegate == null) {
|
||||
Log.w(LOGTAG, "The translations session delegate is not set.");
|
||||
return;
|
||||
}
|
||||
if (ON_OFFER_EVENT.equals(event)) {
|
||||
delegate.onOfferTranslate(mSession);
|
||||
return;
|
||||
} else if (ON_STATE_CHANGE_EVENT.equals(event)) {
|
||||
final GeckoBundle data = message.getBundle("data");
|
||||
final TranslationState translationState = TranslationState.fromBundle(data);
|
||||
delegate.onTranslationStateChange(mSession, translationState);
|
||||
if (translationState != null
|
||||
&& translationState.detectedLanguages != null
|
||||
&& translationState.detectedLanguages.docLangTag != null
|
||||
&& translationState.detectedLanguages.userLangTag != null
|
||||
&& translationState.detectedLanguages.isDocLangTagSupported)
|
||||
// Also check if engine is supported when runtime functions are added in ToDo: bug 1852313
|
||||
{
|
||||
delegate.onExpectedTranslate(mSession);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,11 +19,16 @@ exclude: true
|
|||
- Added `Builder` pattern constructors for [`ReviewAnalysis`][120.2] and [`Recommendation`][120.3] (part of [bug 1846341]({{bugzilla}}1846341))
|
||||
- Added `DisabledFlags.APP_VERSION` for extensions disabled because they aren't compatible with the application version. ([bug 1847266]({{bugzilla}}1847266))
|
||||
- Added more metadata to the [WebExtension][120.4] class. ([bug 1850674]({{bugzilla}}1850674))
|
||||
- Added session and translations controller. Includes [`TranslationsController`][120.5], [`TranslationsController.SessionTranslation`][120.6] (notably [translate][120.7]), and a [translations delegate][120.8].
|
||||
|
||||
[120.1]: {{javadoc_uri}}/WebExtensionController.html#disableExtensionProcessSpawning
|
||||
[120.2]: {{javadoc_uri}}/GeckoSession.html#ReviewAnalysis.Builder.html
|
||||
[120.3]: {{javadoc_uri}}/GeckoSession.html#Recommendation.Builder.html
|
||||
[120.4]: {{javadoc_uri}}/WebExtension.html)
|
||||
[120.5]: {{javadoc_uri}}/TranslationsController.html
|
||||
[120.6]: {{javadoc_uri}}/TranslationsController.SessionTranslation.html
|
||||
[120.7]: {{javadoc_uri}}/TranslationsController.SessionTranslation.html#translate(java.lang.String,java.lang.String,org.mozilla.geckoview.TranslationsController.SessionTranslation.TranslationOptions)
|
||||
[120.8]: {{javadoc_uri}}/TranslationsController.SessionTranslation.Delegate.html
|
||||
|
||||
## v119
|
||||
- Added `remoteType` to GeckoView child crash intent. ([bug 1851518]({{bugzilla}}1851518))
|
||||
|
@ -1444,4 +1449,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]: 834b35e061b37da5e862ad47dd45f9b26a80ed1f
|
||||
[api-version]: a3b3103cf8ea7f5d05c8b73c197399196c57d391
|
||||
|
|
|
@ -39,8 +39,10 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
@ -87,6 +89,7 @@ import org.mozilla.geckoview.MediaSession;
|
|||
import org.mozilla.geckoview.OrientationController;
|
||||
import org.mozilla.geckoview.RuntimeTelemetry;
|
||||
import org.mozilla.geckoview.SlowScriptResponse;
|
||||
import org.mozilla.geckoview.TranslationsController;
|
||||
import org.mozilla.geckoview.WebExtension;
|
||||
import org.mozilla.geckoview.WebExtensionController;
|
||||
import org.mozilla.geckoview.WebNotification;
|
||||
|
@ -440,6 +443,10 @@ public class GeckoViewActivity extends AppCompatActivity
|
|||
private boolean mCanGoBack;
|
||||
private boolean mCanGoForward;
|
||||
private boolean mFullScreen;
|
||||
private boolean mExpectedTranslate = false;
|
||||
private boolean mTranslateRestore = false;
|
||||
|
||||
private String mDetectedLanguage = null;
|
||||
|
||||
private HashMap<String, Integer> mNotificationIDMap = new HashMap<>();
|
||||
private int mLastID = 100;
|
||||
|
@ -1134,6 +1141,8 @@ public class GeckoViewActivity extends AppCompatActivity
|
|||
|
||||
session.setMediaSessionDelegate(new ExampleMediaSessionDelegate(this));
|
||||
|
||||
session.setTranslationsSessionDelegate(new ExampleTranslationsSessionDelegate());
|
||||
|
||||
session.setSelectionActionDelegate(new BasicSelectionActionDelegate(this));
|
||||
if (sExtensionManager.extension != null) {
|
||||
final WebExtension.SessionController sessionController = session.getWebExtensionController();
|
||||
|
@ -1230,7 +1239,8 @@ public class GeckoViewActivity extends AppCompatActivity
|
|||
menu.findItem(R.id.action_tpe).setEnabled(hasSession && mTrackingProtectionPermission != null);
|
||||
menu.findItem(R.id.action_pb).setEnabled(hasSession);
|
||||
menu.findItem(R.id.desktop_mode).setEnabled(hasSession);
|
||||
|
||||
menu.findItem(R.id.translate).setVisible(mExpectedTranslate);
|
||||
menu.findItem(R.id.translate_restore).setVisible(mTranslateRestore);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1297,6 +1307,12 @@ public class GeckoViewActivity extends AppCompatActivity
|
|||
case R.id.poll_shopping_analysis_status:
|
||||
pollForAnalysisCompleted(session, mCurrentUri);
|
||||
break;
|
||||
case R.id.translate:
|
||||
translate(session);
|
||||
break;
|
||||
case R.id.translate_restore:
|
||||
translateRestore(session);
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@ -1405,6 +1421,88 @@ public class GeckoViewActivity extends AppCompatActivity
|
|||
session.didPrintPageContent();
|
||||
}
|
||||
|
||||
private void translate(GeckoSession session) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.translate);
|
||||
|
||||
// Bug 1844518 - Change to dropdowns with language pairs
|
||||
final EditText fromSelect = new EditText(this);
|
||||
fromSelect.setText(mDetectedLanguage);
|
||||
final EditText toSelect = new EditText(this);
|
||||
// Bug 1844518 - Detect to language preference
|
||||
toSelect.setText("en");
|
||||
builder.setView(translateLayout(fromSelect, toSelect));
|
||||
builder.setPositiveButton(
|
||||
R.string.translate_action,
|
||||
(dialog, which) -> {
|
||||
final String fromLang = fromSelect.getText().toString();
|
||||
final String toLang = toSelect.getText().toString();
|
||||
session.getSessionTranslation().translate(fromLang, toLang, null);
|
||||
mTranslateRestore = true;
|
||||
});
|
||||
builder.setNegativeButton(
|
||||
R.string.cancel,
|
||||
(dialog, which) -> {
|
||||
// Nothing to do
|
||||
});
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void translateRestore(GeckoSession session) {
|
||||
|
||||
session
|
||||
.getSessionTranslation()
|
||||
.restoreOriginalPage()
|
||||
.then(
|
||||
new GeckoResult.OnValueListener<Void, Object>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public GeckoResult<Object> onValue(@Nullable Void value) throws Throwable {
|
||||
mTranslateRestore = false;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private RelativeLayout translateLayout(EditText fromSelect, EditText toSelect) {
|
||||
// From fields
|
||||
TextView fromLangLabel = new TextView(this);
|
||||
fromLangLabel.setText(R.string.translate_language_from_hint);
|
||||
fromSelect.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
fromSelect.setHint(R.string.translate_language_from_hint);
|
||||
LinearLayout from = new LinearLayout(this);
|
||||
from.setId(View.generateViewId());
|
||||
from.addView(fromLangLabel);
|
||||
from.addView(fromSelect);
|
||||
RelativeLayout.LayoutParams fromParams =
|
||||
new RelativeLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
|
||||
fromParams.setMarginStart(30);
|
||||
|
||||
// To fields
|
||||
TextView toLangLabel = new TextView(this);
|
||||
toLangLabel.setText(R.string.translate_language_to_hint);
|
||||
toSelect.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
toSelect.setHint(R.string.translate_language_to_hint);
|
||||
LinearLayout to = new LinearLayout(this);
|
||||
to.setId(View.generateViewId());
|
||||
to.addView(toLangLabel);
|
||||
to.addView(toSelect);
|
||||
RelativeLayout.LayoutParams toParams =
|
||||
new RelativeLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
|
||||
toParams.setMarginStart(30);
|
||||
toParams.addRule(RelativeLayout.BELOW, from.getId());
|
||||
|
||||
// Layout
|
||||
RelativeLayout layout = new RelativeLayout(this);
|
||||
layout.addView(from, fromParams);
|
||||
layout.addView(to, toParams);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeTab(TabSession session) {
|
||||
mTabSessionManager.closeSession(session);
|
||||
|
@ -1918,6 +2016,8 @@ public class GeckoViewActivity extends AppCompatActivity
|
|||
Log.i(LOGTAG, "Starting to load page at " + url);
|
||||
Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + " - page load start");
|
||||
mCb.clearCounters();
|
||||
mExpectedTranslate = false;
|
||||
mTranslateRestore = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2545,6 +2645,30 @@ public class GeckoViewActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
private class ExampleTranslationsSessionDelegate
|
||||
implements TranslationsController.SessionTranslation.Delegate {
|
||||
@Override
|
||||
public void onOfferTranslate(@NonNull GeckoSession session) {
|
||||
Log.i(LOGTAG, "onOfferTranslate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpectedTranslate(@NonNull GeckoSession session) {
|
||||
Log.i(LOGTAG, "onExpectedTranslate");
|
||||
mExpectedTranslate = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTranslationStateChange(
|
||||
@NonNull GeckoSession session,
|
||||
@Nullable TranslationsController.SessionTranslation.TranslationState translationState) {
|
||||
Log.i(LOGTAG, "onTranslationStateChange");
|
||||
if (translationState.detectedLanguages != null) {
|
||||
mDetectedLanguage = translationState.detectedLanguages.docLangTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ExampleMediaSessionDelegate implements MediaSession.Delegate {
|
||||
private final Activity mActivity;
|
||||
|
||||
|
|
|
@ -18,5 +18,7 @@
|
|||
<item android:title="Create Shopping Analysis" android:id="@+id/create_shopping_analysis"/>
|
||||
<item android:title="Get Shopping Analysis Status" android:id="@+id/get_shopping_analysis_status"/>
|
||||
<item android:title="Poll Until Analysis Completed" android:id="@+id/poll_shopping_analysis_status"/>
|
||||
<item android:title="@string/translate" android:id="@+id/translate"/>
|
||||
<item android:title="@string/translate_restore" android:id="@+id/translate_restore"/>
|
||||
<item android:title="@string/settings" android:id="@+id/settings" app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
|
@ -52,6 +52,11 @@
|
|||
<string name="addon_uri">WebExtension xpi URL</string>
|
||||
<string name="save_pdf">Save as PDF</string>
|
||||
<string name="print_page">Print Page</string>
|
||||
<string name="translate">Translate</string>
|
||||
<string name="translate_restore">Restore to Original</string>
|
||||
<string name="translate_language_from_hint">From Language</string>
|
||||
<string name="translate_language_to_hint">To Language</string>
|
||||
<string name="translate_action">Translate</string>
|
||||
|
||||
# Preferences
|
||||
<string name="key_tracking_protection">tracking_protection</string>
|
||||
|
|
|
@ -12,6 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
|
||||
LoadURIDelegate: "resource://gre/modules/LoadURIDelegate.sys.mjs",
|
||||
isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
|
||||
TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "ReferrerInfo", () =>
|
||||
|
@ -651,7 +652,7 @@ export class GeckoViewNavigation extends GeckoViewModule {
|
|||
isTopLevel: aWebProgress.isTopLevel,
|
||||
permissions,
|
||||
};
|
||||
|
||||
lazy.TranslationsParent.onLocationChange(this.browser);
|
||||
this.eventDispatcher.sendRequest(message);
|
||||
|
||||
this.isProductURL(aLocationURI);
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/* 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/. */
|
||||
|
||||
import { GeckoViewModule } from "resource://gre/modules/GeckoViewModule.sys.mjs";
|
||||
|
||||
export class GeckoViewTranslations extends GeckoViewModule {
|
||||
onInit() {
|
||||
debug`onInit`;
|
||||
this.registerListener([
|
||||
"GeckoView:Translations:Translate",
|
||||
"GeckoView:Translations:RestorePage",
|
||||
]);
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
debug`onEnable`;
|
||||
this.window.addEventListener("TranslationsParent:OfferTranslation", this);
|
||||
this.window.addEventListener("TranslationsParent:LanguageState", this);
|
||||
}
|
||||
|
||||
onDisable() {
|
||||
debug`onDisable`;
|
||||
this.window.removeEventListener(
|
||||
"TranslationsParent:OfferTranslation",
|
||||
this
|
||||
);
|
||||
this.window.removeEventListener("TranslationsParent:LanguageState", this);
|
||||
}
|
||||
|
||||
onEvent(aEvent, aData, aCallback) {
|
||||
debug`onEvent: event=${aEvent}, data=${aData}`;
|
||||
switch (aEvent) {
|
||||
case "GeckoView:Translations:Translate":
|
||||
const { fromLanguage, toLanguage } = aData;
|
||||
try {
|
||||
this.getActor("Translations").translate(fromLanguage, toLanguage);
|
||||
aCallback.onSuccess();
|
||||
} catch (error) {
|
||||
// Bug 1853055 will add named error states.
|
||||
aCallback.onError(`Could not translate: ${error}`);
|
||||
}
|
||||
break;
|
||||
case "GeckoView:Translations:RestorePage":
|
||||
try {
|
||||
this.getActor("Translations").restorePage();
|
||||
aCallback.onSuccess();
|
||||
} catch (error) {
|
||||
// Bug 1853055 will add named error states.
|
||||
aCallback.onError(`Could not restore page: ${error}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(aEvent) {
|
||||
debug`handleEvent: ${aEvent.type}`;
|
||||
switch (aEvent.type) {
|
||||
case "TranslationsParent:OfferTranslation":
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:Translations:Offer",
|
||||
});
|
||||
break;
|
||||
case "TranslationsParent:LanguageState":
|
||||
this.eventDispatcher.sendRequest({
|
||||
type: "GeckoView:Translations:StateChange",
|
||||
data: aEvent.detail,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const { debug, warn } = GeckoViewTranslations.initLogging(
|
||||
"GeckoViewTranslations"
|
||||
);
|
|
@ -33,6 +33,7 @@ EXTRA_JS_MODULES += [
|
|||
"GeckoViewTab.sys.mjs",
|
||||
"GeckoViewTelemetry.sys.mjs",
|
||||
"GeckoViewTestUtils.sys.mjs",
|
||||
"GeckoViewTranslations.sys.mjs",
|
||||
"GeckoViewUtils.sys.mjs",
|
||||
"GeckoViewWebExtension.sys.mjs",
|
||||
"LoadURIDelegate.sys.mjs",
|
||||
|
|
|
@ -433,11 +433,17 @@ export class TranslationsParent extends JSWindowActorParent {
|
|||
}
|
||||
|
||||
// Only offer the translation if it's still the current page.
|
||||
if (
|
||||
documentURI.spec ===
|
||||
this.browsingContext.topChromeWindow.gBrowser.selectedBrowser.documentURI
|
||||
.spec
|
||||
) {
|
||||
var isCurrentPage = false;
|
||||
if (AppConstants.platform !== "android") {
|
||||
isCurrentPage =
|
||||
documentURI.spec ===
|
||||
this.browsingContext.topChromeWindow.gBrowser.selectedBrowser
|
||||
.documentURI.spec;
|
||||
} else {
|
||||
// In Android, the active window is the active tab.
|
||||
isCurrentPage = documentURI.spec === browser.documentURI.spec;
|
||||
}
|
||||
if (isCurrentPage) {
|
||||
lazy.console.log(
|
||||
"maybeOfferTranslations - Offering a translation",
|
||||
documentURI.spec,
|
||||
|
|
Загрузка…
Ссылка в новой задаче