Iframe support (#148)
* Iframe support * Fix linting errors * Disable legacy extension * Process translations in the top frame * Do not load translation scripts to all frames * Add tests for iframe * Fix command name * Prevent beforeunload dialogue * Integrate QE telemetry * Limit precision of QE metrics * Do not report QE metrics if it's disabled * Fix tab telemetry clearing * Fix unloading event * Increase waiting time * Fix closing notification * Report initial QE enabled state * Increase test timeout
This commit is contained in:
Родитель
25d7060880
Коммит
f8182d85fd
|
@ -1,3 +1,4 @@
|
|||
node_modules
|
||||
web-ext-artifacts
|
||||
extension/settings.js
|
||||
extension/settings.js
|
||||
gecko
|
|
@ -1,4 +1,4 @@
|
|||
/* global LanguageDetection, browser, PingSender, loadFastText, FastText */
|
||||
/* global LanguageDetection, browser, PingSender, BERGAMOT_VERSION_FULL, Telemetry, loadFastText, FastText */
|
||||
|
||||
/*
|
||||
* we need the background script in order to have full access to the
|
||||
|
@ -15,6 +15,24 @@ let languageDetection = null;
|
|||
|
||||
// as soon we load, we should turn off the legacy prefs to avoid UI conflicts
|
||||
browser.experiments.translationbar.switchOnPreferences();
|
||||
let telemetryByTab = new Map();
|
||||
|
||||
const init = async () => {
|
||||
cachedEnvInfo = await browser.experiments.telemetryEnvironment.getFxTelemetryMetrics();
|
||||
telemetryByTab.forEach(t => t.environment(cachedEnvInfo));
|
||||
}
|
||||
|
||||
const getTelemetry = tabId => {
|
||||
if (!telemetryByTab.has(tabId)) {
|
||||
let telemetry = new Telemetry(pingSender);
|
||||
telemetryByTab.set(tabId, telemetry);
|
||||
telemetry.versions(browser.runtime.getManifest().version, "?", BERGAMOT_VERSION_FULL);
|
||||
if (cachedEnvInfo) {
|
||||
telemetry.environment(cachedEnvInfo);
|
||||
}
|
||||
}
|
||||
return telemetryByTab.get(tabId);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
const messageListener = async function(message, sender) {
|
||||
|
@ -115,30 +133,82 @@ const messageListener = async function(message, sender) {
|
|||
);
|
||||
|
||||
break;
|
||||
case "translate":
|
||||
// propagate translation message from iframe to top frame
|
||||
message.frameId = sender.frameId;
|
||||
browser.tabs.sendMessage(
|
||||
message.tabId,
|
||||
message,
|
||||
{ frameId: 0 }
|
||||
);
|
||||
break;
|
||||
case "translationComplete":
|
||||
// propagate translation message from top frame to the source frame
|
||||
browser.tabs.sendMessage(
|
||||
message.tabId,
|
||||
message,
|
||||
{ frameId: message.translationMessage.frameId }
|
||||
);
|
||||
break;
|
||||
case "displayOutboundTranslation":
|
||||
// propagate "display outbound" command from top frame to other frames
|
||||
browser.tabs.sendMessage(
|
||||
message.tabId,
|
||||
message
|
||||
);
|
||||
break;
|
||||
case "recordTelemetry":
|
||||
|
||||
case "loadTelemetryInfo":
|
||||
if (cachedEnvInfo === null) {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
cachedEnvInfo = await browser.experiments.telemetryEnvironment.getFxTelemetryMetrics();
|
||||
/*
|
||||
* if the event was to close the infobar, we notify the api as well
|
||||
* we don't need another redundant loop by informing the mediator,
|
||||
* to then inform this script again
|
||||
*/
|
||||
if (message.name === "closed") {
|
||||
browser.experiments.translationbar.closeInfobar(message.tabId);
|
||||
}
|
||||
browser.tabs.sendMessage(sender.tab.id, { command: "telemetryInfoLoaded", env: cachedEnvInfo })
|
||||
|
||||
getTelemetry(message.tabId).record(message.type, message.category, message.name, message.value);
|
||||
break;
|
||||
|
||||
case "sendPing":
|
||||
pingSender.submit(message.pingName, message.data)
|
||||
.catch(e => console.error(`Telemetry: ping submission has failed: ${e}`));
|
||||
break;
|
||||
case "reportTranslationStats": {
|
||||
let wps = getTelemetry(message.tabId).addAndGetTranslationTimeStamp(message.numWords, message.engineTimeElapsed);
|
||||
browser.tabs.sendMessage(
|
||||
message.tabId,
|
||||
{
|
||||
command: "updateStats",
|
||||
tabId: message.tabId,
|
||||
wps
|
||||
}
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "translationRequested":
|
||||
case "reportOutboundStats":
|
||||
getTelemetry(message.tabId).addOutboundTranslation(message.textAreaId, message.text);
|
||||
break;
|
||||
|
||||
case "reportQeStats":
|
||||
getTelemetry(message.tabId).addQualityEstimation(message.wordScores, message.sentScores);
|
||||
break;
|
||||
|
||||
case "submitPing":
|
||||
getTelemetry(message.tabId).submit();
|
||||
telemetryByTab.delete(message.tabId);
|
||||
break;
|
||||
|
||||
case "translationRequested":
|
||||
// requested for translation received. let's inform the mediator
|
||||
browser.tabs.sendMessage(
|
||||
message.tabId,
|
||||
{ command: "translationRequested",
|
||||
tabId: message.tabId,
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
withOutboundTranslation: message.withOutboundTranslation,
|
||||
withQualityEstimation: message.withQualityEstimation }
|
||||
{
|
||||
command: "translationRequested",
|
||||
tabId: message.tabId,
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
withOutboundTranslation: message.withOutboundTranslation,
|
||||
withQualityEstimation: message.withQualityEstimation
|
||||
}
|
||||
);
|
||||
break;
|
||||
case "updateProgress":
|
||||
|
@ -161,28 +231,6 @@ const messageListener = async function(message, sender) {
|
|||
from: message.to, // we switch the requests directions here
|
||||
to: message.from }
|
||||
);
|
||||
break;
|
||||
case "onInfobarEvent":
|
||||
|
||||
/*
|
||||
* inform the mediator that a UI event occurred in Infobar
|
||||
*/
|
||||
browser.tabs.sendMessage(
|
||||
message.tabId,
|
||||
{ command: "onInfobarEvent",
|
||||
tabId: message.tabId,
|
||||
name: message.name }
|
||||
);
|
||||
|
||||
/*
|
||||
* if the event was to close the infobar, we notify the api as well
|
||||
* we don't need another redundant loop by informing the mediator,
|
||||
* to then inform this script again
|
||||
*/
|
||||
if (message.name === "closed") {
|
||||
browser.experiments.translationbar.closeInfobar(message.tabId);
|
||||
}
|
||||
|
||||
break;
|
||||
case "displayStatistics":
|
||||
|
||||
|
@ -206,6 +254,7 @@ const messageListener = async function(message, sender) {
|
|||
|
||||
browser.runtime.onMessage.addListener(messageListener);
|
||||
browser.experiments.translationbar.onTranslationRequest.addListener(messageListener);
|
||||
init().catch(error => console.error("bgScript initialization failed: ", error.message));
|
||||
|
||||
// loads fasttext (language detection) wasm module and model
|
||||
fetch(browser
|
||||
|
|
|
@ -59,17 +59,17 @@ class Translation {
|
|||
payload: null
|
||||
});
|
||||
break;
|
||||
case "onError":
|
||||
case "reportError":
|
||||
this.mediator.contentScriptsMessageListener(this, {
|
||||
command: "onError",
|
||||
command: "reportError",
|
||||
payload: translationMessage.data[1]
|
||||
});
|
||||
break;
|
||||
|
||||
case "onModelEvent":
|
||||
case "reportPerformanceTimespan":
|
||||
this.mediator.contentScriptsMessageListener(this, {
|
||||
command: "onModelEvent",
|
||||
payload: { type: translationMessage.data[1], timeMs: translationMessage.data[2] }
|
||||
command: "reportPerformanceTimespan",
|
||||
payload: { metric: translationMessage.data[1], timeMs: translationMessage.data[2] }
|
||||
});
|
||||
break;
|
||||
|
||||
|
@ -135,6 +135,7 @@ class Translation {
|
|||
sourceParagraph,
|
||||
type,
|
||||
tabId,
|
||||
frameId,
|
||||
navigatorLanguage,
|
||||
pageLanguage,
|
||||
attrId,
|
||||
|
@ -173,6 +174,7 @@ class Translation {
|
|||
break;
|
||||
}
|
||||
translationMessage.tabId = tabId;
|
||||
translationMessage.frameId = frameId;
|
||||
translationMessage.type = type;
|
||||
translationMessage.attrId = attrId;
|
||||
translationMessage.withOutboundTranslation = withOutboundTranslation;
|
||||
|
|
|
@ -7,6 +7,7 @@ class TranslationMessage {
|
|||
this.sourceLanguage = null;
|
||||
this.targetLanguage = null;
|
||||
this.tabId = null;
|
||||
this.frameId = null;
|
||||
this.type = null;
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@ class TranslationHelper {
|
|||
engineRegistry.bergamotTranslatorWasm.sha256
|
||||
);
|
||||
if (!wasmArrayBuffer) {
|
||||
postMessage(["onError", "engine_download"]);
|
||||
postMessage(["reportError", "engine_download"]);
|
||||
console.log("Error loading engine from cache or web.");
|
||||
return;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class TranslationHelper {
|
|||
this.WasmEngineModule = Module;
|
||||
} catch (e) {
|
||||
console.log("Error loading wasm module:", e);
|
||||
postMessage(["onError", "engine_load"]);
|
||||
postMessage(["reportError", "engine_load"]);
|
||||
postMessage(["updateProgress", "errorLoadingWasm"]);
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ class TranslationHelper {
|
|||
timeElapsed
|
||||
]);
|
||||
} catch (e) {
|
||||
postMessage(["onError", "translation"]);
|
||||
postMessage(["reportError", "translation"]);
|
||||
postMessage(["updateProgress", "translationLoadedWithErrors"]);
|
||||
console.error("Translation error: ", e)
|
||||
throw e;
|
||||
|
@ -263,14 +263,14 @@ class TranslationHelper {
|
|||
let finish = Date.now();
|
||||
console.log(`Model '${sourceLanguage}${targetLanguage}' successfully constructed. Time taken: ${(finish - start) / 1000} secs`);
|
||||
postMessage([
|
||||
"onModelEvent",
|
||||
"loaded",
|
||||
"reportPerformanceTimespan",
|
||||
"model_load_time_num",
|
||||
finish-start
|
||||
]);
|
||||
|
||||
} catch (error) {
|
||||
console.log(`Model '${sourceLanguage}${targetLanguage}' construction failed: '${error.message} - ${error.stack}'`);
|
||||
postMessage(["onError", "model_load"]);
|
||||
postMessage(["reportError", "model_load"]);
|
||||
postMessage(["updateProgress", "errorLoadingWasm"]);
|
||||
return;
|
||||
}
|
||||
|
@ -367,13 +367,13 @@ class TranslationHelper {
|
|||
const shortListBuffer = downloadedBuffers[1];
|
||||
if (!modelBuffer || !shortListBuffer) {
|
||||
console.log("Error loading models from cache or web (models)");
|
||||
postMessage(["onError", "model_download"]);
|
||||
postMessage(["reportError", "model_download"]);
|
||||
throw new Error("Error loading models from cache or web (models)");
|
||||
}
|
||||
const vocabAsArrayBuffer = await this.getItemFromCacheOrWeb(vocabFile, vocabFileSize, vocabFileChecksum);
|
||||
if (!vocabAsArrayBuffer) {
|
||||
console.log("Error loading models from cache or web (vocab)");
|
||||
postMessage(["onError", "model_download"]);
|
||||
postMessage(["reportError", "model_download"]);
|
||||
throw new Error("Error loading models from cache or web (vocab)");
|
||||
}
|
||||
const downloadedVocabBuffers = [];
|
||||
|
@ -382,8 +382,8 @@ class TranslationHelper {
|
|||
let finish = Date.now();
|
||||
console.log(`Total Download time for all files of '${languagePair}': ${(finish - start) / 1000} secs`);
|
||||
postMessage([
|
||||
"onModelEvent",
|
||||
"downloaded",
|
||||
"reportPerformanceTimespan",
|
||||
"model_download_time_num",
|
||||
finish-start
|
||||
]);
|
||||
|
||||
|
@ -583,7 +583,7 @@ class TranslationHelper {
|
|||
return listTranslatedText;
|
||||
} catch (e) {
|
||||
console.error("Error in translation engine ", e)
|
||||
postMessage(["onError", "marian"]);
|
||||
postMessage(["reportError", "marian"]);
|
||||
postMessage(["updateProgress", "translationLoadedWithErrors"]);
|
||||
throw e; // to do: Should we re-throw?
|
||||
} finally {
|
||||
|
|
|
@ -26,11 +26,14 @@
|
|||
"persistent": true,
|
||||
"scripts": [
|
||||
"settings.js",
|
||||
"controller/translation/bergamotTranslatorVersion.js",
|
||||
"model/telemetry/schema.js",
|
||||
"model/telemetry/PingSender.js",
|
||||
"controller/languageDetection/LanguageDetection.js",
|
||||
"controller/languageDetection/fasttext_wasm.js",
|
||||
"controller/languageDetection/fasttext.js",
|
||||
"model/telemetry/Metrics.js",
|
||||
"model/telemetry/Telemetry.js",
|
||||
"view/js/TranslationNotificationManager.js",
|
||||
"controller/backgroundScript.js"
|
||||
]
|
||||
|
@ -42,23 +45,29 @@
|
|||
],
|
||||
"js": [
|
||||
"settings.js",
|
||||
"model/modelRegistry.js",
|
||||
"controller/languageDetection/LanguageDetection.js",
|
||||
"model/Queue.js",
|
||||
"controller/translation/Translation.js",
|
||||
"controller/translation/TranslationMessage.js",
|
||||
"controller/translation/translationWorker.js",
|
||||
"controller/translation/bergamotTranslatorVersion.js",
|
||||
"view/js/OutboundTranslation.js",
|
||||
"view/js/InPageTranslation.js",
|
||||
"model/telemetry/Telemetry.js",
|
||||
"model/telemetry/Metrics.js",
|
||||
"model/telemetry/schema.js",
|
||||
"model/modelRegistry.js",
|
||||
"mediator.js"
|
||||
],
|
||||
"css": [
|
||||
"view/static/outboundTranslation.css"
|
||||
],
|
||||
"all_frames": true,
|
||||
"run_at": "document_idle"
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"js": [
|
||||
"model/Queue.js",
|
||||
"controller/translation/Translation.js",
|
||||
"controller/translation/TranslationMessage.js",
|
||||
"controller/translation/translationWorker.js"
|
||||
],
|
||||
"all_frames": false,
|
||||
"run_at": "document_idle"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -4,55 +4,66 @@
|
|||
*/
|
||||
|
||||
/* global LanguageDetection, OutboundTranslation, Translation , browser,
|
||||
InPageTranslation, browser, Telemetry, BERGAMOT_VERSION_FULL */
|
||||
InPageTranslation, browser */
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
class Mediator {
|
||||
|
||||
constructor() {
|
||||
this.messagesSenderLookupTable = new Map();
|
||||
this.translation = null;
|
||||
this.translationsCounter = 0;
|
||||
this.languageDetection = new LanguageDetection();
|
||||
this.inPageTranslation = new InPageTranslation(this);
|
||||
this.telemetry = new Telemetry((pingName, data) => browser.runtime.sendMessage({
|
||||
command: "sendPing",
|
||||
pingName,
|
||||
data
|
||||
}));
|
||||
this.telemetry.versions(browser.runtime.getManifest().version, "?", BERGAMOT_VERSION_FULL);
|
||||
this.outboundTranslation = null;
|
||||
browser.runtime.onMessage.addListener(this.bgScriptsMessageListener.bind(this));
|
||||
this.translationBarDisplayed = false;
|
||||
this.statsMode = false;
|
||||
// if we are in the protected mochitest page, we flag it.
|
||||
if (window.location.href ===
|
||||
"https://example.com/browser/browser/extensions/translations/test/browser/browser_translation_test.html") {
|
||||
if ((window.location.href ===
|
||||
"https://example.com/browser/browser/extensions/translations/test/browser/browser_translation_test.html") ||
|
||||
(window.location.href ===
|
||||
"https://example.com/browser/browser/extensions/translations/test/browser/frame.html")) {
|
||||
this.isMochitest = true;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
browser.runtime.sendMessage({ command: "monitorTabLoad" });
|
||||
browser.runtime.sendMessage({ command: "loadTelemetryUploadPref" });
|
||||
browser.runtime.sendMessage({ command: "loadTelemetryInfo" });
|
||||
if (window.self === window.top) { // is main frame
|
||||
browser.runtime.sendMessage({ command: "monitorTabLoad" });
|
||||
}
|
||||
}
|
||||
|
||||
// main entrypoint to handle the extension's load
|
||||
start(tabId) {
|
||||
this.tabId = tabId;
|
||||
|
||||
// request the language detection class to extract a page's snippet
|
||||
this.languageDetection.extractPageContent();
|
||||
if (window.self === window.top) { // is main frame
|
||||
// request the language detection class to extract a page's snippet
|
||||
this.languageDetection.extractPageContent();
|
||||
|
||||
/*
|
||||
* request the background script to detect the page's language and
|
||||
* determine if the infobar should be displayed
|
||||
*/
|
||||
browser.runtime.sendMessage({
|
||||
command: "detectPageLanguage",
|
||||
languageDetection: this.languageDetection
|
||||
})
|
||||
/*
|
||||
* request the background script to detect the page's language and
|
||||
* determine if the infobar should be displayed
|
||||
*/
|
||||
browser.runtime.sendMessage({
|
||||
command: "detectPageLanguage",
|
||||
languageDetection: this.languageDetection
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
recordTelemetry(type, category, name, value) {
|
||||
browser.runtime.sendMessage({
|
||||
command: "recordTelemetry",
|
||||
tabId: this.tabId,
|
||||
type,
|
||||
category,
|
||||
name,
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
determineIfTranslationisRequired() {
|
||||
|
||||
/*
|
||||
|
@ -73,17 +84,28 @@ class Mediator {
|
|||
*/
|
||||
if (this.translationBarDisplayed) return;
|
||||
|
||||
// is iframe
|
||||
if ((window.self !== window.top) && this.languageDetection.shouldDisplayTranslation()) {
|
||||
this.translationBarDisplayed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// is main frame
|
||||
const pageLang = this.languageDetection.pageLanguage;
|
||||
const navLang = this.languageDetection.navigatorLanguage;
|
||||
this.telemetry.langPair(pageLang, navLang);
|
||||
this.telemetry.langMismatch();
|
||||
window.onbeforeunload = () => {
|
||||
browser.runtime.sendMessage({
|
||||
command: "reportClosedInfobar",
|
||||
tabId: this.tabId
|
||||
});
|
||||
this.telemetry.pageClosed();
|
||||
}
|
||||
this.recordTelemetry("string", "metadata", "from_lang", pageLang);
|
||||
this.recordTelemetry("string", "metadata", "to_lang", navLang);
|
||||
this.recordTelemetry("counter", "service", "lang_mismatch");
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
|
||||
/*
|
||||
* it is recommended to use visibilitychange event for this use case,
|
||||
* but it triggers some errors because of communication with bgScript, so let's use beforeunload for now
|
||||
*/
|
||||
browser.runtime.sendMessage({ command: "reportClosedInfobar", tabId: this.tabId });
|
||||
browser.runtime.sendMessage({ command: "submitPing", tabId: this.tabId });
|
||||
});
|
||||
|
||||
if (this.languageDetection.shouldDisplayTranslation()) {
|
||||
// request the backgroundscript to display the translationbar
|
||||
|
@ -95,7 +117,7 @@ class Mediator {
|
|||
// create the translation object
|
||||
this.translation = new Translation(this);
|
||||
} else {
|
||||
this.telemetry.langNotSupported();
|
||||
this.recordTelemetry("counter", "service", "not_supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,32 +126,31 @@ class Mediator {
|
|||
* handles all requests received from the content scripts
|
||||
* (views and controllers)
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
contentScriptsMessageListener(sender, message) {
|
||||
switch (message.command) {
|
||||
case "translate":
|
||||
if (!this.translation) {
|
||||
this.translation = new Translation(this);
|
||||
if (window.self === window.top) {
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
this.translate(message);
|
||||
} else {
|
||||
// pass to the worker in top frame through bgScript
|
||||
browser.runtime.sendMessage({
|
||||
command: "translate",
|
||||
tabId: this.tabId,
|
||||
payload: message.payload
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const translationMessage = this.translation.constructTranslationMessage(
|
||||
message.payload.text,
|
||||
message.payload.type,
|
||||
message.tabId,
|
||||
this.languageDetection.navigatorLanguage,
|
||||
this.languageDetection.pageLanguage,
|
||||
message.payload.attrId,
|
||||
message.payload.withOutboundTranslation,
|
||||
message.payload.withQualityEstimation
|
||||
);
|
||||
this.messagesSenderLookupTable.set(translationMessage.messageID, sender);
|
||||
this.translation.translate(translationMessage);
|
||||
// console.log("new translation message sent:", translationMessage, "msg sender lookuptable size:", this.messagesSenderLookupTable.size);
|
||||
|
||||
this.telemetry.infobarState("outbound_enabled", message.payload.withOutboundTranslation === true);
|
||||
|
||||
if (message.payload.type === "outbound") {
|
||||
this.telemetry.addOutboundTranslation(sender.selectedTextArea, message.payload.text);
|
||||
if (!sender.selectedTextArea.id) sender.selectedTextArea.id = self.crypto.randomUUID();
|
||||
browser.runtime.sendMessage({
|
||||
command: "reportOutboundStats",
|
||||
tabId: this.tabId,
|
||||
textAreaId: sender.selectedTextArea.id,
|
||||
text: message.payload.text
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "translationComplete":
|
||||
|
@ -140,25 +161,26 @@ class Mediator {
|
|||
* in order to route the response back, which can be
|
||||
* OutbountTranslation, InPageTranslation etc....
|
||||
*/
|
||||
|
||||
message.payload[1].forEach(translationMessage => {
|
||||
this.messagesSenderLookupTable.get(translationMessage.messageID)
|
||||
.mediatorNotification(translationMessage);
|
||||
this.messagesSenderLookupTable.delete(translationMessage.messageID);
|
||||
// if this message is originated from another frame, pass it back through bgScript
|
||||
if ((window.self === window.top) && (typeof translationMessage.frameId !== "undefined")) {
|
||||
browser.runtime.sendMessage({
|
||||
command: "translationComplete",
|
||||
tabId: this.tabId,
|
||||
translationMessage
|
||||
});
|
||||
} else {
|
||||
this.updateElements(translationMessage);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const wordsPerSecond = this.telemetry
|
||||
.addAndGetTranslationTimeStamp(message.payload[2][0], message.payload[2][1]);
|
||||
|
||||
if (this.statsMode) {
|
||||
// if the user chose to see stats in the infobar, we display them
|
||||
browser.runtime.sendMessage({
|
||||
command: "updateProgress",
|
||||
progressMessage: browser.i18n.getMessage("statsMessage", wordsPerSecond),
|
||||
tabId: this.tabId
|
||||
});
|
||||
}
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
command: "reportTranslationStats",
|
||||
tabId: this.tabId,
|
||||
numWords: message.payload[2][0],
|
||||
engineTimeElapsed: message.payload[2][1]
|
||||
});
|
||||
// console.log("translation complete rcvd:", message, "msg sender lookuptable size:", this.messagesSenderLookupTable.size);
|
||||
break;
|
||||
case "updateProgress":
|
||||
|
@ -184,43 +206,37 @@ class Mediator {
|
|||
});
|
||||
break;
|
||||
case "displayOutboundTranslation":
|
||||
|
||||
/* display the outboundstranslation widget */
|
||||
this.outboundTranslation = new OutboundTranslation(this);
|
||||
this.outboundTranslation.start(
|
||||
this.localizedNavigatorLanguage,
|
||||
this.localizedPageLanguage
|
||||
);
|
||||
this.startOutbound();
|
||||
// broadcast to all frames through bgScript
|
||||
browser.runtime.sendMessage({
|
||||
command: "displayOutboundTranslation",
|
||||
tabId: this.tabId
|
||||
});
|
||||
break;
|
||||
case "onError":
|
||||
case "reportError":
|
||||
// payload is a metric name from metrics.yaml
|
||||
this.telemetry.error(message.payload);
|
||||
this.recordTelemetry("counter", "errors", message.payload);
|
||||
break;
|
||||
case "viewPortWordsNum":
|
||||
this.telemetry.wordsInViewport(message.payload);
|
||||
case "reportViewPortWordsNum":
|
||||
this.recordTelemetry("quantity", "performance", "word_count_visible_in_viewport", message.payload);
|
||||
break;
|
||||
case "onModelEvent":
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
let metric = null;
|
||||
if (message.payload.type === "downloaded") {
|
||||
metric = "model_download_time_num";
|
||||
} else if (message.payload.type === "loaded") {
|
||||
metric = "model_load_time_num";
|
||||
// start timer when the model is fully loaded
|
||||
this.telemetry.translationStarted();
|
||||
} else {
|
||||
throw new Error(`Unexpected event type: ${message.payload.type}`)
|
||||
}
|
||||
this.telemetry.performanceTime(metric, message.payload.timeMs);
|
||||
case "reportPerformanceTimespan":
|
||||
this.recordTelemetry("timespan", "performance", message.payload.metric, message.payload.timeMs);
|
||||
break;
|
||||
case "onFormsEvent":
|
||||
this.telemetry.formsEvent(message.payload);
|
||||
case "reportFormsEvent":
|
||||
// payload is a metric name from metrics.yaml
|
||||
this.recordTelemetry("event", "forms", message.payload);
|
||||
break;
|
||||
case "reportQeMetrics":
|
||||
this.telemetry.addQualityEstimation(message.payload.wordScores, message.payload.sentScores, false);
|
||||
this.recordTelemetry("boolean", "quality", "is_supervised", false);
|
||||
browser.runtime.sendMessage({
|
||||
command: "reportQeStats",
|
||||
tabId: this.tabId,
|
||||
wordScores: message.payload.wordScores,
|
||||
sentScores: message.payload.sentScores
|
||||
});
|
||||
break;
|
||||
case "domMutation":
|
||||
|
||||
if (this.outboundTranslation) {
|
||||
this.outboundTranslation.updateZIndex(message.payload);
|
||||
}
|
||||
|
@ -229,19 +245,57 @@ class Mediator {
|
|||
}
|
||||
}
|
||||
|
||||
translate(message) {
|
||||
if (!this.translation) {
|
||||
this.translation = new Translation(this);
|
||||
}
|
||||
const translationMessage = this.translation.constructTranslationMessage(
|
||||
message.payload.text,
|
||||
message.payload.type,
|
||||
this.tabId,
|
||||
message.frameId,
|
||||
this.languageDetection.navigatorLanguage,
|
||||
this.languageDetection.pageLanguage,
|
||||
message.payload.attrId,
|
||||
message.payload.withOutboundTranslation,
|
||||
message.payload.withQualityEstimation
|
||||
);
|
||||
this.translation.translate(translationMessage);
|
||||
// console.log("new translation message sent:", translationMessage, "msg sender lookuptable size:", this.messagesSenderLookupTable.size);
|
||||
}
|
||||
|
||||
startOutbound() {
|
||||
if (this.outboundTranslation !== null) return;
|
||||
|
||||
/* display the outboundstranslation widget */
|
||||
this.outboundTranslation = new OutboundTranslation(this);
|
||||
this.outboundTranslation.start(
|
||||
this.localizedNavigatorLanguage,
|
||||
this.localizedPageLanguage
|
||||
);
|
||||
}
|
||||
|
||||
updateElements(translationMessage) {
|
||||
if (translationMessage.type === "inpage") {
|
||||
this.inPageTranslation.mediatorNotification(translationMessage);
|
||||
} else if ((translationMessage.type === "outbound") || (translationMessage.type === "backTranslation")) {
|
||||
this.outboundTranslation.mediatorNotification(translationMessage);
|
||||
} else {
|
||||
throw new Error("Unexpected type: ", translationMessage.type);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* handles all communication received from the background script
|
||||
* and properly delegates the calls to the responsible methods
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
bgScriptsMessageListener(message) {
|
||||
switch (message.command) {
|
||||
case "responseMonitorTabLoad":
|
||||
this.start(message.tabId);
|
||||
break;
|
||||
case "telemetryInfoLoaded":
|
||||
this.telemetry.environment(message.env);
|
||||
break;
|
||||
case "responseDetectPageLanguage":
|
||||
this.languageDetection = Object.assign(new LanguageDetection(), message.languageDetection);
|
||||
this.determineIfTranslationisRequired();
|
||||
|
@ -261,13 +315,28 @@ class Mediator {
|
|||
this.inPageTranslation.start(this.languageDetection.pageLanguage);
|
||||
}
|
||||
break;
|
||||
case "translate":
|
||||
this.translate(message)
|
||||
break
|
||||
case "translationComplete":
|
||||
this.updateElements(message.translationMessage);
|
||||
break;
|
||||
case "displayOutboundTranslation":
|
||||
this.startOutbound();
|
||||
break;
|
||||
case "displayStatistics":
|
||||
this.statsMode = true;
|
||||
document.querySelector("html").setAttribute("x-bergamot-debug", true);
|
||||
break;
|
||||
case "onInfobarEvent":
|
||||
// 'name' is a metric name from metrics.yaml
|
||||
this.telemetry.infobarEvent(message.name);
|
||||
case "updateStats":
|
||||
if (this.statsMode) {
|
||||
// if the user chose to see stats in the infobar, we display them
|
||||
browser.runtime.sendMessage({
|
||||
command: "updateProgress",
|
||||
progressMessage: browser.i18n.getMessage("statsMessage", message.wps),
|
||||
tabId: this.tabId
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "localizedLanguages":
|
||||
this.localizedPageLanguage = message.localizedPageLanguage;
|
||||
|
|
|
@ -87,6 +87,9 @@ class Metrics {
|
|||
if (typeof val !== "string") {
|
||||
throw new Error(`Telemetry: ${category}.${name} must be a string, value: ${val}`)
|
||||
}
|
||||
if (val.length > 100) {
|
||||
this._log(`warning: string ${category}.${name} is longer that 100 character will be truncated`);
|
||||
}
|
||||
for (const pingName of this._getPings(category, name, "string")) {
|
||||
let ping = this._build_ping(pingName);
|
||||
if (!("string" in ping.metrics)) {
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
// eslint-disable-next-line
|
||||
class Telemetry {
|
||||
constructor(submitCallback) {
|
||||
this._client = new Metrics(submitCallback);
|
||||
constructor(pingSender) {
|
||||
this._client = new Metrics((pingName, data) => pingSender.submit(pingName, data));
|
||||
this._totalWords = 0;
|
||||
this._totalEngineMs = 0;
|
||||
this._translationStartTimestamp = null;
|
||||
|
@ -17,13 +17,35 @@ class Telemetry {
|
|||
this._sentScores = [];
|
||||
}
|
||||
|
||||
translationStarted() {
|
||||
this._translationStartTimestamp = window.performance.now();
|
||||
this._updateUsageTime();
|
||||
}
|
||||
record(type, category, name, value) {
|
||||
switch (type) {
|
||||
case "event":
|
||||
this._client.event(category, name);
|
||||
this._updateUsageTime();
|
||||
break;
|
||||
case "counter":
|
||||
this._client.increment(category, name)
|
||||
break;
|
||||
case "string":
|
||||
this._client.string(category, name, value)
|
||||
break;
|
||||
case "quantity":
|
||||
this._client.quantity(category, name, value)
|
||||
break;
|
||||
case "timespan":
|
||||
if (name === "model_load_time_num") {
|
||||
this._translationStartTimestamp = window.performance.now();
|
||||
this._updateUsageTime();
|
||||
}
|
||||
|
||||
pageClosed() {
|
||||
this._client.submit("custom");
|
||||
this._client.timespan(category, name, value);
|
||||
break;
|
||||
case "boolean":
|
||||
this._client.boolean(category, name, value);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Metric type is not supported: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
addAndGetTranslationTimeStamp(numWords, engineTimeElapsed) {
|
||||
|
@ -44,11 +66,11 @@ class Telemetry {
|
|||
return engineWps;
|
||||
}
|
||||
|
||||
addOutboundTranslation(textArea, textToTranslate) {
|
||||
this._otLenthPerTextArea.set(textArea, {
|
||||
chars: textToTranslate.length,
|
||||
words: textToTranslate.trim().split(" ").length
|
||||
});
|
||||
addOutboundTranslation(textAreaId, textToTranslate) {
|
||||
this._otLenthPerTextArea.set(textAreaId, {
|
||||
chars: textToTranslate.length,
|
||||
words: textToTranslate.trim().split(" ").length
|
||||
});
|
||||
let charLengthSum = 0;
|
||||
let wordLengthSum = 0;
|
||||
this._otLenthPerTextArea.forEach(v => {
|
||||
|
@ -61,9 +83,7 @@ class Telemetry {
|
|||
this._updateUsageTime();
|
||||
}
|
||||
|
||||
addQualityEstimation(wordScores, sentScores, isSupervised) {
|
||||
this._client.boolean("quality", "is_supervised", isSupervised);
|
||||
|
||||
addQualityEstimation(wordScores, sentScores) {
|
||||
// scores are log probabilities, convert back to probabilities
|
||||
for (const score of wordScores) this._wordScores.push(Math.exp(score));
|
||||
for (const score of sentScores) this._sentScores.push(Math.exp(score));
|
||||
|
@ -87,9 +107,9 @@ class Telemetry {
|
|||
_calcStats(array) {
|
||||
array.sort();
|
||||
const sum = array.reduce((a, b) => a + b, 0);
|
||||
const avg = (sum / array.length) || 0;
|
||||
const median = array[Math.floor(array.length/2)-1] || 0;
|
||||
const perc90 = array[Math.floor(array.length*0.9)-1] || 0;
|
||||
const avg = ((sum / array.length) || 0).toFixed(3);
|
||||
const median = (array[Math.floor(array.length/2)-1] || 0).toFixed(3);
|
||||
const perc90 = (array[Math.floor(array.length*0.9)-1] || 0).toFixed(3);
|
||||
|
||||
return { avg, median, perc90 }
|
||||
}
|
||||
|
@ -119,45 +139,8 @@ class Telemetry {
|
|||
this._client.string("metadata", "bergamot_translator_version", engineVersion);
|
||||
}
|
||||
|
||||
infobarEvent(name) {
|
||||
this._client.event("infobar", name);
|
||||
this._updateUsageTime();
|
||||
|
||||
/* event corresponds to user action, but boolean value is useful to report the state and to filter */
|
||||
if (name === "outbound_checked") {
|
||||
this.infobarState("outbound_enabled", true);
|
||||
} else if (name === "outbound_unchecked") {
|
||||
this.infobarState("outbound_enabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
infobarState(name, val) {
|
||||
this._client.boolean("infobar", name, val);
|
||||
}
|
||||
|
||||
formsEvent(name) {
|
||||
this._client.event("forms", name);
|
||||
this._updateUsageTime();
|
||||
}
|
||||
|
||||
error(name) {
|
||||
this._client.increment("errors", name);
|
||||
}
|
||||
|
||||
langMismatch() {
|
||||
this._client.increment("service", "lang_mismatch");
|
||||
}
|
||||
|
||||
langNotSupported() {
|
||||
this._client.increment("service", "not_supported");
|
||||
}
|
||||
|
||||
performanceTime(metric, timeMs) {
|
||||
this._client.timespan("performance", metric, timeMs);
|
||||
}
|
||||
|
||||
wordsInViewport(val) {
|
||||
this._client.quantity("performance", "word_count_visible_in_viewport", val);
|
||||
submit() {
|
||||
this._client.submit("custom");
|
||||
}
|
||||
|
||||
_updateUsageTime() {
|
||||
|
|
|
@ -353,7 +353,7 @@ class InPageTranslation {
|
|||
viewPortWordsNum += value.textContent.trim().split(/\s+/).length;
|
||||
}
|
||||
|
||||
this.notifyMediator("viewPortWordsNum", viewPortWordsNum);
|
||||
this.notifyMediator("reportViewPortWordsNum", viewPortWordsNum);
|
||||
// report words in viewport only for initially loaded content
|
||||
this.initialWordsInViewportReported = true;
|
||||
}
|
||||
|
@ -596,7 +596,12 @@ class InPageTranslation {
|
|||
currentNode = nodeIterator.nextNode();
|
||||
}
|
||||
}
|
||||
this.notifyMediator("reportQeMetrics", { wordScores: Array.from(wordScores.values()), sentScores: Array.from(sentScores.values()) });
|
||||
if ((sentScores.size > 0) || (wordScores.size > 0)) {
|
||||
this.notifyMediator("reportQeMetrics", {
|
||||
wordScores: Array.from(wordScores.values()),
|
||||
sentScores: Array.from(sentScores.values())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enqueueElement(translationMessage) {
|
||||
|
|
|
@ -121,7 +121,7 @@ class OutboundTranslation {
|
|||
// and clear its forms
|
||||
this.otTextArea.value = "";
|
||||
this.backTranslationsTextArea.value = "";
|
||||
this.notifyMediator("onFormsEvent", "hidden");
|
||||
this.notifyMediator("reportFormsEvent", "hidden");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -131,7 +131,7 @@ class OutboundTranslation {
|
|||
this.otTextArea.value = widgetContent.typedContent;
|
||||
this.backTranslationsTextArea.value = widgetContent.translatedContent;
|
||||
}
|
||||
this.notifyMediator("onFormsEvent", "displayed");
|
||||
this.notifyMediator("reportFormsEvent", "displayed");
|
||||
}
|
||||
|
||||
sendTextToTranslation() {
|
||||
|
|
|
@ -51,7 +51,7 @@ class TranslationNotificationManager {
|
|||
STATE_TRANSLATED: 2,
|
||||
STATE_ERROR: 3,
|
||||
STATE_UNAVAILABLE: 4,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
set localizedLabels(val) {
|
||||
|
@ -63,9 +63,9 @@ class TranslationNotificationManager {
|
|||
}
|
||||
|
||||
loadLanguages() {
|
||||
for (const languagePair of Object.keys(this.modelRegistry)){
|
||||
const firstLang = languagePair.substring(0,2);
|
||||
const secondLang = languagePair.substring(2,4);
|
||||
for (const languagePair of Object.keys(this.modelRegistry)) {
|
||||
const firstLang = languagePair.substring(0, 2);
|
||||
const secondLang = languagePair.substring(2, 4);
|
||||
this.languageSet.add(firstLang);
|
||||
this.languageSet.add(secondLang);
|
||||
|
||||
|
@ -79,32 +79,40 @@ class TranslationNotificationManager {
|
|||
}
|
||||
}
|
||||
|
||||
reportInfobarEvent(name) {
|
||||
reportInfobarMetric(type, name, value) {
|
||||
|
||||
/*
|
||||
* propagate UI event to bgScript
|
||||
* to have the mediator notified
|
||||
*/
|
||||
const message = { command: "onInfobarEvent", tabId: this.tabId, name };
|
||||
const message = {
|
||||
command: "recordTelemetry",
|
||||
tabId: this.tabId,
|
||||
type,
|
||||
category: "infobar",
|
||||
name,
|
||||
value
|
||||
};
|
||||
this.bgScriptListenerCallback(message);
|
||||
}
|
||||
|
||||
requestInPageTranslation(from, to, withOutboundTranslation, withQualityEstimation){
|
||||
requestInPageTranslation(from, to, withOutboundTranslation, withQualityEstimation) {
|
||||
|
||||
/*
|
||||
* request received. let's forward to the background script in order
|
||||
* to have the mediator notified
|
||||
*/
|
||||
const message = { command: "translationRequested",
|
||||
from,
|
||||
to,
|
||||
withOutboundTranslation,
|
||||
withQualityEstimation,
|
||||
tabId: this.tabId };
|
||||
const message = {
|
||||
command: "translationRequested",
|
||||
from,
|
||||
to,
|
||||
withOutboundTranslation,
|
||||
withQualityEstimation,
|
||||
tabId: this.tabId
|
||||
};
|
||||
this.bgScriptListenerCallback(message);
|
||||
}
|
||||
|
||||
enableStats(){
|
||||
enableStats() {
|
||||
|
||||
/*
|
||||
* notify the mediator that the user wants to see statistics
|
||||
|
|
|
@ -130,7 +130,15 @@ window.MozTranslationNotification = class extends MozElements.Notification {
|
|||
}
|
||||
|
||||
this.state = this.translationNotificationManager.TranslationInfoBarStates.STATE_OFFER;
|
||||
this.translationNotificationManager.reportInfobarEvent("displayed");
|
||||
this.translationNotificationManager.reportInfobarMetric("event", "displayed");
|
||||
this.translationNotificationManager.reportInfobarMetric(
|
||||
"boolean", "outbound_enabled",
|
||||
this._getAnonElt("outboundtranslations-check").checked === true
|
||||
);
|
||||
this.translationNotificationManager.reportInfobarMetric(
|
||||
"boolean", "qe_enabled",
|
||||
this._getAnonElt("qualityestimations-check").checked === true
|
||||
);
|
||||
}
|
||||
|
||||
_getAnonElt(anonId) {
|
||||
|
@ -138,12 +146,12 @@ window.MozTranslationNotification = class extends MozElements.Notification {
|
|||
}
|
||||
|
||||
fromLanguageChanged() {
|
||||
this.translationNotificationManager.reportInfobarEvent("change_lang");
|
||||
this.translationNotificationManager.reportInfobarMetric("event","change_lang");
|
||||
this._getAnonElt("translate").disabled = false;
|
||||
}
|
||||
|
||||
translate() {
|
||||
this.translationNotificationManager.reportInfobarEvent("translate");
|
||||
this.translationNotificationManager.reportInfobarMetric("event","translate");
|
||||
const from = this._getSourceLang();
|
||||
const to = this._getTargetLang();
|
||||
this.translationNotificationManager.requestInPageTranslation(
|
||||
|
@ -159,19 +167,22 @@ window.MozTranslationNotification = class extends MozElements.Notification {
|
|||
|
||||
onOutboundClick() {
|
||||
if (this._getAnonElt("outboundtranslations-check").checked) {
|
||||
this.translationNotificationManager.reportInfobarEvent("outbound_checked");
|
||||
this.translationNotificationManager.reportInfobarMetric("event", "outbound_checked");
|
||||
this.translationNotificationManager.reportInfobarMetric("boolean", "outbound_enabled", true);
|
||||
} else {
|
||||
this.translationNotificationManager.reportInfobarEvent("outbound_unchecked");
|
||||
this.translationNotificationManager.reportInfobarMetric("event", "outbound_unchecked");
|
||||
this.translationNotificationManager.reportInfobarMetric("boolean", "outbound_enabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
onQeClick() {
|
||||
// eslint-disable-next-line no-warning-comments
|
||||
// todo: report boolean qe_enabled after iframe support is merged
|
||||
if (this._getAnonElt("qualityestimations-check").checked) {
|
||||
this.translationNotificationManager.reportInfobarEvent("qe_checked");
|
||||
this.translationNotificationManager.reportInfobarMetric("event","qe_checked");
|
||||
this.translationNotificationManager.reportInfobarMetric("boolean", "qe_enabled", true);
|
||||
} else {
|
||||
this.translationNotificationManager.reportInfobarEvent("qe_unchecked");
|
||||
this.translationNotificationManager.reportInfobarMetric("event","qe_unchecked");
|
||||
this.translationNotificationManager.reportInfobarMetric("boolean", "qe_enabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,7 +191,7 @@ window.MozTranslationNotification = class extends MozElements.Notification {
|
|||
* by clicking the notification's close button, the not now button or choosing never to translate)
|
||||
*/
|
||||
closeCommand() {
|
||||
this.translationNotificationManager.reportInfobarEvent("closed");
|
||||
this.translationNotificationManager.reportInfobarMetric("event","closed");
|
||||
this.close();
|
||||
}
|
||||
|
||||
|
@ -242,7 +253,7 @@ window.MozTranslationNotification = class extends MozElements.Notification {
|
|||
}
|
||||
|
||||
neverForLanguage() {
|
||||
this.translationNotificationManager.reportInfobarEvent("never_translate_lang");
|
||||
this.translationNotificationManager.reportInfobarMetric("event","never_translate_lang");
|
||||
const kPrefName = "browser.translation.neverForLanguages";
|
||||
const sourceLang = this._getSourceLang();
|
||||
|
||||
|
@ -258,7 +269,7 @@ window.MozTranslationNotification = class extends MozElements.Notification {
|
|||
}
|
||||
|
||||
neverForSite() {
|
||||
this.translationNotificationManager.reportInfobarEvent("never_translate_site");
|
||||
this.translationNotificationManager.reportInfobarMetric("event","never_translate_site");
|
||||
const principal = this.translationNotificationManager.browser.contentPrincipal;
|
||||
const perms = Services.perms;
|
||||
perms.addFromPrincipal(principal, "translate", perms.DENY_ACTION);
|
||||
|
|
|
@ -7,6 +7,7 @@ prefs =
|
|||
[browser_translation_test.js]
|
||||
support-files =
|
||||
browser_translation_test.html
|
||||
frame.html
|
||||
esen/lex.50.50.esen.s2t.bin
|
||||
esen/model.esen.intgemm.alphas.bin
|
||||
esen/vocab.esen.spm
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
<body>
|
||||
<div id="translationDiv">Hola mundo. Eso es una prueba de testes de traducciones.</div>
|
||||
<textarea id="mainTextarea"></textarea>
|
||||
<iframe id="iframe" src="frame.html" title="Iframe test page"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -2,6 +2,7 @@
|
|||
/* eslint-disable no-undef */
|
||||
/* eslint-disable max-lines-per-function */
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
const baseURL = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
|
@ -63,37 +64,40 @@ add_task(async function testTranslationBarDisplayed() {
|
|||
|
||||
// and check if the translation happened
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
await new Promise(resolve => content.setTimeout(resolve, 5000));
|
||||
const checkTranslation = async (document, message) => {
|
||||
is(
|
||||
document.getElementById("translationDiv").innerHTML,
|
||||
"Hello world. That's a test of translation tests.",
|
||||
`Text was correctly translated. (${message})`
|
||||
);
|
||||
|
||||
is(
|
||||
content.document.getElementById("translationDiv").innerHTML,
|
||||
"Hello world. That's a test of translation tests.",
|
||||
"Text was correctly translated."
|
||||
);
|
||||
/*
|
||||
* let's now select the outbound translation form
|
||||
* await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
*/
|
||||
document.getElementById("mainTextarea").focus();
|
||||
document.getElementById("OTapp").querySelectorAll("textarea")[0].value = "Hello World";
|
||||
document.getElementById("OTapp").querySelectorAll("textarea")[0].dispatchEvent(new content.KeyboardEvent('keydown', {'key': 'Enter'}));
|
||||
await new Promise(resolve => content.setTimeout(resolve, 5000));
|
||||
|
||||
is(
|
||||
document.getElementById("mainTextarea").value.trim(),
|
||||
"Hola Mundo",
|
||||
`Form translation text was correctly translated. (${message})`
|
||||
);
|
||||
|
||||
is(
|
||||
document.getElementById("OTapp").querySelectorAll("textarea")[1].value.trim(),
|
||||
"Hello World",
|
||||
`Back Translation text was correctly translated. (${message})`
|
||||
);
|
||||
}
|
||||
|
||||
await new Promise(resolve => content.setTimeout(resolve, 10000));
|
||||
await checkTranslation(content.document, "main frame");
|
||||
await checkTranslation(content.document.getElementById("iframe").contentWindow.document, "iframe");
|
||||
});
|
||||
|
||||
// let's now select the outbound translation form
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
|
||||
content.document.getElementById("mainTextarea").focus();
|
||||
content.document.getElementById("OTapp").querySelectorAll("textarea")[0].value = "Hello World";
|
||||
content.document.getElementById("OTapp").querySelectorAll("textarea")[0].dispatchEvent(new content.KeyboardEvent('keydown', {'key': 'Enter'}));
|
||||
await new Promise(resolve => content.setTimeout(resolve, 5000));
|
||||
|
||||
is(
|
||||
content.document.getElementById("mainTextarea").value.trim(),
|
||||
"Hola Mundo",
|
||||
"Form translation text was correctly translated."
|
||||
);
|
||||
|
||||
is(
|
||||
content.document.getElementById("OTapp").querySelectorAll("textarea")[1].value.trim(),
|
||||
"Hello World",
|
||||
"Back Translation text was correctly translated."
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
delete window.MozTranslationNotification;
|
||||
delete window.now;
|
||||
notification.close();
|
||||
|
|
|
@ -24,6 +24,7 @@ subprocess.call("unzip web-ext-artifacts/firefox_translations.xpi -d gecko/brows
|
|||
subprocess.call("mkdir -p gecko/browser/extensions/translations/test/browser/".split(), cwd=root)
|
||||
subprocess.call("cp scripts/tests/browser.ini gecko/browser/extensions/translations/test/browser/".split(), cwd=root)
|
||||
subprocess.call("cp scripts/tests/browser_translation_test.html gecko/browser/extensions/translations/test/browser/".split(), cwd=root)
|
||||
subprocess.call("cp scripts/tests/frame.html gecko/browser/extensions/translations/test/browser/".split(), cwd=root)
|
||||
subprocess.call("cp scripts/tests/browser_translation_test.js gecko/browser/extensions/translations/test/browser/".split(), cwd=root)
|
||||
subprocess.call("cp -r scripts/tests/esen/ gecko/browser/extensions/translations/test/browser/esen/".split(), cwd=root)
|
||||
subprocess.call("cp -r scripts/tests/enes/ gecko/browser/extensions/translations/test/browser/enes/".split(), cwd=root)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Iframe test.</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="translationDiv">Hola mundo. Eso es una prueba de testes de traducciones.</div>
|
||||
<textarea id="mainTextarea"></textarea>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче