зеркало из https://github.com/mozilla/gecko-dev.git
merge fx-team to mozilla-central a=merge
This commit is contained in:
Коммит
94b457497a
|
@ -65,6 +65,10 @@ const CATEGORIES = [{
|
|||
color: "#d99b28",
|
||||
abbrev: "events",
|
||||
label: L10N.getStr("category.events")
|
||||
}, {
|
||||
color: "#8fa1b2",
|
||||
abbrev: "tools",
|
||||
label: L10N.getStr("category.tools")
|
||||
}];
|
||||
|
||||
/**
|
||||
|
@ -81,6 +85,9 @@ const CATEGORY_MAPPINGS = {
|
|||
"1024": CATEGORIES[5], // js::ProfileEntry::Category::GRAPHICS
|
||||
"2048": CATEGORIES[6], // js::ProfileEntry::Category::STORAGE
|
||||
"4096": CATEGORIES[7], // js::ProfileEntry::Category::EVENTS
|
||||
|
||||
// non-bitmasks for specially-assigned categories
|
||||
"9000": CATEGORIES[8],
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -106,17 +113,17 @@ const [CATEGORY_MASK, CATEGORY_MASK_LIST] = (function () {
|
|||
return [
|
||||
function (name, index) {
|
||||
if (!(name in bitmasksForCategory)) {
|
||||
throw new Error(`Category abbreviation '${name}' does not exist.`);
|
||||
throw new Error(`Category abbreviation "${name}" does not exist.`);
|
||||
}
|
||||
if (arguments.length == 1) {
|
||||
if (bitmasksForCategory[name].length != 1) {
|
||||
throw new Error(`Expected exactly one category number for '${name}'.`);
|
||||
throw new Error(`Expected exactly one category number for "${name}".`);
|
||||
} else {
|
||||
return bitmasksForCategory[name][0];
|
||||
}
|
||||
} else {
|
||||
if (index > bitmasksForCategory[name].length) {
|
||||
throw new Error(`Index '${index}' too high for category '${name}'.`);
|
||||
throw new Error(`Index "${index}" too high for category "${name}".`);
|
||||
} else {
|
||||
return bitmasksForCategory[name][index - 1];
|
||||
}
|
||||
|
@ -125,7 +132,7 @@ const [CATEGORY_MASK, CATEGORY_MASK_LIST] = (function () {
|
|||
|
||||
function (name) {
|
||||
if (!(name in bitmasksForCategory)) {
|
||||
throw new Error(`Category abbreviation '${name}' does not exist.`);
|
||||
throw new Error(`Category abbreviation "${name}" does not exist.`);
|
||||
}
|
||||
return bitmasksForCategory[name];
|
||||
}
|
||||
|
@ -135,11 +142,15 @@ const [CATEGORY_MASK, CATEGORY_MASK_LIST] = (function () {
|
|||
// Human-readable "other" category bitmask. Older Geckos don't have all the
|
||||
// necessary instrumentation in the sampling profiler backend for creating
|
||||
// a categories graph, in which case we default to the "other" category.
|
||||
const CATEGORY_OTHER = CATEGORY_MASK('other');
|
||||
const CATEGORY_OTHER = CATEGORY_MASK("other");
|
||||
|
||||
// Human-readable JIT category bitmask. Certain pseudo-frames in a sample,
|
||||
// like "EnterJIT", don't have any associated `cateogry` information.
|
||||
const CATEGORY_JIT = CATEGORY_MASK('js');
|
||||
// like "EnterJIT", don't have any associated `category` information.
|
||||
const CATEGORY_JIT = CATEGORY_MASK("js");
|
||||
|
||||
// Human-readable "devtools" category bitmask. Not emitted from frames themselves,
|
||||
// but used manually in the client.
|
||||
const CATEGORY_DEVTOOLS = CATEGORY_MASK("tools");
|
||||
|
||||
// Exported symbols.
|
||||
exports.L10N = L10N;
|
||||
|
@ -150,3 +161,4 @@ exports.CATEGORY_MASK = CATEGORY_MASK;
|
|||
exports.CATEGORY_MASK_LIST = CATEGORY_MASK_LIST;
|
||||
exports.CATEGORY_OTHER = CATEGORY_OTHER;
|
||||
exports.CATEGORY_JIT = CATEGORY_JIT;
|
||||
exports.CATEGORY_DEVTOOLS = CATEGORY_DEVTOOLS;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "global",
|
||||
"devtools/performance/global");
|
||||
|
||||
// Character codes used in various parsing helper functions.
|
||||
const CHAR_CODE_A = "a".charCodeAt(0);
|
||||
|
@ -162,33 +162,55 @@ function parseLocation(location, fallbackLine, fallbackColumn) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Checks if the specified function represents a chrome or content frame.
|
||||
* Sets the properties of `isContent` and `category` on a frame.
|
||||
*
|
||||
* @param string location
|
||||
* The location of the frame.
|
||||
* @param number category [optional]
|
||||
* If a chrome frame, the category.
|
||||
* @return boolean
|
||||
* True if a content frame, false if a chrome frame.
|
||||
* @param {InflatedFrame} frame
|
||||
*/
|
||||
function isContent({ location, category }) {
|
||||
function computeIsContentAndCategory(frame) {
|
||||
// Only C++ stack frames have associated category information.
|
||||
if (category) {
|
||||
return false;
|
||||
if (frame.category) {
|
||||
return;
|
||||
}
|
||||
|
||||
let location = frame.location;
|
||||
|
||||
// Locations in frames with function names look like:
|
||||
// "functionName (foo://bar)".
|
||||
// Look for the starting left parenthesis, then try to match a
|
||||
// scheme name.
|
||||
for (let i = 0; i < location.length; i++) {
|
||||
if (location.charCodeAt(i) === CHAR_CODE_LPAREN) {
|
||||
return isContentScheme(location, i + 1);
|
||||
if (isContentScheme(location, i + 1)) {
|
||||
frame.isContent = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let j = i + 1; j < location.length; j++) {
|
||||
if (location.charCodeAt(j) === CHAR_CODE_R &&
|
||||
isChromeScheme(location, j) &&
|
||||
(location.indexOf("resource://gre/modules/devtools") !== -1 ||
|
||||
location.indexOf("resource:///modules/devtools") !== -1)) {
|
||||
frame.category = global.CATEGORY_DEVTOOLS;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If there was no left parenthesis, try matching from the start.
|
||||
return isContentScheme(location, 0);
|
||||
if (isContentScheme(location, 0)) {
|
||||
frame.isContent = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (location === "EnterJIT") {
|
||||
frame.category = global.CATEGORY_JIT;
|
||||
return;
|
||||
}
|
||||
|
||||
frame.category = global.CATEGORY_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -247,10 +269,17 @@ function InflatedFrame(index, frameTable, stringTable, allocationsTable) {
|
|||
this.optimizations = frame[OPTIMIZATIONS_SLOT];
|
||||
this.line = frame[LINE_SLOT];
|
||||
this.column = undefined;
|
||||
this.category = category;
|
||||
this.metaCategory = category || CATEGORY_OTHER;
|
||||
this.allocations = allocationsTable ? allocationsTable[index] : 0;
|
||||
this.isContent = isContent(this);
|
||||
this.category = category;
|
||||
this.isContent = false;
|
||||
|
||||
// Attempt to compute if this frame is a content frame, and if not,
|
||||
// its category.
|
||||
//
|
||||
// Since only C++ stack frames have associated category information,
|
||||
// attempt to generate a useful category, fallback to the one provided
|
||||
// by the profiling data, or fallback to an unknown category.
|
||||
computeIsContentAndCategory(this);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -284,7 +313,7 @@ InflatedFrame.prototype.getFrameKey = function getFrameKey(options) {
|
|||
// non-leaf platform frames don't give any meaningful context, and so we
|
||||
// can safely filter them out.
|
||||
options.isMetaCategoryOut = true;
|
||||
return this.metaCategory;
|
||||
return this.category;
|
||||
}
|
||||
|
||||
// Return an empty string denoting that this frame should be skipped.
|
||||
|
@ -418,8 +447,8 @@ function isNumeric(c) {
|
|||
return c >= CHAR_CODE_0 && c <= CHAR_CODE_9;
|
||||
}
|
||||
|
||||
exports.computeIsContentAndCategory = computeIsContentAndCategory;
|
||||
exports.parseLocation = parseLocation;
|
||||
exports.isContent = isContent;
|
||||
exports.getInflatedFrameCache = getInflatedFrameCache;
|
||||
exports.getOrAddInflatedFrame = getOrAddInflatedFrame;
|
||||
exports.InflatedFrame = InflatedFrame;
|
||||
|
|
|
@ -9,12 +9,6 @@ loader.lazyRequireGetter(this, "L10N",
|
|||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORIES",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORY_JIT",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
|
||||
"devtools/performance/global", true);
|
||||
loader.lazyRequireGetter(this, "JITOptimizations",
|
||||
"devtools/performance/jit", true);
|
||||
loader.lazyRequireGetter(this, "FrameUtils",
|
||||
|
@ -377,15 +371,15 @@ function FrameNode(frameKey, { location, line, category, allocations, isContent
|
|||
this.key = frameKey;
|
||||
this.location = location;
|
||||
this.line = line;
|
||||
this.category = category;
|
||||
this.allocations = allocations;
|
||||
this.samples = 0;
|
||||
this.duration = 0;
|
||||
this.calls = [];
|
||||
this.isContent = isContent;
|
||||
this.isContent = !!isContent;
|
||||
this._optimizations = null;
|
||||
this._stringTable = null;
|
||||
this.isMetaCategory = isMetaCategory;
|
||||
this.isMetaCategory = !!isMetaCategory;
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
FrameNode.prototype = {
|
||||
|
@ -462,19 +456,7 @@ FrameNode.prototype = {
|
|||
* function name and source url.
|
||||
*/
|
||||
_computeInfo: function() {
|
||||
// "EnterJIT" pseudoframes are special, not actually on the stack.
|
||||
if (this.location == "EnterJIT") {
|
||||
this.category = CATEGORY_JIT;
|
||||
}
|
||||
|
||||
if (this.isMetaCategory && !this.category) {
|
||||
this.category = CATEGORY_OTHER;
|
||||
}
|
||||
|
||||
// Since only C++ stack frames have associated category information,
|
||||
// default to an "unknown" category otherwise.
|
||||
let categoryData = CATEGORY_MAPPINGS[this.category] || {};
|
||||
|
||||
let parsedData = FrameUtils.parseLocation(this.location, this.line, this.column);
|
||||
parsedData.nodeType = "Frame";
|
||||
parsedData.categoryData = categoryData;
|
||||
|
|
|
@ -117,8 +117,6 @@ skip-if = e10s # GC events seem unreliable in multiprocess
|
|||
[browser_perf-recording-selected-03.js]
|
||||
[browser_perf-recording-selected-04.js]
|
||||
[browser_perf-theme-toggle-01.js]
|
||||
[browser_profiler_categories.js]
|
||||
[browser_profiler_content-check.js]
|
||||
[browser_profiler_tree-abstract-01.js]
|
||||
[browser_profiler_tree-abstract-02.js]
|
||||
[browser_profiler_tree-abstract-03.js]
|
||||
|
|
|
@ -26,6 +26,7 @@ function* spawnTest () {
|
|||
|
||||
let maxMarkerTime = model._timelineStartTime + model.getDuration() + TIME_CLOSE_TO;
|
||||
|
||||
ok(markers.every(({stack}) => typeof stack === "number"), "All markers have stack references.");
|
||||
ok(markers.every(({name}) => name === "TimeStamp"), "All markers found are TimeStamp markers");
|
||||
ok(markers.length === 2, "found 2 TimeStamp markers");
|
||||
ok(markers.every(({start}) => typeof start === "number" && start > 0 && start < maxMarkerTime),
|
||||
|
|
|
@ -40,7 +40,12 @@ function run_test() {
|
|||
}
|
||||
|
||||
add_task(function () {
|
||||
const { isContent, parseLocation } = devtools.require("devtools/performance/frame-utils");
|
||||
const { computeIsContentAndCategory, parseLocation } = devtools.require("devtools/performance/frame-utils");
|
||||
let isContent = (frame) => {
|
||||
computeIsContentAndCategory(frame);
|
||||
return frame.isContent;
|
||||
};
|
||||
|
||||
|
||||
for (let frame of CONTENT_LOCATIONS) {
|
||||
ok(isContent.apply(null, frameify(frame)), `${frame[0]} should be considered a content frame.`);
|
||||
|
|
|
@ -6,46 +6,53 @@
|
|||
* works properly.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function () {
|
||||
let FrameUtils = devtools.require("devtools/performance/frame-utils");
|
||||
|
||||
ok(FrameUtils.isContent({ location: "http://foo" }),
|
||||
let isContent = (frame) => {
|
||||
FrameUtils.computeIsContentAndCategory(frame);
|
||||
return frame.isContent;
|
||||
};
|
||||
|
||||
ok(isContent({ location: "http://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(FrameUtils.isContent({ location: "https://foo" }),
|
||||
ok(isContent({ location: "https://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(FrameUtils.isContent({ location: "file://foo" }),
|
||||
ok(isContent({ location: "file://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!FrameUtils.isContent({ location: "chrome://foo" }),
|
||||
ok(!isContent({ location: "chrome://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ location: "resource://foo" }),
|
||||
ok(!isContent({ location: "resource://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!FrameUtils.isContent({ location: "chrome://foo -> http://bar" }),
|
||||
ok(!isContent({ location: "chrome://foo -> http://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ location: "chrome://foo -> https://bar" }),
|
||||
ok(!isContent({ location: "chrome://foo -> https://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ location: "chrome://foo -> file://bar" }),
|
||||
ok(!isContent({ location: "chrome://foo -> file://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!FrameUtils.isContent({ location: "resource://foo -> http://bar" }),
|
||||
ok(!isContent({ location: "resource://foo -> http://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ location: "resource://foo -> https://bar" }),
|
||||
ok(!isContent({ location: "resource://foo -> https://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ location: "resource://foo -> file://bar" }),
|
||||
ok(!isContent({ location: "resource://foo -> file://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!FrameUtils.isContent({ category: 1, location: "chrome://foo" }),
|
||||
ok(!isContent({ category: 1, location: "chrome://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ category: 1, location: "resource://foo" }),
|
||||
ok(!isContent({ category: 1, location: "resource://foo" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> http://bar" }),
|
||||
ok(!isContent({ category: 1, location: "file://foo -> http://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> https://bar" }),
|
||||
ok(!isContent({ category: 1, location: "file://foo -> https://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
ok(!FrameUtils.isContent({ category: 1, location: "file://foo -> file://bar" }),
|
||||
ok(!isContent({ category: 1, location: "file://foo -> file://bar" }),
|
||||
"Verifying content/chrome frames is working properly.");
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
|
@ -5,7 +5,11 @@
|
|||
* Tests if the profiler categories are mapped correctly.
|
||||
*/
|
||||
|
||||
function test() {
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function () {
|
||||
let global = devtools.require("devtools/performance/global");
|
||||
let l10n = global.L10N;
|
||||
let categories = global.CATEGORIES;
|
||||
|
@ -15,20 +19,18 @@ function test() {
|
|||
ok(count,
|
||||
"Should have a non-empty list of categories available.");
|
||||
|
||||
ok(!categories.some(e => !e.color),
|
||||
ok(categories.some(e => e.color),
|
||||
"All categories have an associated color.");
|
||||
|
||||
ok(!categories.some(e => !e.label),
|
||||
ok(categories.every(e => e.label),
|
||||
"All categories have an associated label.");
|
||||
|
||||
ok(!categories.some(e => e.label != l10n.getStr("category." + e.abbrev)),
|
||||
ok(categories.every(e => e.label === l10n.getStr("category." + e.abbrev)),
|
||||
"All categories have a correctly localized label.");
|
||||
|
||||
ok(!Object.keys(mappings).some(e => !Number.isInteger(Math.log2(e))),
|
||||
"All bitmask mappings keys are powers of 2.");
|
||||
ok(Object.keys(mappings).every(e => (Number(e) >= 9000 && Number(e) <= 9999) || Number.isInteger(Math.log2(e))),
|
||||
"All bitmask mappings keys are powers of 2, or between 9000-9999 for special categories.");
|
||||
|
||||
ok(!Object.keys(mappings).some(e => categories.indexOf(mappings[e]) == -1),
|
||||
ok(Object.keys(mappings).every(e => categories.indexOf(mappings[e]) !== -1),
|
||||
"All bitmask mappings point to a category.");
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
|
@ -26,7 +26,7 @@ add_task(function test() {
|
|||
"The correct duration was calculated for the root node.");
|
||||
equal(root.getInfo().functionName, "(root)",
|
||||
"The correct function name was retrieved for the root node.");
|
||||
equal(root.getInfo().categoryData.toSource(), "({})",
|
||||
equal(root.getInfo().categoryData.abbrev, "other",
|
||||
"The correct empty category data was retrieved for the root node.");
|
||||
|
||||
equal(root.calls.length, 1,
|
||||
|
|
|
@ -13,238 +13,82 @@ add_task(function test() {
|
|||
let FrameUtils = devtools.require("devtools/performance/frame-utils");
|
||||
let { FrameNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CATEGORY_OTHER } = devtools.require("devtools/performance/global");
|
||||
let compute = frame => {
|
||||
FrameUtils.computeIsContentAndCategory(frame);
|
||||
return frame;
|
||||
};
|
||||
|
||||
let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", {
|
||||
location: "hello/<.world (http://foo/bar.js:123:987)",
|
||||
line: 456,
|
||||
isContent: FrameUtils.isContent({
|
||||
location: "hello/<.world (http://foo/bar.js:123:987)"
|
||||
})
|
||||
}, false);
|
||||
|
||||
equal(frame1.getInfo().nodeType, "Frame",
|
||||
"The first frame node has the correct type.");
|
||||
equal(frame1.getInfo().functionName, "hello/<.world",
|
||||
"The first frame node has the correct function name.");
|
||||
equal(frame1.getInfo().fileName, "bar.js",
|
||||
"The first frame node has the correct file name.");
|
||||
equal(frame1.getInfo().hostName, "foo",
|
||||
"The first frame node has the correct host name.");
|
||||
equal(frame1.getInfo().url, "http://foo/bar.js",
|
||||
"The first frame node has the correct url.");
|
||||
equal(frame1.getInfo().line, 123,
|
||||
"The first frame node has the correct line.");
|
||||
equal(frame1.getInfo().column, 987,
|
||||
"The first frame node has the correct column.");
|
||||
equal(frame1.getInfo().categoryData.toSource(), "({})",
|
||||
"The first frame node has the correct category data.");
|
||||
equal(frame1.getInfo().isContent, true,
|
||||
"The first frame node has the correct content flag.");
|
||||
|
||||
let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", {
|
||||
location: "hello/<.world (http://foo/bar.js#baz:123:987)",
|
||||
line: 456,
|
||||
isContent: FrameUtils.isContent({
|
||||
location: "hello/<.world (http://foo/bar.js#baz:123:987)"
|
||||
})
|
||||
}, false);
|
||||
|
||||
equal(frame2.getInfo().nodeType, "Frame",
|
||||
"The second frame node has the correct type.");
|
||||
equal(frame2.getInfo().functionName, "hello/<.world",
|
||||
"The second frame node has the correct function name.");
|
||||
equal(frame2.getInfo().fileName, "bar.js",
|
||||
"The second frame node has the correct file name.");
|
||||
equal(frame2.getInfo().hostName, "foo",
|
||||
"The second frame node has the correct host name.");
|
||||
equal(frame2.getInfo().url, "http://foo/bar.js#baz",
|
||||
"The second frame node has the correct url.");
|
||||
equal(frame2.getInfo().line, 123,
|
||||
"The second frame node has the correct line.");
|
||||
equal(frame2.getInfo().column, 987,
|
||||
"The second frame node has the correct column.");
|
||||
equal(frame2.getInfo().categoryData.toSource(), "({})",
|
||||
"The second frame node has the correct category data.");
|
||||
equal(frame2.getInfo().isContent, true,
|
||||
"The second frame node has the correct content flag.");
|
||||
|
||||
let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", {
|
||||
location: "hello/<.world (http://foo/#bar:123:987)",
|
||||
line: 456,
|
||||
isContent: FrameUtils.isContent({
|
||||
location: "hello/<.world (http://foo/#bar:123:987)"
|
||||
})
|
||||
}, false);
|
||||
|
||||
equal(frame3.getInfo().nodeType, "Frame",
|
||||
"The third frame node has the correct type.");
|
||||
equal(frame3.getInfo().functionName, "hello/<.world",
|
||||
"The third frame node has the correct function name.");
|
||||
equal(frame3.getInfo().fileName, "/",
|
||||
"The third frame node has the correct file name.");
|
||||
equal(frame3.getInfo().hostName, "foo",
|
||||
"The third frame node has the correct host name.");
|
||||
equal(frame3.getInfo().url, "http://foo/#bar",
|
||||
"The third frame node has the correct url.");
|
||||
equal(frame3.getInfo().line, 123,
|
||||
"The third frame node has the correct line.");
|
||||
equal(frame3.getInfo().column, 987,
|
||||
"The third frame node has the correct column.");
|
||||
equal(frame3.getInfo().categoryData.toSource(), "({})",
|
||||
"The third frame node has the correct category data.");
|
||||
equal(frame3.getInfo().isContent, true,
|
||||
"The third frame node has the correct content flag.");
|
||||
|
||||
let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", {
|
||||
location: "hello/<.world (http://foo/:123:987)",
|
||||
line: 456,
|
||||
isContent: FrameUtils.isContent({
|
||||
location: "hello/<.world (http://foo/:123:987)"
|
||||
})
|
||||
}, false);
|
||||
|
||||
equal(frame4.getInfo().nodeType, "Frame",
|
||||
"The fourth frame node has the correct type.");
|
||||
equal(frame4.getInfo().functionName, "hello/<.world",
|
||||
"The fourth frame node has the correct function name.");
|
||||
equal(frame4.getInfo().fileName, "/",
|
||||
"The fourth frame node has the correct file name.");
|
||||
equal(frame4.getInfo().hostName, "foo",
|
||||
"The fourth frame node has the correct host name.");
|
||||
equal(frame4.getInfo().url, "http://foo/",
|
||||
"The fourth frame node has the correct url.");
|
||||
equal(frame4.getInfo().line, 123,
|
||||
"The fourth frame node has the correct line.");
|
||||
equal(frame4.getInfo().column, 987,
|
||||
"The fourth frame node has the correct column.");
|
||||
equal(frame4.getInfo().categoryData.toSource(), "({})",
|
||||
"The fourth frame node has the correct category data.");
|
||||
equal(frame4.getInfo().isContent, true,
|
||||
"The fourth frame node has the correct content flag.");
|
||||
|
||||
let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", {
|
||||
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
|
||||
line: 456,
|
||||
isContent: FrameUtils.isContent({
|
||||
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)"
|
||||
})
|
||||
}, false);
|
||||
|
||||
equal(frame5.getInfo().nodeType, "Frame",
|
||||
"The fifth frame node has the correct type.");
|
||||
equal(frame5.getInfo().functionName, "hello/<.world",
|
||||
"The fifth frame node has the correct function name.");
|
||||
equal(frame5.getInfo().fileName, "baz.js",
|
||||
"The fifth frame node has the correct file name.");
|
||||
equal(frame5.getInfo().hostName, "bar",
|
||||
"The fifth frame node has the correct host name.");
|
||||
equal(frame5.getInfo().url, "http://bar/baz.js",
|
||||
"The fifth frame node has the correct url.");
|
||||
equal(frame5.getInfo().line, 123,
|
||||
"The fifth frame node has the correct line.");
|
||||
equal(frame5.getInfo().column, 987,
|
||||
"The fifth frame node has the correct column.");
|
||||
equal(frame5.getInfo().categoryData.toSource(), "({})",
|
||||
"The fifth frame node has the correct category data.");
|
||||
equal(frame5.getInfo().isContent, false,
|
||||
"The fifth frame node has the correct content flag.");
|
||||
|
||||
let frame6 = new FrameNode("Foo::Bar::Baz", {
|
||||
location: "Foo::Bar::Baz",
|
||||
line: 456,
|
||||
category: CATEGORY_OTHER,
|
||||
isContent: FrameUtils.isContent({
|
||||
let frames = [
|
||||
new FrameNode("hello/<.world (http://foo/bar.js:123:987)", compute({
|
||||
location: "hello/<.world (http://foo/bar.js:123:987)",
|
||||
line: 456,
|
||||
}), false),
|
||||
new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", compute({
|
||||
location: "hello/<.world (http://foo/bar.js#baz:123:987)",
|
||||
line: 456,
|
||||
}), false),
|
||||
new FrameNode("hello/<.world (http://foo/#bar:123:987)", compute({
|
||||
location: "hello/<.world (http://foo/#bar:123:987)",
|
||||
line: 456,
|
||||
}), false),
|
||||
new FrameNode("hello/<.world (http://foo/:123:987)", compute({
|
||||
location: "hello/<.world (http://foo/:123:987)",
|
||||
line: 456,
|
||||
}), false),
|
||||
new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", compute({
|
||||
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
|
||||
line: 456,
|
||||
}), false),
|
||||
new FrameNode("Foo::Bar::Baz", compute({
|
||||
location: "Foo::Bar::Baz",
|
||||
category: CATEGORY_OTHER
|
||||
})
|
||||
}, false);
|
||||
line: 456,
|
||||
category: CATEGORY_OTHER,
|
||||
}), false),
|
||||
new FrameNode("EnterJIT", compute({
|
||||
location: "EnterJIT",
|
||||
}), false),
|
||||
new FrameNode("chrome://browser/content/content.js", compute({
|
||||
location: "chrome://browser/content/content.js",
|
||||
line: 456,
|
||||
column: 123
|
||||
}), false),
|
||||
new FrameNode("hello/<.world (resource://gre/foo.js:123:434)", compute({
|
||||
location: "hello/<.world (resource://gre/foo.js:123:434)",
|
||||
line: 456
|
||||
}), false),
|
||||
new FrameNode("main (http://localhost:8888/file.js:123:987)", compute({
|
||||
location: "main (http://localhost:8888/file.js:123:987)",
|
||||
line: 123,
|
||||
}), false),
|
||||
new FrameNode("main (resource://gre/modules/devtools/timeline.js:123)", compute({
|
||||
location: "main (resource://gre/modules/devtools/timeline.js:123)",
|
||||
}), false),
|
||||
];
|
||||
|
||||
equal(frame6.getInfo().nodeType, "Frame",
|
||||
"The sixth frame node has the correct type.");
|
||||
equal(frame6.getInfo().functionName, "Foo::Bar::Baz",
|
||||
"The sixth frame node has the correct function name.");
|
||||
equal(frame6.getInfo().fileName, null,
|
||||
"The sixth frame node has the correct file name.");
|
||||
equal(frame6.getInfo().hostName, null,
|
||||
"The sixth frame node has the correct host name.");
|
||||
equal(frame6.getInfo().url, null,
|
||||
"The sixth frame node has the correct url.");
|
||||
equal(frame6.getInfo().line, 456,
|
||||
"The sixth frame node has the correct line.");
|
||||
equal(frame6.getInfo().categoryData.abbrev, "other",
|
||||
"The sixth frame node has the correct category data.");
|
||||
equal(frame6.getInfo().isContent, false,
|
||||
"The sixth frame node has the correct content flag.");
|
||||
let fields = ["nodeType", "functionName", "fileName", "hostName", "url", "line", "column", "categoryData.abbrev", "isContent", "port"]
|
||||
let expected = [
|
||||
// nodeType, functionName, fileName, hostName, url, line, column, categoryData.abbrev, isContent, port
|
||||
["Frame", "hello/<.world", "bar.js", "foo", "http://foo/bar.js", 123, 987, void 0, true],
|
||||
["Frame", "hello/<.world", "bar.js", "foo", "http://foo/bar.js#baz", 123, 987, void 0, true],
|
||||
["Frame", "hello/<.world", "/", "foo", "http://foo/#bar", 123, 987, void 0, true],
|
||||
["Frame", "hello/<.world", "/", "foo", "http://foo/", 123, 987, void 0, true],
|
||||
["Frame", "hello/<.world", "baz.js", "bar", "http://bar/baz.js", 123, 987, "other", false],
|
||||
["Frame", "Foo::Bar::Baz", null, null, null, 456, void 0, "other", false],
|
||||
["Frame", "EnterJIT", null, null, null, null, null, "js", false],
|
||||
["Frame", "chrome://browser/content/content.js", null, null, null, 456, null, "other", false],
|
||||
["Frame", "hello/<.world", "foo.js", null, "resource://gre/foo.js", 123, 434, "other", false],
|
||||
["Frame", "main", "file.js", "localhost", "http://localhost:8888/file.js", 123, 987, null, true, 8888],
|
||||
["Frame", "main", "timeline.js", null, "resource://gre/modules/devtools/timeline.js", 123, null, "tools", false]
|
||||
];
|
||||
|
||||
let frame7 = new FrameNode("EnterJIT", {
|
||||
location: "EnterJIT",
|
||||
isContent: FrameUtils.isContent({
|
||||
location: "EnterJIT"
|
||||
})
|
||||
}, false);
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
let info = frames[i].getInfo();
|
||||
let expect = expected[i];
|
||||
|
||||
equal(frame7.getInfo().nodeType, "Frame",
|
||||
"The seventh frame node has the correct type.");
|
||||
equal(frame7.getInfo().functionName, "EnterJIT",
|
||||
"The seventh frame node has the correct function name.");
|
||||
equal(frame7.getInfo().fileName, null,
|
||||
"The seventh frame node has the correct file name.");
|
||||
equal(frame7.getInfo().hostName, null,
|
||||
"The seventh frame node has the correct host name.");
|
||||
equal(frame7.getInfo().url, null,
|
||||
"The seventh frame node has the correct url.");
|
||||
equal(frame7.getInfo().line, null,
|
||||
"The seventh frame node has the correct line.");
|
||||
equal(frame7.getInfo().column, null,
|
||||
"The seventh frame node has the correct column.");
|
||||
equal(frame7.getInfo().categoryData.abbrev, "js",
|
||||
"The seventh frame node has the correct category data.");
|
||||
equal(frame7.getInfo().isContent, false,
|
||||
"The seventh frame node has the correct content flag.");
|
||||
|
||||
let frame8 = new FrameNode("chrome://browser/content/content.js", {
|
||||
location: "chrome://browser/content/content.js",
|
||||
line: 456,
|
||||
column: 123
|
||||
}, false);
|
||||
|
||||
equal(frame8.getInfo().hostName, null,
|
||||
"The eighth frame node has the correct host name.");
|
||||
|
||||
let frame9 = new FrameNode("hello/<.world (resource://gre/foo.js:123:434)", {
|
||||
location: "hello/<.world (resource://gre/foo.js:123:434)",
|
||||
line: 456
|
||||
}, false);
|
||||
|
||||
equal(frame9.getInfo().hostName, null,
|
||||
"The ninth frame node has the correct host name.");
|
||||
|
||||
let frame10 = new FrameNode("main (http://localhost:8888/file.js:123:987)", {
|
||||
location: "main (http://localhost:8888/file.js:123:987)",
|
||||
line: 123,
|
||||
isContent: FrameUtils.isContent({
|
||||
location: "main (http://localhost:8888/file.js:123:987)"
|
||||
})
|
||||
}, false);
|
||||
|
||||
equal(frame10.getInfo().nodeType, "Frame",
|
||||
"The tenth frame node has the correct type.");
|
||||
equal(frame10.getInfo().functionName, "main",
|
||||
"The tenth frame node has the correct function name.");
|
||||
equal(frame10.getInfo().fileName, "file.js",
|
||||
"The tenth frame node has the correct file name.");
|
||||
equal(frame10.getInfo().hostName, "localhost",
|
||||
"The tenth frame node has the correct host name.");
|
||||
equal(frame10.getInfo().url, "http://localhost:8888/file.js",
|
||||
"The tenth frame node has the correct url.");
|
||||
equal(frame10.getInfo().line, 123,
|
||||
"The tenth frame node has the correct line.");
|
||||
equal(frame10.getInfo().column, 987,
|
||||
"The tenth frame node has the correct column.");
|
||||
equal(frame10.getInfo().isContent, true,
|
||||
"The tenth frame node has the correct content flag.");
|
||||
equal(frame10.getInfo().host, "localhost:8888",
|
||||
"The tenth frame node has the correct host.");
|
||||
equal(frame10.getInfo().port, 8888,
|
||||
"The tenth frame node has the correct port.");
|
||||
for (let j = 0; j < fields.length; j++) {
|
||||
let field = fields[j];
|
||||
let value = field === "categoryData.abbrev" ? info.categoryData.abbrev : info[field];
|
||||
equal(value, expect[j], `${field} for frame #${i} is correct: ${expect[j]}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that when displaying only content nodes, platform nodes are generalized.
|
||||
*/
|
||||
|
||||
let { CATEGORY_MASK } = devtools.require("devtools/performance/global");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let url = (n) => `http://content/${n}`;
|
||||
|
||||
// Create a root node from a given samples array.
|
||||
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)");
|
||||
|
||||
/*
|
||||
* should have a tree like:
|
||||
* root
|
||||
* - (Tools)
|
||||
* - A
|
||||
* - B
|
||||
* - C
|
||||
* - D
|
||||
* - E
|
||||
* - F
|
||||
* - (Tools)
|
||||
*/
|
||||
|
||||
// Test the root node.
|
||||
|
||||
equal(root.calls.length, 2, "root has 2 children");
|
||||
ok(getFrameNodePath(root, url("A")), "root has content child");
|
||||
ok(getFrameNodePath(root, "9000"), "root has platform generalized child from Chrome JS");
|
||||
equal(getFrameNodePath(root, "9000").calls.length, 0, "platform generalized child is a leaf.");
|
||||
|
||||
ok(getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 9000`),
|
||||
"a second leaf of the generalized Chrome JS exists.");
|
||||
|
||||
equal(getFrameNodePath(root, "9000").category,
|
||||
getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 9000`).category,
|
||||
"generalized frames of same type are duplicated in top-down view");
|
||||
});
|
||||
|
||||
let gThread = synthesizeProfileForTest([{
|
||||
time: 5,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "http://content/A" },
|
||||
{ location: "http://content/B" },
|
||||
{ location: "http://content/C" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 6,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "http://content/A" },
|
||||
{ location: "http://content/B" },
|
||||
{ location: "fn (resource://loader.js -> resource:///modules/devtools/timeline.js)" },
|
||||
{ location: "http://content/D" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 6 + 7,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "http://content/A" },
|
||||
{ location: "http://content/E" },
|
||||
{ location: "http://content/F" },
|
||||
{ location: "fn (resource://loader.js -> resource://gre/modules/devtools/promise.js)" }
|
||||
]
|
||||
}, {
|
||||
time: 5 + 20,
|
||||
frames: [
|
||||
{ location: "(root)" },
|
||||
{ location: "somefn (resource://loader.js -> resource:///modules/devtools/framerate.js)" }
|
||||
]
|
||||
}]);
|
|
@ -5,7 +5,9 @@ tail =
|
|||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
|
||||
[test_profiler-categories.js]
|
||||
[test_frame-utils-01.js]
|
||||
[test_frame-utils-02.js]
|
||||
[test_tree-model-01.js]
|
||||
[test_tree-model-02.js]
|
||||
[test_tree-model-03.js]
|
||||
|
@ -14,3 +16,4 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
|||
[test_tree-model-06.js]
|
||||
[test_tree-model-07.js]
|
||||
[test_tree-model-08.js]
|
||||
[test_tree-model-09.js]
|
||||
|
|
|
@ -82,6 +82,7 @@ category.network=Network
|
|||
category.graphics=Graphics
|
||||
category.storage=Storage
|
||||
category.events=Input & Events
|
||||
category.tools=Tools
|
||||
|
||||
# LOCALIZATION NOTE (graphs.ms):
|
||||
# This string is displayed in the call tree after units of time in milliseconds.
|
||||
|
|
|
@ -15,7 +15,7 @@ TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName,
|
|||
MOZ_COUNT_CTOR(TimelineMarker);
|
||||
MOZ_ASSERT(aName);
|
||||
aDocShell->Now(&mTime);
|
||||
if (aMetaData == TRACING_INTERVAL_START) {
|
||||
if (aMetaData == TRACING_INTERVAL_START || aMetaData == TRACING_TIMESTAMP) {
|
||||
CaptureStack();
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,9 @@ TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName,
|
|||
MOZ_COUNT_CTOR(TimelineMarker);
|
||||
MOZ_ASSERT(aName);
|
||||
aDocShell->Now(&mTime);
|
||||
if (aMetaData == TRACING_INTERVAL_START && aStackRequest != NO_STACK) {
|
||||
if ((aMetaData == TRACING_INTERVAL_START ||
|
||||
aMetaData == TRACING_TIMESTAMP) &&
|
||||
aStackRequest != NO_STACK) {
|
||||
CaptureStack();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2997,6 +2997,7 @@ nsDocShell::PopProfileTimelineMarkers(
|
|||
marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
|
||||
marker->mStart = startPayload->GetTime();
|
||||
marker->mEnd = startPayload->GetTime();
|
||||
marker->mStack = startPayload->GetStack();
|
||||
startPayload->AddDetails(aCx, *marker);
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -1065,7 +1065,6 @@ public:
|
|||
const nsAString& aCause)
|
||||
: TimelineMarker(aDocShell, "TimeStamp", aMetaData, aCause)
|
||||
{
|
||||
CaptureStack();
|
||||
MOZ_ASSERT(aMetaData == TRACING_TIMESTAMP);
|
||||
}
|
||||
|
||||
|
@ -1075,7 +1074,6 @@ public:
|
|||
if (!GetCause().IsEmpty()) {
|
||||
aMarker.mCauseName.Construct(GetCause());
|
||||
}
|
||||
aMarker.mEndStack = GetStack();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import java.util.HashSet;
|
|||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONArray;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
@ -113,9 +114,21 @@ public class DoorHangerPopup extends AnchoredPopup
|
|||
final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
|
||||
|
||||
config.setMessage(json.getString("message"));
|
||||
config.appendButtonsFromJSON(json.getJSONArray("buttons"));
|
||||
config.setOptions(json.getJSONObject("options"));
|
||||
|
||||
final JSONArray buttonArray = json.getJSONArray("buttons");
|
||||
int numButtons = buttonArray.length();
|
||||
if (numButtons > 2) {
|
||||
Log.e(LOGTAG, "Doorhanger can have a maximum of two buttons!");
|
||||
numButtons = 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numButtons; i++) {
|
||||
final JSONObject buttonJSON = buttonArray.getJSONObject(i);
|
||||
final boolean isPositive = buttonJSON.optBoolean("positive", false);
|
||||
config.setButton(buttonJSON.getString("label"), buttonJSON.getInt("callback"), isPositive);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
|
|||
import org.mozilla.gecko.mozglue.JNITarget;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
|
@ -853,7 +854,7 @@ public class Tabs implements GeckoEventListener {
|
|||
boolean external = (flags & LOADURL_EXTERNAL) != 0;
|
||||
|
||||
final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(mAppContext);
|
||||
final boolean isPrivatePref = sharedPrefs.getBoolean("android.not_a_preference.openExternalURLsPrivately", false);
|
||||
final boolean isPrivatePref = sharedPrefs.getBoolean(GeckoPreferences.PREFS_OPEN_URLS_IN_PRIVATE, false);
|
||||
if (isPrivatePref && external) {
|
||||
isPrivate = true;
|
||||
}
|
||||
|
|
|
@ -554,7 +554,7 @@ public class BrowserSearch extends HomeFragment
|
|||
ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>();
|
||||
for (int i = 0; i < engines.length(); i++) {
|
||||
final JSONObject engineJSON = engines.getJSONObject(i);
|
||||
final SearchEngine engine = new SearchEngine(engineJSON);
|
||||
final SearchEngine engine = new SearchEngine((Context) getActivity(), engineJSON);
|
||||
|
||||
if (engine.name.equals(suggestEngine) && suggestTemplate != null) {
|
||||
// Suggest engine should be at the front of the list.
|
||||
|
|
|
@ -6,11 +6,14 @@
|
|||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -25,7 +28,7 @@ public class SearchEngine {
|
|||
private final Bitmap icon;
|
||||
private volatile List<String> suggestions = new ArrayList<String>(); // Never null.
|
||||
|
||||
public SearchEngine(JSONObject engineJSON) throws JSONException {
|
||||
public SearchEngine(final Context context, final JSONObject engineJSON) throws JSONException {
|
||||
if (engineJSON == null) {
|
||||
throw new IllegalArgumentException("Can't instantiate SearchEngine from null JSON.");
|
||||
}
|
||||
|
@ -40,10 +43,14 @@ public class SearchEngine {
|
|||
final String iconURI = getString(engineJSON, "iconURI");
|
||||
if (iconURI == null) {
|
||||
Log.w(LOG_TAG, "iconURI is null for search engine " + this.name);
|
||||
this.icon = null;
|
||||
return;
|
||||
}
|
||||
this.icon = BitmapUtils.getBitmapFromDataURI(iconURI);
|
||||
final Bitmap tempIcon = BitmapUtils.getBitmapFromDataURI(iconURI);
|
||||
|
||||
this.icon = (tempIcon != null) ? tempIcon : getDefaultFavicon(context);
|
||||
}
|
||||
|
||||
private Bitmap getDefaultFavicon(final Context context) {
|
||||
return BitmapFactory.decodeResource(context.getResources(), R.drawable.favicon_search);
|
||||
}
|
||||
|
||||
private static String getString(JSONObject data, String key) throws JSONException {
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.widget.FaviconView;
|
||||
|
@ -94,9 +95,9 @@ public class SearchEngineBar extends TwoWayView
|
|||
view = convertView;
|
||||
}
|
||||
|
||||
final FaviconView faviconView = (FaviconView) view.findViewById(R.id.search_engine_icon);
|
||||
final ImageView faviconView = (ImageView) view.findViewById(R.id.search_engine_icon);
|
||||
final SearchEngine searchEngine = searchEngines.get(position);
|
||||
faviconView.updateAndScaleImage(searchEngine.getIcon(), searchEngine.getEngineIdentifier());
|
||||
faviconView.setImageBitmap(searchEngine.getIcon());
|
||||
|
||||
final View container = view.findViewById(R.id.search_engine_icon_container);
|
||||
final String desc = getResources().getString(R.string.search_bar_item_desc, searchEngine.getEngineIdentifier());
|
||||
|
|
|
@ -127,7 +127,7 @@ OnSharedPreferenceChangeListener
|
|||
private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
|
||||
private static final String PREFS_TRACKING_PROTECTION = "privacy.trackingprotection.enabled";
|
||||
private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
|
||||
private static final String PREFS_OPEN_URLS_IN_PRIVATE = NON_PREF_PREFIX + "openExternalURLsPrivately";
|
||||
public static final String PREFS_OPEN_URLS_IN_PRIVATE = NON_PREF_PREFIX + "openExternalURLsPrivately";
|
||||
|
||||
private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
|
||||
|
||||
|
@ -703,9 +703,9 @@ OnSharedPreferenceChangeListener
|
|||
preferences.removePreference(pref);
|
||||
i--;
|
||||
continue;
|
||||
} else if (AppConstants.RELEASE_BUILD &&
|
||||
} else if (!AppConstants.NIGHTLY_BUILD &&
|
||||
PREFS_OPEN_URLS_IN_PRIVATE.equals(key)) {
|
||||
// Remove UI for opening external links in private borwsing onrelease builds.
|
||||
// Remove UI for opening external links in private browsing on non-Nightly builds.
|
||||
preferences.removePreference(pref);
|
||||
i--;
|
||||
continue;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="false">
|
||||
<shape>
|
||||
<solid android:color="@color/toolbar_menu_dark_grey" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/toolbar_grey_pressed" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="false">
|
||||
<shape>
|
||||
<solid android:color="@color/link_blue"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<solid android:color="@color/link_blue_pressed" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
|
@ -0,0 +1,33 @@
|
|||
<?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/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView android:id="@+id/doorhanger_message"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
|
||||
|
||||
<LinearLayout android:id="@+id/doorhanger_inputs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"
|
||||
android:gravity="right"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<CheckBox android:id="@+id/doorhanger_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"
|
||||
android:checked="true"
|
||||
android:textColor="@color/placeholder_active_grey"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -5,51 +5,41 @@
|
|||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<LinearLayout android:layout_width="wrap_content"
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/doorhanger_padding">
|
||||
|
||||
<ImageView android:id="@+id/doorhanger_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="@dimen/doorhanger_padding"
|
||||
android:layout_width="@dimen/doorhanger_icon_size"
|
||||
android:layout_height="@dimen/doorhanger_icon_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingRight="@dimen/doorhanger_section_padding_small"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView android:id="@+id/doorhanger_message"
|
||||
android:focusable="true"
|
||||
<ViewStub android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:id="@+id/doorhanger_inputs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="right"
|
||||
android:paddingLeft="@dimen/doorhanger_padding"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<CheckBox android:id="@+id/doorhanger_checkbox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:textColor="@color/placeholder_active_grey"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<View android:id="@+id/divider_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/divider_light"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout android:id="@+id/doorhanger_buttons"
|
||||
android:layout_width="match_parent"
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"/>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button android:id="@+id/doorhanger_button_negative"
|
||||
style="@style/Widget.Doorhanger.Button"
|
||||
android:textColor="@android:color/black"
|
||||
android:background="@drawable/action_bar_button_negative"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<Button android:id="@+id/doorhanger_button_positive"
|
||||
style="@style/Widget.Doorhanger.Button"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="@drawable/action_bar_button_positive"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View android:id="@+id/divider_doorhanger"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<?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/. -->
|
||||
|
||||
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dip"
|
||||
android:textColor="@color/placeholder_active_grey"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/action_bar_button"/>
|
|
@ -3,66 +3,30 @@
|
|||
- 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/. -->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_small"
|
||||
android:paddingLeft="@dimen/doorhanger_section_padding_small">
|
||||
<TextView android:id="@+id/doorhanger_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_small"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/>
|
||||
|
||||
<ImageView android:id="@+id/doorhanger_icon"
|
||||
android:layout_width="@dimen/doorhanger_icon_size"
|
||||
android:layout_height="@dimen/doorhanger_icon_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingRight="@dimen/doorhanger_section_padding_small"
|
||||
android:src="@drawable/icon_key"/>
|
||||
<TextView android:id="@+id/doorhanger_message"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_large"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView android:id="@+id/doorhanger_link"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:textColor="@color/link_blue"
|
||||
android:layout_marginBottom="@dimen/doorhanger_section_padding_small"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView android:id="@+id/doorhanger_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_small"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/>
|
||||
|
||||
<TextView android:id="@+id/doorhanger_message"
|
||||
android:focusable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_large"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
|
||||
|
||||
<TextView android:id="@+id/doorhanger_link"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:textColor="@color/link_blue"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_large"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View android:id="@+id/divider_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/divider_light"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout android:id="@+id/doorhanger_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<View android:id="@+id/divider_doorhanger"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/divider_light"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</merge>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -25,6 +25,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/doorhanger_login_edit_toggle"
|
||||
android:paddingTop="@dimen/doorhanger_padding"/>
|
||||
android:layout_marginTop="@dimen/doorhanger_padding"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
View (browser_search at the time of this writing). -->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/search_engine_icon_container"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="72dp"
|
||||
|
@ -19,12 +18,11 @@
|
|||
|
||||
<!-- Width & height are set to make the Favicons as sharp as possible
|
||||
based on asset size. -->
|
||||
<org.mozilla.gecko.widget.FaviconView
|
||||
<ImageView
|
||||
android:id="@+id/search_engine_icon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
gecko:defaultFaviconDrawable="@drawable/favicon_search_large"
|
||||
gecko:defaultFaviconKey="@string/favicon_search_large_key"/>
|
||||
android:scaleType="fitCenter"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
- 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/. -->
|
||||
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:gecko="http://schemas.android.com/apk/res-auto">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Remove favicon_search_large_key when landing bug 1158275. -->
|
||||
<org.mozilla.gecko.widget.FaviconView android:id="@+id/suggestion_icon"
|
||||
android:layout_width="@dimen/favicon_bg"
|
||||
android:layout_height="@dimen/favicon_bg"
|
||||
|
@ -14,9 +12,7 @@
|
|||
android:layout_marginRight="10dip"
|
||||
android:layout_centerVertical="true"
|
||||
android:minWidth="@dimen/favicon_bg"
|
||||
android:minHeight="@dimen/favicon_bg"
|
||||
gecko:defaultFaviconDrawable="@drawable/favicon_search_large"
|
||||
gecko:defaultFaviconKey="@string/favicon_search_large_key"/>
|
||||
android:minHeight="@dimen/favicon_bg"/>
|
||||
|
||||
<org.mozilla.gecko.widget.FlowLayout android:id="@+id/suggestion_layout"
|
||||
android:layout_toRightOf="@id/suggestion_icon"
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
|
||||
android:text="@string/identity_run_by"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_small"/>
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"/>
|
||||
|
||||
<TextView android:id="@+id/owner"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -60,7 +60,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_small"/>
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_small"/>
|
||||
|
||||
</LinearLayout>
|
||||
<TextView android:id="@+id/site_settings_link"
|
||||
|
@ -68,8 +68,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:textColor="@color/link_blue"
|
||||
android:paddingTop="@dimen/doorhanger_section_padding_large"
|
||||
android:paddingBottom="@dimen/doorhanger_padding"
|
||||
android:layout_marginTop="@dimen/doorhanger_section_padding_large"
|
||||
android:layout_marginBottom="@dimen/doorhanger_padding"
|
||||
android:text="@string/contextmenu_site_settings"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -187,8 +187,6 @@
|
|||
<declare-styleable name="FaviconView">
|
||||
<attr name="dominantBorderEnabled" format="boolean" />
|
||||
<attr name="overrideScaleType" format="boolean" />
|
||||
<attr name="defaultFaviconDrawable" format="reference" />
|
||||
<attr name="defaultFaviconKey" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="OverlayDialogButton">
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<color name="action_orange">#E66000</color>
|
||||
<color name="action_orange_pressed">#DC5600</color>
|
||||
<color name="link_blue">#0096DD</color>
|
||||
<color name="link_blue_pressed">#0082C6</color>
|
||||
<color name="private_browsing_purple">#CF68FF</color>
|
||||
|
||||
<color name="placeholder_active_grey">#222222</color>
|
||||
|
|
|
@ -281,6 +281,14 @@
|
|||
<item name="android:textSize">18sp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Doorhanger.Button" parent="Widget.BaseButton">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:minHeight">48dp</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<!--
|
||||
TextAppearance
|
||||
Note: Gecko uses light theme as default, while Android uses dark.
|
||||
|
|
|
@ -543,6 +543,4 @@
|
|||
<string name="percent">&percent;</string>
|
||||
|
||||
<string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
|
||||
|
||||
<string name="favicon_search_large_key">DefaultFaviconSearch</string>
|
||||
</resources>
|
||||
|
|
|
@ -202,8 +202,8 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.LOGIN, buttonClickListener);
|
||||
|
||||
// Set buttons.
|
||||
config.appendButton(mContext.getString(R.string.button_cancel), ButtonType.CANCEL.ordinal());
|
||||
config.appendButton(mContext.getString(R.string.button_copy), ButtonType.COPY.ordinal());
|
||||
config.setButton(mContext.getString(R.string.button_cancel), ButtonType.CANCEL.ordinal(), false);
|
||||
config.setButton(mContext.getString(R.string.button_copy), ButtonType.COPY.ordinal(), true);
|
||||
|
||||
// Set message.
|
||||
String username = ((JSONObject) logins.get(0)).getString("username");
|
||||
|
@ -361,10 +361,10 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
|
|||
|
||||
private void addNotificationButtons(DoorhangerConfig config, boolean blocked) {
|
||||
if (blocked) {
|
||||
config.appendButton(mContext.getString(R.string.disable_protection), ButtonType.DISABLE.ordinal());
|
||||
config.appendButton(mContext.getString(R.string.keep_blocking), ButtonType.KEEP_BLOCKING.ordinal());
|
||||
config.setButton(mContext.getString(R.string.disable_protection), ButtonType.DISABLE.ordinal(), false);
|
||||
config.setButton(mContext.getString(R.string.keep_blocking), ButtonType.KEEP_BLOCKING.ordinal(), true);
|
||||
} else {
|
||||
config.appendButton(mContext.getString(R.string.enable_protection), ButtonType.ENABLE.ordinal());
|
||||
config.setButton(mContext.getString(R.string.enable_protection), ButtonType.ENABLE.ordinal(), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,17 @@
|
|||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -29,12 +37,15 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
|
||||
private static int sSpinnerTextColor = -1;
|
||||
|
||||
private final TextView mMessage;
|
||||
private List<PromptInput> mInputs;
|
||||
private CheckBox mCheckBox;
|
||||
|
||||
public DefaultDoorHanger(Context context, DoorhangerConfig config, Type type) {
|
||||
super(context, config, type);
|
||||
|
||||
mMessage = (TextView) findViewById(R.id.doorhanger_message);
|
||||
|
||||
if (sSpinnerTextColor == -1) {
|
||||
sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only);
|
||||
}
|
||||
|
@ -58,7 +69,12 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
addLink(link.label, link.url, link.delimiter);
|
||||
}
|
||||
|
||||
setButtons(config);
|
||||
addButtonsToLayout(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentResource() {
|
||||
return R.layout.default_doorhanger;
|
||||
}
|
||||
|
||||
private List<PromptInput> getInputs() {
|
||||
|
@ -154,6 +170,30 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
};
|
||||
}
|
||||
|
||||
private void setMessage(String message) {
|
||||
Spanned markupMessage = Html.fromHtml(message);
|
||||
mMessage.setText(markupMessage);
|
||||
}
|
||||
|
||||
private void addLink(String label, String url, String delimiter) {
|
||||
String title = mMessage.getText().toString();
|
||||
SpannableString titleWithLink = new SpannableString(title + delimiter + label);
|
||||
URLSpan linkSpan = new URLSpan(url) {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Tabs.getInstance().loadUrlInTab(getURL());
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent text outside the link from flashing when clicked.
|
||||
ForegroundColorSpan colorSpan = new ForegroundColorSpan(mMessage.getCurrentTextColor());
|
||||
titleWithLink.setSpan(colorSpan, 0, title.length(), 0);
|
||||
|
||||
titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0);
|
||||
mMessage.setText(titleWithLink);
|
||||
mMessage.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
private void styleInput(PromptInput input, View view) {
|
||||
if (input instanceof PromptInput.MenulistInput) {
|
||||
styleDropdownInputs(input, view);
|
||||
|
|
|
@ -7,24 +7,14 @@ package org.mozilla.gecko.widget;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewStub;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
|
||||
public abstract class DoorHanger extends LinearLayout {
|
||||
|
||||
|
@ -46,17 +36,13 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
public void onButtonClick(JSONObject response, DoorHanger doorhanger);
|
||||
}
|
||||
|
||||
protected static final LayoutParams sButtonParams;
|
||||
static {
|
||||
sButtonParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f);
|
||||
}
|
||||
|
||||
private static final String LOGTAG = "GeckoDoorHanger";
|
||||
|
||||
// Divider between doorhangers.
|
||||
private final View mDivider;
|
||||
|
||||
protected final LinearLayout mButtonsContainer;
|
||||
private final Button mNegativeButton;
|
||||
private final Button mPositiveButton;
|
||||
protected final OnButtonClickListener mOnButtonClickListener;
|
||||
|
||||
// The tab this doorhanger is associated with.
|
||||
|
@ -67,8 +53,7 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
|
||||
protected final Type mType;
|
||||
|
||||
private final ImageView mIcon;
|
||||
private final TextView mMessage;
|
||||
protected final ImageView mIcon;
|
||||
|
||||
protected final Context mContext;
|
||||
protected final Resources mResources;
|
||||
|
@ -81,34 +66,32 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
|
||||
protected DoorHanger(Context context, DoorhangerConfig config, Type type) {
|
||||
super(context);
|
||||
|
||||
mContext = context;
|
||||
mResources = context.getResources();
|
||||
mTabId = config.getTabId();
|
||||
mIdentifier = config.getId();
|
||||
|
||||
int resource;
|
||||
switch (type) {
|
||||
case LOGIN:
|
||||
resource = R.layout.login_doorhanger;
|
||||
break;
|
||||
default:
|
||||
resource = R.layout.doorhanger;
|
||||
}
|
||||
|
||||
LayoutInflater.from(context).inflate(resource, this);
|
||||
mDivider = findViewById(R.id.divider_doorhanger);
|
||||
mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
|
||||
mMessage = (TextView) findViewById(R.id.doorhanger_message);
|
||||
|
||||
mType = type;
|
||||
|
||||
mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
|
||||
LayoutInflater.from(context).inflate(R.layout.doorhanger, this);
|
||||
setOrientation(VERTICAL);
|
||||
|
||||
mDivider = findViewById(R.id.divider_doorhanger);
|
||||
mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
|
||||
|
||||
mNegativeButton = (Button) findViewById(R.id.doorhanger_button_negative);
|
||||
mPositiveButton = (Button) findViewById(R.id.doorhanger_button_positive);
|
||||
mOnButtonClickListener = config.getButtonClickListener();
|
||||
|
||||
mDividerColor = mResources.getColor(R.color.divider_light);
|
||||
setOrientation(VERTICAL);
|
||||
|
||||
final ViewStub contentStub = (ViewStub) findViewById(R.id.content);
|
||||
contentStub.setLayoutResource(getContentResource());
|
||||
contentStub.inflate();
|
||||
}
|
||||
|
||||
protected abstract int getContentResource();
|
||||
|
||||
protected abstract void loadConfig(DoorhangerConfig config);
|
||||
|
||||
protected void setOptions(final JSONObject options) {
|
||||
|
@ -125,17 +108,20 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
}
|
||||
}
|
||||
|
||||
protected void setButtons(DoorhangerConfig config) {
|
||||
final JSONArray buttons = config.getButtons();
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
try {
|
||||
final JSONObject buttonObject = buttons.getJSONObject(i);
|
||||
final String label = buttonObject.getString("label");
|
||||
final int callbackId = buttonObject.getInt("callback");
|
||||
addButtonToLayout(label, callbackId);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating doorhanger button", e);
|
||||
}
|
||||
protected void addButtonsToLayout(DoorhangerConfig config) {
|
||||
final DoorhangerConfig.ButtonConfig negativeButtonConfig = config.getNegativeButtonConfig();
|
||||
final DoorhangerConfig.ButtonConfig positiveButtonConfig = config.getPositiveButtonConfig();
|
||||
|
||||
if (negativeButtonConfig != null) {
|
||||
mNegativeButton.setText(negativeButtonConfig.label);
|
||||
mNegativeButton.setOnClickListener(makeOnButtonClickListener(negativeButtonConfig.callback));
|
||||
mNegativeButton.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
if (positiveButtonConfig != null) {
|
||||
mPositiveButton.setText(positiveButtonConfig.label);
|
||||
mPositiveButton.setOnClickListener(makeOnButtonClickListener(positiveButtonConfig.callback));
|
||||
mPositiveButton.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,61 +146,6 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
mIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
protected void setMessage(String message) {
|
||||
Spanned markupMessage = Html.fromHtml(message);
|
||||
mMessage.setText(markupMessage);
|
||||
}
|
||||
|
||||
protected void addLink(String label, String url, String delimiter) {
|
||||
String title = mMessage.getText().toString();
|
||||
SpannableString titleWithLink = new SpannableString(title + delimiter + label);
|
||||
URLSpan linkSpan = new URLSpan(url) {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Tabs.getInstance().loadUrlInTab(getURL());
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent text outside the link from flashing when clicked.
|
||||
ForegroundColorSpan colorSpan = new ForegroundColorSpan(mMessage.getCurrentTextColor());
|
||||
titleWithLink.setSpan(colorSpan, 0, title.length(), 0);
|
||||
|
||||
titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0);
|
||||
mMessage.setText(titleWithLink);
|
||||
mMessage.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a button into the DoorHanger.
|
||||
* @param text Button text
|
||||
* @param id Identifier associated with the button
|
||||
*/
|
||||
private void addButtonToLayout(String text, int id) {
|
||||
final Button button = createButtonInstance(text, id);
|
||||
if (mButtonsContainer.getChildCount() == 0) {
|
||||
// If this is the first button we're adding, make the choices layout visible.
|
||||
mButtonsContainer.setVisibility(View.VISIBLE);
|
||||
// Make the divider above the buttons visible.
|
||||
View divider = findViewById(R.id.divider_buttons);
|
||||
divider.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
// Add a vertical divider between additional buttons.
|
||||
Divider divider = new Divider(getContext(), null);
|
||||
divider.setOrientation(Divider.Orientation.VERTICAL);
|
||||
divider.setBackgroundColor(mDividerColor);
|
||||
mButtonsContainer.addView(divider);
|
||||
}
|
||||
|
||||
mButtonsContainer.addView(button, sButtonParams);
|
||||
}
|
||||
|
||||
protected Button createButtonInstance(String text, int id) {
|
||||
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
|
||||
button.setText(text);
|
||||
button.setOnClickListener(makeOnButtonClickListener(id));
|
||||
return button;
|
||||
}
|
||||
|
||||
protected abstract OnClickListener makeOnButtonClickListener(final int id);
|
||||
|
||||
/*
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.mozilla.gecko.widget.DoorHanger.Type;
|
||||
|
@ -26,6 +23,15 @@ public class DoorhangerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
public static class ButtonConfig {
|
||||
public final String label;
|
||||
public final int callback;
|
||||
|
||||
public ButtonConfig(String label, int callback) {
|
||||
this.label = label;
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
private static final String LOGTAG = "DoorhangerConfig";
|
||||
|
||||
private final int tabId;
|
||||
|
@ -35,7 +41,8 @@ public class DoorhangerConfig {
|
|||
private String message;
|
||||
private JSONObject options;
|
||||
private Link link;
|
||||
private JSONArray buttons = new JSONArray();
|
||||
private ButtonConfig positiveButtonConfig;
|
||||
private ButtonConfig negativeButtonConfig;
|
||||
|
||||
public DoorhangerConfig(Type type, DoorHanger.OnButtonClickListener listener) {
|
||||
// XXX: This should only be used by SiteIdentityPopup doorhangers which
|
||||
|
@ -79,39 +86,27 @@ public class DoorhangerConfig {
|
|||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add buttons from JSON to the Config object.
|
||||
* @param buttons JSONArray of JSONObjects of the form { label: <label>, callback: <callback_id> }
|
||||
*/
|
||||
public void appendButtonsFromJSON(JSONArray buttons) {
|
||||
try {
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
this.buttons.put(buttons.get(i));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error parsing buttons from JSON", e);
|
||||
public void setButton(String label, int callbackId, boolean isPositive) {
|
||||
final ButtonConfig buttonConfig = new ButtonConfig(label, callbackId);
|
||||
if (isPositive) {
|
||||
positiveButtonConfig = buttonConfig;
|
||||
} else {
|
||||
negativeButtonConfig = buttonConfig;
|
||||
}
|
||||
}
|
||||
|
||||
public void appendButton(String label, int callbackId) {
|
||||
final JSONObject button = new JSONObject();
|
||||
try {
|
||||
button.put("label", label);
|
||||
button.put("callback", callbackId);
|
||||
this.buttons.put(button);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating button", e);
|
||||
}
|
||||
public ButtonConfig getPositiveButtonConfig() {
|
||||
return positiveButtonConfig;
|
||||
}
|
||||
|
||||
public ButtonConfig getNegativeButtonConfig() {
|
||||
return negativeButtonConfig;
|
||||
}
|
||||
|
||||
public DoorHanger.OnButtonClickListener getButtonClickListener() {
|
||||
return this.buttonClickListener;
|
||||
}
|
||||
|
||||
public JSONArray getButtons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
public void setLink(String label, String url, String delimiter) {
|
||||
this.link = new Link(label, url, delimiter);
|
||||
}
|
||||
|
|
|
@ -21,19 +21,9 @@ import android.widget.ImageView;
|
|||
* Special version of ImageView for favicons.
|
||||
* Displays solid colour background around Favicon to fill space not occupied by the icon. Colour
|
||||
* selected is the dominant colour of the provided Favicon.
|
||||
*
|
||||
* The default favicon can be overridden by both passing a drawable to the
|
||||
* gecko:defaultFaviconDrawable and a String to gecko:defaultFaviconKey. The String should be a
|
||||
* unique identifier to each image - it is automatically namespaced by prepending the class name to
|
||||
* the given value.
|
||||
*/
|
||||
public class FaviconView extends ImageView {
|
||||
// These values will be used unless overridden by the gecko:defaultFavicon* attributes (see class comment).
|
||||
private static String DEFAULT_FAVICON_KEY = "DefaultFavicon";
|
||||
private static int DEFAULT_FAVICON_RES_ID = R.drawable.favicon_globe;
|
||||
|
||||
private int defaultFaviconDrawableId;
|
||||
private String defaultFaviconKey;
|
||||
private static String DEFAULT_FAVICON_KEY = FaviconView.class.getSimpleName() + "DefaultFavicon";
|
||||
|
||||
private Bitmap mIconBitmap;
|
||||
|
||||
|
@ -92,15 +82,10 @@ public class FaviconView extends ImageView {
|
|||
try {
|
||||
isDominantBorderEnabled = a.getBoolean(R.styleable.FaviconView_dominantBorderEnabled, true);
|
||||
isOverrideScaleTypeEnabled = a.getBoolean(R.styleable.FaviconView_overrideScaleType, true);
|
||||
|
||||
defaultFaviconDrawableId = a.getResourceId(R.styleable.FaviconView_defaultFaviconDrawable, -1);
|
||||
defaultFaviconKey = a.getString(R.styleable.FaviconView_defaultFaviconKey);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
validateAndAdjustDefaultFavicon();
|
||||
|
||||
if (isOverrideScaleTypeEnabled) {
|
||||
setScaleType(ImageView.ScaleType.CENTER);
|
||||
}
|
||||
|
@ -117,26 +102,6 @@ public class FaviconView extends ImageView {
|
|||
mBackgroundRect.left = mBackgroundRect.top = sStrokeWidth * 2.0f;
|
||||
}
|
||||
|
||||
private void validateAndAdjustDefaultFavicon() {
|
||||
if ((defaultFaviconDrawableId < 0 && defaultFaviconKey != null) ||
|
||||
(defaultFaviconDrawableId >= 0 && defaultFaviconKey == null)) {
|
||||
throw new IllegalStateException("defaultFaviconDrawable and defaultFaviconKey both " +
|
||||
"either need to be specified or omitted");
|
||||
}
|
||||
|
||||
if (DEFAULT_FAVICON_KEY.equals(defaultFaviconKey)) {
|
||||
throw new IllegalStateException("defaultFaviconKey cannot be " + DEFAULT_FAVICON_KEY);
|
||||
}
|
||||
|
||||
if (defaultFaviconDrawableId < 0) { // key == null too
|
||||
defaultFaviconDrawableId = DEFAULT_FAVICON_RES_ID;
|
||||
defaultFaviconKey = DEFAULT_FAVICON_KEY;
|
||||
}
|
||||
|
||||
// Avoid potential collisions in the favicon cache keys.
|
||||
defaultFaviconKey = FaviconView.class.getSimpleName() + defaultFaviconKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh){
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
@ -256,8 +221,8 @@ public class FaviconView extends ImageView {
|
|||
// casing it. This means that the icon can be scaled both up and down, and the dominant
|
||||
// color box can used if it is enabled in XML attrs.
|
||||
final Bitmap defaultFaviconBitmap = BitmapFactory.decodeResource(getResources(),
|
||||
defaultFaviconDrawableId);
|
||||
updateAndScaleImage(defaultFaviconBitmap, defaultFaviconKey);
|
||||
R.drawable.favicon_globe);
|
||||
updateAndScaleImage(defaultFaviconBitmap, DEFAULT_FAVICON_KEY);
|
||||
}
|
||||
|
||||
private void showNoImage() {
|
||||
|
|
|
@ -7,12 +7,12 @@ package org.mozilla.gecko.widget;
|
|||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -27,17 +27,16 @@ import ch.boye.httpclientandroidlib.util.TextUtils;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONArray;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.toolbar.SiteIdentityPopup;
|
||||
|
||||
public class LoginDoorHanger extends DoorHanger {
|
||||
private static final String LOGTAG = "LoginDoorHanger";
|
||||
private enum ActionType { EDIT, SELECT }
|
||||
|
||||
private final TextView mTitle;
|
||||
private final TextView mMessage;
|
||||
private final TextView mLink;
|
||||
private int mCallbackID;
|
||||
|
||||
|
@ -45,16 +44,31 @@ public class LoginDoorHanger extends DoorHanger {
|
|||
super(context, config, Type.LOGIN);
|
||||
|
||||
mTitle = (TextView) findViewById(R.id.doorhanger_title);
|
||||
mMessage = (TextView) findViewById(R.id.doorhanger_message);
|
||||
mLink = (TextView) findViewById(R.id.doorhanger_link);
|
||||
mIcon.setImageResource(R.drawable.icon_key);
|
||||
mIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
loadConfig(config);
|
||||
}
|
||||
|
||||
private void setMessage(String message) {
|
||||
Spanned markupMessage = Html.fromHtml(message);
|
||||
mMessage.setText(markupMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadConfig(DoorhangerConfig config) {
|
||||
setOptions(config.getOptions());
|
||||
setMessage(config.getMessage());
|
||||
setButtons(config);
|
||||
// Store the positive callback id for nested dialogs that need the same callback id.
|
||||
mCallbackID = config.getPositiveButtonConfig().callback;
|
||||
addButtonsToLayout(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getContentResource() {
|
||||
return R.layout.login_doorhanger;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -89,13 +103,6 @@ public class LoginDoorHanger extends DoorHanger {
|
|||
addActionText(actionText);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Button createButtonInstance(final String text, final int id) {
|
||||
// HACK: Confirm button will the the rightmost/last button added. Bug 1147064 should add differentiation of the two.
|
||||
mCallbackID = id;
|
||||
return super.createButtonInstance(text, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OnClickListener makeOnButtonClickListener(final int id) {
|
||||
return new Button.OnClickListener() {
|
||||
|
@ -207,7 +214,7 @@ public class LoginDoorHanger extends DoorHanger {
|
|||
public void onClick(DialogInterface dialog, int which) {
|
||||
final JSONObject response = new JSONObject();
|
||||
try {
|
||||
response.put("callback", SiteIdentityPopup.ButtonType.COPY.ordinal());
|
||||
response.put("callback", mCallbackID);
|
||||
response.put("password", passwords[which]);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error making login select dialog JSON", e);
|
||||
|
|
|
@ -29,17 +29,18 @@ var OfflineApps = {
|
|||
|
||||
let strings = Strings.browser;
|
||||
let buttons = [{
|
||||
label: strings.GetStringFromName("offlineApps.allow"),
|
||||
callback: function() {
|
||||
OfflineApps.allowSite(aContentWindow.document);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: strings.GetStringFromName("offlineApps.dontAllow2"),
|
||||
callback: function(aChecked) {
|
||||
if (aChecked)
|
||||
OfflineApps.disallowSite(aContentWindow.document);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: strings.GetStringFromName("offlineApps.allow"),
|
||||
callback: function() {
|
||||
OfflineApps.allowSite(aContentWindow.document);
|
||||
},
|
||||
positive: true
|
||||
}];
|
||||
|
||||
let requestor = BrowserApp.manifest ? "'" + BrowserApp.manifest.name + "'" : host;
|
||||
|
|
|
@ -27,16 +27,6 @@ var PluginHelper = {
|
|||
let message = Strings.browser.formatStringFromName("clickToPlayPlugins.message2",
|
||||
[uri.host], 1);
|
||||
let buttons = [
|
||||
{
|
||||
label: Strings.browser.GetStringFromName("clickToPlayPlugins.activate"),
|
||||
callback: function(aChecked) {
|
||||
// If the user checked "Don't ask again", make a permanent exception
|
||||
if (aChecked)
|
||||
Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
|
||||
PluginHelper.playAllPlugins(aTab.browser.contentWindow);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: Strings.browser.GetStringFromName("clickToPlayPlugins.dontActivate"),
|
||||
callback: function(aChecked) {
|
||||
|
@ -46,6 +36,17 @@ var PluginHelper = {
|
|||
|
||||
// Other than that, do nothing
|
||||
}
|
||||
},
|
||||
{
|
||||
label: Strings.browser.GetStringFromName("clickToPlayPlugins.activate"),
|
||||
callback: function(aChecked) {
|
||||
// If the user checked "Don't ask again", make a permanent exception
|
||||
if (aChecked)
|
||||
Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
|
||||
PluginHelper.playAllPlugins(aTab.browser.contentWindow);
|
||||
},
|
||||
positive: true
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -113,7 +113,8 @@ var WebrtcUI = {
|
|||
allowedDevices.AppendElement(videoDevices[videoId]);
|
||||
|
||||
Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}];
|
||||
},
|
||||
|
||||
|
|
|
@ -3064,7 +3064,8 @@ var LightWeightThemeWebInstaller = {
|
|||
label: allowButtonText,
|
||||
callback: function () {
|
||||
LightWeightThemeWebInstaller._install(data);
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}];
|
||||
|
||||
NativeWindow.doorhanger.show(message, "Personas", buttons, BrowserApp.selectedTab.id);
|
||||
|
@ -6259,7 +6260,8 @@ var XPInstallObserver = {
|
|||
// Kick off the install
|
||||
installInfo.install();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}];
|
||||
}
|
||||
NativeWindow.doorhanger.show(message, aTopic, buttons, tab.id);
|
||||
|
@ -6353,7 +6355,8 @@ var XPInstallObserver = {
|
|||
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
|
||||
appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}];
|
||||
|
||||
let message = Strings.browser.GetStringFromName("notificationRestart.normal");
|
||||
|
@ -6648,6 +6651,13 @@ var PopupBlockerObserver = {
|
|||
.replace("#2", popupCount);
|
||||
|
||||
let buttons = [
|
||||
{
|
||||
label: strings.GetStringFromName("popup.dontShow"),
|
||||
callback: function(aChecked) {
|
||||
if (aChecked)
|
||||
PopupBlockerObserver.allowPopupsForSite(false);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: strings.GetStringFromName("popup.show"),
|
||||
callback: function(aChecked) {
|
||||
|
@ -6656,14 +6666,8 @@ var PopupBlockerObserver = {
|
|||
PopupBlockerObserver.allowPopupsForSite(true);
|
||||
|
||||
PopupBlockerObserver.showPopupsForSite();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: strings.GetStringFromName("popup.dontShow"),
|
||||
callback: function(aChecked) {
|
||||
if (aChecked)
|
||||
PopupBlockerObserver.allowPopupsForSite(false);
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -6765,13 +6769,7 @@ var IndexedDB = {
|
|||
observer.observe(null, responseTopic, Ci.nsIPermissionManager.UNKNOWN_ACTION);
|
||||
}
|
||||
|
||||
let buttons = [{
|
||||
label: strings.GetStringFromName("offlineApps.allow"),
|
||||
callback: function() {
|
||||
clearTimeout(timeoutId);
|
||||
observer.observe(null, responseTopic, Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
}
|
||||
},
|
||||
let buttons = [
|
||||
{
|
||||
label: strings.GetStringFromName("offlineApps.dontAllow2"),
|
||||
callback: function(aChecked) {
|
||||
|
@ -6779,6 +6777,14 @@ var IndexedDB = {
|
|||
let action = aChecked ? Ci.nsIPermissionManager.DENY_ACTION : Ci.nsIPermissionManager.UNKNOWN_ACTION;
|
||||
observer.observe(null, responseTopic, action);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: strings.GetStringFromName("offlineApps.allow"),
|
||||
callback: function() {
|
||||
clearTimeout(timeoutId);
|
||||
observer.observe(null, responseTopic, Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
},
|
||||
positive: true
|
||||
}];
|
||||
|
||||
let options = { checkbox: Strings.browser.GetStringFromName("offlineApps.dontAskAgain") };
|
||||
|
|
|
@ -99,6 +99,16 @@ ContentPermissionPrompt.prototype = {
|
|||
let entityName = kEntities[perm.type];
|
||||
|
||||
let buttons = [{
|
||||
label: browserBundle.GetStringFromName(entityName + ".dontAllow"),
|
||||
callback: function(aChecked) {
|
||||
// If the user checked "Don't ask again", make a permanent exception
|
||||
if (aChecked)
|
||||
Services.perms.addFromPrincipal(request.principal, access, Ci.nsIPermissionManager.DENY_ACTION);
|
||||
|
||||
request.cancel();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: browserBundle.GetStringFromName(entityName + ".allow"),
|
||||
callback: function(aChecked) {
|
||||
// If the user checked "Don't ask again", make a permanent exception
|
||||
|
@ -110,17 +120,8 @@ ContentPermissionPrompt.prototype = {
|
|||
}
|
||||
|
||||
request.allow();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: browserBundle.GetStringFromName(entityName + ".dontAllow"),
|
||||
callback: function(aChecked) {
|
||||
// If the user checked "Don't ask again", make a permanent exception
|
||||
if (aChecked)
|
||||
Services.perms.addFromPrincipal(request.principal, access, Ci.nsIPermissionManager.DENY_ACTION);
|
||||
|
||||
request.cancel();
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}];
|
||||
|
||||
let requestor = chromeWin.BrowserApp.manifest ? "'" + chromeWin.BrowserApp.manifest.name + "'" : request.principal.URI.host;
|
||||
|
|
|
@ -225,7 +225,8 @@ LoginManagerPrompter.prototype = {
|
|||
}
|
||||
pwmgr.addLogin(aLogin);
|
||||
promptHistogram.add(PROMPT_ADD);
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -282,7 +283,8 @@ LoginManagerPrompter.prototype = {
|
|||
callback: function() {
|
||||
self._updateLogin(aOldLogin, aNewPassword);
|
||||
promptHistogram.add(PROMPT_UPDATE);
|
||||
}
|
||||
},
|
||||
positive: true
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const promise = require("promise");
|
||||
|
||||
/**
|
||||
* Creates "registered" actors factory meant for creating another kind of
|
||||
* factories, ObservedActorFactory, during the call to listTabs.
|
||||
|
@ -378,7 +380,7 @@ OriginalLocation.prototype = {
|
|||
exports.OriginalLocation = OriginalLocation;
|
||||
|
||||
/**
|
||||
* A GeneratedLocation represents a location in an original source.
|
||||
* A GeneratedLocation represents a location in a generated source.
|
||||
*
|
||||
* @param SourceActor actor
|
||||
* A SourceActor representing a generated source.
|
||||
|
@ -484,3 +486,36 @@ exports.getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
|
|||
|
||||
return bestOffsetMapping.columnNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method decorator that ensures the actor is in the expected state before
|
||||
* proceeding. If the actor is not in the expected state, the decorated method
|
||||
* returns a rejected promise.
|
||||
*
|
||||
* The actor's state must be at this.state property.
|
||||
*
|
||||
* @param String expectedState
|
||||
* The expected state.
|
||||
* @param String activity
|
||||
* Additional info about what's going on.
|
||||
* @param Function method
|
||||
* The actor method to proceed with when the actor is in the expected
|
||||
* state.
|
||||
*
|
||||
* @returns Function
|
||||
* The decorated method.
|
||||
*/
|
||||
function expectState(expectedState, method, activity) {
|
||||
return function(...args) {
|
||||
if (this.state !== expectedState) {
|
||||
const msg = `Wrong state while ${activity}:` +
|
||||
`Expected '${expectedState}', ` +
|
||||
`but current state is '${this.state}'.`;
|
||||
return promise.reject(new Error(msg));
|
||||
}
|
||||
|
||||
return method.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
exports.expectState = expectState;
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
/* 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 protocol = require("devtools/server/protocol");
|
||||
const { method, RetVal, Arg, types } = protocol;
|
||||
const { expectState, ActorPool } = require("devtools/server/actors/common");
|
||||
const { ObjectActor, createValueGrip } = require("devtools/server/actors/object");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
|
||||
// Teach protocol.js how to deal with legacy actor types
|
||||
types.addType("ObjectActor", {
|
||||
write: actor => actor.grip(),
|
||||
read: grip => grip
|
||||
});
|
||||
|
||||
/**
|
||||
* The Promises Actor provides support for getting the list of live promises and
|
||||
* observing changes to their settlement state.
|
||||
*/
|
||||
let PromisesActor = protocol.ActorClass({
|
||||
typeName: "promises",
|
||||
|
||||
events: {
|
||||
// Event emitted for new promises allocated in debuggee and bufferred by
|
||||
// sending the list of promise objects in a batch.
|
||||
"new-promises": {
|
||||
type: "new-promises",
|
||||
data: Arg(0, "array:ObjectActor"),
|
||||
},
|
||||
// Event emitted for promise settlements.
|
||||
"promises-settled": {
|
||||
type: "promises-settled",
|
||||
data: Arg(0, "array:ObjectActor")
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param conn DebuggerServerConnection.
|
||||
* @param parent TabActor|RootActor
|
||||
*/
|
||||
initialize: function(conn, parent) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
|
||||
this.conn = conn;
|
||||
this.parent = parent;
|
||||
this.state = "detached";
|
||||
this._dbg = null;
|
||||
this._gripDepth = 0;
|
||||
this._navigationLifetimePool = null;
|
||||
this._newPromises = null;
|
||||
this._promisesSettled = null;
|
||||
|
||||
this.objectGrip = this.objectGrip.bind(this);
|
||||
this._makePromiseEventHandler = this._makePromiseEventHandler.bind(this);
|
||||
this._onWindowReady = this._onWindowReady.bind(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
protocol.Actor.prototype.destroy.call(this, this.conn);
|
||||
|
||||
if (this.state === "attached") {
|
||||
this.detach();
|
||||
}
|
||||
},
|
||||
|
||||
get dbg() {
|
||||
if (!this._dbg) {
|
||||
this._dbg = this.parent.makeDebugger();
|
||||
}
|
||||
return this._dbg;
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach to the PromisesActor.
|
||||
*/
|
||||
attach: method(expectState("detached", function() {
|
||||
this.dbg.addDebuggees();
|
||||
|
||||
this._navigationLifetimePool = this._createActorPool();
|
||||
this.conn.addActorPool(this._navigationLifetimePool);
|
||||
|
||||
this._newPromises = [];
|
||||
this._promisesSettled = [];
|
||||
|
||||
events.on(this.parent, "window-ready", this._onWindowReady);
|
||||
|
||||
this.state = "attached";
|
||||
}, `attaching to the PromisesActor`), {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Detach from the PromisesActor upon Debugger closing.
|
||||
*/
|
||||
detach: method(expectState("attached", function() {
|
||||
this.dbg.removeAllDebuggees();
|
||||
this.dbg.enabled = false;
|
||||
this._dbg = null;
|
||||
this._newPromises = null;
|
||||
this._promisesSettled = null;
|
||||
|
||||
if (this._navigationLifetimePool) {
|
||||
this.conn.removeActorPool(this._navigationLifetimePool);
|
||||
this._navigationLifetimePool = null;
|
||||
}
|
||||
|
||||
events.off(this.parent, "window-ready", this._onWindowReady);
|
||||
|
||||
this.state = "detached";
|
||||
}, `detaching from the PromisesActor`), {
|
||||
request: {},
|
||||
response: {}
|
||||
}),
|
||||
|
||||
_createActorPool: function() {
|
||||
let pool = new ActorPool(this.conn);
|
||||
pool.objectActors = new WeakMap();
|
||||
return pool;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an ObjectActor for the given Promise object.
|
||||
*
|
||||
* @param object promise
|
||||
* The promise object
|
||||
* @return object
|
||||
* An ObjectActor object that wraps the given Promise object
|
||||
*/
|
||||
_createObjectActorForPromise: function(promise) {
|
||||
if (this._navigationLifetimePool.objectActors.has(promise)) {
|
||||
return this._navigationLifetimePool.objectActors.get(promise);
|
||||
}
|
||||
|
||||
let actor = new ObjectActor(promise, {
|
||||
getGripDepth: () => this._gripDepth,
|
||||
incrementGripDepth: () => this._gripDepth++,
|
||||
decrementGripDepth: () => this._gripDepth--,
|
||||
createValueGrip: v =>
|
||||
createValueGrip(v, this._navigationLifetimePool, this.objectGrip),
|
||||
sources: () => DevToolsUtils.reportException("PromisesActor",
|
||||
Error("sources not yet implemented")),
|
||||
createEnvironmentActor: () => DevToolsUtils.reportException(
|
||||
"PromisesActor", Error("createEnvironmentActor not yet implemented"))
|
||||
});
|
||||
|
||||
this._navigationLifetimePool.addActor(actor);
|
||||
this._navigationLifetimePool.objectActors.set(promise, actor);
|
||||
|
||||
return actor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a grip for the given Promise object.
|
||||
*
|
||||
* @param object value
|
||||
* The Promise object
|
||||
* @return object
|
||||
* The grip for the given Promise object
|
||||
*/
|
||||
objectGrip: function(value) {
|
||||
return this._createObjectActorForPromise(value).grip();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of ObjectActors for all live Promise Objects.
|
||||
*/
|
||||
listPromises: method(function() {
|
||||
let promises = this.dbg.findObjects({ class: "Promise" });
|
||||
|
||||
this.dbg.onNewPromise = this._makePromiseEventHandler(this._newPromises,
|
||||
"new-promises");
|
||||
this.dbg.onPromiseSettled = this._makePromiseEventHandler(
|
||||
this._promisesSettled, "promises-settled");
|
||||
|
||||
return promises.map(p => this._createObjectActorForPromise(p));
|
||||
}, {
|
||||
request: {
|
||||
},
|
||||
response: {
|
||||
promises: RetVal("array:ObjectActor")
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Creates an event handler for onNewPromise that will add the new
|
||||
* Promise ObjectActor to the array and schedule it to be emitted as a
|
||||
* batch for the provided event.
|
||||
*
|
||||
* @param array array
|
||||
* The list of Promise ObjectActors to emit
|
||||
* @param string eventName
|
||||
* The event name
|
||||
*/
|
||||
_makePromiseEventHandler: function(array, eventName) {
|
||||
return promise => {
|
||||
let actor = this._createObjectActorForPromise(promise)
|
||||
let needsScheduling = array.length == 0;
|
||||
|
||||
array.push(actor);
|
||||
|
||||
if (needsScheduling) {
|
||||
DevToolsUtils.executeSoon(() => {
|
||||
events.emit(this, eventName, array.splice(0, array.length));
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onWindowReady: expectState("attached", function({ isTopLevel }) {
|
||||
if (!isTopLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._navigationLifetimePool.cleanup();
|
||||
this.dbg.removeAllDebuggees();
|
||||
this.dbg.addDebuggees();
|
||||
})
|
||||
});
|
||||
|
||||
exports.PromisesActor = PromisesActor;
|
||||
|
||||
exports.PromisesFront = protocol.FrontClass(PromisesActor, {
|
||||
initialize: function(client, form) {
|
||||
protocol.Front.prototype.initialize.call(this, client, form);
|
||||
this.actorID = form.promisesActor;
|
||||
this.manage(this);
|
||||
}
|
||||
});
|
|
@ -7,40 +7,12 @@
|
|||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { reportException } = require("devtools/toolkit/DevToolsUtils");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
const { expectState } = require("devtools/server/actors/common");
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "EventTarget", "sdk/event/target", true);
|
||||
loader.lazyRequireGetter(this, "StackFrameCache",
|
||||
"devtools/server/actors/utils/stack", true);
|
||||
|
||||
/**
|
||||
* A method decorator that ensures the actor is in the expected state before
|
||||
* proceeding. If the actor is not in the expected state, the decorated method
|
||||
* returns a rejected promise.
|
||||
*
|
||||
* @param String expectedState
|
||||
* The expected state.
|
||||
* @param String activity
|
||||
* Additional info about what's going on.
|
||||
* @param Function method
|
||||
* The actor method to proceed with when the actor is in the expected
|
||||
* state.
|
||||
*
|
||||
* @returns Function
|
||||
* The decorated method.
|
||||
*/
|
||||
function expectState(expectedState, method, activity) {
|
||||
return function(...args) {
|
||||
if (this.state !== expectedState) {
|
||||
const msg = `Wrong state while ${activity}:` +
|
||||
`Expected '${expectedState}',` +
|
||||
`but current state is '${this.state}'.`;
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
|
||||
return method.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that returns memory data for a parent actor's window.
|
||||
* Using a tab-scoped actor with this instance will measure the memory footprint of its
|
||||
|
|
|
@ -544,6 +544,11 @@ var DebuggerServer = {
|
|||
constructor: "AnimationsActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/promises", {
|
||||
prefix: "promises",
|
||||
constructor: "PromisesActor",
|
||||
type: { global: true, tab: true }
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -77,6 +77,7 @@ EXTRA_JS_MODULES.devtools.server.actors += [
|
|||
'actors/preference.js',
|
||||
'actors/pretty-print-worker.js',
|
||||
'actors/profiler.js',
|
||||
'actors/promises.js',
|
||||
'actors/root.js',
|
||||
'actors/script.js',
|
||||
'actors/settings.js',
|
||||
|
|
|
@ -15,12 +15,6 @@ let object = require("sdk/util/object");
|
|||
|
||||
exports.emit = events.emit;
|
||||
|
||||
// Waiting for promise.done() to be added, see bug 851321
|
||||
function promiseDone(err) {
|
||||
console.error(err);
|
||||
return promise.reject(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Types: named marshallers/demarshallers.
|
||||
*
|
||||
|
@ -1337,7 +1331,7 @@ let frontProto = function(proto) {
|
|||
}
|
||||
|
||||
return ret;
|
||||
}).then(null, promiseDone);
|
||||
});
|
||||
}
|
||||
|
||||
// Release methods should call the destroy function on return.
|
||||
|
|
|
@ -301,6 +301,20 @@ function initTestDebuggerServer(aServer = DebuggerServer)
|
|||
aServer.init(function () { return true; });
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the testing debugger server with a tab whose title is |title|.
|
||||
*/
|
||||
function startTestDebuggerServer(title, server = DebuggerServer) {
|
||||
initTestDebuggerServer(server);
|
||||
addTestGlobal(title);
|
||||
DebuggerServer.addTabActors();
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
let client = new DebuggerClient(transport);
|
||||
|
||||
return connect(client).then(() => client);
|
||||
}
|
||||
|
||||
function initTestTracerServer(aServer = DebuggerServer)
|
||||
{
|
||||
aServer.registerModule("xpcshell-test/testactors");
|
||||
|
@ -338,6 +352,11 @@ function get_chrome_actors(callback)
|
|||
});
|
||||
}
|
||||
|
||||
function getChromeActors(client, server = DebuggerServer) {
|
||||
server.allowChromeProcess = true;
|
||||
return client.getProcess().then(response => response.form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a relative file path and returns the absolute file url for it.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we can attach and detach to the PromisesActor under the correct
|
||||
* states.
|
||||
*/
|
||||
|
||||
const { PromisesFront } = devtools.require("devtools/server/actors/promises");
|
||||
|
||||
add_task(function*() {
|
||||
let client = yield startTestDebuggerServer("promises-actor-test");
|
||||
let chromeActors = yield getChromeActors(client);
|
||||
|
||||
yield testAttach(client, chromeActors);
|
||||
|
||||
let response = yield listTabs(client);
|
||||
let targetTab = findTab(response.tabs, "promises-actor-test");
|
||||
ok(targetTab, "Found our target tab.");
|
||||
|
||||
let [ tabResponse ] = yield attachTab(client, targetTab);
|
||||
|
||||
yield testAttach(client, tabResponse);
|
||||
|
||||
yield close(client);
|
||||
});
|
||||
|
||||
function* testAttach(client, parent) {
|
||||
let promises = PromisesFront(client, parent);
|
||||
|
||||
try {
|
||||
yield promises.detach();
|
||||
ok(false, "Should not be able to detach when in a detached state.")
|
||||
} catch(e) {
|
||||
ok(true, "Expected detach to fail when already in a detached state.");
|
||||
}
|
||||
|
||||
yield promises.attach();
|
||||
ok(true, "Expected attach to succeed.");
|
||||
|
||||
try {
|
||||
yield promises.attach();
|
||||
ok(false, "Should not be able to attach when in an attached state.");
|
||||
} catch(e) {
|
||||
ok(true, "Expected attach to fail when already in an attached state.");
|
||||
}
|
||||
|
||||
yield promises.detach();
|
||||
ok(true, "Expected detach to succeed.");
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that the PromisesActor exists in the TabActors and ChromeActors.
|
||||
*/
|
||||
|
||||
add_task(function*() {
|
||||
let client = yield startTestDebuggerServer("promises-actor-test");
|
||||
|
||||
let response = yield listTabs(client);
|
||||
let targetTab = findTab(response.tabs, "promises-actor-test");
|
||||
ok(targetTab, "Found our target tab.")
|
||||
|
||||
// Attach to the TabActor and check the response
|
||||
client.request({ to: targetTab.actor, type: "attach" }, response => {
|
||||
ok(!("error" in response), "Expect no error in response.");
|
||||
ok(response.from, targetTab.actor,
|
||||
"Expect the target TabActor in response form field.");
|
||||
ok(response.type, "tabAttached",
|
||||
"Expect tabAttached in the response type.");
|
||||
is(typeof response.promisesActor === "string",
|
||||
"Should have a tab context PromisesActor.");
|
||||
});
|
||||
|
||||
let chromeActors = yield getChromeActors(client);
|
||||
ok(typeof chromeActors.promisesActor === "string",
|
||||
"Should have a chrome context PromisesActor.");
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we can get the list of all live Promise objects from the
|
||||
* PromisesActor.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { PromisesFront } = devtools.require("devtools/server/actors/promises");
|
||||
const SECRET = "MyLittleSecret";
|
||||
|
||||
add_task(function*() {
|
||||
let client = yield startTestDebuggerServer("promises-actor-test");
|
||||
let chromeActors = yield getChromeActors(client);
|
||||
|
||||
yield testListPromises(client, chromeActors, v =>
|
||||
new Promise(resolve => resolve(v)));
|
||||
|
||||
let response = yield listTabs(client);
|
||||
let targetTab = findTab(response.tabs, "promises-actor-test");
|
||||
ok(targetTab, "Found our target tab.");
|
||||
|
||||
yield testListPromises(client, targetTab, v => {
|
||||
const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
|
||||
return debuggee.Promise.resolve(v);
|
||||
});
|
||||
|
||||
yield close(client);
|
||||
});
|
||||
|
||||
function* testListPromises(client, form, makePromise) {
|
||||
let resolution = SECRET + Math.random();
|
||||
let promise = makePromise(resolution);
|
||||
let front = PromisesFront(client, form);
|
||||
|
||||
yield front.attach();
|
||||
|
||||
let promises = yield front.listPromises();
|
||||
|
||||
let found = false;
|
||||
for (let p of promises) {
|
||||
equal(p.type, "object", "Expect type to be Object");
|
||||
equal(p.class, "Promise", "Expect class to be Promise");
|
||||
|
||||
if (p.promiseState.state === "fulfilled" &&
|
||||
p.promiseState.value === resolution) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
ok(found, "Found our promise");
|
||||
yield front.detach();
|
||||
// Appease eslint
|
||||
void promise;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we can get the list of all new Promise objects from the
|
||||
* PromisesActor onNewPromise event handler.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { PromisesFront } = devtools.require("devtools/server/actors/promises");
|
||||
|
||||
let events = devtools.require("sdk/event/core");
|
||||
|
||||
add_task(function*() {
|
||||
let client = yield startTestDebuggerServer("promises-actor-test");
|
||||
let chromeActors = yield getChromeActors(client);
|
||||
|
||||
ok(Promise.toString().contains("native code"), "Expect native DOM Promise");
|
||||
|
||||
yield testNewPromisesEvent(client, chromeActors,
|
||||
v => new Promise(resolve => resolve(v)));
|
||||
|
||||
let response = yield listTabs(client);
|
||||
let targetTab = findTab(response.tabs, "promises-actor-test");
|
||||
ok(targetTab, "Found our target tab.");
|
||||
|
||||
yield testNewPromisesEvent(client, targetTab, v => {
|
||||
const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
|
||||
return debuggee.Promise.resolve(v);
|
||||
});
|
||||
|
||||
yield close(client);
|
||||
});
|
||||
|
||||
function* testNewPromisesEvent(client, form, makePromise) {
|
||||
let front = PromisesFront(client, form);
|
||||
let resolution = "MyLittleSecret" + Math.random();
|
||||
let found = false;
|
||||
|
||||
yield front.attach();
|
||||
yield front.listPromises();
|
||||
|
||||
let onNewPromise = new Promise(resolve => {
|
||||
events.on(front, "new-promises", promises => {
|
||||
for (let p of promises) {
|
||||
equal(p.type, "object", "Expect type to be Object");
|
||||
equal(p.class, "Promise", "Expect class to be Promise");
|
||||
|
||||
if (p.promiseState.state === "fulfilled" &&
|
||||
p.promiseState.value === resolution) {
|
||||
found = true;
|
||||
resolve();
|
||||
} else {
|
||||
dump("Found non-target promise\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let promise = makePromise(resolution);
|
||||
|
||||
yield onNewPromise;
|
||||
ok(found, "Found our new promise");
|
||||
yield front.detach();
|
||||
// Appease eslint
|
||||
void promise;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we can get the list of Promise objects that have settled from the
|
||||
* PromisesActor onPromiseSettled event handler.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { PromisesFront } = devtools.require("devtools/server/actors/promises");
|
||||
|
||||
let events = devtools.require("sdk/event/core");
|
||||
|
||||
add_task(function*() {
|
||||
let client = yield startTestDebuggerServer("promises-actor-test");
|
||||
let chromeActors = yield getChromeActors(client);
|
||||
|
||||
ok(Promise.toString().contains("native code"), "Expect native DOM Promise");
|
||||
|
||||
yield testPromisesSettled(client, chromeActors,
|
||||
v => new Promise(resolve => resolve(v)),
|
||||
v => new Promise((resolve, reject) => reject(v)));
|
||||
|
||||
let response = yield listTabs(client);
|
||||
let targetTab = findTab(response.tabs, "promises-actor-test");
|
||||
ok(targetTab, "Found our target tab.");
|
||||
|
||||
yield testPromisesSettled(client, targetTab, v => {
|
||||
const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
|
||||
return debuggee.Promise.resolve(v);
|
||||
}, v => {
|
||||
const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
|
||||
return debuggee.Promise.reject(v);
|
||||
});
|
||||
|
||||
yield close(client);
|
||||
});
|
||||
|
||||
function* testPromisesSettled(client, form, makeResolvePromise,
|
||||
makeRejectPromise) {
|
||||
let front = PromisesFront(client, form);
|
||||
let resolution = "MyLittleSecret" + Math.random();
|
||||
|
||||
yield front.attach();
|
||||
yield front.listPromises();
|
||||
|
||||
let onPromiseSettled = oncePromiseSettled(front, resolution, true, false);
|
||||
let resolvedPromise = makeResolvePromise(resolution);
|
||||
let foundResolvedPromise = yield onPromiseSettled;
|
||||
ok(foundResolvedPromise, "Found our resolved promise");
|
||||
|
||||
onPromiseSettled = oncePromiseSettled(front, resolution, false, true);
|
||||
let rejectedPromise = makeRejectPromise(resolution);
|
||||
let foundRejectedPromise = yield onPromiseSettled;
|
||||
ok(foundRejectedPromise, "Found our rejected promise");
|
||||
|
||||
yield front.detach();
|
||||
// Appease eslint
|
||||
void resolvedPromise;
|
||||
void rejectedPromise;
|
||||
}
|
||||
|
||||
function oncePromiseSettled(front, resolution, resolveValue, rejectValue) {
|
||||
return new Promise(resolve => {
|
||||
events.on(front, "promises-settled", promises => {
|
||||
for (let p of promises) {
|
||||
equal(p.type, "object", "Expect type to be Object");
|
||||
equal(p.class, "Promise", "Expect class to be Promise");
|
||||
|
||||
if (p.promiseState.state === "fulfilled" &&
|
||||
p.promiseState.value === resolution) {
|
||||
resolve(resolveValue);
|
||||
} else if (p.promiseState.state === "rejected" &&
|
||||
p.promiseState.reason === resolution) {
|
||||
resolve(rejectValue);
|
||||
} else {
|
||||
dump("Found non-target promise\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -9,11 +9,21 @@ const { TabSources } = require("devtools/server/actors/utils/TabSources");
|
|||
const promise = require("promise");
|
||||
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
|
||||
|
||||
var gTestGlobals = [];
|
||||
let gTestGlobals = [];
|
||||
DebuggerServer.addTestGlobal = function(aGlobal) {
|
||||
gTestGlobals.push(aGlobal);
|
||||
};
|
||||
|
||||
DebuggerServer.getTestGlobal = function(name) {
|
||||
for (let g of gTestGlobals) {
|
||||
if (g.__name == name) {
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// A mock tab list, for use by tests. This simply presents each global in
|
||||
// gTestGlobals as a tab, and the list is fixed: it never calls its
|
||||
// onListChanged handler.
|
||||
|
|
|
@ -80,6 +80,11 @@ support-files =
|
|||
[test_eval-03.js]
|
||||
[test_eval-04.js]
|
||||
[test_eval-05.js]
|
||||
[test_promises_actor_attach.js]
|
||||
[test_promises_actor_exist.js]
|
||||
[test_promises_actor_list_promises.js]
|
||||
[test_promises_actor_onnewpromise.js]
|
||||
[test_promises_actor_onpromisesettled.js]
|
||||
[test_protocol_abort.js]
|
||||
[test_protocol_async.js]
|
||||
[test_protocol_children.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче