diff options
Diffstat (limited to 'browser/components/translation/TranslationChild.jsm')
-rw-r--r-- | browser/components/translation/TranslationChild.jsm | 163 |
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; + } +} |