Merge fx-team to m-c. a=merge
@ -1395,6 +1395,9 @@ pref("devtools.scratchpad.showTrailingSpace", false);
|
||||
pref("devtools.scratchpad.enableCodeFolding", true);
|
||||
pref("devtools.scratchpad.enableAutocompletion", true);
|
||||
|
||||
// Enable the Storage Inspector
|
||||
pref("devtools.storage.enabled", false);
|
||||
|
||||
// Enable the Style Editor.
|
||||
pref("devtools.styleeditor.enabled", true);
|
||||
pref("devtools.styleeditor.source-maps-enabled", false);
|
||||
|
@ -6,9 +6,9 @@
|
||||
var gSafeBrowsing = {
|
||||
|
||||
setReportPhishingMenu: function() {
|
||||
|
||||
// A phishing page will have a specific about:blocked content documentURI
|
||||
var isPhishingPage = content.document.documentURI.startsWith("about:blocked?e=phishingBlocked");
|
||||
var uri = getBrowser().currentURI;
|
||||
var isPhishingPage = uri && uri.spec.startsWith("about:blocked?e=phishingBlocked");
|
||||
|
||||
// Show/hide the appropriate menu item.
|
||||
document.getElementById("menu_HelpPopup_reportPhishingtoolmenu")
|
||||
@ -24,7 +24,6 @@ var gSafeBrowsing = {
|
||||
if (!broadcaster)
|
||||
return;
|
||||
|
||||
var uri = getBrowser().currentURI;
|
||||
if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
|
||||
broadcaster.removeAttribute("disabled");
|
||||
else
|
||||
|
463
browser/components/loop/CardDavImporter.jsm
Normal file
@ -0,0 +1,463 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["CardDavImporter"];
|
||||
|
||||
let log = Log.repository.getLogger("Loop.Importer.CardDAV");
|
||||
log.level = Log.Level.Debug;
|
||||
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
|
||||
|
||||
const DEPTH_RESOURCE_ONLY = "0";
|
||||
const DEPTH_RESOURCE_AND_CHILDREN = "1";
|
||||
const DEPTH_RESOURCE_AND_ALL_DESCENDENTS = "infinity";
|
||||
|
||||
this.CardDavImporter = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* CardDAV Address Book importer for Loop.
|
||||
*
|
||||
* The model for address book importers is to have a single public method,
|
||||
* "startImport." When the import is done (or upon a fatal error), the
|
||||
* caller's callback method is called.
|
||||
*
|
||||
* The current model for this importer is based on the subset of CardDAV
|
||||
* implemented by Google. In theory, it should work with other CardDAV
|
||||
* sources, but it has only been tested against Google at the moment.
|
||||
*
|
||||
* At the moment, this importer assumes that no local changes will be made
|
||||
* to data retreived from a remote source: when performing a re-import,
|
||||
* any records that have been previously imported will be completely
|
||||
* removed and replaced with the data received from the CardDAV server.
|
||||
* Witout this behavior, it would be impossible for users to take any
|
||||
* actions to remove fields that are no longer valid.
|
||||
*/
|
||||
|
||||
this.CardDavImporter.prototype = {
|
||||
/**
|
||||
* Begin import of an address book from a CardDAV server.
|
||||
*
|
||||
* @param {Object} options Information needed to perform the address
|
||||
* book import. The following fields are currently
|
||||
* defined:
|
||||
* - "host": CardDAV server base address
|
||||
* (e.g., "google.com")
|
||||
* - "auth": Authentication mechanism to use.
|
||||
* Currently, only "basic" is implemented.
|
||||
* - "user": Username to use for basic auth
|
||||
* - "password": Password to use for basic auth
|
||||
* @param {Function} callback Callback function that will be invoked once the
|
||||
* import operation is complete. The first argument
|
||||
* passed to the callback will be an 'Error' object
|
||||
* or 'null'. If the import operation was
|
||||
* successful, then the second parameter will be a
|
||||
* count of the number of contacts that were
|
||||
* successfully imported.
|
||||
* @param {Object} db Database to add imported contacts into.
|
||||
* Nominally, this is the LoopContacts API. In
|
||||
* practice, anything with the same interface
|
||||
* should work here.
|
||||
*/
|
||||
|
||||
startImport: function(options, callback, db) {
|
||||
let auth;
|
||||
if (!("auth" in options)) {
|
||||
callback(new Error("No authentication specified"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.auth === "basic") {
|
||||
if (!("user" in options) || !("password" in options)) {
|
||||
callback(new Error("Missing user or password for basic authentication"));
|
||||
return;
|
||||
}
|
||||
auth = { method: "basic",
|
||||
user: options.user,
|
||||
password: options.password };
|
||||
} else {
|
||||
callback(new Error("Unknown authentication method"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!("host" in options)){
|
||||
callback(new Error("Missing host for CardDav import"));
|
||||
return;
|
||||
}
|
||||
let host = options.host;
|
||||
|
||||
Task.spawn(function* () {
|
||||
log.info("Starting CardDAV import from " + host);
|
||||
let baseURL = "https://" + host;
|
||||
let startURL = baseURL + "/.well-known/carddav";
|
||||
let abookURL;
|
||||
|
||||
// Get list of contact URLs
|
||||
let body = "<d:propfind xmlns:d='DAV:'><d:prop><d:getetag />" +
|
||||
"</d:prop></d:propfind>";
|
||||
let abook = yield this._davPromise("PROPFIND", startURL, auth,
|
||||
DEPTH_RESOURCE_AND_CHILDREN, body);
|
||||
|
||||
// Build multiget REPORT body from URLs in PROPFIND result
|
||||
let contactElements = abook.responseXML.
|
||||
getElementsByTagNameNS("DAV:", "href");
|
||||
|
||||
body = "<c:addressbook-multiget xmlns:d='DAV:' " +
|
||||
"xmlns:c='urn:ietf:params:xml:ns:carddav'>" +
|
||||
"<d:prop><d:getetag /> <c:address-data /></d:prop>\n";
|
||||
|
||||
for (let element of contactElements) {
|
||||
let href = element.textContent;
|
||||
if (href.substr(-1) == "/") {
|
||||
abookURL = baseURL + href;
|
||||
} else {
|
||||
body += "<d:href>" + href + "</d:href>\n";
|
||||
}
|
||||
}
|
||||
body += "</c:addressbook-multiget>";
|
||||
|
||||
// Retreive contact URL contents
|
||||
let allEntries = yield this._davPromise("REPORT", abookURL, auth,
|
||||
DEPTH_RESOURCE_AND_CHILDREN,
|
||||
body);
|
||||
|
||||
// Parse multiget entites and add to DB
|
||||
let addressData = allEntries.responseXML.getElementsByTagNameNS(
|
||||
"urn:ietf:params:xml:ns:carddav", "address-data");
|
||||
|
||||
log.info("Retreived " + addressData.length + " contacts from " +
|
||||
host + "; importing into database");
|
||||
|
||||
let importCount = 0;
|
||||
for (let i = 0; i < addressData.length; i++) {
|
||||
let vcard = addressData.item(i).textContent;
|
||||
let contact = this._convertVcard(vcard);
|
||||
contact.id += "@" + host;
|
||||
contact.category = ["carddav@" + host];
|
||||
|
||||
let existing = yield this._dbPromise(db, "getByServiceId", contact.id);
|
||||
if (existing) {
|
||||
yield this._dbPromise(db, "remove", existing._guid);
|
||||
}
|
||||
|
||||
// If the contact contains neither email nor phone number, then it
|
||||
// is not useful in the Loop address book: do not add.
|
||||
if (!("tel" in contact) && !("email" in contact)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield this._dbPromise(db, "add", contact);
|
||||
importCount++;
|
||||
}
|
||||
|
||||
return importCount;
|
||||
}.bind(this)).then(
|
||||
(result) => {
|
||||
log.info("Import complete: " + result + " contacts imported.");
|
||||
callback(null, result);
|
||||
},
|
||||
(error) => {
|
||||
log.error("Aborting import: " + error.fileName + ":" +
|
||||
error.lineNumber + ": " + error.message);
|
||||
callback(error);
|
||||
}).then(null,
|
||||
(error) => {
|
||||
log.error("Error in callback: " + error.fileName +
|
||||
":" + error.lineNumber + ": " + error.message);
|
||||
callback(error);
|
||||
}).then(null,
|
||||
(error) => {
|
||||
log.error("Error calling failure callback, giving up: " +
|
||||
error.fileName + ":" + error.lineNumber + ": " +
|
||||
error.message);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrap a LoopContacts-style operation in a promise. The operation is run
|
||||
* immediately, and a corresponding Promise is returned. Error callbacks
|
||||
* cause the promise to be rejected, and success cause it to be resolved.
|
||||
*
|
||||
* @param {Object} db Object the operation is to be performed on
|
||||
* @param {String} method Name of operation being wrapped
|
||||
* @param {Object} param Parameter to be passed to the operation
|
||||
*
|
||||
* @return {Object} Promise corresponding to the result of the operation.
|
||||
*/
|
||||
|
||||
_dbPromise: function(db, method, param) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db[method](param, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a contact in VCard format (see RFC 6350) to the format used
|
||||
* by the LoopContacts class.
|
||||
*
|
||||
* @param {String} vcard The contact to convert, in vcard format
|
||||
* @return {Object} a LoopContacts-style contact object containing
|
||||
* the relevant fields from the vcard.
|
||||
*/
|
||||
|
||||
_convertVcard: function(vcard) {
|
||||
let contact = {};
|
||||
let nickname;
|
||||
vcard.split(/[\r\n]+(?! )/).forEach(
|
||||
function (contentline) {
|
||||
contentline = contentline.replace(/[\r\n]+ /g, "");
|
||||
let match = /^(.*?[^\\]):(.*)$/.exec(contentline);
|
||||
if (match) {
|
||||
let nameparam = match[1];
|
||||
let value = match[2];
|
||||
|
||||
// Poor-man's unescaping
|
||||
value = value.replace(/\\:/g, ":");
|
||||
value = value.replace(/\\,/g, ",");
|
||||
value = value.replace(/\\n/gi, "\n");
|
||||
value = value.replace(/\\\\/g, "\\");
|
||||
|
||||
let param = nameparam.split(/;/);
|
||||
let name = param[0];
|
||||
let pref = false;
|
||||
let type = [];
|
||||
|
||||
for (let i = 1; i < param.length; i++) {
|
||||
if (/^PREF/.exec(param[i]) || /^TYPE=PREF/.exec(param[i])) {
|
||||
pref = true;
|
||||
}
|
||||
let typeMatch = /^TYPE=(.*)/.exec(param[i]);
|
||||
if (typeMatch) {
|
||||
type.push(typeMatch[1].toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
if (!type.length) {
|
||||
type.push("other");
|
||||
}
|
||||
|
||||
if (name === "FN") {
|
||||
value = value.replace(/\\;/g, ";");
|
||||
contact.name = [value];
|
||||
}
|
||||
|
||||
if (name === "N") {
|
||||
// Because we don't have lookbehinds, matching unescaped
|
||||
// semicolons is a pain. Luckily, we know that \r and \n
|
||||
// cannot appear in the strings, so we use them to swap
|
||||
// unescaped semicolons for \n.
|
||||
value = value.replace(/\\;/g, "\r");
|
||||
value = value.replace(/;/g, "\n");
|
||||
value = value.replace(/\r/g, ";");
|
||||
|
||||
let family, given, additional, prefix, suffix;
|
||||
let values = value.split(/\n/);
|
||||
if (values.length >= 5) {
|
||||
[family, given, additional, prefix, suffix] = values;
|
||||
if (prefix.length) {
|
||||
contact.honorificPrefix = [prefix];
|
||||
}
|
||||
if (given.length) {
|
||||
contact.givenName = [given];
|
||||
}
|
||||
if (additional.length) {
|
||||
contact.additionalName = [additional];
|
||||
}
|
||||
if (family.length) {
|
||||
contact.familyName = [family];
|
||||
}
|
||||
if (suffix.length) {
|
||||
contact.honorificSuffix = [suffix];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (name === "EMAIL") {
|
||||
value = value.replace(/\\;/g, ";");
|
||||
if (!("email" in contact)) {
|
||||
contact.email = [];
|
||||
}
|
||||
contact.email.push({
|
||||
pref: pref,
|
||||
type: type,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
if (name === "NICKNAME") {
|
||||
value = value.replace(/\\;/g, ";");
|
||||
// We don't store nickname in contact because it's not
|
||||
// a supported field. We're saving it off here in case we
|
||||
// need to use it if the fullname is blank.
|
||||
nickname = value;
|
||||
};
|
||||
|
||||
if (name === "ADR") {
|
||||
value = value.replace(/\\;/g, "\r");
|
||||
value = value.replace(/;/g, "\n");
|
||||
value = value.replace(/\r/g, ";");
|
||||
let pobox, extra, street, locality, region, code, country;
|
||||
let values = value.split(/\n/);
|
||||
if (values.length >= 7) {
|
||||
[pobox, extra, street, locality, region, code, country] = values;
|
||||
if (!("adr" in contact)) {
|
||||
contact.adr = [];
|
||||
}
|
||||
contact.adr.push({
|
||||
pref: pref,
|
||||
type: type,
|
||||
streetAddress: (street || pobox) + (extra ? (" " + extra) : ""),
|
||||
locality: locality,
|
||||
region: region,
|
||||
postalCode: code,
|
||||
countryName: country
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (name === "TEL") {
|
||||
value = value.replace(/\\;/g, ";");
|
||||
if (!("tel" in contact)) {
|
||||
contact.tel = [];
|
||||
}
|
||||
contact.tel.push({
|
||||
pref: pref,
|
||||
type: type,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
if (name === "ORG") {
|
||||
value = value.replace(/\\;/g, "\r");
|
||||
value = value.replace(/;/g, "\n");
|
||||
value = value.replace(/\r/g, ";");
|
||||
if (!("org" in contact)) {
|
||||
contact.org = [];
|
||||
}
|
||||
contact.org.push(value.replace(/\n.*/, ""));
|
||||
}
|
||||
|
||||
if (name === "TITLE") {
|
||||
value = value.replace(/\\;/g, ";");
|
||||
if (!("jobTitle" in contact)) {
|
||||
contact.jobTitle = [];
|
||||
}
|
||||
contact.jobTitle.push(value);
|
||||
}
|
||||
|
||||
if (name === "BDAY") {
|
||||
value = value.replace(/\\;/g, ";");
|
||||
contact.bday = Date.parse(value);
|
||||
}
|
||||
|
||||
if (name === "UID") {
|
||||
contact.id = value;
|
||||
}
|
||||
|
||||
if (name === "NOTE") {
|
||||
value = value.replace(/\\;/g, ";");
|
||||
if (!("note" in contact)) {
|
||||
contact.note = [];
|
||||
}
|
||||
contact.note.push(value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Basic sanity checking: make sure the name field isn't empty
|
||||
if (!("name" in contact) || contact.name[0].length == 0) {
|
||||
if (("familyName" in contact) && ("givenName" in contact)) {
|
||||
// First, try to synthesize a full name from the name fields.
|
||||
// Ordering is culturally sensitive, but we don't have
|
||||
// cultural origin information available here. The best we
|
||||
// can really do is "family, given additional"
|
||||
contact.name = [contact.familyName[0] + ", " + contact.givenName[0]];
|
||||
if (("additionalName" in contact)) {
|
||||
contact.name[0] += " " + contact.additionalName[0];
|
||||
}
|
||||
} else {
|
||||
if (nickname) {
|
||||
contact.name = [nickname];
|
||||
} else if ("familyName" in contact) {
|
||||
contact.name = [contact.familyName[0]];
|
||||
} else if ("givenName" in contact) {
|
||||
contact.name = [contact.givenName[0]];
|
||||
} else if ("org" in contact) {
|
||||
contact.name = [contact.org[0]];
|
||||
} else if ("email" in contact) {
|
||||
contact.name = [contact.email[0].value];
|
||||
} else if ("tel" in contact) {
|
||||
contact.name = [contact.tel[0].value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return contact;
|
||||
},
|
||||
|
||||
/**
|
||||
* Issues a CardDAV request (see RFC 6352) and returns a Promise to represent
|
||||
* the success or failure state of the request.
|
||||
*
|
||||
* @param {String} method WebDAV method to use (e.g., "PROPFIND")
|
||||
* @param {String} url HTTP URL to use for the request
|
||||
* @param {Object} auth Object with authentication-related configuration.
|
||||
* See documentation for startImport for details.
|
||||
* @param {Number} depth Value to use for the WebDAV (HTTP) "Depth" header
|
||||
* @param {String} body Body to include in the WebDAV (HTTP) request
|
||||
*
|
||||
* @return {Object} Promise representing the request operation outcome.
|
||||
* If resolved, the resolution value is the XMLHttpRequest
|
||||
* that was used to perform the request.
|
||||
*/
|
||||
_davPromise: function(method, url, auth, depth, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance(Ci.nsIXMLHttpRequest);
|
||||
let user = "";
|
||||
let password = "";
|
||||
|
||||
if (auth.method == "basic") {
|
||||
user = auth.user;
|
||||
password = auth.password;
|
||||
}
|
||||
|
||||
req.open(method, url, true, user, password);
|
||||
|
||||
req.setRequestHeader("Depth", depth);
|
||||
req.setRequestHeader("Content-Type", "application/xml; charset=utf-8");
|
||||
|
||||
req.onload = function() {
|
||||
if (req.status < 400) {
|
||||
resolve(req);
|
||||
} else {
|
||||
reject(new Error(req.status + " " + req.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function(error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
req.send(body);
|
||||
});
|
||||
}
|
||||
};
|
@ -10,6 +10,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
|
||||
"resource:///modules/loop/LoopStorage.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CardDavImporter",
|
||||
"resource:///modules/loop/CardDavImporter.jsm");
|
||||
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
|
||||
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
|
||||
return new EventEmitter();
|
||||
@ -318,6 +320,13 @@ LoopStorage.on("upgrade", function(e, db) {
|
||||
* violated. You'll notice this as well in the documentation for each method.
|
||||
*/
|
||||
let LoopContactsInternal = Object.freeze({
|
||||
/**
|
||||
* Map of contact importer names to instances
|
||||
*/
|
||||
_importServices: {
|
||||
"carddav": new CardDavImporter()
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a contact to the data store.
|
||||
*
|
||||
@ -757,8 +766,15 @@ let LoopContactsInternal = Object.freeze({
|
||||
* be the result of the operation, if successfull.
|
||||
*/
|
||||
startImport: function(options, callback) {
|
||||
//TODO in bug 972000.
|
||||
callback(new Error("Not implemented yet!"));
|
||||
if (!("service" in options)) {
|
||||
callback(new Error("No import service specified in options"));
|
||||
return;
|
||||
}
|
||||
if (!(options.service in this._importServices)) {
|
||||
callback(new Error("Unknown import service specified: " + options.service));
|
||||
return;
|
||||
}
|
||||
this._importServices[options.service].startImport(options, callback, this);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -543,7 +543,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
return (
|
||||
FeedbackLayout({title: l10n.get("feedback_thank_you_heading")},
|
||||
React.DOM.p({className: "info thank-you"},
|
||||
l10n.get("feedback_window_will_close_in", {
|
||||
l10n.get("feedback_window_will_close_in2", {
|
||||
countdown: this.state.countdown,
|
||||
num: this.state.countdown
|
||||
}))
|
||||
|
@ -543,7 +543,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
return (
|
||||
<FeedbackLayout title={l10n.get("feedback_thank_you_heading")}>
|
||||
<p className="info thank-you">{
|
||||
l10n.get("feedback_window_will_close_in", {
|
||||
l10n.get("feedback_window_will_close_in2", {
|
||||
countdown: this.state.countdown,
|
||||
num: this.state.countdown
|
||||
})}</p>
|
||||
|
@ -13,6 +13,7 @@ BROWSER_CHROME_MANIFESTS += [
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.loop += [
|
||||
'CardDavImporter.jsm',
|
||||
'LoopContacts.jsm',
|
||||
'LoopStorage.jsm',
|
||||
'MozLoopAPI.jsm',
|
||||
|
@ -4,6 +4,7 @@ support-files =
|
||||
loop_fxa.sjs
|
||||
../../../../base/content/test/general/browser_fxa_oauth.html
|
||||
|
||||
[browser_CardDavImporter.js]
|
||||
[browser_fxa_login.js]
|
||||
skip-if = !debug
|
||||
[browser_loop_fxa_server.js]
|
||||
|
@ -0,0 +1,364 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const {CardDavImporter} = Cu.import("resource:///modules/loop/CardDavImporter.jsm", {});
|
||||
|
||||
const mockDb = {
|
||||
_store: { },
|
||||
_next_guid: 1,
|
||||
|
||||
add: function(details, callback) {
|
||||
if (!("id" in details)) {
|
||||
callback(new Error("No 'id' field present"));
|
||||
return;
|
||||
}
|
||||
details._guid = this._next_guid++;
|
||||
this._store[details._guid] = details;
|
||||
callback(null, details);
|
||||
},
|
||||
remove: function(guid, callback) {
|
||||
if (!guid in this._store) {
|
||||
callback(new Error("Could not find _guid '" + guid + "' in database"));
|
||||
return;
|
||||
}
|
||||
delete this._store[guid];
|
||||
callback(null);
|
||||
},
|
||||
get: function(guid, callback) {
|
||||
callback(null, this._store[guid]);
|
||||
},
|
||||
getByServiceId: function(serviceId, callback) {
|
||||
for (let guid in this._store) {
|
||||
if (serviceId === this._store[guid].id) {
|
||||
callback(null, this._store[guid]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback(null, null);
|
||||
},
|
||||
removeAll: function(callback) {
|
||||
this._store = {};
|
||||
this._next_guid = 1;
|
||||
callback(null);
|
||||
}
|
||||
};
|
||||
|
||||
const kAuth = {
|
||||
"method": "basic",
|
||||
"user": "username",
|
||||
"password": "p455w0rd"
|
||||
}
|
||||
|
||||
|
||||
// "pid" for "provider ID"
|
||||
let vcards = [
|
||||
"VERSION:3.0\n" +
|
||||
"N:Smith;John;;;\n" +
|
||||
"FN:John Smith\n" +
|
||||
"EMAIL;TYPE=work:john.smith@example.com\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid1\n" +
|
||||
"END:VCARD\n",
|
||||
|
||||
"VERSION:3.0\n" +
|
||||
"N:Smith;Jane;;;\n" +
|
||||
"FN:Jane Smith\n" +
|
||||
"EMAIL:jane.smith@example.com\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid2\n" +
|
||||
"END:VCARD\n",
|
||||
|
||||
"VERSION:3.0\n" +
|
||||
"N:García Fernández;Miguel Angel;José Antonio;Mr.;Jr.\n" +
|
||||
"FN:Mr. Miguel Angel José Antonio\n García Fernández, Jr.\n" +
|
||||
"EMAIL:mike@example.org\n" +
|
||||
"EMAIL;PREF=1;TYPE=work:miguel.angel@example.net\n" +
|
||||
"EMAIL;TYPE=home;UNKNOWNPARAMETER=frotz:majacf@example.com\n" +
|
||||
"TEL:+3455555555\n" +
|
||||
"TEL;PREF=1;TYPE=work:+3455556666\n" +
|
||||
"TEL;TYPE=home;UNKNOWNPARAMETER=frotz:+3455557777\n" +
|
||||
"ADR:;Suite 123;Calle Aduana\\, 29;MADRID;;28070;SPAIN\n" +
|
||||
"ADR;TYPE=work:P.O. BOX 555;;;Washington;DC;20024-00555;USA\n" +
|
||||
"ORG:Acme España SL\n" +
|
||||
"TITLE:President\n" +
|
||||
"BDAY:1965-05-05\n" +
|
||||
"NOTE:Likes tulips\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid3\n" +
|
||||
"END:VCARD\n",
|
||||
|
||||
"VERSION:3.0\n" +
|
||||
"N:Jones;Bob;;;\n" +
|
||||
"EMAIL:bob.jones@example.com\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid4\n" +
|
||||
"END:VCARD\n",
|
||||
|
||||
"VERSION:3.0\n" +
|
||||
"N:Jones;Davy;Randall;;\n" +
|
||||
"EMAIL:davy.jones@example.com\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid5\n" +
|
||||
"END:VCARD\n",
|
||||
|
||||
"VERSION:3.0\n" +
|
||||
"EMAIL:trip@example.com\n" +
|
||||
"NICKNAME:Trip\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid6\n" +
|
||||
"END:VCARD\n",
|
||||
|
||||
"VERSION:3.0\n" +
|
||||
"EMAIL:acme@example.com\n" +
|
||||
"ORG:Acme, Inc.\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid7\n" +
|
||||
"END:VCARD\n",
|
||||
|
||||
"VERSION:3.0\n" +
|
||||
"EMAIL:anyone@example.com\n" +
|
||||
"REV:2011-07-12T14:43:20Z\n" +
|
||||
"UID:pid8\n" +
|
||||
"END:VCARD\n",
|
||||
];
|
||||
|
||||
|
||||
const monkeyPatchImporter = function(importer) {
|
||||
// Set up the response bodies
|
||||
let listPropfind =
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n' +
|
||||
'<d:multistatus xmlns:card="urn:ietf:params:xml:ns:carddav"\n' +
|
||||
' xmlns:d="DAV:">\n' +
|
||||
' <d:response>\n' +
|
||||
' <d:href>/carddav/abook/</d:href>\n' +
|
||||
' <d:propstat>\n' +
|
||||
' <d:status>HTTP/1.1 200 OK</d:status>\n' +
|
||||
' </d:propstat>\n' +
|
||||
' <d:propstat>\n' +
|
||||
' <d:status>HTTP/1.1 404 Not Found</d:status>\n' +
|
||||
' <d:prop>\n' +
|
||||
' <d:getetag/>\n' +
|
||||
' </d:prop>\n' +
|
||||
' </d:propstat>\n' +
|
||||
' </d:response>\n';
|
||||
|
||||
let listReportMultiget =
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n' +
|
||||
'<d:multistatus xmlns:card="urn:ietf:params:xml:ns:carddav"\n' +
|
||||
' xmlns:d="DAV:">\n';
|
||||
|
||||
vcards.forEach(vcard => {
|
||||
let uid = /\nUID:(.*?)\n/.exec(vcard);
|
||||
listPropfind +=
|
||||
' <d:response>\n' +
|
||||
' <d:href>/carddav/abook/' + uid + '</d:href>\n' +
|
||||
' <d:propstat>\n' +
|
||||
' <d:status>HTTP/1.1 200 OK</d:status>\n' +
|
||||
' <d:prop>\n' +
|
||||
' <d:getetag>"2011-07-12T07:43:20.855-07:00"</d:getetag>\n' +
|
||||
' </d:prop>\n' +
|
||||
' </d:propstat>\n' +
|
||||
' </d:response>\n';
|
||||
|
||||
listReportMultiget +=
|
||||
' <d:response>\n' +
|
||||
' <d:href>/carddav/abook/' + uid + '</d:href>\n' +
|
||||
' <d:propstat>\n' +
|
||||
' <d:status>HTTP/1.1 200 OK</d:status>\n' +
|
||||
' <d:prop>\n' +
|
||||
' <d:getetag>"2011-07-12T07:43:20.855-07:00"</d:getetag>\n' +
|
||||
' <card:address-data>' + vcard + '</card:address-data>\n' +
|
||||
' </d:prop>\n' +
|
||||
' </d:propstat>\n' +
|
||||
' </d:response>\n';
|
||||
});
|
||||
|
||||
listPropfind += "</d:multistatus>\n";
|
||||
listReportMultiget += "</d:multistatus>\n";
|
||||
|
||||
importer._davPromise = function(method, url, auth, depth, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (auth.method != "basic" ||
|
||||
auth.user != kAuth.user ||
|
||||
auth.password != kAuth.password) {
|
||||
reject(new Error("401 Auth Failure"));
|
||||
return;
|
||||
}
|
||||
|
||||
let request = method + " " + url + " " + depth;
|
||||
let xmlParser = new DOMParser();
|
||||
let responseXML;
|
||||
switch (request) {
|
||||
case "PROPFIND https://example.com/.well-known/carddav 1":
|
||||
responseXML = xmlParser.parseFromString(listPropfind, "text/xml");
|
||||
break;
|
||||
case "REPORT https://example.com/carddav/abook/ 1":
|
||||
responseXML = xmlParser.parseFromString(listReportMultiget, "text/xml");
|
||||
break;
|
||||
default:
|
||||
reject(new Error("404 Not Found"));
|
||||
return;
|
||||
}
|
||||
resolve({"responseXML": responseXML});
|
||||
});
|
||||
}.bind(importer);
|
||||
return importer;
|
||||
}
|
||||
|
||||
add_task(function* test_CardDavImport() {
|
||||
let importer = monkeyPatchImporter(new CardDavImporter());
|
||||
yield new Promise ((resolve, reject) => {
|
||||
info("Initiating import");
|
||||
importer.startImport({
|
||||
"host": "example.com",
|
||||
"auth": kAuth.method,
|
||||
"user": kAuth.user,
|
||||
"password": kAuth.password
|
||||
}, (err, result) => { err ? reject(err) : resolve(result); }, mockDb);
|
||||
});
|
||||
info("Import succeeded");
|
||||
|
||||
Assert.equal(vcards.length, Object.keys(mockDb._store).length,
|
||||
"Should import all VCards into database");
|
||||
|
||||
// Basic checks
|
||||
let c = mockDb._store[1];
|
||||
Assert.equal(c.name[0], "John Smith", "Full name should match");
|
||||
Assert.equal(c.givenName[0], "John", "Given name should match");
|
||||
Assert.equal(c.familyName[0], "Smith", "Family name should match");
|
||||
Assert.equal(c.email[0].type, "work", "Email type should match");
|
||||
Assert.equal(c.email[0].value, "john.smith@example.com", "Email should match");
|
||||
Assert.equal(c.email[0].pref, false, "Pref should match");
|
||||
Assert.equal(c.id, "pid1@example.com", "UID should match and be scoped to provider");
|
||||
|
||||
c = mockDb._store[2];
|
||||
Assert.equal(c.name[0], "Jane Smith", "Full name should match");
|
||||
Assert.equal(c.givenName[0], "Jane", "Given name should match");
|
||||
Assert.equal(c.familyName[0], "Smith", "Family name should match");
|
||||
Assert.equal(c.email[0].type, "other", "Email type should match");
|
||||
Assert.equal(c.email[0].value, "jane.smith@example.com", "Email should match");
|
||||
Assert.equal(c.email[0].pref, false, "Pref should match");
|
||||
Assert.equal(c.id, "pid2@example.com", "UID should match and be scoped to provider");
|
||||
|
||||
// Check every field
|
||||
c = mockDb._store[3];
|
||||
Assert.equal(c.name[0], "Mr. Miguel Angel José Antonio García Fernández, Jr.", "Full name should match");
|
||||
Assert.equal(c.givenName[0], "Miguel Angel", "Given name should match");
|
||||
Assert.equal(c.additionalName[0], "José Antonio", "Other name should match");
|
||||
Assert.equal(c.familyName[0], "García Fernández", "Family name should match");
|
||||
Assert.equal(c.email.length, 3, "Email count should match");
|
||||
Assert.equal(c.email[0].type, "other", "Email type should match");
|
||||
Assert.equal(c.email[0].value, "mike@example.org", "Email should match");
|
||||
Assert.equal(c.email[0].pref, false, "Pref should match");
|
||||
Assert.equal(c.email[1].type, "work", "Email type should match");
|
||||
Assert.equal(c.email[1].value, "miguel.angel@example.net", "Email should match");
|
||||
Assert.equal(c.email[1].pref, true, "Pref should match");
|
||||
Assert.equal(c.email[2].type, "home", "Email type should match");
|
||||
Assert.equal(c.email[2].value, "majacf@example.com", "Email should match");
|
||||
Assert.equal(c.email[2].pref, false, "Pref should match");
|
||||
Assert.equal(c.tel.length, 3, "Phone number count should match");
|
||||
Assert.equal(c.tel[0].type, "other", "Phone type should match");
|
||||
Assert.equal(c.tel[0].value, "+3455555555", "Phone number should match");
|
||||
Assert.equal(c.tel[0].pref, false, "Pref should match");
|
||||
Assert.equal(c.tel[1].type, "work", "Phone type should match");
|
||||
Assert.equal(c.tel[1].value, "+3455556666", "Phone number should match");
|
||||
Assert.equal(c.tel[1].pref, true, "Pref should match");
|
||||
Assert.equal(c.tel[2].type, "home", "Phone type should match");
|
||||
Assert.equal(c.tel[2].value, "+3455557777", "Phone number should match");
|
||||
Assert.equal(c.tel[2].pref, false, "Pref should match");
|
||||
Assert.equal(c.adr.length, 2, "Address count should match");
|
||||
Assert.equal(c.adr[0].pref, false, "Pref should match");
|
||||
Assert.equal(c.adr[0].type, "other", "Type should match");
|
||||
Assert.equal(c.adr[0].streetAddress, "Calle Aduana, 29 Suite 123", "Street address should match");
|
||||
Assert.equal(c.adr[0].locality, "MADRID", "Locality should match");
|
||||
Assert.equal(c.adr[0].postalCode, "28070", "Post code should match");
|
||||
Assert.equal(c.adr[0].countryName, "SPAIN", "Country should match");
|
||||
Assert.equal(c.adr[1].pref, false, "Pref should match");
|
||||
Assert.equal(c.adr[1].type, "work", "Type should match");
|
||||
Assert.equal(c.adr[1].streetAddress, "P.O. BOX 555", "Street address should match");
|
||||
Assert.equal(c.adr[1].locality, "Washington", "Locality should match");
|
||||
Assert.equal(c.adr[1].region, "DC", "Region should match");
|
||||
Assert.equal(c.adr[1].postalCode, "20024-00555", "Post code should match");
|
||||
Assert.equal(c.adr[1].countryName, "USA", "Country should match");
|
||||
Assert.equal(c.org[0], "Acme España SL", "Org should match");
|
||||
Assert.equal(c.jobTitle[0], "President", "Title should match");
|
||||
Assert.equal(c.note[0], "Likes tulips", "Note should match");
|
||||
let bday = new Date(c.bday);
|
||||
Assert.equal(bday.getUTCFullYear(), 1965, "Birthday year should match");
|
||||
Assert.equal(bday.getUTCMonth(), 4, "Birthday month should match");
|
||||
Assert.equal(bday.getUTCDate(), 5, "Birthday day should match");
|
||||
Assert.equal(c.id, "pid3@example.com", "UID should match and be scoped to provider");
|
||||
|
||||
// Check name synthesis
|
||||
c = mockDb._store[4];
|
||||
Assert.equal(c.name[0], "Jones, Bob", "Full name should be synthesized correctly");
|
||||
c = mockDb._store[5];
|
||||
Assert.equal(c.name[0], "Jones, Davy Randall", "Full name should be synthesized correctly");
|
||||
c = mockDb._store[6];
|
||||
Assert.equal(c.name[0], "Trip", "Full name should be synthesized correctly");
|
||||
c = mockDb._store[7];
|
||||
Assert.equal(c.name[0], "Acme, Inc.", "Full name should be synthesized correctly");
|
||||
c = mockDb._store[8];
|
||||
Assert.equal(c.name[0], "anyone@example.com", "Full name should be synthesized correctly");
|
||||
|
||||
// Check that a re-import doesn't cause contact duplication.
|
||||
yield new Promise ((resolve, reject) => {
|
||||
info("Initiating import");
|
||||
importer.startImport({
|
||||
"host": "example.com",
|
||||
"auth": kAuth.method,
|
||||
"user": kAuth.user,
|
||||
"password": kAuth.password
|
||||
}, (err, result) => { err ? reject(err) : resolve(result); }, mockDb);
|
||||
});
|
||||
Assert.equal(vcards.length, Object.keys(mockDb._store).length,
|
||||
"Second import shouldn't increase DB size");
|
||||
|
||||
// Check that errors are propagated back to caller
|
||||
let error = yield new Promise ((resolve, reject) => {
|
||||
info("Initiating import");
|
||||
importer.startImport({
|
||||
"host": "example.com",
|
||||
"auth": kAuth.method,
|
||||
"user": kAuth.user,
|
||||
"password": "invalidpassword"
|
||||
}, (err, result) => { err ? resolve(err) : reject(new Error("Should have failed")); }, mockDb);
|
||||
});
|
||||
Assert.equal(error.message, "401 Auth Failure", "Auth error should propagate");
|
||||
|
||||
let error = yield new Promise ((resolve, reject) => {
|
||||
info("Initiating import");
|
||||
importer.startImport({
|
||||
"host": "example.invalid",
|
||||
"auth": kAuth.method,
|
||||
"user": kAuth.user,
|
||||
"password": kAuth.password
|
||||
}, (err, result) => { err ? resolve(err) : reject(new Error("Should have failed")); }, mockDb);
|
||||
});
|
||||
Assert.equal(error.message, "404 Not Found", "Not found error should propagate");
|
||||
|
||||
let tmp = mockDb.getByServiceId;
|
||||
mockDb.getByServiceId = function(serviceId, callback) {
|
||||
callback(new Error("getByServiceId failed"));
|
||||
};
|
||||
let error = yield new Promise ((resolve, reject) => {
|
||||
info("Initiating import");
|
||||
importer.startImport({
|
||||
"host": "example.com",
|
||||
"auth": kAuth.method,
|
||||
"user": kAuth.user,
|
||||
"password": kAuth.password
|
||||
}, (err, result) => { err ? resolve(err) : reject(new Error("Should have failed")); }, mockDb);
|
||||
});
|
||||
Assert.equal(error.message, "getByServiceId failed", "Database error should propagate");
|
||||
mockDb.getByServiceId = tmp;
|
||||
|
||||
let error = yield new Promise ((resolve, reject) => {
|
||||
info("Initiating import");
|
||||
importer.startImport({
|
||||
"host": "example.com",
|
||||
}, (err, result) => { err ? resolve(err) : reject(new Error("Should have failed")); }, mockDb);
|
||||
});
|
||||
Assert.equal(error.message, "No authentication specified", "Missing parameters should generate error");
|
||||
})
|
@ -11,7 +11,7 @@ add_task(loadLoopPanel);
|
||||
add_task(function* test_mozLoop_pluralStrings() {
|
||||
Assert.ok(gMozLoopAPI, "mozLoop should exist");
|
||||
|
||||
var strings = JSON.parse(gMozLoopAPI.getStrings("feedback_window_will_close_in"));
|
||||
var strings = JSON.parse(gMozLoopAPI.getStrings("feedback_window_will_close_in2"));
|
||||
Assert.equal(gMozLoopAPI.getPluralForm(0, strings.textContent),
|
||||
"This window will close in {{countdown}} seconds");
|
||||
Assert.equal(gMozLoopAPI.getPluralForm(1, strings.textContent),
|
||||
|
@ -29,7 +29,6 @@ const MAX_ORDINAL = 99;
|
||||
this.DevTools = function DevTools() {
|
||||
this._tools = new Map(); // Map<toolId, tool>
|
||||
this._themes = new Map(); // Map<themeId, theme>
|
||||
this._eventParsers = new Map(); // Map<parserID, [handlers]>
|
||||
this._toolboxes = new Map(); // Map<target, toolbox>
|
||||
|
||||
// destroy() is an observer's handler so we need to preserve context.
|
||||
@ -42,7 +41,7 @@ this.DevTools = function DevTools() {
|
||||
|
||||
Services.obs.addObserver(this._teardown, "devtools-unloaded", false);
|
||||
Services.obs.addObserver(this.destroy, "quit-application", false);
|
||||
}
|
||||
};
|
||||
|
||||
DevTools.prototype = {
|
||||
/**
|
||||
@ -66,10 +65,6 @@ DevTools.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
get eventParsers() {
|
||||
return this._eventParsers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a new developer tool.
|
||||
*
|
||||
@ -145,85 +140,6 @@ DevTools.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a new event parser to be used in the processing of event info.
|
||||
*
|
||||
* @param {Object} parserObj
|
||||
* Each parser must contain the following properties:
|
||||
* - parser, which must take the following form:
|
||||
* {
|
||||
* id {String}: "jQuery events", // Unique id.
|
||||
* getListeners: function(node) { }, // Function that takes a node and
|
||||
* // returns an array of eventInfo
|
||||
* // objects (see below).
|
||||
*
|
||||
* hasListeners: function(node) { }, // Optional function that takes a
|
||||
* // node and returns a boolean
|
||||
* // indicating whether a node has
|
||||
* // listeners attached.
|
||||
*
|
||||
* normalizeHandler: function(fnDO) { }, // Optional function that takes a
|
||||
* // Debugger.Object instance and
|
||||
* // climbs the scope chain to get
|
||||
* // the function that should be
|
||||
* // displayed in the event bubble
|
||||
* // see the following url for
|
||||
* // details:
|
||||
* // https://developer.mozilla.org/
|
||||
* // docs/Tools/Debugger-API/
|
||||
* // Debugger.Object
|
||||
* }
|
||||
*
|
||||
* An eventInfo object should take the following form:
|
||||
* {
|
||||
* type {String}: "click",
|
||||
* handler {Function}: event handler,
|
||||
* tags {String}: "jQuery,Live", // These tags will be displayed as
|
||||
* // attributes in the events popup.
|
||||
* hide: { // Hide or show fields:
|
||||
* debugger: false, // Debugger icon
|
||||
* type: false, // Event type e.g. click
|
||||
* filename: false, // Filename
|
||||
* capturing: false, // Capturing
|
||||
* dom0: false // DOM 0
|
||||
* },
|
||||
*
|
||||
* override: { // The following can be overridden:
|
||||
* type: "click",
|
||||
* origin: "http://www.mozilla.com",
|
||||
* searchString: 'onclick="doSomething()"',
|
||||
* DOM0: true,
|
||||
* capturing: true
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
registerEventParser: function(parserObj) {
|
||||
let parserId = parserObj.id;
|
||||
|
||||
if (!parserId) {
|
||||
throw new Error("Cannot register new event parser with id " + parserId);
|
||||
}
|
||||
if (this._eventParsers.has(parserId)) {
|
||||
throw new Error("Duplicate event parser id " + parserId);
|
||||
}
|
||||
|
||||
this._eventParsers.set(parserId, {
|
||||
getListeners: parserObj.getListeners,
|
||||
hasListeners: parserObj.hasListeners,
|
||||
normalizeHandler: parserObj.normalizeHandler
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes parser that matches a given parserId.
|
||||
*
|
||||
* @param {String} parserId
|
||||
* id of the event parser to unregister.
|
||||
*/
|
||||
unregisterEventParser: function(parserId) {
|
||||
this._eventParsers.delete(parserId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sorting function used for sorting tools based on their ordinals.
|
||||
*/
|
||||
@ -555,10 +471,6 @@ DevTools.prototype = {
|
||||
this.unregisterTool(key, true);
|
||||
}
|
||||
|
||||
for (let [id] of this._eventParsers) {
|
||||
this.unregisterEventParser(id, true);
|
||||
}
|
||||
|
||||
// Cleaning down the toolboxes: i.e.
|
||||
// for (let [target, toolbox] of this._toolboxes) toolbox.destroy();
|
||||
// Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
|
||||
|
@ -26,6 +26,7 @@ browser.jar:
|
||||
content/browser/devtools/frame-script-utils.js (shared/frame-script-utils.js)
|
||||
content/browser/devtools/styleeditor.xul (styleeditor/styleeditor.xul)
|
||||
content/browser/devtools/styleeditor.css (styleeditor/styleeditor.css)
|
||||
content/browser/devtools/storage.xul (storage/storage.xul)
|
||||
content/browser/devtools/computedview.xhtml (styleinspector/computedview.xhtml)
|
||||
content/browser/devtools/cssruleview.xhtml (styleinspector/cssruleview.xhtml)
|
||||
content/browser/devtools/ruleview.css (styleinspector/ruleview.css)
|
||||
|
@ -21,8 +21,6 @@ loader.lazyGetter(this, "osString", () => Cc["@mozilla.org/xre/app-info;1"].getS
|
||||
|
||||
let events = require("sdk/system/events");
|
||||
|
||||
require("devtools/toolkit/event-parsers");
|
||||
|
||||
// Panels
|
||||
loader.lazyGetter(this, "OptionsPanel", () => require("devtools/framework/toolbox-options").OptionsPanel);
|
||||
loader.lazyGetter(this, "InspectorPanel", () => require("devtools/inspector/inspector-panel").InspectorPanel);
|
||||
@ -35,6 +33,7 @@ loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/webaudioe
|
||||
loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel").ProfilerPanel);
|
||||
loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/netmonitor/panel").NetMonitorPanel);
|
||||
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/scratchpad/scratchpad-panel").ScratchpadPanel);
|
||||
loader.lazyGetter(this, "StoragePanel", () => require("devtools/storage/panel").StoragePanel);
|
||||
|
||||
// Strings
|
||||
const toolboxProps = "chrome://browser/locale/devtools/toolbox.properties";
|
||||
@ -44,11 +43,12 @@ const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.propertie
|
||||
const shaderEditorProps = "chrome://browser/locale/devtools/shadereditor.properties";
|
||||
const canvasDebuggerProps = "chrome://browser/locale/devtools/canvasdebugger.properties";
|
||||
const webAudioEditorProps = "chrome://browser/locale/devtools/webaudioeditor.properties";
|
||||
|
||||
const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties";
|
||||
const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
|
||||
const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties";
|
||||
const scratchpadProps = "chrome://browser/locale/devtools/scratchpad.properties";
|
||||
const storageProps = "chrome://browser/locale/devtools/storage.properties";
|
||||
|
||||
loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps));
|
||||
loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
|
||||
loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
|
||||
@ -60,6 +60,7 @@ loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(
|
||||
loader.lazyGetter(this, "profilerStrings",() => Services.strings.createBundle(profilerProps));
|
||||
loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps));
|
||||
loader.lazyGetter(this, "scratchpadStrings", () => Services.strings.createBundle(scratchpadProps));
|
||||
loader.lazyGetter(this, "storageStrings", () => Services.strings.createBundle(storageProps));
|
||||
|
||||
let Tools = {};
|
||||
exports.Tools = Tools;
|
||||
@ -318,9 +319,34 @@ Tools.netMonitor = {
|
||||
}
|
||||
};
|
||||
|
||||
Tools.storage = {
|
||||
id: "storage",
|
||||
key: l10n("open.commandkey", storageStrings),
|
||||
ordinal: 9,
|
||||
accesskey: l10n("open.accesskey", storageStrings),
|
||||
modifiers: "shift",
|
||||
visibilityswitch: "devtools.storage.enabled",
|
||||
icon: "chrome://browser/skin/devtools/tool-storage.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://browser/content/devtools/storage.xul",
|
||||
label: l10n("storage.label", storageStrings),
|
||||
tooltip: l10n("storage.tooltip", storageStrings),
|
||||
inMenu: true,
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return target.isLocalTab ||
|
||||
(target.client.traits.storageInspector && !target.isAddon);
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new StoragePanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
}
|
||||
};
|
||||
|
||||
Tools.scratchpad = {
|
||||
id: "scratchpad",
|
||||
ordinal: 9,
|
||||
ordinal: 10,
|
||||
visibilityswitch: "devtools.scratchpad.enabled",
|
||||
icon: "chrome://browser/skin/devtools/tool-scratchpad.svg",
|
||||
invertIconForLightTheme: true,
|
||||
@ -352,6 +378,7 @@ let defaultTools = [
|
||||
Tools.webAudioEditor,
|
||||
Tools.jsprofiler,
|
||||
Tools.netMonitor,
|
||||
Tools.storage,
|
||||
Tools.scratchpad
|
||||
];
|
||||
|
||||
|
@ -23,6 +23,7 @@ DIRS += [
|
||||
'shadereditor',
|
||||
'shared',
|
||||
'sourceeditor',
|
||||
'storage',
|
||||
'styleeditor',
|
||||
'styleinspector',
|
||||
'tilt',
|
||||
|
@ -16,7 +16,7 @@ function test() {
|
||||
"-H 'User-Agent: " + aDebuggee.navigator.userAgent + "'",
|
||||
"-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'",
|
||||
"-H 'Accept-Language: " + aDebuggee.navigator.language + "'",
|
||||
"-H 'Accept-Encoding: gzip, deflate'",
|
||||
"--compressed",
|
||||
"-H 'X-Custom-Header-1: Custom value'",
|
||||
"-H 'X-Custom-Header-2: 8.8.8.8'",
|
||||
"-H 'X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT'",
|
||||
@ -31,7 +31,7 @@ function test() {
|
||||
'-H "User-Agent: ' + aDebuggee.navigator.userAgent + '"',
|
||||
'-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"',
|
||||
'-H "Accept-Language: ' + aDebuggee.navigator.language + '"',
|
||||
'-H "Accept-Encoding: gzip, deflate"',
|
||||
"--compressed",
|
||||
'-H "X-Custom-Header-1: Custom value"',
|
||||
'-H "X-Custom-Header-2: 8.8.8.8"',
|
||||
'-H "X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"',
|
||||
|
@ -57,6 +57,13 @@ var Resource = Class({
|
||||
*/
|
||||
get hasChildren() { return this.children && this.children.size > 0; },
|
||||
|
||||
/**
|
||||
* Is this Resource the root (top level for the store)?
|
||||
*/
|
||||
get isRoot() {
|
||||
return !this.parent
|
||||
},
|
||||
|
||||
/**
|
||||
* Sorted array of children for display
|
||||
*/
|
||||
|
@ -84,6 +84,9 @@ var ResourceContainer = Class({
|
||||
evt.stopPropagation();
|
||||
}, true);
|
||||
|
||||
if (!this.resource.isRoot) {
|
||||
this.expanded = false;
|
||||
}
|
||||
this.update();
|
||||
},
|
||||
|
||||
|
@ -35,8 +35,14 @@ let test = asyncTest(function*() {
|
||||
function selectFileFirstLoad(projecteditor, resource) {
|
||||
ok (resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
|
||||
projecteditor.projectTree.selectResource(resource);
|
||||
let container = projecteditor.projectTree.getViewContainer(resource);
|
||||
|
||||
if (resource.isRoot) {
|
||||
ok (container.expanded, "The root directory is expanded by default.");
|
||||
return;
|
||||
}
|
||||
if (resource.isDir) {
|
||||
ok (!container.expanded, "A directory is not expanded by default.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -120,6 +120,10 @@ this.Curl = {
|
||||
}
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
let header = headers[i];
|
||||
if (header.name === "Accept-Encoding"){
|
||||
command.push("--compressed");
|
||||
continue;
|
||||
}
|
||||
if (ignoredHeaders.has(header.name)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ const MAX_VISIBLE_STRING_SIZE = 100;
|
||||
* entry in the table. Default: name.
|
||||
* - emptyText: text to display when no entries in the table to display.
|
||||
* - highlightUpdated: true to highlight the changed/added row.
|
||||
* - removableColumns: Whether columns are removeable. If set to true,
|
||||
* - removableColumns: Whether columns are removeable. If set to false,
|
||||
* the context menu in the headers will not appear.
|
||||
* - firstColumn: key of the first column that should appear.
|
||||
*/
|
||||
@ -55,7 +55,7 @@ function TableWidget(node, options={}) {
|
||||
this.uniqueId = uniqueId || "name";
|
||||
this.firstColumn = firstColumn || "";
|
||||
this.highlightUpdated = highlightUpdated || false;
|
||||
this.removableColumns = removableColumns || false;
|
||||
this.removableColumns = removableColumns !== false;
|
||||
|
||||
this.tbody = this.document.createElementNS(XUL_NS, "hbox");
|
||||
this.tbody.className = "table-widget-body theme-body";
|
||||
@ -276,7 +276,7 @@ TableWidget.prototype = {
|
||||
item = item[this.uniqueId];
|
||||
}
|
||||
|
||||
return item == this.selectedRow[this.uniqueId];
|
||||
return this.selectedRow && item == this.selectedRow[this.uniqueId];
|
||||
},
|
||||
|
||||
/**
|
||||
@ -687,6 +687,8 @@ Column.prototype = {
|
||||
/**
|
||||
* Event handler for the command event coming from the header context menu.
|
||||
* Toggles the column if it was requested by the user.
|
||||
* When called explicitly without parameters, it toggles the corresponding
|
||||
* column.
|
||||
*
|
||||
* @param {string} event
|
||||
* The name of the event. i.e. EVENTS.HEADER_CONTEXT_MENU
|
||||
@ -696,6 +698,11 @@ Column.prototype = {
|
||||
* true if the column is visible
|
||||
*/
|
||||
toggleColumn: function(event, id, checked) {
|
||||
if (arguments.length == 0) {
|
||||
// Act like a toggling method when called with no params
|
||||
id = this.id;
|
||||
checked = this.wrapper.hasAttribute("hidden");
|
||||
}
|
||||
if (id != this.id) {
|
||||
return;
|
||||
}
|
||||
@ -960,6 +967,8 @@ Cell.prototype = {
|
||||
*/
|
||||
flash: function() {
|
||||
this.label.classList.remove("flash-out");
|
||||
// Cause a reflow so that the animation retriggers on adding back the class
|
||||
let a = this.label.parentNode.offsetWidth;
|
||||
this.label.classList.add("flash-out");
|
||||
},
|
||||
|
||||
|
@ -65,7 +65,7 @@ TreeWidget.prototype = {
|
||||
this._selectedLabel = this._selectedItem = null;
|
||||
return;
|
||||
}
|
||||
if (!typeof id == "array") {
|
||||
if (!Array.isArray(id)) {
|
||||
return;
|
||||
}
|
||||
this._selectedLabel = this.root.setSelectedItem(id);
|
||||
|
12
browser/devtools/storage/moz.build
Normal file
@ -0,0 +1,12 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
EXTRA_JS_MODULES.devtools.storage += [
|
||||
'panel.js',
|
||||
'ui.js'
|
||||
]
|
83
browser/devtools/storage/panel.js
Normal file
@ -0,0 +1,83 @@
|
||||
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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 {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
let EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
loader.lazyGetter(this, "StorageFront",
|
||||
() => require("devtools/server/actors/storage").StorageFront);
|
||||
|
||||
loader.lazyGetter(this, "StorageUI",
|
||||
() => require("devtools/storage/ui").StorageUI);
|
||||
|
||||
this.StoragePanel = function StoragePanel(panelWin, toolbox) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._toolbox = toolbox;
|
||||
this._target = toolbox.target;
|
||||
this._panelWin = panelWin;
|
||||
|
||||
this.destroy = this.destroy.bind(this);
|
||||
}
|
||||
|
||||
exports.StoragePanel = StoragePanel;
|
||||
|
||||
StoragePanel.prototype = {
|
||||
get target() this._toolbox.target,
|
||||
|
||||
get panelWindow() this._panelWin,
|
||||
|
||||
/**
|
||||
* open is effectively an asynchronous constructor
|
||||
*/
|
||||
open: function() {
|
||||
let targetPromise;
|
||||
// We always interact with the target as if it were remote
|
||||
if (!this.target.isRemote) {
|
||||
targetPromise = this.target.makeRemote();
|
||||
} else {
|
||||
targetPromise = Promise.resolve(this.target);
|
||||
}
|
||||
|
||||
return targetPromise.then(() => {
|
||||
this.target.on("close", this.destroy);
|
||||
this._front = new StorageFront(this.target.client, this.target.form);
|
||||
|
||||
this.UI = new StorageUI(this._front, this._target, this._panelWin);
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
return this;
|
||||
}, console.error);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the style editor.
|
||||
*/
|
||||
destroy: function() {
|
||||
if (!this._destroyed) {
|
||||
this.UI.destroy();
|
||||
this._destroyed = true;
|
||||
|
||||
this._target.off("close", this.destroy);
|
||||
this._target = null;
|
||||
this._toolbox = null;
|
||||
this._panelDoc = null;
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyGetter(StoragePanel.prototype, "strings",
|
||||
function () {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/storage.properties");
|
||||
});
|
31
browser/devtools/storage/storage.xul
Normal file
@ -0,0 +1,31 @@
|
||||
<?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/. -->
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/storage.css" type="text/css"?>
|
||||
|
||||
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||
<script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<box flex="1" class="devtools-responsive-container theme-body">
|
||||
<vbox id="storage-tree"/>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="storage-table" class="theme-sidebar" flex="1"/>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="storage-sidebar" class="devtools-sidebar-tabs" hidden="true">
|
||||
<vbox flex="1"/>
|
||||
</vbox>
|
||||
</box>
|
||||
|
||||
</window>
|
15
browser/devtools/storage/test/browser.ini
Normal file
@ -0,0 +1,15 @@
|
||||
[DEFAULT]
|
||||
skip-if = e10s # Bug 1049888 - storage actors do not work in e10s for now
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
storage-complex-values.html
|
||||
storage-listings.html
|
||||
storage-secured-iframe.html
|
||||
storage-unsecured-iframe.html
|
||||
storage-updates.html
|
||||
head.js
|
||||
|
||||
[browser_storage_basic.js]
|
||||
[browser_storage_dynamic_updates.js]
|
||||
[browser_storage_sidebar.js]
|
||||
[browser_storage_values.js]
|
114
browser/devtools/storage/test/browser_storage_basic.js
Normal file
@ -0,0 +1,114 @@
|
||||
/* 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/. */
|
||||
|
||||
// Basic test to assert that the storage tree and table corresponding to each
|
||||
// item in the storage tree is correctly displayed
|
||||
|
||||
// Entries that should be present in the tree for this test
|
||||
// Format for each entry in the array :
|
||||
// [
|
||||
// ["path", "to", "tree", "item"], - The path to the tree item to click formed
|
||||
// by id of each item
|
||||
// ["key_value1", "key_value2", ...] - The value of the first (unique) column
|
||||
// for each row in the table corresponding
|
||||
// to the tree item selected.
|
||||
// ]
|
||||
// These entries are formed by the cookies, local storage, session storage and
|
||||
// indexedDB entries created in storage-listings.html,
|
||||
// storage-secured-iframe.html and storage-unsecured-iframe.html
|
||||
const storeItems = [
|
||||
[["cookies", "test1.example.org"],
|
||||
["c1", "cs2", "c3", "uc1"]],
|
||||
[["cookies", "sectest1.example.org"],
|
||||
["uc1", "cs2", "sc1"]],
|
||||
[["localStorage", "http://test1.example.org"],
|
||||
["ls1", "ls2"]],
|
||||
[["localStorage", "http://sectest1.example.org"],
|
||||
["iframe-u-ls1"]],
|
||||
[["localStorage", "https://sectest1.example.org"],
|
||||
["iframe-s-ls1"]],
|
||||
[["sessionStorage", "http://test1.example.org"],
|
||||
["ss1"]],
|
||||
[["sessionStorage", "http://sectest1.example.org"],
|
||||
["iframe-u-ss1", "iframe-u-ss2"]],
|
||||
[["sessionStorage", "https://sectest1.example.org"],
|
||||
["iframe-s-ss1"]],
|
||||
[["indexedDB", "http://test1.example.org"],
|
||||
["idb1", "idb2"]],
|
||||
[["indexedDB", "http://test1.example.org", "idb1"],
|
||||
["obj1", "obj2"]],
|
||||
[["indexedDB", "http://test1.example.org", "idb2"],
|
||||
["obj3"]],
|
||||
[["indexedDB", "http://test1.example.org", "idb1", "obj1"],
|
||||
[1, 2, 3]],
|
||||
[["indexedDB", "http://test1.example.org", "idb1", "obj2"],
|
||||
[1]],
|
||||
[["indexedDB", "http://test1.example.org", "idb2", "obj3"],
|
||||
[]],
|
||||
[["indexedDB", "http://sectest1.example.org"],
|
||||
[]],
|
||||
[["indexedDB", "https://sectest1.example.org"],
|
||||
["idb-s1", "idb-s2"]],
|
||||
[["indexedDB", "https://sectest1.example.org", "idb-s1"],
|
||||
["obj-s1"]],
|
||||
[["indexedDB", "https://sectest1.example.org", "idb-s2"],
|
||||
["obj-s2"]],
|
||||
[["indexedDB", "https://sectest1.example.org", "idb-s1", "obj-s1"],
|
||||
[6, 7]],
|
||||
[["indexedDB", "https://sectest1.example.org", "idb-s2", "obj-s2"],
|
||||
[16]],
|
||||
];
|
||||
|
||||
/**
|
||||
* Test that the desired number of tree items are present
|
||||
*/
|
||||
function testTree() {
|
||||
let doc = gPanelWindow.document;
|
||||
for (let item of storeItems) {
|
||||
ok(doc.querySelector("[data-id='" + JSON.stringify(item[0]) + "']"),
|
||||
"Tree item " + item[0] + " should be present in the storage tree");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that correct table entries are shown for each of the tree item
|
||||
*/
|
||||
let testTables = Task.async(function*() {
|
||||
let doc = gPanelWindow.document;
|
||||
// Expand all nodes so that the synthesized click event actually works
|
||||
gUI.tree.expandAll();
|
||||
|
||||
// First tree item is already selected so no clicking and waiting for update
|
||||
for (let id of storeItems[0][1]) {
|
||||
ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
|
||||
"Table item " + id + " should be present");
|
||||
}
|
||||
|
||||
// Click rest of the tree items and wait for the table to be updated
|
||||
for (let item of storeItems.slice(1)) {
|
||||
selectTreeItem(item[0]);
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
// Check whether correct number of items are present in the table
|
||||
is(doc.querySelectorAll(
|
||||
".table-widget-wrapper:first-of-type .table-widget-cell"
|
||||
).length, item[1].length, "Number of items in table is correct");
|
||||
|
||||
// Check if all the desired items are present in the table
|
||||
for (let id of item[1]) {
|
||||
ok(doc.querySelector(".table-widget-cell[data-id='" + id + "']"),
|
||||
"Table item " + id + " should be present");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let startTest = Task.async(function*() {
|
||||
testTree();
|
||||
yield testTables();
|
||||
finishTests();
|
||||
});
|
||||
|
||||
function test() {
|
||||
openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html").then(startTest);
|
||||
}
|
211
browser/devtools/storage/test/browser_storage_dynamic_updates.js
Normal file
@ -0,0 +1,211 @@
|
||||
/* 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/. */
|
||||
|
||||
let testUpdates = Task.async(function*() {
|
||||
|
||||
let $ = id => gPanelWindow.document.querySelector(id);
|
||||
let $$ = sel => gPanelWindow.document.querySelectorAll(sel);
|
||||
|
||||
gUI.tree.expandAll();
|
||||
|
||||
ok(gUI.sidebar.hidden, "Sidebar is initially hidden");
|
||||
selectTableItem("c1");
|
||||
yield gUI.once("sidebar-updated");
|
||||
|
||||
// test that value is something initially
|
||||
let initialValue = [[
|
||||
{name: "c1", value: "1.2.3.4.5.6.7"},
|
||||
{name: "c1.path", value: "/browser"}
|
||||
],[
|
||||
{name: "c1", value: "Array"},
|
||||
{name: "c1.0", value: "1"},
|
||||
{name: "c1.6", value: "7"}
|
||||
]];
|
||||
|
||||
// test that value is something initially
|
||||
let finalValue = [[
|
||||
{name: "c1", value: '{"foo": 4,"bar":6}'},
|
||||
{name: "c1.path", value: "/browser"}
|
||||
],[
|
||||
{name: "c1", value: "Object"},
|
||||
{name: "c1.foo", value: "4"},
|
||||
{name: "c1.bar", value: "6"}
|
||||
]];
|
||||
// Check that sidebar shows correct initial value
|
||||
yield findVariableViewProperties(initialValue[0], false);
|
||||
yield findVariableViewProperties(initialValue[1], true);
|
||||
// Check if table shows correct initial value
|
||||
ok($("#value [data-id='c1'].table-widget-cell"), "cell is present");
|
||||
is($("#value [data-id='c1'].table-widget-cell").value, "1.2.3.4.5.6.7",
|
||||
"correct initial value in table");
|
||||
gWindow.addCookie("c1", '{"foo": 4,"bar":6}', "/browser");
|
||||
yield gUI.once("sidebar-updated");
|
||||
|
||||
yield findVariableViewProperties(finalValue[0], false);
|
||||
yield findVariableViewProperties(finalValue[1], true);
|
||||
ok($("#value [data-id='c1'].table-widget-cell"), "cell is present after update");
|
||||
is($("#value [data-id='c1'].table-widget-cell").value, '{"foo": 4,"bar":6}',
|
||||
"correct final value in table");
|
||||
|
||||
// Add a new entry
|
||||
is($$("#value .table-widget-cell").length, 2,
|
||||
"Correct number of rows before update 0");
|
||||
|
||||
gWindow.addCookie("c3", "booyeah");
|
||||
|
||||
// Wait once for update and another time for value fetching
|
||||
yield gUI.once("store-objects-updated");
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 3,
|
||||
"Correct number of rows after update 1");
|
||||
|
||||
// Add another
|
||||
gWindow.addCookie("c4", "booyeah");
|
||||
|
||||
// Wait once for update and another time for value fetching
|
||||
yield gUI.once("store-objects-updated");
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 4,
|
||||
"Correct number of rows after update 2");
|
||||
|
||||
// Removing cookies
|
||||
gWindow.removeCookie("c1", "/browser");
|
||||
|
||||
yield gUI.once("sidebar-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 3,
|
||||
"Correct number of rows after delete update 3");
|
||||
|
||||
ok(!$("#c1"), "Correct row got deleted");
|
||||
|
||||
ok(!gUI.sidebar.hidden, "Sidebar still visible for next row");
|
||||
|
||||
// Check if next element's value is visible in sidebar
|
||||
yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
|
||||
|
||||
// Keep deleting till no rows
|
||||
|
||||
gWindow.removeCookie("c3");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 2,
|
||||
"Correct number of rows after delete update 4");
|
||||
|
||||
// Check if next element's value is visible in sidebar
|
||||
yield findVariableViewProperties([{name: "c2", value: "foobar"}]);
|
||||
|
||||
gWindow.removeCookie("c2", "/browser");
|
||||
|
||||
yield gUI.once("sidebar-updated");
|
||||
|
||||
yield findVariableViewProperties([{name: "c4", value: "booyeah"}]);
|
||||
|
||||
is($$("#value .table-widget-cell").length, 1,
|
||||
"Correct number of rows after delete update 5");
|
||||
|
||||
gWindow.removeCookie("c4");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 0,
|
||||
"Correct number of rows after delete update 6");
|
||||
ok(gUI.sidebar.hidden, "Sidebar is hidden when no rows");
|
||||
|
||||
// Testing in local storage
|
||||
selectTreeItem(["localStorage", "http://test1.example.org"]);
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 7,
|
||||
"Correct number of rows after delete update 7");
|
||||
|
||||
ok($(".table-widget-cell[data-id='ls4']"), "ls4 exists before deleting");
|
||||
|
||||
gWindow.localStorage.removeItem("ls4");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 6,
|
||||
"Correct number of rows after delete update 8");
|
||||
ok(!$(".table-widget-cell[data-id='ls4']"),
|
||||
"ls4 does not exists after deleting");
|
||||
|
||||
gWindow.localStorage.setItem("ls4", "again");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 7,
|
||||
"Correct number of rows after delete update 9");
|
||||
ok($(".table-widget-cell[data-id='ls4']"),
|
||||
"ls4 came back after adding it again");
|
||||
|
||||
// Updating a row
|
||||
gWindow.localStorage.setItem("ls2", "ls2-changed");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($("#value [data-id='ls2']").value, "ls2-changed",
|
||||
"Value got updated for local storage");
|
||||
|
||||
// Testing in session storage
|
||||
selectTreeItem(["sessionStorage", "http://test1.example.org"]);
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 3,
|
||||
"Correct number of rows for session storage");
|
||||
|
||||
gWindow.sessionStorage.setItem("ss4", "new-item");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 4,
|
||||
"Correct number of rows after session storage update");
|
||||
|
||||
// deleting item
|
||||
|
||||
gWindow.sessionStorage.removeItem("ss3");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
gWindow.sessionStorage.removeItem("ss1");
|
||||
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
is($$("#value .table-widget-cell").length, 2,
|
||||
"Correct number of rows after removing items from session storage");
|
||||
|
||||
selectTableItem("ss2");
|
||||
|
||||
yield gUI.once("sidebar-updated");
|
||||
|
||||
ok(!gUI.sidebar.hidden, "sidebar is visible");
|
||||
|
||||
// Checking for correct value in sidebar before update
|
||||
yield findVariableViewProperties([{name: "ss2", value: "foobar"}]);
|
||||
|
||||
gWindow.sessionStorage.setItem("ss2", "changed=ss2");
|
||||
|
||||
yield gUI.once("sidebar-updated");
|
||||
|
||||
is($("#value [data-id='ss2']").value, "changed=ss2",
|
||||
"Value got updated for session storage in the table");
|
||||
|
||||
yield findVariableViewProperties([{name: "ss2", value: "changed=ss2"}]);
|
||||
|
||||
});
|
||||
|
||||
let startTest = Task.async(function*() {
|
||||
yield testUpdates();
|
||||
finishTests();
|
||||
});
|
||||
|
||||
function test() {
|
||||
openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html").then(startTest);
|
||||
}
|
67
browser/devtools/storage/test/browser_storage_sidebar.js
Normal file
@ -0,0 +1,67 @@
|
||||
/* 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 to verify that the sidebar opens, closes and updates
|
||||
// This test is not testing the values in the sidebar, being tested in _values
|
||||
|
||||
// Format: [
|
||||
// <id of the table item to click> or <id array for tree item to select> or
|
||||
// null to press Escape,
|
||||
// <do we wait for the async "sidebar-updated" event>,
|
||||
// <is the sidebar open>
|
||||
// ]
|
||||
const testCases = [
|
||||
[["cookies", "sectest1.example.org"], 0, 0],
|
||||
["cs2", 1, 1],
|
||||
[null, 0, 0],
|
||||
["cs2", 1, 1],
|
||||
["uc1", 1, 1],
|
||||
["uc1", 0, 1],
|
||||
[["localStorage", "http://sectest1.example.org"], 0, 0],
|
||||
["iframe-u-ls1", 1, 1],
|
||||
["iframe-u-ls1", 0, 1],
|
||||
[null, 0, 0],
|
||||
[["sessionStorage", "http://test1.example.org"], 0, 0],
|
||||
["ss1", 1, 1],
|
||||
[null, 0, 0],
|
||||
[["indexedDB", "http://test1.example.org"], 0, 0],
|
||||
["idb2", 1, 1],
|
||||
[["indexedDB", "http://test1.example.org", "idb2", "obj3"], 0, 0],
|
||||
[["indexedDB", "https://sectest1.example.org", "idb-s2"], 0, 0],
|
||||
["obj-s2", 1, 1],
|
||||
[null, 0, 0],
|
||||
[null, 0, 0],
|
||||
["obj-s2", 1, 1],
|
||||
[null, 0, 0],
|
||||
];
|
||||
|
||||
let testSidebar = Task.async(function*() {
|
||||
let doc = gPanelWindow.document;
|
||||
for (let item of testCases) {
|
||||
info("clicking for item " + item);
|
||||
if (Array.isArray(item[0])) {
|
||||
selectTreeItem(item[0]);
|
||||
yield gUI.once("store-objects-updated");
|
||||
}
|
||||
else if (item[0]) {
|
||||
selectTableItem(item[0]);
|
||||
}
|
||||
else {
|
||||
EventUtils.sendKey("ESCAPE", gPanelWindow);
|
||||
}
|
||||
if (item[1]) {
|
||||
yield gUI.once("sidebar-updated");
|
||||
}
|
||||
is(!item[2], gUI.sidebar.hidden, "Correct visibility state of sidebar");
|
||||
}
|
||||
});
|
||||
|
||||
let startTest = Task.async(function*() {
|
||||
yield testSidebar();
|
||||
finishTests();
|
||||
});
|
||||
|
||||
function test() {
|
||||
openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html").then(startTest);
|
||||
}
|
143
browser/devtools/storage/test/browser_storage_values.js
Normal file
@ -0,0 +1,143 @@
|
||||
/* 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 to verify that the values shown in sidebar are correct
|
||||
|
||||
// Format: [
|
||||
// <id of the table item to click> or <id array for tree item to select> or
|
||||
// null do click nothing,
|
||||
// null to skip checking value in variables view or a key value pair object
|
||||
// which will be asserted to exist in the storage sidebar,
|
||||
// true if the check is to be made in the parsed value section
|
||||
// ]
|
||||
const testCases = [
|
||||
["cs2", [
|
||||
{name: "cs2", value: "sessionCookie"},
|
||||
{name: "cs2.path", value: "/"},
|
||||
{name: "cs2.isDomain", value: "true"},
|
||||
{name: "cs2.isHttpOnly", value: "false"},
|
||||
{name: "cs2.host", value: ".example.org"},
|
||||
{name: "cs2.expires", value: "Session"},
|
||||
{name: "cs2.isSecure", value: "false"},
|
||||
]],
|
||||
["c1", [
|
||||
{name: "c1", value: JSON.stringify(["foo", "Bar", {foo: "Bar"}])},
|
||||
{name: "c1.path", value: "/browser"},
|
||||
{name: "c1.isDomain", value: "false"},
|
||||
{name: "c1.isHttpOnly", value: "false"},
|
||||
{name: "c1.host", value: "test1.example.org"},
|
||||
{name: "c1.expires", value: new Date(2000000000000).toLocaleString()},
|
||||
{name: "c1.isSecure", value: "false"},
|
||||
]],
|
||||
[/*"c1"*/, [
|
||||
{name: "c1", value: "Array"},
|
||||
{name: "c1.0", value: "foo"},
|
||||
{name: "c1.1", value: "Bar"},
|
||||
{name: "c1.2", value: "Object"},
|
||||
{name: "c1.2.foo", value: "Bar"},
|
||||
], true],
|
||||
[["localStorage", "http://test1.example.org"]],
|
||||
["ls2", [
|
||||
{name: "ls2", value: "foobar-2"}
|
||||
]],
|
||||
["ls1", [
|
||||
{name: "ls1", value: JSON.stringify({
|
||||
es6: "for", the: "win", baz: [0, 2, 3, {
|
||||
deep: "down",
|
||||
nobody: "cares"
|
||||
}]})}
|
||||
]],
|
||||
[/*ls1*/, [
|
||||
{name: "ls1", value: "Object"},
|
||||
{name: "ls1.es6", value: "for"},
|
||||
{name: "ls1.the", value: "win"},
|
||||
{name: "ls1.baz", value: "Array"},
|
||||
{name: "ls1.baz.0", value: "0"},
|
||||
{name: "ls1.baz.1", value: "2"},
|
||||
{name: "ls1.baz.2", value: "3"},
|
||||
{name: "ls1.baz.3", value: "Object"},
|
||||
{name: "ls1.baz.3.deep", value: "down"},
|
||||
{name: "ls1.baz.3.nobody", value: "cares"},
|
||||
], true],
|
||||
["ls3", [
|
||||
{name: "ls3", "value": "http://foobar.com/baz.php"}
|
||||
]],
|
||||
[/*ls3*/, [
|
||||
{name: "ls3", "value": "http://foobar.com/baz.php", dontMatch: true}
|
||||
], true],
|
||||
[["sessionStorage", "http://test1.example.org"]],
|
||||
["ss1", [
|
||||
{name: "ss1", value: "This#is#an#array"}
|
||||
]],
|
||||
[/*ss1*/, [
|
||||
{name: "ss1", value: "Array"},
|
||||
{name: "ss1.0", value: "This"},
|
||||
{name: "ss1.1", value: "is"},
|
||||
{name: "ss1.2", value: "an"},
|
||||
{name: "ss1.3", value: "array"},
|
||||
], true],
|
||||
["ss2", [
|
||||
{name: "ss2", value: "Array"},
|
||||
{name: "ss2.0", value: "This"},
|
||||
{name: "ss2.1", value: "is"},
|
||||
{name: "ss2.2", value: "another"},
|
||||
{name: "ss2.3", value: "array"},
|
||||
], true],
|
||||
["ss3", [
|
||||
{name: "ss3", value: "Object"},
|
||||
{name: "ss3.this", value: "is"},
|
||||
{name: "ss3.an", value: "object"},
|
||||
{name: "ss3.foo", value: "bar"},
|
||||
], true],
|
||||
[["indexedDB", "http://test1.example.org", "idb1", "obj1"]],
|
||||
[1, [
|
||||
{name: 1, value: JSON.stringify({id: 1, name: "foo", email: "foo@bar.com"})}
|
||||
]],
|
||||
[/*1*/, [
|
||||
{name: "1.id", value: "1"},
|
||||
{name: "1.name", value: "foo"},
|
||||
{name: "1.email", value: "foo@bar.com"},
|
||||
], true],
|
||||
[["indexedDB", "http://test1.example.org", "idb1", "obj2"]],
|
||||
[1, [
|
||||
{name: 1, value: JSON.stringify({
|
||||
id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"
|
||||
})}
|
||||
]],
|
||||
[/*1*/, [
|
||||
{name: "1.id2", value: "1"},
|
||||
{name: "1.name", value: "foo"},
|
||||
{name: "1.email", value: "foo@bar.com"},
|
||||
{name: "1.extra", value: "baz"},
|
||||
], true]
|
||||
];
|
||||
|
||||
let testValues = Task.async(function*() {
|
||||
gUI.tree.expandAll();
|
||||
let doc = gPanelWindow.document;
|
||||
for (let item of testCases) {
|
||||
info("clicking for item " + item);
|
||||
if (Array.isArray(item[0])) {
|
||||
selectTreeItem(item[0]);
|
||||
yield gUI.once("store-objects-updated");
|
||||
continue;
|
||||
}
|
||||
else if (item[0]) {
|
||||
selectTableItem(item[0]);
|
||||
}
|
||||
if (item[0] && item[1]) {
|
||||
yield gUI.once("sidebar-updated");
|
||||
}
|
||||
yield findVariableViewProperties(item[1], item[2]);
|
||||
}
|
||||
});
|
||||
|
||||
let startTest = Task.async(function*() {
|
||||
yield testValues();
|
||||
finishTests();
|
||||
});
|
||||
|
||||
function test() {
|
||||
openTabAndSetupStorage(MAIN_DOMAIN + "storage-complex-values.html").then(startTest);
|
||||
}
|
499
browser/devtools/storage/test/head.js
Normal file
@ -0,0 +1,499 @@
|
||||
/* 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";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope);
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm", tempScope);
|
||||
const console = tempScope.console;
|
||||
const devtools = tempScope.devtools;
|
||||
tempScope = null;
|
||||
const require = devtools.require;
|
||||
const TargetFactory = devtools.TargetFactory;
|
||||
|
||||
const SPLIT_CONSOLE_PREF = "devtools.toolbox.splitconsoleEnabled";
|
||||
const STORAGE_PREF = "devtools.storage.enabled";
|
||||
const PATH = "browser/browser/devtools/storage/test/";
|
||||
const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
|
||||
const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
|
||||
const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
|
||||
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
let gToolbox, gPanelWindow, gWindow, gUI;
|
||||
|
||||
Services.prefs.setBoolPref(STORAGE_PREF, true);
|
||||
gDevTools.testing = true;
|
||||
registerCleanupFunction(() => {
|
||||
gToolbox = gPanelWindow = gWindow = gUI = null;
|
||||
Services.prefs.clearUserPref(STORAGE_PREF);
|
||||
Services.prefs.clearUserPref(SPLIT_CONSOLE_PREF);
|
||||
gDevTools.testing = false;
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
*
|
||||
* @param {String} url The url to be loaded in the new tab
|
||||
*
|
||||
* @return a promise that resolves to the content window when the url is loaded
|
||||
*/
|
||||
function addTab(url) {
|
||||
info("Adding a new tab with URL: '" + url + "'");
|
||||
let def = promise.defer();
|
||||
|
||||
// Bug 921935 should bring waitForFocus() support to e10s, which would
|
||||
// probably cover the case of the test losing focus when the page is loading.
|
||||
// For now, we just make sure the window is focused.
|
||||
window.focus();
|
||||
|
||||
let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url);
|
||||
let linkedBrowser = tab.linkedBrowser;
|
||||
|
||||
linkedBrowser.addEventListener("load", function onload(event) {
|
||||
if (event.originalTarget.location.href != url) {
|
||||
return;
|
||||
}
|
||||
linkedBrowser.removeEventListener("load", onload, true);
|
||||
info("URL '" + url + "' loading complete");
|
||||
def.resolve(tab.linkedBrowser.contentWindow);
|
||||
}, true);
|
||||
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given url in a new tab, then sets up the page by waiting for
|
||||
* all cookies, indexedDB items etc. to be created; Then opens the storage
|
||||
* inspector and waits for the storage tree and table to be populated
|
||||
*
|
||||
* @param url {String} The url to be opened in the new tab
|
||||
*
|
||||
* @return {Promise} A promise that resolves after storage inspector is ready
|
||||
*/
|
||||
let openTabAndSetupStorage = Task.async(function*(url) {
|
||||
/**
|
||||
* This method iterates over iframes in a window and setups the indexed db
|
||||
* required for this test.
|
||||
*/
|
||||
let setupIDBInFrames = (w, i, c) => {
|
||||
if (w[i] && w[i].idbGenerator) {
|
||||
w[i].setupIDB = w[i].idbGenerator(() => setupIDBInFrames(w, i + 1, c));
|
||||
w[i].setupIDB.next();
|
||||
}
|
||||
else if (w[i] && w[i + 1]) {
|
||||
setupIDBInFrames(w, i + 1, c);
|
||||
}
|
||||
else {
|
||||
c();
|
||||
}
|
||||
};
|
||||
|
||||
let content = yield addTab(url);
|
||||
|
||||
let def = promise.defer();
|
||||
// Setup the indexed db in main window.
|
||||
gWindow = content.wrappedJSObject;
|
||||
if (gWindow.idbGenerator) {
|
||||
gWindow.setupIDB = gWindow.idbGenerator(() => {
|
||||
setupIDBInFrames(gWindow, 0, () => {
|
||||
def.resolve();
|
||||
});
|
||||
});
|
||||
gWindow.setupIDB.next();
|
||||
yield def.promise;
|
||||
}
|
||||
|
||||
// open storage inspector
|
||||
return yield openStoragePanel();
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the storage tool visible.
|
||||
*
|
||||
* @param cb {Function} Optional callback, if you don't want to use the returned
|
||||
* promise
|
||||
*
|
||||
* @return {Promise} a promise that resolves when the storage inspector is ready
|
||||
*/
|
||||
let openStoragePanel = Task.async(function*(cb) {
|
||||
info("Opening the storage inspector");
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
let storage, toolbox;
|
||||
|
||||
// Checking if the toolbox and the storage are already loaded
|
||||
// The storage-updated event should only be waited for if the storage
|
||||
// isn't loaded yet
|
||||
toolbox = gDevTools.getToolbox(target);
|
||||
if (toolbox) {
|
||||
storage = toolbox.getPanel("storage");
|
||||
if (storage) {
|
||||
gPanelWindow = storage.panelWindow;
|
||||
gUI = storage.UI;
|
||||
gToolbox = toolbox;
|
||||
info("Toolbox and storage already open");
|
||||
if (cb) {
|
||||
return cb(storage, toolbox);
|
||||
} else {
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
storage: storage
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info("Opening the toolbox");
|
||||
toolbox = yield gDevTools.showToolbox(target, "storage");
|
||||
storage = toolbox.getPanel("storage");
|
||||
gPanelWindow = storage.panelWindow;
|
||||
gUI = storage.UI;
|
||||
gToolbox = toolbox;
|
||||
|
||||
info("Waiting for the stores to update");
|
||||
yield gUI.once("store-objects-updated");
|
||||
|
||||
yield waitForToolboxFrameFocus(toolbox);
|
||||
|
||||
if (cb) {
|
||||
return cb(storage, toolbox);
|
||||
} else {
|
||||
return {
|
||||
toolbox: toolbox,
|
||||
storage: storage
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait for the toolbox frame to receive focus after it loads
|
||||
*
|
||||
* @param toolbox {Toolbox}
|
||||
*
|
||||
* @return a promise that resolves when focus has been received
|
||||
*/
|
||||
function waitForToolboxFrameFocus(toolbox) {
|
||||
info("Making sure that the toolbox's frame is focused");
|
||||
let def = promise.defer();
|
||||
let win = toolbox.frame.contentWindow;
|
||||
waitForFocus(def.resolve, win);
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces GC, CC and Shrinking GC to get rid of disconnected docshells and
|
||||
* windows.
|
||||
*/
|
||||
function forceCollections() {
|
||||
Cu.forceGC();
|
||||
Cu.forceCC();
|
||||
Cu.forceShrinkingGC();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up and finishes the test
|
||||
*/
|
||||
function finishTests() {
|
||||
// Cleanup so that indexed db created from this test do not interfere next ones
|
||||
|
||||
/**
|
||||
* This method iterates over iframes in a window and clears the indexed db
|
||||
* created by this test.
|
||||
*/
|
||||
let clearIDB = (w, i, c) => {
|
||||
if (w[i] && w[i].clear) {
|
||||
w[i].clearIterator = w[i].clear(() => clearIDB(w, i + 1, c));
|
||||
w[i].clearIterator.next();
|
||||
}
|
||||
else if (w[i] && w[i + 1]) {
|
||||
clearIDB(w, i + 1, c);
|
||||
}
|
||||
else {
|
||||
c();
|
||||
}
|
||||
};
|
||||
|
||||
gWindow.clearIterator = gWindow.clear(() => {
|
||||
clearIDB(gWindow, 0, () => {
|
||||
// Forcing GC/CC to get rid of docshells and windows created by this test.
|
||||
forceCollections();
|
||||
finish();
|
||||
});
|
||||
});
|
||||
gWindow.clearIterator.next();
|
||||
}
|
||||
|
||||
// Sends a click event on the passed DOM node in an async manner
|
||||
function click(node) {
|
||||
node.scrollIntoView()
|
||||
executeSoon(() => EventUtils.synthesizeMouseAtCenter(node, {}, gPanelWindow));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Recursively expand the variables view up to a given property.
|
||||
*
|
||||
* @param aOptions
|
||||
* Options for view expansion:
|
||||
* - rootVariable: start from the given scope/variable/property.
|
||||
* - expandTo: string made up of property names you want to expand.
|
||||
* For example: "body.firstChild.nextSibling" given |rootVariable:
|
||||
* document|.
|
||||
* @return object
|
||||
* A promise that is resolved only when the last property in |expandTo|
|
||||
* is found, and rejected otherwise. Resolution reason is always the
|
||||
* last property - |nextSibling| in the example above. Rejection is
|
||||
* always the last property that was found.
|
||||
*/
|
||||
function variablesViewExpandTo(aOptions) {
|
||||
let root = aOptions.rootVariable;
|
||||
let expandTo = aOptions.expandTo.split(".");
|
||||
let lastDeferred = promise.defer();
|
||||
|
||||
function getNext(aProp) {
|
||||
let name = expandTo.shift();
|
||||
let newProp = aProp.get(name);
|
||||
|
||||
if (expandTo.length > 0) {
|
||||
ok(newProp, "found property " + name);
|
||||
if (newProp && newProp.expand) {
|
||||
newProp.expand();
|
||||
getNext(newProp);
|
||||
}
|
||||
else {
|
||||
lastDeferred.reject(aProp);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (newProp) {
|
||||
lastDeferred.resolve(newProp);
|
||||
}
|
||||
else {
|
||||
lastDeferred.reject(aProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fetchError(aProp) {
|
||||
lastDeferred.reject(aProp);
|
||||
}
|
||||
|
||||
if (root && root.expand) {
|
||||
root.expand();
|
||||
getNext(root);
|
||||
}
|
||||
else {
|
||||
lastDeferred.resolve(root)
|
||||
}
|
||||
|
||||
return lastDeferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find variables or properties in a VariablesView instance.
|
||||
*
|
||||
* @param array aRules
|
||||
* The array of rules you want to match. Each rule is an object with:
|
||||
* - name (string|regexp): property name to match.
|
||||
* - value (string|regexp): property value to match.
|
||||
* - dontMatch (boolean): make sure the rule doesn't match any property.
|
||||
* @param boolean aParsed
|
||||
* true if we want to test the rules in the parse value section of the
|
||||
* storage sidebar
|
||||
* @return object
|
||||
* A promise object that is resolved when all the rules complete
|
||||
* matching. The resolved callback is given an array of all the rules
|
||||
* you wanted to check. Each rule has a new property: |matchedProp|
|
||||
* which holds a reference to the Property object instance from the
|
||||
* VariablesView. If the rule did not match, then |matchedProp| is
|
||||
* undefined.
|
||||
*/
|
||||
function findVariableViewProperties(aRules, aParsed) {
|
||||
// Initialize the search.
|
||||
function init() {
|
||||
// If aParsed is true, we are checking rules in the parsed value section of
|
||||
// the storage sidebar. That scope uses a blank variable as a placeholder
|
||||
// Thus, adding a blank parent to each name
|
||||
if (aParsed) {
|
||||
aRules = aRules.map(({name, value, dontMatch}) => {
|
||||
return {name: "." + name, value, dontMatch}
|
||||
});
|
||||
}
|
||||
// Separate out the rules that require expanding properties throughout the
|
||||
// view.
|
||||
let expandRules = [];
|
||||
let rules = aRules.filter((aRule) => {
|
||||
if (typeof aRule.name == "string" && aRule.name.indexOf(".") > -1) {
|
||||
expandRules.push(aRule);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Search through the view those rules that do not require any properties to
|
||||
// be expanded. Build the array of matchers, outstanding promises to be
|
||||
// resolved.
|
||||
let outstanding = [];
|
||||
|
||||
finder(rules, gUI.view, outstanding);
|
||||
|
||||
// Process the rules that need to expand properties.
|
||||
let lastStep = processExpandRules.bind(null, expandRules);
|
||||
|
||||
// Return the results - a promise resolved to hold the updated aRules array.
|
||||
let returnResults = onAllRulesMatched.bind(null, aRules);
|
||||
|
||||
return promise.all(outstanding).then(lastStep).then(returnResults);
|
||||
}
|
||||
|
||||
function onMatch(aProp, aRule, aMatched) {
|
||||
if (aMatched && !aRule.matchedProp) {
|
||||
aRule.matchedProp = aProp;
|
||||
}
|
||||
}
|
||||
|
||||
function finder(aRules, aView, aPromises) {
|
||||
for (let scope of aView) {
|
||||
for (let [id, prop] of scope) {
|
||||
for (let rule of aRules) {
|
||||
let matcher = matchVariablesViewProperty(prop, rule);
|
||||
aPromises.push(matcher.then(onMatch.bind(null, prop, rule)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processExpandRules(aRules) {
|
||||
let rule = aRules.shift();
|
||||
if (!rule) {
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
let deferred = promise.defer();
|
||||
let expandOptions = {
|
||||
rootVariable: gUI.view.getScopeAtIndex(aParsed ? 1: 0),
|
||||
expandTo: rule.name
|
||||
};
|
||||
|
||||
variablesViewExpandTo(expandOptions).then(function onSuccess(aProp) {
|
||||
let name = rule.name;
|
||||
let lastName = name.split(".").pop();
|
||||
rule.name = lastName;
|
||||
|
||||
let matched = matchVariablesViewProperty(aProp, rule);
|
||||
return matched.then(onMatch.bind(null, aProp, rule)).then(function() {
|
||||
rule.name = name;
|
||||
});
|
||||
}, function onFailure() {
|
||||
return promise.resolve(null);
|
||||
}).then(processExpandRules.bind(null, aRules)).then(function() {
|
||||
deferred.resolve(null);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function onAllRulesMatched(aRules) {
|
||||
for (let rule of aRules) {
|
||||
let matched = rule.matchedProp;
|
||||
if (matched && !rule.dontMatch) {
|
||||
ok(true, "rule " + rule.name + " matched for property " + matched.name);
|
||||
}
|
||||
else if (matched && rule.dontMatch) {
|
||||
ok(false, "rule " + rule.name + " should not match property " +
|
||||
matched.name);
|
||||
}
|
||||
else {
|
||||
ok(rule.dontMatch, "rule " + rule.name + " did not match any property");
|
||||
}
|
||||
}
|
||||
return aRules;
|
||||
}
|
||||
|
||||
return init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given Property object from the variables view matches the given
|
||||
* rule.
|
||||
*
|
||||
* @param object aProp
|
||||
* The variable's view Property instance.
|
||||
* @param object aRule
|
||||
* Rules for matching the property. See findVariableViewProperties() for
|
||||
* details.
|
||||
* @return object
|
||||
* A promise that is resolved when all the checks complete. Resolution
|
||||
* result is a boolean that tells your promise callback the match
|
||||
* result: true or false.
|
||||
*/
|
||||
function matchVariablesViewProperty(aProp, aRule) {
|
||||
function resolve(aResult) {
|
||||
return promise.resolve(aResult);
|
||||
}
|
||||
|
||||
if (!aProp) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
if (aRule.name) {
|
||||
let match = aRule.name instanceof RegExp ?
|
||||
aRule.name.test(aProp.name) :
|
||||
aProp.name == aRule.name;
|
||||
if (!match) {
|
||||
return resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
if ("value" in aRule) {
|
||||
let displayValue = aProp.displayValue;
|
||||
if (aProp.displayValueClassName == "token-string") {
|
||||
displayValue = displayValue.substring(1, displayValue.length - 1);
|
||||
}
|
||||
|
||||
let match = aRule.value instanceof RegExp ?
|
||||
aRule.value.test(displayValue) :
|
||||
displayValue == aRule.value;
|
||||
if (!match) {
|
||||
info("rule " + aRule.name + " did not match value, expected '" +
|
||||
aRule.value + "', found '" + displayValue + "'");
|
||||
return resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click selects a row in the table.
|
||||
*
|
||||
* @param {[String]} ids
|
||||
* The array id of the item in the tree
|
||||
*/
|
||||
function selectTreeItem(ids) {
|
||||
// Expand tree as some/all items could be collapsed leading to click on an
|
||||
// incorrect tree item
|
||||
gUI.tree.expandAll();
|
||||
click(gPanelWindow.document.querySelector("[data-id='" + JSON.stringify(ids) +
|
||||
"'] > .tree-widget-item"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Click selects a row in the table.
|
||||
*
|
||||
* @param {String} id
|
||||
* The id of the row in the table widget
|
||||
*/
|
||||
function selectTableItem(id) {
|
||||
click(gPanelWindow.document.querySelector(".table-widget-cell[data-id='" +
|
||||
id + "']"));
|
||||
}
|
8
browser/devtools/storage/test/moz.build
Normal file
@ -0,0 +1,8 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['browser.ini']
|
||||
|
99
browser/devtools/storage/test/storage-complex-values.html
Normal file
@ -0,0 +1,99 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 970517 - Storage inspector front end - tests
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Storage inspector test for correct values in the sidebar</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
let partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
|
||||
let cookieExpiresTime = 2000000000000;
|
||||
// Setting up some cookies to eat.
|
||||
document.cookie = "c1=" + JSON.stringify([
|
||||
"foo", "Bar", {
|
||||
foo: "Bar"
|
||||
}]) + "; expires=" + new Date(cookieExpiresTime).toGMTString() +
|
||||
"; path=/browser";
|
||||
document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
|
||||
// ... and some local storage items ..
|
||||
var es6 = "for";
|
||||
localStorage.setItem("ls1", JSON.stringify({
|
||||
es6, the: "win", baz: [0, 2, 3, {
|
||||
deep: "down",
|
||||
nobody: "cares"
|
||||
}]}));
|
||||
localStorage.setItem("ls2", "foobar-2");
|
||||
localStorage.setItem("ls3", "http://foobar.com/baz.php");
|
||||
// ... and finally some session storage items too
|
||||
sessionStorage.setItem("ss1", "This#is#an#array");
|
||||
sessionStorage.setItem("ss2", "This~is~another~array");
|
||||
sessionStorage.setItem("ss3", "this#is~an#object~foo#bar");
|
||||
console.log("added cookies and stuff from main page");
|
||||
|
||||
function success(event) {
|
||||
setupIDB.next(event);
|
||||
}
|
||||
|
||||
window.idbGenerator = function*(callback) {
|
||||
let request = indexedDB.open("idb1", 1);
|
||||
request.onupgradeneeded = success;
|
||||
request.onerror = function(e) {
|
||||
throw new Error("error opening db connection");
|
||||
};
|
||||
let event = yield undefined;
|
||||
let db = event.target.result;
|
||||
let store1 = db.createObjectStore("obj1", { keyPath: "id" });
|
||||
store1.createIndex("name", "name", { unique: false });
|
||||
store1.createIndex("email", "email", { unique: true });
|
||||
let store2 = db.createObjectStore("obj2", { keyPath: "id2" });
|
||||
|
||||
store1.add({id: 1, name: "foo", email: "foo@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"}).onsuccess = success;
|
||||
yield undefined;
|
||||
|
||||
store1.transaction.oncomplete = success;
|
||||
yield undefined;
|
||||
db.close();
|
||||
|
||||
request = indexedDB.open("idb2", 1);
|
||||
request.onupgradeneeded = success;
|
||||
event = yield undefined;
|
||||
|
||||
let db2 = event.target.result;
|
||||
let store3 = db2.createObjectStore("obj3", { keyPath: "id3" });
|
||||
store3.createIndex("name2", "name2", { unique: true });
|
||||
store3.transaction.oncomplete = success;
|
||||
yield undefined;
|
||||
db2.close();
|
||||
console.log("added cookies and stuff from main page");
|
||||
callback();
|
||||
}
|
||||
|
||||
function successClear(event) {
|
||||
clearIterator.next(event);
|
||||
}
|
||||
|
||||
window.clear = function*(callback) {
|
||||
document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/browser";
|
||||
document.cookie = "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
indexedDB.deleteDatabase("idb1").onsuccess = successClear;
|
||||
yield undefined;
|
||||
indexedDB.deleteDatabase("idb2").onsuccess = successClear;
|
||||
yield undefined;
|
||||
console.log("removed cookies and stuff from main page");
|
||||
callback();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
94
browser/devtools/storage/test/storage-listings.html
Normal file
@ -0,0 +1,94 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 970517 - Storage inspector front end - tests
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Storage inspector test for listing hosts and storages</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="http://sectest1.example.org/browser/browser/devtools/storage/test/storage-unsecured-iframe.html"></iframe>
|
||||
<iframe src="https://sectest1.example.org:443/browser/browser/devtools/storage/test/storage-secured-iframe.html"></iframe>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
let partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
|
||||
let cookieExpiresTime1 = 2000000000000;
|
||||
let cookieExpiresTime2 = 2000000001000;
|
||||
// Setting up some cookies to eat.
|
||||
document.cookie = "c1=foobar; expires=" +
|
||||
new Date(cookieExpiresTime1).toGMTString() + "; path=/browser";
|
||||
document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
|
||||
document.cookie = "c3=foobar-2; secure=true; expires=" +
|
||||
new Date(cookieExpiresTime2).toGMTString() + "; path=/";
|
||||
// ... and some local storage items ..
|
||||
localStorage.setItem("ls1", "foobar");
|
||||
localStorage.setItem("ls2", "foobar-2");
|
||||
// ... and finally some session storage items too
|
||||
sessionStorage.setItem("ss1", "foobar-3");
|
||||
console.log("added cookies and stuff from main page");
|
||||
|
||||
function success(event) {
|
||||
setupIDB.next(event);
|
||||
}
|
||||
|
||||
window.idbGenerator = function*(callback) {
|
||||
let request = indexedDB.open("idb1", 1);
|
||||
request.onupgradeneeded = success;
|
||||
request.onerror = function(e) {
|
||||
throw new Error("error opening db connection");
|
||||
};
|
||||
let event = yield undefined;
|
||||
let db = event.target.result;
|
||||
let store1 = db.createObjectStore("obj1", { keyPath: "id" });
|
||||
store1.createIndex("name", "name", { unique: false });
|
||||
store1.createIndex("email", "email", { unique: true });
|
||||
let store2 = db.createObjectStore("obj2", { keyPath: "id2" });
|
||||
|
||||
store1.add({id: 1, name: "foo", email: "foo@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"}).onsuccess = success;
|
||||
yield undefined;
|
||||
|
||||
store1.transaction.oncomplete = success;
|
||||
yield undefined;
|
||||
db.close();
|
||||
|
||||
request = indexedDB.open("idb2", 1);
|
||||
request.onupgradeneeded = success;
|
||||
event = yield undefined;
|
||||
|
||||
let db2 = event.target.result;
|
||||
let store3 = db2.createObjectStore("obj3", { keyPath: "id3" });
|
||||
store3.createIndex("name2", "name2", { unique: true });
|
||||
store3.transaction.oncomplete = success;
|
||||
yield undefined;
|
||||
db2.close();
|
||||
console.log("added cookies and stuff from main page");
|
||||
callback();
|
||||
}
|
||||
|
||||
function successClear(event) {
|
||||
clearIterator.next(event);
|
||||
}
|
||||
|
||||
window.clear = function*(callback) {
|
||||
document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/browser";
|
||||
document.cookie = "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; secure=true";
|
||||
document.cookie = "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=" + partialHostname;
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
indexedDB.deleteDatabase("idb1").onsuccess = successClear;
|
||||
yield undefined;
|
||||
indexedDB.deleteDatabase("idb2").onsuccess = successClear;
|
||||
yield undefined;
|
||||
console.log("removed cookies and stuff from main page");
|
||||
callback();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
71
browser/devtools/storage/test/storage-secured-iframe.html
Normal file
@ -0,0 +1,71 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Iframe for testing multiple host detetion in storage actor
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
document.cookie = "sc1=foobar;";
|
||||
localStorage.setItem("iframe-s-ls1", "foobar");
|
||||
sessionStorage.setItem("iframe-s-ss1", "foobar-2");
|
||||
|
||||
function success(event) {
|
||||
setupIDB.next(event);
|
||||
}
|
||||
|
||||
window.idbGenerator = function*(callback) {
|
||||
let request = indexedDB.open("idb-s1", 1);
|
||||
request.onupgradeneeded = success;
|
||||
request.onerror = function(e) {
|
||||
throw new Error("error opening db connection");
|
||||
};
|
||||
let event = yield undefined;
|
||||
let db = event.target.result;
|
||||
let store1 = db.createObjectStore("obj-s1", { keyPath: "id" });
|
||||
|
||||
store1.add({id: 6, name: "foo", email: "foo@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store1.add({id: 7, name: "foo2", email: "foo2@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store1.transaction.oncomplete = success;
|
||||
yield undefined;
|
||||
db.close();
|
||||
|
||||
request = indexedDB.open("idb-s2", 1);
|
||||
request.onupgradeneeded = success;
|
||||
event = yield undefined;
|
||||
|
||||
let db2 = event.target.result;
|
||||
let store3 = db2.createObjectStore("obj-s2", { keyPath: "id3", autoIncrement: true });
|
||||
store3.createIndex("name2", "name2", { unique: true });
|
||||
store3.add({id3: 16, name2: "foo", email: "foo@bar.com"}).onsuccess = success;
|
||||
yield undefined;
|
||||
store3.transaction.oncomplete = success;
|
||||
yield undefined;
|
||||
db2.close();
|
||||
console.log("added cookies and stuff from secured iframe");
|
||||
callback();
|
||||
}
|
||||
|
||||
function successClear(event) {
|
||||
clearIterator.next(event);
|
||||
}
|
||||
|
||||
window.clear = function*(callback) {
|
||||
document.cookie = "sc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
indexedDB.deleteDatabase("idb-s1").onsuccess = successClear;
|
||||
yield undefined;
|
||||
indexedDB.deleteDatabase("idb-s2").onsuccess = successClear;
|
||||
yield undefined;
|
||||
console.log("removed cookies and stuff from secured iframe");
|
||||
callback();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
27
browser/devtools/storage/test/storage-unsecured-iframe.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Iframe for testing multiple host detetion in storage actor
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true";
|
||||
localStorage.setItem("iframe-u-ls1", "foobar");
|
||||
sessionStorage.setItem("iframe-u-ss1", "foobar1");
|
||||
sessionStorage.setItem("iframe-u-ss2", "foobar2");
|
||||
console.log("added cookies and stuff from unsecured iframe");
|
||||
|
||||
window.clear = function*(callback) {
|
||||
document.cookie = "uc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.example.org; secure=true";
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
console.log("removed cookies and stuff from unsecured iframe");
|
||||
callback();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
63
browser/devtools/storage/test/storage-updates.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 965872 - Storage inspector actor with cookies, local storage and session storage.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Storage inspector blank html for tests</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
window.addCookie = function(name, value, path, domain, expires, secure) {
|
||||
var cookieString = name + "=" + value + ";";
|
||||
if (path) {
|
||||
cookieString += "path=" + path + ";";
|
||||
}
|
||||
if (domain) {
|
||||
cookieString += "domain=" + domain + ";";
|
||||
}
|
||||
if (expires) {
|
||||
cookieString += "expires=" + expires + ";";
|
||||
}
|
||||
if (secure) {
|
||||
cookieString += "secure=true;";
|
||||
}
|
||||
document.cookie = cookieString;
|
||||
};
|
||||
|
||||
window.removeCookie = function(name, path) {
|
||||
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=" + path;
|
||||
}
|
||||
|
||||
window.clear = function*(callback) {
|
||||
var cookies = document.cookie;
|
||||
for (var cookie of cookies.split(";")) {
|
||||
removeCookie(cookie.split("=")[0]);
|
||||
removeCookie(cookie.split("=")[0], "/browser");
|
||||
}
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
callback();
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
addCookie("c1", "1.2.3.4.5.6.7", "/browser");
|
||||
addCookie("c2", "foobar", "/browser");
|
||||
|
||||
localStorage.setItem("ls1", "testing");
|
||||
localStorage.setItem("ls2", "testing");
|
||||
localStorage.setItem("ls3", "testing");
|
||||
localStorage.setItem("ls4", "testing");
|
||||
localStorage.setItem("ls5", "testing");
|
||||
localStorage.setItem("ls6", "testing");
|
||||
localStorage.setItem("ls7", "testing");
|
||||
|
||||
sessionStorage.setItem("ss1", "foobar");
|
||||
sessionStorage.setItem("ss2", "foobar");
|
||||
sessionStorage.setItem("ss3", "foobar");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
565
browser/devtools/storage/ui.js
Normal file
@ -0,0 +1,565 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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 {Cu} = require("chrome");
|
||||
const STORAGE_STRINGS = "chrome://browser/locale/devtools/storage.properties";
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyGetter(this, "TreeWidget",
|
||||
() => require("devtools/shared/widgets/TreeWidget").TreeWidget);
|
||||
XPCOMUtils.defineLazyGetter(this, "TableWidget",
|
||||
() => require("devtools/shared/widgets/TableWidget").TableWidget);
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
|
||||
"resource://gre/modules/devtools/event-emitter.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ViewHelpers",
|
||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
|
||||
"resource:///modules/devtools/VariablesView.jsm");
|
||||
|
||||
/**
|
||||
* Localization convenience methods.
|
||||
*/
|
||||
let L10N = new ViewHelpers.L10N(STORAGE_STRINGS);
|
||||
|
||||
const GENERIC_VARIABLES_VIEW_SETTINGS = {
|
||||
lazyEmpty: true,
|
||||
lazyEmptyDelay: 10, // ms
|
||||
searchEnabled: true,
|
||||
searchPlaceholder: L10N.getStr("storage.search.placeholder"),
|
||||
preventDescriptorModifiers: true
|
||||
};
|
||||
|
||||
// Columns which are hidden by default in the storage table
|
||||
const HIDDEN_COLUMNS = [
|
||||
"creationTime",
|
||||
"isDomain",
|
||||
"isSecure"
|
||||
];
|
||||
|
||||
/**
|
||||
* StorageUI is controls and builds the UI of the Storage Inspector.
|
||||
*
|
||||
* @param {Front} front
|
||||
* Front for the storage actor
|
||||
* @param {Target} target
|
||||
* Interface for the page we're debugging
|
||||
* @param {Window} panelWin
|
||||
* Window of the toolbox panel to populate UI in.
|
||||
*/
|
||||
this.StorageUI = function StorageUI(front, target, panelWin) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._target = target;
|
||||
this._window = panelWin;
|
||||
this._panelDoc = panelWin.document;
|
||||
this.front = front;
|
||||
|
||||
let treeNode = this._panelDoc.getElementById("storage-tree");
|
||||
this.tree = new TreeWidget(treeNode, {defaultType: "dir"});
|
||||
this.onHostSelect = this.onHostSelect.bind(this);
|
||||
this.tree.on("select", this.onHostSelect);
|
||||
|
||||
let tableNode = this._panelDoc.getElementById("storage-table");
|
||||
this.table = new TableWidget(tableNode, {
|
||||
emptyText: L10N.getStr("table.emptyText"),
|
||||
highlightUpdated: true,
|
||||
});
|
||||
this.displayObjectSidebar = this.displayObjectSidebar.bind(this);
|
||||
this.table.on(TableWidget.EVENTS.ROW_SELECTED, this.displayObjectSidebar)
|
||||
|
||||
this.sidebar = this._panelDoc.getElementById("storage-sidebar");
|
||||
this.sidebar.setAttribute("width", "300");
|
||||
this.view = new VariablesView(this.sidebar.firstChild,
|
||||
GENERIC_VARIABLES_VIEW_SETTINGS);
|
||||
|
||||
this.front.listStores().then(storageTypes => {
|
||||
this.populateStorageTree(storageTypes);
|
||||
});
|
||||
this.onUpdate = this.onUpdate.bind(this);
|
||||
this.front.on("stores-update", this.onUpdate);
|
||||
|
||||
this.handleKeypress = this.handleKeypress.bind(this);
|
||||
this._panelDoc.addEventListener("keypress", this.handleKeypress);
|
||||
}
|
||||
|
||||
exports.StorageUI = StorageUI;
|
||||
|
||||
StorageUI.prototype = {
|
||||
|
||||
storageTypes: null,
|
||||
shouldResetColumns: true,
|
||||
|
||||
destroy: function() {
|
||||
this.front.off("stores-update", this.onUpdate);
|
||||
this._panelDoc.removeEventListener("keypress", this.handleKeypress)
|
||||
},
|
||||
|
||||
/**
|
||||
* Empties and hides the object viewer sidebar
|
||||
*/
|
||||
hideSidebar: function() {
|
||||
this.view.empty();
|
||||
this.sidebar.hidden = true;
|
||||
this.table.clearSelection();
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the given item from the storage table. Reselects the next item in
|
||||
* the table and repopulates the sidebar with that item's data if the item
|
||||
* being removed was selected.
|
||||
*/
|
||||
removeItemFromTable: function(name) {
|
||||
if (this.table.isSelected(name)) {
|
||||
if (this.table.selectedIndex == 0) {
|
||||
this.table.selectNextRow()
|
||||
}
|
||||
else {
|
||||
this.table.selectPreviousRow();
|
||||
}
|
||||
this.table.remove(name);
|
||||
this.displayObjectSidebar();
|
||||
}
|
||||
else {
|
||||
this.table.remove(name);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler for "stores-update" event coming from the storage actor.
|
||||
*
|
||||
* @param {object} argument0
|
||||
* An object containing the details of the added, changed and deleted
|
||||
* storage objects.
|
||||
* Each of these 3 objects are of the following format:
|
||||
* {
|
||||
* <store_type1>: {
|
||||
* <host1>: [<store_names1>, <store_name2>...],
|
||||
* <host2>: [<store_names34>...], ...
|
||||
* },
|
||||
* <store_type2>: {
|
||||
* <host1>: [<store_names1>, <store_name2>...],
|
||||
* <host2>: [<store_names34>...], ...
|
||||
* }, ...
|
||||
* }
|
||||
* Where store_type1 and store_type2 is one of cookies, indexedDB,
|
||||
* sessionStorage and localStorage; host1, host2 are the host in which
|
||||
* this change happened; and [<store_namesX] is an array of the names
|
||||
* of the changed store objects. This array is empty for deleted object
|
||||
* if the host was completely removed.
|
||||
*/
|
||||
onUpdate: function({ changed, added, deleted }) {
|
||||
if (deleted) {
|
||||
for (let type in deleted) {
|
||||
for (let host in deleted[type]) {
|
||||
if (!deleted[type][host].length) {
|
||||
// This means that the whole host is deleted, thus the item should
|
||||
// be removed from the storage tree
|
||||
if (this.tree.isSelected([type, host])) {
|
||||
this.table.clear();
|
||||
this.hideSidebar();
|
||||
this.tree.selectPreviousItem();
|
||||
}
|
||||
|
||||
this.tree.remove([type, host]);
|
||||
}
|
||||
else if (this.tree.isSelected([type, host])) {
|
||||
for (let name of deleted[type][host]) {
|
||||
try {
|
||||
// trying to parse names in case its for indexedDB
|
||||
let names = JSON.parse(name);
|
||||
if (!names[2]) {
|
||||
if (this.tree.isSelected([type, host, names[0], names[1]])) {
|
||||
this.tree.selectPreviousItem();
|
||||
this.tree.remove([type, host, names[0], names[1]]);
|
||||
this.table.clear();
|
||||
this.hideSidebar();
|
||||
}
|
||||
}
|
||||
else if (this.tree.isSelected([type, host, names[0], names[1]])) {
|
||||
this.removeItemFromTable(names[2]);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
this.removeItemFromTable(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (added) {
|
||||
for (let type in added) {
|
||||
for (let host in added[type]) {
|
||||
this.tree.add([type, {id: host, type: "url"}]);
|
||||
for (let name of added[type][host]) {
|
||||
try {
|
||||
name = JSON.parse(name);
|
||||
if (name.length == 3) {
|
||||
name.splice(2, 1);
|
||||
}
|
||||
this.tree.add([type, host, ...name]);
|
||||
if (!this.tree.selectedItem) {
|
||||
this.tree.selectedItem = [type, host, name[0], name[1]];
|
||||
this.fetchStorageObjects(type, host, [JSON.stringify(name)], 1);
|
||||
}
|
||||
} catch(ex) {}
|
||||
}
|
||||
|
||||
if (this.tree.isSelected([type, host])) {
|
||||
this.fetchStorageObjects(type, host, added[type][host], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
let [type, host, db, objectStore] = this.tree.selectedItem;
|
||||
if (changed[type] && changed[type][host]) {
|
||||
if (changed[type][host].length) {
|
||||
try {
|
||||
let toUpdate = [];
|
||||
for (let name of changed[type][host]) {
|
||||
let names = JSON.parse(name);
|
||||
if (names[0] == db && names[1] == objectStore && names[2]) {
|
||||
toUpdate.push(name);
|
||||
}
|
||||
}
|
||||
this.fetchStorageObjects(type, host, toUpdate, 2);
|
||||
}
|
||||
catch (ex) {
|
||||
this.fetchStorageObjects(type, host, changed[type][host], 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (added || deleted || changed) {
|
||||
this.emit("store-objects-updated");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the storage objects from the storage actor and populates the
|
||||
* storage table with the returned data.
|
||||
*
|
||||
* @param {string} type
|
||||
* The type of storage. Ex. "cookies"
|
||||
* @param {string} host
|
||||
* Hostname
|
||||
* @param {array} names
|
||||
* Names of particular store objects. Empty if all are requested
|
||||
* @param {number} reason
|
||||
* 2 for update, 1 for new row in an existing table and 0 when
|
||||
* populating a table for the first time for the given host/type
|
||||
*/
|
||||
fetchStorageObjects: function(type, host, names, reason) {
|
||||
this.storageTypes[type].getStoreObjects(host, names).then(({data}) => {
|
||||
if (!data.length) {
|
||||
this.emit("store-objects-updated");
|
||||
return;
|
||||
}
|
||||
if (this.shouldResetColumns) {
|
||||
this.resetColumns(data[0], type);
|
||||
}
|
||||
this.populateTable(data, reason);
|
||||
this.emit("store-objects-updated");
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates the storage tree which displays the list of storages present for
|
||||
* the page.
|
||||
*
|
||||
* @param {object} storageTypes
|
||||
* List of storages and their corresponding hosts returned by the
|
||||
* StorageFront.listStores call.
|
||||
*/
|
||||
populateStorageTree: function(storageTypes) {
|
||||
this.storageTypes = {};
|
||||
for (let type in storageTypes) {
|
||||
let typeLabel = L10N.getStr("tree.labels." + type);
|
||||
this.tree.add([{id: type, label: typeLabel, type: "store"}]);
|
||||
if (storageTypes[type].hosts) {
|
||||
this.storageTypes[type] = storageTypes[type];
|
||||
for (let host in storageTypes[type].hosts) {
|
||||
this.tree.add([type, {id: host, type: "url"}]);
|
||||
for (let name of storageTypes[type].hosts[host]) {
|
||||
|
||||
try {
|
||||
let names = JSON.parse(name);
|
||||
this.tree.add([type, host, ...names]);
|
||||
if (!this.tree.selectedItem) {
|
||||
this.tree.selectedItem = [type, host, names[0], names[1]];
|
||||
this.fetchStorageObjects(type, host, [name], 0);
|
||||
}
|
||||
} catch(ex) {}
|
||||
}
|
||||
if (!this.tree.selectedItem) {
|
||||
this.tree.selectedItem = [type, host];
|
||||
this.fetchStorageObjects(type, host, null, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates the selected entry from teh table in the sidebar for a more
|
||||
* detailed view.
|
||||
*/
|
||||
displayObjectSidebar: function() {
|
||||
let item = this.table.selectedRow;
|
||||
if (!item) {
|
||||
// Make sure that sidebar is hidden and return
|
||||
this.sidebar.hidden = true;
|
||||
return;
|
||||
}
|
||||
this.sidebar.hidden = false;
|
||||
this.view.empty();
|
||||
let mainScope = this.view.addScope(L10N.getStr("storage.data.label"));
|
||||
mainScope.expanded = true;
|
||||
|
||||
if (item.name && item.valueActor) {
|
||||
let itemVar = mainScope.addItem(item.name + "", {}, true);
|
||||
|
||||
item.valueActor.string().then(value => {
|
||||
// The main area where the value will be displayed
|
||||
itemVar.setGrip(value);
|
||||
|
||||
// May be the item value is a json or a key value pair itself
|
||||
this.parseItemValue(item.name, value);
|
||||
|
||||
// By default the item name and value are shown. If this is the only
|
||||
// information available, then nothing else is to be displayed.
|
||||
let itemProps = Object.keys(item);
|
||||
if (itemProps.length == 3) {
|
||||
this.emit("sidebar-updated");
|
||||
return;
|
||||
}
|
||||
|
||||
// Display any other information other than the item name and value
|
||||
// which may be available.
|
||||
let rawObject = Object.create(null);
|
||||
let otherProps =
|
||||
itemProps.filter(e => e != "name" && e != "value" && e != "valueActor");
|
||||
for (let prop of otherProps) {
|
||||
rawObject[prop] = item[prop];
|
||||
}
|
||||
itemVar.populate(rawObject, {sorted: true});
|
||||
itemVar.twisty = true;
|
||||
itemVar.expanded = true;
|
||||
this.emit("sidebar-updated");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Case when displaying IndexedDB db/object store properties.
|
||||
for (let key in item) {
|
||||
mainScope.addItem(key, {}, true).setGrip(item[key]);
|
||||
this.parseItemValue(key, item[key]);
|
||||
}
|
||||
this.emit("sidebar-updated");
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to parse a string value into either a json or a key-value separated
|
||||
* object and populates the sidebar with the parsed value. The value can also
|
||||
* be a key separated array.
|
||||
*
|
||||
* @param {string} name
|
||||
* The key corresponding to the `value` string in the object
|
||||
* @param {string} value
|
||||
* The string to be parsed into an object
|
||||
*/
|
||||
parseItemValue: function(name, value) {
|
||||
let json = null
|
||||
try {
|
||||
json = JSON.parse(value);
|
||||
}
|
||||
catch (ex) {
|
||||
json = null;
|
||||
}
|
||||
|
||||
if (!json && value) {
|
||||
json = this._extractKeyValPairs(value);
|
||||
}
|
||||
|
||||
// return if json is null, or same as value, or just a string.
|
||||
if (!json || json == value || typeof json == "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
// One special case is a url which gets separated as key value pair on :
|
||||
if ((json.length == 2 || Object.keys(json).length == 1) &&
|
||||
((json[0] || Object.keys(json)[0]) + "").match(/^(http|file|ftp)/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let jsonObject = Object.create(null);
|
||||
jsonObject[name] = json;
|
||||
let valueScope = this.view.getScopeAtIndex(1) ||
|
||||
this.view.addScope(L10N.getStr("storage.parsedValue.label"));
|
||||
valueScope.expanded = true;
|
||||
let jsonVar = valueScope.addItem("", Object.create(null), true);
|
||||
jsonVar.expanded = true;
|
||||
jsonVar.twisty = true;
|
||||
jsonVar.populate(jsonObject, {expanded: true});
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to parse a string into an object on the basis of key-value pairs,
|
||||
* separated by various separators. If failed, tries to parse for single
|
||||
* separator separated values to form an array.
|
||||
*
|
||||
* @param {string} value
|
||||
* The string to be parsed into an object or array
|
||||
*/
|
||||
_extractKeyValPairs: function(value) {
|
||||
let makeObject = (keySep, pairSep) => {
|
||||
let object = {};
|
||||
for (let pair of value.split(pairSep)) {
|
||||
let [key, val] = pair.split(keySep);
|
||||
object[key] = val;
|
||||
}
|
||||
return object;
|
||||
};
|
||||
|
||||
// Possible separators.
|
||||
const separators = ["=", ":", "~", "#", "&", "\\*", ",", "\\."];
|
||||
// Testing for object
|
||||
for (let i = 0; i < separators.length; i++) {
|
||||
let kv = separators[i];
|
||||
for (let j = 0; j < separators.length; j++) {
|
||||
if (i == j) {
|
||||
continue;
|
||||
}
|
||||
let p = separators[j];
|
||||
let regex = new RegExp("^([^" + kv + p + "]*" + kv + "+[^" + kv + p +
|
||||
"]*" + p + "*)+$", "g");
|
||||
if (value.match(regex) && value.contains(kv) &&
|
||||
(value.contains(p) || value.split(kv).length == 2)) {
|
||||
return makeObject(kv, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Testing for array
|
||||
for (let i = 0; i < separators.length; i++) {
|
||||
let p = separators[i];
|
||||
let regex = new RegExp("^[^" + p + "]+(" + p + "+[^" + p + "]*)+$", "g");
|
||||
if (value.match(regex)) {
|
||||
return value.split(p.replace(/\\*/g, ""));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Select handler for the storage tree. Fetches details of the selected item
|
||||
* from the storage details and populates the storage tree.
|
||||
*
|
||||
* @param {string} event
|
||||
* The name of the event fired
|
||||
* @param {array} item
|
||||
* An array of ids which represent the location of the selected item in
|
||||
* the storage tree
|
||||
*/
|
||||
onHostSelect: function(event, item) {
|
||||
this.table.clear();
|
||||
this.hideSidebar();
|
||||
|
||||
let [type, host] = item;
|
||||
let names = null;
|
||||
if (!host) {
|
||||
return;
|
||||
}
|
||||
if (item.length > 2) {
|
||||
names = [JSON.stringify(item.slice(2))];
|
||||
}
|
||||
this.shouldResetColumns = true;
|
||||
this.fetchStorageObjects(type, host, names, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the column headers in the storage table with the pased object `data`
|
||||
*
|
||||
* @param {object} data
|
||||
* The object from which key and values will be used for naming the
|
||||
* headers of the columns
|
||||
* @param {string} type
|
||||
* The type of storage corresponding to the after-reset columns in the
|
||||
* table.
|
||||
*/
|
||||
resetColumns: function(data, type) {
|
||||
let columns = {};
|
||||
let uniqueKey = null;
|
||||
for (let key in data) {
|
||||
if (!uniqueKey) {
|
||||
this.table.uniqueId = uniqueKey = key;
|
||||
}
|
||||
columns[key] = L10N.getStr("table.headers." + type + "." + key);
|
||||
}
|
||||
this.table.setColumns(columns, null, HIDDEN_COLUMNS);
|
||||
this.shouldResetColumns = false;
|
||||
this.hideSidebar();
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates or updates the rows in the storage table.
|
||||
*
|
||||
* @param {array[object]} data
|
||||
* Array of objects to be populated in the storage table
|
||||
* @param {number} reason
|
||||
* The reason of this populateTable call. 2 for update, 1 for new row
|
||||
* in an existing table and 0 when populating a table for the first
|
||||
* time for the given host/type
|
||||
*/
|
||||
populateTable: function(data, reason) {
|
||||
for (let item of data) {
|
||||
if (item.value) {
|
||||
item.valueActor = item.value;
|
||||
item.value = item.value.initial || "";
|
||||
}
|
||||
if (item.expires != null) {
|
||||
item.expires = item.expires
|
||||
? new Date(item.expires).toLocaleString()
|
||||
: L10N.getStr("label.expires.session");
|
||||
}
|
||||
if (item.creationTime != null) {
|
||||
item.creationTime = new Date(item.creationTime).toLocaleString();
|
||||
}
|
||||
if (item.lastAccessed != null) {
|
||||
item.lastAccessed = new Date(item.lastAccessed).toLocaleString();
|
||||
}
|
||||
if (reason < 2) {
|
||||
this.table.push(item, reason == 0);
|
||||
}
|
||||
else {
|
||||
this.table.update(item);
|
||||
if (item == this.table.selectedRow && !this.sidebar.hidden) {
|
||||
this.displayObjectSidebar();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles keypress event on the body table to close the sidebar when open
|
||||
*
|
||||
* @param {DOMEvent} event
|
||||
* The event passed by the keypress event.
|
||||
*/
|
||||
handleKeypress: function(event) {
|
||||
if (event.keyCode == event.DOM_VK_ESCAPE && !this.sidebar.hidden) {
|
||||
// Stop Propagation to prevent opening up of split console
|
||||
this.hideSidebar();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
100
browser/locales/en-US/chrome/browser/devtools/storage.properties
Normal file
@ -0,0 +1,100 @@
|
||||
# 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/.
|
||||
|
||||
# LOCALIZATION NOTE These strings are used inside the Storage Editor tool.
|
||||
# LOCALIZATION NOTE The correct localization of this file might be to keep it
|
||||
# in English, or another language commonly spoken among web developers.
|
||||
# You want to make that choice consistent across the developer tools.
|
||||
# A good criteria is the language in which you'd find the best documentation
|
||||
# on web development on the web.
|
||||
|
||||
# LOCALIZATION NOTE (chromeWindowTitle): This is the title of the Storage
|
||||
# 'chrome' window. That is, the main window with all the cookies, etc.
|
||||
# The argument is either the content document's title or its href if no title
|
||||
# is available.
|
||||
chromeWindowTitle=Storage [%S]
|
||||
|
||||
# LOCALIZATION NOTE (open.commandkey): This the key to use in
|
||||
# conjunction with shift to open the storage editor
|
||||
open.commandkey=VK_F9
|
||||
|
||||
# LOCALIZATION NOTE (open.accesskey): The access key used to open the storage
|
||||
# editor.
|
||||
open.accesskey=d
|
||||
|
||||
# LOCALIZATION NOTE (storage.label):
|
||||
# This string is displayed in the title of the tab when the storage editor is
|
||||
# displayed inside the developer tools window and in the Developer Tools Menu.
|
||||
storage.label=Storage
|
||||
|
||||
# LOCALIZATION NOTE (storage.tooltip):
|
||||
# This string is displayed in the tooltip of the tab when the storage editor is
|
||||
# displayed inside the developer tools window.
|
||||
storage.tooltip=Storage Inspector (Cookies, Local Storage ...)
|
||||
|
||||
# LOCALIZATION NOTE (tree.emptyText):
|
||||
# This string is displayed when the Storage Tree is empty. This can happen when
|
||||
# there are no websites on the current page (about:blank)
|
||||
tree.emptyText=No hosts on the page
|
||||
|
||||
# LOCALIZATION NOTE (table.emptyText):
|
||||
# This string is displayed when there are no rows in the Storage Table for the
|
||||
# selected host.
|
||||
table.emptyText=No data present for selected host
|
||||
|
||||
# LOCALIZATION NOTE (tree.labels.*):
|
||||
# These strings are the labels for Storage type groups present in the Storage
|
||||
# Tree, like cookies, local storage etc.
|
||||
tree.labels.cookies=Cookies
|
||||
tree.labels.localStorage=Local Storage
|
||||
tree.labels.sessionStorage=Session Storage
|
||||
tree.labels.indexedDB=Indexed DB
|
||||
|
||||
# LOCALIZATION NOTE (table.headers.*.*):
|
||||
# These strings are the header names of the columns in the Storage Table for
|
||||
# each type of storage available through the Storage Tree to the side.
|
||||
table.headers.cookies.name=Name
|
||||
table.headers.cookies.path=Path
|
||||
table.headers.cookies.host=Domain
|
||||
table.headers.cookies.expires=Expires on
|
||||
table.headers.cookies.value=Value
|
||||
table.headers.cookies.lastAccessed:Last accessed on
|
||||
table.headers.cookies.creationTime:Created on
|
||||
table.headers.cookies.isHttpOnly:isHttpOnly
|
||||
table.headers.cookies.isSecure:isSecure
|
||||
table.headers.cookies.isDomain:isDomain
|
||||
|
||||
table.headers.localStorage.name=Key
|
||||
table.headers.localStorage.value=Value
|
||||
|
||||
table.headers.sessionStorage.name=Key
|
||||
table.headers.sessionStorage.value=Value
|
||||
|
||||
table.headers.indexedDB.name=Key
|
||||
table.headers.indexedDB.db=Database Name
|
||||
table.headers.indexedDB.objectStore=Object Store Name
|
||||
table.headers.indexedDB.value=Value
|
||||
table.headers.indexedDB.origin=Origin
|
||||
table.headers.indexedDB.version=Version
|
||||
table.headers.indexedDB.objectStores=Object Stores
|
||||
table.headers.indexedDB.keyPath=Key
|
||||
table.headers.indexedDB.autoIncrement=Auto Increment
|
||||
table.headers.indexedDB.indexes=Indexes
|
||||
|
||||
# LOCALIZATION NOTE (label.expires.session):
|
||||
# This string is displayed in the expires column when the cookie is Session
|
||||
# Cookie
|
||||
label.expires.session=Session
|
||||
|
||||
# LOCALIZATION NOTE (storage.search.placeholder):
|
||||
# This is the placeholder text in the sidebar search box
|
||||
storage.search.placeholder=Filter values
|
||||
|
||||
# LOCALIZATION NOTE (storage.data.label):
|
||||
# This is the heading displayed over the item value in the sidebar
|
||||
storage.data.label=Data
|
||||
|
||||
# LOCALIZATION NOTE (storage.parsedValue.label):
|
||||
# This is the heading displayed over the item parsed value in the sidebar
|
||||
storage.parsedValue.label=Parsed Value
|
@ -55,11 +55,11 @@ feedback_category_other=Other:
|
||||
feedback_custom_category_text_placeholder=What went wrong?
|
||||
feedback_submit_button=Submit
|
||||
feedback_back_button=Back
|
||||
## LOCALIZATION NOTE (feedback_window_will_close_in):
|
||||
## LOCALIZATION NOTE (feedback_window_will_close_in2):
|
||||
## Semicolon-separated list of plural forms. See:
|
||||
## http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
## In this item, don't translate the part between {{..}}
|
||||
feedback_window_will_close_in=This window will close in {{countdown}} second;This window will close in {{countdown}} seconds
|
||||
feedback_window_will_close_in2=This window will close in {{countdown}} second;This window will close in {{countdown}} seconds
|
||||
|
||||
share_email_subject2=Invitation to chat
|
||||
## LOCALIZATION NOTE (share_email_body2): In this item, don't translate the
|
||||
|
@ -43,6 +43,7 @@
|
||||
locale/browser/devtools/tilt.properties (%chrome/browser/devtools/tilt.properties)
|
||||
locale/browser/devtools/scratchpad.properties (%chrome/browser/devtools/scratchpad.properties)
|
||||
locale/browser/devtools/scratchpad.dtd (%chrome/browser/devtools/scratchpad.dtd)
|
||||
locale/browser/devtools/storage.properties (%chrome/browser/devtools/storage.properties)
|
||||
locale/browser/devtools/styleeditor.properties (%chrome/browser/devtools/styleeditor.properties)
|
||||
locale/browser/devtools/styleeditor.dtd (%chrome/browser/devtools/styleeditor.dtd)
|
||||
locale/browser/devtools/styleinspector.dtd (%chrome/browser/devtools/styleinspector.dtd)
|
||||
|
@ -203,6 +203,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/filetype-dir-close.svg (../shared/devtools/images/filetypes/dir-close.svg)
|
||||
skin/classic/browser/devtools/filetype-dir-open.svg (../shared/devtools/images/filetypes/dir-open.svg)
|
||||
skin/classic/browser/devtools/filetype-globe.svg (../shared/devtools/images/filetypes/globe.svg)
|
||||
skin/classic/browser/devtools/filetype-store.svg (../shared/devtools/images/filetypes/store.svg)
|
||||
skin/classic/browser/devtools/commandline-icon.png (../shared/devtools/images/commandline-icon.png)
|
||||
skin/classic/browser/devtools/commandline-icon@2x.png (../shared/devtools/images/commandline-icon@2x.png)
|
||||
skin/classic/browser/devtools/command-paintflashing.png (../shared/devtools/images/command-paintflashing.png)
|
||||
@ -248,6 +249,7 @@ browser.jar:
|
||||
* skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css)
|
||||
* skin/classic/browser/devtools/splitview.css (../shared/devtools/splitview.css)
|
||||
skin/classic/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css)
|
||||
skin/classic/browser/devtools/storage.css (../shared/devtools/storage.css)
|
||||
* skin/classic/browser/devtools/webaudioeditor.css (devtools/webaudioeditor.css)
|
||||
skin/classic/browser/devtools/magnifying-glass.png (../shared/devtools/images/magnifying-glass.png)
|
||||
skin/classic/browser/devtools/magnifying-glass@2x.png (../shared/devtools/images/magnifying-glass@2x.png)
|
||||
@ -310,6 +312,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/tool-inspector.svg (../shared/devtools/images/tool-inspector.svg)
|
||||
skin/classic/browser/devtools/tool-inspector.svg (../shared/devtools/images/tool-inspector.svg)
|
||||
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
|
||||
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
|
||||
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
|
||||
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
|
||||
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
|
||||
|
BIN
browser/themes/osx/Toolbar-yosemite.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
browser/themes/osx/Toolbar-yosemite@2x.png
Normal file
After Width: | Height: | Size: 34 KiB |
@ -145,6 +145,10 @@ browser.jar:
|
||||
skin/classic/browser/loop/toolbar@2x.png (loop/toolbar@2x.png)
|
||||
skin/classic/browser/loop/toolbar-inverted.png (loop/toolbar-inverted.png)
|
||||
skin/classic/browser/loop/toolbar-inverted@2x.png (loop/toolbar-inverted@2x.png)
|
||||
skin/classic/browser/yosemite/loop/menuPanel.png (loop/menuPanel-yosemite.png)
|
||||
skin/classic/browser/yosemite/loop/menuPanel@2x.png (loop/menuPanel-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/loop/toolbar.png (loop/toolbar-yosemite.png)
|
||||
skin/classic/browser/yosemite/loop/toolbar@2x.png (loop/toolbar-yosemite@2x.png)
|
||||
skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
|
||||
skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png)
|
||||
skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png (customizableui/customize-titleBar-toggle@2x.png)
|
||||
@ -326,6 +330,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/filetype-dir-close.svg (../shared/devtools/images/filetypes/dir-close.svg)
|
||||
skin/classic/browser/devtools/filetype-dir-open.svg (../shared/devtools/images/filetypes/dir-open.svg)
|
||||
skin/classic/browser/devtools/filetype-globe.svg (../shared/devtools/images/filetypes/globe.svg)
|
||||
skin/classic/browser/devtools/filetype-store.svg (../shared/devtools/images/filetypes/store.svg)
|
||||
skin/classic/browser/devtools/commandline-icon.png (../shared/devtools/images/commandline-icon.png)
|
||||
skin/classic/browser/devtools/commandline-icon@2x.png (../shared/devtools/images/commandline-icon@2x.png)
|
||||
skin/classic/browser/devtools/command-paintflashing.png (../shared/devtools/images/command-paintflashing.png)
|
||||
@ -372,6 +377,7 @@ browser.jar:
|
||||
* skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css)
|
||||
* skin/classic/browser/devtools/splitview.css (../shared/devtools/splitview.css)
|
||||
skin/classic/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css)
|
||||
skin/classic/browser/devtools/storage.css (../shared/devtools/storage.css)
|
||||
* skin/classic/browser/devtools/webaudioeditor.css (devtools/webaudioeditor.css)
|
||||
skin/classic/browser/devtools/magnifying-glass.png (../shared/devtools/images/magnifying-glass.png)
|
||||
skin/classic/browser/devtools/magnifying-glass@2x.png (../shared/devtools/images/magnifying-glass@2x.png)
|
||||
@ -434,6 +440,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/tool-inspector.svg (../shared/devtools/images/tool-inspector.svg)
|
||||
skin/classic/browser/devtools/tool-inspector.svg (../shared/devtools/images/tool-inspector.svg)
|
||||
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
|
||||
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
|
||||
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
|
||||
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
|
||||
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
|
||||
@ -503,6 +510,22 @@ browser.jar:
|
||||
skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon@2x.png (tabbrowser/alltabs-box-bkgnd-icon-lion@2x.png)
|
||||
skin/classic/browser/lion/tabview/tabview.png (tabview/tabview-lion.png)
|
||||
skin/classic/browser/lion/places/toolbar.png (places/toolbar-lion.png)
|
||||
skin/classic/browser/yosemite/Toolbar.png (Toolbar-yosemite.png)
|
||||
skin/classic/browser/yosemite/Toolbar@2x.png (Toolbar-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/menuPanel.png (menuPanel-yosemite.png)
|
||||
skin/classic/browser/yosemite/menuPanel@2x.png (menuPanel-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/menuPanel-customize.png (menuPanel-customize-yosemite.png)
|
||||
skin/classic/browser/yosemite/menuPanel-customize@2x.png (menuPanel-customize-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/menuPanel-exit.png (menuPanel-exit-yosemite.png)
|
||||
skin/classic/browser/yosemite/menuPanel-exit@2x.png (menuPanel-exit-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/menuPanel-help.png (menuPanel-help-yosemite.png)
|
||||
skin/classic/browser/yosemite/menuPanel-help@2x.png (menuPanel-help-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/menuPanel-small.png (menuPanel-small-yosemite.png)
|
||||
skin/classic/browser/yosemite/menuPanel-small@2x.png (menuPanel-small-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/reload-stop-go.png (reload-stop-go-yosemite.png)
|
||||
skin/classic/browser/yosemite/reload-stop-go@2x.png (reload-stop-go-yosemite@2x.png)
|
||||
skin/classic/browser/yosemite/sync-horizontalbar.png (sync-horizontalbar-yosemite.png)
|
||||
skin/classic/browser/yosemite/sync-horizontalbar@2x.png (sync-horizontalbar-yosemite@2x.png)
|
||||
skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
|
||||
skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png)
|
||||
skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png)
|
||||
@ -522,3 +545,23 @@ browser.jar:
|
||||
% override chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon.png chrome://browser/skin/lion/tabbrowser/alltabs-box-bkgnd-icon.png os=Darwin osversion>=10.7
|
||||
% override chrome://browser/skin/tabview/tabview.png chrome://browser/skin/lion/tabview/tabview.png os=Darwin osversion>=10.7
|
||||
% override chrome://browser/skin/places/toolbar.png chrome://browser/skin/lion/places/toolbar.png os=Darwin osversion>=10.7
|
||||
% override chrome://browser/skin/Toolbar.png chrome://browser/skin/yosemite/Toolbar.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/Toolbar@2x.png chrome://browser/skin/yosemite/Toolbar@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel.png chrome://browser/skin/yosemite/menuPanel.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel@2x.png chrome://browser/skin/yosemite/menuPanel@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/loop/menuPanel.png chrome://browser/skin/yosemite/loop/menuPanel.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/loop/menuPanel@2x.png chrome://browser/skin/yosemite/loop/menuPanel@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/loop/toolbar.png chrome://browser/skin/yosemite/loop/toolbar.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/loop/toolbar@2x.png chrome://browser/skin/yosemite/loop/toolbar@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-customize.png chrome://browser/skin/yosemite/menuPanel-customize.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-customize@2x.png chrome://browser/skin/yosemite/menuPanel-customize@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-exit.png chrome://browser/skin/yosemite/menuPanel-exit.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-exit@2x.png chrome://browser/skin/yosemite/menuPanel-exit@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-help.png chrome://browser/skin/yosemite/menuPanel-help.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-help@2x.png chrome://browser/skin/yosemite/menuPanel-help@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-small.png chrome://browser/skin/yosemite/menuPanel-small.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/menuPanel-small@2x.png chrome://browser/skin/yosemite/menuPanel-small@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/reload-stop-go.png chrome://browser/skin/yosemite/reload-stop-go.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/reload-stop-go@2x.png chrome://browser/skin/yosemite/reload-stop-go@2x.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/sync-horizontalbar.png chrome://browser/skin/yosemite/sync-horizontalbar.png os=Darwin osversion>=10.10
|
||||
% override chrome://browser/skin/sync-horizontalbar@2x.png chrome://browser/skin/yosemite/sync-horizontalbar@2x.png os=Darwin osversion>=10.10
|
||||
|
BIN
browser/themes/osx/loop/menuPanel-yosemite.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
browser/themes/osx/loop/menuPanel-yosemite@2x.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
browser/themes/osx/loop/toolbar-yosemite.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
browser/themes/osx/loop/toolbar-yosemite@2x.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
browser/themes/osx/menuPanel-customize-yosemite.png
Normal file
After Width: | Height: | Size: 219 B |
BIN
browser/themes/osx/menuPanel-customize-yosemite@2x.png
Normal file
After Width: | Height: | Size: 364 B |
BIN
browser/themes/osx/menuPanel-exit-yosemite.png
Normal file
After Width: | Height: | Size: 515 B |
BIN
browser/themes/osx/menuPanel-exit-yosemite@2x.png
Normal file
After Width: | Height: | Size: 939 B |
BIN
browser/themes/osx/menuPanel-help-yosemite.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
browser/themes/osx/menuPanel-help-yosemite@2x.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
browser/themes/osx/menuPanel-small-yosemite.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
browser/themes/osx/menuPanel-small-yosemite@2x.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
browser/themes/osx/menuPanel-yosemite.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
browser/themes/osx/menuPanel-yosemite@2x.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
browser/themes/osx/reload-stop-go-yosemite.png
Normal file
After Width: | Height: | Size: 923 B |
BIN
browser/themes/osx/reload-stop-go-yosemite@2x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
browser/themes/osx/sync-horizontalbar-yosemite.png
Normal file
After Width: | Height: | Size: 311 B |
BIN
browser/themes/osx/sync-horizontalbar-yosemite@2x.png
Normal file
After Width: | Height: | Size: 609 B |
@ -0,0 +1,7 @@
|
||||
<svg width="16" xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 16 16" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 16 16">
|
||||
<g>
|
||||
<path d="m1.3,12.5v-2.4c0,0 0,2.5 6.7,2.5 6.7,0 6.7-2.5 6.7-2.5v2.4c0,0 0,2.7-6.8,2.7-6.6,0-6.6-2.7-6.6-2.7z"/>
|
||||
<path d="m14.7,3.4c0-1.4-3-2.5-6.7-2.5s-6.7,1.1-6.7,2.5c0,.2 0,.3 .1,.5-.1-.3-.1-.4-.1-.4v1.5c0,0 0,2.7 6.7,2.7 6.7,0 6.8-2.7 6.8-2.7v-1.6c0,.1 0,.2-.1,.5-0-.2-0-.3-0-.5z"/>
|
||||
<path d="m1.3,8.7v-2.4c0,0 0,2.5 6.7,2.5 6.7,0 6.7-2.5 6.7-2.5v2.4c0,0 0,2.7-6.8,2.7-6.6-0-6.6-2.7-6.6-2.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 574 B |
7
browser/themes/shared/devtools/images/tool-storage.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="16" xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 16 16" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 16 16">
|
||||
<g fill="#edf0f1">
|
||||
<path d="m1.3,12.5v-2.4c0,0 0,2.5 6.7,2.5 6.7,0 6.7-2.5 6.7-2.5v2.4c0,0 0,2.7-6.8,2.7-6.6,0-6.6-2.7-6.6-2.7z"/>
|
||||
<path d="m14.7,3.4c0-1.4-3-2.5-6.7-2.5s-6.7,1.1-6.7,2.5c0,.2 0,.3 .1,.5-.1-.3-.1-.4-.1-.4v1.5c0,0 0,2.7 6.7,2.7 6.7,0 6.8-2.7 6.8-2.7v-1.6c0,.1 0,.2-.1,.5-0-.2-0-.3-0-.5z"/>
|
||||
<path d="m1.3,8.7v-2.4c0,0 0,2.5 6.7,2.5 6.7,0 6.7-2.5 6.7-2.5v2.4c0,0 0,2.7-6.8,2.7-6.6-0-6.6-2.7-6.6-2.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 589 B |
48
browser/themes/shared/devtools/storage.css
Normal file
@ -0,0 +1,48 @@
|
||||
/* 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/. */
|
||||
|
||||
/* Storage Host Tree */
|
||||
|
||||
#storage-tree {
|
||||
min-width: 220px;
|
||||
max-width: 500px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.theme-dark #storage-tree {
|
||||
background: #343c45; /* Toolbars */
|
||||
}
|
||||
|
||||
#storage-tree .tree-widget-item[type="store"]:after {
|
||||
background-image: url(chrome://browser/skin/devtools/filetype-store.svg);
|
||||
background-size: 18px 18px;
|
||||
background-position: -1px 0;
|
||||
}
|
||||
|
||||
/* Columns with date should have a min width so that date is visible */
|
||||
#expires, #lastAccessed, #creationTime {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* Variables View Sidebar */
|
||||
|
||||
#storage-sidebar {
|
||||
max-width: 500px;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
/* Responsive sidebar */
|
||||
@media (max-width: 700px) {
|
||||
#storage-tree {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#storage-table #path {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#storage-table .table-widget-cell {
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
@ -1430,6 +1430,7 @@
|
||||
padding: 10px 20px;
|
||||
font-size: medium;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Tree Item */
|
||||
|
@ -239,6 +239,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/filetype-dir-close.svg (../shared/devtools/images/filetypes/dir-close.svg)
|
||||
skin/classic/browser/devtools/filetype-dir-open.svg (../shared/devtools/images/filetypes/dir-open.svg)
|
||||
skin/classic/browser/devtools/filetype-globe.svg (../shared/devtools/images/filetypes/globe.svg)
|
||||
skin/classic/browser/devtools/filetype-store.svg (../shared/devtools/images/filetypes/store.svg)
|
||||
skin/classic/browser/devtools/commandline-icon.png (../shared/devtools/images/commandline-icon.png)
|
||||
skin/classic/browser/devtools/commandline-icon@2x.png (../shared/devtools/images/commandline-icon@2x.png)
|
||||
skin/classic/browser/devtools/alerticon-warning.png (../shared/devtools/images/alerticon-warning.png)
|
||||
@ -283,6 +284,7 @@ browser.jar:
|
||||
* skin/classic/browser/devtools/profiler.css (devtools/profiler.css)
|
||||
* skin/classic/browser/devtools/scratchpad.css (devtools/scratchpad.css)
|
||||
* skin/classic/browser/devtools/shadereditor.css (devtools/shadereditor.css)
|
||||
skin/classic/browser/devtools/storage.css (../shared/devtools/storage.css)
|
||||
* skin/classic/browser/devtools/splitview.css (../shared/devtools/splitview.css)
|
||||
skin/classic/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css)
|
||||
* skin/classic/browser/devtools/webaudioeditor.css (devtools/webaudioeditor.css)
|
||||
@ -346,6 +348,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/tool-debugger-paused.svg (../shared/devtools/images/tool-debugger-paused.svg)
|
||||
skin/classic/browser/devtools/tool-inspector.svg (../shared/devtools/images/tool-inspector.svg)
|
||||
skin/classic/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
|
||||
skin/classic/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
|
||||
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
|
||||
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
|
||||
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
|
||||
@ -654,6 +657,7 @@ browser.jar:
|
||||
skin/classic/aero/browser/devtools/filetype-dir-close.svg (../shared/devtools/images/filetypes/dir-close.svg)
|
||||
skin/classic/aero/browser/devtools/filetype-dir-open.svg (../shared/devtools/images/filetypes/dir-open.svg)
|
||||
skin/classic/aero/browser/devtools/filetype-globe.svg (../shared/devtools/images/filetypes/globe.svg)
|
||||
skin/classic/aero/browser/devtools/filetype-store.svg (../shared/devtools/images/filetypes/store.svg)
|
||||
skin/classic/aero/browser/devtools/commandline-icon.png (../shared/devtools/images/commandline-icon.png)
|
||||
skin/classic/aero/browser/devtools/commandline-icon@2x.png (../shared/devtools/images/commandline-icon@2x.png)
|
||||
skin/classic/aero/browser/devtools/command-paintflashing.png (../shared/devtools/images/command-paintflashing.png)
|
||||
@ -700,6 +704,7 @@ browser.jar:
|
||||
* skin/classic/aero/browser/devtools/shadereditor.css (devtools/shadereditor.css)
|
||||
* skin/classic/aero/browser/devtools/splitview.css (../shared/devtools/splitview.css)
|
||||
skin/classic/aero/browser/devtools/styleeditor.css (../shared/devtools/styleeditor.css)
|
||||
skin/classic/aero/browser/devtools/storage.css (../shared/devtools/storage.css)
|
||||
* skin/classic/aero/browser/devtools/webaudioeditor.css (devtools/webaudioeditor.css)
|
||||
skin/classic/aero/browser/devtools/magnifying-glass.png (../shared/devtools/images/magnifying-glass.png)
|
||||
skin/classic/aero/browser/devtools/magnifying-glass@2x.png (../shared/devtools/images/magnifying-glass@2x.png)
|
||||
@ -761,6 +766,7 @@ browser.jar:
|
||||
skin/classic/aero/browser/devtools/tool-debugger-paused.svg (../shared/devtools/images/tool-debugger-paused.svg)
|
||||
skin/classic/aero/browser/devtools/tool-inspector.svg (../shared/devtools/images/tool-inspector.svg)
|
||||
skin/classic/aero/browser/devtools/tool-styleeditor.svg (../shared/devtools/images/tool-styleeditor.svg)
|
||||
skin/classic/aero/browser/devtools/tool-storage.svg (../shared/devtools/images/tool-storage.svg)
|
||||
skin/classic/aero/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
|
||||
skin/classic/aero/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
|
||||
skin/classic/aero/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
|
||||
|
@ -129,10 +129,14 @@
|
||||
<activity-alias android:name=".App"
|
||||
android:label="@MOZ_APP_DISPLAYNAME@"
|
||||
android:targetActivity="org.mozilla.gecko.BrowserApp">
|
||||
<intent-filter>
|
||||
<!-- android:priority ranges between -1000 and 1000. We never want
|
||||
another activity to usurp the MAIN action, so we ratchet our
|
||||
priority up. -->
|
||||
<intent-filter android:priority="999">
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
|
||||
@ -232,8 +236,7 @@
|
||||
|
||||
<activity android:name="org.mozilla.gecko.StartPane"
|
||||
android:theme="@style/GeckoStartPane"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true" />
|
||||
android:excludeFromRecents="true"/>
|
||||
|
||||
<activity android:name="org.mozilla.gecko.webapp.Dispatcher"
|
||||
android:noHistory="true" >
|
||||
|
@ -94,6 +94,14 @@ public class AppConstants {
|
||||
// add additional quotes we end up with ""x.y"", which is a syntax error.
|
||||
public static final String MOZILLA_VERSION = @MOZILLA_VERSION@;
|
||||
|
||||
public static final String MOZ_STUMBLER_API_KEY =
|
||||
#ifdef MOZ_ANDROID_MLS_STUMBLER
|
||||
"@MOZ_STUMBLER_API_KEY@";
|
||||
#else
|
||||
null;
|
||||
#endif
|
||||
public static final boolean MOZ_STUMBLER_BUILD_TIME_ENABLED = (MOZ_STUMBLER_API_KEY != null);
|
||||
|
||||
public static final String MOZ_CHILD_PROCESS_NAME = "@MOZ_CHILD_PROCESS_NAME@";
|
||||
public static final String MOZ_UPDATE_CHANNEL = "@MOZ_UPDATE_CHANNEL@";
|
||||
public static final String OMNIJAR_NAME = "@OMNIJAR_NAME@";
|
||||
|
@ -669,6 +669,10 @@ public class BrowserApp extends GeckoApp
|
||||
super.onResume();
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
|
||||
"Prompt:ShowTop");
|
||||
if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
|
||||
// Starts or pings the stumbler lib, see also usage in handleMessage(): Gecko:DelayedStartup.
|
||||
GeckoPreferences.broadcastStumblerPref(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1418,6 +1422,11 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
});
|
||||
|
||||
if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
|
||||
// Start (this acts as ping if started already) the stumbler lib; if the stumbler has queued data it will upload it.
|
||||
// Stumbler operates on its own thread, and startup impact is further minimized by delaying work (such as upload) a few seconds.
|
||||
GeckoPreferences.broadcastStumblerPref(this);
|
||||
}
|
||||
super.handleMessage(event, message);
|
||||
} else if (event.equals("Gecko:Ready")) {
|
||||
// Handle this message in GeckoApp, but also enable the Settings
|
||||
|
@ -108,7 +108,8 @@ public class GeckoEvent {
|
||||
TELEMETRY_UI_SESSION_STOP(43),
|
||||
TELEMETRY_UI_EVENT(44),
|
||||
GAMEPAD_ADDREMOVE(45),
|
||||
GAMEPAD_DATA(46);
|
||||
GAMEPAD_DATA(46),
|
||||
LONG_PRESS(47);
|
||||
|
||||
public final int value;
|
||||
|
||||
@ -420,6 +421,16 @@ public class GeckoEvent {
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GeckoEvent that contains the data from the LongPressEvent, to be
|
||||
* dispatched in CSS pixels relative to gecko's scroll position.
|
||||
*/
|
||||
public static GeckoEvent createLongPressEvent(MotionEvent m) {
|
||||
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LONG_PRESS);
|
||||
event.initMotionEvent(m, false);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void initMotionEvent(MotionEvent m, boolean keepInViewCoordinates) {
|
||||
mAction = m.getActionMasked();
|
||||
mTime = (System.currentTimeMillis() - SystemClock.elapsedRealtime()) + m.getEventTime();
|
||||
|
@ -2,15 +2,13 @@
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.sync.setup.activities;
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.BrowserLocaleManager;
|
||||
import org.mozilla.gecko.LocaleManager;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.StrictMode;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
@ -25,19 +23,14 @@ import android.support.v4.app.FragmentActivity;
|
||||
* or <code>LocaleAwareActivity</code>.
|
||||
*/
|
||||
public class LocaleAware {
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
|
||||
public static void initializeLocale(Context context) {
|
||||
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
|
||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||
StrictMode.allowThreadDiskWrites();
|
||||
try {
|
||||
localeManager.getAndApplyPersistedLocale(context);
|
||||
} else {
|
||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||
StrictMode.allowThreadDiskWrites();
|
||||
try {
|
||||
localeManager.getAndApplyPersistedLocale(context);
|
||||
} finally {
|
||||
StrictMode.setThreadPolicy(savedPolicy);
|
||||
}
|
||||
} finally {
|
||||
StrictMode.setThreadPolicy(savedPolicy);
|
||||
}
|
||||
}
|
||||
|
149
mobile/android/base/RemoteTabsExpandableListAdapter.java
Normal file
@ -0,0 +1,149 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteClient;
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteTab;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseExpandableListAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* An adapter that populates group and child views with remote client and tab
|
||||
* data maintained in a monolithic static array.
|
||||
* <p>
|
||||
* The group and child view resources are parameters to allow future
|
||||
* specialization to home fragment styles.
|
||||
*/
|
||||
public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
|
||||
protected final ArrayList<RemoteClient> clients;
|
||||
protected int groupLayoutId;
|
||||
protected int childLayoutId;
|
||||
|
||||
/**
|
||||
* Construct a new adapter.
|
||||
* <p>
|
||||
* It's fine to create with clients to be null, and then to use
|
||||
* {@link RemoteTabsExpandableListAdapter#replaceClients(List)} to
|
||||
* update this list of clients.
|
||||
*
|
||||
* @param groupLayoutId
|
||||
* @param childLayoutId
|
||||
* @param clients
|
||||
* initial list of clients; can be null.
|
||||
*/
|
||||
public RemoteTabsExpandableListAdapter(int groupLayoutId, int childLayoutId, List<RemoteClient> clients) {
|
||||
this.groupLayoutId = groupLayoutId;
|
||||
this.childLayoutId = childLayoutId;
|
||||
this.clients = new ArrayList<TabsAccessor.RemoteClient>();
|
||||
if (clients != null) {
|
||||
this.clients.addAll(clients);
|
||||
}
|
||||
}
|
||||
|
||||
public void replaceClients(List<RemoteClient> clients) {
|
||||
this.clients.clear();
|
||||
if (clients != null) {
|
||||
this.clients.addAll(clients);
|
||||
this.notifyDataSetChanged();
|
||||
} else {
|
||||
this.notifyDataSetInvalidated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return false; // Client GUIDs are stable, but tab hashes are not.
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getGroupId(int groupPosition) {
|
||||
return clients.get(groupPosition).guid.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGroupCount() {
|
||||
return clients.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGroup(int groupPosition) {
|
||||
return clients.get(groupPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildrenCount(int groupPosition) {
|
||||
return clients.get(groupPosition).tabs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
|
||||
final Context context = parent.getContext();
|
||||
final View view;
|
||||
if (convertView != null) {
|
||||
view = convertView;
|
||||
} else {
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
view = inflater.inflate(groupLayoutId, parent, false);
|
||||
}
|
||||
|
||||
final RemoteClient client = clients.get(groupPosition);
|
||||
|
||||
final TextView nameView = (TextView) view.findViewById(R.id.client);
|
||||
nameView.setText(client.name);
|
||||
|
||||
final TextView lastModifiedView = (TextView) view.findViewById(R.id.last_synced);
|
||||
final long now = System.currentTimeMillis();
|
||||
lastModifiedView.setText(TabsAccessor.getLastSyncedString(context, now, client.lastModified));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildSelectable(int groupPosition, int childPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getChild(int groupPosition, int childPosition) {
|
||||
return clients.get(groupPosition).tabs.get(childPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getChildId(int groupPosition, int childPosition) {
|
||||
return clients.get(groupPosition).tabs.get(childPosition).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
|
||||
final Context context = parent.getContext();
|
||||
final View view;
|
||||
if (convertView != null) {
|
||||
view = convertView;
|
||||
} else {
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
view = inflater.inflate(childLayoutId, parent, false);
|
||||
}
|
||||
|
||||
final RemoteClient client = clients.get(groupPosition);
|
||||
final RemoteTab tab = client.tabs.get(childPosition);
|
||||
|
||||
final TextView titleView = (TextView) view.findViewById(R.id.title);
|
||||
titleView.setText(TextUtils.isEmpty(tab.title) ? tab.url : tab.title);
|
||||
|
||||
final TextView urlView = (TextView) view.findViewById(R.id.url);
|
||||
urlView.setText(tab.url);
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
@ -4,127 +4,241 @@
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UIAsyncTask;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class TabsAccessor {
|
||||
private static final String LOGTAG = "GeckoTabsAccessor";
|
||||
|
||||
private static final String[] CLIENTS_AVAILABILITY_PROJECTION = new String[] {
|
||||
BrowserContract.Clients.GUID
|
||||
};
|
||||
|
||||
private static final String[] TABS_PROJECTION_COLUMNS = new String[] {
|
||||
public static final String[] TABS_PROJECTION_COLUMNS = new String[] {
|
||||
BrowserContract.Tabs.TITLE,
|
||||
BrowserContract.Tabs.URL,
|
||||
BrowserContract.Clients.GUID,
|
||||
BrowserContract.Clients.NAME,
|
||||
BrowserContract.Clients.LAST_MODIFIED,
|
||||
BrowserContract.Clients.DEVICE_TYPE,
|
||||
};
|
||||
|
||||
// Projection column numbers
|
||||
public static enum TABS_COLUMN {
|
||||
TITLE,
|
||||
URL,
|
||||
GUID,
|
||||
NAME,
|
||||
LAST_MODIFIED,
|
||||
};
|
||||
private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
|
||||
private static final String REMOTE_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL";
|
||||
|
||||
private static final String CLIENTS_SELECTION = BrowserContract.Clients.GUID + " IS NOT NULL";
|
||||
private static final String TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL";
|
||||
private static final String REMOTE_TABS_SORT_ORDER =
|
||||
// Most recently synced clients first.
|
||||
BrowserContract.Clients.LAST_MODIFIED + " DESC, " +
|
||||
// If two clients somehow had the same last modified time, this will
|
||||
// group them (arbitrarily).
|
||||
BrowserContract.Clients.GUID + " DESC, " +
|
||||
// Within a single client, most recently used tabs first.
|
||||
BrowserContract.Tabs.LAST_USED + " DESC";
|
||||
|
||||
private static final String LOCAL_CLIENT_SELECTION = BrowserContract.Clients.GUID + " IS NULL";
|
||||
private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
|
||||
|
||||
private static final Pattern FILTERED_URL_PATTERN = Pattern.compile("^(about|chrome|wyciwyg|file):");
|
||||
|
||||
/**
|
||||
* A thin representation of a remote client.
|
||||
* <p>
|
||||
* We use the hash of the client's GUID as the ID in
|
||||
* {@link RemoteTabsExpandableListAdapter#getGroupId(int)}.
|
||||
*/
|
||||
public static class RemoteClient {
|
||||
public final String guid;
|
||||
public final String name;
|
||||
public final long lastModified;
|
||||
public final String deviceType;
|
||||
public final ArrayList<RemoteTab> tabs;
|
||||
|
||||
public RemoteClient(String guid, String name, long lastModified, String deviceType) {
|
||||
this.guid = guid;
|
||||
this.name = name;
|
||||
this.lastModified = lastModified;
|
||||
this.deviceType = deviceType;
|
||||
this.tabs = new ArrayList<RemoteTab>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A thin representation of a remote tab.
|
||||
* <p>
|
||||
* We use the hash of the tab as the ID in
|
||||
* {@link RemoteTabsExpandableListAdapter#getClientId(int)}, and therefore we
|
||||
* must implement equality as well. These are generated functions.
|
||||
*/
|
||||
public static class RemoteTab {
|
||||
public String title;
|
||||
public String url;
|
||||
public String guid;
|
||||
public String name;
|
||||
/**
|
||||
* This is the last time the remote client uploaded a tabs record; that
|
||||
* is, it is not per tab, but per remote client.
|
||||
*/
|
||||
public long lastModified;
|
||||
public final String title;
|
||||
public final String url;
|
||||
|
||||
public RemoteTab(String title, String url) {
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((title == null) ? 0 : title.hashCode());
|
||||
result = prime * result + ((url == null) ? 0 : url.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RemoteTab other = (RemoteTab) obj;
|
||||
if (title == null) {
|
||||
if (other.title != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!title.equals(other.title)) {
|
||||
return false;
|
||||
}
|
||||
if (url == null) {
|
||||
if (other.url != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!url.equals(other.url)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract client and tab records from a cursor.
|
||||
* <p>
|
||||
* The position of the cursor is moved to before the first record before
|
||||
* reading. The cursor is advanced until there are no more records to be
|
||||
* read. The position of the cursor is restored before returning.
|
||||
*
|
||||
* @param cursor
|
||||
* to extract records from. The records should already be grouped
|
||||
* by client GUID.
|
||||
* @return list of clients, each containing list of tabs.
|
||||
*/
|
||||
public static List<RemoteClient> getClientsFromCursor(final Cursor cursor) {
|
||||
final ArrayList<RemoteClient> clients = new ArrayList<TabsAccessor.RemoteClient>();
|
||||
|
||||
final int originalPosition = cursor.getPosition();
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return clients;
|
||||
}
|
||||
|
||||
final int tabTitleIndex = cursor.getColumnIndex(BrowserContract.Tabs.TITLE);
|
||||
final int tabUrlIndex = cursor.getColumnIndex(BrowserContract.Tabs.URL);
|
||||
final int clientGuidIndex = cursor.getColumnIndex(BrowserContract.Clients.GUID);
|
||||
final int clientNameIndex = cursor.getColumnIndex(BrowserContract.Clients.NAME);
|
||||
final int clientLastModifiedIndex = cursor.getColumnIndex(BrowserContract.Clients.LAST_MODIFIED);
|
||||
final int clientDeviceTypeIndex = cursor.getColumnIndex(BrowserContract.Clients.DEVICE_TYPE);
|
||||
|
||||
// A walking partition, chunking by client GUID. We assume the
|
||||
// cursor records are already grouped by client GUID; see the query
|
||||
// sort order.
|
||||
RemoteClient lastClient = null;
|
||||
while (!cursor.isAfterLast()) {
|
||||
final String clientGuid = cursor.getString(clientGuidIndex);
|
||||
if (lastClient == null || !TextUtils.equals(lastClient.guid, clientGuid)) {
|
||||
final String clientName = cursor.getString(clientNameIndex);
|
||||
final long lastModified = cursor.getLong(clientLastModifiedIndex);
|
||||
final String deviceType = cursor.getString(clientDeviceTypeIndex);
|
||||
lastClient = new RemoteClient(clientGuid, clientName, lastModified, deviceType);
|
||||
clients.add(lastClient);
|
||||
}
|
||||
|
||||
final String tabTitle = cursor.getString(tabTitleIndex);
|
||||
final String tabUrl = cursor.getString(tabUrlIndex);
|
||||
lastClient.tabs.add(new RemoteTab(tabTitle, tabUrl));
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cursor.moveToPosition(originalPosition);
|
||||
}
|
||||
|
||||
return clients;
|
||||
}
|
||||
|
||||
public static Cursor getRemoteTabsCursor(Context context) {
|
||||
return getRemoteTabsCursor(context, -1);
|
||||
}
|
||||
|
||||
public static Cursor getRemoteTabsCursor(Context context, int limit) {
|
||||
Uri uri = BrowserContract.Tabs.CONTENT_URI;
|
||||
|
||||
if (limit > 0) {
|
||||
uri = uri.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
|
||||
.build();
|
||||
}
|
||||
|
||||
final Cursor cursor = context.getContentResolver().query(uri,
|
||||
TABS_PROJECTION_COLUMNS,
|
||||
REMOTE_TABS_SELECTION,
|
||||
null,
|
||||
REMOTE_TABS_SORT_ORDER);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public interface OnQueryTabsCompleteListener {
|
||||
public void onQueryTabsComplete(List<RemoteTab> tabs);
|
||||
public void onQueryTabsComplete(List<RemoteClient> clients);
|
||||
}
|
||||
|
||||
// This method returns all tabs from all remote clients,
|
||||
// ordered by most recent client first, most recent tab first
|
||||
// This method returns all tabs from all remote clients,
|
||||
// ordered by most recent client first, most recent tab first
|
||||
public static void getTabs(final Context context, final OnQueryTabsCompleteListener listener) {
|
||||
getTabs(context, 0, listener);
|
||||
}
|
||||
|
||||
// This method returns limited number of tabs from all remote clients,
|
||||
// ordered by most recent client first, most recent tab first
|
||||
// This method returns limited number of tabs from all remote clients,
|
||||
// ordered by most recent client first, most recent tab first
|
||||
public static void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) {
|
||||
// If there is no listener, no point in doing work.
|
||||
if (listener == null)
|
||||
return;
|
||||
|
||||
(new UIAsyncTask.WithoutParams<List<RemoteTab>>(ThreadUtils.getBackgroundHandler()) {
|
||||
(new UIAsyncTask.WithoutParams<List<RemoteClient>>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
protected List<RemoteTab> doInBackground() {
|
||||
Uri uri = BrowserContract.Tabs.CONTENT_URI;
|
||||
|
||||
if (limit > 0) {
|
||||
uri = uri.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
|
||||
.build();
|
||||
}
|
||||
|
||||
Cursor cursor = context.getContentResolver().query(uri,
|
||||
TABS_PROJECTION_COLUMNS,
|
||||
TABS_SELECTION,
|
||||
null,
|
||||
null);
|
||||
|
||||
protected List<RemoteClient> doInBackground() {
|
||||
final Cursor cursor = getRemoteTabsCursor(context, limit);
|
||||
if (cursor == null)
|
||||
return null;
|
||||
|
||||
RemoteTab tab;
|
||||
final ArrayList<RemoteTab> tabs = new ArrayList<RemoteTab> ();
|
||||
try {
|
||||
while (cursor.moveToNext()) {
|
||||
tab = new RemoteTab();
|
||||
tab.title = cursor.getString(TABS_COLUMN.TITLE.ordinal());
|
||||
tab.url = cursor.getString(TABS_COLUMN.URL.ordinal());
|
||||
tab.guid = cursor.getString(TABS_COLUMN.GUID.ordinal());
|
||||
tab.name = cursor.getString(TABS_COLUMN.NAME.ordinal());
|
||||
tab.lastModified = cursor.getLong(TABS_COLUMN.LAST_MODIFIED.ordinal());
|
||||
|
||||
tabs.add(tab);
|
||||
}
|
||||
try {
|
||||
return Collections.unmodifiableList(getClientsFromCursor(cursor));
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(tabs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<RemoteTab> tabs) {
|
||||
listener.onQueryTabsComplete(tabs);
|
||||
protected void onPostExecute(List<RemoteClient> clients) {
|
||||
listener.onQueryTabsComplete(clients);
|
||||
}
|
||||
}).execute();
|
||||
}
|
||||
@ -210,4 +324,16 @@ public final class TabsAccessor {
|
||||
private static boolean isFilteredURL(String url) {
|
||||
return FILTERED_URL_PATTERN.matcher(url).lookingAt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a relative "Last synced" time span for the given tab record.
|
||||
*
|
||||
* @param now local time.
|
||||
* @param time to format string for.
|
||||
* @return string describing time span
|
||||
*/
|
||||
public static String getLastSyncedString(Context context, long now, long time) {
|
||||
final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
|
||||
return context.getResources().getString(R.string.remote_tabs_last_synced, relativeTimeSpanString);
|
||||
}
|
||||
}
|
||||
|
@ -5,67 +5,115 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/androidextra/Base64.java',
|
||||
'ch/boye/httpclientandroidlib/androidextra/HttpClientAndroidLog.java',
|
||||
'ch/boye/httpclientandroidlib/annotation/GuardedBy.java',
|
||||
'ch/boye/httpclientandroidlib/annotation/Immutable.java',
|
||||
'ch/boye/httpclientandroidlib/annotation/NotThreadSafe.java',
|
||||
'ch/boye/httpclientandroidlib/annotation/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/annotation/ThreadSafe.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AUTH.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthenticationException.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthOption.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthProtocolState.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthScheme.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthSchemeFactory.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthSchemeProvider.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthSchemeRegistry.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthScope.java',
|
||||
'ch/boye/httpclientandroidlib/auth/AuthState.java',
|
||||
'ch/boye/httpclientandroidlib/auth/BasicUserPrincipal.java',
|
||||
'ch/boye/httpclientandroidlib/auth/ChallengeState.java',
|
||||
'ch/boye/httpclientandroidlib/auth/ContextAwareAuthScheme.java',
|
||||
'ch/boye/httpclientandroidlib/auth/Credentials.java',
|
||||
'ch/boye/httpclientandroidlib/auth/InvalidCredentialsException.java',
|
||||
'ch/boye/httpclientandroidlib/auth/MalformedChallengeException.java',
|
||||
'ch/boye/httpclientandroidlib/auth/NTCredentials.java',
|
||||
'ch/boye/httpclientandroidlib/auth/NTUserPrincipal.java',
|
||||
'ch/boye/httpclientandroidlib/auth/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/auth/params/AuthParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/auth/params/AuthParams.java',
|
||||
'ch/boye/httpclientandroidlib/auth/params/AuthPNames.java',
|
||||
'ch/boye/httpclientandroidlib/auth/params/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/auth/UsernamePasswordCredentials.java',
|
||||
'ch/boye/httpclientandroidlib/client/AuthCache.java',
|
||||
'ch/boye/httpclientandroidlib/client/AuthenticationHandler.java',
|
||||
'ch/boye/httpclientandroidlib/client/AuthenticationStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/client/BackoffManager.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/CacheResponseStatus.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HeaderConstants.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheContext.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheEntry.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheEntrySerializationException.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheEntrySerializer.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheInvalidator.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheStorage.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheUpdateCallback.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/HttpCacheUpdateException.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/InputLimit.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/Resource.java',
|
||||
'ch/boye/httpclientandroidlib/client/cache/ResourceFactory.java',
|
||||
'ch/boye/httpclientandroidlib/client/CircularRedirectException.java',
|
||||
'ch/boye/httpclientandroidlib/client/ClientProtocolException.java',
|
||||
'ch/boye/httpclientandroidlib/client/config/AuthSchemes.java',
|
||||
'ch/boye/httpclientandroidlib/client/config/CookieSpecs.java',
|
||||
'ch/boye/httpclientandroidlib/client/config/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/client/config/RequestConfig.java',
|
||||
'ch/boye/httpclientandroidlib/client/ConnectionBackoffStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/client/CookieStore.java',
|
||||
'ch/boye/httpclientandroidlib/client/CredentialsProvider.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/DecompressingEntity.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/DeflateDecompressingEntity.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/DeflateInputStream.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/EntityBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/GzipCompressingEntity.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/GzipDecompressingEntity.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/LazyDecompressingInputStream.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/client/entity/UrlEncodedFormEntity.java',
|
||||
'ch/boye/httpclientandroidlib/client/HttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/client/HttpRequestRetryHandler.java',
|
||||
'ch/boye/httpclientandroidlib/client/HttpResponseException.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/AbortableHttpRequest.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/AbstractExecutionAwareRequest.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/CloseableHttpResponse.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/Configurable.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpDelete.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpEntityEnclosingRequestBase.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpExecutionAware.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpGet.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpHead.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpOptions.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpPatch.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpPost.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpPut.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpRequestBase.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpRequestWrapper.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpTrace.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/HttpUriRequest.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/client/methods/RequestBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/client/NonRepeatableRequestException.java',
|
||||
'ch/boye/httpclientandroidlib/client/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/AllClientPNames.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/AuthPolicy.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/ClientParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/ClientPNames.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/CookiePolicy.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/HttpClientParamConfig.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/HttpClientParams.java',
|
||||
'ch/boye/httpclientandroidlib/client/params/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/ClientContext.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/ClientContextConfigurer.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/HttpClientContext.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestAcceptEncoding.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestAddCookies.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestAuthCache.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestAuthenticationBase.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestClientConnControl.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestDefaultHeaders.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestExpectContinue.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestProxyAuthentication.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/RequestTargetAuthentication.java',
|
||||
'ch/boye/httpclientandroidlib/client/protocol/ResponseAuthCache.java',
|
||||
@ -76,14 +124,30 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/client/RedirectStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/client/RequestDirector.java',
|
||||
'ch/boye/httpclientandroidlib/client/ResponseHandler.java',
|
||||
'ch/boye/httpclientandroidlib/client/ServiceUnavailableRetryStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/client/UserTokenHandler.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/CloneUtils.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/DateUtils.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/HttpClientUtils.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/Idn.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/JdkIdn.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/Punycode.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/Rfc3492Idn.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/URIBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/URIUtils.java',
|
||||
'ch/boye/httpclientandroidlib/client/utils/URLEncodedUtils.java',
|
||||
'ch/boye/httpclientandroidlib/concurrent/BasicFuture.java',
|
||||
'ch/boye/httpclientandroidlib/concurrent/Cancellable.java',
|
||||
'ch/boye/httpclientandroidlib/concurrent/FutureCallback.java',
|
||||
'ch/boye/httpclientandroidlib/concurrent/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/config/ConnectionConfig.java',
|
||||
'ch/boye/httpclientandroidlib/config/Lookup.java',
|
||||
'ch/boye/httpclientandroidlib/config/MessageConstraints.java',
|
||||
'ch/boye/httpclientandroidlib/config/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/config/Registry.java',
|
||||
'ch/boye/httpclientandroidlib/config/RegistryBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/config/SocketConfig.java',
|
||||
'ch/boye/httpclientandroidlib/conn/BasicEofSensorWatcher.java',
|
||||
'ch/boye/httpclientandroidlib/conn/BasicManagedEntity.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ClientConnectionManager.java',
|
||||
@ -93,14 +157,21 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/conn/ConnectionKeepAliveStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ConnectionPoolTimeoutException.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ConnectionReleaseTrigger.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ConnectionRequest.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ConnectTimeoutException.java',
|
||||
'ch/boye/httpclientandroidlib/conn/DnsResolver.java',
|
||||
'ch/boye/httpclientandroidlib/conn/EofSensorInputStream.java',
|
||||
'ch/boye/httpclientandroidlib/conn/EofSensorWatcher.java',
|
||||
'ch/boye/httpclientandroidlib/conn/HttpClientConnectionManager.java',
|
||||
'ch/boye/httpclientandroidlib/conn/HttpConnectionFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/HttpHostConnectException.java',
|
||||
'ch/boye/httpclientandroidlib/conn/HttpInetSocketAddress.java',
|
||||
'ch/boye/httpclientandroidlib/conn/HttpRoutedConnection.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ManagedClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ManagedHttpClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/conn/MultihomePlainSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/OperatedClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/conn/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/conn/params/ConnConnectionParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/conn/params/ConnConnectionPNames.java',
|
||||
'ch/boye/httpclientandroidlib/conn/params/ConnManagerParamBean.java',
|
||||
@ -111,36 +182,58 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/conn/params/ConnRouteParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/conn/params/ConnRouteParams.java',
|
||||
'ch/boye/httpclientandroidlib/conn/params/ConnRoutePNames.java',
|
||||
'ch/boye/httpclientandroidlib/conn/params/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/conn/routing/BasicRouteDirector.java',
|
||||
'ch/boye/httpclientandroidlib/conn/routing/HttpRoute.java',
|
||||
'ch/boye/httpclientandroidlib/conn/routing/HttpRouteDirector.java',
|
||||
'ch/boye/httpclientandroidlib/conn/routing/HttpRoutePlanner.java',
|
||||
'ch/boye/httpclientandroidlib/conn/routing/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/conn/routing/RouteInfo.java',
|
||||
'ch/boye/httpclientandroidlib/conn/routing/RouteTracker.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/HostNameResolver.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/LayeredSchemeSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/LayeredSchemeSocketFactoryAdaptor.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/LayeredSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/LayeredSocketFactoryAdaptor.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/PlainSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/Scheme.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SchemeLayeredSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SchemeLayeredSocketFactoryAdaptor.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SchemeLayeredSocketFactoryAdaptor2.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SchemeRegistry.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SchemeSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SchemeSocketFactoryAdaptor.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/scheme/SocketFactoryAdaptor.java',
|
||||
'ch/boye/httpclientandroidlib/conn/SchemePortResolver.java',
|
||||
'ch/boye/httpclientandroidlib/conn/socket/ConnectionSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/socket/LayeredConnectionSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/socket/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/conn/socket/PlainConnectionSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/AbstractVerifier.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/AllowAllHostnameVerifier.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/BrowserCompatHostnameVerifier.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/DistinguishedNameParser.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/PrivateKeyDetails.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/PrivateKeyStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/SSLConnectionSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/SSLContextBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/SSLContexts.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/SSLInitializationException.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/SSLSocketFactory.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/StrictHostnameVerifier.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/TrustManagerDecorator.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/TokenParser.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/TrustSelfSignedStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/TrustStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/conn/ssl/X509HostnameVerifier.java',
|
||||
'ch/boye/httpclientandroidlib/conn/UnsupportedSchemeException.java',
|
||||
'ch/boye/httpclientandroidlib/conn/util/InetAddressUtils.java',
|
||||
'ch/boye/httpclientandroidlib/conn/util/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/ConnectionClosedException.java',
|
||||
'ch/boye/httpclientandroidlib/ConnectionReuseStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/Consts.java',
|
||||
'ch/boye/httpclientandroidlib/ContentTooLongException.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/ClientCookie.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/Cookie.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/CookieAttributeHandler.java',
|
||||
@ -150,10 +243,13 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/cookie/CookieRestrictionViolationException.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/CookieSpec.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/CookieSpecFactory.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/CookieSpecProvider.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/CookieSpecRegistry.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/MalformedCookieException.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/params/CookieSpecParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/params/CookieSpecPNames.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/params/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/SetCookie.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/SetCookie2.java',
|
||||
'ch/boye/httpclientandroidlib/cookie/SM.java',
|
||||
@ -163,10 +259,32 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/entity/ByteArrayEntity.java',
|
||||
'ch/boye/httpclientandroidlib/entity/ContentLengthStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/entity/ContentProducer.java',
|
||||
'ch/boye/httpclientandroidlib/entity/ContentType.java',
|
||||
'ch/boye/httpclientandroidlib/entity/EntityTemplate.java',
|
||||
'ch/boye/httpclientandroidlib/entity/FileEntity.java',
|
||||
'ch/boye/httpclientandroidlib/entity/HttpEntityWrapper.java',
|
||||
'ch/boye/httpclientandroidlib/entity/InputStreamEntity.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/AbstractMultipartForm.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/AbstractContentBody.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/ByteArrayBody.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/ContentBody.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/ContentDescriptor.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/FileBody.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/InputStreamBody.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/content/StringBody.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/FormBodyPart.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/Header.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/HttpBrowserCompatibleMultipart.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/HttpMultipartMode.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/HttpRFC6532Multipart.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/HttpStrictMultipart.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/MIME.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/MinimalField.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/MultipartEntityBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/MultipartFormEntity.java',
|
||||
'ch/boye/httpclientandroidlib/entity/mime/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/entity/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/entity/SerializableEntity.java',
|
||||
'ch/boye/httpclientandroidlib/entity/StringEntity.java',
|
||||
'ch/boye/httpclientandroidlib/FormattedHeader.java',
|
||||
@ -176,6 +294,7 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/HeaderIterator.java',
|
||||
'ch/boye/httpclientandroidlib/HttpClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/HttpConnection.java',
|
||||
'ch/boye/httpclientandroidlib/HttpConnectionFactory.java',
|
||||
'ch/boye/httpclientandroidlib/HttpConnectionMetrics.java',
|
||||
'ch/boye/httpclientandroidlib/HttpEntity.java',
|
||||
'ch/boye/httpclientandroidlib/HttpEntityEnclosingRequest.java',
|
||||
@ -200,22 +319,80 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/impl/auth/BasicSchemeFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/DigestScheme.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/DigestSchemeFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/HttpAuthenticator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/HttpEntityDigester.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/NTLMEngine.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/NTLMEngineException.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/NTLMEngineImpl.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/NTLMScheme.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/NTLMSchemeFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/RFC2617Scheme.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/SpnegoTokenGenerator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/auth/UnsupportedDigestAlgorithmException.java',
|
||||
'ch/boye/httpclientandroidlib/impl/BHttpConnectionBase.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/Variant.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/Clock.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java',
|
||||
@ -224,41 +401,86 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/HttpClients.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/ProxyClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/SystemClock.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/AbstractClientConnAdapter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/AbstractPooledConnAdapter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/AbstractPoolEntry.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/BasicClientConnectionManager.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/BasicHttpClientConnectionManager.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/ConnectionShutdownException.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/CPool.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/CPoolEntry.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/CPoolProxy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnectionOperator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParserFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultHttpRoutePlanner.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultManagedHttpClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultProxyRoutePlanner.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultResponseParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/HttpInetSocketAddress.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultRoutePlanner.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/DefaultSchemePortResolver.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/HttpClientConnectionOperator.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/HttpConnPool.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/HttpPoolEntry.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/IdleConnectionHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/InMemoryDnsResolver.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/LoggingInputStream.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/LoggingManagedHttpClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/LoggingOutputStream.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/LoggingSessionInputBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/LoggingSessionOutputBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/ManagedClientConnectionImpl.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/ManagedHttpClientConnectionFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/PoolingClientConnectionManager.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/PoolingHttpClientConnectionManager.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/ProxySelectorRoutePlanner.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/SchemeRegistryFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/SingleClientConnManager.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/SystemDefaultDnsResolver.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/SystemDefaultRoutePlanner.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/AbstractConnPool.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPooledConnAdapter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntry.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntryRef.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/ConnPoolByRoute.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/PoolEntryRequest.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/RefQueueHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/RefQueueWorker.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/RouteSpecificPool.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/ThreadSafeClientConnManager.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThread.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThreadAborter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/conn/Wire.java',
|
||||
'ch/boye/httpclientandroidlib/impl/ConnSupport.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieAttributeHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieSpec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie.java',
|
||||
@ -273,6 +495,7 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpecFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpecFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatVersionAttributeHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/CookieSpecBase.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/DateParseException.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/DateUtils.java',
|
||||
@ -282,6 +505,7 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftHeaderParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpecFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixFilter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixListParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/RFC2109DomainHandler.java',
|
||||
@ -295,16 +519,37 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/RFC2965Spec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/RFC2965SpecFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/cookie/RFC2965VersionAttributeHandler.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnectionFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnectionFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultConnectionReuseStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultHttpClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultHttpRequestFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultHttpResponseFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/DefaultHttpServerConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/EnglishReasonPhraseCatalog.java',
|
||||
'ch/boye/httpclientandroidlib/impl/entity/DisallowIdentityContentLengthStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/entity/EntityDeserializer.java',
|
||||
'ch/boye/httpclientandroidlib/impl/entity/EntitySerializer.java',
|
||||
'ch/boye/httpclientandroidlib/impl/entity/LaxContentLengthStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/entity/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/entity/StrictContentLengthStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/BackoffStrategyExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/ClientExecChain.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/ConnectionHolder.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/HttpResponseProxy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/MainClientExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/MinimalClientExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/ProtocolExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/RedirectExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/RequestAbortedException.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/RequestEntityProxy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/ResponseEntityProxy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/RetryExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/ServiceUnavailableRetryExec.java',
|
||||
'ch/boye/httpclientandroidlib/impl/execchain/TunnelRefusedException.java',
|
||||
'ch/boye/httpclientandroidlib/impl/HttpConnectionMetricsImpl.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/AbstractMessageParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/AbstractMessageWriter.java',
|
||||
@ -314,6 +559,14 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/impl/io/ChunkedOutputStream.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/ContentLengthInputStream.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/ContentLengthOutputStream.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParserFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriterFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParserFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriterFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/HttpRequestParser.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/HttpRequestWriter.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/HttpResponseParser.java',
|
||||
@ -321,16 +574,27 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/impl/io/HttpTransportMetricsImpl.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/IdentityInputStream.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/IdentityOutputStream.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/SessionInputBufferImpl.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/SessionOutputBufferImpl.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/SocketInputBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/impl/io/SocketOutputBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/impl/NoConnectionReuseStrategy.java',
|
||||
'ch/boye/httpclientandroidlib/impl/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/pool/BasicConnFactory.java',
|
||||
'ch/boye/httpclientandroidlib/impl/pool/BasicConnPool.java',
|
||||
'ch/boye/httpclientandroidlib/impl/pool/BasicPoolEntry.java',
|
||||
'ch/boye/httpclientandroidlib/impl/pool/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/impl/SocketHttpClientConnection.java',
|
||||
'ch/boye/httpclientandroidlib/impl/SocketHttpServerConnection.java',
|
||||
'ch/boye/httpclientandroidlib/io/BufferInfo.java',
|
||||
'ch/boye/httpclientandroidlib/io/EofSensor.java',
|
||||
'ch/boye/httpclientandroidlib/io/HttpMessageParser.java',
|
||||
'ch/boye/httpclientandroidlib/io/HttpMessageParserFactory.java',
|
||||
'ch/boye/httpclientandroidlib/io/HttpMessageWriter.java',
|
||||
'ch/boye/httpclientandroidlib/io/HttpMessageWriterFactory.java',
|
||||
'ch/boye/httpclientandroidlib/io/HttpTransportMetrics.java',
|
||||
'ch/boye/httpclientandroidlib/io/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/io/SessionInputBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/io/SessionOutputBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/MalformedChunkCodingException.java',
|
||||
@ -357,10 +621,13 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/message/HeaderValueParser.java',
|
||||
'ch/boye/httpclientandroidlib/message/LineFormatter.java',
|
||||
'ch/boye/httpclientandroidlib/message/LineParser.java',
|
||||
'ch/boye/httpclientandroidlib/message/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/message/ParserCursor.java',
|
||||
'ch/boye/httpclientandroidlib/MessageConstraintException.java',
|
||||
'ch/boye/httpclientandroidlib/MethodNotSupportedException.java',
|
||||
'ch/boye/httpclientandroidlib/NameValuePair.java',
|
||||
'ch/boye/httpclientandroidlib/NoHttpResponseException.java',
|
||||
'ch/boye/httpclientandroidlib/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/params/AbstractHttpParams.java',
|
||||
'ch/boye/httpclientandroidlib/params/BasicHttpParams.java',
|
||||
'ch/boye/httpclientandroidlib/params/CoreConnectionPNames.java',
|
||||
@ -369,28 +636,46 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/params/HttpAbstractParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/params/HttpConnectionParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/params/HttpConnectionParams.java',
|
||||
'ch/boye/httpclientandroidlib/params/HttpParamConfig.java',
|
||||
'ch/boye/httpclientandroidlib/params/HttpParams.java',
|
||||
'ch/boye/httpclientandroidlib/params/HttpParamsNames.java',
|
||||
'ch/boye/httpclientandroidlib/params/HttpProtocolParamBean.java',
|
||||
'ch/boye/httpclientandroidlib/params/HttpProtocolParams.java',
|
||||
'ch/boye/httpclientandroidlib/params/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/params/SyncBasicHttpParams.java',
|
||||
'ch/boye/httpclientandroidlib/ParseException.java',
|
||||
'ch/boye/httpclientandroidlib/pool/AbstractConnPool.java',
|
||||
'ch/boye/httpclientandroidlib/pool/ConnFactory.java',
|
||||
'ch/boye/httpclientandroidlib/pool/ConnPool.java',
|
||||
'ch/boye/httpclientandroidlib/pool/ConnPoolControl.java',
|
||||
'ch/boye/httpclientandroidlib/pool/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/pool/PoolEntry.java',
|
||||
'ch/boye/httpclientandroidlib/pool/PoolEntryCallback.java',
|
||||
'ch/boye/httpclientandroidlib/pool/PoolEntryFuture.java',
|
||||
'ch/boye/httpclientandroidlib/pool/PoolStats.java',
|
||||
'ch/boye/httpclientandroidlib/pool/RouteSpecificPool.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/BasicHttpContext.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/BasicHttpProcessor.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/ChainBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/DefaultedHttpContext.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/ExecutionContext.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HTTP.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpContext.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpCoreContext.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpDateGenerator.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpExpectationVerifier.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpProcessor.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpProcessorBuilder.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpRequestExecutor.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpRequestHandler.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpRequestHandlerMapper.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpRequestHandlerRegistry.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpRequestHandlerResolver.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpRequestInterceptorList.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpResponseInterceptorList.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/HttpService.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/ImmutableHttpProcessor.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/RequestConnControl.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/RequestContent.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/RequestDate.java',
|
||||
@ -402,6 +687,7 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/protocol/ResponseDate.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/ResponseServer.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/SyncBasicHttpContext.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/UriHttpRequestHandlerMapper.java',
|
||||
'ch/boye/httpclientandroidlib/protocol/UriPatternMatcher.java',
|
||||
'ch/boye/httpclientandroidlib/ProtocolException.java',
|
||||
'ch/boye/httpclientandroidlib/ProtocolVersion.java',
|
||||
@ -411,12 +697,18 @@ sync_thirdparty_java_files = [
|
||||
'ch/boye/httpclientandroidlib/TokenIterator.java',
|
||||
'ch/boye/httpclientandroidlib/TruncatedChunkException.java',
|
||||
'ch/boye/httpclientandroidlib/UnsupportedHttpVersionException.java',
|
||||
'ch/boye/httpclientandroidlib/util/Args.java',
|
||||
'ch/boye/httpclientandroidlib/util/Asserts.java',
|
||||
'ch/boye/httpclientandroidlib/util/ByteArrayBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/util/CharArrayBuffer.java',
|
||||
'ch/boye/httpclientandroidlib/util/CharsetUtils.java',
|
||||
'ch/boye/httpclientandroidlib/util/EncodingUtils.java',
|
||||
'ch/boye/httpclientandroidlib/util/EntityUtils.java',
|
||||
'ch/boye/httpclientandroidlib/util/ExceptionUtils.java',
|
||||
'ch/boye/httpclientandroidlib/util/LangUtils.java',
|
||||
'ch/boye/httpclientandroidlib/util/NetUtils.java',
|
||||
'ch/boye/httpclientandroidlib/util/package-info.java',
|
||||
'ch/boye/httpclientandroidlib/util/TextUtils.java',
|
||||
'ch/boye/httpclientandroidlib/util/VersionInfo.java',
|
||||
'org/json/simple/ItemList.java',
|
||||
'org/json/simple/JSONArray.java',
|
||||
@ -771,7 +1063,6 @@ sync_java_files = [
|
||||
'sync/setup/activities/AccountActivity.java',
|
||||
'sync/setup/activities/ActivityUtils.java',
|
||||
'sync/setup/activities/ClientRecordArrayAdapter.java',
|
||||
'sync/setup/activities/LocaleAware.java',
|
||||
'sync/setup/activities/RedirectToSetupActivity.java',
|
||||
'sync/setup/activities/SendTabActivity.java',
|
||||
'sync/setup/activities/SendTabData.java',
|
||||
|
@ -24,7 +24,6 @@ import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Build;
|
||||
import android.util.SparseArray;
|
||||
|
||||
/**
|
||||
@ -234,22 +233,14 @@ public class HealthReportDatabaseStorage implements HealthReportStorage {
|
||||
return parent.getAbsolutePath() + File.separator + name;
|
||||
}
|
||||
|
||||
public static boolean CAN_USE_ABSOLUTE_DB_PATH = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO);
|
||||
public HealthReportSQLiteOpenHelper(Context context, File profileDirectory, String name) {
|
||||
this(context, profileDirectory, name, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
// For testing DBs of different versions.
|
||||
public HealthReportSQLiteOpenHelper(Context context, File profileDirectory, String name, int version) {
|
||||
super(
|
||||
(CAN_USE_ABSOLUTE_DB_PATH ? context : new AbsolutePathContext(context, profileDirectory)),
|
||||
(CAN_USE_ABSOLUTE_DB_PATH ? getAbsolutePath(profileDirectory, name) : name),
|
||||
null,
|
||||
version);
|
||||
|
||||
if (CAN_USE_ABSOLUTE_DB_PATH) {
|
||||
Logger.pii(LOG_TAG, "Opening: " + getAbsolutePath(profileDirectory, name));
|
||||
}
|
||||
super(context, getAbsolutePath(profileDirectory, name), null, version);
|
||||
Logger.pii(LOG_TAG, "Opening: " + getAbsolutePath(profileDirectory, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,7 +9,7 @@ import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareActivity;
|
||||
import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
|
@ -12,7 +12,7 @@ import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
|
||||
import org.mozilla.gecko.sync.setup.activities.LocaleAware;
|
||||
import org.mozilla.gecko.LocaleAware;
|
||||
|
||||
import android.accounts.AccountAuthenticatorActivity;
|
||||
import android.content.Intent;
|
||||
|
@ -4,18 +4,33 @@
|
||||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareFragmentActivity;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.LocaleAware.LocaleAwareFragmentActivity;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* Activity which displays account status.
|
||||
@ -93,14 +108,81 @@ public class FxAccountStatusActivity extends LocaleAwareFragmentActivity {
|
||||
return new AndroidFxAccount(this, account);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to maybe remove the given Android account.
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
public void maybeDeleteAndroidAccount(final Account account) {
|
||||
if (account == null) {
|
||||
Logger.warn(LOG_TAG, "Trying to delete null account; ignoring request.");
|
||||
return;
|
||||
}
|
||||
|
||||
final AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Boolean> future) {
|
||||
Logger.info(LOG_TAG, "Account " + Utils.obfuscateEmail(account.name) + " removed.");
|
||||
final Activity activity = FxAccountStatusActivity.this;
|
||||
final String text = activity.getResources().getString(R.string.fxaccount_remove_account_toast, account.name);
|
||||
Toast.makeText(activity, text, Toast.LENGTH_LONG).show();
|
||||
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Get the best dialog icon from the theme on v11+.
|
||||
* See http://stackoverflow.com/questions/14910536/android-dialog-theme-makes-icon-too-light/14910945#14910945.
|
||||
*/
|
||||
final int icon;
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
final TypedValue typedValue = new TypedValue();
|
||||
getTheme().resolveAttribute(android.R.attr.alertDialogIcon, typedValue, true);
|
||||
icon = typedValue.resourceId;
|
||||
} else {
|
||||
icon = android.R.drawable.ic_dialog_alert;
|
||||
}
|
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.fxaccount_remove_account_dialog_title)
|
||||
.setIcon(icon)
|
||||
.setMessage(R.string.fxaccount_remove_account_dialog_message)
|
||||
.setPositiveButton(android.R.string.ok, new Dialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
AccountManager.get(FxAccountStatusActivity.this).removeAccount(account, callback, null);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new Dialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.cancel();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
switch (itemId) {
|
||||
case android.R.id.home:
|
||||
if (itemId == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.remove_account) {
|
||||
maybeDeleteAndroidAccount(FirefoxAccounts.getFirefoxAccount(this));
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
final MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.fxaccount_status_menu, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
};
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentResolver;
|
||||
@ -84,6 +85,7 @@ public class FxAccountStatusFragment
|
||||
|
||||
protected EditTextPreference deviceNamePreference;
|
||||
protected Preference syncServerPreference;
|
||||
protected Preference morePreference;
|
||||
|
||||
protected volatile AndroidFxAccount fxAccount;
|
||||
// The contract is: when fxAccount is non-null, then clientsDataDelegate is
|
||||
@ -111,6 +113,13 @@ public class FxAccountStatusFragment
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// We need to do this before we can query the hardware menu button state.
|
||||
// We're guaranteed to have an activity at this point (onAttach is called
|
||||
// before onCreate). It's okay to call this multiple times (with different
|
||||
// contexts).
|
||||
HardwareUtils.init(getActivity());
|
||||
|
||||
addPreferences();
|
||||
}
|
||||
|
||||
@ -155,6 +164,12 @@ public class FxAccountStatusFragment
|
||||
deviceNamePreference.setOnPreferenceChangeListener(this);
|
||||
|
||||
syncServerPreference = ensureFindPreference("sync_server");
|
||||
morePreference = ensureFindPreference("more");
|
||||
morePreference.setOnPreferenceClickListener(this);
|
||||
|
||||
if (HardwareUtils.hasMenuButton()) {
|
||||
syncCategory.removePreference(morePreference);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,6 +224,11 @@ public class FxAccountStatusFragment
|
||||
return true;
|
||||
}
|
||||
|
||||
if (preference == morePreference) {
|
||||
getActivity().openOptionsMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1344,7 +1344,8 @@ class JavaPanZoomController
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent motionEvent) {
|
||||
sendPointToGecko("Gesture:LongPress", motionEvent);
|
||||
GeckoEvent e = GeckoEvent.createLongPressEvent(motionEvent);
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -206,6 +206,15 @@
|
||||
the two uses differently. -->
|
||||
<!ENTITY fxaccount_status_linktos 'Terms of Service'>
|
||||
<!ENTITY fxaccount_status_linkprivacy 'Privacy Notice'>
|
||||
<!ENTITY fxaccount_status_more 'More&ellipsis;'>
|
||||
|
||||
<!ENTITY fxaccount_remove_account_dialog_title 'Remove Firefox Account?'>
|
||||
<!ENTITY fxaccount_remove_account_dialog_message '&brandShortName; will stop syncing with your account, but won’t delete any of your browsing data on this device.'>
|
||||
<!-- Localization note: format string below will be replaced
|
||||
with the Firefox Account's email address. -->
|
||||
<!ENTITY fxaccount_remove_account_toast 'Firefox Account &formatS; removed.'>
|
||||
|
||||
<!ENTITY fxaccount_remove_account_menu_item 'Remove Account'>
|
||||
|
||||
<!-- Localization note: this is the name shown by the Android system
|
||||
itself for a Firefox Account. Don't localize this. -->
|
||||
@ -235,6 +244,6 @@
|
||||
<!ENTITY fxaccount_remote_error_COULD_NOT_CONNECT 'Cannot connect to network'>
|
||||
|
||||
<!ENTITY fxaccount_sync_sign_in_error_notification_title2 '&syncBrand.shortName.label; is not connected'>
|
||||
<!-- Note to translators: the format string below will be replaced
|
||||
<!-- Localization note: the format string below will be replaced
|
||||
with the Firefox Account's email address. -->
|
||||
<!ENTITY fxaccount_sync_sign_in_error_notification_text2 'Tap to sign in as &formatS;'>
|
||||
|
@ -314,6 +314,7 @@ gbjar.sources += [
|
||||
'JavaAddonManager.java',
|
||||
'LightweightTheme.java',
|
||||
'LightweightThemeDrawable.java',
|
||||
'LocaleAware.java',
|
||||
'LocaleManager.java',
|
||||
'MediaCastingBar.java',
|
||||
'MemoryMonitor.java',
|
||||
@ -368,6 +369,7 @@ gbjar.sources += [
|
||||
'prompts/PromptService.java',
|
||||
'prompts/TabInput.java',
|
||||
'ReaderModeUtils.java',
|
||||
'RemoteTabsExpandableListAdapter.java',
|
||||
'Restarter.java',
|
||||
'ScrollAnimator.java',
|
||||
'ServiceNotificationClient.java',
|
||||
@ -781,6 +783,7 @@ if CONFIG['MOZ_CRASHREPORTER']:
|
||||
if CONFIG['MOZ_ANDROID_MLS_STUMBLER']:
|
||||
main.included_projects += ['../FennecStumbler']
|
||||
main.referenced_projects += ['../FennecStumbler']
|
||||
DEFINES['MOZ_STUMBLER_API_KEY'] = CONFIG['MOZ_MOZILLA_API_KEY']
|
||||
|
||||
if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
|
||||
searchres = add_android_eclipse_library_project('FennecResourcesSearch')
|
||||
|
@ -115,6 +115,7 @@ OnSharedPreferenceChangeListener
|
||||
private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled";
|
||||
private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
|
||||
private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
|
||||
private static final String PREFS_STUMBLER_ENABLED = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
|
||||
|
||||
// This isn't a Gecko pref, even if it looks like one.
|
||||
private static final String PREFS_BROWSER_LOCALE = "locale";
|
||||
@ -849,6 +850,34 @@ OnSharedPreferenceChangeListener
|
||||
broadcastAction(context, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the provided value as the value of the
|
||||
* <code>PREFS_STUMBLER_ENABLED</code> pref.
|
||||
*/
|
||||
public static void broadcastStumblerPref(final Context context, final boolean value) {
|
||||
Intent intent = new Intent(PREFS_STUMBLER_ENABLED)
|
||||
.putExtra("pref", PREFS_GEO_REPORTING)
|
||||
.putExtra("branch", GeckoSharedPrefs.APP_PREFS_NAME)
|
||||
.putExtra("enabled", value)
|
||||
.putExtra("moz_mozilla_api_key", AppConstants.MOZ_STUMBLER_API_KEY);
|
||||
if (GeckoAppShell.getGeckoInterface() != null) {
|
||||
intent.putExtra("user_agent", GeckoAppShell.getGeckoInterface().getDefaultUAString());
|
||||
}
|
||||
if (!AppConstants.MOZILLA_OFFICIAL) {
|
||||
intent.putExtra("is_debug", true);
|
||||
}
|
||||
broadcastAction(context, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the current value of the
|
||||
* <code>PREFS_STUMBLER_ENABLED</code> pref.
|
||||
*/
|
||||
public static void broadcastStumblerPref(final Context context) {
|
||||
final boolean value = getBooleanPref(context, PREFS_GEO_REPORTING, true);
|
||||
broadcastStumblerPref(context, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the named preference in the default preferences file.
|
||||
*
|
||||
@ -1009,6 +1038,7 @@ OnSharedPreferenceChangeListener
|
||||
// repeated background upload attempts.
|
||||
broadcastHealthReportUploadPref(this, ((Boolean) newValue).booleanValue());
|
||||
} else if (PREFS_GEO_REPORTING.equals(prefName)) {
|
||||
broadcastStumblerPref(this, ((Boolean) newValue).booleanValue());
|
||||
// Translate boolean value to int for geo reporting pref.
|
||||
newValue = ((Boolean) newValue) ? 1 : 0;
|
||||
} else if (handlers.containsKey(prefName)) {
|
||||
|
@ -12,7 +12,7 @@
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp">
|
||||
|
||||
<TextView android:id="@+id/tab"
|
||||
<TextView android:id="@+id/title"
|
||||
style="@style/TabRowTextAppearance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item
|
||||
android:id="@+id/remove_account"
|
||||
android:title="@string/fxaccount_remove_account_menu_item" />
|
||||
</menu>
|
@ -78,12 +78,18 @@
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_device_name" />
|
||||
|
||||
<Preference
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:key="sync_server"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_sync_server" />
|
||||
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:key="more"
|
||||
android:persistent="false"
|
||||
android:title="@string/fxaccount_status_more" />
|
||||
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="legal_category"
|
||||
|
@ -27,7 +27,7 @@ import org.mozilla.gecko.sync.repositories.NullCursorException;
|
||||
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareActivity;
|
||||
import org.mozilla.gecko.LocaleAware.LocaleAwareActivity;
|
||||
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
|
@ -5,42 +5,32 @@
|
||||
package org.mozilla.gecko.tabs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.TabsAccessor;
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteClient;
|
||||
import org.mozilla.gecko.TabsAccessor.RemoteTab;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ExpandableListView;
|
||||
import android.widget.SimpleExpandableListAdapter;
|
||||
|
||||
/**
|
||||
* The actual list of synced tabs. This serves as the only child view of {@link RemoteTabsContainerPanel}
|
||||
* so it can be refreshed using a swipe-to-refresh gesture.
|
||||
*/
|
||||
class RemoteTabsList extends ExpandableListView
|
||||
implements ExpandableListView.OnGroupClickListener,
|
||||
ExpandableListView.OnChildClickListener,
|
||||
TabsAccessor.OnQueryTabsCompleteListener {
|
||||
private static final String[] CLIENT_KEY = new String[] { "name", "last_synced" };
|
||||
private static final String[] TAB_KEY = new String[] { "title", "url" };
|
||||
private static final int[] CLIENT_RESOURCE = new int[] { R.id.client, R.id.last_synced };
|
||||
private static final int[] TAB_RESOURCE = new int[] { R.id.tab, R.id.url };
|
||||
|
||||
private final Context context;
|
||||
public class RemoteTabsList extends ExpandableListView
|
||||
implements ExpandableListView.OnGroupClickListener,
|
||||
ExpandableListView.OnChildClickListener,
|
||||
TabsAccessor.OnQueryTabsCompleteListener {
|
||||
private TabsPanel tabsPanel;
|
||||
|
||||
private ArrayList <HashMap <String, String>> clients;
|
||||
private ArrayList <ArrayList <HashMap <String, String>>> tabsList;
|
||||
|
||||
// A list of the clients that are currently expanded.
|
||||
private List<String> expandedClientList;
|
||||
|
||||
@ -49,10 +39,10 @@ class RemoteTabsList extends ExpandableListView
|
||||
|
||||
public RemoteTabsList(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
|
||||
setOnGroupClickListener(this);
|
||||
setOnChildClickListener(this);
|
||||
setAdapter(new RemoteTabsExpandableListAdapter(R.layout.remote_tabs_group, R.layout.remote_tabs_child, null));
|
||||
}
|
||||
|
||||
public void setTabsPanel(TabsPanel panel) {
|
||||
@ -65,7 +55,8 @@ class RemoteTabsList extends ExpandableListView
|
||||
|
||||
@Override
|
||||
public boolean onGroupClick(ExpandableListView parent, View view, int groupPosition, long id) {
|
||||
final String clientGuid = clients.get(groupPosition).get("guid");
|
||||
final RemoteClient client = (RemoteClient) parent.getExpandableListAdapter().getGroup(groupPosition);
|
||||
final String clientGuid = client.guid;
|
||||
|
||||
if (isGroupExpanded(groupPosition)) {
|
||||
collapseGroup(groupPosition);
|
||||
@ -81,7 +72,7 @@ class RemoteTabsList extends ExpandableListView
|
||||
|
||||
@Override
|
||||
public boolean onChildClick(ExpandableListView parent, View view, int groupPosition, int childPosition, long id) {
|
||||
HashMap <String, String> tab = tabsList.get(groupPosition).get(childPosition);
|
||||
final RemoteTab tab = (RemoteTab) parent.getExpandableListAdapter().getChild(groupPosition, childPosition);
|
||||
if (tab == null) {
|
||||
autoHidePanel();
|
||||
return true;
|
||||
@ -89,64 +80,23 @@ class RemoteTabsList extends ExpandableListView
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "remote");
|
||||
|
||||
Tabs.getInstance().loadUrl(tab.get("url"), Tabs.LOADURL_NEW_TAB);
|
||||
Tabs.getInstance().loadUrl(tab.url, Tabs.LOADURL_NEW_TAB);
|
||||
autoHidePanel();
|
||||
|
||||
clientScrollPosition = clients.get(groupPosition).get("guid");
|
||||
final RemoteClient client = (RemoteClient) parent.getExpandableListAdapter().getGroup(groupPosition);
|
||||
clientScrollPosition = client.guid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryTabsComplete(List<TabsAccessor.RemoteTab> remoteTabsList) {
|
||||
ArrayList<TabsAccessor.RemoteTab> remoteTabs = new ArrayList<TabsAccessor.RemoteTab> (remoteTabsList);
|
||||
if (remoteTabs == null || remoteTabs.size() == 0)
|
||||
return;
|
||||
|
||||
clients = new ArrayList <HashMap <String, String>>();
|
||||
tabsList = new ArrayList <ArrayList <HashMap <String, String>>>();
|
||||
|
||||
String oldGuid = null;
|
||||
ArrayList <HashMap <String, String>> tabsForClient = null;
|
||||
HashMap <String, String> client;
|
||||
HashMap <String, String> tab;
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
for (TabsAccessor.RemoteTab remoteTab : remoteTabs) {
|
||||
final String clientGuid = remoteTab.guid;
|
||||
if (oldGuid == null || !TextUtils.equals(oldGuid, clientGuid)) {
|
||||
client = new HashMap <String, String>();
|
||||
client.put("name", remoteTab.name);
|
||||
client.put("last_synced", getLastSyncedString(now, remoteTab.lastModified));
|
||||
client.put("guid", clientGuid);
|
||||
clients.add(client);
|
||||
|
||||
tabsForClient = new ArrayList <HashMap <String, String>>();
|
||||
tabsList.add(tabsForClient);
|
||||
|
||||
oldGuid = new String(clientGuid);
|
||||
}
|
||||
|
||||
tab = new HashMap<String, String>();
|
||||
tab.put("title", TextUtils.isEmpty(remoteTab.title) ? remoteTab.url : remoteTab.title);
|
||||
tab.put("url", remoteTab.url);
|
||||
tabsForClient.add(tab);
|
||||
}
|
||||
|
||||
setAdapter(new SimpleExpandableListAdapter(context,
|
||||
clients,
|
||||
R.layout.remote_tabs_group,
|
||||
CLIENT_KEY,
|
||||
CLIENT_RESOURCE,
|
||||
tabsList,
|
||||
R.layout.remote_tabs_child,
|
||||
TAB_KEY,
|
||||
TAB_RESOURCE));
|
||||
public void onQueryTabsComplete(List<RemoteClient> clients) {
|
||||
((RemoteTabsExpandableListAdapter) getExpandableListAdapter()).replaceClients(clients);
|
||||
|
||||
// Either set the initial UI state, or restore it.
|
||||
List<String> newExpandedClientList = new ArrayList<String>();
|
||||
for (int i = 0; i < clients.size(); i++) {
|
||||
final String clientGuid = clients.get(i).get("guid");
|
||||
final String clientGuid = clients.get(i).guid;
|
||||
|
||||
if (expandedClientList == null) {
|
||||
// On initial entry we expand all clients by default.
|
||||
@ -167,16 +117,4 @@ class RemoteTabsList extends ExpandableListView
|
||||
}
|
||||
expandedClientList = newExpandedClientList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a relative "Last synced" time span for the given tab record.
|
||||
*
|
||||
* @param now local time.
|
||||
* @param time to format string for.
|
||||
* @return string describing time span
|
||||
*/
|
||||
protected String getLastSyncedString(long now, long time) {
|
||||
CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
|
||||
return getResources().getString(R.string.remote_tabs_last_synced, relativeTimeSpanString);
|
||||
}
|
||||
}
|
||||
|
@ -702,7 +702,7 @@ var SelectionHandler = {
|
||||
attachCaret: function sh_attachCaret(aElement) {
|
||||
// Ensure it isn't disabled, isn't handled by Android native dialog, and is editable text element
|
||||
if (aElement.disabled || InputWidgetHelper.hasInputWidget(aElement) || !this.isElementEditableText(aElement)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
this._initTargetInfo(aElement, this.TYPE_CURSOR);
|
||||
@ -722,6 +722,8 @@ var SelectionHandler = {
|
||||
handles: [this.HANDLE_TYPE_MIDDLE]
|
||||
});
|
||||
this._updateMenu();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// Target initialization for both TYPE_CURSOR and TYPE_SELECTION
|
||||
|
@ -2044,16 +2044,17 @@ var NativeWindow = {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
contextmenus: {
|
||||
items: {}, // a list of context menu items that we may show
|
||||
DEFAULT_HTML5_ORDER: -1, // Sort order for HTML5 context menu items
|
||||
|
||||
init: function() {
|
||||
Services.obs.addObserver(this, "Gesture:LongPress", false);
|
||||
BrowserApp.deck.addEventListener("contextmenu", this.show.bind(this), false);
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(this, "Gesture:LongPress");
|
||||
BrowserApp.deck.removeEventListener("contextmenu", this.show.bind(this), false);
|
||||
},
|
||||
|
||||
add: function() {
|
||||
@ -2295,7 +2296,7 @@ var NativeWindow = {
|
||||
},
|
||||
|
||||
// Returns true if there are any context menu items to show
|
||||
shouldShow: function() {
|
||||
_shouldShow: function() {
|
||||
for (let context in this.menus) {
|
||||
let menu = this.menus[context];
|
||||
if (menu.length > 0) {
|
||||
@ -2378,36 +2379,51 @@ var NativeWindow = {
|
||||
* any html5 context menus we are about to show, and fire some local notifications
|
||||
* for chrome consumers to do lazy menuitem construction
|
||||
*/
|
||||
_sendToContent: function(x, y) {
|
||||
let target = this._findTarget(x, y);
|
||||
if (!target)
|
||||
show: function(event) {
|
||||
// Android Long-press / contextmenu event provides clientX/Y data. This is not provided
|
||||
// by mochitest: test_browserElement_inproc_ContextmenuEvents.html.
|
||||
if (!event.clientX || !event.clientY) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._target = target;
|
||||
// Find the target of the long-press / contextmenu event.
|
||||
this._target = this._findTarget(event.clientX, event.clientY);
|
||||
if (!this._target) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, "before-build-contextmenu", "");
|
||||
this._buildMenu(x, y);
|
||||
// Try to build a list of contextmenu items. If successful, actually show the
|
||||
// native context menu by passing the list to Java.
|
||||
this._buildMenu(event.clientX, event.clientY);
|
||||
if (this._shouldShow()) {
|
||||
BrowserEventHandler._cancelTapHighlight();
|
||||
|
||||
// only send the contextmenu event to content if we are planning to show a context menu (i.e. not on every long tap)
|
||||
if (this.shouldShow()) {
|
||||
let event = target.ownerDocument.createEvent("MouseEvent");
|
||||
event.initMouseEvent("contextmenu", true, true, target.defaultView,
|
||||
0, x, y, x, y, false, false, false, false,
|
||||
0, null);
|
||||
target.ownerDocument.defaultView.addEventListener("contextmenu", this, false);
|
||||
target.dispatchEvent(event);
|
||||
} else {
|
||||
this.menus = null;
|
||||
Services.obs.notifyObservers({target: target, x: x, y: y}, "context-menu-not-shown", "");
|
||||
// Consume / preventDefault the event, and show the contextmenu.
|
||||
event.preventDefault();
|
||||
this._innerShow(this._target, event.clientX, event.clientY);
|
||||
this._target = null;
|
||||
|
||||
if (SelectionHandler.canSelect(target)) {
|
||||
if (!SelectionHandler.startSelection(target, {
|
||||
mode: SelectionHandler.SELECT_AT_POINT,
|
||||
x: x,
|
||||
y: y
|
||||
})) {
|
||||
SelectionHandler.attachCaret(target);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If no context-menu for long-press event, it may be meant to trigger text-selection.
|
||||
this.menus = null;
|
||||
Services.obs.notifyObservers(
|
||||
{target: this._target, x: event.clientX, y: event.clientY}, "context-menu-not-shown", "");
|
||||
|
||||
if (SelectionHandler.canSelect(this._target)) {
|
||||
// If textSelection WORD is successful,
|
||||
// consume / preventDefault the context menu event.
|
||||
if (SelectionHandler.startSelection(this._target,
|
||||
{ mode: SelectionHandler.SELECT_AT_POINT, x: event.clientX, y: event.clientY })) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
// If textSelection caret-attachment is successful,
|
||||
// consume / preventDefault the context menu event.
|
||||
if (SelectionHandler.attachCaret(this._target)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2477,17 +2493,6 @@ var NativeWindow = {
|
||||
}
|
||||
},
|
||||
|
||||
// Actually shows the native context menu by passing a list of context menu items to
|
||||
// show to the Java.
|
||||
_show: function(aEvent) {
|
||||
let popupNode = this._target;
|
||||
this._target = null;
|
||||
if (aEvent.defaultPrevented || !popupNode) {
|
||||
return;
|
||||
}
|
||||
this._innerShow(popupNode, aEvent.clientX, aEvent.clientY);
|
||||
},
|
||||
|
||||
// Walks the DOM tree to find a title from a node
|
||||
_findTitle: function(node) {
|
||||
let title = "";
|
||||
@ -2645,20 +2650,6 @@ var NativeWindow = {
|
||||
}
|
||||
},
|
||||
|
||||
// Called when the contextmenu is done propagating to content. If the event wasn't cancelled, will show a contextmenu.
|
||||
handleEvent: function(aEvent) {
|
||||
BrowserEventHandler._cancelTapHighlight();
|
||||
aEvent.target.ownerDocument.defaultView.removeEventListener("contextmenu", this, false);
|
||||
this._show(aEvent);
|
||||
},
|
||||
|
||||
// Called when a long press is observed in the native Java frontend. Will start the process of generating/showing a contextmenu.
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
let data = JSON.parse(aData);
|
||||
// content gets first crack at cancelling context menus
|
||||
this._sendToContent(data.x, data.y);
|
||||
},
|
||||
|
||||
// XXX - These are stolen from Util.js, we should remove them if we bring it back
|
||||
makeURLAbsolute: function makeURLAbsolute(base, url) {
|
||||
// Note: makeURI() will throw if url is not a valid URI
|
||||
@ -5512,6 +5503,12 @@ var FormAssistant = {
|
||||
// autocomplete suggestions
|
||||
_currentInputElement: null,
|
||||
|
||||
// The value of the currently focused input
|
||||
_currentInputValue: null,
|
||||
|
||||
// Whether we're in the middle of an autocomplete
|
||||
_doingAutocomplete: false,
|
||||
|
||||
_isBlocklisted: false,
|
||||
|
||||
// Keep track of whether or not an invalid form has been submitted
|
||||
@ -5526,6 +5523,7 @@ var FormAssistant = {
|
||||
|
||||
// We need to use a capturing listener for focus events
|
||||
BrowserApp.deck.addEventListener("focus", this, true);
|
||||
BrowserApp.deck.addEventListener("blur", this, true);
|
||||
BrowserApp.deck.addEventListener("click", this, true);
|
||||
BrowserApp.deck.addEventListener("input", this, false);
|
||||
BrowserApp.deck.addEventListener("pageshow", this, false);
|
||||
@ -5541,6 +5539,7 @@ var FormAssistant = {
|
||||
Services.obs.removeObserver(this, "PanZoom:StateChange");
|
||||
|
||||
BrowserApp.deck.removeEventListener("focus", this);
|
||||
BrowserApp.deck.removeEventListener("blur", this);
|
||||
BrowserApp.deck.removeEventListener("click", this);
|
||||
BrowserApp.deck.removeEventListener("input", this);
|
||||
BrowserApp.deck.removeEventListener("pageshow", this);
|
||||
@ -5572,6 +5571,8 @@ var FormAssistant = {
|
||||
|
||||
let editableElement = this._currentInputElement.QueryInterface(Ci.nsIDOMNSEditableElement);
|
||||
|
||||
this._doingAutocomplete = true;
|
||||
|
||||
// If we have an active composition string, commit it before sending
|
||||
// the autocomplete event with the text that will replace it.
|
||||
try {
|
||||
@ -5581,10 +5582,14 @@ var FormAssistant = {
|
||||
} catch (e) {}
|
||||
|
||||
editableElement.setUserInput(aData);
|
||||
this._currentInputValue = aData;
|
||||
|
||||
let event = this._currentInputElement.ownerDocument.createEvent("Events");
|
||||
event.initEvent("DOMAutoComplete", true, true);
|
||||
this._currentInputElement.dispatchEvent(event);
|
||||
|
||||
this._doingAutocomplete = false;
|
||||
|
||||
break;
|
||||
|
||||
case "FormAssist:Blocklisted":
|
||||
@ -5615,15 +5620,21 @@ var FormAssistant = {
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "focus":
|
||||
case "focus": {
|
||||
let currentElement = aEvent.target;
|
||||
|
||||
// Only show a validation message on focus.
|
||||
this._showValidationMessage(currentElement);
|
||||
break;
|
||||
}
|
||||
|
||||
case "click":
|
||||
currentElement = aEvent.target;
|
||||
case "blur": {
|
||||
this._currentInputValue = null;
|
||||
break;
|
||||
}
|
||||
|
||||
case "click": {
|
||||
let currentElement = aEvent.target;
|
||||
|
||||
// Prioritize a form validation message over autocomplete suggestions
|
||||
// when the element is first focused (a form validation message will
|
||||
@ -5639,9 +5650,21 @@ var FormAssistant = {
|
||||
|
||||
this._showAutoCompleteSuggestions(currentElement, checkResultsClick);
|
||||
break;
|
||||
}
|
||||
|
||||
case "input":
|
||||
currentElement = aEvent.target;
|
||||
case "input": {
|
||||
let currentElement = aEvent.target;
|
||||
|
||||
// If this element isn't focused, we're already in middle of an
|
||||
// autocomplete, or its value hasn't changed, don't show the
|
||||
// autocomplete popup.
|
||||
if (currentElement !== BrowserApp.getFocusedInput(BrowserApp.selectedBrowser) ||
|
||||
this._doingAutocomplete ||
|
||||
currentElement.value === this._currentInputValue) {
|
||||
break;
|
||||
}
|
||||
|
||||
this._currentInputValue = currentElement.value;
|
||||
|
||||
// Since we can only show one popup at a time, prioritze autocomplete
|
||||
// suggestions over a form validation message
|
||||
@ -5658,9 +5681,10 @@ var FormAssistant = {
|
||||
|
||||
this._showAutoCompleteSuggestions(currentElement, checkResultsInput);
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset invalid submit state on each pageshow
|
||||
case "pageshow":
|
||||
case "pageshow": {
|
||||
if (!this._invalidSubmit)
|
||||
return;
|
||||
|
||||
@ -5671,6 +5695,8 @@ var FormAssistant = {
|
||||
if (target == selectedDocument || target.ownerDocument == selectedDocument)
|
||||
this._invalidSubmit = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -23,8 +23,4 @@ public class Constants {
|
||||
public static final int SUGGESTION_MAX = 5;
|
||||
|
||||
public static final String ABOUT_BLANK = "about:blank";
|
||||
|
||||
// The default search engine for new users. This should match one of
|
||||
// the SearchEngineFactory.Engine enum values.
|
||||
public static final String DEFAULT_SEARCH_ENGINE = "YAHOO";
|
||||
}
|
||||
|
@ -21,6 +21,10 @@ import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.search.providers.SearchEngine;
|
||||
import org.mozilla.search.providers.SearchEngineManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This activity allows users to modify the settings for the search activity.
|
||||
@ -40,6 +44,8 @@ public class SearchPreferenceActivity extends PreferenceActivity
|
||||
public static final String PREF_CLEAR_HISTORY_KEY = "search.not_a_preference.clear_history";
|
||||
public static final String PREF_SEARCH_ENGINE_KEY = "search.engines.default";
|
||||
|
||||
private SearchEngineManager searchEngineManager;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -47,6 +53,8 @@ public class SearchPreferenceActivity extends PreferenceActivity
|
||||
|
||||
getPreferenceManager().setSharedPreferencesName(GeckoSharedPrefs.APP_PREFS_NAME);
|
||||
|
||||
searchEngineManager = new SearchEngineManager(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
@ -54,6 +62,12 @@ public class SearchPreferenceActivity extends PreferenceActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
searchEngineManager.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
@ -96,12 +110,39 @@ public class SearchPreferenceActivity extends PreferenceActivity
|
||||
}
|
||||
});
|
||||
|
||||
// Set summary for search engine picker.
|
||||
final ListPreference searchEnginePref = (ListPreference) findPreference(PREF_SEARCH_ENGINE_KEY);
|
||||
if (searchEnginePref.getValue() == null) {
|
||||
searchEnginePref.setValue(Constants.DEFAULT_SEARCH_ENGINE);
|
||||
}
|
||||
searchEnginePref.setSummary(searchEnginePref.getEntry());
|
||||
setUpSearchEnginePref();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setUpSearchEnginePref() {
|
||||
final AsyncTask<Void, Void, List<SearchEngine>> task = new AsyncTask<Void, Void, List<SearchEngine>>() {
|
||||
@Override
|
||||
protected List<SearchEngine> doInBackground(Void... params) {
|
||||
return searchEngineManager.getAllEngines();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<SearchEngine> engines) {
|
||||
final CharSequence[] entries = new CharSequence[engines.size()];
|
||||
final CharSequence[] entryValues = new CharSequence[engines.size()];
|
||||
|
||||
for (int i = 0; i < engines.size(); i++) {
|
||||
final SearchEngine engine = engines.get(i);
|
||||
entries[i] = engine.getName();
|
||||
entryValues[i] = engine.getIdentifier();
|
||||
}
|
||||
|
||||
final ListPreference searchEnginePref = (ListPreference) findPreference(PREF_SEARCH_ENGINE_KEY);
|
||||
searchEnginePref.setEntries(entries);
|
||||
searchEnginePref.setEntryValues(entryValues);
|
||||
|
||||
if (searchEnginePref.getValue() == null) {
|
||||
searchEnginePref.setValue(getResources().getString(R.string.default_engine_identifier));
|
||||
}
|
||||
searchEnginePref.setSummary(searchEnginePref.getEntry());
|
||||
}
|
||||
};
|
||||
task.execute();
|
||||
}
|
||||
|
||||
private void clearHistory() {
|
||||
|
@ -5,6 +5,7 @@
|
||||
package org.mozilla.search.providers;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.util.Xml;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
@ -19,6 +20,7 @@ import java.util.Locale;
|
||||
* the search activity.
|
||||
*/
|
||||
public class SearchEngine {
|
||||
private static final String LOG_TAG = "SearchEngine";
|
||||
|
||||
private static final String URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
|
||||
private static final String URLTYPE_SEARCH_HTML = "text/html";
|
||||
@ -166,6 +168,10 @@ public class SearchEngine {
|
||||
return String.format(STYLE_INJECTION_SCRIPT, css);
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return shortName;
|
||||
}
|
||||
@ -184,6 +190,10 @@ public class SearchEngine {
|
||||
* @param query The user's query. This method will escape and encode the query.
|
||||
*/
|
||||
public String resultsUriForQuery(String query) {
|
||||
if (resultsUri == null) {
|
||||
Log.e(LOG_TAG, "No results URL for search engine: " + identifier);
|
||||
return "";
|
||||
}
|
||||
final String template = Uri.decode(resultsUri.toString());
|
||||
return paramSubstitution(template, Uri.encode(query));
|
||||
}
|
||||
@ -194,6 +204,10 @@ public class SearchEngine {
|
||||
* @param query The user's query. This method will escape and encode the query.
|
||||
*/
|
||||
public String getSuggestionTemplate(String query) {
|
||||
if (suggestUri == null) {
|
||||
Log.e(LOG_TAG, "No suggestions template for search engine: " + identifier);
|
||||
return "";
|
||||
}
|
||||
final String template = Uri.decode(suggestUri.toString());
|
||||
return paramSubstitution(template, Uri.encode(query));
|
||||
}
|
||||
|
@ -13,12 +13,16 @@ import android.util.Log;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
import org.mozilla.search.Constants;
|
||||
import org.mozilla.search.R;
|
||||
import org.mozilla.search.SearchPreferenceActivity;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final String LOG_TAG = "SearchEngineManager";
|
||||
@ -81,9 +85,10 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
final AsyncTask<Void, Void, SearchEngine> task = new AsyncTask<Void, Void, SearchEngine>() {
|
||||
@Override
|
||||
protected SearchEngine doInBackground(Void... params) {
|
||||
final String identifier = GeckoSharedPrefs.forApp(context)
|
||||
.getString(SearchPreferenceActivity.PREF_SEARCH_ENGINE_KEY, Constants.DEFAULT_SEARCH_ENGINE)
|
||||
.toLowerCase();
|
||||
String identifier = GeckoSharedPrefs.forApp(context).getString(SearchPreferenceActivity.PREF_SEARCH_ENGINE_KEY, null);
|
||||
if (TextUtils.isEmpty(identifier)) {
|
||||
identifier = context.getResources().getString(R.string.default_engine_identifier);
|
||||
}
|
||||
return createEngine(identifier);
|
||||
}
|
||||
|
||||
@ -99,6 +104,55 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
task.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of SearchEngine instances from all available open search plugins.
|
||||
* This method does disk I/O, call it from a background thread.
|
||||
*
|
||||
* @return List of SearchEngine instances
|
||||
*/
|
||||
public List<SearchEngine> getAllEngines() {
|
||||
// First try to read the engine list from the jar.
|
||||
final String url = getSearchPluginsJarUrl("list.txt");
|
||||
InputStream in = GeckoJarReader.getStream(url);
|
||||
|
||||
// Fallback for standalone search activity.
|
||||
if (in == null) {
|
||||
try {
|
||||
in = context.getResources().getAssets().open("engines/list.txt");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error reading list.txt");
|
||||
}
|
||||
}
|
||||
|
||||
final List<SearchEngine> list = new ArrayList<SearchEngine>();
|
||||
InputStreamReader isr = null;
|
||||
|
||||
try {
|
||||
isr = new InputStreamReader(in);
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String identifier;
|
||||
while ((identifier = br.readLine()) != null) {
|
||||
list.add(createEngine(identifier));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error creating all search engines from list.txt");
|
||||
} finally {
|
||||
if (isr != null) {
|
||||
try {
|
||||
isr.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SearchEngine instance from an open search plugin.
|
||||
* This method does disk I/O, call it from a background thread.
|
||||
@ -142,7 +196,7 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
*/
|
||||
private InputStream getEngineFromAssets(String identifier) {
|
||||
try {
|
||||
return context.getResources().getAssets().open(identifier + ".xml");
|
||||
return context.getResources().getAssets().open("engines/" + identifier + ".xml");
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "Exception getting search engine from assets", e);
|
||||
return null;
|
||||
@ -157,12 +211,21 @@ public class SearchEngineManager implements SharedPreferences.OnSharedPreference
|
||||
* @return InputStream for open search plugin XML
|
||||
*/
|
||||
private InputStream getEngineFromJar(String identifier) {
|
||||
final String url = getSearchPluginsJarUrl(identifier + ".xml");
|
||||
return GeckoJarReader.getStream(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the jar URL for a file in the searchplugins directory
|
||||
*
|
||||
* @param fileName
|
||||
* @return
|
||||
*/
|
||||
private String getSearchPluginsJarUrl(String fileName) {
|
||||
// TODO: Get the real value for this
|
||||
final String locale = "en-US";
|
||||
|
||||
final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + identifier + ".xml";
|
||||
final String url = "jar:jar:file://" + context.getPackageResourcePath() + "!/" + AppConstants.OMNIJAR_NAME + path;
|
||||
|
||||
return GeckoJarReader.getStream(url);
|
||||
final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + fileName;
|
||||
return "jar:jar:file://" + context.getPackageResourcePath() + "!/" + AppConstants.OMNIJAR_NAME + path;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_searchProvider_entries">
|
||||
<item>@string/pref_searchProvider_bing</item>
|
||||
<item>@string/pref_searchProvider_google</item>
|
||||
<item>@string/pref_searchProvider_yahoo</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_searchProvider_values">
|
||||
<item>BING</item>
|
||||
<item>GOOGLE</item>
|
||||
<item>YAHOO</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -10,7 +10,5 @@
|
||||
<ListPreference
|
||||
android:key="search.engines.default"
|
||||
android:title="@string/pref_searchProvider_title"
|
||||
android:dialogTitle="@string/pref_searchProvider_title"
|
||||
android:entries="@array/pref_searchProvider_entries"
|
||||
android:entryValues="@array/pref_searchProvider_values" />
|
||||
android:dialogTitle="@string/pref_searchProvider_title" />
|
||||
</PreferenceScreen>
|
||||
|
@ -18,9 +18,8 @@
|
||||
|
||||
<!ENTITY pref_searchProvider_title 'Search engine'>
|
||||
|
||||
<!ENTITY pref_searchProvider_bing 'Bing'>
|
||||
<!ENTITY pref_searchProvider_google 'Google'>
|
||||
<!ENTITY pref_searchProvider_yahoo 'Yahoo'>
|
||||
|
||||
<!ENTITY search_widget_button_label 'Search'>
|
||||
|
||||
<!-- Localization note (default_engine_identifier): Search engine identifier for the default
|
||||
engine. This should be one of the identifiers listed in /searchplugins/list.txt -->
|
||||
<!ENTITY default_engine_identifier 'yahoo'>
|
||||
|
@ -12,9 +12,7 @@
|
||||
|
||||
<string name="pref_searchProvider_title">&pref_searchProvider_title;</string>
|
||||
|
||||
<string name="pref_searchProvider_bing">&pref_searchProvider_bing;</string>
|
||||
<string name="pref_searchProvider_google">&pref_searchProvider_google;</string>
|
||||
<string name="pref_searchProvider_yahoo">&pref_searchProvider_yahoo;</string>
|
||||
|
||||
<string name="search_widget_name">&search_app_name;</string>
|
||||
<string name="search_widget_button_label">&search_widget_button_label;</string>
|
||||
|
||||
<string name="default_engine_identifier">&default_engine_identifier;</string>
|
||||
|
@ -190,6 +190,7 @@
|
||||
<string name="fxaccount_status_legal">&fxaccount_status_legal;</string>
|
||||
<string name="fxaccount_status_linktos">&fxaccount_status_linktos;</string>
|
||||
<string name="fxaccount_status_linkprivacy">&fxaccount_status_linkprivacy;</string>
|
||||
<string name="fxaccount_status_more">&fxaccount_status_more;</string>
|
||||
|
||||
<string name="fxaccount_label">&fxaccount_account_type_label;</string>
|
||||
|
||||
@ -208,3 +209,9 @@
|
||||
|
||||
<string name="fxaccount_sync_sign_in_error_notification_title">&fxaccount_sync_sign_in_error_notification_title2;</string>
|
||||
<string name="fxaccount_sync_sign_in_error_notification_text">&fxaccount_sync_sign_in_error_notification_text2;</string>
|
||||
|
||||
<!-- Remove Account -->
|
||||
<string name="fxaccount_remove_account_dialog_title">&fxaccount_remove_account_dialog_title;</string>
|
||||
<string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
|
||||
<string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
|
||||
<string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
|
||||
|