Bug 1248499: [webext] Implement tabs.detectLanguage. r=billm

MozReview-Commit-ID: F4GpSesj2ho

--HG--
extra : rebase_source : e9ffab3396b4f813c60e366c371ea54baccc160a
This commit is contained in:
Kris Maglione 2016-02-26 13:20:28 -08:00
Родитель 89517813d0
Коммит 66f9b57852
7 изменённых файлов: 156 добавлений и 1 удалений

Просмотреть файл

@ -656,6 +656,19 @@ extensions.registerSchemaAPI("tabs", null, (extension, context) => {
message, recipient);
},
detectLanguage: function(tabId) {
let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
if (!tab) {
return Promise.reject({message: `Invalid tab ID: ${tabId}`});
}
let browser = tab.linkedBrowser;
let recipient = {innerWindowID: browser.innerWindowID};
return context.sendMessage(browser.messageManager, "Extension:DetectLanguage",
{}, recipient);
},
_execute: function(tabId, details, kind, method) {
let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
let mm = tab.linkedBrowser.messageManager;

Просмотреть файл

@ -9,6 +9,8 @@ support-files =
file_popup_api_injection_b.html
file_iframe_document.html
file_iframe_document.sjs
file_language_fr_en.html
file_language_ja.html
[browser_ext_simple.js]
[browser_ext_currentWindow.js]
@ -28,6 +30,7 @@ support-files =
[browser_ext_runtime_setUninstallURL.js]
[browser_ext_tabs_audio.js]
[browser_ext_tabs_captureVisibleTab.js]
[browser_ext_tabs_detectLanguage.js]
[browser_ext_tabs_events.js]
[browser_ext_tabs_executeScript.js]
[browser_ext_tabs_executeScript_good.js]

Просмотреть файл

@ -0,0 +1,57 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testDetectLanguage() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
},
background() {
const BASE_PATH = "browser/browser/components/extensions/test/browser";
function loadTab(url) {
let tabId;
let awaitUpdated = new Promise(resolve => {
browser.tabs.onUpdated.addListener(function onUpdated(changedTabId, changed, tab) {
if (changedTabId === tabId && changed.url) {
browser.tabs.onUpdated.removeListener(onUpdated);
resolve(tab);
}
});
});
return browser.tabs.create({url}).then(tab => {
tabId = tab.id;
return awaitUpdated;
});
}
loadTab(`http://example.co.jp/${BASE_PATH}/file_language_ja.html`).then(tab => {
return browser.tabs.detectLanguage(tab.id).then(lang => {
browser.test.assertEq("ja", lang, "Japanese document should be detected as Japanese");
return browser.tabs.remove(tab.id);
});
}).then(() => {
return loadTab(`http://example.co.jp/${BASE_PATH}/file_language_fr_en.html`);
}).then(tab => {
return browser.tabs.detectLanguage(tab.id).then(lang => {
browser.test.assertEq("fr", lang, "French/English document should be detected as primarily French");
return browser.tabs.remove(tab.id);
});
}).then(() => {
browser.test.notifyPass("detectLanguage");
}).catch(e => {
browser.test.fail(`Error: ${e} :: ${e.stack}`);
browser.test.notifyFail("detectLanguage");
});
},
});
yield extension.startup();
yield extension.awaitFinish("detectLanguage");
yield extension.unload();
});

Просмотреть файл

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
France is the largest country in Western Europe and the third-largest in Europe as a whole.
A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter
Cet article concerne le pays européen aujourdhui appelé République française. Pour dautres usages du nom France,
Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus.
Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumps over the lazy dog.
</body>
</html>

Просмотреть файл

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
このペ ジでは アカウントに指定された予算の履歴を一覧にしています それぞれの項目には 予算額と特定期間のステ タスが表示されます 現在または今後の予算を設定するには
</body>
</html>

Просмотреть файл

@ -25,6 +25,8 @@ Cu.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
@ -33,7 +35,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
"resource://gre/modules/MessageChannel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
"resource://gre/modules/WebNavigationFrames.jsm");
@ -47,6 +48,7 @@ var {
injectAPI,
flushJarCache,
detectLanguage,
promiseDocumentReady,
} = ExtensionUtils;
function isWhenBeforeOrSame(when1, when2) {
@ -732,6 +734,7 @@ class ExtensionGlobal {
this.global = global;
MessageChannel.addListener(global, "Extension:Capture", this);
MessageChannel.addListener(global, "Extension:DetectLanguage", this);
MessageChannel.addListener(global, "Extension:Execute", this);
MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);
@ -760,6 +763,8 @@ class ExtensionGlobal {
switch (messageName) {
case "Extension:Capture":
return this.handleExtensionCapture(data.width, data.height, data.options);
case "Extension:DetectLanguage":
return this.handleDetectLanguage(target);
case "Extension:Execute":
return this.handleExtensionExecute(target, recipient.extensionId, data.options);
case "WebNavigation:GetFrame":
@ -790,6 +795,36 @@ class ExtensionGlobal {
return canvas.toDataURL(`image/${options.format}`, options.quality / 100);
}
handleDetectLanguage(target) {
let doc = target.content.document;
return promiseDocumentReady(doc).then(() => {
let elem = doc.documentElement;
let language = (elem.getAttribute("xml:lang") || elem.getAttribute("lang") ||
doc.contentLanguage || null);
// We only want the last element of the TLD here.
// Only country codes have any effect on the results, but other
// values cause no harm.
let tld = doc.location.hostname.match(/[a-z]*$/)[0];
// The CLD2 library used by the language detector is capable of
// analyzing raw HTML. Unfortunately, that takes much more memory,
// and since it's hosted by emscripten, and therefore can't shrink
// its heap after it's grown, it has a performance cost.
// So we send plain text instead.
let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"].createInstance(Ci.nsIDocumentEncoder);
encoder.init(doc, "text/plain", encoder.SkipInvisibleContent);
let text = encoder.encodeToStringWithMaxLength(60 * 1024);
let encoding = doc.characterSet;
return LanguageDetector.detectLanguage({language, tld, text, encoding})
.then(result => result.language);
});
}
handleExtensionExecute(target, extensionId, options) {
return DocumentManager.executeScript(target, extensionId, options).then(result => {
try {

Просмотреть файл

@ -630,6 +630,28 @@ function injectAPI(source, dest) {
}
}
/**
* Returns a Promise which resolves when the given document's DOM has
* fully loaded.
*
* @param {Document} doc The document to await the load of.
* @returns {Promise<Document>}
*/
function promiseDocumentReady(doc) {
if (doc.readyState == "interactive" || doc.readyState == "complete") {
return Promise.resolve(doc);
}
return new Promise(resolve => {
doc.addEventListener("DOMContentLoaded", function onReady(event) {
if (event.target === event.currentTarget) {
doc.removeEventListener("DOMContentLoaded", onReady, true);
resolve(doc);
}
}, true);
});
}
/*
* Messaging primitives.
*/
@ -1011,6 +1033,7 @@ this.ExtensionUtils = {
ignoreEvent,
injectAPI,
instanceOf,
promiseDocumentReady,
runSafe,
runSafeSync,
runSafeSyncWithoutClone,