mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
Bug 1129056 - Implement Yandex translation engine. r=felipe
Some parts of the patch by Felipe, with f=florian
This commit is contained in:
parent
907cabd285
commit
d79ffe0d98
@ -1851,6 +1851,8 @@ pref("browser.translation.detectLanguage", false);
|
||||
pref("browser.translation.neverForLanguages", "");
|
||||
// Show the translation UI bits, like the info bar, notification icon and preferences.
|
||||
pref("browser.translation.ui.show", false);
|
||||
// Allows to define the translation engine. Bing is default, Yandex may optionally switched on.
|
||||
pref("browser.translation.engine", "bing");
|
||||
|
||||
// Telemetry settings.
|
||||
// Determines if Telemetry pings can be archived locally.
|
||||
|
@ -8,6 +8,7 @@ this.EXPORTED_SYMBOLS = [ "TranslationContentHandler" ];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
|
||||
"resource:///modules/translation/LanguageDetector.jsm");
|
||||
@ -120,7 +121,6 @@ TranslationContentHandler.prototype = {
|
||||
case "Translation:TranslateDocument":
|
||||
{
|
||||
Cu.import("resource:///modules/translation/TranslationDocument.jsm");
|
||||
Cu.import("resource:///modules/translation/BingTranslator.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
|
||||
@ -128,16 +128,27 @@ TranslationContentHandler.prototype = {
|
||||
// translated text.
|
||||
let translationDocument = this.global.content.translationDocument ||
|
||||
new TranslationDocument(this.global.content.document);
|
||||
let bingTranslator = new BingTranslator(translationDocument,
|
||||
msg.data.from,
|
||||
msg.data.to);
|
||||
|
||||
let preferredEngine = Services.prefs.getCharPref("browser.translation.engine");
|
||||
let translator = null;
|
||||
if (preferredEngine == "yandex") {
|
||||
Cu.import("resource:///modules/translation/YandexTranslator.jsm");
|
||||
translator = new YandexTranslator(translationDocument,
|
||||
msg.data.from,
|
||||
msg.data.to);
|
||||
} else {
|
||||
Cu.import("resource:///modules/translation/BingTranslator.jsm");
|
||||
translator = new BingTranslator(translationDocument,
|
||||
msg.data.from,
|
||||
msg.data.to);
|
||||
}
|
||||
|
||||
this.global.content.translationDocument = translationDocument;
|
||||
translationDocument.translatedFrom = msg.data.from;
|
||||
translationDocument.translatedTo = msg.data.to;
|
||||
translationDocument.translationError = false;
|
||||
|
||||
bingTranslator.translate().then(
|
||||
translator.translate().then(
|
||||
result => {
|
||||
this.global.sendAsyncMessage("Translation:Finished", {
|
||||
characterCount: result.characterCount,
|
||||
|
343
browser/components/translation/YandexTranslator.jsm
Normal file
343
browser/components/translation/YandexTranslator.jsm
Normal file
@ -0,0 +1,343 @@
|
||||
/* 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";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "YandexTranslator" ];
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/Http.jsm");
|
||||
|
||||
// The maximum amount of net data allowed per request on Bing's API.
|
||||
const MAX_REQUEST_DATA = 5000; // Documentation says 10000 but anywhere
|
||||
// close to that is refused by the service.
|
||||
|
||||
// The maximum number of chunks allowed to be translated in a single
|
||||
// request.
|
||||
const MAX_REQUEST_CHUNKS = 1000; // Documentation says 2000.
|
||||
|
||||
// Self-imposed limit of 15 requests. This means that a page that would need
|
||||
// to be broken in more than 15 requests won't be fully translated.
|
||||
// The maximum amount of data that we will translate for a single page
|
||||
// is MAX_REQUESTS * MAX_REQUEST_DATA.
|
||||
const MAX_REQUESTS = 15;
|
||||
|
||||
const YANDEX_RETURN_CODE_OK = 200;
|
||||
|
||||
const YANDEX_ERR_KEY_INVALID = 401; // Invalid API key
|
||||
const YANDEX_ERR_KEY_BLOCKED = 402; // This API key has been blocked
|
||||
const YANDEX_ERR_DAILY_REQ_LIMIT_EXCEEDED = 403; // Daily limit for requests reached
|
||||
const YANDEX_ERR_DAILY_CHAR_LIMIT_EXCEEDED = 404; // Daily limit of chars reached
|
||||
const YANDEX_ERR_TEXT_TOO_LONG = 413; // The text size exceeds the maximum
|
||||
const YANDEX_ERR_UNPROCESSABLE_TEXT = 422; // The text could not be translated
|
||||
const YANDEX_ERR_LANG_NOT_SUPPORTED = 501; // The specified translation direction is not supported
|
||||
|
||||
// Errors that should activate the service unavailable handling
|
||||
const YANDEX_PERMANENT_ERRORS = [
|
||||
YANDEX_ERR_KEY_INVALID,
|
||||
YANDEX_ERR_KEY_BLOCKED,
|
||||
YANDEX_ERR_DAILY_REQ_LIMIT_EXCEEDED,
|
||||
YANDEX_ERR_DAILY_CHAR_LIMIT_EXCEEDED,
|
||||
];
|
||||
|
||||
/**
|
||||
* Translates a webpage using Yandex's Translation API.
|
||||
*
|
||||
* @param translationDocument The TranslationDocument object that represents
|
||||
* the webpage to be translated
|
||||
* @param sourceLanguage The source language of the document
|
||||
* @param targetLanguage The target language for the translation
|
||||
*
|
||||
* @returns {Promise} A promise that will resolve when the translation
|
||||
* task is finished.
|
||||
*/
|
||||
this.YandexTranslator = function(translationDocument, sourceLanguage, targetLanguage) {
|
||||
this.translationDocument = translationDocument;
|
||||
this.sourceLanguage = sourceLanguage;
|
||||
this.targetLanguage = targetLanguage;
|
||||
this._pendingRequests = 0;
|
||||
this._partialSuccess = false;
|
||||
this._serviceUnavailable = false;
|
||||
this._translatedCharacterCount = 0;
|
||||
};
|
||||
|
||||
this.YandexTranslator.prototype = {
|
||||
/**
|
||||
* Performs the translation, splitting the document into several chunks
|
||||
* respecting the data limits of the API.
|
||||
*
|
||||
* @returns {Promise} A promise that will resolve when the translation
|
||||
* task is finished.
|
||||
*/
|
||||
translate: function() {
|
||||
return Task.spawn(function *() {
|
||||
let currentIndex = 0;
|
||||
this._onFinishedDeferred = Promise.defer();
|
||||
|
||||
// Let's split the document into various requests to be sent to
|
||||
// Yandex's Translation API.
|
||||
for (let requestCount = 0; requestCount < MAX_REQUESTS; requestCount++) {
|
||||
// Generating the text for each request can be expensive, so
|
||||
// let's take the opportunity of the chunkification process to
|
||||
// allow for the event loop to attend other pending events
|
||||
// before we continue.
|
||||
yield CommonUtils.laterTickResolvingPromise();
|
||||
|
||||
// Determine the data for the next request.
|
||||
let request = this._generateNextTranslationRequest(currentIndex);
|
||||
|
||||
// Create a real request to the server, and put it on the
|
||||
// pending requests list.
|
||||
let yandexRequest = new YandexRequest(request.data,
|
||||
this.sourceLanguage,
|
||||
this.targetLanguage);
|
||||
this._pendingRequests++;
|
||||
yandexRequest.fireRequest().then(this._chunkCompleted.bind(this),
|
||||
this._chunkFailed.bind(this));
|
||||
|
||||
currentIndex = request.lastIndex;
|
||||
if (request.finished) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this._onFinishedDeferred.promise;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Function called when a request sent to the server completed successfully.
|
||||
* This function handles calling the function to parse the result and the
|
||||
* function to resolve the promise returned by the public `translate()`
|
||||
* method when there are no pending requests left.
|
||||
*
|
||||
* @param request The YandexRequest sent to the server
|
||||
*/
|
||||
_chunkCompleted: function(yandexRequest) {
|
||||
if (this._parseChunkResult(yandexRequest)) {
|
||||
this._partialSuccess = true;
|
||||
// Count the number of characters successfully translated.
|
||||
this._translatedCharacterCount += yandexRequest.characterCount;
|
||||
}
|
||||
|
||||
this._checkIfFinished();
|
||||
},
|
||||
|
||||
/**
|
||||
* Function called when a request sent to the server has failed.
|
||||
* This function handles deciding if the error is transient or means the
|
||||
* service is unavailable (zero balance on the key or request credentials are
|
||||
* not in an active state) and calling the function to resolve the promise
|
||||
* returned by the public `translate()` method when there are no pending
|
||||
* requests left.
|
||||
*
|
||||
* @param aError [optional] The XHR object of the request that failed.
|
||||
*/
|
||||
_chunkFailed: function(aError) {
|
||||
if (aError instanceof Ci.nsIXMLHttpRequest) {
|
||||
let body = aError.responseText;
|
||||
let json = { code: 0 };
|
||||
try {
|
||||
json = JSON.parse(body);
|
||||
} catch (e) {}
|
||||
|
||||
if (json.code && YANDEX_PERMANENT_ERRORS.indexOf(json.code) != -1)
|
||||
this._serviceUnavailable = true;
|
||||
}
|
||||
|
||||
this._checkIfFinished();
|
||||
},
|
||||
|
||||
/**
|
||||
* Function called when a request sent to the server has completed.
|
||||
* This function handles resolving the promise
|
||||
* returned by the public `translate()` method when all chunks are completed.
|
||||
*/
|
||||
_checkIfFinished: function() {
|
||||
// Check if all pending requests have been
|
||||
// completed and then resolves the promise.
|
||||
// If at least one chunk was successful, the
|
||||
// promise will be resolved positively which will
|
||||
// display the "Success" state for the infobar. Otherwise,
|
||||
// the "Error" state will appear.
|
||||
if (--this._pendingRequests == 0) {
|
||||
if (this._partialSuccess) {
|
||||
this._onFinishedDeferred.resolve({
|
||||
characterCount: this._translatedCharacterCount
|
||||
});
|
||||
} else {
|
||||
let error = this._serviceUnavailable ? "unavailable" : "failure";
|
||||
this._onFinishedDeferred.reject(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function parses the result returned by Yandex's Translation API,
|
||||
* which returns a JSON result that contains a number of elements. The
|
||||
* API is documented here:
|
||||
* http://api.yandex.com/translate/doc/dg/reference/translate.xml
|
||||
*
|
||||
* @param request The request sent to the server.
|
||||
* @returns boolean True if parsing of this chunk was successful.
|
||||
*/
|
||||
_parseChunkResult: function(yandexRequest) {
|
||||
let results;
|
||||
try {
|
||||
let result = JSON.parse(yandexRequest.networkRequest.responseText);
|
||||
if (result.code != 200) {
|
||||
Services.console.logStringMessage("YandexTranslator: Result is " + result.code);
|
||||
return false;
|
||||
}
|
||||
results = result.text
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let len = results.length;
|
||||
if (len != yandexRequest.translationData.length) {
|
||||
// This should never happen, but if the service returns a different number
|
||||
// of items (from the number of items submitted), we can't use this chunk
|
||||
// because all items would be paired incorrectly.
|
||||
return false;
|
||||
}
|
||||
|
||||
let error = false;
|
||||
for (let i = 0; i < len; i++) {
|
||||
try {
|
||||
let result = results[i];
|
||||
let root = yandexRequest.translationData[i][0];
|
||||
root.parseResult(result);
|
||||
} catch (e) { error = true; }
|
||||
}
|
||||
|
||||
return !error;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function will determine what is the data to be used for
|
||||
* the Nth request we are generating, based on the input params.
|
||||
*
|
||||
* @param startIndex What is the index, in the roots list, that the
|
||||
* chunk should start.
|
||||
*/
|
||||
_generateNextTranslationRequest: function(startIndex) {
|
||||
let currentDataSize = 0;
|
||||
let currentChunks = 0;
|
||||
let output = [];
|
||||
let rootsList = this.translationDocument.roots;
|
||||
|
||||
for (let i = startIndex; i < rootsList.length; i++) {
|
||||
let root = rootsList[i];
|
||||
let text = this.translationDocument.generateTextForItem(root);
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newCurSize = currentDataSize + text.length;
|
||||
let newChunks = currentChunks + 1;
|
||||
|
||||
if (newCurSize > MAX_REQUEST_DATA ||
|
||||
newChunks > MAX_REQUEST_CHUNKS) {
|
||||
|
||||
// If we've reached the API limits, let's stop accumulating data
|
||||
// for this request and return. We return information useful for
|
||||
// the caller to pass back on the next call, so that the function
|
||||
// can keep working from where it stopped.
|
||||
return {
|
||||
data: output,
|
||||
finished: false,
|
||||
lastIndex: i
|
||||
};
|
||||
}
|
||||
|
||||
currentDataSize = newCurSize;
|
||||
currentChunks = newChunks;
|
||||
output.push([root, text]);
|
||||
}
|
||||
|
||||
return {
|
||||
data: output,
|
||||
finished: true,
|
||||
lastIndex: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a request (for 1 chunk) sent off to Yandex's service.
|
||||
*
|
||||
* @params translationData The data to be used for this translation,
|
||||
* generated by the generateNextTranslationRequest...
|
||||
* function.
|
||||
* @param sourceLanguage The source language of the document.
|
||||
* @param targetLanguage The target language for the translation.
|
||||
*
|
||||
*/
|
||||
function YandexRequest(translationData, sourceLanguage, targetLanguage) {
|
||||
this.translationData = translationData;
|
||||
this.sourceLanguage = sourceLanguage;
|
||||
this.targetLanguage = targetLanguage;
|
||||
this.characterCount = 0;
|
||||
}
|
||||
|
||||
YandexRequest.prototype = {
|
||||
/**
|
||||
* Initiates the request
|
||||
*/
|
||||
fireRequest: function() {
|
||||
return Task.spawn(function *(){
|
||||
// Prepare URL.
|
||||
let url = getUrlParam("https://translate.yandex.net/api/v1.5/tr.json/translate",
|
||||
"browser.translation.yandex.translateURLOverride");
|
||||
|
||||
// Prepare the request body.
|
||||
let apiKey = getUrlParam("%YANDEX_API_KEY%", "browser.translation.yandex.apiKeyOverride");
|
||||
let params = [
|
||||
["key", apiKey],
|
||||
["format", "html"],
|
||||
["lang", this.sourceLanguage + "-" + this.targetLanguage],
|
||||
];
|
||||
|
||||
for (let [, text] of this.translationData) {
|
||||
params.push(["text", text]);
|
||||
this.characterCount += text.length;
|
||||
}
|
||||
|
||||
// Set up request options.
|
||||
let deferred = Promise.defer();
|
||||
let options = {
|
||||
onLoad: (function(responseText, xhr) {
|
||||
deferred.resolve(this);
|
||||
}).bind(this),
|
||||
onError: function(e, responseText, xhr) {
|
||||
deferred.reject(xhr);
|
||||
},
|
||||
postData: params
|
||||
};
|
||||
|
||||
// Fire the request.
|
||||
this.networkRequest = httpRequest(url, options);
|
||||
|
||||
return deferred.promise;
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch an auth token (clientID or client secret), which may be overridden by
|
||||
* a pref if it's set.
|
||||
*/
|
||||
function getUrlParam(paramValue, prefName) {
|
||||
if (Services.prefs.getPrefType(prefName))
|
||||
paramValue = Services.prefs.getCharPref(prefName);
|
||||
paramValue = Services.urlFormatter.formatURL(paramValue);
|
||||
return paramValue;
|
||||
}
|
@ -9,7 +9,8 @@ EXTRA_JS_MODULES.translation = [
|
||||
'LanguageDetector.jsm',
|
||||
'Translation.jsm',
|
||||
'TranslationContentHandler.jsm',
|
||||
'TranslationDocument.jsm'
|
||||
'TranslationDocument.jsm',
|
||||
'YandexTranslator.jsm'
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
@ -1,10 +1,13 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
bing.sjs
|
||||
yandex.sjs
|
||||
fixtures/bug1022725-fr.html
|
||||
fixtures/result-da39a3ee5e.txt
|
||||
fixtures/result-yandex-d448894848.json
|
||||
|
||||
[browser_translation_bing.js]
|
||||
[browser_translation_yandex.js]
|
||||
[browser_translation_fhr.js]
|
||||
skip-if = e10s
|
||||
[browser_translation_infobar.js]
|
||||
|
@ -0,0 +1,81 @@
|
||||
/* 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/. */
|
||||
|
||||
// Test the Yandex Translator client against a mock Yandex service, yandex.sjs.
|
||||
|
||||
"use strict";
|
||||
|
||||
const kEnginePref = "browser.translation.engine";
|
||||
const kApiKeyPref = "browser.translation.yandex.apiKeyOverride";
|
||||
|
||||
const {YandexTranslator} = Cu.import("resource:///modules/translation/YandexTranslator.jsm", {});
|
||||
const {TranslationDocument} = Cu.import("resource:///modules/translation/TranslationDocument.jsm", {});
|
||||
const {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
add_task(function* setup() {
|
||||
Services.prefs.setCharPref(kEnginePref, "yandex");
|
||||
Services.prefs.setCharPref(kApiKeyPref, "yandexValidKey");
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(kEnginePref);
|
||||
Services.prefs.clearUserPref(kApiKeyPref);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensure that the translation engine behaives as expected when translating
|
||||
* a sample page.
|
||||
*/
|
||||
add_task(function* test_yandex_translation() {
|
||||
|
||||
// Loading the fixture page.
|
||||
let url = constructFixtureURL("bug1022725-fr.html");
|
||||
let tab = yield promiseTestPageLoad(url);
|
||||
|
||||
// Translating the contents of the loaded tab.
|
||||
gBrowser.selectedTab = tab;
|
||||
let browser = tab.linkedBrowser;
|
||||
let client = new YandexTranslator(
|
||||
new TranslationDocument(browser.contentDocument), "fr", "en");
|
||||
let result = yield client.translate();
|
||||
|
||||
Assert.ok(result, "There should be a result.");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* A helper function for constructing a URL to a page stored in the
|
||||
* local fixture folder.
|
||||
*
|
||||
* @param filename Name of a fixture file.
|
||||
*/
|
||||
function constructFixtureURL(filename){
|
||||
// Deduce the Mochitest server address in use from a pref that was pre-processed.
|
||||
let server = Services.prefs.getCharPref("browser.translation.yandex.translateURLOverride")
|
||||
.replace("http://", "");
|
||||
server = server.substr(0, server.indexOf("/"));
|
||||
let url = "http://" + server +
|
||||
"/browser/browser/components/translation/test/fixtures/" + filename;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to open a new tab and wait for its content to load.
|
||||
*
|
||||
* @param String url A URL to be loaded in the new tab.
|
||||
*/
|
||||
function promiseTestPageLoad(url) {
|
||||
let deferred = Promise.defer();
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
browser.addEventListener("load", function listener() {
|
||||
if (browser.currentURI.spec == "about:blank")
|
||||
return;
|
||||
info("Page loaded: " + browser.currentURI.spec);
|
||||
browser.removeEventListener("load", listener, true);
|
||||
deferred.resolve(tab);
|
||||
}, true);
|
||||
return deferred.promise;
|
||||
}
|
1
browser/components/translation/test/fixtures/result-yandex-d448894848.json
vendored
Normal file
1
browser/components/translation/test/fixtures/result-yandex-d448894848.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"code":200,"lang":"fr-en","text":["Football's 2014 World Cup","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus diam sem, porttitor eget neque sit amet, ultricies posuere metus. Cras placerat rutrum risus, nec dignissim magna dictum vitae. Fusce eleifend fermentum lacinia. Nulla sagittis cursus nibh. Praesent adipiscing, elit at pulvinar dapibus, neque massa tincidunt sapien, eu consectetur lectus metus sit amet odio. Proin blandit consequat porttitor. Pellentesque vehicula justo sed luctus vestibulum. Donec metus."]}
|
220
browser/components/translation/test/yandex.sjs
Normal file
220
browser/components/translation/test/yandex.sjs
Normal file
@ -0,0 +1,220 @@
|
||||
/* 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";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, Constructor: CC} = Components;
|
||||
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
|
||||
"nsIBinaryInputStream",
|
||||
"setInputStream");
|
||||
|
||||
function handleRequest(req, res) {
|
||||
try {
|
||||
reallyHandleRequest(req, res);
|
||||
} catch (ex) {
|
||||
res.setStatusLine("1.0", 200, "AlmostOK");
|
||||
let msg = "Error handling request: " + ex + "\n" + ex.stack;
|
||||
log(msg);
|
||||
res.write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
dump("YANDEX-SERVER-MOCK: " + msg + "\n");
|
||||
}
|
||||
|
||||
const statusCodes = {
|
||||
400: "Bad Request",
|
||||
401: "Invalid API key",
|
||||
402: "This API key has been blocked",
|
||||
403: "Daily limit for requests reached",
|
||||
404: "Daily limit for chars reached",
|
||||
413: "The text size exceeds the maximum",
|
||||
422: "The text could not be translated",
|
||||
500: "Internal Server Error",
|
||||
501: "The specified translation direction is not supported",
|
||||
503: "Service Unavailable"
|
||||
};
|
||||
|
||||
function HTTPError(code = 500, message) {
|
||||
this.code = code;
|
||||
this.name = statusCodes[code] || "HTTPError";
|
||||
this.message = message || this.name;
|
||||
}
|
||||
HTTPError.prototype = new Error();
|
||||
HTTPError.prototype.constructor = HTTPError;
|
||||
|
||||
function sendError(res, err) {
|
||||
if (!(err instanceof HTTPError)) {
|
||||
err = new HTTPError(typeof err == "number" ? err : 500,
|
||||
err.message || typeof err == "string" ? err : "");
|
||||
}
|
||||
res.setStatusLine("1.1", err.code, err.name);
|
||||
res.write(err.message);
|
||||
}
|
||||
|
||||
// Based on the code borrowed from:
|
||||
// http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
|
||||
function parseQuery(query) {
|
||||
let match,
|
||||
params = {},
|
||||
pl = /\+/g,
|
||||
search = /([^&=]+)=?([^&]*)/g,
|
||||
decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); };
|
||||
|
||||
while (match = search.exec(query)) {
|
||||
let k = decode(match[1]),
|
||||
v = decode(match[2]);
|
||||
if (k in params) {
|
||||
if(params[k] instanceof Array)
|
||||
params[k].push(v);
|
||||
else
|
||||
params[k] = [params[k], v];
|
||||
} else {
|
||||
params[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function sha1(str) {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
// `result` is an out parameter, `result.value` will contain the array length.
|
||||
let result = {};
|
||||
// `data` is an array of bytes.
|
||||
let data = converter.convertToByteArray(str, result);
|
||||
let ch = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
ch.init(ch.SHA1);
|
||||
ch.update(data, data.length);
|
||||
let hash = ch.finish(false);
|
||||
|
||||
// Return the two-digit hexadecimal code for a byte.
|
||||
function toHexString(charCode) {
|
||||
return ("0" + charCode.toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
// Convert the binary hash data to a hex string.
|
||||
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");function sha1(str) {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
// `result` is an out parameter, `result.value` will contain the array length.
|
||||
let result = {};
|
||||
// `data` is an array of bytes.
|
||||
let data = converter.convertToByteArray(str, result);
|
||||
let ch = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
ch.init(ch.SHA1);
|
||||
ch.update(data, data.length);
|
||||
let hash = ch.finish(false);
|
||||
|
||||
// Return the two-digit hexadecimal code for a byte.
|
||||
function toHexString(charCode) {
|
||||
return ("0" + charCode.toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
// Convert the binary hash data to a hex string.
|
||||
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
|
||||
}
|
||||
}
|
||||
|
||||
function getRequestBody(req) {
|
||||
let avail;
|
||||
let bytes = [];
|
||||
let body = new BinaryInputStream(req.bodyInputStream);
|
||||
|
||||
while ((avail = body.available()) > 0)
|
||||
Array.prototype.push.apply(bytes, body.readByteArray(avail));
|
||||
|
||||
return String.fromCharCode.apply(null, bytes);
|
||||
}
|
||||
|
||||
function getInputStream(path) {
|
||||
let file = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties)
|
||||
.get("CurWorkD", Ci.nsILocalFile);
|
||||
for (let part of path.split("/"))
|
||||
file.append(part);
|
||||
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
fileStream.init(file, 1, 0, false);
|
||||
return fileStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yandex Requests have to be signed with an API Key. This mock server
|
||||
* supports the following keys:
|
||||
*
|
||||
* yandexValidKey Always passes the authentication,
|
||||
* yandexInvalidKey Never passes authentication and fails with 401 code,
|
||||
* yandexBlockedKey Never passes authentication and fails with 402 code,
|
||||
* yandexOutOfRequestsKey Never passes authentication and fails with 403 code,
|
||||
* yandexOutOfCharsKey Never passes authentication and fails with 404 code.
|
||||
*
|
||||
* If any other key is used the server reponds with 401 error code.
|
||||
*/
|
||||
function checkAuth(params) {
|
||||
if(!("key" in params))
|
||||
throw new HTTPError(400);
|
||||
|
||||
let key = params.key;
|
||||
if(key === "yandexValidKey")
|
||||
return true;
|
||||
|
||||
let invalidKeys = {
|
||||
"yandexInvalidKey" : 401,
|
||||
"yandexBlockedKey" : 402,
|
||||
"yandexOutOfRequestsKey" : 403,
|
||||
"yandexOutOfCharsKey" : 404,
|
||||
};
|
||||
|
||||
if(key in invalidKeys)
|
||||
throw new HTTPError(invalidKeys[key]);
|
||||
|
||||
throw new HTTPError(401);
|
||||
}
|
||||
|
||||
function reallyHandleRequest(req, res) {
|
||||
|
||||
try {
|
||||
|
||||
// Preparing the query parameters.
|
||||
let params = {};
|
||||
if(req.method == 'POST') {
|
||||
params = parseQuery(getRequestBody(req));
|
||||
}
|
||||
|
||||
// Extracting the API key and attempting to authenticate the request.
|
||||
log(JSON.stringify(params));
|
||||
|
||||
checkAuth(params);
|
||||
methodHandlers['translate'](res, params);
|
||||
|
||||
} catch (ex) {
|
||||
sendError(res, ex, ex.code);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const methodHandlers = {
|
||||
translate: function(res, params) {
|
||||
res.setStatusLine("1.1", 200, "OK");
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
let hash = sha1(JSON.stringify(params)).substr(0, 10);
|
||||
log("SHA1 hash of content: " + hash);
|
||||
|
||||
let fixture = "browser/browser/components/translation/test/fixtures/result-yandex-" + hash + ".json";
|
||||
log("PATH: " + fixture);
|
||||
|
||||
let inputStream = getInputStream(fixture);
|
||||
res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
};
|
@ -255,6 +255,8 @@ user_pref("security.ssl.errorReporting.url", "https://example.com/browser/browse
|
||||
// Make sure Translation won't hit the network.
|
||||
user_pref("browser.translation.bing.authURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
|
||||
user_pref("browser.translation.bing.translateArrayURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
|
||||
user_pref("browser.translation.yandex.translateURLOverride", "http://%(server)s/browser/browser/components/translation/test/yandex.sjs");
|
||||
user_pref("browser.translation.engine", "bing");
|
||||
|
||||
// Make sure we don't try to load snippets from the network.
|
||||
user_pref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
|
||||
|
Loading…
Reference in New Issue
Block a user