Merge m-c to inbound despite the CLOSED TREE

This commit is contained in:
Wes Kocher 2014-06-19 18:29:15 -07:00
commit 960e3291d5
102 changed files with 2412 additions and 305 deletions

View File

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
@ -98,7 +98,7 @@
<project name="platform/system/vold" path="system/vold" revision="919829940468066a32f403980b43f6ebfee5d314"/>
<!-- Emulator specific things -->
<project name="android-development" path="development" remote="b2g" revision="9abf0ab68376afae3e1c7beefa3e9cbee2fde202"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="2fee3bbbfc236b883ef8507e27d88b17b203fe25"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3cae7eee1f7f00ba1eb53c733a8779f345055c6e"/>
<project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
<project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
<project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
@ -23,7 +23,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -19,13 +19,13 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
<project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
@ -98,7 +98,7 @@
<project name="platform/system/vold" path="system/vold" revision="919829940468066a32f403980b43f6ebfee5d314"/>
<!-- Emulator specific things -->
<project name="android-development" path="development" remote="b2g" revision="9abf0ab68376afae3e1c7beefa3e9cbee2fde202"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="2fee3bbbfc236b883ef8507e27d88b17b203fe25"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="3cae7eee1f7f00ba1eb53c733a8779f345055c6e"/>
<project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
<project name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="d2685281e2e54ca14d1df304867aa82c37b27162"/>
<project name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "507ca54e4c4da9d3e1b1af92d7a306a71875d066",
"revision": "dcb55fbcaa9bd33e662a2c5e99cdb7e4c9d7ad51",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
<project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,10 +17,10 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
<project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
<!-- Stock Android things -->

View File

@ -17,12 +17,12 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="135a13ea0ee333314b6a12cb19f18617308a3b46"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ccd70903544486bea04e85d8a4aacf63f1de2a72"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="26e84ee3e9ef5df2ca28b16aa6719c6e77b5f24f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
<project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
<!-- Stock Android things -->
<project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>

View File

@ -6,7 +6,7 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
this.EXPORTED_SYMBOLS = [ "BingTranslation" ];
this.EXPORTED_SYMBOLS = [ "BingTranslator" ];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Log.jsm");
@ -40,7 +40,7 @@ const MAX_REQUESTS = 15;
* @returns {Promise} A promise that will resolve when the translation
* task is finished.
*/
this.BingTranslation = function(translationDocument, sourceLanguage, targetLanguage) {
this.BingTranslator = function(translationDocument, sourceLanguage, targetLanguage) {
this.translationDocument = translationDocument;
this.sourceLanguage = sourceLanguage;
this.targetLanguage = targetLanguage;
@ -50,7 +50,7 @@ this.BingTranslation = function(translationDocument, sourceLanguage, targetLangu
this._translatedCharacterCount = 0;
};
this.BingTranslation.prototype = {
this.BingTranslator.prototype = {
/**
* Performs the translation, splitting the document into several chunks
* respecting the data limits of the API.
@ -282,7 +282,10 @@ BingRequest.prototype = {
return Task.spawn(function *(){
let token = yield BingTokenManager.getToken();
let auth = "Bearer " + token;
let request = new RESTRequest("https://api.microsofttranslator.com/v2/Http.svc/TranslateArray");
let url = getUrlParam("https://api.microsofttranslator.com/v2/Http.svc/TranslateArray",
"browser.translation.bing.translateArrayURL",
false);
let request = new RESTRequest(url);
request.setHeader("Content-type", "text/xml");
request.setHeader("Authorization", auth);
@ -358,15 +361,18 @@ let BingTokenManager = {
* string once it is obtained.
*/
_getNewToken: function() {
let request = new RESTRequest("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13");
let url = getUrlParam("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13",
"browser.translation.bing.authURL",
false);
let request = new RESTRequest(url);
request.setHeader("Content-type", "application/x-www-form-urlencoded");
let params = [
"grant_type=client_credentials",
"scope=" + encodeURIComponent("http://api.microsofttranslator.com"),
"client_id=" +
getAuthTokenParam("%BING_API_CLIENTID%", "browser.translation.bing.clientIdOverride"),
getUrlParam("%BING_API_CLIENTID%", "browser.translation.bing.clientIdOverride"),
"client_secret=" +
getAuthTokenParam("%BING_API_KEY%", "browser.translation.bing.apiKeyOverride")
getUrlParam("%BING_API_KEY%", "browser.translation.bing.apiKeyOverride")
];
let deferred = Promise.defer();
@ -416,11 +422,10 @@ function escapeXML(aStr) {
* Fetch an auth token (clientID or client secret), which may be overridden by
* a pref if it's set.
*/
function getAuthTokenParam(key, prefName) {
let val;
try {
val = Services.prefs.getCharPref(prefName);
} catch(ex) {}
function getUrlParam(paramValue, prefName, encode = true) {
if (Services.prefs.getPrefType(prefName))
paramValue = Services.prefs.getCharPref(prefName);
paramValue = Services.urlFormatter.formatURL(paramValue);
return encodeURIComponent(Services.urlFormatter.formatURL(val || key));
return encode ? encodeURIComponent(paramValue) : paramValue;
}

View File

@ -100,10 +100,16 @@ this.Translation = {
*/
function TranslationUI(aBrowser) {
this.browser = aBrowser;
aBrowser.messageManager.addMessageListener("Translation:Finished", this);
}
TranslationUI.prototype = {
get browser() this._browser,
set browser(aBrowser) {
if (this._browser)
this._browser.messageManager.removeMessageListener("Translation:Finished", this);
aBrowser.messageManager.addMessageListener("Translation:Finished", this);
this._browser = aBrowser;
},
translate: function(aFrom, aTo) {
if (aFrom == aTo ||
(this.state == Translation.STATE_TRANSLATED &&
@ -131,7 +137,17 @@ TranslationUI.prototype = {
if (notification)
PopupNotifications.remove(notification);
let callback = aTopic => {
let callback = (aTopic, aNewBrowser) => {
if (aTopic == "swapping") {
let infoBarVisible =
this.notificationBox.getNotificationWithValue("translation");
aNewBrowser.translationUI = this;
this.browser = aNewBrowser;
if (infoBarVisible)
this.showTranslationInfoBar();
return true;
}
if (aTopic != "showing")
return false;
let notification = this.notificationBox.getNotificationWithValue("translation");

View File

@ -121,16 +121,16 @@ TranslationContentHandler.prototype = {
// translated text.
let translationDocument = this.global.content.translationDocument ||
new TranslationDocument(this.global.content.document);
let bingTranslation = new BingTranslation(translationDocument,
msg.data.from,
msg.data.to);
let bingTranslator = 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;
bingTranslation.translate().then(
bingTranslator.translate().then(
result => {
this.global.sendAsyncMessage("Translation:Finished", {
characterCount: result.characterCount,

View File

@ -0,0 +1,218 @@
/* 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("BING-SERVER-MOCK: " + msg + "\n");
}
const statusCodes = {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
500: "Internal Server Error",
501: "Not Implemented",
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);
}
function parseQuery(query) {
let ret = {};
for (let param of query.replace(/^[?&]/, "").split("&")) {
param = param.split("=");
if (!param[0])
continue;
ret[unescape(param[0])] = unescape(param[1]);
}
return ret;
}
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 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 parseXml(body) {
let DOMParser = Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser);
let xml = DOMParser.parseFromString(body, "text/xml");
if (xml.documentElement.localName == "parsererror")
throw new Error("Invalid XML");
return xml;
}
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;
}
function checkAuth(req) {
let err = new Error("Authorization failed");
err.code = 401;
if (!req.hasHeader("Authorization"))
throw new HTTPError(401, "No Authorization header provided.");
let auth = req.getHeader("Authorization");
if (!auth.startsWith("Bearer "))
throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
}
function reallyHandleRequest(req, res) {
log("method: " + req.method);
if (req.method != "POST") {
sendError(res, "Bing only deals with POST requests, not '" + req.method + "'.");
return;
}
let body = getRequestBody(req);
log("body: " + body);
// First, we'll see if we're dealing with an XML body:
let contentType = req.hasHeader("Content-Type") ? req.getHeader("Content-Type") : null;
log("contentType: " + contentType);
if (contentType == "text/xml") {
try {
// For all these requests the client needs to supply the correct
// authentication headers.
checkAuth(req);
let xml = parseXml(body);
let method = xml.documentElement.localName;
log("invoking method: " + method);
// If the requested method is supported, delegate it to its handler.
if (methodHandlers[method])
methodHandlers[method](res, xml);
else
throw new HTTPError(501);
} catch (ex) {
sendError(res, ex, ex.code);
}
} else {
// Not XML, so it must be a query-string.
let params = parseQuery(body);
// Delegate an authentication request to the correct handler.
if ("grant_type" in params && params.grant_type == "client_credentials")
methodHandlers.authenticate(res, params);
else
sendError(res, 501);
}
}
const methodHandlers = {
authenticate: function(res, params) {
// Validate a few required parameters.
if (params.scope != "http://api.microsofttranslator.com") {
sendError(res, "Invalid scope.");
return;
}
if (!params.client_id) {
sendError(res, "Missing client_id param.");
return;
}
if (!params.client_secret) {
sendError(res, "Missing client_secret param.");
return;
}
let content = JSON.stringify({
access_token: "test",
expires_in: 600
});
res.setStatusLine("1.1", 200, "OK");
res.setHeader("Content-Length", String(content.length));
res.setHeader("Content-Type", "application/json");
res.write(content);
},
TranslateArrayRequest: function(res, xml, body) {
let from = xml.querySelector("From").firstChild.nodeValue;
let to = xml.querySelector("To").firstChild.nodeValue
log("translating from '" + from + "' to '" + to + "'");
res.setStatusLine("1.1", 200, "OK");
res.setHeader("Content-Type", "text/xml");
let hash = sha1(body).substr(0, 10);
log("SHA1 hash of content: " + hash);
let inputStream = getInputStream(
"browser/browser/components/translation/test/fixtures/result-" + hash + ".txt");
res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
inputStream.close();
}
};

View File

@ -1,6 +1,10 @@
[DEFAULT]
support-files =
bing.sjs
fixtures/bug1022725-fr.html
fixtures/result-da39a3ee5e.txt
[browser_translation_bing.js]
[browser_translation_fhr.js]
skip-if = true # Needs to wait until bug 1022725.
[browser_translation_infobar.js]
[browser_translation_exceptions.js]

View File

@ -0,0 +1,56 @@
/* 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 Bing Translator client against a mock Bing service, bing.sjs.
"use strict";
const kClientIdPref = "browser.translation.bing.clientIdOverride";
const kClientSecretPref = "browser.translation.bing.apiKeyOverride";
const {BingTranslator} = Cu.import("resource:///modules/translation/BingTranslator.jsm", {});
const {TranslationDocument} = Cu.import("resource:///modules/translation/TranslationDocument.jsm", {});
function test() {
waitForExplicitFinish();
Services.prefs.setCharPref(kClientIdPref, "testClient");
Services.prefs.setCharPref(kClientSecretPref, "testSecret");
// Deduce the Mochitest server address in use from a pref that was pre-processed.
let server = Services.prefs.getCharPref("browser.translation.bing.authURL")
.replace("http://", "");
server = server.substr(0, server.indexOf("/"));
let tab = gBrowser.addTab("http://" + server +
"/browser/browser/components/translation/test/fixtures/bug1022725-fr.html");
gBrowser.selectedTab = tab;
registerCleanupFunction(function () {
gBrowser.removeTab(tab);
Services.prefs.clearUserPref(kClientIdPref);
Services.prefs.clearUserPref(kClientSecretPref);
});
let browser = tab.linkedBrowser;
browser.addEventListener("load", function onload() {
if (browser.currentURI.spec == "about:blank")
return;
browser.removeEventListener("load", onload, true);
let client = new BingTranslator(
new TranslationDocument(browser.contentDocument), "fr", "en");
client.translate().then(
result => {
// XXXmikedeboer; here you would continue the test/ content inspection.
ok(result, "There should be a result.");
finish();
},
error => {
ok(false, "Unexpected Client Error: " + error);
finish();
}
);
}, true);
}

View File

@ -57,7 +57,8 @@ function retrieveTranslationCounts() {
return [0, 0];
}
return [day.get("pageTranslatedCount"), day.get("charactersTranslatedCount")];
// .get() may return `undefined`, which we can't compute.
return [day.get("pageTranslatedCount") || 0, day.get("charactersTranslatedCount") || 0];
});
}

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="fr">
<head>
<!--
- Text retrieved from http://fr.wikipedia.org/wiki/Coupe_du_monde_de_football_de_2014
- at 06/13/2014, Creative Commons Attribution-ShareAlike License.
-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test</title>
</head>
<body>
<h1>Coupe du monde de football de 2014</h1>
<div>La Coupe du monde de football de 2014 est la 20e édition de la Coupe du monde de football, compétition organisée par la FIFA et qui réunit les trente-deux meilleures sélections nationales. Sa phase finale a lieu à l'été 2014 au Brésil. Avec le pays organisateur, toutes les équipes championnes du monde depuis 1930 (Uruguay, Italie, Allemagne, Angleterre, Argentine, France et Espagne) se sont qualifiées pour cette compétition. Elle est aussi la première compétition internationale de la Bosnie-Herzégovine.</div>
</body>
</html>

View File

@ -0,0 +1,22 @@
<ArrayOfTranslateArrayResponse xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<TranslateArrayResponse>
<From>fr</From>
<OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>34</a:int>
</OriginalTextSentenceLengths>
<TranslatedText>Football's 2014 World Cup</TranslatedText>
<TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>25</a:int>
</TranslatedTextSentenceLengths>
</TranslateArrayResponse>
<TranslateArrayResponse>
<From>fr</From>
<OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>508</a:int>
</OriginalTextSentenceLengths>
<TranslatedText>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.</TranslatedText>
<TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>475</a:int>
</TranslatedTextSentenceLengths>
</TranslateArrayResponse>
</ArrayOfTranslateArrayResponse>

View File

@ -152,11 +152,6 @@
<parameter name="aTranslation"/>
<body>
<![CDATA[
if (Translation.serviceUnavailable) {
this.state = Translation.STATE_UNAVAILABLE;
return;
}
this.translation = aTranslation;
let bundle = Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)

View File

@ -127,7 +127,7 @@
<template id="simulator-item-template">
<span>
<button class="simulator-item action-primary" onclick="UI.startSimulator(this.dataset.version)" template='{"type":"attribute","path":"version","name":"data-version"}' title="&connection.startSimulatorTooltip;">
<span template='{"type":"textContent", "path":"version"}'></span>
<span template='{"type":"textContent", "path":"label"}'></span>
</button>
</span>
</template>

View File

@ -10,7 +10,11 @@ let store = new ObservableObject({versions:[]});
function feedStore() {
store.object.versions = Simulator.availableVersions().map(v => {
return {version:v}
let simulator = Simulator.getByVersion(v);
return {
version: v,
label: simulator.appinfo.label
}
});
}

View File

@ -0,0 +1,117 @@
/* 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 Cu = Components.utils;
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const {GetAvailableAddons} = require("devtools/webide/addons");
const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
document.querySelector("#aboutaddons").onclick = function() {
window.parent.UI.openInBrowser("about:addons");
}
document.querySelector("#close").onclick = CloseUI;
GetAvailableAddons().then(BuildUI, (e) => {
console.error(e);
window.alert(Strings.formatStringFromName("error_cantFetchAddonsJSON", [e], 1));
});
}, true);
function CloseUI() {
window.parent.UI.openProject();
}
function BuildUI(addons) {
BuildItem(addons.adb, true /* is adb */);
for (let addon of addons.simulators) {
BuildItem(addon, false /* is adb */);
}
}
function BuildItem(addon, isADB) {
function onAddonUpdate(event, arg) {
switch (event) {
case "update":
progress.removeAttribute("value");
li.setAttribute("status", addon.status);
status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
break;
case "failure":
console.error(arg);
window.alert(arg);
break;
case "progress":
if (arg == -1) {
progress.removeAttribute("value");
} else {
progress.value = arg;
}
break;
}
}
let events = ["update", "failure", "progress"];
for (let e of events) {
addon.on(e, onAddonUpdate);
}
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload);
for (let e of events) {
addon.off(e, onAddonUpdate);
}
});
let li = document.createElement("li");
li.setAttribute("status", addon.status);
// Used in tests
if (isADB) {
li.setAttribute("addon", "adb");
} else {
li.setAttribute("addon", "simulator-" + addon.version);
}
let name = document.createElement("span");
name.className = "name";
if (isADB) {
name.textContent = Strings.GetStringFromName("addons_adb_label");
} else {
let stability = Strings.GetStringFromName("addons_" + addon.stability);
name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
}
li.appendChild(name);
let status = document.createElement("span");
status.className = "status";
status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
li.appendChild(status);
let installButton = document.createElement("button");
installButton.className = "install-button";
installButton.onclick = () => addon.install();
installButton.textContent = Strings.GetStringFromName("addons_install_button");
li.appendChild(installButton);
let uninstallButton = document.createElement("button");
uninstallButton.className = "uninstall-button";
uninstallButton.onclick = () => addon.uninstall();
uninstallButton.textContent = Strings.GetStringFromName("addons_uninstall_button");
li.appendChild(uninstallButton);
let progress = document.createElement("progress");
li.appendChild(progress);
if (isADB) {
let warning = document.createElement("p");
warning.textContent = Strings.GetStringFromName("addons_adb_warning");
warning.className = "warning";
li.appendChild(warning);
}
document.querySelector("ul").appendChild(li);
}

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
%webideDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf8"/>
<link rel="stylesheet" href="chrome://webide/skin/addons.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://webide/content/addons.js"></script>
</head>
<body>
<div id="controls">
<a id="aboutaddons">&addons_aboutaddons;</a>
<a id="close">&deck_close;</a>
</div>
<h1>&addons_title;</h1>
<ul></ul>
</body>
</html>

View File

@ -11,6 +11,12 @@ webide.jar:
content/details.xhtml (details.xhtml)
content/details.js (details.js)
content/cli.js (cli.js)
content/addons.js (addons.js)
content/addons.xhtml (addons.xhtml)
content/permissionstable.js (permissionstable.js)
content/permissionstable.xhtml (permissionstable.xhtml)
content/runtimedetails.js (runtimedetails.js)
content/runtimedetails.xhtml (runtimedetails.xhtml)
# Temporarily include locales in content, until we're ready
# to localize webide

View File

@ -17,6 +17,7 @@ const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
const {AppProjects} = require("devtools/app-manager/app-projects");
const APP_CREATOR_LIST = "devtools.webide.templatesURL";
const {AppManager} = require("devtools/webide/app-manager");
const {GetTemplatesJSON} = require("devtools/webide/remote-resources");
let gTemplateList = null;
@ -33,20 +34,12 @@ window.addEventListener("load", function onLoad() {
}, true);
function getJSON() {
let xhr = new XMLHttpRequest();
xhr.overrideMimeType('text/plain');
xhr.onload = function() {
let list;
try {
list = JSON.parse(this.responseText);
if (!Array.isArray(list)) {
throw new Error("JSON response not an array");
}
if (list.length == 0) {
throw new Error("JSON response is an empty array");
}
} catch(e) {
return failAndBail("Invalid response from server");
GetTemplatesJSON().then(list => {
if (!Array.isArray(list)) {
throw new Error("JSON response not an array");
}
if (list.length == 0) {
throw new Error("JSON response is an empty array");
}
gTemplateList = list;
let templatelistNode = document.querySelector("#templatelist");
@ -76,13 +69,9 @@ function getJSON() {
document.querySelector("#project-name").value = testOptions.name;
doOK();
}
};
xhr.onerror = function() {
failAndBail("Can't download app templates");
};
let url = Services.prefs.getCharPref(APP_CREATOR_LIST);
xhr.open("get", url);
xhr.send();
}, (e) => {
failAndBail("Can't download app templates: " + e);
});
}
function failAndBail(msg) {

View File

@ -0,0 +1,75 @@
/* 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 Cu = Components.utils;
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const {AppManager} = require("devtools/webide/app-manager");
const {Connection} = require("devtools/client/connection-manager");
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
document.querySelector("#close").onclick = CloseUI;
AppManager.on("app-manager-update", OnAppManagerUpdate);
BuildUI();
}, true);
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload);
AppManager.off("app-manager-update", OnAppManagerUpdate);
});
function CloseUI() {
window.parent.UI.openProject();
}
function OnAppManagerUpdate(event, what) {
if (what == "connection" || what == "list-tabs-response") {
BuildUI();
}
}
let getRawPermissionsTablePromise; // Used by tests
function BuildUI() {
let table = document.querySelector("table");
let lines = table.querySelectorAll(".line");
for (let line of lines) {
line.remove();
}
if (AppManager.connection &&
AppManager.connection.status == Connection.Status.CONNECTED &&
AppManager.deviceFront) {
getRawPermissionsTablePromise = AppManager.deviceFront.getRawPermissionsTable();
getRawPermissionsTablePromise.then(json => {
let permissionsTable = json.rawPermissionsTable;
for (let name in permissionsTable) {
let tr = document.createElement("tr");
tr.className = "line";
let td = document.createElement("td");
td.textContent = name;
tr.appendChild(td);
for (let type of ["app","privileged","certified"]) {
let td = document.createElement("td");
if (permissionsTable[name][type] == json.ALLOW_ACTION) {
td.textContent = "✓";
td.className = "permallow";
}
if (permissionsTable[name][type] == json.PROMPT_ACTION) {
td.textContent = "!";
td.className = "permprompt";
}
if (permissionsTable[name][type] == json.DENY_ACTION) {
td.textContent = "✕";
td.className = "permdeny"
}
tr.appendChild(td);
}
table.appendChild(tr);
}
});
} else {
CloseUI();
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
%webideDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf8"/>
<link rel="stylesheet" href="chrome://webide/skin/tabledoc.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://webide/content/permissionstable.js"></script>
</head>
<body>
<div id="controls">
<a id="close">&deck_close;</a>
</div>
<h1>&permissionstable_title;</h1>
<table class="permissionstable">
<tr>
<th>&permissionstable_name_header;</th>
<th>type:web</th>
<th>type:privileged</th>
<th>type:certified</th>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,56 @@
/* 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 Cu = Components.utils;
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const {AppManager} = require("devtools/webide/app-manager");
const {Connection} = require("devtools/client/connection-manager");
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
document.querySelector("#close").onclick = CloseUI;
AppManager.on("app-manager-update", OnAppManagerUpdate);
BuildUI();
}, true);
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload);
AppManager.off("app-manager-update", OnAppManagerUpdate);
});
function CloseUI() {
window.parent.UI.openProject();
}
function OnAppManagerUpdate(event, what) {
if (what == "connection" || what == "list-tabs-response") {
BuildUI();
}
}
let getDescriptionPromise; // Used by tests
function BuildUI() {
let table = document.querySelector("table");
table.innerHTML = "";
if (AppManager.connection &&
AppManager.connection.status == Connection.Status.CONNECTED &&
AppManager.deviceFront) {
getDescriptionPromise = AppManager.deviceFront.getDescription();
getDescriptionPromise.then(json => {
for (let name in json) {
let tr = document.createElement("tr");
let td = document.createElement("td");
td.textContent = name;
tr.appendChild(td);
td = document.createElement("td");
td.textContent = json[name];
tr.appendChild(td);
table.appendChild(tr);
}
});
} else {
CloseUI();
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
%webideDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf8"/>
<link rel="stylesheet" href="chrome://webide/skin/tabledoc.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://webide/content/runtimedetails.js"></script>
</head>
<body>
<div id="controls">
<a id="close">&deck_close;</a>
</div>
<h1>&runtimedetails_title;</h1>
<table></table>
</body>
</html>

View File

@ -18,12 +18,19 @@ const {Connection} = require("devtools/client/connection-manager");
const {AppManager} = require("devtools/webide/app-manager");
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const ProjectEditor = require("projecteditor/projecteditor");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {GetAvailableAddons} = require("devtools/webide/addons");
const {GetTemplatesJSON, GetAddonsJSON} = require("devtools/webide/remote-resources");
const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
const HTML = "http://www.w3.org/1999/xhtml";
const HELP_URL = "https://developer.mozilla.org/Firefox_OS/Using_the_App_Manager#Troubleshooting";
// download some JSON early.
GetTemplatesJSON(true);
GetAddonsJSON(true);
// See bug 989619
console.log = console.log.bind(console);
console.warn = console.warn.bind(console);
@ -56,18 +63,32 @@ let UI = {
window.addEventListener("focus", this.onfocus, true);
AppProjects.load().then(() => {
let lastProjectLocation = Services.prefs.getCharPref("devtools.webide.lastprojectlocation");
if (lastProjectLocation) {
let lastProject = AppProjects.get(lastProjectLocation);
if (lastProject) {
AppManager.selectedProject = lastProject;
} else {
AppManager.selectedProject = null;
}
this.openLastProject();
});
// Auto install the ADB Addon Helper. Only once.
// If the user decides to uninstall the addon, we won't install it again.
let autoInstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
if (autoInstallADBHelper && !Devices.helperAddonInstalled) {
GetAvailableAddons().then(addons => {
addons.adb.install();
}, console.error);
}
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
},
openLastProject: function() {
let lastProjectLocation = Services.prefs.getCharPref("devtools.webide.lastprojectlocation");
if (lastProjectLocation) {
let lastProject = AppProjects.get(lastProjectLocation);
if (lastProject) {
AppManager.selectedProject = lastProject;
} else {
AppManager.selectedProject = null;
}
});
} else {
AppManager.selectedProject = null;
}
},
uninit: function() {
@ -203,7 +224,7 @@ let UI = {
}
}];
let nbox = document.querySelector("#body");
let nbox = document.querySelector("#notificationbox");
nbox.removeAllNotifications(true);
nbox.appendNotification(text, "webide:errornotification", null,
nbox.PRIORITY_WARNING_LOW, buttons);
@ -216,6 +237,28 @@ let UI = {
let simulatorListNode = document.querySelector("#runtime-panel-simulators");
let customListNode = document.querySelector("#runtime-panel-custom");
let noHelperNode = document.querySelector("#runtime-panel-noadbhelper");
let noUSBNode = document.querySelector("#runtime-panel-nousbdevice");
let noSimulatorNode = document.querySelector("#runtime-panel-nosimulator");
if (Devices.helperAddonInstalled) {
noHelperNode.setAttribute("hidden", "true");
} else {
noHelperNode.removeAttribute("hidden");
}
if (AppManager.runtimeList.usb.length == 0 && Devices.helperAddonInstalled) {
noUSBNode.removeAttribute("hidden");
} else {
noUSBNode.setAttribute("hidden", "true");
}
if (AppManager.runtimeList.simulator.length > 0) {
noSimulatorNode.setAttribute("hidden", "true");
} else {
noSimulatorNode.removeAttribute("hidden");
}
for (let [type, parent] of [
["usb", USBListNode],
["simulator", simulatorListNode],
@ -283,7 +326,7 @@ let UI = {
return this.projecteditor.loaded;
}
let projecteditorIframe = document.querySelector("#projecteditor");
let projecteditorIframe = document.querySelector("#deck-panel-projecteditor");
this.projecteditor = ProjectEditor.ProjectEditor(projecteditorIframe);
this.projecteditor.on("onEditorSave", (editor, resource) => {
AppManager.validateProject(AppManager.selectedProject);
@ -306,7 +349,7 @@ let UI = {
iconUrl: project.icon,
projectOverviewURL: "chrome://webide/content/details.xhtml",
validationStatus: status
});
}).then(null, console.error);
}, console.error);
},
@ -315,17 +358,12 @@ let UI = {
},
openProject: function() {
let detailsIframe = document.querySelector("#details");
let projecteditorIframe = document.querySelector("#projecteditor");
let project = AppManager.selectedProject;
// Nothing to show
if (!project) {
detailsIframe.setAttribute("hidden", "true");
projecteditorIframe.setAttribute("hidden", "true");
document.commandDispatcher.focusedElement = document.documentElement;
this.resetDeck();
return;
}
@ -342,16 +380,13 @@ let UI = {
if (project.type != "packaged" ||
!this.isProjectEditorEnabled() ||
forceDetailsOnly) {
detailsIframe.removeAttribute("hidden");
projecteditorIframe.setAttribute("hidden", "true");
document.commandDispatcher.focusedElement = document.documentElement;
this.selectDeckPanel("details");
return;
}
// Show ProjectEditor
detailsIframe.setAttribute("hidden", "true");
projecteditorIframe.removeAttribute("hidden");
this.selectDeckPanel("projecteditor");
this.getProjectEditor().then(() => {
this.updateProjectEditorHeader();
@ -362,6 +397,26 @@ let UI = {
}
},
/********** DECK **********/
resetFocus: function() {
document.commandDispatcher.focusedElement = document.documentElement;
},
selectDeckPanel: function(id) {
this.hidePanels();
this.resetFocus();
let deck = document.querySelector("#deck");
let panel = deck.querySelector("#deck-panel-" + id);
deck.selectedPanel = panel;
},
resetDeck: function() {
this.resetFocus();
let deck = document.querySelector("#deck");
deck.selectedPanel = null;
},
/********** COMMANDS **********/
updateCommands: function() {
@ -506,9 +561,7 @@ let UI = {
},
closeToolboxUI: function() {
let body = document.querySelector("#body");
body.removeAttribute("hidden");
this.resetFocus();
Services.prefs.setIntPref("devtools.toolbox.footer.height", this.toolboxIframe.height);
// We have to destroy the iframe, otherwise, the keybindings of webide don't work
@ -727,81 +780,11 @@ let Cmds = {
},
showPermissionsTable: function() {
return UI.busyUntil(AppManager.deviceFront.getRawPermissionsTable().then(json => {
let styleContent = "";
styleContent += "body {background:white; font-family: monospace}";
styleContent += "table {border-collapse: collapse}";
styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
styleContent += "th {min-width: 130px}";
styleContent += "td {text-align: center}";
styleContent += "th:first-of-type, td:first-of-type {text-align:left}";
styleContent += ".permallow {color:rgb(152, 207, 57)}";
styleContent += ".permprompt {color:rgb(0,158,237)}";
styleContent += ".permdeny {color:rgb(204,73,8)}";
let style = document.createElementNS(HTML, "style");
style.textContent = styleContent;
let table = document.createElementNS(HTML, "table");
table.innerHTML = "<tr><th>Name</th><th>type:web</th><th>type:privileged</th><th>type:certified</th></tr>";
let permissionsTable = json.rawPermissionsTable;
for (let name in permissionsTable) {
let tr = document.createElementNS(HTML, "tr");
let td = document.createElementNS(HTML, "td");
td.textContent = name;
tr.appendChild(td);
for (let type of ["app","privileged","certified"]) {
let td = document.createElementNS(HTML, "td");
if (permissionsTable[name][type] == json.ALLOW_ACTION) {
td.textContent = "✓";
td.className = "permallow";
}
if (permissionsTable[name][type] == json.PROMPT_ACTION) {
td.textContent = "!";
td.className = "permprompt";
}
if (permissionsTable[name][type] == json.DENY_ACTION) {
td.textContent = "✕";
td.className = "permdeny"
}
tr.appendChild(td);
}
table.appendChild(tr);
}
let body = document.createElementNS(HTML, "body");
body.appendChild(style);
body.appendChild(table);
let url = "data:text/html;charset=utf-8,";
url += encodeURIComponent(body.outerHTML);
UI.openInBrowser(url);
}), "showing permission table");
UI.selectDeckPanel("permissionstable");
},
showRuntimeDetails: function() {
return UI.busyUntil(AppManager.deviceFront.getDescription().then(json => {
let styleContent = "";
styleContent += "body {background:white; font-family: monospace}";
styleContent += "table {border-collapse: collapse}";
styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
let style = document.createElementNS(HTML, "style");
style.textContent = styleContent;
let table = document.createElementNS(HTML, "table");
for (let name in json) {
let tr = document.createElementNS(HTML, "tr");
let td = document.createElementNS(HTML, "td");
td.textContent = name;
tr.appendChild(td);
td = document.createElementNS(HTML, "td");
td.textContent = json[name];
tr.appendChild(td);
table.appendChild(tr);
}
let body = document.createElementNS(HTML, "body");
body.appendChild(style);
body.appendChild(table);
let url = "data:text/html;charset=utf-8,";
url += encodeURIComponent(body.outerHTML);
UI.openInBrowser(url);
}), "showing runtime details");
UI.selectDeckPanel("runtimedetails");
},
play: function() {
@ -846,4 +829,8 @@ let Cmds = {
showTroubleShooting: function() {
UI.openInBrowser(HELP_URL);
},
showAddons: function() {
UI.selectDeckPanel("addons");
},
}

View File

@ -41,6 +41,8 @@
<command id="cmd_showRuntimeDetails" oncommand="Cmds.showRuntimeDetails()" label="&runtimeMenu_showDetails_label;"/>
<command id="cmd_takeScreenshot" oncommand="Cmds.takeScreenshot()" label="&runtimeMenu_takeScreenshot_label;"/>
<command id="cmd_toggleEditor" oncommand="Cmds.toggleEditors()" label="&viewMenu_toggleEditor_label;"/>
<command id="cmd_showAddons" oncommand="Cmds.showAddons()"/>
<command id="cmd_showTroubleShooting" oncommand="Cmds.showTroubleShooting()"/>
<command id="cmd_play" oncommand="Cmds.play()"/>
<command id="cmd_stop" oncommand="Cmds.stop()"/>
<command id="cmd_toggleToolbox" oncommand="Cmds.toggleToolbox()"/>
@ -76,6 +78,7 @@
<menu id="menu-view" label="&viewMenu_label;" accesskey="&viewMenu_accesskey;">
<menupopup id="menu-ViewPopup">
<menuitem command="cmd_toggleEditor" key="key_toggleEditor" accesskey="&viewMenu_toggleEditor_accesskey;"/>
<menuitem command="cmd_showAddons" label="&viewMenu_showAddons_label;" accesskey="&viewMenu_showAddons_accesskey;"/>
</menupopup>
</menu>
@ -136,8 +139,11 @@
<panel id="runtime-panel" type="arrow" position="bottomcenter topright" consumeoutsideclicks="true" animate="false">
<vbox flex="1">
<label class="panel-header">&runtimePanel_USBDevices;</label>
<toolbarbutton class="panel-item-help" label="&runtimePanel_nousbdevice;" id="runtime-panel-nousbdevice" command="cmd_showTroubleShooting"/>
<toolbarbutton class="panel-item-help" label="&runtimePanel_noadbhelper;" id="runtime-panel-noadbhelper" command="cmd_showAddons"/>
<vbox id="runtime-panel-usbruntime"></vbox>
<label class="panel-header">&runtimePanel_simulators;</label>
<toolbarbutton class="panel-item-help" label="&runtimePanel_nosimulator;" id="runtime-panel-nosimulator" command="cmd_showAddons"/>
<vbox id="runtime-panel-simulators"></vbox>
<label class="panel-header">&runtimePanel_custom;</label>
<vbox id="runtime-panel-custom"></vbox>
@ -151,9 +157,14 @@
</popupset>
<notificationbox flex="1" id="body">
<iframe id="details" flex="1" hidden="true" src="details.xhtml"/>
<iframe id="projecteditor" flex="1" hidden="true"/>
<notificationbox flex="1" id="notificationbox">
<deck flex="1" id="deck" selectedIndex="-1">
<iframe id="deck-panel-details" flex="1" src="details.xhtml"/>
<iframe id="deck-panel-projecteditor" flex="1"/>
<iframe id="deck-panel-addons" flex="1" src="addons.xhtml"/>
<iframe id="deck-panel-permissionstable" flex="1" src="permissionstable.xhtml"/>
<iframe id="deck-panel-runtimedetails" flex="1" src="runtimedetails.xhtml"/>
</deck>
</notificationbox>
<splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>

View File

@ -38,6 +38,8 @@
<!ENTITY viewMenu_accesskey "V">
<!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
<!ENTITY viewMenu_toggleEditor_accesskey "E">
<!ENTITY viewMenu_showAddons_label "Manage simulators">
<!ENTITY viewMenu_showAddons_accesskey "M">
<!ENTITY projectButton_label "Open App">
<!ENTITY runtimeButton_label "Select Runtime">
@ -61,6 +63,9 @@
<!ENTITY runtimePanel_USBDevices "USB Devices">
<!ENTITY runtimePanel_simulators "Simulators">
<!ENTITY runtimePanel_custom "Custom">
<!ENTITY runtimePanel_nosimulator "Install Simulator">
<!ENTITY runtimePanel_noadbhelper "Install ADB Helper">
<!ENTITY runtimePanel_nousbdevice "Can't see your device?">
<!-- Lense -->
<!ENTITY details_valid_header "valid">
@ -76,3 +81,19 @@
<!ENTITY newAppHeader "Select template">
<!ENTITY newAppLoadingTemplate "Loading templates…">
<!ENTITY newAppProjectName "Project Name:">
<!-- Decks -->
<!ENTITY deck_close "close">
<!-- Addons -->
<!ENTITY addons_title "Extra Components:">
<!ENTITY addons_aboutaddons "Open Addons Manager">
<!-- Permissions Table -->
<!ENTITY permissionstable_title "Permissions Table">
<!ENTITY permissionstable_name_header "Name">
<!-- Runtime Details -->
<!ENTITY runtimedetails_title "Runtime Info">

View File

@ -26,3 +26,18 @@ error_listRunningApps=Can't get app list from device
error_cantConnectToApp=Can't connect to app: %1$S
error_cantInstallNotFullyConnected=Can't install project. Not fully connected.
error_cantInstallValidationErrors=Can't install project. Validation errors.
error_cantFetchAddonsJSON=Can't fetch the addon list: %S
addons_stable=stable
addons_unstable=unstable
addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
addons_install_button=install
addons_uninstall_button=uninstall
addons_adb_label=ADB Addon Helper
addons_adb_warning=USB devices won't be detected without this add-on
addons_status_unknown=?
addons_status_installed=Installed
addons_status_uninstalled=Not Installed
addons_status_preparing=preparing
addons_status_downloading=downloading
addons_status_installing=installing

View File

@ -0,0 +1,206 @@
/* 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 {Cu} = require("chrome");
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm");
const {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm");
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {GetAddonsJSON} = require("devtools/webide/remote-resources");
let SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
let ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
let SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
let ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
let OS = "";
if (platform.indexOf("Win") != -1) {
OS = "win32";
} else if (platform.indexOf("Mac") != -1) {
OS = "mac64";
} else if (platform.indexOf("Linux") != -1) {
if (platform.indexOf("x86_64") != -1) {
OS = "linux64";
} else {
OS = "linux";
}
}
Simulator.on("unregister", updateSimulatorAddons);
Simulator.on("register", updateSimulatorAddons);
Devices.on("addon-status-updated", updateAdbAddon);
function updateSimulatorAddons(event, version) {
GetAvailableAddons().then(addons => {
let foundAddon = null;
for (let addon of addons.simulators) {
if (addon.version == version) {
foundAddon = addon;
break;
}
}
if (!foundAddon) {
console.warn("An unknown simulator (un)registered", version);
return;
}
foundAddon.updateInstallStatus();
});
}
function updateAdbAddon() {
GetAvailableAddons().then(addons => {
addons.adb.updateInstallStatus();
});
}
let GetAvailableAddons_promise = null;
let GetAvailableAddons = exports.GetAvailableAddons = function() {
if (!GetAvailableAddons_promise) {
let deferred = promise.defer();
GetAvailableAddons_promise = deferred.promise;
let addons = {
simulators: [],
adb: null
}
GetAddonsJSON().then(json => {
for (let stability in json) {
for (let version of json[stability]) {
addons.simulators.push(new SimulatorAddon(stability, version));
}
}
addons.adb = new ADBAddon();
deferred.resolve(addons);
}, e => {
GetAvailableAddons_promise = null;
deferred.reject(e);
});
}
return GetAvailableAddons_promise;
}
function Addon() {}
Addon.prototype = {
_status: "unknown",
set status(value) {
if (this._status != value) {
this._status = value;
this.emit("update");
}
},
get status() {
return this._status;
},
install: function() {
if (this.status != "uninstalled") {
throw new Error("Not uninstalled");
}
this.status = "preparing";
AddonManager.getAddonByID(this.addonID, (addon) => {
if (addon && addon.userDisabled) {
addon.userDisabled = false;
} else {
AddonManager.getInstallForURL(this.xpiLink, (install) => {
install.addListener(this);
install.install();
}, "application/x-xpinstall");
}
});
},
uninstall: function() {
AddonManager.getAddonByID(this.addonID, (addon) => {
addon.uninstall();
});
},
installFailureHandler: function(install, message) {
this.status = "uninstalled";
this.emit("failure", message);
},
onDownloadStarted: function() {
this.status = "downloading";
},
onInstallStarted: function() {
this.status = "installing";
},
onDownloadProgress: function(install) {
if (install.maxProgress == -1) {
this.emit("progress", -1);
} else {
this.emit("progress", install.progress / install.maxProgress);
}
},
onInstallEnded: function({addon}) {
addon.userDisabled = false;
},
onDownloadCancelled: function(install) {
this.installFailureHandler(install, "Download cancelled");
},
onDownloadFailed: function(install) {
this.installFailureHandler(install, "Download failed");
},
onInstallCancelled: function(install) {
this.installFailureHandler(install, "Install cancelled");
},
onInstallFailed: function(install) {
this.installFailureHandler(install, "Install failed");
},
}
function SimulatorAddon(stability, version) {
EventEmitter.decorate(this);
this.stability = stability;
this.version = version;
this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, OS)
.replace(/#VERSION#/g, version)
.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
this.addonID = SIMULATOR_ADDON_ID.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
this.updateInstallStatus();
}
SimulatorAddon.prototype = Object.create(Addon.prototype, {
updateInstallStatus: {
enumerable: true,
value: function() {
let sim = Simulator.getByVersion(this.version);
if (sim) {
this.status = "installed";
} else {
this.status = "uninstalled";
}
}
},
});
function ADBAddon() {
EventEmitter.decorate(this);
this.xpiLink = ADB_LINK.replace(/#OS#/g, OS);
this.addonID = ADB_ADDON_ID;
this.updateInstallStatus();
}
ADBAddon.prototype = Object.create(Addon.prototype, {
updateInstallStatus: {
enumerable: true,
value: function() {
if (Devices.helperAddonInstalled) {
this.status = "installed";
} else {
this.status = "uninstalled";
}
}
},
});

View File

@ -0,0 +1,54 @@
/* 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 {Cu, CC} = require("chrome");
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
function getJSON(bypassCache, pref) {
if (!bypassCache) {
try {
let str = Services.prefs.getCharPref(pref + "_cache");
let json = JSON.parse(str);
return promise.resolve(json);
} catch(e) {/* no pref or invalid json. Let's continue */}
}
let deferred = promise.defer();
let xhr = new XMLHttpRequest();
xhr.onload = () => {
let json;
try {
json = JSON.parse(xhr.responseText);
} catch(e) {
return deferred.reject("Not valid JSON");
}
Services.prefs.setCharPref(pref + "_cache", xhr.responseText);
deferred.resolve(json);
}
xhr.onerror = (e) => {
deferred.reject("Network error");
}
xhr.open("get", Services.prefs.getCharPref(pref));
xhr.send();
return deferred.promise;
}
exports.GetTemplatesJSON = function(bypassCache) {
return getJSON(bypassCache, "devtools.webide.templatesURL");
}
exports.GetAddonsJSON = function(bypassCache) {
return getJSON(bypassCache, "devtools.webide.addonsURL");
}

View File

@ -56,12 +56,11 @@ SimulatorRuntime.prototype = {
return this.version;
},
getName: function() {
return this.version;
return Simulator.getByVersion(this.version).appinfo.label;
},
}
let gLocalRuntime = {
supportApps: false, // Temporary static value
connect: function(connection) {
if (!DebuggerServer.initialized) {
DebuggerServer.init();

View File

@ -0,0 +1,4 @@
{
"stable": ["1.0", "2.0"],
"unstable": ["3.0"]
}

View File

@ -3,6 +3,23 @@ support-files =
app/index.html
app/manifest.webapp
app.zip
addons/simulators.json
addons/fxos_1_0_simulator-linux.xpi
addons/fxos_1_0_simulator-linux64.xpi
addons/fxos_1_0_simulator-win32.xpi
addons/fxos_1_0_simulator-mac64.xpi
addons/fxos_2_0_simulator-linux.xpi
addons/fxos_2_0_simulator-linux64.xpi
addons/fxos_2_0_simulator-win32.xpi
addons/fxos_2_0_simulator-mac64.xpi
addons/fxos_3_0_simulator-linux.xpi
addons/fxos_3_0_simulator-linux64.xpi
addons/fxos_3_0_simulator-win32.xpi
addons/fxos_3_0_simulator-mac64.xpi
addons/adbhelper-linux.xpi
addons/adbhelper-linux64.xpi
addons/adbhelper-win32.xpi
addons/adbhelper-mac64.xpi
head.js
hosted_app.manifest
templates.json
@ -13,3 +30,5 @@ support-files =
[test_runtime.html]
[test_cli.html]
[test_manifestUpdate.html]
[test_addons.html]
[test_deviceinfo.html]

View File

@ -19,14 +19,29 @@ const TEST_BASE = "chrome://mochitests/content/chrome/browser/devtools/webide/te
Services.prefs.setBoolPref("devtools.webide.enabled", true);
Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true);
Services.prefs.setCharPref("devtools.webide.addonsURL", TEST_BASE + "addons/simulators.json");
Services.prefs.setCharPref("devtools.webide.simulatorAddonsURL", TEST_BASE + "addons/fxos_#SLASHED_VERSION#_simulator-#OS#.xpi");
Services.prefs.setCharPref("devtools.webide.adbAddonURL", TEST_BASE + "addons/adbhelper-#OS#.xpi");
Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.webide.templatesURL");
Services.prefs.clearUserPref("devtools.webide.enabled");
Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
Services.prefs.clearUserPref("devtools.webide.addonsURL");
Services.prefs.clearUserPref("devtools.webide.simulatorAddonsURL");
Services.prefs.clearUserPref("devtools.webide.adbAddonURL");
Services.prefs.clearUserPref("devtools.webide.autoInstallADBHelper", false);
});
function openWebIDE() {
function openWebIDE(autoInstallADBHelper) {
info("opening WebIDE");
if (!autoInstallADBHelper) {
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
}
let deferred = promise.defer();
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
@ -80,3 +95,18 @@ function nextTick() {
return deferred.promise;
}
function documentIsLoaded(doc) {
let deferred = promise.defer();
if (doc.readyState == "complete") {
deferred.resolve();
} else {
doc.addEventListener("readystatechange", function onChange() {
if (doc.readyState == "complete") {
doc.removeEventListener("readystatechange", onChange);
deferred.resolve();
}
});
}
return deferred.promise;
}

View File

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title></title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript;version=1.8" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<script type="application/javascript;version=1.8">
window.onload = function() {
SimpleTest.waitForExplicitFinish();
const {GetAvailableAddons} = require("devtools/webide/addons");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
let adbAddonsInstalled = promise.defer();
Devices.on("addon-status-updated", function onUpdate1() {
Devices.off("addon-status-updated", onUpdate1);
adbAddonsInstalled.resolve();
});
function onSimulatorInstalled(version) {
let deferred = promise.defer();
Simulator.on("register", function onUpdate() {
if (Simulator.getByVersion(version)) {
Simulator.off("register", onUpdate);
nextTick().then(deferred.resolve);
}
});
return deferred.promise;
}
function installSimulatorFromUI(doc, version) {
let li = doc.querySelector('[addon="simulator-' + version + '"]');
li.querySelector(".install-button").click();
return onSimulatorInstalled(version);
}
function uninstallSimulatorFromUI(doc, version) {
let deferred = promise.defer();
Simulator.on("unregister", function onUpdate() {
nextTick().then(() => {
let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + version + '"]');
if (li) {
Simulator.off("unregister", onUpdate);
deferred.resolve();
} else {
deferred.reject("Can't find item");
}
})
});
let li = doc.querySelector('[status="installed"][addon="simulator-' + version + '"]');
li.querySelector(".uninstall-button").click();
return deferred.promise;
}
function uninstallADBFromUI(doc) {
let deferred = promise.defer();
Devices.on("addon-status-updated", function onUpdate() {
nextTick().then(() => {
let li = doc.querySelector('[status="uninstalled"][addon="adb"]');
if (li) {
Devices.off("addon-status-updated", onUpdate);
deferred.resolve();
} else {
deferred.reject("Can't find item");
}
})
});
let li = doc.querySelector('[status="installed"][addon="adb"]');
li.querySelector(".uninstall-button").click();
return deferred.promise;
}
Task.spawn(function* () {
ok(!Devices.helperAddonInstalled, "Helper not installed");
let win = yield openWebIDE(true);
yield adbAddonsInstalled.promise;
ok(Devices.helperAddonInstalled, "Helper has been auto-installed");
yield nextTick();
let addons = yield GetAvailableAddons();
is(addons.simulators.length, 3, "3 simulator addons to install");
let sim10 = addons.simulators.filter(a => a.version == "1.0")[0];
sim10.install();
yield onSimulatorInstalled("1.0");
win.Cmds.showAddons();
let frame = win.document.querySelector("#deck-panel-addons");
let addonDoc = frame.contentWindow.document;
let lis;
lis = addonDoc.querySelectorAll("li");
is(lis.length, 4, "4 addons listed");
lis = addonDoc.querySelectorAll('li[status="installed"]');
is(lis.length, 2, "2 addons installed");
lis = addonDoc.querySelectorAll('li[status="uninstalled"]');
is(lis.length, 2, "2 addons uninstalled");
info("Uninstalling Simulator 2.0");
yield installSimulatorFromUI(addonDoc, "2.0");
info("Uninstalling Simulator 3.0");
yield installSimulatorFromUI(addonDoc, "3.0");
yield nextTick();
let panelNode = win.document.querySelector("#runtime-panel");
let items;
items = panelNode.querySelectorAll(".runtime-panel-item-usb");
is(items.length, 1, "Found one runtime button");
items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
is(items.length, 3, "Found 3 simulators button");
yield uninstallSimulatorFromUI(addonDoc, "1.0");
yield uninstallSimulatorFromUI(addonDoc, "2.0");
yield uninstallSimulatorFromUI(addonDoc, "3.0");
items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
is(items.length, 0, "No simulator listed");
let w = addonDoc.querySelector(".warning");
let display = addonDoc.defaultView.getComputedStyle(w).display
is(display, "none", "Warning about missing ADB hidden");
yield uninstallADBFromUI(addonDoc, "adb");
items = panelNode.querySelectorAll(".runtime-panel-item-usb");
is(items.length, 0, "No usb runtime listed");
display = addonDoc.defaultView.getComputedStyle(w).display
is(display, "block", "Warning about missing ADB present");
yield closeWebIDE(win);
SimpleTest.finish();
});
}
</script>
</body>
</html>

View File

@ -29,7 +29,7 @@
ok(appmgr.webAppsStore, "WebApps store ready");
// test error reporting
let nbox = win.document.querySelector("#body");
let nbox = win.document.querySelector("#notificationbox");
let notification = nbox.getNotificationWithValue("webide:errornotification");
ok(!notification, "No notification yet");
let deferred = promise.defer();

View File

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title></title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript;version=1.8" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<script type="application/javascript;version=1.8">
window.onload = function() {
SimpleTest.waitForExplicitFinish();
Task.spawn(function* () {
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
let win = yield openWebIDE();
let permIframe = win.document.querySelector("#deck-panel-permissionstable");
let infoIframe = win.document.querySelector("#deck-panel-runtimedetails");
yield documentIsLoaded(permIframe.contentWindow.document);
yield documentIsLoaded(infoIframe.contentWindow.document);
win.AppManager.update("runtimelist");
let panelNode = win.document.querySelector("#runtime-panel");
let items = panelNode.querySelectorAll(".runtime-panel-item-custom");
is(items.length, 2, "Found 2 custom runtimes button");
let deferred = promise.defer();
win.AppManager.on("app-manager-update", function onUpdate(e,w) {
if (w == "list-tabs-response") {
win.AppManager.off("app-manager-update", onUpdate);
deferred.resolve();
}
});
items[1].click();
yield deferred.promise;
yield nextTick();
let perm = win.document.querySelector("#cmd_showPermissionsTable");
let info = win.document.querySelector("#cmd_showRuntimeDetails");
ok(!perm.hasAttribute("disabled"), "perm cmd enabled");
ok(!info.hasAttribute("disabled"), "info cmd enabled");
let deck = win.document.querySelector("#deck");
win.Cmds.showRuntimeDetails();
is(deck.selectedPanel, infoIframe, "info iframe selected");
yield infoIframe.contentWindow.getRawPermissionsTablePromise;
yield nextTick();
// device info and permissions content is checked in other tests
// We just test one value to make sure we get something
let doc = infoIframe.contentWindow.document;
let trs = doc.querySelectorAll("tr");
let found = false;
for (let tr of trs) {
let [name,val] = tr.querySelectorAll("td");
if (name.textContent == "appid") {
found = true;
is(val.textContent, Services.appinfo.ID, "appid has the right value");
}
}
ok(found, "Found appid line");
win.Cmds.showPermissionsTable();
is(deck.selectedPanel, permIframe, "permission iframe selected");
yield infoIframe.contentWindow.getDescriptionPromise;
yield nextTick();
doc = permIframe.contentWindow.document;
trs = doc.querySelectorAll(".line");
found = false;
for (let tr of trs) {
let [name,v1,v2,v3] = tr.querySelectorAll("td");
if (name.textContent == "geolocation") {
found = true;
is(v1.className, "permprompt", "geolocation perm is valid");
is(v2.className, "permprompt", "geolocation perm is valid");
is(v3.className, "permprompt", "geolocation perm is valid");
}
}
ok(found, "Found geolocation line");
doc.querySelector("#close").click();
ok(!deck.selectedPanel, "No panel selected");
DebuggerServer.destroy();
yield closeWebIDE(win);
SimpleTest.finish();
}).then(null, e => {
ok(false, "Exception: " + e);
SimpleTest.finish();
});
}
</script>
</body>
</html>

View File

@ -18,8 +18,6 @@
window.onload = function() {
SimpleTest.waitForExplicitFinish();
Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
Task.spawn(function* () {
let win = yield openWebIDE();
let tmpDir = FileUtils.getDir("TmpD", []);
@ -37,7 +35,6 @@
// Clean up
tmpDir.remove(true);
Services.prefs.clearUserPref("devtools.webide.templatesURL");
yield closeWebIDE(win);
yield removeAllProjects();
SimpleTest.finish();

View File

@ -0,0 +1,121 @@
/* 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/. */
@import url("chrome://browser/skin/in-content/common.css");
html {
font: message-box;
font-size: 15px;
font-weight: normal;
margin: 0;
color: #737980;
background-image: linear-gradient(#fff, #ededed 100px);
height: 100%;
}
body {
padding: 20px;
}
h1 {
font-size: 2.5em;
font-weight: lighter;
line-height: 1.2;
margin: 0;
margin-bottom: .5em;
}
button {
line-height: 20px;
font-size: 1em;
height: 30px;
max-height: 30px;
min-width: 120px;
padding: 3px;
color: #737980;
border: 1px solid rgba(23,50,77,.4);
border-radius: 5px;
background-color: #f1f1f1;
background-image: linear-gradient(#fff, rgba(255,255,255,.1));
box-shadow: 0 1px 1px 0 #fff, inset 0 2px 2px 0 #fff;
text-shadow: 0 1px 1px #fefffe;
-moz-appearance: none;
-moz-border-top-colors: none !important;
-moz-border-right-colors: none !important;
-moz-border-bottom-colors: none !important;
-moz-border-left-colors: none !important;
}
button:hover {
background-image: linear-gradient(#fff, rgba(255,255,255,.6));
cursor: pointer;
}
button:hover:active {
background-image: linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.6));
}
progress {
height: 30px;
vertical-align: middle;
padding: 0;
width: 120px;
}
li {
margin: 20px 0;
}
.name {
display: inline-block;
min-width: 280px;
}
.status {
display: inline-block;
min-width: 120px;
}
.warning {
color: #F06;
margin: 0;
font-size: 0.9em;
}
#controls {
position: absolute;
top: 10px;
right: 10px;
}
#controls > a {
color: #4C9ED9;
font-size: small;
cursor: pointer;
border-bottom: 1px dotted;
}
#close {
margin-left: 10px;
}
li[status="unknown"],
li > .uninstall-button,
li > .install-button,
li > progress {
display: none;
}
li[status="installed"] > .uninstall-button,
li[status="uninstalled"] > .install-button,
li[status="preparing"] > progress,
li[status="downloading"] > progress,
li[status="installing"] > progress {
display: inline;
}
li:not([status="uninstalled"]) > .warning {
display: none;
}

View File

@ -9,3 +9,5 @@ webide.jar:
skin/details.css (details.css)
skin/newapp.css (newapp.css)
skin/throbber.svg (throbber.svg)
skin/addons.css (addons.css)
skin/tabledoc.css (tabledoc.css)

View File

@ -0,0 +1,54 @@
body {
background: white;
}
#controls {
position: fixed;
top: 10px;
right: 10px;
}
#controls > a {
color: #4C9ED9;
font-size: small;
cursor: pointer;
border-bottom: 1px dotted;
}
#close {
margin-left: 10px;
}
table {
font-family: monospace;
border-collapse: collapse;
}
th, td {
padding: 5px;
border: 1px solid #EEE;
}
th {
min-width: 130px;
}
.permissionstable td {
text-align: center;
}
th:first-of-type, td:first-of-type {
text-align: left;
}
.permallow {
color: rgb(152,207,57);
}
.permprompt {
color: rgb(0,158,237);
}
.permdeny {
color: rgb(204,73,8);
}

View File

@ -40,7 +40,7 @@ window:not(.busy) #action-busy {
.panel-button-anchor {
list-style-image: url('icons.png');
-moz-image-region: rect(43px, 563px, 61px, 535px);
width: 12;
width: 12px;
height: 7px;
margin-bottom: -5px;
}
@ -118,13 +118,19 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
width: 180px;
}
.panel-item {
.panel-item,
.panel-item-help {
padding: 3px 12px;
margin: 0;
-moz-appearance: none;
}
.panel-item:hover {
.panel-item-help {
font-size: 0.9em;
}
.panel-item:hover,
.panel-item-help:hover {
background: #CBF0FE;
}
@ -151,7 +157,8 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
height: 18px;
}
.panel-item > .toolbarbutton-text {
.panel-item > .toolbarbutton-text,
.panel-item-help > .toolbarbutton-text {
text-align: start;
}
@ -214,7 +221,7 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
/* Main view */
#body {
#deck {
background-color: rgb(225, 225, 225);
background-image: url('chrome://browser/skin/devtools/app-manager/rocket.svg'), url('chrome://browser/skin/devtools/app-manager/noise.png');
background-repeat: no-repeat, repeat;

View File

@ -4,6 +4,12 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
pref("devtools.webide.showProjectEditor", true);
pref("devtools.webide.templatesURL", "http://people.mozilla.org/~prouget/webidetemplates/template.json"); // See bug 1021504
pref("devtools.webide.templatesURL", "http://code.cdn.mozilla.net/templates/list.json");
pref("devtools.webide.autoinstallADBHelper", true);
pref("devtools.webide.lastprojectlocation", "");
pref("devtools.webide.enableLocalRuntime", false);
pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");

View File

@ -1822,19 +1822,39 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
}
/* Tabbrowser arrowscrollbox arrows */
.tabbrowser-arrowscrollbox > .scrollbutton-up > .toolbarbutton-icon,
.tabbrowser-arrowscrollbox > .scrollbutton-down > .toolbarbutton-icon {
-moz-appearance: none;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up,
.tabbrowser-arrowscrollbox > .scrollbutton-down {
-moz-appearance: none;
list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
margin: 0 0 @tabToolbarNavbarOverlap@;
}
#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-up,
#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-down {
list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png);
}
.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled],
.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] {
opacity: .4;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
transform: scaleX(-1);
}
.tabbrowser-arrowscrollbox > .scrollbutton-down {
transition: 1s box-shadow ease-out;
border-radius: 4px;
transition: 1s background-color ease-out;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
box-shadow: 0 0 5px 5px Highlight inset;
background-color: Highlight;
transition: none;
}

View File

@ -163,6 +163,8 @@ browser.jar:
skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)
skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
skin/classic/browser/tabbrowser/tab-background-start.png (tabbrowser/tab-background-start.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

View File

@ -616,12 +616,13 @@ this.DOMApplicationRegistry = {
}.bind(this)).then(null, Cu.reportError);
},
updateDataStore: function(aId, aOrigin, aManifestURL, aManifest, aAppStatus) {
// Just Certified Apps can use DataStores
let prefName = "dom.testing.datastore_enabled_for_hosted_apps";
if (aAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
(Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
!Services.prefs.getBoolPref(prefName))) {
updateDataStore: function(aId, aOrigin, aManifestURL, aManifest) {
let uri = Services.io.newURI(aOrigin, null, null);
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
let principal = secMan.getAppCodebasePrincipal(uri, aId,
/*mozbrowser*/ false);
if (!dataStoreService.checkPermission(principal)) {
return;
}
@ -1543,7 +1544,7 @@ this.DOMApplicationRegistry = {
true);
}
this.updateDataStore(this.webapps[id].localId, app.origin,
app.manifestURL, newManifest, app.appStatus);
app.manifestURL, newManifest);
this.broadcastMessage("Webapps:UpdateState", {
app: app,
manifest: newManifest,
@ -1961,7 +1962,7 @@ this.DOMApplicationRegistry = {
}
this.updateDataStore(this.webapps[aId].localId, aApp.origin,
aApp.manifestURL, aApp.manifest, aApp.appStatus);
aApp.manifestURL, aApp.manifest);
aApp.name = manifest.name;
aApp.csp = manifest.csp || "";
@ -2494,8 +2495,7 @@ this.DOMApplicationRegistry = {
}
this.updateDataStore(this.webapps[id].localId, this.webapps[id].origin,
this.webapps[id].manifestURL, jsonManifest,
this.webapps[id].appStatus);
this.webapps[id].manifestURL, jsonManifest);
}
for each (let prop in ["installState", "downloadAvailable", "downloading",
@ -2631,7 +2631,7 @@ this.DOMApplicationRegistry = {
}
this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
aNewApp.manifestURL, aManifest, aNewApp.appStatus);
aNewApp.manifestURL, aManifest);
this.broadcastMessage("Webapps:UpdateState", {
app: app,

View File

@ -2264,25 +2264,7 @@ Navigator::HasDataStoreSupport(nsIPrincipal* aPrincipal)
{
workers::AssertIsOnMainThread();
// First of all, the general pref has to be turned on.
bool enabled = false;
Preferences::GetBool("dom.datastore.enabled", &enabled);
if (!enabled) {
return false;
}
// Just for testing, we can enable DataStore for any kind of app.
if (Preferences::GetBool("dom.testing.datastore_enabled_for_hosted_apps", false)) {
return true;
}
uint16_t status;
if (NS_FAILED(aPrincipal->GetAppStatus(&status))) {
return false;
}
// Only support DataStore API for certified apps for now.
return status == nsIPrincipal::APP_STATUS_CERTIFIED;
return DataStoreService::CheckPermission(aPrincipal);
}
// A WorkerMainThreadRunnable to synchronously dispatch the call of

View File

@ -0,0 +1,243 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "CameraPreferences.h"
#include "CameraCommon.h"
#include "mozilla/Monitor.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Preferences.h"
using namespace mozilla;
/* statics */
static StaticAutoPtr<Monitor> sPrefMonitor;
StaticAutoPtr<nsCString> CameraPreferences::sPrefTestEnabled;
StaticAutoPtr<nsCString> CameraPreferences::sPrefHardwareTest;
StaticAutoPtr<nsCString> CameraPreferences::sPrefGonkParameters;
nsresult CameraPreferences::sPrefCameraControlMethodErrorOverride = NS_OK;
nsresult CameraPreferences::sPrefCameraControlAsyncErrorOverride = NS_OK;
/* static */
nsresult
CameraPreferences::UpdatePref(const char* aPref, nsresult& aVal)
{
uint32_t val;
nsresult rv = Preferences::GetUint(aPref, &val);
if (NS_SUCCEEDED(rv)) {
aVal = static_cast<nsresult>(val);
}
return rv;
}
/* static */
nsresult
CameraPreferences::UpdatePref(const char* aPref, nsACString& aVal)
{
nsCString val;
nsresult rv = Preferences::GetCString(aPref, &val);
if (NS_SUCCEEDED(rv)) {
aVal = val;
}
return rv;
}
/* static */
CameraPreferences::Pref CameraPreferences::sPrefs[] = {
{
"camera.control.test.enabled",
kPrefValueIsCString,
{ &sPrefTestEnabled }
},
{
"camera.control.test.hardware",
kPrefValueIsCString,
{ &sPrefHardwareTest }
},
#ifdef MOZ_B2G
{
"camera.control.test.hardware.gonk.parameters",
kPrefValueIsCString,
{ &sPrefGonkParameters }
},
#endif
{
"camera.control.test.method.error",
kPrefValueIsNSResult,
{ &sPrefCameraControlMethodErrorOverride }
},
{
"camera.control.test.async.error",
kPrefValueIsNSResult,
{ &sPrefCameraControlAsyncErrorOverride }
},
};
/* static */
uint32_t
CameraPreferences::PrefToIndex(const char* aPref)
{
for (uint32_t i = 0; i < ArrayLength(sPrefs); ++i) {
if (strcmp(aPref, sPrefs[i].mPref) == 0) {
return i;
}
}
return kPrefNotFound;
}
/* static */
void
CameraPreferences::PreferenceChanged(const char* aPref, void* aClosure)
{
MonitorAutoLock mon(*sPrefMonitor);
uint32_t i = PrefToIndex(aPref);
if (i == kPrefNotFound) {
DOM_CAMERA_LOGE("Preference '%s' is not tracked by CameraPreferences\n", aPref);
return;
}
Pref& p = sPrefs[i];
nsresult rv;
switch (p.mValueType) {
case kPrefValueIsNSResult:
{
nsresult& v = *p.mValue.mAsNsResult;
rv = UpdatePref(aPref, v);
if (NS_SUCCEEDED(rv)) {
DOM_CAMERA_LOGI("Preference '%s' has changed, 0x%x\n", aPref, v);
}
}
break;
case kPrefValueIsCString:
{
nsCString& v = **p.mValue.mAsCString;
rv = UpdatePref(aPref, v);
if (NS_SUCCEEDED(rv)) {
DOM_CAMERA_LOGI("Preference '%s' has changed, '%s'\n", aPref, v.get());
}
}
break;
default:
MOZ_ASSERT_UNREACHABLE("Unhandled preference value type!");
return;
}
#ifdef DEBUG
if (NS_FAILED(rv)) {
nsCString msg;
msg.AppendPrintf("Failed to update pref '%s' (0x%x)\n", aPref, rv);
NS_WARNING(msg.get());
}
#endif
}
/* static */
bool
CameraPreferences::Initialize()
{
DOM_CAMERA_LOGI("Initializing camera preference callbacks\n");
nsresult rv;
sPrefMonitor = new Monitor("CameraPreferences.sPrefMonitor");
sPrefTestEnabled = new nsCString();
sPrefHardwareTest = new nsCString();
sPrefGonkParameters = new nsCString();
for (uint32_t i = 0; i < ArrayLength(sPrefs); ++i) {
rv = Preferences::RegisterCallbackAndCall(CameraPreferences::PreferenceChanged,
sPrefs[i].mPref);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
}
DOM_CAMERA_LOGI("Camera preferences initialized\n");
return true;
}
/* static */
void
CameraPreferences::Shutdown()
{
DOM_CAMERA_LOGI("Shutting down camera preference callbacks\n");
for (uint32_t i = 0; i < ArrayLength(sPrefs); ++i) {
Preferences::UnregisterCallback(CameraPreferences::PreferenceChanged,
sPrefs[i].mPref);
}
sPrefTestEnabled = nullptr;
sPrefHardwareTest = nullptr;
sPrefGonkParameters = nullptr;
sPrefMonitor = nullptr;
DOM_CAMERA_LOGI("Camera preferences shut down\n");
}
/* static */
bool
CameraPreferences::GetPref(const char* aPref, nsACString& aVal)
{
MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
MonitorAutoLock mon(*sPrefMonitor);
uint32_t i = PrefToIndex(aPref);
if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
return false;
}
if (sPrefs[i].mValueType != kPrefValueIsCString) {
DOM_CAMERA_LOGW("Preference '%s' is not a string type\n", aPref);
return false;
}
StaticAutoPtr<nsCString>* s = sPrefs[i].mValue.mAsCString;
if (!*s) {
DOM_CAMERA_LOGE("Preference '%s' cache is not initialized\n", aPref);
return false;
}
if ((*s)->IsEmpty()) {
DOM_CAMERA_LOGI("Preference '%s' is not set\n", aPref);
return false;
}
DOM_CAMERA_LOGI("Preference '%s', got '%s'\n", aPref, (*s)->get());
aVal = **s;
return true;
}
/* static */
bool
CameraPreferences::GetPref(const char* aPref, nsresult& aVal)
{
MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
MonitorAutoLock mon(*sPrefMonitor);
uint32_t i = PrefToIndex(aPref);
if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
return false;
}
if (sPrefs[i].mValueType != kPrefValueIsNSResult) {
DOM_CAMERA_LOGW("Preference '%s' is not an nsresult type\n", aPref);
return false;
}
nsresult v = *sPrefs[i].mValue.mAsNsResult;
if (v == NS_OK) {
DOM_CAMERA_LOGI("Preference '%s' is not set\n", aPref);
return false;
}
DOM_CAMERA_LOGI("Preference '%s', got 0x%x\n", aPref, v);
aVal = v;
return true;
}

View File

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef DOM_CAMERA_CAMERAPREFERENCES_H
#define DOM_CAMERA_CAMERAPREFERENCES_H
#include "nsString.h"
namespace mozilla {
template<class T> class StaticAutoPtr;
class CameraPreferences
{
public:
static bool Initialize();
static void Shutdown();
static bool GetPref(const char* aPref, nsACString& aVal);
static bool GetPref(const char* aPref, nsresult& aVal);
protected:
static const uint32_t kPrefNotFound = UINT32_MAX;
static uint32_t PrefToIndex(const char* aPref);
static void PreferenceChanged(const char* aPref, void* aClosure);
static nsresult UpdatePref(const char* aPref, nsresult& aVar);
static nsresult UpdatePref(const char* aPref, nsACString& aVar);
enum PrefValueType {
kPrefValueIsNSResult,
kPrefValueIsCString
};
struct Pref {
const char* const mPref;
PrefValueType mValueType;
union {
// The 'mAsVoid' member must be first and is required to allow 'mValue'
// to be initialized with any pointer type, as not all of our platforms
// support the use of designated initializers; in their absence, only
// the first element of a union can be statically initialized, and
// 'void*' lets us stuff any pointer type into it.
void* mAsVoid;
StaticAutoPtr<nsCString>* mAsCString;
nsresult* mAsNsResult;
} mValue;
};
static Pref sPrefs[];
static StaticAutoPtr<nsCString> sPrefTestEnabled;
static StaticAutoPtr<nsCString> sPrefHardwareTest;
static StaticAutoPtr<nsCString> sPrefGonkParameters;
static nsresult sPrefCameraControlMethodErrorOverride;
static nsresult sPrefCameraControlAsyncErrorOverride;
private:
// static class only
CameraPreferences();
~CameraPreferences();
};
} // namespace mozilla
#endif // DOM_CAMERA_CAMERAPREFERENCES_H

View File

@ -23,7 +23,7 @@
#include "base/basictypes.h"
#include "nsDebug.h"
#include "mozilla/layers/TextureClient.h"
#include "mozilla/Preferences.h"
#include "CameraPreferences.h"
#include "mozilla/RefPtr.h"
#include "GonkCameraControl.h"
#include "GonkNativeWindow.h"
@ -212,8 +212,8 @@ GonkCameraHardware::Connect(mozilla::nsGonkCameraControl* aTarget, uint32_t aCam
return nullptr;
}
const nsAdoptingCString& test =
mozilla::Preferences::GetCString("camera.control.test.enabled");
nsCString test;
CameraPreferences::GetPref("camera.control.test.enabled", test);
sp<GonkCameraHardware> cameraHardware;
if (test.EqualsASCII("hardware")) {
NS_WARNING("Using test Gonk hardware layer");

View File

@ -20,7 +20,7 @@
#include "CameraCommon.h"
#include "GonkCameraControl.h"
#include "mozilla/Preferences.h"
#include "CameraPreferences.h"
#include "TestGonkCameraControl.h"
using namespace mozilla;
@ -117,13 +117,12 @@ ICameraControl::GetListOfCameras(nsTArray<nsString>& aList)
return NS_OK;
}
static const char* sTestModeEnabled = "camera.control.test.enabled";
// implementation-specific camera factory
already_AddRefed<ICameraControl>
ICameraControl::Create(uint32_t aCameraId)
{
const nsAdoptingCString& test = Preferences::GetCString(sTestModeEnabled);
nsCString test;
CameraPreferences::GetPref("camera.control.test.enabled", test);
nsRefPtr<nsGonkCameraControl> control;
if (test.EqualsASCII("control")) {
NS_WARNING("Using test CameraControl layer");

View File

@ -15,13 +15,10 @@
*/
#include "TestGonkCameraControl.h"
#include "mozilla/Preferences.h"
#include "CameraPreferences.h"
using namespace mozilla;
static const char* sMethodErrorOverride = "camera.control.test.method.error";
static const char* sAsyncErrorOverride = "camera.control.test.async.error";
TestGonkCameraControl::TestGonkCameraControl(uint32_t aCameraId)
: nsGonkCameraControl(aCameraId)
{
@ -37,9 +34,8 @@ TestGonkCameraControl::~TestGonkCameraControl()
nsresult
TestGonkCameraControl::ForceMethodFailWithCodeInternal(const char* aFile, int aLine)
{
nsresult rv =
static_cast<nsresult>(Preferences::GetInt(sMethodErrorOverride,
static_cast<int32_t>(NS_OK)));
nsresult rv = NS_OK;
CameraPreferences::GetPref("camera.control.test.method.error", rv);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGI("[%s:%d] CameraControl method error override: 0x%x\n",
aFile, aLine, rv);
@ -50,9 +46,8 @@ TestGonkCameraControl::ForceMethodFailWithCodeInternal(const char* aFile, int aL
nsresult
TestGonkCameraControl::ForceAsyncFailWithCodeInternal(const char* aFile, int aLine)
{
nsresult rv =
static_cast<nsresult>(Preferences::GetInt(sAsyncErrorOverride,
static_cast<int32_t>(NS_OK)));
nsresult rv = NS_OK;
CameraPreferences::GetPref("camera.control.test.async.error", rv);
if (NS_FAILED(rv)) {
DOM_CAMERA_LOGI("[%s:%d] CameraControl async error override: 0x%x\n",
aFile, aLine, rv);

View File

@ -16,7 +16,7 @@
#include "TestGonkCameraHardware.h"
#include "mozilla/Preferences.h"
#include "CameraPreferences.h"
#include "nsThreadUtils.h"
using namespace android;
@ -52,7 +52,8 @@ TestGonkCameraHardware::Init()
const nsCString
TestGonkCameraHardware::TestCase()
{
const nsCString test = Preferences::GetCString("camera.control.test.hardware");
nsCString test;
CameraPreferences::GetPref("camera.control.test.hardware", test);
return test;
}
@ -77,7 +78,8 @@ TestGonkCameraHardware::GetExtraParameters()
* may contain equals signs or semicolons. We don't enforce that here
* so that we can also test correct handling of improperly-formatted values.
*/
const nsCString parameters = Preferences::GetCString("camera.control.test.hardware.gonk.parameters");
nsCString parameters;
CameraPreferences::GetPref("camera.control.test.hardware.gonk.parameters", parameters);
DOM_CAMERA_LOGA("TestGonkCameraHardware : extra-parameters '%s'\n",
parameters.get());
return parameters;

View File

@ -9,13 +9,13 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
EXPORTS += [
'CameraCommon.h',
'CameraPreviewMediaStream.h',
'CameraPreferences.h',
'DOMCameraManager.h',
'GonkCameraControl.h',
]
SOURCES += [
'CameraControlImpl.cpp',
'CameraPreferences.cpp',
'CameraPreviewMediaStream.cpp',
'CameraRecorderProfiles.cpp',
'DOMCameraCapabilities.cpp',

View File

@ -19,6 +19,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService",
"@mozilla.org/datastore-service;1",
"nsIDataStoreService");
this.DataStoreChangeNotifier = {
children: [],
messages: [ "DataStore:Changed", "DataStore:RegisterForMessages",
@ -65,17 +69,21 @@ this.DataStoreChangeNotifier = {
receiveMessage: function(aMessage) {
debug("receiveMessage");
debug("receiveMessage ");
// No check has to be done when the message is 'child-process-shutdown'
// because at this point the target is already disconnected from
// nsFrameMessageManager, so that assertAppHasStatus will always fail.
let prefName = 'dom.testing.datastore_enabled_for_hosted_apps';
if (aMessage.name != 'child-process-shutdown' &&
(Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID ||
!Services.prefs.getBoolPref(prefName)) &&
!aMessage.target.assertAppHasStatus(Ci.nsIPrincipal.APP_STATUS_CERTIFIED)) {
return;
// No check has to be done when the message is 'child-process-shutdown'.
if (aMessage.name != "child-process-shutdown") {
if (!("principal" in aMessage)) {
return;
}
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
let uri = Services.io.newURI(aMessage.principal.origin, null, null);
let principal = secMan.getAppCodebasePrincipal(uri,
aMessage.principal.appId, aMessage.principal.isInBrowserElement);
if (!principal || !dataStoreService.checkPermission(principal)) {
return;
}
}
switch (aMessage.name) {

View File

@ -100,7 +100,9 @@ DataStore.prototype = {
cpmm.addMessageListener("DataStore:Changed:Return:OK", this);
cpmm.sendAsyncMessage("DataStore:RegisterForMessages",
{ store: this._name, owner: this._owner,
innerWindowID: this._innerWindowID });
innerWindowID: this._innerWindowID },
null,
this._window.document.nodePrincipal);
},
observe: function(aSubject, aTopic, aData) {
@ -110,7 +112,9 @@ DataStore.prototype = {
cpmm.removeMessageListener("DataStore:Changed:Return:OK", this);
cpmm.sendAsyncMessage("DataStore:UnregisterForMessages",
{ innerWindowID: this._innerWindowID });
{ innerWindowID: this._innerWindowID },
null,
this._window.document.nodePrincipal);
this._shuttingdown = true;
this._db.close();
}
@ -336,7 +340,9 @@ DataStore.prototype = {
cpmm.sendAsyncMessage("DataStore:Changed",
{ store: this.name, owner: this._owner,
message: { revisionId: aRevisionId, id: aId,
operation: aOperation, owner: this._owner } } );
operation: aOperation, owner: this._owner } },
null,
this._window.document.nodePrincipal);
},
receiveMessage: function(aMessage) {

View File

@ -903,7 +903,7 @@ DataStoreService::GetDataStores(nsIDOMWindow* aWindow,
return NS_OK;
}
rv = GetDataStoreInfos(aName, appId, stores);
rv = GetDataStoreInfos(aName, appId, principal, stores);
if (NS_FAILED(rv)) {
RejectPromise(window, promise, rv);
promise.forget(aDataStores);
@ -1043,6 +1043,7 @@ DataStoreService::GetDataStoresResolve(nsPIDOMWindow* aWindow,
nsresult
DataStoreService::GetDataStoreInfos(const nsAString& aName,
uint32_t aAppId,
nsIPrincipal* aPrincipal,
nsTArray<DataStoreInfo>& aStores)
{
AssertIsInMainProcess();
@ -1064,15 +1065,7 @@ DataStoreService::GetDataStoreInfos(const nsAString& aName,
return NS_ERROR_DOM_SECURITY_ERR;
}
uint16_t status;
rv = app->GetAppStatus(&status);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (status != nsIPrincipal::APP_STATUS_CERTIFIED &&
!Preferences::GetBool("dom.testing.datastore_enabled_for_hosted_apps",
false)) {
if (!DataStoreService::CheckPermission(aPrincipal)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
@ -1095,6 +1088,45 @@ DataStoreService::GetDataStoreInfos(const nsAString& aName,
return NS_OK;
}
bool
DataStoreService::CheckPermission(nsIPrincipal* aPrincipal)
{
// First of all, the general pref has to be turned on.
bool enabled = false;
Preferences::GetBool("dom.datastore.enabled", &enabled);
if (!enabled) {
return false;
}
// Just for testing, we can enable DataStore for any kind of app.
if (Preferences::GetBool("dom.testing.datastore_enabled_for_hosted_apps", false)) {
return true;
}
if (!aPrincipal) {
return false;
}
uint16_t status;
if (NS_FAILED(aPrincipal->GetAppStatus(&status))) {
return false;
}
// Only support DataStore API for certified apps for now.
return status == nsIPrincipal::APP_STATUS_CERTIFIED;
}
NS_IMETHODIMP
DataStoreService::CheckPermission(nsIPrincipal* aPrincipal,
bool* aResult)
{
MOZ_ASSERT(NS_IsMainThread());
*aResult = DataStoreService::CheckPermission(aPrincipal);
return NS_OK;
}
// This method is called when an app with DataStores is deleted.
void
DataStoreService::DeleteDataStores(uint32_t aAppId)
@ -1311,7 +1343,7 @@ DataStoreService::GetDataStoresFromIPC(const nsAString& aName,
}
nsTArray<DataStoreInfo> stores;
rv = GetDataStoreInfos(aName, appId, stores);
rv = GetDataStoreInfos(aName, appId, aPrincipal, stores);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

View File

@ -48,6 +48,8 @@ public:
static void Shutdown();
static bool CheckPermission(nsIPrincipal* principal);
nsresult GenerateUUID(nsAString& aID);
nsresult GetDataStoresFromIPC(const nsAString& aName,
@ -82,6 +84,7 @@ private:
const nsTArray<DataStoreInfo>& aStores);
nsresult GetDataStoreInfos(const nsAString& aName, uint32_t aAppId,
nsIPrincipal* aPrincipal,
nsTArray<DataStoreInfo>& aStores);
void DeleteDataStores(uint32_t aAppId);

View File

@ -6,8 +6,9 @@
#include "nsISupports.idl"
interface nsIDOMWindow;
interface nsIPrincipal;
[scriptable, uuid(0a050c4f-d292-4a14-8712-09bc1019840a)]
[scriptable, uuid(43a731b9-0b5d-400a-8711-8c912c3c3572)]
interface nsIDataStoreService : nsISupports
{
void installDataStore(in unsigned long appId,
@ -24,4 +25,6 @@ interface nsIDataStoreService : nsISupports
nsISupports getDataStores(in nsIDOMWindow window,
in DOMString name);
boolean checkPermission(in nsIPrincipal principal);
};

View File

@ -132,6 +132,7 @@ using namespace mozilla::system;
#include "mozilla/IMEStateManager.h"
#include "nsDocument.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "CameraPreferences.h"
using namespace mozilla;
using namespace mozilla::net;
@ -296,6 +297,8 @@ nsLayoutStatics::Initialize()
CounterStyleManager::InitializeBuiltinCounterStyles();
CameraPreferences::Initialize();
return NS_OK;
}
@ -426,4 +429,6 @@ nsLayoutStatics::Shutdown()
nsDocument::XPCOMShutdown();
CacheObserver::Shutdown();
CameraPreferences::Shutdown();
}

View File

@ -2706,20 +2706,24 @@ public class GeckoAppShell
}
@WrapElementForJNI(allowMultithread = true, narrowChars = true)
static URLConnection getConnection(String url) throws MalformedURLException, IOException {
String spec;
if (url.startsWith("android://")) {
spec = url.substring(10);
} else {
spec = url.substring(8);
}
static URLConnection getConnection(String url) {
try {
String spec;
if (url.startsWith("android://")) {
spec = url.substring(10);
} else {
spec = url.substring(8);
}
// if the colon got stripped, put it back
int colon = spec.indexOf(':');
if (colon == -1 || colon > spec.indexOf('/')) {
spec = spec.replaceFirst("/", ":/");
// if the colon got stripped, put it back
int colon = spec.indexOf(':');
if (colon == -1 || colon > spec.indexOf('/')) {
spec = spec.replaceFirst("/", ":/");
}
} catch(Exception ex) {
return null;
}
return new URL(spec).openConnection();
return null;
}
@WrapElementForJNI(allowMultithread = true, narrowChars = true)

View File

@ -256,6 +256,7 @@ size. -->
<!ENTITY new_tab "New Tab">
<!ENTITY new_private_tab "New Private Tab">
<!ENTITY close_all_tabs "Close All Tabs">
<!ENTITY close_private_tabs "Close Private Tabs">
<!ENTITY tabs_normal "Tabs">
<!ENTITY tabs_private "Private">
<!ENTITY tabs_synced "Synced">

View File

@ -20,10 +20,11 @@ import android.widget.PopupWindow;
* A popup to show the inflated MenuPanel.
*/
public class MenuPopup extends PopupWindow {
private LinearLayout mPanel;
private final LinearLayout mPanel;
private int mYOffset;
private int mPopupWidth;
private final int mYOffset;
private final int mPopupWidth;
private final int mPopupMinHeight;
public MenuPopup(Context context) {
super(context);
@ -32,6 +33,7 @@ public class MenuPopup extends PopupWindow {
mYOffset = context.getResources().getDimensionPixelSize(R.dimen.menu_popup_offset);
mPopupWidth = context.getResources().getDimensionPixelSize(R.dimen.menu_popup_width);
mPopupMinHeight = context.getResources().getDimensionPixelSize(R.dimen.menu_item_row_height);
// Setting a null background makes the popup to not close on touching outside.
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
@ -63,6 +65,12 @@ public class MenuPopup extends PopupWindow {
*/
@Override
public void showAsDropDown(View anchor) {
showAsDropDown(anchor, 0, -mYOffset);
// Set a height, so that the popup will not be displayed below the bottom of the screen.
setHeight(mPopupMinHeight);
// Attempt to align the center of the popup with the center of the anchor. If the anchor is
// near the edge of the screen, the popup will just align with the edge of the screen.
final int xOffset = anchor.getWidth()/2 - mPopupWidth/2;
showAsDropDown(anchor, xOffset, -mYOffset);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

View File

@ -6,12 +6,24 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageButton android:id="@+id/add_tab"
android:layout_width="fill_parent"
android:layout_width="@dimen/browser_toolbar_height"
android:layout_height="@dimen/browser_toolbar_height"
android:padding="14dip"
android:src="@drawable/tab_new_level"
android:contentDescription="@string/new_tab"
android:background="@drawable/action_bar_button_inverse"
android:gravity="center"/>
android:background="@drawable/action_bar_button_inverse"/>
<View android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1.0"/>
<ImageButton android:id="@+id/menu"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_height"
android:layout_height="@dimen/browser_toolbar_height"
android:padding="@dimen/browser_toolbar_button_padding"
android:src="@drawable/menu_tabs"
android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"/>
</merge>

View File

@ -25,4 +25,13 @@
android:contentDescription="@string/new_tab"
android:background="@drawable/action_bar_button_inverse"/>
<ImageButton android:id="@+id/menu"
style="@style/UrlBar.ImageButton"
android:layout_width="@dimen/browser_toolbar_height"
android:layout_height="@dimen/browser_toolbar_height"
android:padding="@dimen/browser_toolbar_button_padding"
android:src="@drawable/menu_tabs"
android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"/>
</merge>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_tab"
android:title="@string/new_tab"/>
<item android:id="@+id/new_private_tab"
android:title="@string/new_private_tab"/>
<item android:id="@+id/close_all_tabs"
android:title="@string/close_all_tabs"/>
<item android:id="@+id/close_private_tabs"
android:title="@string/close_private_tabs"/>
</menu>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_tab"
android:title="@string/new_tab"/>
<item android:id="@+id/new_private_tab"
android:title="@string/new_private_tab"/>
<item android:id="@+id/close_all_tabs"
android:title="@string/close_all_tabs"/>
<item android:id="@+id/close_private_tabs"
android:title="@string/close_private_tabs"/>
</menu>

View File

@ -246,6 +246,7 @@
<string name="new_tab">&new_tab;</string>
<string name="new_private_tab">&new_private_tab;</string>
<string name="close_all_tabs">&close_all_tabs;</string>
<string name="close_private_tabs">&close_private_tabs;</string>
<string name="tabs_normal">&tabs_normal;</string>
<string name="tabs_private">&tabs_private;</string>
<string name="tabs_synced">&tabs_synced;</string>

View File

@ -13,8 +13,11 @@ import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.LightweightThemeDrawable;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import org.mozilla.gecko.widget.IconTabWidget;
import android.content.Context;
@ -23,7 +26,10 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
@ -32,7 +38,8 @@ import android.widget.LinearLayout;
import android.widget.RelativeLayout;
public class TabsPanel extends LinearLayout
implements LightweightTheme.OnChangeListener,
implements GeckoPopupMenu.OnMenuItemClickListener,
LightweightTheme.OnChangeListener,
IconTabWidget.OnTabChangedListener {
@SuppressWarnings("unused")
private static final String LOGTAG = "Gecko" + TabsPanel.class.getSimpleName();
@ -50,6 +57,10 @@ public class TabsPanel extends LinearLayout
public boolean shouldExpand();
}
public static interface CloseAllPanelView {
public void closeAll();
}
public static interface TabsLayoutChangeListener {
public void onTabsLayoutChange(int width, int height);
}
@ -68,6 +79,7 @@ public class TabsPanel extends LinearLayout
private AppStateListener mAppStateListener;
private IconTabWidget mTabWidget;
private static ImageButton mMenuButton;
private static ImageButton mAddTab;
private Panel mCurrentPanel;
@ -75,6 +87,8 @@ public class TabsPanel extends LinearLayout
private boolean mVisible;
private boolean mHeaderVisible;
private GeckoPopupMenu mPopupMenu;
public TabsPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
@ -91,6 +105,10 @@ public class TabsPanel extends LinearLayout
mIsSideBar = false;
mPopupMenu = new GeckoPopupMenu(context);
mPopupMenu.inflate(R.menu.tabs_menu);
mPopupMenu.setOnMenuItemClickListener(this);
LayoutInflater.from(context).inflate(R.layout.tabs_panel, this);
initialize();
@ -149,13 +167,28 @@ public class TabsPanel extends LinearLayout
}
mTabWidget.setTabSelectionListener(this);
mMenuButton = (ImageButton) findViewById(R.id.menu);
mMenuButton.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
final Menu menu = mPopupMenu.getMenu();
menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
mPopupMenu.show();
}
});
mPopupMenu.setAnchor(mMenuButton);
}
public void addTab() {
private void addTab() {
if (mCurrentPanel == Panel.NORMAL_TABS) {
mActivity.addTab();
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_tab");
mActivity.addTab();
} else {
mActivity.addPrivateTab();
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_private_tab");
mActivity.addPrivateTab();
}
mActivity.autoHideTabs();
@ -172,6 +205,43 @@ public class TabsPanel extends LinearLayout
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
final int itemId = item.getItemId();
if (itemId == R.id.close_all_tabs) {
if (mCurrentPanel == Panel.NORMAL_TABS) {
final String extras = getResources().getResourceEntryName(itemId);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, extras);
// Disable the menu button so that the menu won't interfere with the tab close animation.
mMenuButton.setEnabled(false);
((CloseAllPanelView) mPanelNormal).closeAll();
} else {
Log.e(LOGTAG, "Close all tabs menu item should only be visible for normal tabs panel");
}
return true;
}
if (itemId == R.id.close_private_tabs) {
if (mCurrentPanel == Panel.PRIVATE_TABS) {
final String extras = getResources().getResourceEntryName(itemId);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, extras);
((CloseAllPanelView) mPanelPrivate).closeAll();
} else {
Log.e(LOGTAG, "Close private tabs menu item should only be visible for private tabs panel");
}
return true;
}
if (itemId == R.id.new_tab || itemId == R.id.new_private_tab) {
hide();
}
return mActivity.onOptionsItemSelected(item);
}
private static int getTabContainerHeight(TabsListContainer listContainer) {
Resources resources = listContainer.getContext().getResources();
@ -350,12 +420,17 @@ public class TabsPanel extends LinearLayout
mFooter.setVisibility(View.GONE);
mAddTab.setVisibility(View.INVISIBLE);
mMenuButton.setVisibility(View.INVISIBLE);
} else {
if (mFooter != null)
mFooter.setVisibility(View.VISIBLE);
mAddTab.setVisibility(View.VISIBLE);
mAddTab.setImageLevel(index);
mMenuButton.setVisibility(View.VISIBLE);
mMenuButton.setEnabled(true);
}
if (isSideBar()) {
@ -374,6 +449,7 @@ public class TabsPanel extends LinearLayout
if (mVisible) {
mVisible = false;
mPopupMenu.dismiss();
dispatchLayoutChange(0, 0);
}
}

View File

@ -17,6 +17,7 @@ import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.PropertyAnimator.Property;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.TwoWayView;
import org.mozilla.gecko.widget.TabThumbnailWrapper;
@ -38,38 +39,44 @@ import android.widget.ImageView;
import android.widget.TextView;
class TabsTray extends TwoWayView
implements TabsPanel.PanelView {
implements TabsPanel.PanelView,
TabsPanel.CloseAllPanelView {
private static final String LOGTAG = "Gecko" + TabsTray.class.getSimpleName();
private Context mContext;
private TabsPanel mTabsPanel;
final private boolean mIsPrivate;
private TabsAdapter mTabsAdapter;
private List<View> mPendingClosedTabs;
private int mCloseAnimationCount;
private int mCloseAnimationCount = 0;
private int mCloseAllAnimationCount = 0;
private TabSwipeGestureListener mSwipeListener;
// Time to animate non-flinged tabs of screen, in milliseconds
private static final int ANIMATION_DURATION = 250;
// Time between starting successive tab animations in closeAllTabs.
private static final int ANIMATION_CASCADE_DELAY = 75;
private int mOriginalSize = 0;
public TabsTray(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mCloseAnimationCount = 0;
mPendingClosedTabs = new ArrayList<View>();
setItemsCanFocus(true);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray);
boolean isPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
mIsPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
a.recycle();
mTabsAdapter = new TabsAdapter(mContext, isPrivate);
mTabsAdapter = new TabsAdapter(mContext);
setAdapter(mTabsAdapter);
mSwipeListener = new TabSwipeGestureListener();
@ -137,15 +144,13 @@ class TabsTray extends TwoWayView
// Adapter to bind tabs into a list
private class TabsAdapter extends BaseAdapter implements Tabs.OnTabsChangedListener {
private Context mContext;
private boolean mIsPrivate;
private ArrayList<Tab> mTabs;
private LayoutInflater mInflater;
private Button.OnClickListener mOnCloseClickListener;
public TabsAdapter(Context context, boolean isPrivate) {
public TabsAdapter(Context context) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
mIsPrivate = isPrivate;
mOnCloseClickListener = new Button.OnClickListener() {
@Override
@ -281,16 +286,21 @@ class TabsTray extends TwoWayView
private void resetTransforms(View view) {
ViewHelper.setAlpha(view, 1);
if (mOriginalSize == 0)
return;
if (isVertical()) {
ViewHelper.setHeight(view, mOriginalSize);
ViewHelper.setTranslationX(view, 0);
} else {
ViewHelper.setWidth(view, mOriginalSize);
ViewHelper.setTranslationY(view, 0);
}
// We only need to reset the height or width after individual tab close animations.
if (mOriginalSize != 0) {
if (isVertical()) {
ViewHelper.setHeight(view, mOriginalSize);
} else {
ViewHelper.setWidth(view, mOriginalSize);
}
}
}
@Override
@ -320,6 +330,75 @@ class TabsTray extends TwoWayView
return (getOrientation().compareTo(TwoWayView.Orientation.VERTICAL) == 0);
}
@Override
public void closeAll() {
final int childCount = getChildCount();
// Just close the panel if there are no tabs to close.
if (childCount == 0) {
autoHidePanel();
return;
}
// Disable the view so that gestures won't interfere wth the tab close animation.
setEnabled(false);
// Delay starting each successive animation to create a cascade effect.
int cascadeDelay = 0;
for (int i = childCount - 1; i >= 0; i--) {
final View view = getChildAt(i);
final PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
animator.attach(view, Property.ALPHA, 0);
if (isVertical()) {
animator.attach(view, Property.TRANSLATION_X, view.getWidth());
} else {
animator.attach(view, Property.TRANSLATION_Y, view.getHeight());
}
mCloseAllAnimationCount++;
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
@Override
public void onPropertyAnimationStart() { }
@Override
public void onPropertyAnimationEnd() {
mCloseAllAnimationCount--;
if (mCloseAllAnimationCount > 0) {
return;
}
// Hide the panel after the animation is done.
autoHidePanel();
// Re-enable the view after the animation is done.
TabsTray.this.setEnabled(true);
// Then actually close all the tabs.
final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
for (Tab tab : tabs) {
// In the normal panel we want to close all tabs (both private and normal),
// but in the private panel we only want to close private tabs.
if (!mIsPrivate || tab.isPrivate()) {
Tabs.getInstance().closeTab(tab, false);
}
}
}
});
ThreadUtils.getUiHandler().postDelayed(new Runnable() {
@Override
public void run() {
animator.start();
}
}, cascadeDelay);
cascadeDelay += ANIMATION_CASCADE_DELAY;
}
}
private void animateClose(final View view, int pos) {
PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
animator.attach(view, Property.ALPHA, 0);
@ -565,7 +644,7 @@ class TabsTray extends TwoWayView
}
case MotionEvent.ACTION_MOVE: {
if (mSwipeView == null)
if (mSwipeView == null || mVelocityTracker == null)
break;
mVelocityTracker.addMovement(e);

Some files were not shown because too many files have changed in this diff Show More