mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
3d9b01b5c8
@ -7206,11 +7206,6 @@ let gRemoteTabsUI = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
// e10s isn't supported in safe mode, so don't show the menu items for it
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
if (Services.prefs.getBoolPref("layers.acceleration.disabled")) {
|
||||
// On OS X, "Disable Hardware Acceleration" also disables OMTC and forces
|
||||
|
@ -1126,7 +1126,7 @@ if (Services.prefs.getBoolPref("browser.pocket.enabled")) {
|
||||
}
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
let e10sDisabled = Services.appinfo.inSafeMode;
|
||||
let e10sDisabled = false;
|
||||
#ifdef XP_MACOSX
|
||||
// On OS X, "Disable Hardware Acceleration" also disables OMTC and forces
|
||||
// a fallback to Basic Layers. This is incompatible with e10s.
|
||||
|
@ -981,12 +981,14 @@ BrowserGlue.prototype = {
|
||||
this._checkForOldBuildUpdates();
|
||||
|
||||
let disabledAddons = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
|
||||
for (let id of disabledAddons) {
|
||||
if (AddonManager.getAddonByID(id).signedState <= AddonManager.SIGNEDSTATE_MISSING) {
|
||||
this._notifyUnsignedAddonsDisabled();
|
||||
break;
|
||||
AddonManager.getAddonsByIDs(disabledAddons, (addons) => {
|
||||
for (let addon of addons) {
|
||||
if (addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
|
||||
this._notifyUnsignedAddonsDisabled();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._firstWindowTelemetry(aWindow);
|
||||
},
|
||||
|
@ -90,7 +90,7 @@ Tools.inspector = {
|
||||
panelLabel: l10n("inspector.panelLabel", inspectorStrings),
|
||||
get tooltip() {
|
||||
return l10n("inspector.tooltip2", inspectorStrings,
|
||||
( osString == "Darwin" ? "Cmd+Alt" : "Ctrl+Shift+" ) + this.key);
|
||||
( osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+" ) + this.key);
|
||||
},
|
||||
inMenu: true,
|
||||
commands: [
|
||||
@ -127,7 +127,7 @@ Tools.webConsole = {
|
||||
panelLabel: l10n("ToolboxWebConsole.panelLabel", webConsoleStrings),
|
||||
get tooltip() {
|
||||
return l10n("ToolboxWebconsole.tooltip2", webConsoleStrings,
|
||||
( osString == "Darwin" ? "Cmd+Alt" : "Ctrl+Shift+" ) + this.key);
|
||||
( osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+" ) + this.key);
|
||||
},
|
||||
inMenu: true,
|
||||
commands: "devtools/webconsole/console-commands",
|
||||
@ -163,7 +163,7 @@ Tools.jsdebugger = {
|
||||
panelLabel: l10n("ToolboxDebugger.panelLabel", debuggerStrings),
|
||||
get tooltip() {
|
||||
return l10n("ToolboxDebugger.tooltip2", debuggerStrings,
|
||||
( osString == "Darwin" ? "Cmd+Alt" : "Ctrl+Shift+" ) + this.key);
|
||||
( osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+" ) + this.key);
|
||||
},
|
||||
inMenu: true,
|
||||
commands: "devtools/debugger/debugger-commands",
|
||||
@ -288,7 +288,7 @@ Tools.netMonitor = {
|
||||
panelLabel: l10n("netmonitor.panelLabel", netMonitorStrings),
|
||||
get tooltip() {
|
||||
return l10n("netmonitor.tooltip2", netMonitorStrings,
|
||||
( osString == "Darwin" ? "Cmd+Alt" : "Ctrl+Shift+" ) + this.key);
|
||||
( osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+" ) + this.key);
|
||||
},
|
||||
inMenu: true,
|
||||
|
||||
|
@ -23,21 +23,21 @@ loader.lazyRequireGetter(this, "FrameUtils",
|
||||
* The raw thread object received from the backend. Contains samples,
|
||||
* stackTable, frameTable, and stringTable.
|
||||
* @param object options
|
||||
* Additional supported options, @see ThreadNode.prototype.insert
|
||||
* - number startTime [optional]
|
||||
* - number endTime [optional]
|
||||
* Additional supported options
|
||||
* - number startTime
|
||||
* - number endTime
|
||||
* - boolean contentOnly [optional]
|
||||
* - boolean invertTree [optional]
|
||||
* - boolean flattenRecursion [optional]
|
||||
*/
|
||||
function ThreadNode(thread, options = {}) {
|
||||
if (options.endTime == void 0 || options.startTime == void 0) {
|
||||
throw new Error("ThreadNode requires both `startTime` and `endTime`.");
|
||||
}
|
||||
this.samples = 0;
|
||||
this.duration = 0;
|
||||
this.youngestFrameSamples = 0;
|
||||
this.calls = [];
|
||||
|
||||
// Maps of frame to their self counts and duration.
|
||||
this.selfCount = Object.create(null);
|
||||
this.selfDuration = Object.create(null);
|
||||
this.duration = options.endTime - options.startTime;
|
||||
|
||||
let { samples, stackTable, frameTable, stringTable, allocationsTable } = thread;
|
||||
|
||||
@ -74,8 +74,8 @@ ThreadNode.prototype = {
|
||||
* index.
|
||||
* @param object options
|
||||
* Additional supported options
|
||||
* - number startTime [optional]
|
||||
* - number endTime [optional]
|
||||
* - number startTime
|
||||
* - number endTime
|
||||
* - boolean contentOnly [optional]
|
||||
* - boolean invertTree [optional]
|
||||
*/
|
||||
@ -120,9 +120,6 @@ ThreadNode.prototype = {
|
||||
const InflatedFrame = FrameUtils.InflatedFrame;
|
||||
const getOrAddInflatedFrame = FrameUtils.getOrAddInflatedFrame;
|
||||
|
||||
let selfCount = this.selfCount;
|
||||
let selfDuration = this.selfDuration;
|
||||
|
||||
let samplesData = samples.data;
|
||||
let stacksData = stackTable.data;
|
||||
|
||||
@ -130,8 +127,8 @@ ThreadNode.prototype = {
|
||||
let inflatedFrameCache = FrameUtils.getInflatedFrameCache(frameTable);
|
||||
let leafTable = Object.create(null);
|
||||
|
||||
let startTime = options.startTime || 0
|
||||
let endTime = options.endTime || Infinity;
|
||||
let startTime = options.startTime;
|
||||
let endTime = options.endTime;
|
||||
let flattenRecursion = options.flattenRecursion;
|
||||
|
||||
// Take the timestamp of the first sample as prevSampleTime. 0 is
|
||||
@ -163,7 +160,6 @@ ThreadNode.prototype = {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sampleDuration = sampleTime - prevSampleTime;
|
||||
let stackIndex = sample[SAMPLE_STACK_SLOT];
|
||||
let calls = this.calls;
|
||||
let prevCalls = this.calls;
|
||||
@ -223,22 +219,9 @@ ThreadNode.prototype = {
|
||||
mutableFrameKeyOptions.isRoot = stackIndex === null;
|
||||
let frameKey = inflatedFrame.getFrameKey(mutableFrameKeyOptions);
|
||||
|
||||
// Leaf frames are never skipped and require self count and duration
|
||||
// bookkeeping.
|
||||
if (isLeaf) {
|
||||
// Tabulate self count and duration for the leaf frame. The frameKey
|
||||
// is never empty for a leaf frame.
|
||||
if (selfCount[frameKey] === undefined) {
|
||||
selfCount[frameKey] = 0;
|
||||
selfDuration[frameKey] = 0;
|
||||
}
|
||||
selfCount[frameKey]++;
|
||||
selfDuration[frameKey] += sampleDuration;
|
||||
} else {
|
||||
// An empty frame key means this frame should be skipped.
|
||||
if (frameKey === "") {
|
||||
continue;
|
||||
}
|
||||
// An empty frame key means this frame should be skipped.
|
||||
if (frameKey === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we shouldn't flatten the current frame into the previous one, advance a
|
||||
@ -250,18 +233,18 @@ ThreadNode.prototype = {
|
||||
let frameNode = getOrAddFrameNode(calls, isLeaf, frameKey, inflatedFrame,
|
||||
mutableFrameKeyOptions.isMetaCategoryOut,
|
||||
leafTable);
|
||||
|
||||
frameNode._countSample(prevSampleTime, sampleTime, inflatedFrame.optimizations,
|
||||
stringTable);
|
||||
if (isLeaf) {
|
||||
frameNode.youngestFrameSamples++;
|
||||
}
|
||||
frameNode.samples++;
|
||||
frameNode._addOptimizations(inflatedFrame.optimizations, stringTable);
|
||||
|
||||
prevFrameKey = frameKey;
|
||||
prevCalls = frameNode.calls;
|
||||
isLeaf = mutableFrameKeyOptions.isLeaf = false;
|
||||
}
|
||||
|
||||
this.duration += sampleDuration;
|
||||
this.samples++;
|
||||
prevSampleTime = sampleTime;
|
||||
}
|
||||
},
|
||||
|
||||
@ -347,7 +330,19 @@ ThreadNode.prototype = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A function call node in a tree.
|
||||
* A function call node in a tree. Represents a function call with a unique context,
|
||||
* resulting in each FrameNode having its own row in the corresponding tree view.
|
||||
* Take samples:
|
||||
* A()->B()->C()
|
||||
* A()->B()
|
||||
* Q()->B()
|
||||
*
|
||||
* In inverted tree, A()->B()->C() would have one frame node, and A()->B() and
|
||||
* Q()->B() would share a frame node.
|
||||
* In an uninverted tree, A()->B()->C() and A()->B() would share a frame node,
|
||||
* with Q()->B() having its own.
|
||||
*
|
||||
* In all cases, all the frame nodes originated from the same InflatedFrame.
|
||||
*
|
||||
* @param string frameKey
|
||||
* The key associated with this frame. The key determines identity of
|
||||
@ -372,8 +367,8 @@ function FrameNode(frameKey, { location, line, category, allocations, isContent
|
||||
this.location = location;
|
||||
this.line = line;
|
||||
this.allocations = allocations;
|
||||
this.youngestFrameSamples = 0;
|
||||
this.samples = 0;
|
||||
this.duration = 0;
|
||||
this.calls = [];
|
||||
this.isContent = !!isContent;
|
||||
this._optimizations = null;
|
||||
@ -384,22 +379,15 @@ function FrameNode(frameKey, { location, line, category, allocations, isContent
|
||||
|
||||
FrameNode.prototype = {
|
||||
/**
|
||||
* Count a sample as associated with this node.
|
||||
* Take optimization data observed for this frame.
|
||||
*
|
||||
* @param number prevSampleTime
|
||||
* The time when the immediate previous sample was sampled.
|
||||
* @param number sampleTime
|
||||
* The time when the current sample was sampled.
|
||||
* @param object optimizationSite
|
||||
* Any JIT optimization information attached to the current
|
||||
* sample. Lazily inflated via stringTable.
|
||||
* @param object stringTable
|
||||
* The string table used to inflate the optimizationSite.
|
||||
*/
|
||||
_countSample: function (prevSampleTime, sampleTime, optimizationSite, stringTable) {
|
||||
this.samples++;
|
||||
this.duration += sampleTime - prevSampleTime;
|
||||
|
||||
_addOptimizations: function (optimizationSite, stringTable) {
|
||||
// Simply accumulate optimization sites for now. Processing is done lazily
|
||||
// by JITOptimizations, if optimization information is actually displayed.
|
||||
if (optimizationSite) {
|
||||
@ -424,7 +412,7 @@ FrameNode.prototype = {
|
||||
}
|
||||
|
||||
this.samples += otherNode.samples;
|
||||
this.duration += otherNode.duration;
|
||||
this.youngestFrameSamples += otherNode.youngestFrameSamples;
|
||||
|
||||
if (otherNode._optimizations) {
|
||||
let opts = this._optimizations;
|
||||
|
@ -28,7 +28,7 @@ const DEFAULT_SORTING_PREDICATE = (frameA, frameB) => {
|
||||
}
|
||||
return dataA.selfPercentage < dataB.selfPercentage ? 1 : - 1;
|
||||
}
|
||||
return dataA.samples < dataB.samples ? 1 : -1;
|
||||
return dataA.totalPercentage < dataB.totalPercentage ? 1 : -1;
|
||||
};
|
||||
|
||||
const DEFAULT_AUTO_EXPAND_DEPTH = 3; // depth
|
||||
@ -358,54 +358,28 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
|
||||
|
||||
// Leaf nodes in an inverted tree don't have to do anything special.
|
||||
let isLeaf = this._level === 0;
|
||||
let totalSamples = this.root.frame.samples;
|
||||
let totalDuration = this.root.frame.duration;
|
||||
|
||||
if (this.inverted && !isLeaf && this.parent != null) {
|
||||
let calleeData = this.parent.getDisplayedData();
|
||||
// Percentage of time that this frame called the callee
|
||||
// in this branch
|
||||
let callerPercentage = this.frame.samples / calleeData.samples;
|
||||
// Self duration, cost
|
||||
if (this.visibleCells.selfDuration) {
|
||||
data.selfDuration = this.frame.youngestFrameSamples / totalSamples * totalDuration;
|
||||
}
|
||||
if (this.visibleCells.selfPercentage) {
|
||||
data.selfPercentage = this.frame.youngestFrameSamples / totalSamples * 100;
|
||||
}
|
||||
|
||||
// Self/total duration.
|
||||
if (this.visibleCells.duration) {
|
||||
data.totalDuration = calleeData.totalDuration * callerPercentage;
|
||||
}
|
||||
if (this.visibleCells.selfDuration) {
|
||||
data.selfDuration = 0;
|
||||
}
|
||||
// Total duration, cost
|
||||
if (this.visibleCells.duration) {
|
||||
data.totalDuration = this.frame.samples / totalSamples * totalDuration;
|
||||
}
|
||||
if (this.visibleCells.percentage) {
|
||||
data.totalPercentage = this.frame.samples / totalSamples * 100;
|
||||
}
|
||||
|
||||
// Self/total samples percentage.
|
||||
if (this.visibleCells.percentage) {
|
||||
data.totalPercentage = calleeData.totalPercentage * callerPercentage;
|
||||
}
|
||||
if (this.visibleCells.selfPercentage) {
|
||||
data.selfPercentage = 0;
|
||||
}
|
||||
|
||||
// Raw samples.
|
||||
if (this.visibleCells.samples) {
|
||||
data.samples = this.frame.samples;
|
||||
}
|
||||
} else {
|
||||
// Self/total duration.
|
||||
if (this.visibleCells.duration) {
|
||||
data.totalDuration = this.frame.duration;
|
||||
}
|
||||
if (this.visibleCells.selfDuration) {
|
||||
data.selfDuration = this.root.frame.selfDuration[this.frame.key];
|
||||
}
|
||||
|
||||
// Self/total samples percentage.
|
||||
if (this.visibleCells.percentage) {
|
||||
data.totalPercentage = this.frame.samples / this.root.frame.samples * 100;
|
||||
}
|
||||
if (this.visibleCells.selfPercentage) {
|
||||
data.selfPercentage = this.root.frame.selfCount[this.frame.key] / this.root.frame.samples * 100;
|
||||
}
|
||||
|
||||
// Raw samples.
|
||||
if (this.visibleCells.samples) {
|
||||
data.samples = this.frame.samples;
|
||||
}
|
||||
// Raw samples.
|
||||
if (this.visibleCells.samples) {
|
||||
data.samples = this.frame.youngestFrameSamples;
|
||||
}
|
||||
|
||||
// Self/total allocations count.
|
||||
|
@ -127,8 +127,6 @@ const EVENTS = {
|
||||
|
||||
// Emitted by the OverviewView when a range has been selected in the graphs
|
||||
OVERVIEW_RANGE_SELECTED: "Performance:UI:OverviewRangeSelected",
|
||||
// Emitted by the OverviewView when a selection range has been removed
|
||||
OVERVIEW_RANGE_CLEARED: "Performance:UI:OverviewRangeCleared",
|
||||
|
||||
// Emitted by the DetailsView when a subview is selected
|
||||
DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
|
||||
|
@ -6,7 +6,7 @@
|
||||
*/
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
let { EVENTS, DetailsView, WaterfallView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
@ -19,11 +19,25 @@ function* spawnTest() {
|
||||
|
||||
ok(true, "JsCallTreeView rendered after recording is stopped.");
|
||||
|
||||
yield DetailsView.selectView("waterfall");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
||||
let waterfallRenderedOnWF = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
let waterfallRenderedOnJS = once(JsCallTreeView, EVENTS.WATERFALL_RENDERED);
|
||||
|
||||
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
|
||||
// The waterfall should render by default, and we want to make
|
||||
// sure that the render events don't bleed between detail views
|
||||
// via bug 1173393, so test that's the case after both views have been
|
||||
// created
|
||||
waterfallRenderedOnJS.then(() =>
|
||||
ok(false, "JsCallTreeView should not receive WATERFALL_RENDERED event"));
|
||||
yield waterfallRenderedOnWF;
|
||||
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
yield rendered;
|
||||
|
||||
ok(true, "JsCallTreeView rendered again after recording completed a second time.");
|
||||
|
@ -14,7 +14,7 @@ function* spawnTest() {
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, $, DetailsView, JsCallTreeView, MemoryCallTreeView } = panel.panelWin;
|
||||
let { EVENTS, $, DetailsView, OverviewView, JsCallTreeView, MemoryCallTreeView } = panel.panelWin;
|
||||
|
||||
yield DetailsView.selectView("js-calltree");
|
||||
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
|
||||
@ -26,7 +26,7 @@ function* spawnTest() {
|
||||
yield rendered;
|
||||
|
||||
// Mock the profile used so we can get a deterministic tree created
|
||||
let threadNode = new ThreadNode(gProfile.threads[0]);
|
||||
let threadNode = new ThreadNode(gProfile.threads[0], OverviewView.getTimeInterval());
|
||||
JsCallTreeView._populateCallTree(threadNode);
|
||||
JsCallTreeView.emit(EVENTS.JS_CALL_TREE_RENDERED);
|
||||
|
||||
|
@ -58,7 +58,7 @@ function* spawnTest() {
|
||||
|
||||
// Force a rerender
|
||||
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
JsCallTreeView.render();
|
||||
JsCallTreeView.render(OverviewView.getTimeInterval());
|
||||
yield rendered;
|
||||
|
||||
is($("#jit-optimizations-view").hidden, true, "JIT Optimizations panel still hidden when rerendered");
|
||||
|
@ -61,7 +61,7 @@ function* spawnTest() {
|
||||
|
||||
// Force a rerender
|
||||
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
|
||||
JsCallTreeView.render();
|
||||
JsCallTreeView.render(OverviewView.getTimeInterval());
|
||||
yield rendered;
|
||||
|
||||
Services.prefs.setBoolPref(JIT_PREF, true);
|
||||
|
@ -7,7 +7,7 @@
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
|
||||
let startTime, endTime, params, _;
|
||||
let params, _;
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
@ -21,32 +21,49 @@ function* spawnTest() {
|
||||
|
||||
let graph = OverviewView.graphs.get("timeline");
|
||||
let MAX = graph.width;
|
||||
let duration = PerformanceController.getCurrentRecording().getDuration();
|
||||
let selection = null;
|
||||
|
||||
// Throw out events that select everything, as this will occur on the
|
||||
// first click
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, function handler (_, interval) {
|
||||
if (interval.endTime !== duration) {
|
||||
selection = interval;
|
||||
OverviewView.off(handler);
|
||||
}
|
||||
});
|
||||
|
||||
// Select the first half of the graph
|
||||
let results = onceSpread(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED);
|
||||
// Select the first half of the graph
|
||||
dragStart(graph, 0);
|
||||
dragStop(graph, MAX / 2);
|
||||
[_, { startTime, endTime }] = yield results;
|
||||
yield waitUntil(() => selection);
|
||||
let { startTime, endTime } = selection;
|
||||
|
||||
let mapStart = () => 0;
|
||||
let mapEnd = () => PerformanceController.getCurrentRecording().getDuration();
|
||||
let mapEnd = () => duration;
|
||||
let actual = graph.getMappedSelection({ mapStart, mapEnd });
|
||||
is(actual.min, 0, "graph selection starts at 0");
|
||||
is(actual.max, duration/2, `graph selection ends at ${duration/2}`);
|
||||
|
||||
is(graph.hasSelection(), true,
|
||||
"A selection exists on the graph.");
|
||||
is(startTime, actual.min,
|
||||
is(startTime, 0,
|
||||
"OVERVIEW_RANGE_SELECTED fired with startTime value on click.");
|
||||
is(endTime, actual.max,
|
||||
is(endTime, duration / 2,
|
||||
"OVERVIEW_RANGE_SELECTED fired with endTime value on click.");
|
||||
|
||||
// Listen to deselection
|
||||
results = onceSpread(OverviewView, EVENTS.OVERVIEW_RANGE_CLEARED);
|
||||
results = onceSpread(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED);
|
||||
dropSelection(graph);
|
||||
[_, params] = yield results;
|
||||
|
||||
is(graph.hasSelection(), false,
|
||||
"A selection no longer on the graph.");
|
||||
is(params, undefined,
|
||||
"OVERVIEW_RANGE_CLEARED fired with no additional arguments.");
|
||||
is(params.startTime, 0,
|
||||
"OVERVIEW_RANGE_SELECTED fired with 0 as a startTime.");
|
||||
is(params.endTime, duration,
|
||||
"OVERVIEW_RANGE_SELECTED fired with max duration as endTime");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
|
@ -40,7 +40,7 @@ function* spawnTest() {
|
||||
ok(true, "Flamegraph rerenders after its corresponding pane is shown.");
|
||||
|
||||
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
|
||||
OverviewView.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
|
||||
OverviewView.emit(EVENTS.OVERVIEW_RANGE_SELECTED);
|
||||
yield rendered;
|
||||
ok(true, "Flamegraph rerenders when a range in the overview graph is removed.");
|
||||
|
||||
|
@ -10,7 +10,7 @@ function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode });
|
||||
@ -31,7 +31,7 @@ function test() {
|
||||
|
||||
is(container.childNodes[0].childNodes[0].getAttribute("type"), "duration",
|
||||
"The root node in the tree has a duration cell.");
|
||||
is(container.childNodes[0].childNodes[0].getAttribute("value"), "15 ms",
|
||||
is(container.childNodes[0].childNodes[0].getAttribute("value"), "20 ms",
|
||||
"The root node in the tree has the correct duration cell value.");
|
||||
|
||||
is(container.childNodes[0].childNodes[1].getAttribute("type"), "percentage",
|
||||
@ -51,7 +51,7 @@ function test() {
|
||||
|
||||
is(container.childNodes[0].childNodes[4].getAttribute("type"), "samples",
|
||||
"The root node in the tree has an samples cell.");
|
||||
is(container.childNodes[0].childNodes[4].getAttribute("value"), "4",
|
||||
is(container.childNodes[0].childNodes[4].getAttribute("value"), "0",
|
||||
"The root node in the tree has the correct samples cell value.");
|
||||
|
||||
is(container.childNodes[0].childNodes[5].getAttribute("type"), "function",
|
||||
|
@ -12,7 +12,7 @@ function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode });
|
||||
@ -33,11 +33,11 @@ function test() {
|
||||
is(container.childNodes[0].className, "call-tree-item",
|
||||
"The root node in the tree has the correct class name.");
|
||||
|
||||
is($$dur(0).getAttribute("value"), "15 ms",
|
||||
is($$dur(0).getAttribute("value"), "20 ms",
|
||||
"The root's duration cell displays the correct value.");
|
||||
is($$perc(0).getAttribute("value"), "100%",
|
||||
"The root's percentage cell displays the correct value.");
|
||||
is($$sampl(0).getAttribute("value"), "4",
|
||||
is($$sampl(0).getAttribute("value"), "0",
|
||||
"The root's samples cell displays the correct value.");
|
||||
is($$fun(".call-tree-name")[0].getAttribute("value"), "(root)",
|
||||
"The root's function cell displays the correct name.");
|
||||
@ -59,11 +59,11 @@ function test() {
|
||||
is(container.childNodes[1].className, "call-tree-item",
|
||||
"The .A node in the tree has the correct class name.");
|
||||
|
||||
is($$dur(1).getAttribute("value"), "15 ms",
|
||||
is($$dur(1).getAttribute("value"), "20 ms",
|
||||
"The .A node's duration cell displays the correct value.");
|
||||
is($$perc(1).getAttribute("value"), "100%",
|
||||
"The .A node's percentage cell displays the correct value.");
|
||||
is($$sampl(1).getAttribute("value"), "4",
|
||||
is($$sampl(1).getAttribute("value"), "0",
|
||||
"The .A node's samples cell displays the correct value.");
|
||||
is($fun(".call-tree-name", $$(".call-tree-item")[1]).getAttribute("value"), "A",
|
||||
"The .A node's function cell displays the correct name.");
|
||||
@ -88,11 +88,11 @@ function test() {
|
||||
is(container.childNodes[3].className, "call-tree-item",
|
||||
"The .E node in the tree has the correct class name.");
|
||||
|
||||
is($$dur(2).getAttribute("value"), "8 ms",
|
||||
is($$dur(2).getAttribute("value"), "15 ms",
|
||||
"The .A.B node's duration cell displays the correct value.");
|
||||
is($$perc(2).getAttribute("value"), "75%",
|
||||
"The .A.B node's percentage cell displays the correct value.");
|
||||
is($$sampl(2).getAttribute("value"), "3",
|
||||
is($$sampl(2).getAttribute("value"), "0",
|
||||
"The .A.B node's samples cell displays the correct value.");
|
||||
is($fun(".call-tree-name", $$(".call-tree-item")[2]).getAttribute("value"), "B",
|
||||
"The .A.B node's function cell displays the correct name.");
|
||||
@ -107,11 +107,11 @@ function test() {
|
||||
is($fun(".call-tree-category", $$(".call-tree-item")[2]).getAttribute("value"), "Styles",
|
||||
"The .A.B node's function cell displays the correct category.");
|
||||
|
||||
is($$dur(3).getAttribute("value"), "7 ms",
|
||||
is($$dur(3).getAttribute("value"), "5 ms",
|
||||
"The .A.E node's duration cell displays the correct value.");
|
||||
is($$perc(3).getAttribute("value"), "25%",
|
||||
"The .A.E node's percentage cell displays the correct value.");
|
||||
is($$sampl(3).getAttribute("value"), "1",
|
||||
is($$sampl(3).getAttribute("value"), "0",
|
||||
"The .A.E node's samples cell displays the correct value.");
|
||||
is($fun(".call-tree-name", $$(".call-tree-item")[3]).getAttribute("value"), "E",
|
||||
"The .A.E node's function cell displays the correct name.");
|
||||
|
@ -10,7 +10,7 @@ function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode });
|
||||
@ -57,19 +57,19 @@ function test() {
|
||||
is($$name(6).getAttribute("value"), "F",
|
||||
"The .A.E.F node's function cell displays the correct name.");
|
||||
|
||||
is($$duration(0).getAttribute("value"), "15 ms",
|
||||
is($$duration(0).getAttribute("value"), "20 ms",
|
||||
"The root node's function cell displays the correct duration.");
|
||||
is($$duration(1).getAttribute("value"), "15 ms",
|
||||
is($$duration(1).getAttribute("value"), "20 ms",
|
||||
"The .A node's function cell displays the correct duration.");
|
||||
is($$duration(2).getAttribute("value"), "8 ms",
|
||||
is($$duration(2).getAttribute("value"), "15 ms",
|
||||
"The .A.B node's function cell displays the correct duration.");
|
||||
is($$duration(3).getAttribute("value"), "3 ms",
|
||||
is($$duration(3).getAttribute("value"), "10 ms",
|
||||
"The .A.B.D node's function cell displays the correct duration.");
|
||||
is($$duration(4).getAttribute("value"), "5 ms",
|
||||
"The .A.B.C node's function cell displays the correct duration.");
|
||||
is($$duration(5).getAttribute("value"), "7 ms",
|
||||
is($$duration(5).getAttribute("value"), "5 ms",
|
||||
"The .A.E node's function cell displays the correct duration.");
|
||||
is($$duration(6).getAttribute("value"), "7 ms",
|
||||
is($$duration(6).getAttribute("value"), "5 ms",
|
||||
"The .A.E.F node's function cell displays the correct duration.");
|
||||
|
||||
finish();
|
||||
|
@ -12,7 +12,7 @@ function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode });
|
||||
|
@ -10,7 +10,7 @@ function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode });
|
||||
|
@ -10,7 +10,7 @@ function* spawnTest() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode });
|
||||
|
@ -10,7 +10,7 @@ function* spawnTest() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode });
|
||||
|
@ -26,7 +26,7 @@ function test() {
|
||||
* - (JS)
|
||||
*/
|
||||
|
||||
let threadNode = new ThreadNode(gThread, { contentOnly: true });
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 30, contentOnly: true });
|
||||
// Don't display the synthesized (root) and the real (root) node twice.
|
||||
threadNode.calls = threadNode.calls[0].calls;
|
||||
let treeRoot = new CallView({ frame: threadNode, autoExpandDepth: 10 });
|
||||
|
@ -12,7 +12,7 @@ let test = Task.async(function*() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gSamples, { invertTree: true });
|
||||
let threadNode = new ThreadNode(gSamples, { invertTree: true, startTime: 0, endTime: 10 });
|
||||
let treeRoot = new CallView({ frame: threadNode, inverted: true, autoExpandDepth: 1 });
|
||||
|
||||
let container = document.createElement("vbox");
|
||||
|
@ -10,7 +10,7 @@ function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
let { CallView } = devtools.require("devtools/performance/tree-view");
|
||||
|
||||
let threadNode = new ThreadNode(gThread, { invertTree: true });
|
||||
let threadNode = new ThreadNode(gThread, { invertTree: true, startTime: 0, endTime: 50 });
|
||||
let treeRoot = new CallView({ frame: threadNode, inverted: true, hidden: true });
|
||||
|
||||
let container = document.createElement("vbox");
|
||||
|
@ -14,7 +14,7 @@ add_task(function test() {
|
||||
|
||||
// Create a root node from a given samples array.
|
||||
|
||||
let threadNode = new ThreadNode(gThread);
|
||||
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
|
||||
let root = getFrameNodePath(threadNode, "(root)");
|
||||
|
||||
// Test the root node.
|
||||
@ -22,8 +22,8 @@ add_task(function test() {
|
||||
equal(threadNode.getInfo().nodeType, "Thread",
|
||||
"The correct node type was retrieved for the root node.");
|
||||
|
||||
equal(root.duration, 20,
|
||||
"The correct duration was calculated for the root node.");
|
||||
equal(threadNode.duration, 20,
|
||||
"The correct duration was calculated for the ThreadNode.");
|
||||
equal(root.getInfo().functionName, "(root)",
|
||||
"The correct function name was retrieved for the root node.");
|
||||
equal(root.getInfo().categoryData.abbrev, "other",
|
||||
@ -82,32 +82,38 @@ add_task(function test() {
|
||||
equal(getFrameNodePath(root, "A > E > F").calls.length, 0,
|
||||
"The correct number of child calls were calculated for the 'A > E > F' node.");
|
||||
|
||||
// Check the location, sample times, duration and samples of the root.
|
||||
// Check the location, sample times, and samples of the root.
|
||||
|
||||
equal(getFrameNodePath(root, "A").location, "A",
|
||||
"The 'A' node has the correct location.");
|
||||
equal(getFrameNodePath(root, "A").duration, 20,
|
||||
"The 'A' node has the correct duration in milliseconds.");
|
||||
equal(getFrameNodePath(root, "A").youngestFrameSamples, 0,
|
||||
"The 'A' has correct `youngestFrameSamples`");
|
||||
equal(getFrameNodePath(root, "A").samples, 4,
|
||||
"The 'A' node has the correct number of samples.");
|
||||
"The 'A' has correct `samples`");
|
||||
|
||||
// A frame that is both a leaf and caught in another stack
|
||||
equal(getFrameNodePath(root, "A > B > C").youngestFrameSamples, 1,
|
||||
"The 'A > B > C' has correct `youngestFrameSamples`");
|
||||
equal(getFrameNodePath(root, "A > B > C").samples, 2,
|
||||
"The 'A > B > C' has correct `samples`");
|
||||
|
||||
// ...and the rightmost leaf.
|
||||
|
||||
equal(getFrameNodePath(root, "A > E > F").location, "F",
|
||||
"The 'A > E > F' node has the correct location.");
|
||||
equal(getFrameNodePath(root, "A > E > F").duration, 7,
|
||||
"The 'A > E > F' node has the correct duration in milliseconds.");
|
||||
equal(getFrameNodePath(root, "A > E > F").samples, 1,
|
||||
"The 'A > E > F' node has the correct number of samples.");
|
||||
equal(getFrameNodePath(root, "A > E > F").youngestFrameSamples, 1,
|
||||
"The 'A > E > F' node has the correct number of youngestFrameSamples.");
|
||||
|
||||
// ...and the leftmost leaf.
|
||||
|
||||
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").location, "G",
|
||||
"The 'A > B > C > D > E > F > G' node has the correct location.");
|
||||
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").duration, 2,
|
||||
"The 'A > B > C > D > E > F > G' node has the correct duration in milliseconds.");
|
||||
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").samples, 1,
|
||||
"The 'A > B > C > D > E > F > G' node has the correct number of samples.");
|
||||
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").youngestFrameSamples, 1,
|
||||
"The 'A > B > C > D > E > F > G' node has the correct number of youngestFrameSamples.");
|
||||
});
|
||||
|
||||
let gThread = synthesizeProfileForTest([{
|
||||
|
@ -14,12 +14,12 @@ add_task(function test() {
|
||||
|
||||
// Create a root node from a given samples array.
|
||||
|
||||
let root = getFrameNodePath(new ThreadNode(gThread), "(root)");
|
||||
let thread = new ThreadNode(gThread, { startTime: 0, endTime: 10 });
|
||||
let root = getFrameNodePath(thread, "(root)");
|
||||
|
||||
// Test the root node.
|
||||
|
||||
equal(root.duration, 5,
|
||||
"The correct duration was calculated for the root node.");
|
||||
// Test the ThreadNode, only node with a duration.
|
||||
equal(thread.duration, 10,
|
||||
"The correct duration was calculated for the ThreadNode.");
|
||||
|
||||
equal(root.calls.length, 1,
|
||||
"The correct number of child calls were calculated for the root node.");
|
||||
|
@ -20,12 +20,13 @@ add_task(function test() {
|
||||
// exactly at 18.
|
||||
let startTime = 5;
|
||||
let endTime = 18;
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { startTime, endTime }), "(root)");
|
||||
let thread = new ThreadNode(gThread, { startTime, endTime });
|
||||
let root = getFrameNodePath(thread, "(root)");
|
||||
|
||||
// Test the root node.
|
||||
|
||||
equal(root.duration, endTime - startTime,
|
||||
"The correct duration was calculated for the root node.");
|
||||
equal(thread.duration, endTime - startTime,
|
||||
"The correct duration was calculated for the ThreadNode.");
|
||||
|
||||
equal(root.calls.length, 1,
|
||||
"The correct number of child calls were calculated for the root node.");
|
||||
|
@ -17,12 +17,12 @@ add_task(function test() {
|
||||
|
||||
let startTime = 5;
|
||||
let endTime = 18;
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: startTime, endTime: endTime, contentOnly: true }), "(root)");
|
||||
let thread = new ThreadNode(gThread, { startTime, endTime, contentOnly: true });
|
||||
let root = getFrameNodePath(thread, "(root)");
|
||||
|
||||
// Test the root node.
|
||||
|
||||
equal(root.duration, endTime - startTime,
|
||||
"The correct duration was calculated for the root node.");
|
||||
// Test the ThreadNode, only node which should have duration
|
||||
equal(thread.duration, endTime - startTime,
|
||||
"The correct duration was calculated for the root ThreadNode.");
|
||||
|
||||
equal(root.calls.length, 2,
|
||||
"The correct number of child calls were calculated for the root node.");
|
||||
|
@ -49,7 +49,7 @@ function run_test() {
|
||||
add_task(function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
|
||||
let root = new ThreadNode(gThread, { invertTree: true });
|
||||
let root = new ThreadNode(gThread, { invertTree: true, startTime: 0, endTime: 4 });
|
||||
|
||||
equal(root.calls.length, 2,
|
||||
"Should get the 2 youngest frames, not the 1 oldest frame");
|
||||
|
@ -159,7 +159,7 @@ function run_test() {
|
||||
add_task(function test() {
|
||||
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
|
||||
|
||||
let root = new ThreadNode(gThread);
|
||||
let root = new ThreadNode(gThread, { startTime: 0, endTime: 4 });
|
||||
|
||||
let A = getFrameNodePath(root, "(root) > A");
|
||||
|
||||
|
@ -17,7 +17,7 @@ add_task(function test() {
|
||||
|
||||
// Create a root node from a given samples array.
|
||||
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)");
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: 5, endTime: 30, contentOnly: true }), "(root)");
|
||||
|
||||
/*
|
||||
* should have a tree like:
|
||||
|
@ -17,7 +17,7 @@ add_task(function test() {
|
||||
|
||||
// Create a root node from a given samples array.
|
||||
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)");
|
||||
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: 5, endTime: 25, contentOnly: true }), "(root)");
|
||||
|
||||
/*
|
||||
* should have a tree like:
|
||||
|
@ -20,7 +20,6 @@ let DetailsSubview = {
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onOverviewRangeChange);
|
||||
DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
|
||||
},
|
||||
|
||||
@ -34,7 +33,6 @@ let DetailsSubview = {
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
|
||||
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onOverviewRangeChange);
|
||||
DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
|
||||
},
|
||||
|
||||
@ -92,7 +90,7 @@ let DetailsSubview = {
|
||||
return;
|
||||
}
|
||||
if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {
|
||||
this.render();
|
||||
this.render(OverviewView.getTimeInterval());
|
||||
} else {
|
||||
this.shouldUpdateWhenShown = true;
|
||||
}
|
||||
@ -160,8 +158,3 @@ let DetailsSubview = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the view.
|
||||
*/
|
||||
EventEmitter.decorate(DetailsSubview);
|
||||
|
@ -131,3 +131,5 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
toString: () => "[object JsCallTreeView]"
|
||||
});
|
||||
|
||||
EventEmitter.decorate(JsCallTreeView);
|
||||
|
@ -115,3 +115,5 @@ let JsFlameGraphView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
toString: () => "[object JsFlameGraphView]"
|
||||
});
|
||||
|
||||
EventEmitter.decorate(JsFlameGraphView);
|
||||
|
@ -118,3 +118,5 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
toString: () => "[object MemoryCallTreeView]"
|
||||
});
|
||||
|
||||
EventEmitter.decorate(MemoryCallTreeView);
|
||||
|
@ -113,3 +113,5 @@ let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
toString: () => "[object MemoryFlameGraphView]"
|
||||
});
|
||||
|
||||
EventEmitter.decorate(MemoryFlameGraphView);
|
||||
|
@ -184,3 +184,5 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
toString: () => "[object WaterfallView]"
|
||||
});
|
||||
|
||||
EventEmitter.decorate(WaterfallView);
|
||||
|
@ -165,8 +165,10 @@ let OverviewView = {
|
||||
let mapEnd = () => recording.getDuration();
|
||||
let selection = this.graphs.getMappedSelection({ mapStart, mapEnd });
|
||||
// If no selection returned, this means the overview graphs have not been rendered
|
||||
// yet, so act as if we have no selection (the full recording).
|
||||
if (!selection) {
|
||||
// yet, so act as if we have no selection (the full recording). Also
|
||||
// if the selection range distance is tiny, assume the range was cleared or just
|
||||
// clicked, and we do not have a range.
|
||||
if (!selection || (selection.max - selection.min) < 1) {
|
||||
return { startTime: 0, endTime: recording.getDuration() };
|
||||
}
|
||||
return { startTime: selection.min, endTime: selection.max };
|
||||
@ -300,14 +302,8 @@ let OverviewView = {
|
||||
if (this._stopSelectionChangeEventPropagation) {
|
||||
return;
|
||||
}
|
||||
// If the range is smaller than a pixel (which can happen when performing
|
||||
// a click on the graphs), treat this as a cleared selection.
|
||||
let interval = this.getTimeInterval();
|
||||
if (interval.endTime - interval.startTime < 1) {
|
||||
this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
|
||||
} else {
|
||||
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
|
||||
}
|
||||
|
||||
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, this.getTimeInterval());
|
||||
},
|
||||
|
||||
_onGraphRendered: function (_, graphName) {
|
||||
|
@ -280,6 +280,28 @@ Telemetry.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log a value to a keyed histogram.
|
||||
*
|
||||
* @param {String} histogramId
|
||||
* Histogram in which the data is to be stored.
|
||||
* @param {String} key
|
||||
* The key within the single histogram.
|
||||
* @param value
|
||||
* Value to store.
|
||||
*/
|
||||
logKeyed: function(histogramId, key, value) {
|
||||
if (histogramId) {
|
||||
try {
|
||||
let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
|
||||
histogram.add(key, value);
|
||||
} catch(e) {
|
||||
dump("Warning: An attempt was made to write to the " + histogramId +
|
||||
" histogram, which is not defined in Histograms.json\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log info about usage once per browser version. This allows us to discover
|
||||
* how many individual users are using our tools for each browser version.
|
||||
|
@ -958,6 +958,21 @@ Rule.prototype = {
|
||||
} else {
|
||||
aTextProperty.rule.editor.closeBrace.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a string representation of the rule.
|
||||
*/
|
||||
stringifyRule: function() {
|
||||
let selectorText = this.selectorText;
|
||||
let cssText = "";
|
||||
let terminator = osString == "WINNT" ? "\r\n" : "\n";
|
||||
|
||||
for (let textProp of this.textProps) {
|
||||
cssText += "\t" + textProp.stringifyProperty() + terminator;
|
||||
}
|
||||
|
||||
return selectorText + " {" + terminator + cssText + "}";
|
||||
}
|
||||
};
|
||||
|
||||
@ -1083,6 +1098,21 @@ TextProperty.prototype = {
|
||||
|
||||
remove: function() {
|
||||
this.rule.removeProperty(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a string representation of the rule property.
|
||||
*/
|
||||
stringifyProperty: function() {
|
||||
// Get the displayed property value
|
||||
let declaration = this.name + ": " + this.editor.committed.value + ";";
|
||||
|
||||
// Comment out property declarations that are not enabled
|
||||
if (!this.enabled) {
|
||||
declaration = "/* " + declaration + " */";
|
||||
}
|
||||
|
||||
return declaration;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1136,6 +1166,12 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
|
||||
this._onCopy = this._onCopy.bind(this);
|
||||
this._onCopyColor = this._onCopyColor.bind(this);
|
||||
this._onCopyImageDataUrl = this._onCopyImageDataUrl.bind(this);
|
||||
this._onCopyLocation = this._onCopyLocation.bind(this);
|
||||
this._onCopyPropertyDeclaration = this._onCopyPropertyDeclaration.bind(this);
|
||||
this._onCopyPropertyName = this._onCopyPropertyName.bind(this);
|
||||
this._onCopyPropertyValue = this._onCopyPropertyValue.bind(this);
|
||||
this._onCopyRule = this._onCopyRule.bind(this);
|
||||
this._onCopySelector = this._onCopySelector.bind(this);
|
||||
this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
|
||||
this._onShowMdnDocs = this._onShowMdnDocs.bind(this);
|
||||
this._onFilterStyles = this._onFilterStyles.bind(this);
|
||||
@ -1223,31 +1259,70 @@ CssRuleView.prototype = {
|
||||
this._contextmenu.addEventListener("popupshowing", this._contextMenuUpdate);
|
||||
this._contextmenu.id = "rule-view-context-menu";
|
||||
|
||||
this.menuitemAddRule = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.addNewRule",
|
||||
accesskey: "ruleView.contextmenu.addNewRule.accessKey",
|
||||
command: this._onAddRule
|
||||
});
|
||||
this.menuitemSelectAll = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.selectAll",
|
||||
accesskey: "ruleView.contextmenu.selectAll.accessKey",
|
||||
command: this._onSelectAll
|
||||
});
|
||||
this.menuitemCopy = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copy",
|
||||
accesskey: "ruleView.contextmenu.copy.accessKey",
|
||||
command: this._onCopy
|
||||
});
|
||||
|
||||
this.menuitemCopyLocation = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copyLocation",
|
||||
command: this._onCopyLocation
|
||||
});
|
||||
|
||||
this.menuitemCopyRule = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copyRule",
|
||||
command: this._onCopyRule
|
||||
});
|
||||
|
||||
this.menuitemCopyColor = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copyColor",
|
||||
accesskey: "ruleView.contextmenu.copyColor.accessKey",
|
||||
command: this._onCopyColor
|
||||
});
|
||||
|
||||
this.menuitemCopyImageDataUrl = createMenuItem(this._contextmenu, {
|
||||
label: "styleinspector.contextmenu.copyImageDataUrl",
|
||||
accesskey: "styleinspector.contextmenu.copyImageDataUrl.accessKey",
|
||||
command: this._onCopyImageDataUrl
|
||||
});
|
||||
|
||||
this.menuitemCopyPropertyDeclaration = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copyPropertyDeclaration",
|
||||
command: this._onCopyPropertyDeclaration
|
||||
});
|
||||
|
||||
this.menuitemCopyPropertyName = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copyPropertyName",
|
||||
command: this._onCopyPropertyName
|
||||
});
|
||||
|
||||
this.menuitemCopyPropertyValue = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copyPropertyValue",
|
||||
command: this._onCopyPropertyValue
|
||||
});
|
||||
|
||||
this.menuitemCopySelector = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.copySelector",
|
||||
command: this._onCopySelector
|
||||
});
|
||||
|
||||
createMenuSeparator(this._contextmenu);
|
||||
|
||||
this.menuitemSelectAll = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.selectAll",
|
||||
accesskey: "ruleView.contextmenu.selectAll.accessKey",
|
||||
command: this._onSelectAll
|
||||
});
|
||||
|
||||
createMenuSeparator(this._contextmenu);
|
||||
|
||||
this.menuitemAddRule = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.addNewRule",
|
||||
accesskey: "ruleView.contextmenu.addNewRule.accessKey",
|
||||
command: this._onAddRule
|
||||
});
|
||||
|
||||
this.menuitemSources = createMenuItem(this._contextmenu, {
|
||||
label: "ruleView.contextmenu.showOrigSources",
|
||||
accesskey: "ruleView.contextmenu.showOrigSources.accessKey",
|
||||
@ -1360,6 +1435,23 @@ CssRuleView.prototype = {
|
||||
* appropriate.
|
||||
*/
|
||||
_contextMenuUpdate: function() {
|
||||
this._enableCopyMenuItems(this.doc.popupNode.parentNode);
|
||||
|
||||
this.menuitemAddRule.disabled = this.inspector.selection.isAnonymousNode();
|
||||
|
||||
this.menuitemShowMdnDocs.hidden = !this.enableMdnDocsTooltip ||
|
||||
!this.doc.popupNode.parentNode
|
||||
.classList.contains(PROPERTY_NAME_CLASS);
|
||||
|
||||
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
this.menuitemSources.setAttribute("checked", showOrig);
|
||||
},
|
||||
|
||||
/**
|
||||
* Display the necessary copy context menu items depending on the clicked
|
||||
* node and selection in the rule view.
|
||||
*/
|
||||
_enableCopyMenuItems: function(target) {
|
||||
let win = this.doc.defaultView;
|
||||
|
||||
// Copy selection.
|
||||
@ -1382,18 +1474,31 @@ CssRuleView.prototype = {
|
||||
copy = false;
|
||||
}
|
||||
|
||||
this.menuitemCopy.hidden = !copy;
|
||||
this.menuitemCopyColor.hidden = !this._isColorPopup();
|
||||
this.menuitemCopyImageDataUrl.hidden = !this._isImageUrlPopup();
|
||||
this.menuitemCopy.disabled = !copy;
|
||||
|
||||
let showOrig = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
this.menuitemSources.setAttribute("checked", showOrig);
|
||||
this.menuitemCopyLocation.hidden = true;
|
||||
this.menuitemCopyPropertyDeclaration.hidden = true;
|
||||
this.menuitemCopyPropertyName.hidden = true;
|
||||
this.menuitemCopyPropertyValue.hidden = true;
|
||||
this.menuitemCopySelector.hidden = true;
|
||||
|
||||
this.menuitemShowMdnDocs.hidden = !this.enableMdnDocsTooltip ||
|
||||
!this.doc.popupNode.parentNode
|
||||
.classList.contains(PROPERTY_NAME_CLASS);
|
||||
this._clickedNodeInfo = this.getNodeInfo(target);
|
||||
|
||||
this.menuitemAddRule.disabled = this.inspector.selection.isAnonymousNode();
|
||||
if (!this._clickedNodeInfo) {
|
||||
return;
|
||||
} else if (this._clickedNodeInfo.type == overlays.VIEW_NODE_PROPERTY_TYPE) {
|
||||
this.menuitemCopyPropertyDeclaration.hidden = false;
|
||||
this.menuitemCopyPropertyName.hidden = false;
|
||||
} else if (this._clickedNodeInfo.type == overlays.VIEW_NODE_VALUE_TYPE) {
|
||||
this.menuitemCopyPropertyDeclaration.hidden = false;
|
||||
this.menuitemCopyPropertyValue.hidden = false;
|
||||
} else if (this._clickedNodeInfo.type == overlays.VIEW_NODE_SELECTOR_TYPE) {
|
||||
this.menuitemCopySelector.hidden = false;
|
||||
} else if (this._clickedNodeInfo.type == overlays.VIEW_NODE_LOCATION_TYPE) {
|
||||
this.menuitemCopyLocation.hidden = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1422,7 +1527,8 @@ CssRuleView.prototype = {
|
||||
enabled: prop.enabled,
|
||||
overridden: prop.overridden,
|
||||
pseudoElement: prop.rule.pseudoElement,
|
||||
sheetHref: prop.rule.domRule.href
|
||||
sheetHref: prop.rule.domRule.href,
|
||||
textProperty: prop
|
||||
};
|
||||
} else if (classes.contains("ruleview-propertyvalue") && prop) {
|
||||
type = overlays.VIEW_NODE_VALUE_TYPE;
|
||||
@ -1432,7 +1538,8 @@ CssRuleView.prototype = {
|
||||
enabled: prop.enabled,
|
||||
overridden: prop.overridden,
|
||||
pseudoElement: prop.rule.pseudoElement,
|
||||
sheetHref: prop.rule.domRule.href
|
||||
sheetHref: prop.rule.domRule.href,
|
||||
textProperty: prop
|
||||
};
|
||||
} else if (classes.contains("theme-link") &&
|
||||
!classes.contains("ruleview-rule-source") && prop) {
|
||||
@ -1444,12 +1551,19 @@ CssRuleView.prototype = {
|
||||
enabled: prop.enabled,
|
||||
overridden: prop.overridden,
|
||||
pseudoElement: prop.rule.pseudoElement,
|
||||
sheetHref: prop.rule.domRule.href
|
||||
sheetHref: prop.rule.domRule.href,
|
||||
textProperty: prop
|
||||
};
|
||||
} else if (classes.contains("ruleview-selector-unmatched") ||
|
||||
classes.contains("ruleview-selector-matched")) {
|
||||
classes.contains("ruleview-selector-matched") ||
|
||||
classes.contains("ruleview-selector")) {
|
||||
type = overlays.VIEW_NODE_SELECTOR_TYPE;
|
||||
value = node.textContent;
|
||||
value = node.offsetParent._ruleEditor.selectorText.textContent;
|
||||
} else if (classes.contains("ruleview-rule-source")) {
|
||||
type = overlays.VIEW_NODE_LOCATION_TYPE;
|
||||
let ruleEditor = node.offsetParent._ruleEditor;
|
||||
let rule = ruleEditor.rule;
|
||||
value = (rule.sheet && rule.sheet.href) ? rule.sheet.href : rule.title;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -1624,6 +1738,71 @@ CssRuleView.prototype = {
|
||||
clipboardHelper.copyString(message);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Copy the rule source location of the current clicked node.
|
||||
*/
|
||||
_onCopyLocation: function() {
|
||||
if (!this._clickedNodeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
clipboardHelper.copyString(this._clickedNodeInfo.value, this.doc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the rule property declaration of the current clicked node.
|
||||
*/
|
||||
_onCopyPropertyDeclaration: function() {
|
||||
if (!this._clickedNodeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
let textProp = this._clickedNodeInfo.value.textProperty;
|
||||
clipboardHelper.copyString(textProp.stringifyProperty(), this.doc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the rule property name of the current clicked node.
|
||||
*/
|
||||
_onCopyPropertyName: function() {
|
||||
if (!this._clickedNodeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
clipboardHelper.copyString(this._clickedNodeInfo.value.property, this.doc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the rule property value of the current clicked node.
|
||||
*/
|
||||
_onCopyPropertyValue: function() {
|
||||
if (!this._clickedNodeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
clipboardHelper.copyString(this._clickedNodeInfo.value.value, this.doc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the rule of the current clicked node.
|
||||
*/
|
||||
_onCopyRule: function() {
|
||||
let ruleEditor = this.doc.popupNode.parentNode.offsetParent._ruleEditor;
|
||||
let rule = ruleEditor.rule;
|
||||
clipboardHelper.copyString(rule.stringifyRule(), this.doc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the rule selector of the current clicked node.
|
||||
*/
|
||||
_onCopySelector: function() {
|
||||
if (!this._clickedNodeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
clipboardHelper.copyString(this._clickedNodeInfo.value, this.doc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the original sources pref.
|
||||
*/
|
||||
@ -1831,16 +2010,43 @@ CssRuleView.prototype = {
|
||||
this.menuitemCopyColor = null;
|
||||
|
||||
// Destroy Copy Data URI menuitem.
|
||||
this.menuitemCopyImageDataUrl.removeEventListener("command", this._onCopyImageDataUrl);
|
||||
this.menuitemCopyImageDataUrl.removeEventListener("command",
|
||||
this._onCopyImageDataUrl);
|
||||
this.menuitemCopyImageDataUrl = null;
|
||||
|
||||
this.menuitemCopyLocation.removeEventListener("command",
|
||||
this._onCopyLocation);
|
||||
this.menuitemCopyLocation = null;
|
||||
|
||||
this.menuitemCopyPropertyDeclaration.removeEventListener("command",
|
||||
this._onCopyPropertyDeclaration);
|
||||
this.menuitemCopyPropertyDeclaration = null;
|
||||
|
||||
this.menuitemCopyPropertyName.removeEventListener("command",
|
||||
this._onCopyPropertyName);
|
||||
this.menuitemCopyPropertyName = null;
|
||||
|
||||
this.menuitemCopyPropertyValue.removeEventListener("command",
|
||||
this._onCopyPropertyValue);
|
||||
this.menuitemCopyPropertyValue = null;
|
||||
|
||||
this.menuitemCopyRule.removeEventListener("command",
|
||||
this._onCopyRule);
|
||||
this.menuitemCopyRule = null;
|
||||
|
||||
this.menuitemCopySelector.removeEventListener("command",
|
||||
this._onCopySelector);
|
||||
this.menuitemCopySelector = null;
|
||||
|
||||
this.menuitemSources.removeEventListener("command",
|
||||
this._onToggleOrigSources);
|
||||
this._onToggleOrigSources);
|
||||
this.menuitemSources = null;
|
||||
|
||||
this._clickedNodeInfo = null;
|
||||
|
||||
// Destroy the context menu.
|
||||
this._contextmenu.removeEventListener("popupshowing",
|
||||
this._contextMenuUpdate);
|
||||
this._contextMenuUpdate);
|
||||
this._contextmenu.parentNode.removeChild(this._contextmenu);
|
||||
this._contextmenu = null;
|
||||
}
|
||||
@ -3628,8 +3834,12 @@ function createMenuItem(aMenu, aAttributes) {
|
||||
let item = aMenu.ownerDocument.createElementNS(XUL_NS, "menuitem");
|
||||
|
||||
item.setAttribute("label", _strings.GetStringFromName(aAttributes.label));
|
||||
item.setAttribute("accesskey",
|
||||
_strings.GetStringFromName(aAttributes.accesskey));
|
||||
|
||||
if (aAttributes.accesskey) {
|
||||
item.setAttribute("accesskey",
|
||||
_strings.GetStringFromName(aAttributes.accesskey));
|
||||
}
|
||||
|
||||
item.addEventListener("command", aAttributes.command);
|
||||
|
||||
if (aAttributes.type) {
|
||||
@ -3641,6 +3851,11 @@ function createMenuItem(aMenu, aAttributes) {
|
||||
return item;
|
||||
}
|
||||
|
||||
function createMenuSeparator(aMenu) {
|
||||
let separator = aMenu.ownerDocument.createElementNS(XUL_NS, "menuseparator");
|
||||
aMenu.appendChild(separator);
|
||||
}
|
||||
|
||||
function setTimeout() {
|
||||
let window = Services.appShell.hiddenDOMWindow;
|
||||
return window.setTimeout.apply(window, arguments);
|
||||
@ -3795,6 +4010,10 @@ XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
|
||||
.getService(Ci.nsIClipboardHelper);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "osString", function() {
|
||||
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "_strings", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://global/locale/devtools/styleinspector.properties");
|
||||
|
@ -37,6 +37,7 @@ const VIEW_NODE_SELECTOR_TYPE = exports.VIEW_NODE_SELECTOR_TYPE = 1;
|
||||
const VIEW_NODE_PROPERTY_TYPE = exports.VIEW_NODE_PROPERTY_TYPE = 2;
|
||||
const VIEW_NODE_VALUE_TYPE = exports.VIEW_NODE_VALUE_TYPE = 3;
|
||||
const VIEW_NODE_IMAGE_URL_TYPE = exports.VIEW_NODE_IMAGE_URL_TYPE = 4;
|
||||
const VIEW_NODE_LOCATION_TYPE = exports.VIEW_NODE_LOCATION_TYPE = 5;
|
||||
|
||||
/**
|
||||
* Manages all highlighters in the style-inspector.
|
||||
|
@ -9,6 +9,8 @@ support-files =
|
||||
doc_content_stylesheet_linked.css
|
||||
doc_content_stylesheet_script.css
|
||||
doc_content_stylesheet_xul.css
|
||||
doc_copystyles.css
|
||||
doc_copystyles.html
|
||||
doc_filter.html
|
||||
doc_frame_script.js
|
||||
doc_keyframeanimation.html
|
||||
@ -76,6 +78,7 @@ skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work wit
|
||||
[browser_ruleview_context-menu-show-mdn-docs-01.js]
|
||||
[browser_ruleview_context-menu-show-mdn-docs-02.js]
|
||||
[browser_ruleview_context-menu-show-mdn-docs-03.js]
|
||||
[browser_ruleview_copy_styles.js]
|
||||
[browser_ruleview_cubicbezier-appears-on-swatch-click.js]
|
||||
[browser_ruleview_cubicbezier-commit-on-ENTER.js]
|
||||
[browser_ruleview_cubicbezier-revert-on-ESC.js]
|
||||
|
@ -0,0 +1,246 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests the behaviour of the copy styles context menu items in the rule
|
||||
* view
|
||||
*/
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "osString", function() {
|
||||
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
||||
});
|
||||
|
||||
let TEST_URI = TEST_URL_ROOT + "doc_copystyles.html";
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URI);
|
||||
let { inspector, view } = yield openRuleView();
|
||||
|
||||
yield selectNode("#testid", inspector);
|
||||
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
|
||||
let data = [
|
||||
{
|
||||
desc: "Test Copy Property Name",
|
||||
node: ruleEditor.rule.textProps[0].editor.nameSpan,
|
||||
menuItem: view.menuitemCopyPropertyName,
|
||||
expectedPattern: "color",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Value",
|
||||
node: ruleEditor.rule.textProps[2].editor.valueSpan,
|
||||
menuItem: view.menuitemCopyPropertyValue,
|
||||
expectedPattern: "12px",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: false,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Declaration",
|
||||
node: ruleEditor.rule.textProps[2].editor.nameSpan,
|
||||
menuItem: view.menuitemCopyPropertyDeclaration,
|
||||
expectedPattern: "font-size: 12px;",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Rule",
|
||||
node: ruleEditor.rule.textProps[2].editor.nameSpan,
|
||||
menuItem: view.menuitemCopyRule,
|
||||
expectedPattern: "#testid {[\\r\\n]+" +
|
||||
"\tcolor: #F00;[\\r\\n]+" +
|
||||
"\tbackground-color: #00F;[\\r\\n]+" +
|
||||
"\tfont-size: 12px;[\\r\\n]+" +
|
||||
"}",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Selector",
|
||||
node: ruleEditor.selectorText,
|
||||
menuItem: view.menuitemCopySelector,
|
||||
expectedPattern: "html, body, #testid",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: true,
|
||||
copySelector: false,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Location",
|
||||
node: ruleEditor.source,
|
||||
menuItem: view.menuitemCopyLocation,
|
||||
expectedPattern: "http://example.com/browser/browser/devtools/" +
|
||||
"styleinspector/test/doc_copystyles.css",
|
||||
hidden: {
|
||||
copyLocation: false,
|
||||
copyPropertyDeclaration: true,
|
||||
copyPropertyName: true,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
setup: function*() {
|
||||
yield disableProperty(view);
|
||||
},
|
||||
desc: "Test Copy Rule with Disabled Property",
|
||||
node: ruleEditor.rule.textProps[2].editor.nameSpan,
|
||||
menuItem: view.menuitemCopyRule,
|
||||
expectedPattern: "#testid {[\\r\\n]+" +
|
||||
"\t\/\\* color: #F00; \\*\/[\\r\\n]+" +
|
||||
"\tbackground-color: #00F;[\\r\\n]+" +
|
||||
"\tfont-size: 12px;[\\r\\n]+" +
|
||||
"}",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
desc: "Test Copy Property Declaration with Disabled Property",
|
||||
node: ruleEditor.rule.textProps[0].editor.nameSpan,
|
||||
menuItem: view.menuitemCopyPropertyDeclaration,
|
||||
expectedPattern: "\/\\* color: #F00; \\*\/",
|
||||
hidden: {
|
||||
copyLocation: true,
|
||||
copyPropertyDeclaration: false,
|
||||
copyPropertyName: false,
|
||||
copyPropertyValue: true,
|
||||
copySelector: true,
|
||||
copyRule: false
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
for (let { setup, desc, node, menuItem, expectedPattern, hidden } of data) {
|
||||
if (setup) {
|
||||
yield setup();
|
||||
}
|
||||
|
||||
info(desc);
|
||||
yield checkCopyStyle(view, node, menuItem, expectedPattern, hidden);
|
||||
}
|
||||
});
|
||||
|
||||
function* checkCopyStyle(view, node, menuItem, expectedPattern, hidden) {
|
||||
let win = view.doc.defaultView;
|
||||
|
||||
let onPopup = once(view._contextmenu, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(node,
|
||||
{button: 2, type: "contextmenu"}, win);
|
||||
yield onPopup;
|
||||
|
||||
is(view.menuitemCopy.hidden, true, "Copy hidden is as expected: true");
|
||||
|
||||
is(view.menuitemCopyLocation.hidden,
|
||||
hidden.copyLocation,
|
||||
"Copy Location hidden attribute is as expected: " +
|
||||
hidden.copyLocation);
|
||||
|
||||
is(view.menuitemCopyPropertyDeclaration.hidden,
|
||||
hidden.copyPropertyDeclaration,
|
||||
"Copy Property Declaration hidden attribute is as expected: " +
|
||||
hidden.copyPropertyDeclaration);
|
||||
|
||||
is(view.menuitemCopyPropertyName.hidden,
|
||||
hidden.copyPropertyName,
|
||||
"Copy Property Name hidden attribute is as expected: " +
|
||||
hidden.copyPropertyName);
|
||||
|
||||
is(view.menuitemCopyPropertyValue.hidden,
|
||||
hidden.copyPropertyValue,
|
||||
"Copy Property Value hidden attribute is as expected: " +
|
||||
hidden.copyPropertyValue);
|
||||
|
||||
is(view.menuitemCopySelector.hidden,
|
||||
hidden.copySelector,
|
||||
"Copy Selector hidden attribute is as expected: " +
|
||||
hidden.copySelector);
|
||||
|
||||
is(view.menuitemCopyRule.hidden,
|
||||
hidden.copyRule,
|
||||
"Copy Rule hidden attribute is as expected: " +
|
||||
hidden.copyRule);
|
||||
|
||||
try {
|
||||
yield waitForClipboard(() => menuItem.click(),
|
||||
() => checkClipboardData(expectedPattern));
|
||||
} catch(e) {
|
||||
failedClipboard(expectedPattern);
|
||||
}
|
||||
|
||||
view._contextmenu.hidePopup();
|
||||
}
|
||||
|
||||
function* disableProperty(view) {
|
||||
let ruleEditor = getRuleViewRuleEditor(view, 1);
|
||||
let propEditor = ruleEditor.rule.textProps[0].editor;
|
||||
|
||||
info("Disabling a property");
|
||||
propEditor.enable.click();
|
||||
yield ruleEditor.rule._applyingModifications;
|
||||
}
|
||||
|
||||
function checkClipboardData(expectedPattern) {
|
||||
let actual = SpecialPowers.getClipboardData("text/unicode");
|
||||
let expectedRegExp = new RegExp(expectedPattern, "g");
|
||||
return expectedRegExp.test(actual);
|
||||
}
|
||||
|
||||
function failedClipboard(expectedPattern) {
|
||||
// Format expected text for comparison
|
||||
let terminator = osString == "WINNT" ? "\r\n" : "\n";
|
||||
expectedPattern = expectedPattern.replace(/\[\\r\\n\][+*]/g, terminator);
|
||||
expectedPattern = expectedPattern.replace(/\\\(/g, "(");
|
||||
expectedPattern = expectedPattern.replace(/\\\)/g, ")");
|
||||
|
||||
let actual = SpecialPowers.getClipboardData("text/unicode");
|
||||
|
||||
// Trim the right hand side of our strings. This is because expectedPattern
|
||||
// accounts for windows sometimes adding a newline to our copied data.
|
||||
expectedPattern = expectedPattern.trimRight();
|
||||
actual = actual.trimRight();
|
||||
|
||||
ok(false, "Clipboard text does not match expected " +
|
||||
"results (escaped for accurate comparison):\n");
|
||||
info("Actual: " + escape(actual));
|
||||
info("Expected: " + escape(expectedPattern));
|
||||
}
|
@ -33,8 +33,7 @@ add_task(function*() {
|
||||
'</div>';
|
||||
content.document.title = "Rule view context menu test";
|
||||
|
||||
info("Opening the computed view");
|
||||
let {toolbox, inspector, view} = yield openRuleView();
|
||||
let {inspector, view} = yield openRuleView();
|
||||
|
||||
info("Selecting the test node");
|
||||
yield selectNode("div", inspector);
|
||||
@ -43,17 +42,18 @@ add_task(function*() {
|
||||
yield checkSelectAll(view);
|
||||
});
|
||||
|
||||
function checkCopySelection(view) {
|
||||
function* checkCopySelection(view) {
|
||||
info("Testing selection copy");
|
||||
|
||||
let contentDoc = view.doc;
|
||||
let win = contentDoc.defaultView;
|
||||
let prop = contentDoc.querySelector(".ruleview-property");
|
||||
let values = contentDoc.querySelectorAll(".ruleview-propertyvaluecontainer");
|
||||
|
||||
let range = contentDoc.createRange();
|
||||
range.setStart(prop, 0);
|
||||
range.setEnd(values[4], 2);
|
||||
let selection = view.doc.defaultView.getSelection().addRange(range);
|
||||
view.doc.defaultView.getSelection().addRange(range);
|
||||
|
||||
info("Checking that _Copy() returns the correct clipboard value");
|
||||
|
||||
@ -65,19 +65,28 @@ function checkCopySelection(view) {
|
||||
"html {[\\r\\n]+" +
|
||||
" color: #000;[\\r\\n]*";
|
||||
|
||||
return waitForClipboard(() => {
|
||||
fireCopyEvent(prop);
|
||||
}, () => {
|
||||
return checkClipboardData(expectedPattern);
|
||||
}).then(() => {}, () => {
|
||||
let onPopup = once(view._contextmenu, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(prop,
|
||||
{button: 2, type: "contextmenu"}, win);
|
||||
yield onPopup;
|
||||
|
||||
ok(!view.menuitemCopy.hidden, "Copy menu item is not hidden as expected");
|
||||
|
||||
try {
|
||||
yield waitForClipboard(() => view.menuitemCopy.click(),
|
||||
() => checkClipboardData(expectedPattern));
|
||||
} catch(e) {
|
||||
failedClipboard(expectedPattern);
|
||||
});
|
||||
}
|
||||
|
||||
view._contextmenu.hidePopup();
|
||||
}
|
||||
|
||||
function checkSelectAll(view) {
|
||||
function* checkSelectAll(view) {
|
||||
info("Testing select-all copy");
|
||||
|
||||
let contentDoc = view.doc;
|
||||
let win = contentDoc.defaultView;
|
||||
let prop = contentDoc.querySelector(".ruleview-property");
|
||||
|
||||
info("Checking that _SelectAll() then copy returns the correct clipboard value");
|
||||
@ -93,13 +102,21 @@ function checkSelectAll(view) {
|
||||
" color: #000;[\\r\\n]+" +
|
||||
"}[\\r\\n]*";
|
||||
|
||||
return waitForClipboard(() => {
|
||||
fireCopyEvent(prop);
|
||||
}, () => {
|
||||
return checkClipboardData(expectedPattern);
|
||||
}).then(() => {}, () => {
|
||||
let onPopup = once(view._contextmenu, "popupshown");
|
||||
EventUtils.synthesizeMouseAtCenter(prop,
|
||||
{button: 2, type: "contextmenu"}, win);
|
||||
yield onPopup;
|
||||
|
||||
ok(!view.menuitemCopy.hidden, "Copy menu item is not hidden as expected");
|
||||
|
||||
try {
|
||||
yield waitForClipboard(() => view.menuitemCopy.click(),
|
||||
() => checkClipboardData(expectedPattern));
|
||||
} catch(e) {
|
||||
failedClipboard(expectedPattern);
|
||||
});
|
||||
}
|
||||
|
||||
view._contextmenu.hidePopup();
|
||||
}
|
||||
|
||||
function checkClipboardData(expectedPattern) {
|
||||
|
9
browser/devtools/styleinspector/test/doc_copystyles.css
Normal file
9
browser/devtools/styleinspector/test/doc_copystyles.css
Normal file
@ -0,0 +1,9 @@
|
||||
/* 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/. */
|
||||
|
||||
html, body, #testid {
|
||||
color: #F00;
|
||||
background-color: #00F;
|
||||
font-size: 12px;
|
||||
}
|
11
browser/devtools/styleinspector/test/doc_copystyles.html
Normal file
11
browser/devtools/styleinspector/test/doc_copystyles.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Test case for copying stylesheet in rule-view</title>
|
||||
<link rel="stylesheet" type="text/css" href="doc_copystyles.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id='testid'>Styled Node</div>
|
||||
</body>
|
||||
</html>
|
@ -129,6 +129,8 @@ let AppManager = exports.AppManager = {
|
||||
* runtime-list:
|
||||
* The list of available runtimes has changed, or any of the user-visible
|
||||
* details (like names) for the non-selected runtimes has changed.
|
||||
* runtime-telemetry:
|
||||
* Detailed runtime telemetry has been recorded. Used by tests.
|
||||
* runtime-targets:
|
||||
* The list of remote runtime targets available from the currently
|
||||
* connected runtime (such as tabs or apps) has changed, or any of the
|
||||
@ -183,6 +185,7 @@ let AppManager = exports.AppManager = {
|
||||
// first.
|
||||
this._appsFront = front;
|
||||
this._listTabsResponse = response;
|
||||
this._recordRuntimeInfo();
|
||||
this.update("runtime-global-actors");
|
||||
})
|
||||
.then(() => {
|
||||
@ -192,6 +195,7 @@ let AppManager = exports.AppManager = {
|
||||
});
|
||||
} else {
|
||||
this._listTabsResponse = response;
|
||||
this._recordRuntimeInfo();
|
||||
this.update("runtime-global-actors");
|
||||
}
|
||||
});
|
||||
@ -498,6 +502,33 @@ let AppManager = exports.AppManager = {
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
_recordRuntimeInfo: Task.async(function*() {
|
||||
if (!this.connected) {
|
||||
return;
|
||||
}
|
||||
let runtime = this.selectedRuntime;
|
||||
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE",
|
||||
runtime.type || "UNKNOWN", true);
|
||||
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID",
|
||||
runtime.id || "unknown", true);
|
||||
if (!this.deviceFront) {
|
||||
this.update("runtime-telemetry");
|
||||
return;
|
||||
}
|
||||
let d = yield this.deviceFront.getDescription();
|
||||
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR",
|
||||
d.processor, true);
|
||||
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS",
|
||||
d.os, true);
|
||||
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION",
|
||||
d.platformversion, true);
|
||||
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE",
|
||||
d.apptype, true);
|
||||
this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION",
|
||||
d.version, true);
|
||||
this.update("runtime-telemetry");
|
||||
}),
|
||||
|
||||
isMainProcessDebuggable: function() {
|
||||
// Fx <39 exposes chrome tab actors on RootActor
|
||||
// Fx >=39 exposes a dedicated actor via getProcess request
|
||||
|
@ -35,11 +35,18 @@
|
||||
this.telemetryInfo[histogramId].push(value);
|
||||
}
|
||||
}
|
||||
Telemetry.prototype._oldlogKeyed = Telemetry.prototype.logKeyed;
|
||||
Telemetry.prototype.logKeyed = function(histogramId, key, value) {
|
||||
// This simple reduction is enough to test WebIDE's usage
|
||||
this.log(`${histogramId}|${key}`, value);
|
||||
}
|
||||
}
|
||||
|
||||
function resetTelemetry() {
|
||||
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||
Telemetry.prototype.logKeyed = Telemetry.prototype._oldlogKeyed;
|
||||
delete Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype._oldlogKeyed;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
}
|
||||
|
||||
@ -76,7 +83,7 @@
|
||||
};
|
||||
win.AppManager.runtimeList.wifi.push(wifi);
|
||||
|
||||
let sim = new _SimulatorRuntime("fakeSimulator");
|
||||
let sim = new _SimulatorRuntime({ id: "fakeSimulator" });
|
||||
// Use local pipe instead
|
||||
sim.connect = function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
@ -139,6 +146,10 @@
|
||||
ok(win.document.querySelector("window").className, "busy", "UI is busy");
|
||||
yield win.UI._busyPromise;
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
|
||||
// Logging runtime info needs to use the device actor
|
||||
yield waitForUpdate(win, "runtime-global-actors");
|
||||
// Ensure detailed telemetry is recorded
|
||||
yield waitForUpdate(win, "runtime-telemetry");
|
||||
});
|
||||
}
|
||||
|
||||
@ -198,6 +209,48 @@
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " actions were skipped");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|USB") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|WIFI") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|SIMULATOR") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|REMOTE") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|LOCAL") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|OTHER") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeUSB") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeWiFi") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeSimulator") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|unknown") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|local") {
|
||||
is(value.length, 2, histId + " has 2 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR")) {
|
||||
let processor = histId.split("|")[1];
|
||||
is(processor, Services.appinfo.XPCOMABI.split("-")[0], "Found runtime processor");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS")) {
|
||||
let os = histId.split("|")[1];
|
||||
is(os, Services.appinfo.OS, "Found runtime OS");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION")) {
|
||||
let platformversion = histId.split("|")[1];
|
||||
is(platformversion, Services.appinfo.platformVersion, "Found runtime platform version");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE")) {
|
||||
let apptype = histId.split("|")[1];
|
||||
is(apptype, "firefox", "Found runtime app type");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION")) {
|
||||
let version = histId.split("|")[1];
|
||||
is(version, Services.appinfo.version, "Found runtime version");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else {
|
||||
ok(false, "Unexpected " + histId + " was logged");
|
||||
}
|
||||
@ -205,12 +258,13 @@
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
SimpleTest.testInChaosMode();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let win;
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Task.spawn(function*() {
|
||||
return Task.spawn(function*() {
|
||||
if (win) {
|
||||
yield closeWebIDE(win);
|
||||
}
|
||||
@ -245,18 +299,13 @@
|
||||
// Each one should log a connection result and non-zero connection
|
||||
// time
|
||||
yield connectToRuntime(win, docRuntime, "usb");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "wifi");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "simulator");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "other", 0 /* remote */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "other", 1 /* local */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, docRuntime, "other", 2 /* other */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield closeWebIDE(win);
|
||||
win = null;
|
||||
|
||||
checkResults();
|
||||
|
||||
|
@ -35,11 +35,18 @@
|
||||
this.telemetryInfo[histogramId].push(value);
|
||||
}
|
||||
}
|
||||
Telemetry.prototype._oldlogKeyed = Telemetry.prototype.logKeyed;
|
||||
Telemetry.prototype.logKeyed = function(histogramId, key, value) {
|
||||
// This simple reduction is enough to test WebIDE's usage
|
||||
this.log(`${histogramId}|${key}`, value);
|
||||
}
|
||||
}
|
||||
|
||||
function resetTelemetry() {
|
||||
Telemetry.prototype.log = Telemetry.prototype._oldlog;
|
||||
Telemetry.prototype.logKeyed = Telemetry.prototype._oldlogKeyed;
|
||||
delete Telemetry.prototype._oldlog;
|
||||
delete Telemetry.prototype._oldlogKeyed;
|
||||
delete Telemetry.prototype.telemetryInfo;
|
||||
}
|
||||
|
||||
@ -76,7 +83,7 @@
|
||||
};
|
||||
win.AppManager.runtimeList.wifi.push(wifi);
|
||||
|
||||
let sim = new _SimulatorRuntime("fakeSimulator");
|
||||
let sim = new _SimulatorRuntime({ id: "fakeSimulator" });
|
||||
// Use local pipe instead
|
||||
sim.connect = function(connection) {
|
||||
ok(connection, win.AppManager.connection, "connection is valid");
|
||||
@ -139,6 +146,10 @@
|
||||
ok(win.document.querySelector("window").className, "busy", "UI is busy");
|
||||
yield win.UI._busyPromise;
|
||||
is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
|
||||
// Logging runtime info needs to use the device actor
|
||||
yield waitForUpdate(win, "runtime-global-actors");
|
||||
// Ensure detailed telemetry is recorded
|
||||
yield waitForUpdate(win, "runtime-telemetry");
|
||||
});
|
||||
}
|
||||
|
||||
@ -198,6 +209,48 @@
|
||||
});
|
||||
|
||||
ok(okay, "All " + histId + " actions were skipped");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|USB") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|WIFI") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|SIMULATOR") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|REMOTE") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|LOCAL") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|OTHER") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeUSB") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeWiFi") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeSimulator") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|unknown") {
|
||||
is(value.length, 1, histId + " has 1 connection results");
|
||||
} else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|local") {
|
||||
is(value.length, 2, histId + " has 2 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR")) {
|
||||
let processor = histId.split("|")[1];
|
||||
is(processor, Services.appinfo.XPCOMABI.split("-")[0], "Found runtime processor");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS")) {
|
||||
let os = histId.split("|")[1];
|
||||
is(os, Services.appinfo.OS, "Found runtime OS");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION")) {
|
||||
let platformversion = histId.split("|")[1];
|
||||
is(platformversion, Services.appinfo.platformVersion, "Found runtime platform version");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE")) {
|
||||
let apptype = histId.split("|")[1];
|
||||
is(apptype, "firefox", "Found runtime app type");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION")) {
|
||||
let version = histId.split("|")[1];
|
||||
is(version, Services.appinfo.version, "Found runtime version");
|
||||
is(value.length, 6, histId + " has 6 connection results");
|
||||
} else {
|
||||
ok(false, "Unexpected " + histId + " was logged");
|
||||
}
|
||||
@ -205,12 +258,13 @@
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
SimpleTest.testInChaosMode();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let win;
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Task.spawn(function*() {
|
||||
return Task.spawn(function*() {
|
||||
if (win) {
|
||||
yield closeWebIDE(win);
|
||||
}
|
||||
@ -242,18 +296,13 @@
|
||||
// Each one should log a connection result and non-zero connection
|
||||
// time
|
||||
yield connectToRuntime(win, "usb");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, "wifi");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, "simulator");
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, "other", 0 /* remote */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, "other", 1 /* local */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield connectToRuntime(win, "other", 2 /* other */);
|
||||
yield waitForTime(TOOL_DELAY);
|
||||
yield closeWebIDE(win);
|
||||
win = null;
|
||||
|
||||
checkResults();
|
||||
|
||||
|
@ -301,8 +301,7 @@ browser.jar:
|
||||
* skin/classic/browser/devtools/ruleview.css (../shared/devtools/ruleview.css)
|
||||
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
|
||||
skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
|
||||
skin/classic/browser/devtools/webconsole.png (../shared/devtools/images/webconsole.png)
|
||||
skin/classic/browser/devtools/webconsole@2x.png (../shared/devtools/images/webconsole@2x.png)
|
||||
skin/classic/browser/devtools/webconsole.svg (../shared/devtools/images/webconsole.svg)
|
||||
skin/classic/browser/devtools/commandline.css (../shared/devtools/commandline.css)
|
||||
skin/classic/browser/devtools/markup-view.css (../shared/devtools/markup-view.css)
|
||||
skin/classic/browser/devtools/editor-error.png (../shared/devtools/images/editor-error.png)
|
||||
|
@ -419,8 +419,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png)
|
||||
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
|
||||
skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
|
||||
skin/classic/browser/devtools/webconsole.png (../shared/devtools/images/webconsole.png)
|
||||
skin/classic/browser/devtools/webconsole@2x.png (../shared/devtools/images/webconsole@2x.png)
|
||||
skin/classic/browser/devtools/webconsole.svg (../shared/devtools/images/webconsole.svg)
|
||||
skin/classic/browser/devtools/breadcrumbs-divider@2x.png (../shared/devtools/images/breadcrumbs-divider@2x.png)
|
||||
skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (../shared/devtools/images/breadcrumbs-scrollbutton.png)
|
||||
skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.9 KiB |
101
browser/themes/shared/devtools/images/webconsole.svg
Normal file
101
browser/themes/shared/devtools/images/webconsole.svg
Normal file
@ -0,0 +1,101 @@
|
||||
<?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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="72" height="60" viewBox="0 0 72 60">
|
||||
<defs>
|
||||
<rect id="glyphShape-colorSwatch" width="8" height="8" ry="2" rx="2"/>
|
||||
<rect id="glyphShape-colorSwatch-border" width="10" height="10" ry="2" rx="2"/>
|
||||
<polygon id="glyphShape-errorX" points="9.9,8.5 8.5,9.9 6,7.4 3.6,9.8 2.2,8.4 4.6,6 2.2,3.6 3.6,2.2 6,4.6 8.4,2.2 9.8,3.6 7.4,6"/>
|
||||
<path id="glyphShape-warningTriangle" d="M9.9,8.6l-3.1-6C6.6,2.2,6.3,2,6,2C5.7,2,5.4,2.2,5.2,2.5l-3.1,6C2,8.9,2,9.3,2.1,9.6C2.3,9.8,2.6,10,2.9,10 h6.1c0.4,0,0.6-0.2,0.8-0.4C10,9.3,10,8.9,9.9,8.6z"/>
|
||||
<path id="glyphShape-exclamationPoint" d="M6,7.7c-0.6,0-1,0.4-1,0.8C5,9,5.4,9.3,6,9.3c0.6,0,1-0.4,1-0.8 C7,8.1,6.6,7.7,6,7.7z M6,7c0.6,0,1-0.4,1-1V5c0-0.6-0.4-1-1-1S5,4.4,5,5v1C5,6.6,5.4,7,6,7z"/>
|
||||
<circle id="glyphShape-infoCircle" cx="6" cy="6" r="4"/>
|
||||
<path id="glyphShape-infoGlyph" d="M6,6C5.4,6,5,6.4,5,7v1c0,0.6,0.4,1,1,1s1-0.4,1-1V7C7,6.4,6.6,6,6,6z M6,5c0.6,0,1-0.4,1-1S6.6,3,6,3S5,3.4,5,4S5.4,5,6,5z"/>
|
||||
<style>
|
||||
.icon-colorSwatch-border {
|
||||
fill: #fff;
|
||||
fill-opacity: .7;
|
||||
}
|
||||
.icon-colorSwatch-network {
|
||||
fill: #000;
|
||||
}
|
||||
.icon-colorSwatch-css {
|
||||
fill: #00b6f0;
|
||||
}
|
||||
.icon-colorSwatch-js {
|
||||
fill: #fb9500;
|
||||
}
|
||||
.icon-colorSwatch-logging {
|
||||
fill: #808080;
|
||||
}
|
||||
.icon-colorSwatch-security {
|
||||
fill: #ec1e0d;
|
||||
}
|
||||
.icon-glyphOverlay {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
#icon-indicator-input {
|
||||
fill: #8fa1b2;
|
||||
}
|
||||
#icon-indicator-output {
|
||||
fill: #667380;
|
||||
}
|
||||
#light-icons:target #icon-indicator-input {
|
||||
fill: #45494d;
|
||||
}
|
||||
#light-icons:target #icon-indicator-output {
|
||||
fill: #8a9199;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="icon-colorSwatch-network">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-network" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-css" transform="translate(0 12)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-css" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-js" transform="translate(0 24)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-js" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-logging" transform="translate(0 36)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-logging" x="2" y="2"/>
|
||||
</g>
|
||||
<g id="icon-colorSwatch-security" transform="translate(0 48)">
|
||||
<use xlink:href="#glyphShape-colorSwatch-border" class="icon-colorSwatch-border" x="1" y="1"/>
|
||||
<use xlink:href="#glyphShape-colorSwatch" class="icon-colorSwatch-security" x="2" y="2"/>
|
||||
</g>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-network" class="icon-colorSwatch-network" transform="translate(12)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-css" class="icon-colorSwatch-css" transform="translate(12 12)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-js" class="icon-colorSwatch-js" transform="translate(12 24)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-logging" class="icon-colorSwatch-logging" transform="translate(12 36)"/>
|
||||
<use xlink:href="#glyphShape-errorX" id="icon-errorX-security" class="icon-colorSwatch-security" transform="translate(12 48)"/>
|
||||
<g id="icon-warningTriangle-css" transform="translate(24 12)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-css"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-warningTriangle-js" transform="translate(24 24)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-js"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-warningTriangle-logging" transform="translate(24 36)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-logging"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-warningTriangle-security" transform="translate(24 48)">
|
||||
<use xlink:href="#glyphShape-warningTriangle" class="icon-colorSwatch-security"/>
|
||||
<use xlink:href="#glyphShape-exclamationPoint" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="icon-infoCircle-logging" transform="translate(36 36)">
|
||||
<use xlink:href="#glyphShape-infoCircle" class="icon-colorSwatch-logging"/>
|
||||
<use xlink:href="#glyphShape-infoGlyph" class="icon-glyphOverlay"/>
|
||||
</g>
|
||||
<g id="light-icons">
|
||||
<path id="icon-indicator-input" d="M6.5,1.2L5.4,2.3L9,6L5.3,9.7l1.1,1.1L11,6L6.5,1.2z M1.5,1.2 L0.4,2.3L4,6L0.3,9.7l1.1,1.1L6,6L1.5,1.2z" transform="translate(48 36)"/>
|
||||
<polygon id="icon-indicator-output" points="10,5 4.3,5 6.8,2.4 5.5,1.2 1,6 5.5,10.8 6.9,9.6 4.3,7 10,7" transform="translate(60 36)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.6 KiB |
@ -639,25 +639,24 @@ menuitem.marker-color-graphs-blue:before,
|
||||
|
||||
#jit-optimizations-view .opt-icon::before {
|
||||
content: "";
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.png);
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 48px 40px;
|
||||
margin: 5px 6px 0 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
max-height: 8px;
|
||||
background-size: 72px 60px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
|
||||
margin: 5px 6px 0 0;
|
||||
max-height: 12px;
|
||||
}
|
||||
.theme-light #jit-optimizations-view .opt-icon::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg#light-icons);
|
||||
}
|
||||
|
||||
#jit-optimizations-view .opt-icon[severity=warning]::before {
|
||||
background-position: -16px -16px;
|
||||
background-position: -24px -24px;
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
#jit-optimizations-view .opt-icon::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configurable Options
|
||||
@ -671,21 +670,21 @@ menuitem.marker-color-graphs-blue:before,
|
||||
*/
|
||||
menuitem.experimental-option::before {
|
||||
content: "";
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.png);
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 48px 40px;
|
||||
margin: 2px 5px 0 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
max-height: 8px;
|
||||
background-size: 72px 60px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
background-position: -16px -16px;
|
||||
|
||||
background-position: -24px -24px;
|
||||
margin: 2px 5px 0 0;
|
||||
max-height: 12px;
|
||||
}
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
menuitem.experimental-option::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
|
||||
}
|
||||
.theme-light menuitem.experimental-option::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg#light-icons);
|
||||
}
|
||||
|
||||
#performance-options-menupopup:not(.experimental-enabled) .experimental-option,
|
||||
#performance-options-menupopup:not(.experimental-enabled) .experimental-option::before {
|
||||
display: none;
|
||||
|
@ -44,19 +44,17 @@ a {
|
||||
|
||||
.message > .icon::before {
|
||||
content: "";
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.png);
|
||||
background-position: 8px 8px;
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg);
|
||||
background-position: 12px 12px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 48px 40px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-size: 72px 60px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.25dppx) {
|
||||
.message > .icon::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
|
||||
}
|
||||
.theme-light .message > .icon::before {
|
||||
background-image: url(chrome://browser/skin/devtools/webconsole.svg#light-icons);
|
||||
}
|
||||
|
||||
.message > .message-body-wrapper {
|
||||
@ -228,7 +226,7 @@ a {
|
||||
}
|
||||
|
||||
.message[category=network][severity=error] > .icon::before {
|
||||
background-position: -8px 0;
|
||||
background-position: -12px 0;
|
||||
}
|
||||
|
||||
.message[category=network] > .message-body {
|
||||
@ -285,11 +283,11 @@ a {
|
||||
}
|
||||
|
||||
.message[category=cssparser][severity=error] > .icon::before {
|
||||
background-position: -8px -8px;
|
||||
background-position: -12px -12px;
|
||||
}
|
||||
|
||||
.message[category=cssparser][severity=warn] > .icon::before {
|
||||
background-position: -16px -8px;
|
||||
background-position: -24px -12px;
|
||||
}
|
||||
|
||||
/* JS styles */
|
||||
@ -303,11 +301,11 @@ a {
|
||||
}
|
||||
|
||||
.message[category=exception][severity=error] > .icon::before {
|
||||
background-position: -8px -16px;
|
||||
background-position: -12px -24px;
|
||||
}
|
||||
|
||||
.message[category=exception][severity=warn] > .icon::before {
|
||||
background-position: -16px -16px;
|
||||
background-position: -24px -24px;
|
||||
}
|
||||
|
||||
/* Web Developer styles */
|
||||
@ -322,15 +320,15 @@ a {
|
||||
|
||||
.message[category=console][severity=error] > .icon::before,
|
||||
.message[category=output][severity=error] > .icon::before {
|
||||
background-position: -8px -24px;
|
||||
background-position: -12px -36px;
|
||||
}
|
||||
|
||||
.message[category=console][severity=warn] > .icon::before {
|
||||
background-position: -16px -24px;
|
||||
background-position: -24px -36px;
|
||||
}
|
||||
|
||||
.message[category=console][severity=info] > .icon::before {
|
||||
background-position: -24px -24px;
|
||||
background-position: -36px -36px;
|
||||
}
|
||||
|
||||
/* Input and output styles */
|
||||
@ -340,11 +338,11 @@ a {
|
||||
}
|
||||
|
||||
.message[category=input] > .icon::before {
|
||||
background-position: -32px -24px;
|
||||
background-position: -48px -36px;
|
||||
}
|
||||
|
||||
.message[category=output] > .icon::before {
|
||||
background-position: -40px -24px;
|
||||
background-position: -60px -36px;
|
||||
}
|
||||
|
||||
/* JSTerm Styles */
|
||||
@ -411,11 +409,11 @@ a {
|
||||
}
|
||||
|
||||
.message[category=security][severity=error] > .icon::before {
|
||||
background-position: -8px -32px;
|
||||
background-position: -12px -48px;
|
||||
}
|
||||
|
||||
.message[category=security][severity=warn] > .icon::before {
|
||||
background-position: -16px -32px;
|
||||
background-position: -24px -48px;
|
||||
}
|
||||
|
||||
.navigation-marker {
|
||||
|
@ -400,8 +400,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/editor-debug-location@2x.png (../shared/devtools/images/editor-debug-location@2x.png)
|
||||
* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
|
||||
skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
|
||||
skin/classic/browser/devtools/webconsole.png (../shared/devtools/images/webconsole.png)
|
||||
skin/classic/browser/devtools/webconsole@2x.png (../shared/devtools/images/webconsole@2x.png)
|
||||
skin/classic/browser/devtools/webconsole.svg (../shared/devtools/images/webconsole.svg)
|
||||
skin/classic/browser/devtools/breadcrumbs-divider@2x.png (../shared/devtools/images/breadcrumbs-divider@2x.png)
|
||||
skin/classic/browser/devtools/breadcrumbs-scrollbutton.png (../shared/devtools/images/breadcrumbs-scrollbutton.png)
|
||||
skin/classic/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
|
||||
|
@ -4952,6 +4952,13 @@ if test -n "$MOZ_ANDROID_SHARE_OVERLAY"; then
|
||||
AC_DEFINE(MOZ_ANDROID_SHARE_OVERLAY)
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Show Firefox Account profile details on Android
|
||||
dnl ========================================================
|
||||
if test -n "$MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES"; then
|
||||
AC_DEFINE(MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES)
|
||||
fi
|
||||
|
||||
dnl = Include Tab Queue on Android
|
||||
dnl = Temporary build flag to allow development in Nightly
|
||||
dnl ========================================================
|
||||
@ -8540,6 +8547,7 @@ AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
|
||||
AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
|
||||
AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
|
||||
AC_SUBST(MOZ_ANDROID_SHARE_OVERLAY)
|
||||
AC_SUBST(MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES)
|
||||
AC_SUBST(MOZ_ANDROID_TAB_QUEUE)
|
||||
AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
|
||||
AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
|
||||
|
@ -19,7 +19,7 @@ public class AboutPages {
|
||||
public static final String FIREFOX = "about:firefox";
|
||||
public static final String HEALTHREPORT = "about:healthreport";
|
||||
public static final String HOME = "about:home";
|
||||
public static final String PASSWORDS = "about:passwords";
|
||||
public static final String LOGINS = "about:logins";
|
||||
public static final String PRIVATEBROWSING = "about:privatebrowsing";
|
||||
public static final String READER = "about:reader";
|
||||
public static final String UPDATER = "about:";
|
||||
|
@ -175,6 +175,13 @@ public class AppConstants {
|
||||
false;
|
||||
//#endif
|
||||
|
||||
public static final boolean MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES =
|
||||
//#ifdef MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES
|
||||
true;
|
||||
//#else
|
||||
false;
|
||||
//#endif
|
||||
|
||||
public static final boolean MOZ_TELEMETRY_ON_BY_DEFAULT =
|
||||
//#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
|
||||
true;
|
||||
|
@ -6,7 +6,6 @@
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import com.nineoldandroids.animation.Animator;
|
||||
import com.nineoldandroids.animation.AnimatorListenerAdapter;
|
||||
import com.nineoldandroids.animation.ObjectAnimator;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.DynamicToolbar.PinReason;
|
||||
@ -37,6 +36,7 @@ import org.mozilla.gecko.health.HealthRecorder;
|
||||
import org.mozilla.gecko.health.SessionInformation;
|
||||
import org.mozilla.gecko.home.BrowserSearch;
|
||||
import org.mozilla.gecko.home.HomeBanner;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelType;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
@ -157,6 +157,7 @@ public class BrowserApp extends GeckoApp
|
||||
BrowserSearch.OnEditSuggestionListener,
|
||||
OnUrlOpenListener,
|
||||
OnUrlOpenInBackgroundListener,
|
||||
ReadingListHelper.OnReadingListEventListener,
|
||||
AnchoredPopup.OnVisibilityChangeListener,
|
||||
ActionModeCompat.Presenter,
|
||||
LayoutInflater.Factory {
|
||||
@ -496,6 +497,12 @@ public class BrowserApp extends GeckoApp
|
||||
case BOOKMARK_REMOVED:
|
||||
showBookmarkRemovedToast();
|
||||
break;
|
||||
case READING_LIST_ADDED:
|
||||
onAddedToReadingList(tab.getURL());
|
||||
break;
|
||||
case READING_LIST_REMOVED:
|
||||
onRemovedFromReadingList(tab.getURL());
|
||||
break;
|
||||
|
||||
case UNSELECTED:
|
||||
// We receive UNSELECTED immediately after the SELECTED listeners run
|
||||
@ -574,6 +581,36 @@ public class BrowserApp extends GeckoApp
|
||||
Toast.makeText(this, R.string.bookmark_removed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void showSwitchToReadingListToast(String message) {
|
||||
getButtonToast().show(false,
|
||||
message,
|
||||
ButtonToast.LENGTH_SHORT,
|
||||
getResources().getString(R.string.switch_button_message),
|
||||
R.drawable.switch_button_icon,
|
||||
new ButtonToast.ToastListener() {
|
||||
@Override
|
||||
public void onButtonClicked() {
|
||||
final String aboutPageUrl = AboutPages.getURLForBuiltinPanelType(PanelType.READING_LIST);
|
||||
Tabs.getInstance().loadUrlInTab(aboutPageUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToastHidden(ButtonToast.ReasonHidden reason) { }
|
||||
});
|
||||
}
|
||||
|
||||
public void onAddedToReadingList(String url) {
|
||||
showSwitchToReadingListToast(getResources().getString(R.string.reading_list_added));
|
||||
}
|
||||
|
||||
public void onAlreadyInReadingList(String url) {
|
||||
showSwitchToReadingListToast(getResources().getString(R.string.reading_list_duplicate));
|
||||
}
|
||||
|
||||
public void onRemovedFromReadingList(String url) {
|
||||
Toast.makeText(this, R.string.reading_list_removed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (AndroidGamepadManager.handleKeyEvent(event)) {
|
||||
@ -808,7 +845,7 @@ public class BrowserApp extends GeckoApp
|
||||
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
|
||||
mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext);
|
||||
mBrowserHealthReporter = new BrowserHealthReporter();
|
||||
mReadingListHelper = new ReadingListHelper(appContext, getProfile());
|
||||
mReadingListHelper = new ReadingListHelper(appContext, getProfile(), this);
|
||||
|
||||
if (AppConstants.MOZ_ANDROID_BEAM) {
|
||||
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
|
||||
@ -3385,7 +3422,7 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
if (itemId == R.id.logins) {
|
||||
Tabs.getInstance().loadUrlInTab(AboutPages.PASSWORDS);
|
||||
Tabs.getInstance().loadUrlInTab(AboutPages.LOGINS);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.DBUtils;
|
||||
import org.mozilla.gecko.db.ReadingListAccessor;
|
||||
import org.mozilla.gecko.favicons.Favicons;
|
||||
import org.mozilla.gecko.mozglue.RobocopTarget;
|
||||
@ -23,21 +22,32 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
public final class ReadingListHelper implements NativeEventListener {
|
||||
private static final String LOGTAG = "GeckoReadingListHelper";
|
||||
|
||||
public interface OnReadingListEventListener {
|
||||
void onAddedToReadingList(String url);
|
||||
void onRemovedFromReadingList(String url);
|
||||
void onAlreadyInReadingList(String url);
|
||||
}
|
||||
|
||||
private enum ReadingListEvent {
|
||||
ADDED,
|
||||
REMOVED,
|
||||
ALREADY_EXISTS
|
||||
}
|
||||
|
||||
protected final Context context;
|
||||
private final BrowserDB db;
|
||||
private final ReadingListAccessor readingListAccessor;
|
||||
private final ContentObserver contentObserver;
|
||||
private final OnReadingListEventListener onReadingListEventListener;
|
||||
|
||||
volatile boolean fetchInBackground = true;
|
||||
|
||||
public ReadingListHelper(Context context, GeckoProfile profile) {
|
||||
public ReadingListHelper(Context context, GeckoProfile profile, OnReadingListEventListener listener) {
|
||||
this.context = context;
|
||||
this.db = profile.getDB();
|
||||
this.readingListAccessor = db.getReadingListAccessor();
|
||||
@ -56,6 +66,8 @@ public final class ReadingListHelper implements NativeEventListener {
|
||||
};
|
||||
|
||||
this.readingListAccessor.registerContentObserver(context, contentObserver);
|
||||
|
||||
onReadingListEventListener = listener;
|
||||
}
|
||||
|
||||
public void uninit() {
|
||||
@ -110,11 +122,11 @@ public final class ReadingListHelper implements NativeEventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
if (readingListAccessor.isReadingListItem(cr, url)) {
|
||||
showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
|
||||
handleEvent(ReadingListEvent.ALREADY_EXISTS, url);
|
||||
callback.sendError("URL already in reading list: " + url);
|
||||
} else {
|
||||
readingListAccessor.addReadingListItem(cr, values);
|
||||
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
|
||||
handleEvent(ReadingListEvent.ADDED, url);
|
||||
callback.sendSuccess(url);
|
||||
}
|
||||
}
|
||||
@ -227,7 +239,7 @@ public final class ReadingListHelper implements NativeEventListener {
|
||||
@Override
|
||||
public void run() {
|
||||
readingListAccessor.removeReadingListItemWithURL(context.getContentResolver(), url);
|
||||
showToast(R.string.reading_list_removed, Toast.LENGTH_SHORT);
|
||||
handleEvent(ReadingListEvent.REMOVED, url);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -257,13 +269,23 @@ public final class ReadingListHelper implements NativeEventListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show various status toasts.
|
||||
* Handle various reading list events (and display appropriate toasts).
|
||||
*/
|
||||
private void showToast(final int resId, final int duration) {
|
||||
private void handleEvent(final ReadingListEvent event, final String url) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(context, resId, duration).show();
|
||||
switch(event) {
|
||||
case ADDED:
|
||||
onReadingListEventListener.onAddedToReadingList(url);
|
||||
break;
|
||||
case REMOVED:
|
||||
onReadingListEventListener.onRemovedFromReadingList(url);
|
||||
break;
|
||||
case ALREADY_EXISTS:
|
||||
onReadingListEventListener.onAlreadyInReadingList(url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -582,12 +582,7 @@ public class Tab {
|
||||
}
|
||||
|
||||
mDB.getReadingListAccessor().addBasicReadingListItem(getContentResolver(), url, mTitle);
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(mAppContext, R.string.reading_list_added, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.READING_LIST_ADDED);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -604,12 +599,7 @@ public class Tab {
|
||||
url = ReaderModeUtils.getUrlFromAboutReader(url);
|
||||
}
|
||||
mDB.getReadingListAccessor().removeReadingListItemWithURL(getContentResolver(), url);
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(mAppContext, R.string.reading_list_removed, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.READING_LIST_REMOVED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -638,7 +638,9 @@ public class Tabs implements GeckoEventListener {
|
||||
VIEWPORT_CHANGE,
|
||||
RECORDING_CHANGE,
|
||||
BOOKMARK_ADDED,
|
||||
BOOKMARK_REMOVED
|
||||
BOOKMARK_REMOVED,
|
||||
READING_LIST_ADDED,
|
||||
READING_LIST_REMOVED,
|
||||
}
|
||||
|
||||
public void notifyListeners(Tab tab, TabEvents msg) {
|
||||
|
@ -299,6 +299,7 @@ public class BrowserContract {
|
||||
|
||||
public static final class Clients {
|
||||
private Clients() {}
|
||||
public static final Uri CONTENT_RECENCY_URI = Uri.withAppendedPath(TABS_AUTHORITY_URI, "clients_recency");
|
||||
public static final Uri CONTENT_URI = Uri.withAppendedPath(TABS_AUTHORITY_URI, "clients");
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/client";
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/client";
|
||||
|
@ -33,10 +33,19 @@ public class LocalTabsAccessor implements TabsAccessor {
|
||||
BrowserContract.Tabs.URL,
|
||||
BrowserContract.Clients.GUID,
|
||||
BrowserContract.Clients.NAME,
|
||||
BrowserContract.Tabs.LAST_USED,
|
||||
BrowserContract.Clients.LAST_MODIFIED,
|
||||
BrowserContract.Clients.DEVICE_TYPE,
|
||||
};
|
||||
|
||||
public static final String[] CLIENTS_PROJECTION_COLUMNS = new String[] {
|
||||
BrowserContract.Clients.GUID,
|
||||
BrowserContract.Clients.NAME,
|
||||
BrowserContract.Clients.LAST_MODIFIED,
|
||||
BrowserContract.Clients.DEVICE_TYPE
|
||||
};
|
||||
|
||||
private static final String REMOTE_CLIENTS_SELECTION = BrowserContract.Clients.GUID + " IS NOT NULL";
|
||||
private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
|
||||
private static final String REMOTE_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL";
|
||||
|
||||
@ -53,12 +62,49 @@ public class LocalTabsAccessor implements TabsAccessor {
|
||||
|
||||
private static final Pattern FILTERED_URL_PATTERN = Pattern.compile("^(about|chrome|wyciwyg|file):");
|
||||
|
||||
private final Uri clientsRecencyUriWithProfile;
|
||||
private final Uri tabsUriWithProfile;
|
||||
private final Uri clientsUriWithProfile;
|
||||
|
||||
public LocalTabsAccessor(String mProfile) {
|
||||
tabsUriWithProfile = DBUtils.appendProfileWithDefault(mProfile, BrowserContract.Tabs.CONTENT_URI);
|
||||
clientsUriWithProfile = DBUtils.appendProfileWithDefault(mProfile, BrowserContract.Clients.CONTENT_URI);
|
||||
public LocalTabsAccessor(String profileName) {
|
||||
tabsUriWithProfile = DBUtils.appendProfileWithDefault(profileName, BrowserContract.Tabs.CONTENT_URI);
|
||||
clientsUriWithProfile = DBUtils.appendProfileWithDefault(profileName, BrowserContract.Clients.CONTENT_URI);
|
||||
clientsRecencyUriWithProfile = DBUtils.appendProfileWithDefault(profileName, BrowserContract.Clients.CONTENT_RECENCY_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a List of just RemoteClients from a cursor.
|
||||
* The supplied cursor should be grouped by guid and sorted by most recently used.
|
||||
*/
|
||||
@Override
|
||||
public List<RemoteClient> getClientsWithoutTabsByRecencyFromCursor(Cursor cursor) {
|
||||
final ArrayList<RemoteClient> clients = new ArrayList<>(cursor.getCount());
|
||||
|
||||
final int originalPosition = cursor.getPosition();
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return clients;
|
||||
}
|
||||
|
||||
final int clientGuidIndex = cursor.getColumnIndex(BrowserContract.Clients.GUID);
|
||||
final int clientNameIndex = cursor.getColumnIndex(BrowserContract.Clients.NAME);
|
||||
final int clientLastModifiedIndex = cursor.getColumnIndex(BrowserContract.Clients.LAST_MODIFIED);
|
||||
final int clientDeviceTypeIndex = cursor.getColumnIndex(BrowserContract.Clients.DEVICE_TYPE);
|
||||
|
||||
while (!cursor.isAfterLast()) {
|
||||
final String clientGuid = cursor.getString(clientGuidIndex);
|
||||
final String clientName = cursor.getString(clientNameIndex);
|
||||
final String deviceType = cursor.getString(clientDeviceTypeIndex);
|
||||
final long lastModified = cursor.getLong(clientLastModifiedIndex);
|
||||
|
||||
clients.add(new RemoteClient(clientGuid, clientName, lastModified, deviceType));
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cursor.moveToPosition(originalPosition);
|
||||
}
|
||||
return clients;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,6 +131,7 @@ public class LocalTabsAccessor implements TabsAccessor {
|
||||
|
||||
final int tabTitleIndex = cursor.getColumnIndex(BrowserContract.Tabs.TITLE);
|
||||
final int tabUrlIndex = cursor.getColumnIndex(BrowserContract.Tabs.URL);
|
||||
final int tabLastUsedIndex = cursor.getColumnIndex(BrowserContract.Tabs.LAST_USED);
|
||||
final int clientGuidIndex = cursor.getColumnIndex(BrowserContract.Clients.GUID);
|
||||
final int clientNameIndex = cursor.getColumnIndex(BrowserContract.Clients.NAME);
|
||||
final int clientLastModifiedIndex = cursor.getColumnIndex(BrowserContract.Clients.LAST_MODIFIED);
|
||||
@ -106,7 +153,8 @@ public class LocalTabsAccessor implements TabsAccessor {
|
||||
|
||||
final String tabTitle = cursor.getString(tabTitleIndex);
|
||||
final String tabUrl = cursor.getString(tabUrlIndex);
|
||||
lastClient.tabs.add(new RemoteTab(tabTitle, tabUrl));
|
||||
final long tabLastUsed = cursor.getLong(tabLastUsedIndex);
|
||||
lastClient.tabs.add(new RemoteTab(tabTitle, tabUrl, tabLastUsed));
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
@ -117,6 +165,13 @@ public class LocalTabsAccessor implements TabsAccessor {
|
||||
return clients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getRemoteClientsByRecencyCursor(Context context) {
|
||||
final Uri uri = clientsRecencyUriWithProfile;
|
||||
return context.getContentResolver().query(uri, CLIENTS_PROJECTION_COLUMNS,
|
||||
REMOTE_CLIENTS_SELECTION, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getRemoteTabsCursor(Context context) {
|
||||
return getRemoteTabsCursor(context, -1);
|
||||
|
@ -19,10 +19,12 @@ import android.os.Parcelable;
|
||||
public class RemoteTab implements Parcelable {
|
||||
public final String title;
|
||||
public final String url;
|
||||
public final long lastUsed;
|
||||
|
||||
public RemoteTab(String title, String url) {
|
||||
public RemoteTab(String title, String url, long lastUsed) {
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
this.lastUsed = lastUsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -34,6 +36,7 @@ public class RemoteTab implements Parcelable {
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(title);
|
||||
parcel.writeString(url);
|
||||
parcel.writeLong(lastUsed);
|
||||
}
|
||||
|
||||
public static final Creator<RemoteTab> CREATOR = new Creator<RemoteTab>() {
|
||||
@ -41,8 +44,8 @@ public class RemoteTab implements Parcelable {
|
||||
public RemoteTab createFromParcel(final Parcel source) {
|
||||
final String title = source.readString();
|
||||
final String url = source.readString();
|
||||
|
||||
return new RemoteTab(title, url);
|
||||
final long lastUsed = source.readLong();
|
||||
return new RemoteTab(title, url, lastUsed);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -113,11 +113,21 @@ class StubTabsAccessor implements TabsAccessor {
|
||||
public StubTabsAccessor() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RemoteClient> getClientsWithoutTabsByRecencyFromCursor(Cursor cursor) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RemoteClient> getClientsFromCursor(final Cursor cursor) {
|
||||
return new ArrayList<RemoteClient>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getRemoteClientsByRecencyCursor(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cursor getRemoteTabsCursor(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
@ -17,8 +17,10 @@ public interface TabsAccessor {
|
||||
public void onQueryTabsComplete(List<RemoteClient> clients);
|
||||
}
|
||||
|
||||
public Cursor getRemoteClientsByRecencyCursor(Context context);
|
||||
public Cursor getRemoteTabsCursor(Context context);
|
||||
public Cursor getRemoteTabsCursor(Context context, int limit);
|
||||
public List<RemoteClient> getClientsWithoutTabsByRecencyFromCursor(final Cursor cursor);
|
||||
public List<RemoteClient> getClientsFromCursor(final Cursor cursor);
|
||||
public void getTabs(final Context context, final OnQueryTabsCompleteListener listener);
|
||||
public void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener);
|
||||
|
@ -35,9 +35,11 @@ public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDa
|
||||
static final int TABS_ID = 601;
|
||||
static final int CLIENTS = 602;
|
||||
static final int CLIENTS_ID = 603;
|
||||
static final int CLIENTS_RECENCY = 604;
|
||||
|
||||
static final String DEFAULT_TABS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC, " + Tabs.LAST_USED + " DESC";
|
||||
static final String DEFAULT_CLIENTS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC";
|
||||
static final String DEFAULT_CLIENTS_RECENCY_SORT_ORDER = "COALESCE(MAX(" + Tabs.LAST_USED + "), " + Clients.LAST_MODIFIED + ") DESC";
|
||||
|
||||
static final String INDEX_TABS_GUID = "tabs_guid_index";
|
||||
static final String INDEX_TABS_POSITION = "tabs_position_index";
|
||||
@ -47,12 +49,14 @@ public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDa
|
||||
|
||||
static final Map<String, String> TABS_PROJECTION_MAP;
|
||||
static final Map<String, String> CLIENTS_PROJECTION_MAP;
|
||||
static final Map<String, String> CLIENTS_RECENCY_PROJECTION_MAP;
|
||||
|
||||
static {
|
||||
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs", TABS);
|
||||
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs/#", TABS_ID);
|
||||
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients", CLIENTS);
|
||||
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients/#", CLIENTS_ID);
|
||||
URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients_recency", CLIENTS_RECENCY);
|
||||
|
||||
HashMap<String, String> map;
|
||||
|
||||
@ -76,10 +80,24 @@ public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDa
|
||||
map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED);
|
||||
map.put(Clients.DEVICE_TYPE, Clients.DEVICE_TYPE);
|
||||
CLIENTS_PROJECTION_MAP = Collections.unmodifiableMap(map);
|
||||
|
||||
map = new HashMap<>();
|
||||
map.put(Clients.GUID, projectColumn(TABLE_CLIENTS, Clients.GUID) + " AS guid");
|
||||
map.put(Clients.NAME, projectColumn(TABLE_CLIENTS, Clients.NAME) + " AS name");
|
||||
map.put(Clients.LAST_MODIFIED, projectColumn(TABLE_CLIENTS, Clients.LAST_MODIFIED) + " AS last_modified");
|
||||
map.put(Clients.DEVICE_TYPE, projectColumn(TABLE_CLIENTS, Clients.DEVICE_TYPE) + " AS device_type");
|
||||
// last_used is the max of the tab last_used times, or if there are no tabs,
|
||||
// the client's last_modified time.
|
||||
map.put(Tabs.LAST_USED, "COALESCE(MAX(" + projectColumn(TABLE_TABS, Tabs.LAST_USED) + "), " + projectColumn(TABLE_CLIENTS, Clients.LAST_MODIFIED) + ") AS last_used");
|
||||
CLIENTS_RECENCY_PROJECTION_MAP = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
private static final String projectColumn(String table, String column) {
|
||||
return table + "." + column;
|
||||
}
|
||||
|
||||
private static final String selectColumn(String table, String column) {
|
||||
return table + "." + column + " = ?";
|
||||
return projectColumn(table, column) + " = ?";
|
||||
}
|
||||
|
||||
final class TabsDatabaseHelper extends SQLiteOpenHelper {
|
||||
@ -334,6 +352,7 @@ public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDa
|
||||
SQLiteDatabase db = getReadableDatabase(uri);
|
||||
final int match = URI_MATCHER.match(uri);
|
||||
|
||||
String groupBy = null;
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
|
||||
|
||||
@ -374,12 +393,27 @@ public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDa
|
||||
qb.setTables(TABLE_CLIENTS);
|
||||
break;
|
||||
|
||||
case CLIENTS_RECENCY:
|
||||
trace("Query is on CLIENTS_RECENCY: " + uri);
|
||||
if (TextUtils.isEmpty(sortOrder)) {
|
||||
sortOrder = DEFAULT_CLIENTS_RECENCY_SORT_ORDER;
|
||||
} else {
|
||||
debug("Using sort order " + sortOrder + ".");
|
||||
}
|
||||
|
||||
qb.setProjectionMap(CLIENTS_RECENCY_PROJECTION_MAP);
|
||||
qb.setTables(TABLE_CLIENTS + " LEFT OUTER JOIN " + TABLE_TABS +
|
||||
" ON (" + projectColumn(TABLE_CLIENTS, Clients.GUID) +
|
||||
" = " + projectColumn(TABLE_TABS,Tabs.CLIENT_GUID) + ")");
|
||||
groupBy = projectColumn(TABLE_CLIENTS, Clients.GUID);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown query URI " + uri);
|
||||
}
|
||||
|
||||
trace("Running built query.");
|
||||
final Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, limit);
|
||||
final Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.TABS_AUTHORITY_URI);
|
||||
|
||||
return cursor;
|
||||
|
@ -95,6 +95,7 @@ public class FxAccountStatusFragment
|
||||
private int debugClickCount = 0;
|
||||
|
||||
protected PreferenceCategory accountCategory;
|
||||
protected Preference profilePreference;
|
||||
protected Preference emailPreference;
|
||||
protected Preference authServerPreference;
|
||||
|
||||
@ -160,7 +161,13 @@ public class FxAccountStatusFragment
|
||||
addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
|
||||
|
||||
accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category");
|
||||
profilePreference = ensureFindPreference("profile");
|
||||
emailPreference = ensureFindPreference("email");
|
||||
if (AppConstants.MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES) {
|
||||
accountCategory.removePreference(emailPreference);
|
||||
} else {
|
||||
accountCategory.removePreference(profilePreference);
|
||||
}
|
||||
authServerPreference = ensureFindPreference("auth_server");
|
||||
|
||||
needsPasswordPreference = ensureFindPreference("needs_credentials");
|
||||
@ -184,7 +191,11 @@ public class FxAccountStatusFragment
|
||||
ALWAYS_SHOW_SYNC_SERVER = true;
|
||||
}
|
||||
|
||||
emailPreference.setOnPreferenceClickListener(this);
|
||||
if (AppConstants.MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES) {
|
||||
profilePreference.setOnPreferenceClickListener(this);
|
||||
} else {
|
||||
emailPreference.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
needsPasswordPreference.setOnPreferenceClickListener(this);
|
||||
needsVerificationPreference.setOnPreferenceClickListener(this);
|
||||
@ -222,7 +233,8 @@ public class FxAccountStatusFragment
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (preference == emailPreference) {
|
||||
final Preference personalInformationPreference = AppConstants.MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES ? profilePreference : emailPreference;
|
||||
if (preference == personalInformationPreference) {
|
||||
debugClickCount += 1;
|
||||
if (NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG > 0 && debugClickCount >= NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG) {
|
||||
debugClickCount = 0;
|
||||
@ -493,7 +505,15 @@ public class FxAccountStatusFragment
|
||||
throw new IllegalArgumentException("fxAccount must not be null");
|
||||
}
|
||||
|
||||
emailPreference.setTitle(fxAccount.getEmail());
|
||||
if (AppConstants.MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES) {
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
profilePreference.setIcon(getResources().getDrawable(R.drawable.sync_avatar_default));
|
||||
}
|
||||
profilePreference.setTitle(fxAccount.getAndroidAccount().name);
|
||||
} else {
|
||||
emailPreference.setTitle(fxAccount.getEmail());
|
||||
}
|
||||
|
||||
updateAuthServerPreference();
|
||||
updateSyncServerPreference();
|
||||
|
||||
|
@ -421,7 +421,9 @@ size. -->
|
||||
<!ENTITY site_settings_clear "Clear">
|
||||
<!ENTITY site_settings_no_settings "There are no settings to clear.">
|
||||
|
||||
<!ENTITY reading_list_added "Page added to your Reading List">
|
||||
<!-- Localization note (reading_list_added2) : Used in a toast, please keep as short
|
||||
as possible. -->
|
||||
<!ENTITY reading_list_added2 "Added to list">
|
||||
<!ENTITY reading_list_removed "Page removed from your Reading List">
|
||||
<!-- Localization note (reading_list_remove) : Used to remove the currently open page from
|
||||
the user's reading list. The opposite of overlay_share_reading_list_btn_label. -->
|
||||
|
@ -744,7 +744,7 @@ ANDROID_GENERATED_RESFILES += [
|
||||
for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZILLA_OFFICIAL', 'MOZ_DEBUG',
|
||||
'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_DEVICES', 'MOZ_ANDROID_MLS_STUMBLER',
|
||||
'MOZ_ANDROID_SHARE_OVERLAY', 'MOZ_ANDROID_DOWNLOADS_INTEGRATION', 'MOZ_INSTALL_TRACKING',
|
||||
'MOZ_ANDROID_TAB_QUEUE'):
|
||||
'MOZ_ANDROID_TAB_QUEUE', 'MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES'):
|
||||
if CONFIG[var]:
|
||||
DEFINES[var] = 1
|
||||
|
||||
|
@ -10,11 +10,17 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
import org.mozilla.gecko.db.TabsAccessor;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
|
||||
@ -28,19 +34,14 @@ import org.mozilla.gecko.sync.CommandRunner;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.SyncConstants;
|
||||
import org.mozilla.gecko.sync.repositories.NullCursorException;
|
||||
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -54,8 +55,8 @@ public class SendTab extends ShareMethod {
|
||||
// Key used in the extras Bundle in the share intent used for a send tab ShareMethod.
|
||||
public static final String SEND_TAB_TARGET_DEVICES = "SEND_TAB_TARGET_DEVICES";
|
||||
|
||||
// Key used in broadcast intent from SendTab ShareMethod specifying available ClientRecords.
|
||||
public static final String EXTRA_CLIENT_RECORDS = "RECORDS";
|
||||
// Key used in broadcast intent from SendTab ShareMethod specifying available RemoteClients.
|
||||
public static final String EXTRA_REMOTE_CLIENT_RECORDS = "RECORDS";
|
||||
|
||||
// The intent we should dispatch when the button for this ShareMethod is tapped, instead of
|
||||
// taking the normal action (e.g., "Set up Sync!")
|
||||
@ -190,20 +191,16 @@ public class SendTab extends ShareMethod {
|
||||
* Load the list of Sync clients that are not this device using the given TabSender.
|
||||
*/
|
||||
private void updateClientList(TabSender tabSender) {
|
||||
Collection<ClientRecord> otherClients = getOtherClients(tabSender);
|
||||
Collection<RemoteClient> otherClients = getOtherClients(tabSender);
|
||||
|
||||
// Put the list of RemoteClients into the uiStateIntent and broadcast it.
|
||||
RemoteClient[] records = new RemoteClient[otherClients.size()];
|
||||
records = otherClients.toArray(records);
|
||||
|
||||
ParcelableClientRecord[] records = new ParcelableClientRecord[otherClients.size()];
|
||||
validGUIDs = new HashSet<>();
|
||||
int i = 0;
|
||||
|
||||
// Put the list of ClientRecords into the uiStateIntent and broadcast it.
|
||||
for (ClientRecord client : otherClients) {
|
||||
ParcelableClientRecord record = ParcelableClientRecord.fromClientRecord(client);
|
||||
|
||||
records[i] = record;
|
||||
|
||||
validGUIDs.add(record.guid);
|
||||
i++;
|
||||
for (RemoteClient client : otherClients) {
|
||||
validGUIDs.add(client.guid);
|
||||
}
|
||||
|
||||
if (validGUIDs.isEmpty()) {
|
||||
@ -214,7 +211,7 @@ public class SendTab extends ShareMethod {
|
||||
}
|
||||
|
||||
Intent uiStateIntent = getUIStateIntent();
|
||||
uiStateIntent.putExtra(EXTRA_CLIENT_RECORDS, records);
|
||||
uiStateIntent.putExtra(EXTRA_REMOTE_CLIENT_RECORDS, records);
|
||||
broadcastUIState(uiStateIntent);
|
||||
}
|
||||
|
||||
@ -252,48 +249,25 @@ public class SendTab extends ShareMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A map from GUID to client record for all sync clients, including our own; or null iff
|
||||
* ClientsDatabaseAccessor.fetchAllClients throws NullCursorException.
|
||||
* @return A collection of unique remote clients sorted by most recently used.
|
||||
*/
|
||||
protected Map<String, ClientRecord> getAllClients() {
|
||||
ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
|
||||
try {
|
||||
return db.fetchAllClients();
|
||||
} catch (NullCursorException e) {
|
||||
Log.w(LOGTAG, "NullCursorException while populating device list.", e);
|
||||
return null;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a collection of client records, excluding our own.
|
||||
*/
|
||||
protected Collection<ClientRecord> getOtherClients(final TabSender sender) {
|
||||
protected Collection<RemoteClient> getOtherClients(final TabSender sender) {
|
||||
if (sender == null) {
|
||||
Log.w(LOGTAG, "No tab sender when fetching other client IDs.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Map<String, ClientRecord> all = getAllClients();
|
||||
if (all == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final String ourGUID = sender.getAccountGUID();
|
||||
if (ourGUID == null) {
|
||||
return all.values();
|
||||
}
|
||||
|
||||
final ArrayList<ClientRecord> out = new ArrayList<>(all.size());
|
||||
for (Map.Entry<String, ClientRecord> entry : all.entrySet()) {
|
||||
if (!ourGUID.equals(entry.getKey())) {
|
||||
out.add(entry.getValue());
|
||||
final BrowserDB browserDB = GeckoProfile.get(context).getDB();
|
||||
final TabsAccessor tabsAccessor = browserDB.getTabsAccessor();
|
||||
final Cursor remoteTabsCursor = tabsAccessor.getRemoteClientsByRecencyCursor(context);
|
||||
try {
|
||||
if (remoteTabsCursor.getCount() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return tabsAccessor.getClientsWithoutTabsByRecencyFromCursor(remoteTabsCursor);
|
||||
} finally {
|
||||
remoteTabsCursor.close();
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,6 +9,7 @@ import java.util.Collection;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Assert;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
import org.mozilla.gecko.overlays.service.sharemethods.ParcelableClientRecord;
|
||||
import org.mozilla.gecko.overlays.ui.SendTabList.State;
|
||||
|
||||
@ -22,19 +23,19 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClientRecord> {
|
||||
public class SendTabDeviceListArrayAdapter extends ArrayAdapter<RemoteClient> {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String LOGTAG = "GeckoSendTabAdapter";
|
||||
|
||||
private State currentState;
|
||||
|
||||
// String to display when in a "button-like" special state. Instead of using a
|
||||
// ParcelableClientRecord we override the rendering using this string.
|
||||
// RemoteClient we override the rendering using this string.
|
||||
private String dummyRecordName;
|
||||
|
||||
private final SendTabTargetSelectedListener listener;
|
||||
|
||||
private Collection<ParcelableClientRecord> records;
|
||||
private Collection<RemoteClient> records;
|
||||
|
||||
// The AlertDialog to show in the event the record is pressed while in the SHOW_DEVICES state.
|
||||
// This will show the user a prompt to select a device from a longer list of devices.
|
||||
@ -53,12 +54,12 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClient
|
||||
* Get an array of the contents of this adapter were it in the LIST state.
|
||||
* Useful for determining the "real" contents of the adapter.
|
||||
*/
|
||||
public ParcelableClientRecord[] toArray() {
|
||||
return records.toArray(new ParcelableClientRecord[records.size()]);
|
||||
public RemoteClient[] toArray() {
|
||||
return records.toArray(new RemoteClient[records.size()]);
|
||||
}
|
||||
|
||||
public void setClientRecordList(Collection<ParcelableClientRecord> clientRecordList) {
|
||||
records = clientRecordList;
|
||||
public void setRemoteClientsList(Collection<RemoteClient> remoteClientsList) {
|
||||
records = remoteClientsList;
|
||||
updateRecordList();
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClient
|
||||
if (AppConstants.Versions.feature11Plus) {
|
||||
addAll(records);
|
||||
} else {
|
||||
for (ParcelableClientRecord record : records) {
|
||||
for (RemoteClient record : records) {
|
||||
add(record);
|
||||
}
|
||||
}
|
||||
@ -122,13 +123,13 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClient
|
||||
}
|
||||
|
||||
// The remaining states delegate to the SentTabTargetSelectedListener.
|
||||
final ParcelableClientRecord clientRecord = getItem(position);
|
||||
final RemoteClient remoteClient = getItem(position);
|
||||
if (currentState == State.LIST) {
|
||||
final Drawable clientIcon = context.getResources().getDrawable(getImage(clientRecord));
|
||||
row.setText(clientRecord.name);
|
||||
final Drawable clientIcon = context.getResources().getDrawable(getImage(remoteClient));
|
||||
row.setText(remoteClient.name);
|
||||
row.setDrawable(clientIcon);
|
||||
|
||||
final String listenerGUID = clientRecord.guid;
|
||||
final String listenerGUID = remoteClient.guid;
|
||||
|
||||
row.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@ -148,8 +149,8 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClient
|
||||
return row;
|
||||
}
|
||||
|
||||
private static int getImage(ParcelableClientRecord record) {
|
||||
if ("mobile".equals(record.type)) {
|
||||
private static int getImage(RemoteClient record) {
|
||||
if ("mobile".equals(record.deviceType)) {
|
||||
return R.drawable.device_mobile;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
package org.mozilla.gecko.overlays.ui;
|
||||
|
||||
import static org.mozilla.gecko.overlays.ui.SendTabList.State.LIST;
|
||||
import static org.mozilla.gecko.overlays.ui.SendTabList.State.LOADING;
|
||||
import static org.mozilla.gecko.overlays.ui.SendTabList.State.SHOW_DEVICES;
|
||||
|
||||
@ -15,13 +14,12 @@ import org.mozilla.gecko.Assert;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.overlays.service.sharemethods.ParcelableClientRecord;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
@ -110,10 +108,10 @@ public class SendTabList extends ListView {
|
||||
}
|
||||
}
|
||||
|
||||
public void setSyncClients(final ParcelableClientRecord[] c) {
|
||||
final ParcelableClientRecord[] clients = c == null ? new ParcelableClientRecord[0] : c;
|
||||
public void setSyncClients(final RemoteClient[] c) {
|
||||
final RemoteClient[] clients = c == null ? new RemoteClient[0] : c;
|
||||
|
||||
clientListAdapter.setClientRecordList(Arrays.asList(clients));
|
||||
clientListAdapter.setRemoteClientsList(Arrays.asList(clients));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,7 +129,7 @@ public class SendTabList extends ListView {
|
||||
builder = new AlertDialog.Builder(context);
|
||||
}
|
||||
|
||||
final ParcelableClientRecord[] records = clientListAdapter.toArray();
|
||||
final RemoteClient[] records = clientListAdapter.toArray();
|
||||
final String[] dialogElements = new String[records.length];
|
||||
|
||||
for (int i = 0; i < records.length; i++) {
|
||||
|
@ -15,6 +15,7 @@ import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.db.LocalBrowserDB;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
import org.mozilla.gecko.overlays.OverlayConstants;
|
||||
import org.mozilla.gecko.overlays.service.OverlayActionService;
|
||||
import org.mozilla.gecko.overlays.service.sharemethods.ParcelableClientRecord;
|
||||
@ -101,13 +102,13 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
|
||||
protected void handleSendTabUIEvent(Intent intent) {
|
||||
sendTabOverrideIntent = intent.getParcelableExtra(SendTab.OVERRIDE_INTENT);
|
||||
|
||||
ParcelableClientRecord[] clientrecords = (ParcelableClientRecord[]) intent.getParcelableArrayExtra(SendTab.EXTRA_CLIENT_RECORDS);
|
||||
RemoteClient[] remoteClientRecords = (RemoteClient[]) intent.getParcelableArrayExtra(SendTab.EXTRA_REMOTE_CLIENT_RECORDS);
|
||||
|
||||
// Escape hatch: we don't show the option to open this dialog in this state so this should
|
||||
// never be run. However, due to potential inconsistencies in synced client state
|
||||
// (e.g. bug 1122302 comment 47), we might fail.
|
||||
if (state == State.DEVICES_ONLY &&
|
||||
(clientrecords == null || clientrecords.length == 0)) {
|
||||
(remoteClientRecords == null || remoteClientRecords.length == 0)) {
|
||||
Log.e(LOGTAG, "In state: " + State.DEVICES_ONLY + " and received 0 synced clients. Finishing...");
|
||||
Toast.makeText(this, getResources().getText(R.string.overlay_no_synced_devices), Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
@ -115,11 +116,11 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
|
||||
return;
|
||||
}
|
||||
|
||||
sendTabList.setSyncClients(clientrecords);
|
||||
sendTabList.setSyncClients(remoteClientRecords);
|
||||
|
||||
if (state == State.DEVICES_ONLY ||
|
||||
clientrecords == null ||
|
||||
clientrecords.length <= MAXIMUM_INLINE_DEVICES) {
|
||||
remoteClientRecords == null ||
|
||||
remoteClientRecords.length <= MAXIMUM_INLINE_DEVICES) {
|
||||
// Show the list of devices in-line.
|
||||
sendTabList.switchState(SendTabList.State.LIST);
|
||||
|
||||
@ -128,7 +129,7 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
|
||||
//
|
||||
// Note: a more thorough implementation would add this
|
||||
// (and other non-ListView buttons) into a custom ListView.
|
||||
if (clientrecords == null || clientrecords.length == 0) {
|
||||
if (remoteClientRecords == null || remoteClientRecords.length == 0) {
|
||||
readingListButton.setBackgroundResource(
|
||||
R.drawable.overlay_share_button_background_first);
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -695,8 +695,8 @@
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:maxLines">1</item>
|
||||
<item name="android:ellipsize">none</item>
|
||||
<item name="android:maxLines">3</item>
|
||||
<item name="android:clickable">false</item>
|
||||
<item name="android:focusable">false</item>
|
||||
</style>
|
||||
|
@ -5,6 +5,12 @@
|
||||
<PreferenceCategory
|
||||
android:key="signed_in_as_category"
|
||||
android:title="@string/fxaccount_status_signed_in_as" >
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:key="profile"
|
||||
android:icon="@drawable/sync_avatar_default"
|
||||
android:persistent="false"
|
||||
android:title="" />
|
||||
<Preference
|
||||
android:editable="false"
|
||||
android:key="email"
|
||||
|
@ -40,7 +40,7 @@
|
||||
|
||||
<org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.signon.manage"
|
||||
android:title="@string/pref_manage_logins"
|
||||
url="about:passwords"/>
|
||||
url="about:logins"/>
|
||||
|
||||
<CheckBoxPreference android:key="signon.rememberSignons"
|
||||
android:title="@string/pref_remember_signons"
|
||||
|
@ -321,7 +321,7 @@
|
||||
<string name="site_settings_clear">&site_settings_clear;</string>
|
||||
<string name="site_settings_no_settings">&site_settings_no_settings;</string>
|
||||
|
||||
<string name="reading_list_added">&reading_list_added;</string>
|
||||
<string name="reading_list_added">&reading_list_added2;</string>
|
||||
<string name="reading_list_removed">&reading_list_removed;</string>
|
||||
<string name="reading_list_remove">&reading_list_remove;</string>
|
||||
<string name="reading_list_duplicate">&reading_list_duplicate;</string>
|
||||
|
@ -23,11 +23,13 @@ package org.mozilla.gecko.widget;
|
||||
// Mozilla: New import
|
||||
import android.accounts.Account;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.TabsAccessor;
|
||||
import org.mozilla.gecko.distribution.Distribution;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.overlays.ui.ShareDialog;
|
||||
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.R;
|
||||
import java.io.File;
|
||||
@ -38,6 +40,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObservable;
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
@ -1311,8 +1314,19 @@ public class ActivityChooserModel extends DataSetObservable {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(mContext);
|
||||
return db.clientsCount() > 0;
|
||||
final BrowserDB browserDB = GeckoProfile.get(mContext).getDB();
|
||||
final TabsAccessor tabsAccessor = browserDB.getTabsAccessor();
|
||||
final Cursor remoteClientsCursor = tabsAccessor
|
||||
.getRemoteClientsByRecencyCursor(mContext);
|
||||
if (remoteClientsCursor == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return remoteClientsCursor.getCount() > 0;
|
||||
} finally {
|
||||
remoteClientsCursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,9 +20,9 @@ XPCOMUtils.defineLazyGetter(window, "gChromeWin", function()
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
|
||||
"resource://gre/modules/Prompt.jsm");
|
||||
|
||||
let debug = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "AboutPasswords");
|
||||
let debug = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "AboutLogins");
|
||||
|
||||
let gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutPasswords.properties");
|
||||
let gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutLogins.properties");
|
||||
|
||||
function copyStringAndToast(string, notifyString) {
|
||||
try {
|
||||
@ -30,15 +30,15 @@ function copyStringAndToast(string, notifyString) {
|
||||
clipboard.copyString(string);
|
||||
gChromeWin.NativeWindow.toast.show(notifyString, "short");
|
||||
} catch (e) {
|
||||
debug("Error copying from about:passwords");
|
||||
gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("passwordsDetails.copyFailed"), "short");
|
||||
debug("Error copying from about:logins");
|
||||
gChromeWin.NativeWindow.toast.show(gStringBundle.GetStringFromName("loginsDetails.copyFailed"), "short");
|
||||
}
|
||||
}
|
||||
|
||||
// Delay filtering while typing in MS
|
||||
const FILTER_DELAY = 500;
|
||||
|
||||
let Passwords = {
|
||||
let Logins = {
|
||||
_logins: [],
|
||||
_filterTimer: null,
|
||||
|
||||
@ -157,11 +157,11 @@ let Passwords = {
|
||||
window: window,
|
||||
});
|
||||
let menuItems = [
|
||||
{ label: gStringBundle.GetStringFromName("passwordsMenu.showPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("passwordsMenu.copyPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("passwordsMenu.copyUsername") },
|
||||
{ label: gStringBundle.GetStringFromName("passwordsMenu.details") },
|
||||
{ label: gStringBundle.GetStringFromName("passwordsMenu.delete") }
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.showPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyPassword") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyUsername") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.details") },
|
||||
{ label: gStringBundle.GetStringFromName("loginsMenu.delete") }
|
||||
];
|
||||
|
||||
prompt.setSingleChoiceItems(menuItems);
|
||||
@ -173,21 +173,21 @@ let Passwords = {
|
||||
window: window,
|
||||
message: login.password,
|
||||
buttons: [
|
||||
gStringBundle.GetStringFromName("passwordsDialog.copy"),
|
||||
gStringBundle.GetStringFromName("passwordsDialog.cancel") ]
|
||||
gStringBundle.GetStringFromName("loginsDialog.copy"),
|
||||
gStringBundle.GetStringFromName("loginsDialog.cancel") ]
|
||||
}).show((data) => {
|
||||
switch (data.button) {
|
||||
case 0:
|
||||
// Corresponds to "Copy password" button.
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied"));
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied"));
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
||||
break;
|
||||
case 2:
|
||||
copyStringAndToast(login.username, gStringBundle.GetStringFromName("passwordsDetails.usernameCopied"));
|
||||
copyStringAndToast(login.username, gStringBundle.GetStringFromName("loginsDetails.usernameCopied"));
|
||||
break;
|
||||
case 3:
|
||||
this._showDetails(loginItem);
|
||||
@ -196,10 +196,10 @@ let Passwords = {
|
||||
case 4:
|
||||
let confirmPrompt = new Prompt({
|
||||
window: window,
|
||||
message: gStringBundle.GetStringFromName("passwordsDialog.confirmDelete"),
|
||||
message: gStringBundle.GetStringFromName("loginsDialog.confirmDelete"),
|
||||
buttons: [
|
||||
gStringBundle.GetStringFromName("passwordsDialog.confirm"),
|
||||
gStringBundle.GetStringFromName("passwordsDialog.cancel") ]
|
||||
gStringBundle.GetStringFromName("loginsDialog.confirm"),
|
||||
gStringBundle.GetStringFromName("loginsDialog.cancel") ]
|
||||
});
|
||||
confirmPrompt.show((data) => {
|
||||
switch (data.button) {
|
||||
@ -287,7 +287,7 @@ let Passwords = {
|
||||
observe: function (subject, topic, data) {
|
||||
switch(topic) {
|
||||
case "passwordmgr-storage-changed": {
|
||||
// Reload passwords content.
|
||||
// Reload logins content.
|
||||
this._loadList(this._getLogins());
|
||||
break;
|
||||
}
|
||||
@ -317,7 +317,7 @@ let Passwords = {
|
||||
|
||||
let lastChanged = new Date(login.QueryInterface(Ci.nsILoginMetaInfo).timePasswordChanged);
|
||||
let days = Math.round((Date.now() - lastChanged) / 1000 / 60 / 60/ 24);
|
||||
document.getElementById("detail-age").textContent = gStringBundle.formatStringFromName("passwordsDetails.age", [days], 1);
|
||||
document.getElementById("detail-age").textContent = gStringBundle.formatStringFromName("loginsDetails.age", [days], 1);
|
||||
|
||||
let list = document.getElementById("logins-list");
|
||||
list.setAttribute("hidden", "true");
|
||||
@ -334,13 +334,13 @@ let Passwords = {
|
||||
_copyUsername: function() {
|
||||
let detailItem = document.querySelector("#login-details > .login-item");
|
||||
let login = detailItem.login;
|
||||
copyStringAndToast(login.username, gStringBundle.GetStringFromName("passwordsDetails.usernameCopied"));
|
||||
copyStringAndToast(login.username, gStringBundle.GetStringFromName("loginsDetails.usernameCopied"));
|
||||
},
|
||||
|
||||
_copyPassword: function() {
|
||||
let detailItem = document.querySelector("#login-details > .login-item");
|
||||
let login = detailItem.login;
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("passwordsDetails.passwordCopied"));
|
||||
copyStringAndToast(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
|
||||
},
|
||||
|
||||
_openLink: function (clickEvent) {
|
||||
@ -370,5 +370,5 @@ let Passwords = {
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("load", Passwords.init.bind(Passwords), false);
|
||||
window.addEventListener("unload", Passwords.uninit.bind(Passwords), false);
|
||||
window.addEventListener("load", Logins.init.bind(Logins), false);
|
||||
window.addEventListener("unload", Logins.uninit.bind(Logins), false);
|
@ -5,7 +5,7 @@
|
||||
%globalDTD;
|
||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
||||
%brandDTD;
|
||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutPasswords.dtd" >
|
||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutLogins.dtd" >
|
||||
%aboutDTD;
|
||||
]>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
@ -13,16 +13,16 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&aboutPasswords.title;</title>
|
||||
<title>&aboutLogins.title;</title>
|
||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutPasswords.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutPasswords.js"></script>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutLogins.css" type="text/css"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutLogins.js"></script>
|
||||
</head>
|
||||
<body dir="&locale.dir;">
|
||||
<div id="passwords-header" class="header">
|
||||
<div>&aboutPasswords.title;</div>
|
||||
<div id="logins-header" class="header">
|
||||
<div>&aboutLogins.title;</div>
|
||||
<ul class="toolbar-buttons">
|
||||
<li id="filter-button"></li>
|
||||
</ul>
|
||||
@ -41,8 +41,8 @@
|
||||
<div id="detail-age"></div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<button id="copyusername-btn">&aboutPasswords.copyUsername;</button>
|
||||
<button id="copypassword-btn">&aboutPasswords.copyPassword;</button>
|
||||
<button id="copyusername-btn">&aboutLogins.copyUsername;</button>
|
||||
<button id="copypassword-btn">&aboutLogins.copyPassword;</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -6300,6 +6300,20 @@ var XPInstallObserver = {
|
||||
},
|
||||
|
||||
onInstallFailed: function(aInstall) {
|
||||
this._showErrorMessage(aInstall);
|
||||
},
|
||||
|
||||
onDownloadProgress: function(aInstall) {},
|
||||
|
||||
onDownloadFailed: function(aInstall) {
|
||||
this._showErrorMessage(aInstall);
|
||||
},
|
||||
|
||||
onDownloadCancelled: function(aInstall) {
|
||||
this._showErrorMessage(aInstall);
|
||||
},
|
||||
|
||||
_showErrorMessage: function(aInstall) {
|
||||
// Don't create a notification for distribution add-ons.
|
||||
if (Distribution.pendingAddonInstalls.has(aInstall)) {
|
||||
Cu.reportError("Error installing distribution add-on: " + aInstall.addon.id);
|
||||
@ -6307,39 +6321,44 @@ var XPInstallObserver = {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeWindow.toast.show(Strings.browser.GetStringFromName("alertAddonsFail"), "short");
|
||||
},
|
||||
|
||||
onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {},
|
||||
|
||||
onDownloadFailed: function(aInstall) {
|
||||
this.onInstallFailed(aInstall);
|
||||
},
|
||||
|
||||
onDownloadCancelled: function(aInstall) {
|
||||
let host = (aInstall.originatingURI instanceof Ci.nsIStandardURL) && aInstall.originatingURI.host;
|
||||
if (!host)
|
||||
if (!host) {
|
||||
host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) && aInstall.sourceURI.host;
|
||||
}
|
||||
|
||||
let error = (host || aInstall.error == 0) ? "addonError" : "addonLocalError";
|
||||
if (aInstall.error != 0)
|
||||
if (aInstall.error < 0) {
|
||||
error += aInstall.error;
|
||||
else if (aInstall.addon && aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
|
||||
} else if (aInstall.addon && aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
|
||||
error += "Blocklisted";
|
||||
else if (aInstall.addon && (!aInstall.addon.isCompatible || !aInstall.addon.isPlatformCompatible))
|
||||
} else {
|
||||
error += "Incompatible";
|
||||
else
|
||||
return; // No need to show anything in this case.
|
||||
}
|
||||
|
||||
let msg = Strings.browser.GetStringFromName(error);
|
||||
// TODO: formatStringFromName
|
||||
msg = msg.replace("#1", aInstall.name);
|
||||
if (host)
|
||||
if (host) {
|
||||
msg = msg.replace("#2", host);
|
||||
}
|
||||
msg = msg.replace("#3", Strings.brand.GetStringFromName("brandShortName"));
|
||||
msg = msg.replace("#4", Services.appinfo.version);
|
||||
|
||||
NativeWindow.toast.show(msg, "short");
|
||||
if (aInstall.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
|
||||
new Prompt({
|
||||
window: window,
|
||||
title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
|
||||
message: msg,
|
||||
buttons: [Strings.browser.GetStringFromName("addonError.learnMore")]
|
||||
}).show((data) => {
|
||||
if (data.button === 0) {
|
||||
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
|
||||
BrowserApp.addTab(url, { parentId: BrowserApp.selectedTab.id });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Services.prompt.alert(null, Strings.browser.GetStringFromName("addonError.titleError"), msg);
|
||||
}
|
||||
},
|
||||
|
||||
showRestartPrompt: function() {
|
||||
|
@ -61,8 +61,8 @@ chrome.jar:
|
||||
content/aboutDevices.js (content/aboutDevices.js)
|
||||
#endif
|
||||
#ifdef NIGHTLY_BUILD
|
||||
content/aboutPasswords.xhtml (content/aboutPasswords.xhtml)
|
||||
content/aboutPasswords.js (content/aboutPasswords.js)
|
||||
content/aboutLogins.xhtml (content/aboutLogins.xhtml)
|
||||
content/aboutLogins.js (content/aboutLogins.js)
|
||||
content/WebcompatReporter.js (content/WebcompatReporter.js)
|
||||
#endif
|
||||
#ifdef MOZ_PAY
|
||||
|
@ -88,8 +88,8 @@ if (AppConstants.MOZ_DEVICES) {
|
||||
};
|
||||
}
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
modules['passwords'] = {
|
||||
uri: "chrome://browser/content/aboutPasswords.xhtml",
|
||||
modules['logins'] = {
|
||||
uri: "chrome://browser/content/aboutLogins.xhtml",
|
||||
privileged: true
|
||||
};
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ contract @mozilla.org/network/protocol/about;1?what=blocked {322ba47e-7047-4f71-
|
||||
contract @mozilla.org/network/protocol/about;1?what=devices {322ba47e-7047-4f71-aebf-cb7d69325cd9}
|
||||
#endif
|
||||
#ifdef NIGHTLY_BUILD
|
||||
contract @mozilla.org/network/protocol/about;1?what=passwords {322ba47e-7047-4f71-aebf-cb7d69325cd9}
|
||||
contract @mozilla.org/network/protocol/about;1?what=logins {322ba47e-7047-4f71-aebf-cb7d69325cd9}
|
||||
#endif
|
||||
|
||||
# DirectoryProvider.js
|
||||
|
@ -107,6 +107,11 @@ fi
|
||||
# Use the low-memory GC tuning.
|
||||
export JS_GC_SMALL_CHUNK_SIZE=1
|
||||
|
||||
# Enable FxAccount Avatar
|
||||
if test "$NIGHTLY_BUILD"; then
|
||||
MOZ_ANDROID_FIREFOX_ACCOUNT_PROFILES=1
|
||||
fi
|
||||
|
||||
# Enable checking that add-ons are signed by the trusted root
|
||||
MOZ_ADDON_SIGNING=1
|
||||
if test "$MOZ_OFFICIAL_BRANDING"; then
|
||||
|
@ -2,7 +2,7 @@
|
||||
- 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/. -->
|
||||
|
||||
<!ENTITY aboutPasswords.title "Passwords">
|
||||
<!ENTITY aboutLogins.title "Logins">
|
||||
|
||||
<!ENTITY aboutPasswords.copyUsername "Copy Username">
|
||||
<!ENTITY aboutPasswords.copyPassword "Copy Password">
|
||||
<!ENTITY aboutLogins.copyUsername "Copy Username">
|
||||
<!ENTITY aboutLogins.copyPassword "Copy Password">
|
20
mobile/android/locales/en-US/chrome/aboutLogins.properties
Normal file
20
mobile/android/locales/en-US/chrome/aboutLogins.properties
Normal file
@ -0,0 +1,20 @@
|
||||
# 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/.
|
||||
|
||||
loginsMenu.showPassword=Show password
|
||||
loginsMenu.copyPassword=Copy password
|
||||
loginsMenu.copyUsername=Copy username
|
||||
loginsMenu.details=Details
|
||||
loginsMenu.delete=Delete
|
||||
|
||||
loginsDialog.confirmDelete=Delete this login?
|
||||
loginsDialog.copy=Copy
|
||||
loginsDialog.confirm=OK
|
||||
loginsDialog.cancel=Cancel
|
||||
|
||||
loginsDetails.age=Age: %S days
|
||||
|
||||
loginsDetails.copyFailed=Copy failed
|
||||
loginsDetails.passwordCopied=Password copied
|
||||
loginsDetails.usernameCopied=Username copied
|
@ -1,20 +0,0 @@
|
||||
# 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/.
|
||||
|
||||
passwordsMenu.showPassword=Show password
|
||||
passwordsMenu.copyPassword=Copy password
|
||||
passwordsMenu.copyUsername=Copy username
|
||||
passwordsMenu.details=Details
|
||||
passwordsMenu.delete=Delete
|
||||
|
||||
passwordsDialog.confirmDelete=Delete this login?
|
||||
passwordsDialog.copy=Copy
|
||||
passwordsDialog.confirm=OK
|
||||
passwordsDialog.cancel=Cancel
|
||||
|
||||
passwordsDetails.age=Age: %S days
|
||||
|
||||
passwordsDetails.copyFailed=Copy failed
|
||||
passwordsDetails.passwordCopied=Password copied
|
||||
passwordsDetails.usernameCopied=Username copied
|
@ -7,7 +7,6 @@ addonsConfirmInstall.install=Install
|
||||
|
||||
# Alerts
|
||||
alertAddonsDownloading=Downloading add-on
|
||||
alertAddonsFail=Installation failed
|
||||
alertAddonsInstalledNoRestart.message=Installation complete
|
||||
|
||||
# LOCALIZATION NOTE (alertAddonsInstalledNoRestart.action2): Ideally, this string is short (it's a
|
||||
@ -39,19 +38,25 @@ alertSearchEngineDuplicateToast='%S' is already one of your search engines
|
||||
downloadCancelPromptTitle=Cancel Download
|
||||
downloadCancelPromptMessage=Do you want to cancel this download?
|
||||
|
||||
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
|
||||
addonError.titleError=Error
|
||||
addonError.titleBlocked=Blocked add-on
|
||||
addonError.learnMore=Learn more
|
||||
|
||||
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4, addonError-5):
|
||||
# #1 is the add-on name, #2 is the add-on host, #3 is the application name
|
||||
addonError-1=The add-on could not be downloaded because of a connection failure on #2.
|
||||
addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
|
||||
addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
|
||||
addonError-4=#1 could not be installed because #3 cannot modify the needed file.
|
||||
addonError-5=#3 has prevented #2 from installing an unverified add-on.
|
||||
|
||||
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted):
|
||||
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonLocalError-5, addonErrorIncompatible, addonErrorBlocklisted):
|
||||
# #1 is the add-on name, #3 is the application name, #4 is the application version
|
||||
addonLocalError-1=This add-on could not be installed because of a filesystem error.
|
||||
addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
|
||||
addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
|
||||
addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
|
||||
addonLocalError-5=This add-on could not be installed because it has not been verified.
|
||||
addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
|
||||
addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
|
||||
|
||||
|
@ -39,8 +39,8 @@
|
||||
locale/@AB_CD@/browser/handling.properties (%chrome/handling.properties)
|
||||
locale/@AB_CD@/browser/webapp.properties (%chrome/webapp.properties)
|
||||
#ifdef NIGHTLY_BUILD
|
||||
locale/@AB_CD@/browser/aboutPasswords.dtd (%chrome/aboutPasswords.dtd)
|
||||
locale/@AB_CD@/browser/aboutPasswords.properties (%chrome/aboutPasswords.properties)
|
||||
locale/@AB_CD@/browser/aboutLogins.dtd (%chrome/aboutLogins.dtd)
|
||||
locale/@AB_CD@/browser/aboutLogins.properties (%chrome/aboutLogins.properties)
|
||||
locale/@AB_CD@/browser/webcompatReporter.properties (%chrome/webcompatReporter.properties)
|
||||
#endif
|
||||
|
||||
|
@ -18,6 +18,7 @@ jar.sources += [
|
||||
'src/org/mozilla/tests/browser/junit3/TestImageDownloader.java',
|
||||
'src/org/mozilla/tests/browser/junit3/TestJarReader.java',
|
||||
'src/org/mozilla/tests/browser/junit3/TestRawResource.java',
|
||||
'src/org/mozilla/tests/browser/junit3/TestRemoteTabs.java',
|
||||
'src/org/mozilla/tests/browser/junit3/TestSuggestedSites.java',
|
||||
'src/org/mozilla/tests/browser/junit3/TestTopSitesCursorWrapper.java',
|
||||
]
|
||||
|
@ -0,0 +1,127 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.tests.browser.junit3;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import org.mozilla.gecko.background.db.CursorDumper;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.LocalTabsAccessor;
|
||||
import org.mozilla.gecko.db.RemoteClient;
|
||||
import org.mozilla.gecko.db.TabsAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TestRemoteTabs extends InstrumentationTestCase {
|
||||
public void testGetClientsWithoutTabsByRecencyFromCursor() throws Exception {
|
||||
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
|
||||
final ContentResolver cr = getInstrumentation().getTargetContext().getContentResolver();
|
||||
final ContentProviderClient cpc = cr.acquireContentProviderClient(uri);
|
||||
final LocalTabsAccessor accessor = new LocalTabsAccessor("test"); // The profile name given doesn't matter.
|
||||
|
||||
try {
|
||||
// Delete all tabs to begin with.
|
||||
cpc.delete(uri, null, null);
|
||||
Cursor allClients = cpc.query(uri, null, null, null, null);
|
||||
try {
|
||||
assertEquals(0, allClients.getCount());
|
||||
} finally {
|
||||
allClients.close();
|
||||
}
|
||||
|
||||
// Insert a local and remote1 client record, neither with tabs.
|
||||
final long now = System.currentTimeMillis();
|
||||
// Local client has GUID = null.
|
||||
final ContentValues local = new ContentValues();
|
||||
local.put(BrowserContract.Clients.NAME, "local");
|
||||
local.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
|
||||
// Remote clients have GUID != null.
|
||||
final ContentValues remote1 = new ContentValues();
|
||||
remote1.put(BrowserContract.Clients.GUID, "guid1");
|
||||
remote1.put(BrowserContract.Clients.NAME, "remote1");
|
||||
remote1.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
|
||||
|
||||
final ContentValues remote2 = new ContentValues();
|
||||
remote2.put(BrowserContract.Clients.GUID, "guid2");
|
||||
remote2.put(BrowserContract.Clients.NAME, "remote2");
|
||||
remote2.put(BrowserContract.Clients.LAST_MODIFIED, now + 3);
|
||||
|
||||
ContentValues[] values = new ContentValues[]{local, remote1, remote2};
|
||||
int inserted = cpc.bulkInsert(uri, values);
|
||||
assertEquals(3, inserted);
|
||||
|
||||
allClients = cpc.query(BrowserContract.Clients.CONTENT_RECENCY_URI, null, null, null, null);
|
||||
try {
|
||||
CursorDumper.dumpCursor(allClients);
|
||||
// The local client is not ignored.
|
||||
assertEquals(3, allClients.getCount());
|
||||
final List<RemoteClient> clients = accessor.getClientsWithoutTabsByRecencyFromCursor(allClients);
|
||||
assertEquals(3, clients.size());
|
||||
for (RemoteClient client : clients) {
|
||||
// Each client should not have any tabs.
|
||||
assertNotNull(client.tabs);
|
||||
assertEquals(0, client.tabs.size());
|
||||
}
|
||||
// Since there are no tabs, the order should be based on last_modified.
|
||||
assertEquals("guid2", clients.get(0).guid);
|
||||
assertEquals("guid1", clients.get(1).guid);
|
||||
assertEquals(null, clients.get(2).guid);
|
||||
} finally {
|
||||
allClients.close();
|
||||
}
|
||||
|
||||
// Now let's add a few tabs to one client. The times are chosen so that one tab's
|
||||
// last used is not relevant, and the other tab is the most recent used.
|
||||
final ContentValues remoteTab1 = new ContentValues();
|
||||
remoteTab1.put(BrowserContract.Tabs.CLIENT_GUID, "guid1");
|
||||
remoteTab1.put(BrowserContract.Tabs.TITLE, "title1");
|
||||
remoteTab1.put(BrowserContract.Tabs.URL, "http://test.com/test1");
|
||||
remoteTab1.put(BrowserContract.Tabs.HISTORY, "[\"http://test.com/test1\"]");
|
||||
remoteTab1.put(BrowserContract.Tabs.LAST_USED, now);
|
||||
remoteTab1.put(BrowserContract.Tabs.POSITION, 0);
|
||||
|
||||
final ContentValues remoteTab2 = new ContentValues();
|
||||
remoteTab2.put(BrowserContract.Tabs.CLIENT_GUID, "guid1");
|
||||
remoteTab2.put(BrowserContract.Tabs.TITLE, "title2");
|
||||
remoteTab2.put(BrowserContract.Tabs.URL, "http://test.com/test2");
|
||||
remoteTab2.put(BrowserContract.Tabs.HISTORY, "[\"http://test.com/test2\"]");
|
||||
remoteTab2.put(BrowserContract.Tabs.LAST_USED, now + 5);
|
||||
remoteTab2.put(BrowserContract.Tabs.POSITION, 1);
|
||||
|
||||
values = new ContentValues[]{remoteTab1, remoteTab2};
|
||||
inserted = cpc.bulkInsert(BrowserContract.Tabs.CONTENT_URI, values);
|
||||
assertEquals(2, inserted);
|
||||
|
||||
allClients = cpc.query(BrowserContract.Clients.CONTENT_RECENCY_URI, null, BrowserContract.Clients.GUID + " IS NOT NULL", null, null);
|
||||
try {
|
||||
CursorDumper.dumpCursor(allClients);
|
||||
// The local client is ignored.
|
||||
assertEquals(2, allClients.getCount());
|
||||
final List<RemoteClient> clients = accessor.getClientsWithoutTabsByRecencyFromCursor(allClients);
|
||||
assertEquals(2, clients.size());
|
||||
for (RemoteClient client : clients) {
|
||||
// Each client should be remote and should not have any tabs.
|
||||
assertNotNull(client.guid);
|
||||
assertNotNull(client.tabs);
|
||||
assertEquals(0, client.tabs.size());
|
||||
}
|
||||
// Since now there is a tab attached to the remote2 client more recent than the
|
||||
// remote1 client modified time, it should be first.
|
||||
assertEquals("guid1", clients.get(0).guid);
|
||||
assertEquals("guid2", clients.get(1).guid);
|
||||
} finally {
|
||||
allClients.close();
|
||||
}
|
||||
} finally {
|
||||
cpc.release();
|
||||
}
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ public class StringHelper {
|
||||
public final String ABOUT_DOWNLOADS_URL = "about:downloads";
|
||||
public final String ABOUT_HOME_URL = "about:home";
|
||||
public final String ABOUT_ADDONS_URL = "about:addons";
|
||||
public static final String ABOUT_PASSWORDS_URL = "about:passwords";
|
||||
public static final String ABOUT_LOGINS_URL = "about:logins";
|
||||
public final String ABOUT_APPS_URL = "about:apps";
|
||||
public final String ABOUT_ABOUT_URL = "about:about";
|
||||
public final String ABOUT_SCHEME = "about:";
|
||||
|
@ -6,7 +6,7 @@ subsuite = robocop
|
||||
[testAboutPage.java]
|
||||
# disabled on Android 2.3; bug 975187
|
||||
skip-if = android_version == "10"
|
||||
[testAboutPasswords.java]
|
||||
[testAboutLogins.java]
|
||||
[testAddonManager.java]
|
||||
# disabled on 2.3: bug 941624, bug 1063509, bug 1073374, bug 1087221, bug 1088023, bug 1088027, bug 1090206; on 4.3, bug 1144918
|
||||
skip-if = android_version == "10" || android_version == "18"
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user