Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
TranslationsDocument:
"chrome://global/content/translations/translations-document.sys.mjs",
LRUCache:
"chrome://global/content/translations/translations-document.sys.mjs",
});
/**
* This file is extremely sensitive to memory size and performance!
*/
export class TranslationsChild extends JSWindowActorChild {
/**
* @type {TranslationsDocument | null}
*/
#translatedDoc = null;
get translatedDoc() {
return this.#translatedDoc;
}
/**
* This cache is shared across TranslationsChild instances. This means
* that it will be shared across multiple page loads in the same origin.
*
* @type {LRUCache | null}
*/
static #translationsCache = null;
#isDestroyed = false;
handleEvent(event) {
if (this.#isDestroyed) {
return;
}
if (event.type === "DOMContentLoaded") {
this.sendAsyncMessage("Translations:DOMContentLoaded", {
htmlLangAttribute: this.document.documentElement.lang,
});
} else if (event.type === "load") {
this.sendAsyncMessage("Translations:Load");
}
}
didDestroy() {
this.#isDestroyed = true;
this.#translatedDoc?.destroy();
this.#translatedDoc = null;
}
addProfilerMarker(message, startTime) {
ChromeUtils.addProfilerMarker(
"TranslationsChild",
{
innerWindowId: this.contentWindow?.windowGlobalChild.innerWindowId,
startTime,
},
message
);
}
async receiveMessage({ name, data }) {
if (this.#isDestroyed) {
return undefined;
}
switch (name) {
case "Translations:FindBarOpen": {
this.#translatedDoc?.enterContentEagerTranslationsMode();
return undefined;
}
case "Translations:FindBarClose": {
this.#translatedDoc?.enterLazyTranslationsMode();
return undefined;
}
case "Translations:ExtractPageText": {
const { document } = this;
if (!document) {
return "";
}
const { sufficientLength } = data;
const encoder = Cu.createDocumentEncoder("text/plain");
encoder.init(
document,
"text/plain",
Ci.nsIDocumentEncoder.OutputBodyOnly |
Ci.nsIDocumentEncoder.SkipInvisibleContent |
Ci.nsIDocumentEncoder.AllowCrossShadowBoundary |
Ci.nsIDocumentEncoder.OutputForPlainTextClipboardCopy |
Ci.nsIDocumentEncoder.OutputDisallowLineBreaking |
Ci.nsIDocumentEncoder.OutputDropInvisibleBreak |
Ci.nsIDocumentEncoder.OutputLFLineBreak
);
return encoder.encodeToStringWithMaxLength(sufficientLength);
}
case "Translations:TranslatePage": {
if (this.#translatedDoc?.engineStatus === "error") {
this.#translatedDoc.destroy();
this.#translatedDoc = null;
}
if (this.#translatedDoc) {
console.error("This page was already translated.");
return undefined;
}
const { isFindBarOpen, languagePair, port } = data;
if (
!TranslationsChild.#translationsCache ||
!TranslationsChild.#translationsCache.matches(languagePair)
) {
TranslationsChild.#translationsCache = new lazy.LRUCache(
languagePair
);
}
this.#translatedDoc = new lazy.TranslationsDocument(
this.document,
languagePair.sourceLanguage,
languagePair.targetLanguage,
this.contentWindow.windowGlobalChild.innerWindowId,
port,
() => this.sendAsyncMessage("Translations:RequestPort"),
() => this.sendAsyncMessage("Translations:ReportFirstVisibleChange"),
TranslationsChild.#translationsCache,
isFindBarOpen
);
return undefined;
}
case "Translations:GetDocumentElementLang": {
return this.document.documentElement.lang;
}
case "Translations:IsDocumentReady": {
const state = this.document.readyState;
return state === "interactive" || state === "complete";
}
case "Translations:AcquirePort": {
this.addProfilerMarker("Acquired a port, resuming translations");
this.#translatedDoc.acquirePort(data.port);
return undefined;
}
default:
throw new Error("Unknown message.", name);
}
}
}