merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2014-07-15 16:08:30 +02:00
commit 4457b01a36
38 changed files with 892 additions and 127 deletions

View File

@ -376,7 +376,7 @@
label="&spellCheckToggle.label;"
type="checkbox"
accesskey="&spellCheckToggle.accesskey;"
oncommand="InlineSpellCheckerUI.toggleEnabled();"/>
oncommand="InlineSpellCheckerUI.toggleEnabled(window);"/>
<menuitem id="spell-add-dictionaries-main"
label="&spellAddDictionaries.label;"
accesskey="&spellAddDictionaries.accesskey;"

View File

@ -26,7 +26,8 @@ support-files =
[browser_graphs-09.js]
[browser_graphs-10a.js]
[browser_graphs-10b.js]
[browser_graphs-11.js]
[browser_graphs-11a.js]
[browser_graphs-11b.js]
[browser_graphs-12.js]
[browser_graphs-13.js]
[browser_graphs-14.js]

View File

@ -1,7 +1,7 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that graph widgets can properly add data and regions.
// Tests that graph widgets can properly add data, regions and highlights.
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
const TEST_REGIONS = [{ start: 320, end: 460 }, { start: 780, end: 860 }];
@ -22,13 +22,14 @@ function* performTest() {
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
testGraph(graph);
testDataAndRegions(graph);
testHighlights(graph);
graph.destroy();
host.destroy();
}
function testGraph(graph) {
function testDataAndRegions(graph) {
let thrown1;
try {
graph.setRegions(TEST_REGIONS);
@ -52,7 +53,7 @@ function testGraph(graph) {
ok(graph.hasRegions(), "The graph should now have the regions set.");
is(graph.dataScaleX,
graph.width / (4180 - 112), // last & first tick in TEST_DATA
graph.width / 4180, // last & first tick in TEST_DATA
"The data scale on the X axis is correct.");
is(graph.dataScaleY,
@ -69,3 +70,17 @@ function testGraph(graph) {
"The region's end value was properly normalized.");
}
}
function testHighlights(graph) {
graph.setMask(TEST_REGIONS);
ok(graph.hasMask(),
"The graph should now have the highlights set.");
graph.setMask([]);
ok(graph.hasMask(),
"The graph shouldn't have anything highlighted.");
graph.setMask(null);
ok(!graph.hasMask(),
"The graph should have everything highlighted.");
}

View File

@ -0,0 +1,132 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that bar graph's legend items handle mouseover/mouseout.
let {BarGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
const CATEGORIES = [
{ color: "#46afe3", label: "Foo" },
{ color: "#eb5368", label: "Bar" },
{ color: "#70bf53", label: "Baz" }
];
let test = Task.async(function*() {
yield promiseTab("about:blank");
yield performTest();
gBrowser.removeCurrentTab();
finish();
});
function* performTest() {
let [host, win, doc] = yield createHost();
doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new BarGraphWidget(doc.body, 1);
graph.fixedWidth = 200;
graph.fixedHeight = 100;
yield graph.once("ready");
yield testGraph(graph);
graph.destroy();
host.destroy();
}
function* testGraph(graph) {
graph.format = CATEGORIES;
graph.dataOffsetX = 1000;
graph.setData([{
delta: 1100, values: [0, 2, 3]
}, {
delta: 1200, values: [1, 0, 2]
}, {
delta: 1300, values: [2, 1, 0]
}, {
delta: 1400, values: [0, 3, 1]
}, {
delta: 1500, values: [3, 0, 2]
}, {
delta: 1600, values: [3, 2, 0]
}]);
is(graph._blocksBoundingRects.toSource(), "[{type:1, start:0, end:33.33333333333333, top:70, bottom:100}, {type:2, start:0, end:33.33333333333333, top:24, bottom:69}, {type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100}, {type:2, start:34.33333333333333, end:66.66666666666666, top:54, bottom:84}, {type:0, start:67.66666666666666, end:100, top:70, bottom:100}, {type:1, start:67.66666666666666, end:100, top:54, bottom:69}, {type:1, start:101, end:133.33333333333331, top:55, bottom:100}, {type:2, start:101, end:133.33333333333331, top:39, bottom:54}, {type:0, start:134.33333333333331, end:166.66666666666666, top:55, bottom:100}, {type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54}, {type:0, start:167.66666666666666, end:200, top:55, bottom:100}, {type:1, start:167.66666666666666, end:200, top:24, bottom:54}]",
"The correct blocks bounding rects were calculated for the bar graph.");
let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
is(legendItems.length, 3,
"Three legend items should exist in the entire graph.");
yield testLegend(graph, 0, {
highlights: "[{type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100}, {type:0, start:67.66666666666666, end:100, top:70, bottom:100}, {type:0, start:134.33333333333331, end:166.66666666666666, top:55, bottom:100}, {type:0, start:167.66666666666666, end:200, top:55, bottom:100}]",
selection: "({start:34.33333333333333, end:200})",
leftmost: "({type:0, start:34.33333333333333, end:66.66666666666666, top:85, bottom:100})",
rightmost: "({type:0, start:167.66666666666666, end:200, top:55, bottom:100})"
});
yield testLegend(graph, 1, {
highlights: "[{type:1, start:0, end:33.33333333333333, top:70, bottom:100}, {type:1, start:67.66666666666666, end:100, top:54, bottom:69}, {type:1, start:101, end:133.33333333333331, top:55, bottom:100}, {type:1, start:167.66666666666666, end:200, top:24, bottom:54}]",
selection: "({start:0, end:200})",
leftmost: "({type:1, start:0, end:33.33333333333333, top:70, bottom:100})",
rightmost: "({type:1, start:167.66666666666666, end:200, top:24, bottom:54})"
});
yield testLegend(graph, 2, {
highlights: "[{type:2, start:0, end:33.33333333333333, top:24, bottom:69}, {type:2, start:34.33333333333333, end:66.66666666666666, top:54, bottom:84}, {type:2, start:101, end:133.33333333333331, top:39, bottom:54}, {type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54}]",
selection: "({start:0, end:166.66666666666666})",
leftmost: "({type:2, start:0, end:33.33333333333333, top:24, bottom:69})",
rightmost: "({type:2, start:134.33333333333331, end:166.66666666666666, top:24, bottom:54})"
});
}
function* testLegend(graph, index, { highlights, selection, leftmost, rightmost }) {
// Hover.
let legendItems = graph._document.querySelectorAll(".bar-graph-widget-legend-item");
let colorBlock = legendItems[index].querySelector("[view=color]");
let debounced = graph.once("legend-hover");
graph._onLegendMouseOver({ target: colorBlock });
ok(!graph.hasMask(), "The graph shouldn't get highlights immediately.");
let [type, rects] = yield debounced;
ok(graph.hasMask(), "The graph should now have highlights.");
is(type, index,
"The legend item was correctly hovered.");
is(rects.toSource(), highlights,
"The legend item highlighted the correct regions.");
// Unhover.
let unhovered = graph.once("legend-unhover");
graph._onLegendMouseOut();
ok(!graph.hasMask(), "The graph shouldn't have highlights anymore.");
yield unhovered;
ok(true, "The 'legend-mouseout' event was emitted.");
// Select.
let selected = graph.once("legend-selection");
graph._onLegendMouseDown(mockEvent(colorBlock));
ok(graph.hasSelection(), "The graph should now have a selection.");
is(graph.getSelection().toSource(), selection, "The graph has a correct selection.");
let [left, right] = yield selected;
is(left.toSource(), leftmost, "The correct leftmost data block was found.");
is(right.toSource(), rightmost, "The correct rightmost data block was found.");
// Deselect.
graph.dropSelection();
}
function mockEvent(node) {
return {
target: node,
preventDefault: () => {},
stopPropagation: () => {}
};
}

View File

@ -76,6 +76,11 @@ const BAR_GRAPH_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
const BAR_GRAPH_REGION_BACKGROUND_COLOR = "transparent";
const BAR_GRAPH_REGION_STRIPES_COLOR = "rgba(237,38,85,0.2)";
const BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND = "rgba(255,255,255,0.75)";
const BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES = "rgba(255,255,255,0.5)";
const BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE = 50; // ms
/**
* Small data primitives for all graphs.
*/
@ -232,11 +237,17 @@ AbstractCanvasGraph.prototype = {
this._iframe.remove();
this._data = null;
this._mask = null;
this._maskArgs = null;
this._regions = null;
this._cachedBackgroundImage = null;
this._cachedGraphImage = null;
this._cachedMaskImage = null;
this._renderTargets.clear();
gCachedStripePattern.clear();
this.emit("destroyed");
},
/**
@ -275,6 +286,15 @@ AbstractCanvasGraph.prototype = {
throw "This method needs to be implemented by inheriting classes.";
},
/**
* Optionally builds and caches a mask image for this graph, composited
* over the data image created via `buildGraphImage`. Inheriting classes
* may override this method.
*/
buildMaskImage: function() {
return null;
},
/**
* When setting the data source, the coordinates and values may be
* stretched or squeezed on the X/Y axis, to fit into the available space.
@ -308,6 +328,19 @@ AbstractCanvasGraph.prototype = {
this.setData(data);
}),
/**
* Adds a mask to this graph.
*
* @param any mask, options
* See `buildMaskImage` in inheriting classes for the required args.
*/
setMask: function(mask, ...options) {
this._mask = mask;
this._maskArgs = [mask, ...options];
this._cachedMaskImage = this.buildMaskImage.apply(this, this._maskArgs);
this._shouldRedraw = true;
},
/**
* Adds regions to this graph.
*
@ -340,6 +373,14 @@ AbstractCanvasGraph.prototype = {
return !!this._data;
},
/**
* Gets whether or not this graph has any mask applied.
* @return boolean
*/
hasMask: function() {
return !!this._mask;
},
/**
* Gets whether or not this graph has any regions.
* @return boolean
@ -582,6 +623,9 @@ AbstractCanvasGraph.prototype = {
this._cachedBackgroundImage = this.buildBackgroundImage();
this._cachedGraphImage = this.buildGraphImage();
}
if (this.hasMask()) {
this._cachedMaskImage = this.buildMaskImage.apply(this, this._maskArgs);
}
if (this.hasRegions()) {
this._bakeRegions(this._regions, this._cachedGraphImage);
}
@ -648,12 +692,22 @@ AbstractCanvasGraph.prototype = {
let ctx = this._ctx;
ctx.clearRect(0, 0, this._width, this._height);
if (this._cachedBackgroundImage) {
ctx.drawImage(this._cachedBackgroundImage, 0, 0, this._width, this._height);
}
if (this._cachedGraphImage) {
ctx.drawImage(this._cachedGraphImage, 0, 0, this._width, this._height);
}
if (this._cachedMaskImage) {
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(this._cachedMaskImage, 0, 0, this._width, this._height);
}
if (this._cachedBackgroundImage) {
ctx.globalCompositeOperation = "destination-over";
ctx.drawImage(this._cachedBackgroundImage, 0, 0, this._width, this._height);
}
// Revert to the original global composition operation.
if (this._cachedMaskImage || this._cachedBackgroundImage) {
ctx.globalCompositeOperation = "source-over";
}
if (this.hasCursor()) {
this._drawCliphead();
@ -1112,6 +1166,11 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
regionBackgroundColor: LINE_GRAPH_REGION_BACKGROUND_COLOR,
regionStripesColor: LINE_GRAPH_REGION_STRIPES_COLOR,
/**
* Optionally offsets the `delta` in the data source by this scalar.
*/
dataOffsetX: 0,
/**
* Points that are too close too each other in the graph will not be rendered.
* This scalar specifies the required minimum squared distance between points.
@ -1119,7 +1178,7 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
minDistanceBetweenPoints: LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS,
/**
* Renders the graph on a canvas.
* Renders the graph's data source.
* @see AbstractCanvasGraph.prototype.buildGraphImage
*/
buildGraphImage: function() {
@ -1140,7 +1199,7 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
sumValues += value;
}
let dataScaleX = this.dataScaleX = width / (lastTick - firstTick);
let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
let dataScaleY = this.dataScaleY = height / maxValue * LINE_GRAPH_DAMPEN_VALUES;
/**
@ -1166,7 +1225,7 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
let prevY = 0;
for (let { delta, value } of this._data) {
let currX = (delta - firstTick) * dataScaleX;
let currX = (delta - this.dataOffsetX) * dataScaleX;
let currY = height - value * dataScaleY;
if (delta == firstTick) {
@ -1351,9 +1410,24 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
this.BarGraphWidget = function(parent, ...args) {
AbstractCanvasGraph.apply(this, [parent, "bar-graph", ...args]);
// Populated with [node, event, listener] entries which need to be removed
// when this graph is being destroyed.
this.outstandingEventListeners = [];
this.once("ready", () => {
this._onLegendMouseOver = this._onLegendMouseOver.bind(this);
this._onLegendMouseOut = this._onLegendMouseOut.bind(this);
this._onLegendMouseDown = this._onLegendMouseDown.bind(this);
this._onLegendMouseUp = this._onLegendMouseUp.bind(this);
this._createLegend();
});
this.once("destroyed", () => {
for (let [node, event, listener] of this.outstandingEventListeners) {
node.removeEventListener(event, listener);
}
this.outstandingEventListeners = null;
});
}
BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
@ -1370,6 +1444,11 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
*/
format: null,
/**
* Optionally offsets the `delta` in the data source by this scalar.
*/
dataOffsetX: 0,
/**
* Bars that are too close too each other in the graph will be combined.
* This scalar specifies the required minimum width of each bar.
@ -1401,7 +1480,7 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
},
/**
* Renders the graph on a canvas.
* Renders the graph's data source.
* @see AbstractCanvasGraph.prototype.buildGraphImage
*/
buildGraphImage: function() {
@ -1414,17 +1493,15 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
let totalTypes = this.format.length;
let totalTicks = this._data.length;
let firstTick = this._data[0].delta;
let lastTick = this._data[totalTicks - 1].delta;
let minBarsWidth = this.minBarsWidth * this._pixelRatio;
let minBlocksHeight = this.minBlocksHeight * this._pixelRatio;
let dataScaleX = this.dataScaleX = width / (lastTick - firstTick);
let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
let dataScaleY = this.dataScaleY = height / this._calcMaxHeight({
data: this._data,
dataScaleX: dataScaleX,
dataOffsetX: firstTick,
minBarsWidth: minBarsWidth
}) * BAR_GRAPH_DAMPEN_VALUES;
@ -1434,6 +1511,7 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
// the same color in a single pass. See the @constructor for more
// information about the data source, and how a "bar" contains "blocks".
this._blocksBoundingRects = [];
let prevHeight = [];
let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
let unscaledMarginTop = BAR_GRAPH_BARS_MARGIN_TOP;
@ -1442,17 +1520,17 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
ctx.fillStyle = this.format[type].color || "#000";
ctx.beginPath();
let prevLeft = 0;
let prevRight = 0;
let skippedCount = 0;
let skippedHeight = 0;
for (let tick = 0; tick < totalTicks; tick++) {
let delta = this._data[tick].delta;
let value = this._data[tick].values[type] || 0;
let blockLeft = (delta - firstTick) * dataScaleX;
let blockRight = (delta - this.dataOffsetX) * dataScaleX;
let blockHeight = value * dataScaleY;
let blockWidth = blockLeft - prevLeft;
let blockWidth = blockRight - prevRight;
if (blockWidth < minBarsWidth) {
skippedCount++;
skippedHeight += blockHeight;
@ -1462,10 +1540,19 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
let averageHeight = (blockHeight + skippedHeight) / (skippedCount + 1);
if (averageHeight >= minBlocksHeight) {
let bottom = height - ~~prevHeight[tick];
ctx.moveTo(prevLeft, bottom);
ctx.lineTo(prevLeft, bottom - averageHeight);
ctx.lineTo(blockLeft, bottom - averageHeight);
ctx.lineTo(blockLeft, bottom);
ctx.moveTo(prevRight, bottom);
ctx.lineTo(prevRight, bottom - averageHeight);
ctx.lineTo(blockRight, bottom - averageHeight);
ctx.lineTo(blockRight, bottom);
// Remember this block's type and location.
this._blocksBoundingRects.push({
type: type,
start: prevRight,
end: blockRight,
top: bottom - averageHeight,
bottom: bottom
});
if (prevHeight[tick] === undefined) {
prevHeight[tick] = averageHeight + unscaledMarginTop;
@ -1474,7 +1561,7 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
}
}
prevLeft += blockWidth + scaledMarginEnd;
prevRight += blockWidth + scaledMarginEnd;
skippedHeight = 0;
skippedCount = 0;
}
@ -1482,6 +1569,11 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
ctx.fill();
}
// The blocks bounding rects isn't guaranteed to be sorted ascending by
// block location on the X axis. This should be the case, for better
// cache cohesion and a faster `buildMaskImage`.
this._blocksBoundingRects.sort((a, b) => a.start > b.start ? 1 : -1);
// Update the legend.
while (this._legendNode.hasChildNodes()) {
@ -1494,6 +1586,74 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
return canvas;
},
/**
* Renders the graph's mask.
* Fades in only the parts of the graph that are inside the specified areas.
*
* @param array highlights
* A list of { start, end } values. Optionally, each object
* in the list may also specify { top, bottom } pixel values if the
* highlighting shouldn't span across the full height of the graph.
* @param boolean inPixels
* Set this to true if the { start, end } values in the highlights
* list are pixel values, and not values from the data source.
* @param function unpack [optional]
* @see AbstractCanvasGraph.prototype.getMappedSelection
*/
buildMaskImage: function(highlights, inPixels = false, unpack = e => e.delta) {
// A null `highlights` array is used to clear the mask. An empty array
// will mask the entire graph.
if (!highlights) {
return null;
}
// Get a render target for the highlights. It will be overlaid on top of
// the existing graph, masking the areas that aren't highlighted.
let { canvas, ctx } = this._getNamedCanvas("graph-highlights");
let width = this._width;
let height = this._height;
// Draw the background mask.
let pattern = AbstractCanvasGraph.getStripePattern({
ownerDocument: this._document,
backgroundColor: BAR_GRAPH_HIGHLIGHTS_MASK_BACKGROUND,
stripesColor: BAR_GRAPH_HIGHLIGHTS_MASK_STRIPES
});
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, width, height);
// Clear highlighted areas.
let totalTicks = this._data.length;
let firstTick = unpack(this._data[0]);
let lastTick = unpack(this._data[totalTicks - 1]);
for (let { start, end, top, bottom } of highlights) {
if (!inPixels) {
start = map(start, firstTick, lastTick, 0, width);
end = map(end, firstTick, lastTick, 0, width);
}
let firstSnap = findFirst(this._blocksBoundingRects, e => e.start >= start);
let lastSnap = findLast(this._blocksBoundingRects, e => e.start >= start && e.end <= end);
let x1 = firstSnap ? firstSnap.start : start;
let x2 = lastSnap ? lastSnap.end : firstSnap ? firstSnap.end : end;
let y1 = top || 0;
let y2 = bottom || height;
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
}
return canvas;
},
/**
* A list storing the bounding rectangle for each drawn block in the graph.
* Created whenever `buildGraphImage` is invoked.
*/
_blocksBoundingRects: null,
/**
* Calculates the height of the tallest bar that would eventially be rendered
* in this graph.
@ -1504,18 +1664,18 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
* @return number
* The tallest bar height in this graph.
*/
_calcMaxHeight: function({ data, dataScaleX, dataOffsetX, minBarsWidth }) {
_calcMaxHeight: function({ data, dataScaleX, minBarsWidth }) {
let maxHeight = 0;
let prevLeft = 0;
let prevRight = 0;
let skippedCount = 0;
let skippedHeight = 0;
let scaledMarginEnd = BAR_GRAPH_BARS_MARGIN_END * this._pixelRatio;
for (let { delta, values } of data) {
let barLeft = (delta - dataOffsetX) * dataScaleX;
let barRight = (delta - this.dataOffsetX) * dataScaleX;
let barHeight = values.reduce((a, b) => a + b, 0);
let barWidth = barLeft - prevLeft;
let barWidth = barRight - prevRight;
if (barWidth < minBarsWidth) {
skippedCount++;
skippedHeight += barHeight;
@ -1525,7 +1685,7 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
let averageHeight = (barHeight + skippedHeight) / (skippedCount + 1);
maxHeight = Math.max(averageHeight, maxHeight);
prevLeft += barWidth + scaledMarginEnd;
prevRight += barWidth + scaledMarginEnd;
skippedHeight = 0;
skippedCount = 0;
}
@ -1551,7 +1711,17 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
let colorNode = this._document.createElementNS(HTML_NS, "span");
colorNode.setAttribute("view", "color");
colorNode.setAttribute("data-index", this._legendNode.childNodes.length);
colorNode.style.backgroundColor = color;
colorNode.addEventListener("mouseover", this._onLegendMouseOver);
colorNode.addEventListener("mouseout", this._onLegendMouseOut);
colorNode.addEventListener("mousedown", this._onLegendMouseDown);
colorNode.addEventListener("mouseup", this._onLegendMouseUp);
this.outstandingEventListeners.push([colorNode, "mouseover", this._onLegendMouseOver]);
this.outstandingEventListeners.push([colorNode, "mouseout", this._onLegendMouseOut]);
this.outstandingEventListeners.push([colorNode, "mousedown", this._onLegendMouseDown]);
this.outstandingEventListeners.push([colorNode, "mouseup", this._onLegendMouseUp]);
let labelNode = this._document.createElementNS(HTML_NS, "span");
labelNode.setAttribute("view", "label");
@ -1560,6 +1730,65 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
itemNode.appendChild(colorNode);
itemNode.appendChild(labelNode);
this._legendNode.appendChild(itemNode);
},
/**
* Invoked whenever a color node in the legend is hovered.
*/
_onLegendMouseOver: function(e) {
setNamedTimeout("bar-graph-debounce", BAR_GRAPH_LEGEND_MOUSEOVER_DEBOUNCE, () => {
let type = e.target.dataset.index;
let rects = this._blocksBoundingRects.filter(e => e.type == type);
this._originalHighlights = this._mask;
this._hasCustomHighlights = true;
this.setMask(rects, true);
this.emit("legend-hover", [type, rects]);
});
},
/**
* Invoked whenever a color node in the legend is unhovered.
*/
_onLegendMouseOut: function() {
clearNamedTimeout("bar-graph-debounce");
if (this._hasCustomHighlights) {
this.setMask(this._originalHighlights);
this._hasCustomHighlights = false;
this._originalHighlights = null;
}
this.emit("legend-unhover");
},
/**
* Invoked whenever a color node in the legend is pressed.
*/
_onLegendMouseDown: function(e) {
e.preventDefault();
e.stopPropagation();
let type = e.target.dataset.index;
let rects = this._blocksBoundingRects.filter(e => e.type == type);
let leftmost = rects[0];
let rightmost = rects[rects.length - 1];
if (!leftmost || !rightmost) {
this.dropSelection();
} else {
this.setSelection({ start: leftmost.start, end: rightmost.end });
}
this.emit("legend-selection", [leftmost, rightmost]);
},
/**
* Invoked whenever a color node in the legend is released.
*/
_onLegendMouseUp: function(e) {
e.preventDefault();
e.stopPropagation();
}
});
@ -1694,7 +1923,35 @@ this.CanvasGraphUtils = {
/**
* Maps a value from one range to another.
* @param number value, istart, istop, ostart, ostop
* @return number
*/
function map(value, istart, istop, ostart, ostop) {
return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
}
/**
* Finds the first element in an array that validates a predicate.
* @param array
* @param function predicate
* @return number
*/
function findFirst(array, predicate) {
for (let i = 0, len = array.length; i < len; i++) {
let element = array[i];
if (predicate(element)) return element;
}
}
/**
* Finds the last element in an array that validates a predicate.
* @param array
* @param function predicate
* @return number
*/
function findLast(array, predicate) {
for (let i = array.length - 1; i >= 0; i--) {
let element = array[i];
if (predicate(element)) return element;
}
}

View File

@ -926,10 +926,6 @@
cursor: grabbing;
}
.graph-widget-canvas ~ * {
pointer-events: none;
}
/* Line graph widget */
.line-graph-widget-canvas {
@ -944,6 +940,7 @@
top: 0;
left: 0;
border-right: 1px solid rgba(255,255,255,0.25);
pointer-events: none;
}
.line-graph-widget-gutter-line {
@ -976,6 +973,7 @@
transform: translateY(-50%);
font-size: 80%;
z-index: 1;
pointer-events: none;
}
.line-graph-widget-tooltip::before {
@ -1053,6 +1051,7 @@
left: 8px;
color: #292e33;
font-size: 80%;
pointer-events: none;
}
.bar-graph-widget-legend-item {
@ -1072,6 +1071,8 @@
border: 1px solid #fff;
border-radius: 1px;
-moz-margin-end: 4px;
pointer-events: all;
cursor: pointer;
}
.bar-graph-widget-legend-item > [view="label"] {

View File

@ -152,6 +152,7 @@
#include "mozilla/dom/telephony/PTelephonyChild.h"
#include "mozilla/dom/time/DateCacheCleaner.h"
#include "mozilla/net/NeckoMessageUtils.h"
#include "mozilla/RemoteSpellCheckEngineChild.h"
using namespace base;
using namespace mozilla;
@ -1054,6 +1055,20 @@ ContentChild::AllocPBlobChild(const BlobConstructorParams& aParams)
return nsIContentChild::AllocPBlobChild(aParams);
}
mozilla::PRemoteSpellcheckEngineChild *
ContentChild::AllocPRemoteSpellcheckEngineChild()
{
NS_NOTREACHED("Default Constructor for PRemoteSpellcheckEngineChilf should never be called");
return nullptr;
}
bool
ContentChild::DeallocPRemoteSpellcheckEngineChild(PRemoteSpellcheckEngineChild *child)
{
delete child;
return true;
}
bool
ContentChild::DeallocPBlobChild(PBlobChild* aActor)
{

View File

@ -26,6 +26,7 @@ struct ResourceMapping;
struct OverrideMapping;
namespace mozilla {
class RemoteSpellcheckEngineChild;
namespace ipc {
class OptionalURIParams;
@ -243,6 +244,8 @@ public:
virtual mozilla::jsipc::PJavaScriptChild* AllocPJavaScriptChild() MOZ_OVERRIDE;
virtual bool DeallocPJavaScriptChild(mozilla::jsipc::PJavaScriptChild*) MOZ_OVERRIDE;
virtual PRemoteSpellcheckEngineChild* AllocPRemoteSpellcheckEngineChild() MOZ_OVERRIDE;
virtual bool DeallocPRemoteSpellcheckEngineChild(PRemoteSpellcheckEngineChild*) MOZ_OVERRIDE;
virtual bool RecvSetOffline(const bool& offline) MOZ_OVERRIDE;

View File

@ -158,6 +158,8 @@ using namespace mozilla::system;
#include "JavaScriptParent.h"
#include "mozilla/RemoteSpellCheckEngineParent.h"
#ifdef MOZ_B2G_FM
#include "mozilla/dom/FMRadioParent.h"
#endif
@ -2695,6 +2697,20 @@ ContentParent::DeallocPBlobParent(PBlobParent* aActor)
return true;
}
mozilla::PRemoteSpellcheckEngineParent *
ContentParent::AllocPRemoteSpellcheckEngineParent()
{
mozilla::RemoteSpellcheckEngineParent *parent = new mozilla::RemoteSpellcheckEngineParent();
return parent;
}
bool
ContentParent::DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent *parent)
{
delete parent;
return true;
}
void
ContentParent::KillHard()
{

View File

@ -36,6 +36,7 @@ class nsIMemoryReporter;
class ParentIdleListener;
namespace mozilla {
class PRemoteSpellcheckEngineParent;
namespace ipc {
class OptionalURIParams;
@ -252,6 +253,7 @@ public:
RecvPJavaScriptConstructor(PJavaScriptParent* aActor) MOZ_OVERRIDE {
return PContentParent::RecvPJavaScriptConstructor(aActor);
}
virtual PRemoteSpellcheckEngineParent* AllocPRemoteSpellcheckEngineParent() MOZ_OVERRIDE;
virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
const nsString& aPageURL,
@ -395,6 +397,7 @@ private:
virtual bool DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*) MOZ_OVERRIDE;
virtual bool DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) MOZ_OVERRIDE;
virtual PBrowserParent* AllocPBrowserParent(const IPCTabContext& aContext,
const uint32_t& aChromeFlags,
const uint64_t& aId,

View File

@ -31,6 +31,7 @@ include protocol PStorage;
include protocol PTelephony;
include protocol PTestShell;
include protocol PJavaScript;
include protocol PRemoteSpellcheckEngine;
include DOMTypes;
include JavaScriptTypes;
include InputStreamParams;
@ -311,6 +312,7 @@ intr protocol PContent
manages PTelephony;
manages PTestShell;
manages PJavaScript;
manages PRemoteSpellcheckEngine;
both:
// Depending on exactly how the new browser is being created, it might be
@ -464,6 +466,7 @@ parent:
async PJavaScript();
sync PRemoteSpellcheckEngine();
PDeviceStorageRequest(DeviceStorageParams params);
PFileSystemRequest(FileSystemParams params);

View File

@ -42,6 +42,7 @@
#include "nsString.h" // for nsAutoString, nsString, etc
#include "nsStringFwd.h" // for nsAFlatString
#include "nsStyleUtil.h" // for nsStyleUtil
#include "nsXULAppAPI.h" // for XRE_GetProcessType
using namespace mozilla;
@ -705,8 +706,16 @@ nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallba
NS_ENSURE_STATE(doc);
doc->GetContentLanguage(fetcher->mRootDocContentLang);
rv = fetcher->Fetch(mEditor);
NS_ENSURE_SUCCESS(rv, rv);
if (XRE_GetProcessType() == GeckoProcessType_Content) {
// Content prefs don't work in E10S (Bug 1027898) pretend that we
// didn't have any & trigger the asynchrous completion.
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethodWithArg<uint16_t>(fetcher, &DictionaryFetcher::HandleCompletion, 0);
NS_DispatchToMainThread(runnable);
} else {
rv = fetcher->Fetch(mEditor);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}

View 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/. */
include protocol PContent;
namespace mozilla {
sync protocol PRemoteSpellcheckEngine {
manager PContent;
parent:
__delete__();
sync CheckForMisspelling(nsString aWord) returns (bool isMisspelled);
sync SetDictionary(nsString aDictionary) returns (bool success);
};
} // namespace mozilla

View File

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "RemoteSpellCheckEngineChild.h"
namespace mozilla {
RemoteSpellcheckEngineChild::RemoteSpellcheckEngineChild(mozSpellChecker *aOwner)
:mOwner(aOwner)
{
}
RemoteSpellcheckEngineChild::~RemoteSpellcheckEngineChild()
{
// null out the owner's SpellcheckEngineChild to prevent state corruption
// during shutdown
mOwner->DeleteRemoteEngine();
}
} //namespace mozilla

View File

@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef RemoteSpellcheckEngineChild_h_
#define RemoteSpellcheckEngineChild_h_
#include "mozilla/PRemoteSpellcheckEngineChild.h"
#include "mozSpellChecker.h"
class mozSpellChecker;
namespace mozilla {
class RemoteSpellcheckEngineChild : public mozilla::PRemoteSpellcheckEngineChild
{
public:
RemoteSpellcheckEngineChild(mozSpellChecker *aOwner);
~RemoteSpellcheckEngineChild();
private:
mozSpellChecker *mOwner;
};
} //namespace mozilla
#endif // RemoteSpellcheckEngineChild_h_

View File

@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "RemoteSpellCheckEngineParent.h"
#include "mozISpellCheckingEngine.h"
#include "nsServiceManagerUtils.h"
#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
namespace mozilla {
RemoteSpellcheckEngineParent::RemoteSpellcheckEngineParent()
{
mEngine = do_GetService(DEFAULT_SPELL_CHECKER);
}
RemoteSpellcheckEngineParent::~RemoteSpellcheckEngineParent()
{
}
bool
RemoteSpellcheckEngineParent::RecvSetDictionary(
const nsString& aDictionary,
bool* success)
{
nsresult rv = mEngine->SetDictionary(aDictionary.get());
*success = NS_SUCCEEDED(rv);
return true;
}
bool
RemoteSpellcheckEngineParent::RecvCheckForMisspelling(
const nsString& aWord,
bool* isMisspelled)
{
bool isCorrect = false;
mEngine->Check(aWord.get(), &isCorrect);
*isMisspelled = !isCorrect;
return true;
}
void
RemoteSpellcheckEngineParent::ActorDestroy(ActorDestroyReason aWhy)
{
}
} // namespace mozilla

View File

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef RemoteSpellcheckEngineParent_h_
#define RemoteSpellcheckEngineParent_h_
#include "mozISpellCheckingEngine.h"
#include "mozilla/PRemoteSpellcheckEngineParent.h"
#include "nsCOMPtr.h"
namespace mozilla {
class RemoteSpellcheckEngineParent : public mozilla::PRemoteSpellcheckEngineParent {
public:
RemoteSpellcheckEngineParent();
~RemoteSpellcheckEngineParent();
virtual void ActorDestroy(ActorDestroyReason aWhy);
bool RecvSetDictionary(const nsString& aDictionary, bool* success);
bool RecvCheckForMisspelling( const nsString& aWord, bool* isMisspelled);
private:
nsCOMPtr<mozISpellCheckingEngine> mEngine;
};
}
#endif

View File

@ -4,9 +4,11 @@
# 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/.
UNIFIED_SOURCES += [
SOURCES += [
'mozHunspell.cpp',
'mozHunspellDirProvider.cpp',
'RemoteSpellCheckEngineChild.cpp',
'RemoteSpellCheckEngineParent.cpp',
]
if not CONFIG['MOZ_NATIVE_HUNSPELL']:
@ -40,3 +42,14 @@ LOCAL_INCLUDES += [
# Suppress warnings in third-party code.
if CONFIG['CLANG_CXX']:
CXXFLAGS += ['-Wno-unused-private-field']
include('/ipc/chromium/chromium-config.mozbuild')
IPDL_SOURCES = [
'PRemoteSpellcheckEngine.ipdl',
]
EXPORTS.mozilla += [
'RemoteSpellCheckEngineChild.h',
'RemoteSpellCheckEngineParent.h',
]

View File

@ -4,7 +4,8 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES += [
include('/ipc/chromium/chromium-config.mozbuild')
SOURCES += [
'mozEnglishWordUtils.cpp',
'mozGenericWordUtils.cpp',
'mozInlineSpellChecker.cpp',
@ -24,5 +25,8 @@ LOCAL_INCLUDES += [
'/content/base/src',
'/editor/libeditor/base',
]
EXPORTS.mozilla += [
'mozSpellChecker.h',
]
FAIL_ON_WARNINGS = True

View File

@ -10,6 +10,13 @@
#include "nsICategoryManager.h"
#include "nsISupportsPrimitives.h"
#include "nsISimpleEnumerator.h"
#include "mozilla/PRemoteSpellcheckEngineChild.h"
#include "mozilla/dom/ContentChild.h"
#include "nsXULAppAPI.h"
using mozilla::dom::ContentChild;
using mozilla::PRemoteSpellcheckEngineChild;
using mozilla::RemoteSpellcheckEngineChild;
#define DEFAULT_SPELL_CHECKER "@mozilla.org/spellchecker/engine;1"
@ -38,6 +45,10 @@ mozSpellChecker::~mozSpellChecker()
}
mSpellCheckingEngine = nullptr;
mPersonalDictionary = nullptr;
if(XRE_GetProcessType() == GeckoProcessType_Content) {
mEngine->Send__delete__(mEngine);
}
}
nsresult
@ -46,6 +57,12 @@ mozSpellChecker::Init()
mPersonalDictionary = do_GetService("@mozilla.org/spellchecker/personaldictionary;1");
mSpellCheckingEngine = nullptr;
if (XRE_GetProcessType() == GeckoProcessType_Content) {
mozilla::dom::ContentChild* contentChild = mozilla::dom::ContentChild::GetSingleton();
MOZ_ASSERT(contentChild);
mEngine = new RemoteSpellcheckEngineChild(this);
contentChild->SendPRemoteSpellcheckEngineConstructor(mEngine);
}
return NS_OK;
}
@ -108,9 +125,16 @@ mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled, nsTArray
{
nsresult result;
bool correct;
if(!mSpellCheckingEngine)
return NS_ERROR_NULL_POINTER;
if (XRE_GetProcessType() == GeckoProcessType_Content) {
nsString wordwrapped = nsString(aWord);
bool rv = mEngine->SendCheckForMisspelling(wordwrapped, aIsMisspelled);
return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
if(!mSpellCheckingEngine) {
return NS_ERROR_NULL_POINTER;
}
*aIsMisspelled = false;
result = mSpellCheckingEngine->Check(PromiseFlatString(aWord).get(), &correct);
NS_ENSURE_SUCCESS(result, result);
@ -332,6 +356,13 @@ mozSpellChecker::GetCurrentDictionary(nsAString &aDictionary)
NS_IMETHODIMP
mozSpellChecker::SetCurrentDictionary(const nsAString &aDictionary)
{
if (XRE_GetProcessType() == GeckoProcessType_Content) {
nsString wrappedDict = nsString(aDictionary);
bool isSuccess;
mEngine->SendSetDictionary(wrappedDict, &isSuccess);
return isSuccess ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
// Calls to mozISpellCheckingEngine::SetDictionary might destroy us
nsRefPtr<mozSpellChecker> kungFuDeathGrip = this;
@ -528,3 +559,7 @@ mozSpellChecker::GetEngineList(nsCOMArray<mozISpellCheckingEngine>* aSpellChecki
return NS_OK;
}
void mozSpellChecker::DeleteRemoteEngine() {
mEngine = nullptr;
}

View File

@ -17,6 +17,12 @@
#include "nsTArray.h"
#include "mozISpellI18NUtil.h"
#include "nsCycleCollectionParticipant.h"
#include "RemoteSpellCheckEngineChild.h"
namespace mozilla {
class PRemoteSpellcheckEngineChild;
class RemoteSpellcheckEngineChild;
}
class mozSpellChecker : public nsISpellChecker
{
@ -43,6 +49,7 @@ public:
NS_IMETHOD GetCurrentDictionary(nsAString &aDictionary);
NS_IMETHOD SetCurrentDictionary(const nsAString &aDictionary);
NS_IMETHOD CheckCurrentDictionary();
void DeleteRemoteEngine();
protected:
virtual ~mozSpellChecker();
@ -59,5 +66,7 @@ protected:
nsresult GetCurrentBlockIndex(nsITextServicesDocument *aDoc, int32_t *outBlockIndex);
nsresult GetEngineList(nsCOMArray<mozISpellCheckingEngine> *aDictionaryList);
mozilla::PRemoteSpellcheckEngineChild *mEngine;
};
#endif // mozSpellChecker_h__

View File

@ -1310,6 +1310,12 @@ public class BrowserApp extends GeckoApp
GeckoPreferences.setResourceToOpen(settingsIntent, resource);
startActivityForResult(settingsIntent, ACTIVITY_REQUEST_PREFERENCES);
// Don't use a transition to settings if we're on a device where that
// would look bad.
if (HardwareUtils.IS_KINDLE_DEVICE) {
overridePendingTransition(0, 0);
}
} else if ("Telemetry:Gather".equals(event)) {
Telemetry.HistogramAdd("PLACES_PAGES_COUNT",
BrowserDB.getCount(getContentResolver(), "history"));
@ -1604,6 +1610,9 @@ public class BrowserApp extends GeckoApp
int flags = Tabs.LOADURL_NONE;
if (newTab) {
flags |= Tabs.LOADURL_NEW_TAB;
if (Tabs.getInstance().getSelectedTab().isPrivate()) {
flags |= Tabs.LOADURL_PRIVATE;
}
}
Tabs.getInstance().loadUrl(url, searchEngine, -1, flags);

View File

@ -34,8 +34,6 @@ import android.database.MatrixCursor.RowBuilder;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -252,9 +250,7 @@ public class RecentTabsPanel extends HomeFragment
mClosedTabs = closedTabs;
// Reload the cursor to show recently closed tabs.
if (mClosedTabs.length > 0 && canLoad()) {
getLoaderManager().restartLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
}
getLoaderManager().restartLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
}
});
}
@ -310,8 +306,8 @@ public class RecentTabsPanel extends HomeFragment
for (int i = 0; i < length; i++) {
final String url = closedTabs[i].url;
// Don't show recent tabs for about:home.
if (!AboutPages.isAboutHome(url)) {
// Don't show recent tabs for about:home or about:privatebrowsing.
if (!AboutPages.isTitlelessAboutPage(url)) {
// If this is the first closed tab we're adding, add a header for the section.
if (visibleClosedTabs == 0) {
addRow(c, null, context.getString(R.string.home_closed_tabs_title), RecentTabs.TYPE_HEADER);

View File

@ -33,6 +33,7 @@ import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
import org.mozilla.gecko.home.HomePanelPicker;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.FloatingHintEditText;
@ -86,6 +87,11 @@ OnSharedPreferenceChangeListener
{
private static final String LOGTAG = "GeckoPreferences";
// We have a white background, which makes transitions on
// some devices look bad. Don't use transitions on those
// devices.
private static final boolean NO_TRANSITIONS = HardwareUtils.IS_KINDLE_DEVICE;
private static final String NON_PREF_PREFIX = "android.not_a_preference.";
public static final String INTENT_EXTRA_RESOURCES = "resource";
public static String PREFS_HEALTHREPORT_UPLOAD_ENABLED = NON_PREF_PREFIX + "healthreport.uploadEnabled";
@ -133,6 +139,19 @@ OnSharedPreferenceChangeListener
private Locale lastLocale = Locale.getDefault();
private boolean localeSwitchingIsEnabled;
private void startActivityForResultChoosingTransition(final Intent intent, final int requestCode) {
startActivityForResult(intent, requestCode);
if (NO_TRANSITIONS) {
overridePendingTransition(0, 0);
}
}
private void finishChoosingTransition() {
finish();
if (NO_TRANSITIONS) {
overridePendingTransition(0, 0);
}
}
private void updateActionBarTitle(int title) {
if (Build.VERSION.SDK_INT >= 14) {
final String newTitle = getString(title);
@ -250,10 +269,10 @@ OnSharedPreferenceChangeListener
// We also don't need to update the title.
final Intent intent = (Intent) getIntent().clone();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN);
startActivityForResultChoosingTransition(intent, REQUEST_CODE_PREF_SCREEN);
setResult(RESULT_CODE_LOCALE_DID_CHANGE);
finish();
finishChoosingTransition();
}
private void checkLocale() {
@ -440,6 +459,15 @@ OnSharedPreferenceChangeListener
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (NO_TRANSITIONS) {
overridePendingTransition(0, 0);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
@ -494,7 +522,7 @@ OnSharedPreferenceChangeListener
// the settings screens.
// We need to start nested PreferenceScreens withStartActivityForResult().
// Android doesn't let us do that (see Preference.onClick), so we're overriding here.
startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN);
startActivityForResultChoosingTransition(intent, REQUEST_CODE_PREF_SCREEN);
}
@Override
@ -505,9 +533,12 @@ OnSharedPreferenceChangeListener
// Overriding because we want to use startActivityForResult for Fragment intents.
Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
if (resultTo == null) {
startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN);
startActivityForResultChoosingTransition(intent, REQUEST_CODE_PREF_SCREEN);
} else {
resultTo.startActivityForResult(intent, resultRequestCode);
if (NO_TRANSITIONS) {
overridePendingTransition(0, 0);
}
}
}
@ -525,7 +556,7 @@ OnSharedPreferenceChangeListener
// Pass this result up to the parent activity.
setResult(RESULT_CODE_EXIT_SETTINGS);
finish();
finishChoosingTransition();
break;
}
break;
@ -690,7 +721,7 @@ OnSharedPreferenceChangeListener
@Override
public boolean onPreferenceClick(Preference preference) {
Intent dialogIntent = new Intent(GeckoPreferences.this, HomePanelPicker.class);
startActivityForResult(dialogIntent, HomePanelPicker.REQUEST_CODE_ADD_PANEL);
startActivityForResultChoosingTransition(dialogIntent, HomePanelPicker.REQUEST_CODE_ADD_PANEL);
return true;
}
});
@ -740,7 +771,7 @@ OnSharedPreferenceChangeListener
int itemId = item.getItemId();
switch (itemId) {
case android.R.id.home:
finish();
finishChoosingTransition();
return true;
}
@ -1007,7 +1038,7 @@ OnSharedPreferenceChangeListener
((ListPreference) preference).setSummary(newEntry);
} else if (preference instanceof LinkPreference) {
setResult(RESULT_CODE_EXIT_SETTINGS);
finish();
finishChoosingTransition();
} else if (preference instanceof FontSizePreference) {
final FontSizePreference fontSizePref = (FontSizePreference) preference;
fontSizePref.setSummary(fontSizePref.getSavedFontSizeName());

View File

@ -8,6 +8,7 @@ package org.mozilla.gecko.preferences;
import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.Context;
import android.content.Intent;
@ -39,6 +40,11 @@ class SyncPreference extends Preference {
private void launchFxASetup() {
Intent intent = new Intent(mContext, FxAccountGetStartedActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (HardwareUtils.IS_KINDLE_DEVICE) {
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
}
mContext.startActivity(intent);
}

View File

@ -6,45 +6,50 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<org.mozilla.gecko.tabspanel.TabsTray android:id="@+id/private_tabs_tray"
style="@style/TabsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
gecko:tabs="tabs_private"/>
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:id="@+id/private_tabs_empty"
style="@style/TabsPanelFrame.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.mozilla.gecko.tabspanel.TabsTray android:id="@+id/private_tabs_tray"
style="@style/TabsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
gecko:tabs="tabs_private"/>
<LinearLayout style="@style/TabsPanelSection.PrivateTabs.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView style="@style/TabsPanelItem.TextAppearance.Header.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/private_browsing_title"/>
<TextView style="@style/TabsPanelItem.TextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/private_tabs_panel_empty_desc"/>
</LinearLayout>
<LinearLayout style="@style/TabsPanelSection.PrivateTabs"
<LinearLayout android:id="@+id/private_tabs_empty"
style="@style/TabsPanelFrame.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/private_tabs_learn_more"
style="@style/TabsPanelItem.TextAppearance.Linkified.LearnMore"
android:layout_width="match_parent"
android:text="@string/private_tabs_panel_learn_more"/>
<LinearLayout style="@style/TabsPanelSection.PrivateTabs.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView style="@style/TabsPanelItem.TextAppearance.Header.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/private_browsing_title"/>
<TextView style="@style/TabsPanelItem.TextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/private_tabs_panel_empty_desc"/>
</LinearLayout>
<LinearLayout style="@style/TabsPanelSection.PrivateTabs"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/private_tabs_learn_more"
style="@style/TabsPanelItem.TextAppearance.Linkified.LearnMore"
android:layout_width="match_parent"
android:text="@string/private_tabs_panel_learn_more"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</merge>

View File

@ -33,7 +33,7 @@
</style>
<style name="TabsPanelSection.PrivateTabs.Header">
<item name="android:paddingTop">18dp</item>
<item name="android:paddingTop">10dp</item>
</style>
<style name="TabsPanelItem">

View File

@ -88,7 +88,7 @@
<item name="android:paddingRight">6dp</item>
<item name="android:layout_marginLeft">12dp</item>
<item name="android:layout_marginRight">12dp</item>
<item name="android:textSize">16dp</item>
<item name="android:textSize">16sp</item>
</style>
<style name="TabsPanelItem.TextAppearance.Header.FXAccounts">

View File

@ -474,8 +474,8 @@
<style name="TabsPanelSectionBase">
<item name="android:orientation">vertical</item>
<item name="android:layout_marginLeft">16dp</item>
<item name="android:layout_marginRight">16dp</item>
<item name="android:layout_marginLeft">40dp</item>
<item name="android:layout_marginRight">40dp</item>
</style>
<style name="TabsPanelSection" parent="TabsPanelSectionBase">
@ -503,7 +503,7 @@
<style name="TabsPanelItem.ButtonBase">
<item name="android:background">@drawable/remote_tabs_setup_button_background</item>
<item name="android:textColor">#FFFEFF</item>
<item name="android:textSize">20sp</item>
<item name="android:textSize">16sp</item>
<item name="android:gravity">center</item>
</style>
@ -512,17 +512,17 @@
<item name="android:paddingBottom">18dp</item>
<item name="android:paddingLeft">9dp</item>
<item name="android:paddingRight">9dp</item>
<item name="android:layout_marginLeft">24dp</item>
<item name="android:layout_marginRight">24dp</item>
</style>
<style name="TabsPanelItem.TextAppearance">
<item name="android:textColor">#C0C9D0</item>
<item name="android:textSize">16sp</item>
<item name="android:textSize">14sp</item>
<item name="android:lineSpacingMultiplier">1.35</item>
</style>
<style name="TabsPanelItem.TextAppearance.Header">
<item name="android:textSize">20sp</item>
<item name="android:textSize">18sp</item>
<item name="android:layout_marginBottom">16dp</item>
</style>
<style name="TabsPanelItem.TextAppearance.Header.FXAccounts">

View File

@ -17,6 +17,7 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ScrollView;
/**
* A container that wraps the private tabs {@link android.widget.AdapterView} and empty
@ -24,7 +25,7 @@ import android.widget.FrameLayout;
* this container as calling {@link android.widget.AdapterView#setVisibility} does not affect the
* empty View's visibility.
*/
class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
class PrivateTabsPanel extends ScrollView implements CloseAllPanelView {
private TabsPanel tabsPanel;
private TabsTray tabsTray;

View File

@ -42,7 +42,7 @@ class RemoteTabsList extends ExpandableListView
private ArrayList <ArrayList <HashMap <String, String>>> tabsList;
// A list of the clients that are currently expanded.
private List<String> expandedClientList = new ArrayList<String>();
private List<String> expandedClientList;
// The client that previously had an item selected is used to restore the scroll position.
private String clientScrollPosition;
@ -143,17 +143,26 @@ class RemoteTabsList extends ExpandableListView
TAB_KEY,
TAB_RESOURCE));
// Expand the client groups, and restore the previous scroll position.
// Either set the initial UI state, or restore it.
List<String> newExpandedClientList = new ArrayList<String>();
for (int i = 0; i < clients.size(); i++) {
final String clientGuid = clients.get(i).get("guid");
if (expandedClientList.contains(clientGuid)) {
if (expandedClientList == null) {
// On initial entry we expand all clients by default.
newExpandedClientList.add(clientGuid);
expandGroup(i);
}
} else {
// On subsequent entries, we expand clients based on their previous UI state.
if (expandedClientList.contains(clientGuid)) {
newExpandedClientList.add(clientGuid);
expandGroup(i);
}
if (clientGuid.equals(clientScrollPosition)) {
setSelectedGroup(i);
// Additionally we reset the UI scroll position.
if (clientGuid.equals(clientScrollPosition)) {
setSelectedGroup(i);
}
}
}
expandedClientList = newExpandedClientList;

View File

@ -30,6 +30,10 @@ public final class HardwareUtils {
// Number of bytes of /proc/meminfo to read in one go.
private static final int MEMINFO_BUFFER_SIZE_BYTES = 256;
private static final boolean IS_AMAZON_DEVICE = Build.MANUFACTURER.equalsIgnoreCase("Amazon");
public static final boolean IS_KINDLE_DEVICE = IS_AMAZON_DEVICE &&
(Build.MODEL.equals("Kindle Fire") ||
Build.MODEL.startsWith("KF"));
private static volatile int sTotalRAM = -1;
private static volatile boolean sInited;

View File

@ -1020,9 +1020,6 @@ var BrowserApp = {
evt.initUIEvent("TabClose", true, false, window, tabIndex);
aTab.browser.dispatchEvent(evt);
aTab.destroy();
this._tabs.splice(tabIndex, 1);
if (aShowUndoToast) {
// Get a title for the undo close toast. Fall back to the URL if there is no title.
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
@ -1043,11 +1040,14 @@ var BrowserApp = {
label: Strings.browser.GetStringFromName("undoCloseToast.action2"),
callback: function() {
UITelemetry.addEvent("undo.1", "toast", null, "closetab");
ss.undoCloseTab(window, 0);
ss.undoCloseTab(window, closedTabData);
}
}
});
}
aTab.destroy();
this._tabs.splice(tabIndex, 1);
},
// Use this method to select a tab from JS. This method sends a message

View File

@ -14,7 +14,7 @@ interface nsIDOMNode;
* tabs contained in them.
*/
[scriptable, uuid(91eca9cf-6741-4c8f-a3a0-2e957240894d)]
[scriptable, uuid(5497d9a1-c378-47a9-9488-4c47a644131a)]
interface nsISessionStore : nsISupports
{
/**
@ -38,10 +38,10 @@ interface nsISessionStore : nsISupports
/**
* @param aWindow is the browser window to reopen a closed tab in.
* @param aIndex is the index of the tab to be restored (FIFO ordered).
* @param aCloseTabData is the data of the tab to be restored.
* @returns a reference to the reopened tab.
*/
nsIDOMNode undoCloseTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
nsIDOMNode undoCloseTab(in nsIDOMWindow aWindow, in jsval aCloseTabData);
/**
* @param aWindow is the browser window associated with the closed tab.

View File

@ -18,6 +18,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava", "resource://gre/modules/Messaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
function dump(a) {
Services.console.logStringMessage(a);
@ -84,6 +85,7 @@ SessionStore.prototype = {
observerService.addObserver(this, "application-background", true);
observerService.addObserver(this, "ClosedTabs:StartNotifications", true);
observerService.addObserver(this, "ClosedTabs:StopNotifications", true);
observerService.addObserver(this, "last-pb-context-exited", true);
break;
case "final-ui-startup":
observerService.removeObserver(this, "final-ui-startup");
@ -169,6 +171,13 @@ SessionStore.prototype = {
case "ClosedTabs:StopNotifications":
this._notifyClosedTabs = false;
break;
case "last-pb-context-exited":
// Clear private closed tab data when we leave private browsing.
for (let [, window] in Iterator(this._windows)) {
window.closedTabs = window.closedTabs.filter(tab => !tab.isPrivate);
}
this._lastClosedTabIndex = -1;
break;
}
},
@ -365,6 +374,13 @@ SessionStore.prototype = {
this.saveStateDelayed();
this._updateCrashReportURL(aWindow);
// If the selected tab has changed while listening for closed tab
// notifications, we may have switched between different private browsing
// modes.
if (this._notifyClosedTabs) {
this._sendClosedTabsToJava(aWindow);
}
},
saveStateDelayed: function ss_saveStateDelayed() {
@ -849,7 +865,7 @@ SessionStore.prototype = {
return this._windows[aWindow.__SSID].closedTabs;
},
undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
undoCloseTab: function ss_undoCloseTab(aWindow, aCloseTabData) {
if (!aWindow.__SSID)
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
@ -857,28 +873,28 @@ SessionStore.prototype = {
if (!closedTabs)
return null;
// default to the most-recently closed tab
aIndex = aIndex || 0;
if (!(aIndex in closedTabs))
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
// fetch the data of closed tab, while removing it from the array
let closedTab = closedTabs.splice(aIndex, 1).shift();
// If the tab data is in the closedTabs array, remove it.
closedTabs.find(function (tabData, i) {
if (tabData == aCloseTabData) {
closedTabs.splice(i, 1);
return true;
}
});
// create a new tab and bring to front
let params = {
selected: true,
isPrivate: closedTab.isPrivate,
desktopMode: closedTab.desktopMode,
isPrivate: aCloseTabData.isPrivate,
desktopMode: aCloseTabData.desktopMode,
tabIndex: this._lastClosedTabIndex
};
let tab = aWindow.BrowserApp.addTab(closedTab.entries[closedTab.index - 1].url, params);
this._restoreHistory(closedTab, tab.browser.sessionHistory);
let tab = aWindow.BrowserApp.addTab(aCloseTabData.entries[aCloseTabData.index - 1].url, params);
this._restoreHistory(aCloseTabData, tab.browser.sessionHistory);
this._lastClosedTabIndex = -1;
// Put back the extra data
tab.browser.__SS_extdata = closedTab.extData;
tab.browser.__SS_extdata = aCloseTabData.extData;
if (this._notifyClosedTabs) {
this._sendClosedTabsToJava(aWindow);
@ -915,9 +931,10 @@ SessionStore.prototype = {
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
let closedTabs = this._windows[aWindow.__SSID].closedTabs;
let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aWindow.BrowserApp.selectedBrowser.contentWindow);
let tabs = closedTabs
.filter(tab => !tab.isPrivate)
.filter(tab => tab.isPrivate == isPrivate)
.map(function (tab) {
// Get the url and title for the last entry in the session history.
let lastEntry = tab.entries[tab.entries.length - 1];

View File

@ -541,7 +541,7 @@
<xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
<xul:menuseparator anonid="spell-check-separator"/>
<xul:menuitem label="&spellCheckToggle.label;" type="checkbox" accesskey="&spellCheckToggle.accesskey;" anonid="spell-check-enabled"
oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled();"/>
oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled(window);"/>
<xul:menu label="&spellDictionaries.label;" accesskey="&spellDictionaries.accesskey;" anonid="spell-dictionaries">
<xul:menupopup anonid="spell-dictionaries-menu"
onpopupshowing="event.stopPropagation();"

View File

@ -191,7 +191,7 @@ InlineSpellChecker.prototype = {
if (curlang == sortedList[i].id) {
item.setAttribute("checked", "true");
} else {
var callback = function(me, val) { return function(evt) { me.selectDictionary(val); } };
var callback = function(me, val) { return function(evt) { me.selectDictionary(val, me.menu.ownerDocument.defaultView); } };
item.addEventListener("command", callback(this, i), true);
}
if (insertBefore)
@ -272,8 +272,20 @@ InlineSpellChecker.prototype = {
},
// callback for selecting a dictionary
selectDictionary: function(index)
selectDictionary: function(index, aWindow)
{
// Avoid a crash in multiprocess until Bug 1030451 lands
// Remove aWindow parameter at that time
const Ci = Components.interfaces;
let chromeFlags = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).treeOwner.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIXULWindow).chromeFlags;
let chromeRemoteWindow = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW;
if (chromeFlags & chromeRemoteWindow) {
return;
}
if (! this.mInlineSpellChecker || index < 0 || index >= this.mDictionaryNames.length)
return;
var spellchecker = this.mInlineSpellChecker.spellChecker;
@ -293,8 +305,20 @@ InlineSpellChecker.prototype = {
},
// callback for enabling or disabling spellchecking
toggleEnabled: function()
toggleEnabled: function(aWindow)
{
// Avoid a crash in multiprocess until Bug 1030451 lands
// Remove aWindow parameter at that time
const Ci = Components.interfaces;
let chromeFlags = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).treeOwner.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIXULWindow).chromeFlags;
let chromeRemoteWindow = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW;
if (chromeFlags & chromeRemoteWindow) {
return;
}
this.mEditor.setSpellcheckUserOverride(!this.mInlineSpellChecker.enableRealTimeSpell);
},