Merge mozilla-central and fx-team
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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");
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -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");
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"stable": ["1.0", "2.0"],
|
||||
"unstable": ["3.0"]
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
После Ширина: | Высота: | Размер: 250 B |
После Ширина: | Высота: | Размер: 368 B |
|
@ -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)
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
После Ширина: | Высота: | Размер: 136 B |
После Ширина: | Высота: | Размер: 136 B |
После Ширина: | Высота: | Размер: 136 B |
После Ширина: | Высота: | Размер: 138 B |
После Ширина: | Высота: | Размер: 136 B |
После Ширина: | Высота: | Размер: 138 B |
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -200,3 +200,7 @@ user_pref('identity.fxaccounts.auth.uri', 'https://%(server)s/fxa-dummy/');
|
|||
|
||||
// Enable logging of APZ test data (see bug 961289).
|
||||
user_pref('apz.test.logging_enabled', true);
|
||||
|
||||
// Make sure Translation won't hit the network.
|
||||
user_pref("browser.translation.bing.authURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
|
||||
user_pref("browser.translation.bing.translateArrayURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
|
||||
|
|
|
@ -8,17 +8,24 @@ Components.utils.import("resource://gre/modules/devtools/event-emitter.js");
|
|||
|
||||
const EXPORTED_SYMBOLS = ["Simulator"];
|
||||
|
||||
function getVersionNumber(fullVersion) {
|
||||
return fullVersion.match(/(\d+\.\d+)/)[0];
|
||||
}
|
||||
|
||||
const Simulator = {
|
||||
_simulators: {},
|
||||
|
||||
register: function (version, simulator) {
|
||||
this._simulators[version] = simulator;
|
||||
this.emit("register");
|
||||
register: function (label, simulator) {
|
||||
// simulators register themselves as "Firefox OS X.Y"
|
||||
let versionNumber = getVersionNumber(label);
|
||||
this._simulators[versionNumber] = simulator;
|
||||
this.emit("register", versionNumber);
|
||||
},
|
||||
|
||||
unregister: function (version) {
|
||||
delete this._simulators[version];
|
||||
this.emit("unregister");
|
||||
unregister: function (label) {
|
||||
let versionNumber = getVersionNumber(label);
|
||||
delete this._simulators[versionNumber];
|
||||
this.emit("unregister", versionNumber);
|
||||
},
|
||||
|
||||
availableVersions: function () {
|
||||
|
|