summaryrefslogtreecommitdiffstats
path: root/browser/components/translation/TranslationChild.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/translation/TranslationChild.jsm')
-rw-r--r--browser/components/translation/TranslationChild.jsm163
1 files changed, 163 insertions, 0 deletions
diff --git a/browser/components/translation/TranslationChild.jsm b/browser/components/translation/TranslationChild.jsm
new file mode 100644
index 0000000000..80a4c48661
--- /dev/null
+++ b/browser/components/translation/TranslationChild.jsm
@@ -0,0 +1,163 @@
+/* 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";
+
+var EXPORTED_SYMBOLS = ["TranslationChild"];
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ LanguageDetector:
+ "resource://gre/modules/translation/LanguageDetector.sys.mjs",
+});
+
+const STATE_OFFER = 0;
+const STATE_TRANSLATED = 2;
+const STATE_ERROR = 3;
+
+class TranslationChild extends JSWindowActorChild {
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "pageshow":
+ // We are only listening to pageshow events.
+ let content = this.contentWindow;
+ if (!content.detectedLanguage) {
+ return;
+ }
+
+ let data = {};
+ let trDoc = content.translationDocument;
+ if (trDoc) {
+ data.state = trDoc.translationError ? STATE_ERROR : STATE_TRANSLATED;
+ data.translatedFrom = trDoc.translatedFrom;
+ data.translatedTo = trDoc.translatedTo;
+ data.originalShown = trDoc.originalShown;
+ } else {
+ data.state = STATE_OFFER;
+ data.originalShown = true;
+ }
+ data.detectedLanguage = content.detectedLanguage;
+
+ this.sendAsyncMessage("Translation:DocumentState", data);
+ break;
+
+ case "load":
+ this.checkForTranslation();
+ break;
+ }
+ }
+
+ checkForTranslation() {
+ let url = String(this.document.location);
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
+ return;
+ }
+
+ let content = this.contentWindow;
+ if (content.detectedLanguage) {
+ return;
+ }
+
+ // Grab a 60k sample of text from the page.
+ let encoder = Cu.createDocumentEncoder("text/plain");
+ encoder.init(this.document, "text/plain", encoder.SkipInvisibleContent);
+ let string = encoder.encodeToStringWithMaxLength(60 * 1024);
+
+ // Language detection isn't reliable on very short strings.
+ if (string.length < 100) {
+ return;
+ }
+
+ lazy.LanguageDetector.detectLanguage(string).then(result => {
+ // Bail if we're not confident.
+ if (!result.confident) {
+ return;
+ }
+
+ // The window might be gone by now.
+ if (Cu.isDeadWrapper(content)) {
+ return;
+ }
+
+ content.detectedLanguage = result.language;
+
+ let data = {
+ state: STATE_OFFER,
+ originalShown: true,
+ detectedLanguage: result.language,
+ };
+ this.sendAsyncMessage("Translation:DocumentState", data);
+ });
+ }
+
+ async doTranslation(aFrom, aTo) {
+ var { TranslationDocument } = ChromeUtils.import(
+ "resource:///modules/translation/TranslationDocument.jsm"
+ );
+
+ // If a TranslationDocument already exists for this document, it should
+ // be used instead of creating a new one so that we can use the original
+ // content of the page for the new translation instead of the newly
+ // translated text.
+ let translationDocument =
+ this.contentWindow.translationDocument ||
+ new TranslationDocument(this.document);
+
+ let engine = Services.prefs.getCharPref("browser.translation.engine");
+ let importScope = ChromeUtils.import(
+ `resource:///modules/translation/${engine}Translator.jsm`
+ );
+ let translator = new importScope[engine + "Translator"](
+ translationDocument,
+ aFrom,
+ aTo
+ );
+
+ this.contentWindow.translationDocument = translationDocument;
+ translationDocument.translatedFrom = aFrom;
+ translationDocument.translatedTo = aTo;
+ translationDocument.translationError = false;
+
+ let result;
+ try {
+ let translateResult = await translator.translate();
+
+ result = {
+ characterCount: translateResult.characterCount,
+ from: aFrom,
+ to: aTo,
+ success: true,
+ };
+
+ translationDocument.showTranslation();
+ return result;
+ } catch (ex) {
+ translationDocument.translationError = true;
+ result = { success: false };
+ if (ex == "unavailable") {
+ result.unavailable = true;
+ }
+ }
+
+ return result;
+ }
+
+ receiveMessage(aMessage) {
+ switch (aMessage.name) {
+ case "Translation:TranslateDocument": {
+ return this.doTranslation(aMessage.data.from, aMessage.data.to);
+ }
+
+ case "Translation:ShowOriginal":
+ this.contentWindow.translationDocument.showOriginal();
+ break;
+
+ case "Translation:ShowTranslation":
+ this.contentWindow.translationDocument.showTranslation();
+ break;
+ }
+
+ return undefined;
+ }
+}