Merge m-c to inbound

This commit is contained in:
Wes Kocher 2014-06-10 18:55:47 -07:00
commit 934871f3a2
113 changed files with 1221 additions and 2029 deletions

View File

@ -7,7 +7,6 @@ support-files =
systemapp_helper.js
[test_sandbox_permission.html]
skip-if = true # bug 984274 - frequent timeouts
[test_filepicker_path.html]
[test_permission_deny.html]
[test_permission_gum_remember.html]

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>

View File

@ -28,7 +28,8 @@
"VARIANT": "user",
"MOZILLA_OFFICIAL": "1",
"MOZ_TELEMETRY_REPORTING": "1",
"B2G_UPDATE_CHANNEL": "nightly"
"B2G_UPDATE_CHANNEL": "nightly",
"GAIA_KEYBOARD_LAYOUTS": "en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak"
},
"b2g_manifest": "flame.xml",
"b2g_manifest_intree": true,

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
@ -134,7 +134,7 @@
<project name="platform/hardware/libhardware" path="hardware/libhardware" revision="484802559ed106bac4811bd01c024ca64f741e60"/>
<project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="83f363a26069e9c188d2aaef5b9ef63e84ad1511"/>
<project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="5e110615212302c5d798a3c223dcee458817651c"/>
<project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="e38444b6ce12c7c25883272a439800376d5308eb"/>
<project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="280d29203b2aa30d713c5a6cc63d626e5a7df822"/>
<project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="5dc48bd46f9589653f8bf297be5d73676f2e2867"/>
<project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="8a0d0b0d9889ef99c4c6317c810db4c09295f15a"/>
<project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "37cd9a62b2cb570985d99a5519b136672614b980",
"revision": "16222b6246385cf793ae5fdced0ea6d548b7b949",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0c637f14265291ed81934058ec1cc019612127c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0750f66a0004870773c9a743fa6bdbe124379336"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -1929,13 +1929,21 @@ let CustomizableUIInternal = {
this.notifyListeners("onWidgetCreated", widget.id);
if (widget.defaultArea) {
let addToDefaultPlacements = false;
let area = gAreas.get(widget.defaultArea);
//XXXgijs this won't have any effect for legacy items. Sort of OK because
// consumers can modify currentset? Maybe?
if (area.has("defaultPlacements")) {
area.get("defaultPlacements").push(widget.id);
} else {
area.set("defaultPlacements", [widget.id]);
if (widget.source == CustomizableUI.SOURCE_BUILTIN) {
addToDefaultPlacements = true;
} else if (!CustomizableUI.isBuiltinToolbar(widget.defaultArea) &&
widget.defaultArea != CustomizableUI.AREA_PANEL) {
addToDefaultPlacements = true;
}
if (addToDefaultPlacements) {
if (area.has("defaultPlacements")) {
area.get("defaultPlacements").push(widget.id);
} else {
area.set("defaultPlacements", [widget.id]);
}
}
}

View File

@ -665,7 +665,7 @@ CustomizeMode.prototype = {
if (customizationTarget && customizationTarget != areaNode) {
areas.push(customizationTarget.id);
}
let overflowTarget = areaNode.getAttribute("overflowtarget");
let overflowTarget = areaNode && areaNode.getAttribute("overflowtarget");
if (overflowTarget) {
areas.push(overflowTarget);
}

View File

@ -94,6 +94,7 @@ skip-if = os == "linux"
[browser_978084_dragEnd_after_move.js]
[browser_980155_add_overflow_toolbar.js]
[browser_981418-widget-onbeforecreated-handler.js]
[browser_982656_restore_defaults_builtin_widgets.js]
[browser_984455_bookmarks_items_reparenting.js]
skip-if = os == "linux"

View File

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Restoring default should not place addon widgets back in the toolbar
add_task(function() {
ok(CustomizableUI.inDefaultState, "Default state to begin");
const kWidgetId = "bug982656-add-on-widget-should-not-restore-to-default-area";
let widgetSpec = {
id: kWidgetId,
defaultArea: CustomizableUI.AREA_NAVBAR
};
CustomizableUI.createWidget(widgetSpec);
ok(!CustomizableUI.inDefaultState, "Not in default state after widget added");
is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
yield resetCustomization();
ok(CustomizableUI.inDefaultState, "Back in default state after reset");
is(CustomizableUI.getPlacementOfWidget(kWidgetId), null, "Widget now in palette");
CustomizableUI.destroyWidget(kWidgetId);
});
// resetCustomization shouldn't move 3rd party widgets out of custom toolbars
add_task(function() {
const kToolbarId = "bug982656-toolbar-with-defaultset";
const kWidgetId = "bug982656-add-on-widget-should-restore-to-default-area-when-area-is-not-builtin";
ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
let toolbar = createToolbarWithPlacements(kToolbarId);
ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
"Toolbar has been registered.");
is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
"Area should be registered as toolbar");
let widgetSpec = {
id: kWidgetId,
defaultArea: kToolbarId
};
CustomizableUI.createWidget(widgetSpec);
ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible.");
is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, kToolbarId, "Widget should be in custom toolbar");
yield resetCustomization();
ok(CustomizableUI.inDefaultState, "Back in default state after reset");
is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, kToolbarId, "Widget still in custom toolbar");
ok(toolbar.collapsed, "Custom toolbar should be collapsed after reset");
toolbar.remove();
CustomizableUI.destroyWidget(kWidgetId);
CustomizableUI.unregisterArea(kToolbarId);
});

View File

@ -41,25 +41,35 @@ let startTests = Task.async(function*() {
function* performTests(inspector, ruleview) {
yield togglePseudoClass(inspector);
yield testAdded(inspector, ruleview);
yield assertPseudoAddedToNode(inspector, ruleview);
yield togglePseudoClass(inspector);
yield testRemoved();
yield testRemovedFromUI(inspector, ruleview);
yield assertPseudoRemovedFromNode();
yield assertPseudoRemovedFromView(inspector, ruleview);
yield togglePseudoClass(inspector);
yield testNavigate(inspector, ruleview);
}
function* togglePseudoClass(inspector) {
info("Toggle the pseudoclass, wait for the pseudoclass event and wait for the refresh of the rule view");
info("Toggle the pseudoclass, wait for it to be applied");
// Give the inspector panels a chance to update when the pseudoclass changes
let onPseudo = inspector.selection.once("pseudoclass");
let onRefresh = inspector.once("rule-view-refreshed");
inspector.togglePseudoClass(PSEUDO);
let onMutations = waitForMutation(inspector);
yield inspector.togglePseudoClass(PSEUDO);
yield onPseudo;
yield onRefresh;
yield onMutations;
}
function waitForMutation(inspector) {
let def = promise.defer();
inspector.walker.once("mutations", def.resolve);
return def.promise;
}
function* testNavigate(inspector, ruleview) {
@ -87,7 +97,7 @@ function showPickerOn(node, inspector) {
return highlighter.showBoxModel(getNodeFront(node));
}
function* testAdded(inspector, ruleview) {
function* assertPseudoAddedToNode(inspector, ruleview) {
info("Make sure the pseudoclass lock is applied to #div-1 and its ancestors");
let node = getNode("#div-1");
do {
@ -110,7 +120,7 @@ function* testAdded(inspector, ruleview) {
yield inspector.toolbox.highlighter.hideBoxModel();
}
function* testRemoved() {
function* assertPseudoRemovedFromNode() {
info("Make sure the pseudoclass lock is removed from #div-1 and its ancestors");
let node = getNode("#div-1");
do {
@ -120,7 +130,7 @@ function* testRemoved() {
} while (node.parentNode)
}
function* testRemovedFromUI(inspector, ruleview) {
function* assertPseudoRemovedFromView(inspector, ruleview) {
info("Check that the ruleview no longer contains the pseudo-class rule");
let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator");
is(rules.length, 2, "rule view is showing 2 rules after removing lock");
@ -137,6 +147,6 @@ function* finishUp(toolbox) {
toolbox.destroy();
yield onDestroy;
yield testRemoved(getNode("#div-1"));
yield assertPseudoRemovedFromNode(getNode("#div-1"));
gBrowser.removeCurrentTab();
}

View File

@ -20,7 +20,12 @@ function* performTest() {
doc.body.setAttribute("style", "position: fixed; width: 100%; height: 100%; margin: 0;");
let graph = new LineGraphWidget(doc.body, "fps");
yield graph.once("ready");
let readyEventEmitted;
graph.once("ready", () => readyEventEmitted = true);
yield graph.ready();
ok(readyEventEmitted, "The 'ready' event should have been emitted");
testGraph(host, graph);

View File

@ -5,8 +5,9 @@
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/event-emitter.js");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
this.EXPORTED_SYMBOLS = ["LineGraphWidget"];
@ -116,6 +117,7 @@ GraphSelectionResizer.prototype = {
this.AbstractCanvasGraph = function(parent, name, sharpness) {
EventEmitter.decorate(this);
this._ready = promise.defer();
this._parent = parent;
this._uid = "canvas-graph-" + Date.now();
@ -165,6 +167,7 @@ this.AbstractCanvasGraph = function(parent, name, sharpness) {
this._animationId = this._window.requestAnimationFrame(this._onAnimationFrame);
this._ready.resolve(this);
this.emit("ready", this);
});
}
@ -181,6 +184,13 @@ AbstractCanvasGraph.prototype = {
return this._height;
},
/**
* Returns a promise resolved once this graph is ready to receive data.
*/
ready: function() {
return this._ready.promise;
},
/**
* Destroys this graph.
*/
@ -501,15 +511,16 @@ AbstractCanvasGraph.prototype = {
let ctx = this._ctx;
ctx.clearRect(0, 0, this._width, this._height);
// Draw the graph underneath the cursor and selection.
if (this.hasData()) {
ctx.drawImage(this._cachedGraphImage, 0, 0, this._width, this._height);
}
if (this.hasCursor()) {
this._drawCliphead();
}
if (this.hasSelection() || this.hasSelectionInProgress()) {
this._drawSelection();
}
if (this.hasData()) {
ctx.drawImage(this._cachedGraphImage, 0, 0, this._width, this._height);
}
this._shouldRedraw = false;
},
@ -957,24 +968,17 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
let width = canvas.width = this._width;
let height = canvas.height = this._height;
let totalTicks = this._data.length;
let firstTick = this._data[0].delta;
let lastTick = this._data[totalTicks - 1].delta;
let maxValue = Number.MIN_SAFE_INTEGER;
let minValue = Number.MAX_SAFE_INTEGER;
let sumValues = 0;
let totalTicks = 0;
let firstTick;
let lastTick;
for (let { delta, value } of this._data) {
maxValue = Math.max(value, maxValue);
minValue = Math.min(value, minValue);
sumValues += value;
totalTicks++;
if (!firstTick) {
firstTick = delta;
} else {
lastTick = delta;
}
}
let dataScaleX = this.dataScaleX = width / lastTick;
@ -997,7 +1001,6 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
ctx.fillStyle = gradient;
ctx.strokeStyle = LINE_GRAPH_STROKE_COLOR;
ctx.lineWidth = LINE_GRAPH_STROKE_WIDTH;
ctx.setLineDash([]);
ctx.beginPath();
let prevX = 0;

View File

@ -377,7 +377,8 @@ exports.AppManager = AppManager = {
project.manifest);
}
function waitUntilProjectRuns() {
let manifest = self.getProjectManifestURL(project);
if (!self._runningApps.has(manifest)) {
let deferred = promise.defer();
self.on("app-manager-update", function onUpdate(event, what) {
if (what == "project-is-running") {
@ -385,13 +386,8 @@ exports.AppManager = AppManager = {
deferred.resolve();
}
});
return deferred.promise;
}
let manifest = self.getProjectManifestURL(project);
if (!self._runningApps.has(manifest)) {
yield AppActorFront.launchApp(client, actor, manifest);
yield waitUntilProjectRuns();
yield deferred.promise;
} else {
yield AppActorFront.reloadApp(client, actor, manifest);

View File

@ -1829,14 +1829,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
margin: 0 0 @tabToolbarNavbarOverlap@;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up {
-moz-border-start: 0;
-moz-border-end: 2px solid transparent;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down {
-moz-border-start: 2px solid transparent;
-moz-border-end: 0;
transition: 1s box-shadow ease-out;
border-radius: 4px;
}
@ -1846,20 +1839,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
transition: none;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):-moz-locale-dir(ltr),
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):-moz-locale-dir(rtl) {
border-width: 0 2px 0 0;
border-style: solid;
border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):-moz-locale-dir(ltr),
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):-moz-locale-dir(rtl) {
border-width: 0 0 0 2px;
border-style: solid;
border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
}
#TabsToolbar .toolbarbutton-1 {
margin-bottom: @tabToolbarNavbarOverlap@;
}

View File

@ -165,7 +165,7 @@ browser.jar:
skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)
skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
skin/classic/browser/tabbrowser/tab-background-start.png (tabbrowser/tab-background-start.png)
skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
# NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
# Makefile.in with a non-default marker of "%" and the result of that gets packaged.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

View File

@ -2971,20 +2971,6 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):-moz-locale-dir(ltr),
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):-moz-locale-dir(rtl) {
border-width: 0 2px 0 0;
border-style: solid;
border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
}
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):-moz-locale-dir(ltr),
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):-moz-locale-dir(rtl) {
border-width: 0 0 0 2px;
border-style: solid;
border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
}
/**
* Tabstrip & add-on bar toolbar buttons
*/

View File

@ -277,6 +277,7 @@ browser.jar:
skin/classic/browser/tabbrowser/tab-background-middle@2x.png (tabbrowser/tab-background-middle@2x.png)
skin/classic/browser/tabbrowser/tab-background-start.png (tabbrowser/tab-background-start.png)
skin/classic/browser/tabbrowser/tab-background-start@2x.png (tabbrowser/tab-background-start@2x.png)
skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
# NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
# Makefile.in with a non-default marker of "%" and the result of that gets packaged.
@ -287,7 +288,6 @@ browser.jar:
skin/classic/browser/tabbrowser/tab-stroke-end@2x.png (tabbrowser/tab-stroke-end@2x.png)
skin/classic/browser/tabbrowser/tab-stroke-start.png (tabbrowser/tab-stroke-start.png)
skin/classic/browser/tabbrowser/tab-stroke-start@2x.png (tabbrowser/tab-stroke-start@2x.png)
skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
skin/classic/browser/tabbrowser/tabDragIndicator@2x.png (tabbrowser/tabDragIndicator@2x.png)
skin/classic/browser/tabbrowser/tab-separator.png (tabbrowser/tab-separator.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

View File

@ -130,6 +130,43 @@
-moz-padding-start: @tabCurveHalfWidth@;
}
/* Tab Overflow */
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]),
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
background-image: url(chrome://browser/skin/tabbrowser/tab-overflow-indicator.png);
background-size: 100% 100%;
width: 14px;
margin-bottom: @tabToolbarNavbarOverlap@;
pointer-events: none;
position: relative;
z-index: 3; /* the selected tab's z-index + 1 */
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:-moz-locale-dir(rtl),
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:-moz-locale-dir(ltr) {
transform: scaleX(-1);
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]) {
-moz-margin-start: -2px;
-moz-margin-end: -12px;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
-moz-margin-start: -12px;
-moz-margin-end: -2px;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator[collapsed],
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator[collapsed] {
opacity: 0;
}
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator,
.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator {
transition: opacity 150ms ease;
}
.tab-background-start[selected=true]::after,
.tab-background-start[selected=true]::before,
.tab-background-start,

View File

@ -1855,9 +1855,6 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
.tabbrowser-arrowscrollbox > .scrollbutton-down {
list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
margin: 0 0 @tabToolbarNavbarOverlap@;
padding-right: 2px;
border-right: 2px solid transparent;
background-origin: border-box;
}
#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-up,
@ -1884,13 +1881,6 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
transition: none;
}
.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]),
.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]) {
border-width: 0 2px 0 0;
border-style: solid;
border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
}
.tabs-newtab-button > .toolbarbutton-icon {
margin-top: -1px;
margin-bottom: -1px;

View File

@ -196,7 +196,7 @@ browser.jar:
skin/classic/browser/tabbrowser/tab-background-middle@2x.png (tabbrowser/tab-background-middle@2x.png)
skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)
skin/classic/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
# NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
# Makefile.in with a non-default marker of "%" and the result of that gets packaged.
@ -601,7 +601,7 @@ browser.jar:
skin/classic/aero/browser/tabbrowser/tab-background-middle@2x.png (tabbrowser/tab-background-middle@2x.png)
skin/classic/aero/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)
skin/classic/aero/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
skin/classic/aero/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
skin/classic/aero/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
# NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
# Makefile.in with a non-default marker of "%" and the result of that gets packaged.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

View File

@ -3941,7 +3941,6 @@ MOZ_USE_NATIVE_POPUP_WINDOWS=
MOZ_ANDROID_HISTORY=
MOZ_WEBSMS_BACKEND=
MOZ_ANDROID_BEAM=
MOZ_ANDROID_SYNTHAPKS=
MOZ_LOCALE_SWITCHER=
ACCESSIBILITY=1
MOZ_TIME_MANAGER=
@ -4966,18 +4965,6 @@ if test -n "$MOZ_ANDROID_BEAM"; then
AC_DEFINE(MOZ_ANDROID_BEAM)
fi
dnl ========================================================
dnl = Synthesized Webapp APKs on Android
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(android-synthapks,
[ --enable-android-synthapks Enable synthesized APKs],
MOZ_ANDROID_SYNTHAPKS=1,
MOZ_ANDROID_SYNTHAPKS=)
if test -n "$MOZ_ANDROID_SYNTHAPKS"; then
AC_DEFINE(MOZ_ANDROID_SYNTHAPKS)
fi
dnl ========================================================
dnl = JS Debugger XPCOM component (js/jsd)
dnl ========================================================
@ -8574,7 +8561,6 @@ AC_SUBST(MOZ_METRO)
AC_SUBST(MOZ_ANDROID_HISTORY)
AC_SUBST(MOZ_WEBSMS_BACKEND)
AC_SUBST(MOZ_ANDROID_BEAM)
AC_SUBST(MOZ_ANDROID_SYNTHAPKS)
AC_SUBST(MOZ_LOCALE_SWITCHER)
AC_SUBST(MOZ_DISABLE_GECKOVIEW)
AC_SUBST(ENABLE_STRIP)

View File

@ -6,7 +6,7 @@
#include "nsISupports.idl"
#include "nsIDOMWindow.idl"
[scriptable,uuid(ff9c0e45-4646-45ec-b2f0-3b16d9e41875)]
[scriptable,uuid(b3a7a402-2760-4583-b4a3-af095fe00c84)]
interface nsITabSource : nsISupports
{
nsIDOMWindow getTabToStream();

View File

@ -71,7 +71,7 @@ function _setAppProperties(aObj, aApp) {
aObj.csp = aApp.csp;
aObj.installOrigin = aApp.installOrigin;
aObj.origin = aApp.origin;
#ifdef MOZ_ANDROID_SYNTHAPKS
#ifdef MOZ_WIDGET_ANDROID
aObj.apkPackageName = aApp.apkPackageName;
#endif
aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null;

View File

@ -1090,7 +1090,7 @@ this.DOMApplicationRegistry = {
switch (aMessage.name) {
case "Webapps:Install": {
#ifdef MOZ_ANDROID_SYNTHAPKS
#ifdef MOZ_WIDGET_ANDROID
Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg));
#else
this.doInstall(msg, mm);
@ -1119,7 +1119,7 @@ this.DOMApplicationRegistry = {
this.doGetAll(msg, mm);
break;
case "Webapps:InstallPackage": {
#ifdef MOZ_ANDROID_SYNTHAPKS
#ifdef MOZ_WIDGET_ANDROID
Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg));
#else
this.doInstallPackage(msg, mm);
@ -2510,15 +2510,15 @@ this.DOMApplicationRegistry = {
} else if (manifest.package_path) {
// If it is a local app then it must been installed from a local file
// instead of web.
#ifdef MOZ_ANDROID_SYNTHAPKS
// In that case, we would already have the manifest, not just the update
// manifest.
#ifdef MOZ_WIDGET_ANDROID
dontNeedNetwork = !!aData.app.manifest;
#else
if (aData.app.localInstallPath) {
dontNeedNetwork = true;
jsonManifest.package_path = "file://" + aData.app.localInstallPath;
}
}
#endif
// origin for install apps is meaningless here, since it's app:// and this

View File

@ -1,4 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et filetype=javascript
* 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/. */
@ -34,14 +36,7 @@ const BDADDR_ALL = "ff:ff:ff:ff:ff:ff";
const BDADDR_LOCAL = "ff:ff:ff:00:00:00";
// A user friendly name for remote BT device.
const REMOTE_DEVICE_NAME = "Remote_BT_Device";
// A system message signature of pairing request event
const BT_PAIRING_REQ = "bluetooth-pairing-request";
// Passkey and pincode used to reply pairing requst
const BT_PAIRING_PASSKEY = 123456;
const BT_PAIRING_PINCODE = "ABCDEFG";
const REMOTE_DEVICE_NAME = "Remote BT Device";
let Promise =
SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
@ -85,33 +80,6 @@ function runEmulatorCmdSafe(aCommand) {
return deferred.promise;
}
/**
* Wrap DOMRequest onsuccess/onerror events to Promise resolve/reject.
*
* Fulfill params: A DOMEvent.
* Reject params: A DOMEvent.
*
* @param aRequest
* A DOMRequest instance.
*
* @return A deferred promise.
*/
function wrapDomRequestAsPromise(aRequest) {
let deferred = Promise.defer();
ok(aRequest instanceof DOMRequest,
"aRequest is instanceof " + aRequest.constructor);
aRequest.onsuccess = function(aEvent) {
deferred.resolve(aEvent);
};
aRequest.onerror = function(aEvent) {
deferred.reject(aEvent);
};
return deferred.promise;
}
/**
* Add a Bluetooth remote device to scatternet and set its properties.
*
@ -220,11 +188,13 @@ function setEmulatorDeviceProperty(aAddress, aPropertyName, aValue) {
function getEmulatorDeviceProperty(aAddress, aPropertyName) {
let cmd = "bt property " + aAddress + " " + aPropertyName;
return runEmulatorCmdSafe(cmd)
.then(aResults => aResults[0]);
.then(function(aResults) {
return aResults[0];
});
}
/**
* Start discovering Bluetooth devices.
* Start dicovering Bluetooth devices.
*
* Allows the device's adapter to start seeking for remote devices.
*
@ -232,28 +202,32 @@ function getEmulatorDeviceProperty(aAddress, aPropertyName) {
* Reject params: a DOMError
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
* A BluetoothAdapter which is used to interact with local BT dev
*
* @return A deferred promise.
*/
function startDiscovery(aAdapter) {
let request = aAdapter.startDiscovery();
let deferred = Promise.defer();
return wrapDomRequestAsPromise(request)
.then(function resolve() {
// TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
// Currently, discovering state wouldn't change immediately here.
// We would turn on this check when the redesigned API are landed.
// is(aAdapter.discovering, false, "BluetoothAdapter.discovering");
log(" Start discovery - Success");
}, function reject(aEvent) {
ok(false, "Start discovery - Fail");
throw aEvent.target.error;
});
let request = aAdapter.startDiscovery();
request.onsuccess = function () {
log(" Start discovery - Success");
// TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
// Currently, discovering state wouldn't change immediately here.
// We would turn on this check when the redesigned API are landed.
// is(aAdapter.discovering, true, "BluetoothAdapter.discovering");
deferred.resolve();
}
request.onerror = function (aEvent) {
ok(false, "Start discovery - Fail");
deferred.reject(aEvent.target.error);
}
return deferred.promise;
}
/**
* Stop discovering Bluetooth devices.
* Stop dicovering Bluetooth devices.
*
* Allows the device's adapter to stop seeking for remote devices.
*
@ -266,184 +240,24 @@ function startDiscovery(aAdapter) {
* @return A deferred promise.
*/
function stopDiscovery(aAdapter) {
let request = aAdapter.stopDiscovery();
return wrapDomRequestAsPromise(request)
.then(function resolve() {
// TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
// Currently, discovering state wouldn't change immediately here.
// We would turn on this check when the redesigned API are landed.
// is(aAdapter.discovering, false, "BluetoothAdapter.discovering");
log(" Stop discovery - Success");
}, function reject(aEvent) {
ok(false, "Stop discovery - Fail");
throw aEvent.target.error;
});
}
/**
* Wait for 'devicefound' event of specified devices.
*
* Resolve if that every specified devices has been found. Never reject.
*
* Fulfill params: an array which contains addresses of remote devices.
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
* @param aRemoteAddresses
* An array which contains addresses of remote devices.
*
* @return A deferred promise.
*/
function waitForDevicesFound(aAdapter, aRemoteAddresses) {
let deferred = Promise.defer();
var addrArray = [];
aAdapter.addEventListener("devicefound", function onevent(aEvent) {
if(aRemoteAddresses.indexOf(aEvent.device.address) != -1) {
addrArray.push(aEvent.device.address);
}
if(addrArray.length == aRemoteAddresses.length) {
aAdapter.removeEventListener("devicefound", onevent);
ok(true, "BluetoothAdapter has found all remote devices.");
deferred.resolve(addrArray);
}
});
let request = aAdapter.stopDiscovery();
request.onsuccess = function () {
log(" Stop discovery - Success");
// TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
// Currently, discovering state wouldn't change immediately here.
// We would turn on this check when the redesigned API are landed.
// is(aAdapter.discovering, false, "BluetoothAdapter.discovering");
deferred.resolve();
}
request.onerror = function (aEvent) {
ok(false, "Stop discovery - Fail");
deferred.reject(aEvent.target.error);
}
return deferred.promise;
}
/**
* Start discovering Bluetooth devices and wait for 'devicefound' events.
*
* Allows the device's adapter to start seeking for remote devices and wait for
* the 'devicefound' events of specified devices.
*
* Fulfill params: an array of addresses of found devices.
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
* @param aRemoteAddresses
* An array which contains addresses of remote devices.
*
* @return A deferred promise.
*/
function startDiscoveryAndWaitDevicesFound(aAdapter, aRemoteAddresses) {
let promises = [];
promises.push(waitForDevicesFound(aAdapter, aRemoteAddresses));
promises.push(startDiscovery(aAdapter));
return Promise.all(promises)
.then(aResults => aResults[0]);
}
/**
* Start pairing a remote device.
*
* Start pairing a remote device with the device's adapter.
*
* Fulfill params: (none)
* Reject params: a DOMError
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
* @param aDeviceAddress
* The string of remote Bluetooth address with format xx:xx:xx:xx:xx:xx.
*
* @return A deferred promise.
*/
function pair(aAdapter, aDeviceAddress) {
let request = aAdapter.pair(aDeviceAddress);
return wrapDomRequestAsPromise(request)
.then(function resolve() {
log(" Pair - Success");
}, function reject(aEvent) {
ok(false, "Pair - Fail");
throw aEvent.target.error;
});
}
/**
* Remove the paired device from the paired device list.
*
* Remove the paired device from the paired device list of the device's adapter.
*
* Fulfill params: (none)
* Reject params: a DOMError
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
* @param aDeviceAddress
* The string of remote Bluetooth address with format xx:xx:xx:xx:xx:xx.
*
* @return A deferred promise.
*/
function unpair(aAdapter, aDeviceAddress) {
let request = aAdapter.unpair(aDeviceAddress);
return wrapDomRequestAsPromise(request)
.then(function resolve() {
log(" Unpair - Success");
}, function reject(aEvent) {
ok(false, "Unpair - Fail");
throw aEvent.target.error;
});
}
/**
* Start pairing a remote device and wait for "pairedstatuschanged" event.
*
* Start pairing a remote device with the device's adapter and wait for
* "pairedstatuschanged" event.
*
* Fulfill params: an array of promise results contains the fulfilled params of
* 'waitForAdapterEvent' and 'pair'.
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
* @param aDeviceAddress
* The string of remote Bluetooth address with format xx:xx:xx:xx:xx:xx.
*
* @return A deferred promise.
*/
function pairDeviceAndWait(aAdapter, aDeviceAddress) {
let promises = [];
promises.push(waitForAdapterEvent(aAdapter, "pairedstatuschanged"));
promises.push(pair(aAdapter, aDeviceAddress));
return Promise.all(promises);
}
/**
* Get paried Bluetooth devices.
*
* The getPairedDevices method is used to retrieve the full list of all devices
* paired with the device's adapter.
*
* Fulfill params: a shallow copy of the Array of paired BluetoothDevice
* objects.
* Reject params: a DOMError
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
*
* @return A deferred promise.
*/
function getPairedDevices(aAdapter) {
let request = aAdapter.getPairedDevices();
return wrapDomRequestAsPromise(request)
.then(function resolve() {
log(" getPairedDevices - Success");
let pairedDevices = request.result.slice();
return pairedDevices;
}, function reject(aEvent) {
ok(false, "getPairedDevices - Fail");
throw aEvent.target.error;
});
}
/**
* Get mozSettings value specified by @aKey.
*
@ -460,16 +274,19 @@ function getPairedDevices(aAdapter) {
* @return A deferred promise.
*/
function getSettings(aKey) {
let request = navigator.mozSettings.createLock().get(aKey);
let deferred = Promise.defer();
return wrapDomRequestAsPromise(request)
.then(function resolve(aEvent) {
ok(true, "getSettings(" + aKey + ")");
return aEvent.target.result[aKey];
}, function reject(aEvent) {
ok(false, "getSettings(" + aKey + ")");
throw aEvent.target.error;
});
let request = navigator.mozSettings.createLock().get(aKey);
request.addEventListener("success", function(aEvent) {
ok(true, "getSettings(" + aKey + ")");
deferred.resolve(aEvent.target.result[aKey]);
});
request.addEventListener("error", function() {
ok(false, "getSettings(" + aKey + ")");
deferred.reject();
});
return deferred.promise;
}
/**
@ -486,15 +303,19 @@ function getSettings(aKey) {
* @return A deferred promise.
*/
function setSettings(aSettings) {
let request = navigator.mozSettings.createLock().set(aSettings);
let deferred = Promise.defer();
return wrapDomRequestAsPromise(request)
.then(function resolve() {
ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
}, function reject(aEvent) {
ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
throw aEvent.target.error;
});
let request = navigator.mozSettings.createLock().set(aSettings);
request.addEventListener("success", function() {
ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.resolve();
});
request.addEventListener("error", function() {
ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
deferred.reject();
});
return deferred.promise;
}
/**
@ -617,7 +438,7 @@ function waitForManagerEvent(aEventName) {
* Fulfill params: the DOMEvent passed.
*
* @param aAdapter
* A BluetoothAdapter which is used to interact with local BT device.
* The BluetoothAdapter you want to use.
* @param aEventName
* The name of the EventHandler.
*
@ -642,8 +463,7 @@ function waitForAdapterEvent(aAdapter, aEventName) {
*
* Resolve if that named event occurs. Reject if we can't set settings.
*
* Fulfill params: an array of promise results contains the fulfill params of
* 'waitForManagerEvent' and 'setBluetoothEnabled'.
* Fulfill params: the DOMEvent passed.
* Reject params: (none)
*
* @return A deferred promise.

View File

@ -3,10 +3,8 @@ b2g = true
browser = false
qemu = true
[test_navigate_to_default_url.py]
[test_dom_BluetoothManager_enabled.js]
[test_dom_BluetoothManager_adapteradded.js]
[test_dom_BluetoothAdapter_setters.js]
[test_dom_BluetoothAdapter_getters.js]
[test_dom_BluetoothAdapter_discovery.js]
[test_dom_BluetoothAdapter_pair.js]

View File

@ -1,11 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et filetype=javascript
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
///////////////////////////////////////////////////////////////////////////////
// Test Purpose:
// To verify that discovery process of BluetoothAdapter is correct.
// Use B2G emulator commands to add/remove remote devices to simulate
// Use B2G emulator commands to add/remote remote devices to simulate
// discovering behavior.
//
// Test Coverage:
@ -22,9 +24,15 @@ MARIONETTE_HEAD_JS = 'head.js';
startBluetoothTest(true, function testCaseMain(aAdapter) {
log("Testing the discovery process of BluetoothAdapter ...");
// The properties of remote device.
let theProperties = {
"name": REMOTE_DEVICE_NAME,
"discoverable": true
};
return Promise.resolve()
.then(() => removeEmulatorRemoteDevice(BDADDR_ALL))
.then(() => addEmulatorRemoteDevice(null))
.then(() => addEmulatorRemoteDevice(/*theProperties*/ null))
.then(function(aRemoteAddress) {
let promises = [];
promises.push(waitForAdapterEvent(aAdapter, "devicefound"));

View File

@ -1,4 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et filetype=javascript
* 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/. */

View File

@ -1,66 +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/. */
///////////////////////////////////////////////////////////////////////////////
// Test Purpose:
// To verify that pairing process of BluetoothAdapter is correct.
// Use B2G emulator commands to add/remove remote devices to simulate
// discovering behavior. With current emulator implemation, the pair method
// between adapter and remote device would be 'confirmation'.
//
// Test Coverage:
// - BluetoothAdapter.startDiscovery()
// - BluetoothAdapter.stopDiscovery()
// - BluetoothAdapter.pair()
// - BluetoothAdapter.unpair()
// - BluetoothAdapter.onpairedstatuschanged()
// - BluetoothAdapter.setPairingConfirmation()
//
///////////////////////////////////////////////////////////////////////////////
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
function replyPairingReq(aAdapter, aPairingEvent) {
switch (aPairingEvent.method) {
case 'confirmation':
log("The pairing passkey is " + aPairingEvent.passkey);
aAdapter.setPairingConfirmation(aPairingEvent.address, true);
break;
case 'pincode':
let pincode = BT_PAIRING_PINCODE;
aAdapter.setPinCode(aPairingEvent.address, pincode);
break;
case 'passkey':
let passkey = BT_PAIRING_PASSKEY;
aAdapter.setPasskey(aPairingEvent.address, passkey);
break;
default:
ok(false, "Unsupported pairing method. [" + aPairingEvent.method + "]");
}
}
startBluetoothTest(true, function testCaseMain(aAdapter) {
log("Testing the pairing process of BluetoothAdapter ...");
// listens to the system message BT_PAIRING_REQ
navigator.mozSetMessageHandler(BT_PAIRING_REQ,
(evt) => replyPairingReq(aAdapter, evt));
return Promise.resolve()
.then(() => removeEmulatorRemoteDevice(BDADDR_ALL))
.then(() => addEmulatorRemoteDevice())
.then((aRemoteAddress) =>
startDiscoveryAndWaitDevicesFound(aAdapter, [aRemoteAddress]))
.then((aRemoteAddresses) =>
stopDiscovery(aAdapter).then(() => aRemoteAddresses))
// 'aRemoteAddresses' is an arrary which contains addresses of discovered
// remote devices.
// Pairs with the first device in 'aRemoteAddresses' to test the functionality
// of BluetoothAdapter.pair and BluetoothAdapter.onpairedstatuschanged.
.then((aRemoteAddresses) => pairDeviceAndWait(aAdapter, aRemoteAddresses.pop()))
.then(() => getPairedDevices(aAdapter))
.then((aPairedDevices) => unpair(aAdapter, aPairedDevices.pop().address))
.then(() => removeEmulatorRemoteDevice(BDADDR_ALL));
});

View File

@ -1,4 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et filetype=javascript
* 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/. */

View File

@ -1,4 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et filetype=javascript
* 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/. */

View File

@ -1,4 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: sw=2 ts=2 sts=2 et filetype=javascript
* 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/. */

View File

@ -1,12 +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/.
from marionette_test import MarionetteTestCase
class testNavigateToDefault(MarionetteTestCase):
def test_navigate_to_default_url(self):
try:
self.marionette.navigate("app://system.gaiamobile.org/index.html")
except:
self.assertTrue(Flase, "Can not navigate to system app.")

View File

@ -505,17 +505,10 @@ nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv)
}
void
nsDOMCameraControl::SetExposureCompensation(const Optional<double>& aCompensation, ErrorResult& aRv)
nsDOMCameraControl::SetExposureCompensation(double aCompensation, ErrorResult& aRv)
{
MOZ_ASSERT(mCameraControl);
if (!aCompensation.WasPassed()) {
// use NaN to switch the camera back into auto mode
aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN);
return;
}
aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation.Value());
aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation);
}
double

View File

@ -76,7 +76,7 @@ public:
double GetFocusDistanceNear(ErrorResult& aRv);
double GetFocusDistanceOptimum(ErrorResult& aRv);
double GetFocusDistanceFar(ErrorResult& aRv);
void SetExposureCompensation(const dom::Optional<double>& aCompensation, ErrorResult& aRv);
void SetExposureCompensation(double aCompensation, ErrorResult& aRv);
double GetExposureCompensation(ErrorResult& aRv);
int32_t SensorAngle();
already_AddRefed<dom::CameraCapabilities> Capabilities();

View File

@ -72,10 +72,11 @@ nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
, mDeferConfigUpdate(0)
, mMediaProfiles(nullptr)
, mRecorder(nullptr)
, mRecorderMonitor("GonkCameraControl::mRecorder.Monitor")
, mProfileManager(nullptr)
, mRecorderProfile(nullptr)
, mVideoFile(nullptr)
, mReentrantMonitor("GonkCameraControl::OnTakePictureMonitor")
, mReentrantMonitor("GonkCameraControl::OnTakePicture.Monitor")
{
// Constructor runs on the main thread...
DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
@ -952,6 +953,8 @@ nsGonkCameraControl::StartRecordingImpl(DeviceStorageFileDescriptor* aFileDescri
{
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
ReentrantMonitorAutoEnter mon(mRecorderMonitor);
NS_ENSURE_TRUE(mRecorderProfile, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_FALSE(mRecorder, NS_ERROR_FAILURE);
@ -1041,7 +1044,7 @@ nsGonkCameraControl::StopRecordingImpl()
nsRefPtr<DeviceStorageFile> mFile;
};
MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
ReentrantMonitorAutoEnter mon(mRecorderMonitor);
// nothing to do if we have no mRecorder
NS_ENSURE_TRUE(mRecorder, NS_OK);
@ -1570,6 +1573,8 @@ nsGonkCameraControl::SetupRecording(int aFd, int aRotation,
const size_t SIZE = 256;
char buffer[SIZE];
ReentrantMonitorAutoEnter mon(mRecorderMonitor);
mRecorder = new GonkRecorder();
CHECK_SETARG_RETURN(mRecorder->init(), NS_ERROR_FAILURE);

View File

@ -155,6 +155,10 @@ protected:
android::MediaProfiles* mMediaProfiles;
nsRefPtr<android::GonkRecorder> mRecorder;
// Touching mRecorder happens inside this monitor because the destructor
// can run on any thread, and we need to be able to clean up properly if
// GonkCameraControl goes away.
ReentrantMonitor mRecorderMonitor;
// Camcorder profile settings for the desired quality level
nsRefPtr<GonkRecorderProfileManager> mProfileManager;

View File

@ -228,43 +228,43 @@ var tests = [
"exposureCompensation = " + cam.exposureCompensation);
// Check normal values
cam.setExposureCompensation(0.0);
cam.exposureCompensation = 0.0;
ok(cam.exposureCompensation == 0.0,
"exposureCompensation = " + cam.exposureCompensation);
cam.setExposureCompensation(cap.minExposureCompensation);
cam.exposureCompensation = cap.minExposureCompensation;
ok(cam.exposureCompensation == cap.minExposureCompensation,
"exposureCompensation(min) = " + cam.exposureCompensation);
cam.setExposureCompensation(cap.maxExposureCompensation);
cam.exposureCompensation = cap.maxExposureCompensation;
ok(cam.exposureCompensation == cap.maxExposureCompensation,
"exposureCompensation(max) = " + cam.exposureCompensation);
// Rounding
cam.setExposureCompensation(1.24);
cam.exposureCompensation = 1.24;
ok(cam.exposureCompensation == 1.0,
"exposureCompensation(1.24) = " + cam.exposureCompensation);
cam.setExposureCompensation(1.25);
cam.exposureCompensation = 1.25;
ok(cam.exposureCompensation == 1.5,
"exposureCompensation(1.25) = " + cam.exposureCompensation);
cam.setExposureCompensation(-1.24);
cam.exposureCompensation = -1.24;
ok(cam.exposureCompensation == -1.0,
"exposureCompensation(-1.24) = " + cam.exposureCompensation);
cam.setExposureCompensation(-1.25);
cam.exposureCompensation = -1.25;
ok(cam.exposureCompensation == -1.5,
"exposureCompensation(-1.25) = " + cam.exposureCompensation);
// Check out-of-bounds values
cam.setExposureCompensation(cap.minExposureCompensation - 1.0);
cam.exposureCompensation = cap.minExposureCompensation - 1.0;
ok(cam.exposureCompensation == cap.minExposureCompensation,
"exposureCompensation(min - 1.0) = " + cam.exposureCompensation);
cam.setExposureCompensation(cap.maxExposureCompensation + 1.0);
cam.exposureCompensation = cap.maxExposureCompensation + 1.0;
ok(cam.exposureCompensation == cap.maxExposureCompensation,
"exposureCompensation(max + 1.0) = " + cam.exposureCompensation);
// Check extreme values
cam.setExposureCompensation(-1 * Math.pow(2, 32));
cam.exposureCompensation = -1 * Math.pow(2, 32);
ok(cam.exposureCompensation == cap.minExposureCompensation,
"exposureCompensation(-2^32) = " + cam.exposureCompensation);
cam.setExposureCompensation(Math.pow(2, 32));
cam.exposureCompensation = Math.pow(2, 32);
ok(cam.exposureCompensation == cap.maxExposureCompensation,
"exposureCompensation(2^32) = " + cam.exposureCompensation);

View File

@ -7,7 +7,8 @@ MARIONETTE_HEAD_JS = "head.js";
let url = "http://www.mozilla.org";
// TODO : Get this from emulator console command.
const T2T_RE_INDEX = 2;
const T1T_RE_INDEX = 2;
const T2T_RE_INDEX = 3;
function activateRE(re) {
let deferred = Promise.defer();
@ -35,7 +36,7 @@ function setTagData(re, flag, tnf, type, payload) {
return deferred.promise;
}
function testUrlTagDiscover() {
function testUrlTagDiscover(re) {
log("Running \'testUrlTagDiscover\'");
// TODO : Make flag value readable.
let flag = 0xd0;
@ -60,12 +61,21 @@ function testUrlTagDiscover() {
});
toggleNFC(true)
.then(() => setTagData(T2T_RE_INDEX, flag, tnf, btoa(type), btoa(payload)))
.then(() => activateRE(T2T_RE_INDEX));
.then(() => setTagData(re, flag, tnf, btoa(type), btoa(payload)))
.then(() => activateRE(re));
}
function testUrlT1TDiscover() {
testUrlTagDiscover(T1T_RE_INDEX);
}
function testUrlT2TDiscover() {
testUrlTagDiscover(T2T_RE_INDEX);
}
let tests = [
testUrlTagDiscover
testUrlT1TDiscover,
testUrlT2TDiscover
];
SpecialPowers.pushPermissions(

View File

@ -15,6 +15,7 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
@ -23,15 +24,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageListenerManager");
XPCOMUtils.defineLazyGetter(this, "gEncoder", function() {
return new TextEncoder();
});
XPCOMUtils.defineLazyGetter(this, "gDecoder", function() {
return new TextDecoder();
});
const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
const NOTIFICATION_STORE_PATH =
OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");
@ -58,7 +50,7 @@ let NotificationDB = {
this.loaded = false;
this.tasks = []; // read/write operation queue
this.runningTask = false;
this.runningTask = null;
Services.obs.addObserver(this, "xpcom-shutdown", false);
this.registerListeners();
@ -86,15 +78,13 @@ let NotificationDB = {
},
// Attempt to read notification file, if it's not there we will create it.
load: function(callback) {
var promise = OS.File.read(NOTIFICATION_STORE_PATH);
promise.then(
load: function() {
var promise = OS.File.read(NOTIFICATION_STORE_PATH, { encoding: "utf-8"});
return promise.then(
function onSuccess(data) {
try {
this.notifications = JSON.parse(gDecoder.decode(data));
} catch (e) {
if (DEBUG) { debug("Unable to parse file data " + e); }
}
// JSON parsing failure will get handled by a later catch in the promise
// chain
this.notifications = JSON.parse(data);
// populate the list of notifications by tag
if (this.notifications) {
for (var origin in this.notifications) {
@ -108,70 +98,43 @@ let NotificationDB = {
}
}
this.loaded = true;
callback && callback();
}.bind(this),
// If read failed, we assume we have no notifications to load.
function onFailure(reason) {
this.loaded = true;
this.createStore(callback);
return this.createStore();
}.bind(this)
);
},
// Creates the notification directory.
createStore: function(callback) {
createStore: function() {
var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
ignoreExisting: true
});
promise.then(
function onSuccess() {
this.createFile(callback);
}.bind(this),
function onFailure(reason) {
if (DEBUG) { debug("Directory creation failed:" + reason); }
callback && callback();
}
return promise.then(
this.createFile.bind(this)
);
},
// Creates the notification file once the directory is created.
createFile: function(callback) {
var promise = OS.File.open(NOTIFICATION_STORE_PATH, {create: true});
promise.then(
function onSuccess(handle) {
handle.close();
callback && callback();
},
function onFailure(reason) {
if (DEBUG) { debug("File creation failed:" + reason); }
callback && callback();
}
);
createFile: function() {
return OS.File.writeAtomic(NOTIFICATION_STORE_PATH, "");
},
// Save current notifications to the file.
save: function(callback) {
var data = gEncoder.encode(JSON.stringify(this.notifications));
var promise = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data);
promise.then(
function onSuccess() {
callback && callback();
},
function onFailure(reason) {
if (DEBUG) { debug("Save failed:" + reason); }
callback && callback();
}
);
save: function() {
var data = JSON.stringify(this.notifications);
return OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data, { encoding: "utf-8"});
},
// Helper function: callback will be called once file exists and/or is loaded.
ensureLoaded: function(callback) {
// Helper function: promise will be resolved once file exists and/or is loaded.
ensureLoaded: function() {
if (!this.loaded) {
this.load(callback);
return this.load();
} else {
callback();
return Promise.resolve();
}
},
@ -190,37 +153,57 @@ let NotificationDB = {
switch (message.name) {
case "Notification:GetAll":
this.queueTask("getall", message.data, function(notifications) {
this.queueTask("getall", message.data).then(function(notifications) {
returnMessage("Notification:GetAll:Return:OK", {
requestID: message.data.requestID,
origin: message.data.origin,
notifications: notifications
});
}).catch(function(error) {
returnMessage("Notification:GetAll:Return:KO", {
requestID: message.data.requestID,
origin: message.data.origin,
errorMsg: error
});
});
break;
case "Notification:GetAllCrossOrigin":
this.queueTask("getallaccrossorigin", message.data,
this.queueTask("getallaccrossorigin", message.data).then(
function(notifications) {
returnMessage("Notification:GetAllCrossOrigin:Return:OK", {
notifications: notifications
});
}).catch(function(error) {
returnMessage("Notification:GetAllCrossOrigin:Return:KO", {
errorMsg: error
});
});
break;
case "Notification:Save":
this.queueTask("save", message.data, function() {
this.queueTask("save", message.data).then(function() {
returnMessage("Notification:Save:Return:OK", {
requestID: message.data.requestID
});
}).catch(function(error) {
returnMessage("Notification:Save:Return:KO", {
requestID: message.data.requestID,
errorMsg: error
});
});
break;
case "Notification:Delete":
this.queueTask("delete", message.data, function() {
this.queueTask("delete", message.data).then(function() {
returnMessage("Notification:Delete:Return:OK", {
requestID: message.data.requestID
});
}).catch(function(error) {
returnMessage("Notification:Delete:Return:KO", {
requestID: message.data.requestID,
errorMsg: error
});
});
break;
@ -231,12 +214,20 @@ let NotificationDB = {
// We need to make sure any read/write operations are atomic,
// so use a queue to run each operation sequentially.
queueTask: function(operation, data, callback) {
queueTask: function(operation, data) {
if (DEBUG) { debug("Queueing task: " + operation); }
var defer = {};
this.tasks.push({
operation: operation,
data: data,
callback: callback
defer: defer
});
var promise = new Promise(function(resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
// Only run immediately if we aren't currently running another task.
@ -244,49 +235,60 @@ let NotificationDB = {
if (DEBUG) { debug("Task queue was not running, starting now..."); }
this.runNextTask();
}
return promise;
},
runNextTask: function() {
if (this.tasks.length === 0) {
if (DEBUG) { debug("No more tasks to run, queue depleted"); }
this.runningTask = false;
this.runningTask = null;
return;
}
this.runningTask = true;
this.runningTask = this.tasks.shift();
// Always make sure we are loaded before performing any read/write tasks.
this.ensureLoaded(function() {
var task = this.tasks.shift();
// Wrap the task callback to make sure we immediately
// run the next task after running the original callback.
var wrappedCallback = function() {
if (DEBUG) { debug("Finishing task: " + task.operation); }
task.callback.apply(this, arguments);
this.runNextTask();
}.bind(this);
this.ensureLoaded()
.then(function() {
var task = this.runningTask;
switch (task.operation) {
case "getall":
this.taskGetAll(task.data, wrappedCallback);
return this.taskGetAll(task.data);
break;
case "getallaccrossorigin":
this.taskGetAllCrossOrigin(wrappedCallback);
return this.taskGetAllCrossOrigin();
break;
case "save":
this.taskSave(task.data, wrappedCallback);
return this.taskSave(task.data);
break;
case "delete":
this.taskDelete(task.data, wrappedCallback);
return this.taskDelete(task.data);
break;
}
}.bind(this))
.then(function(payload) {
if (DEBUG) {
debug("Finishing task: " + this.runningTask.operation);
}
this.runningTask.defer.resolve(payload);
}.bind(this))
.catch(function(err) {
if (DEBUG) {
debug("Error while running " + this.runningTask.operation + ": " + err);
}
this.runningTask.defer.reject(new String(err));
}.bind(this))
.then(function() {
this.runNextTask();
}.bind(this));
},
taskGetAll: function(data, callback) {
taskGetAll: function(data) {
if (DEBUG) { debug("Task, getting all"); }
var origin = data.origin;
var notifications = [];
@ -294,10 +296,10 @@ let NotificationDB = {
for (var i in this.notifications[origin]) {
notifications.push(this.notifications[origin][i]);
}
callback(notifications);
return Promise.resolve(notifications);
},
taskGetAllCrossOrigin: function(callback) {
taskGetAllCrossOrigin: function() {
if (DEBUG) { debug("Task, getting all whatever origin"); }
var notifications = [];
for (var origin in this.notifications) {
@ -315,10 +317,10 @@ let NotificationDB = {
notifications.push(notification);
}
}
callback(notifications);
return Promise.resolve(notifications);
},
taskSave: function(data, callback) {
taskSave: function(data) {
if (DEBUG) { debug("Task, saving"); }
var origin = data.origin;
var notification = data.notification;
@ -336,32 +338,30 @@ let NotificationDB = {
}
this.notifications[origin][notification.id] = notification;
this.save(callback);
return this.save();
},
taskDelete: function(data, callback) {
taskDelete: function(data) {
if (DEBUG) { debug("Task, deleting"); }
var origin = data.origin;
var id = data.id;
if (!this.notifications[origin]) {
if (DEBUG) { debug("No notifications found for origin: " + origin); }
callback();
return;
return Promise.resolve();
}
// Make sure we can find the notification to delete.
var oldNotification = this.notifications[origin][id];
if (!oldNotification) {
if (DEBUG) { debug("No notification found with id: " + id); }
callback();
return;
return Promise.resolve();
}
if (oldNotification.tag) {
delete this.byTag[origin][oldNotification.tag];
}
delete this.notifications[origin][id];
this.save(callback);
return this.save();
}
};

View File

@ -16,10 +16,24 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const NOTIFICATIONSTORAGE_CID = "{37f819b0-0b5c-11e3-8ffd-0800200c9a66}";
const NOTIFICATIONSTORAGE_CONTRACTID = "@mozilla.org/notificationStorage;1";
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
const kMessageNotificationGetAllOk = "Notification:GetAll:Return:OK";
const kMessageNotificationGetAllKo = "Notification:GetAll:Return:KO";
const kMessageNotificationSaveKo = "Notification:Save:Return:KO";
const kMessageNotificationDeleteKo = "Notification:Delete:Return:KO";
const kMessages = [
kMessageNotificationGetAllOk,
kMessageNotificationGetAllKo,
kMessageNotificationSaveKo,
kMessageNotificationDeleteKo
];
function NotificationStorage() {
// cache objects
@ -30,12 +44,33 @@ function NotificationStorage() {
this._requests = {};
this._requestCount = 0;
Services.obs.addObserver(this, "xpcom-shutdown", false);
// Register for message listeners.
cpmm.addMessageListener("Notification:GetAll:Return:OK", this);
this.registerListeners();
}
NotificationStorage.prototype = {
registerListeners: function() {
for (let message of kMessages) {
cpmm.addMessageListener(message, this);
}
},
unregisterListeners: function() {
for (let message of kMessages) {
cpmm.removeMessageListener(message, this);
}
},
observe: function(aSubject, aTopic, aData) {
if (DEBUG) debug("Topic: " + aTopic);
if (aTopic == "xpcom-shutdown") {
Services.obs.removeObserver(this, "xpcom-shutdown");
this.unregisterListeners();
}
},
put: function(origin, id, title, dir, lang, body, tag, icon, alertName) {
if (DEBUG) { debug("PUT: " + id + ": " + title); }
var notification = {
@ -99,14 +134,27 @@ NotificationStorage.prototype = {
},
receiveMessage: function(message) {
var request = this._requests[message.data.requestID];
switch (message.name) {
case "Notification:GetAll:Return:OK":
var request = this._requests[message.data.requestID];
case kMessageNotificationGetAllOk:
delete this._requests[message.data.requestID];
this._populateCache(message.data.notifications);
this._fetchFromCache(request.origin, request.tag, request.callback);
break;
case kMessageNotificationGetAllKo:
delete this._requests[message.data.requestID];
try {
request.callback.done();
} catch (e) {
debug("Error calling callback done: " + e);
}
case kMessageNotificationSaveOk:
case kMessageNotificationDeleteOk:
debug("Error received when treating: '" + message.name + "': " + message.data.errorMsg);
break;
default:
if (DEBUG) debug("Unrecognized message: " + message.name);
break;

View File

@ -218,16 +218,13 @@ interface CameraControl : MediaStream
[Throws]
readonly attribute unrestricted double focusDistanceFar;
/* 'compensation' is optional, and if missing, will
set the camera to use automatic exposure compensation.
acceptable values must range from minExposureCompensation
to maxExposureCompensation in steps of stepExposureCompensation;
invalid values will be rounded to the nearest valid value. */
/* over- or under-expose the image; acceptable values must range from
minExposureCompensation to maxExposureCompensation in steps of
stepExposureCompensation. Invalid values will be rounded to the nearest
valid value; out-of-bounds values will be limited to the range
supported by the camera. */
[Throws]
void setExposureCompensation(optional double compensation);
[Throws]
readonly attribute unrestricted double exposureCompensation;
attribute double exposureCompensation;
/* one of the values chosen from capabilities.isoModes; default
value is "auto" if supported. */

View File

@ -1,15 +0,0 @@
[DEFAULT]
; true if the test requires an emulator, otherwise false
qemu = false
; true if the test is compatible with the browser, otherwise false
browser = true
; true if the test is compatible with b2g, otherwise false
b2g = true
; true if the test should be skipped
skip = false
[test_touchcaret.py]
b2g = false ; Bug 1020261

View File

@ -1,307 +0,0 @@
# -*- coding: 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/.
import string
from by import By
from marionette import Actions
from marionette_test import MarionetteTestCase
class TouchCaretTest(MarionetteTestCase):
_input_selector = (By.ID, 'input')
_textarea_selector = (By.ID, 'textarea')
_contenteditable_selector = (By.ID, 'contenteditable')
def setUp(self):
# Code to execute before a tests are run.
MarionetteTestCase.setUp(self)
self.actions = Actions(self.marionette)
def openTestHtml(self, enabled=True):
'''Open html for testing and locate elements, and enable/disable touch
caret.'''
self.marionette.execute_script(
'SpecialPowers.setBoolPref("touchcaret.enabled", %s);' %
('true' if enabled else 'false'))
test_html = self.marionette.absolute_url('test_touchcaret.html')
self.marionette.navigate(test_html)
self._input = self.marionette.find_element(*self._input_selector)
self._textarea = self.marionette.find_element(*self._textarea_selector)
self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
def is_input_or_textarea(self, element):
'''Return True if element is either <input> or <textarea>'''
return element.tag_name in ('input', 'textarea')
def get_js_selection_cmd(self, element):
'''Return a command snippet to get selection object.
If the element is <input> or <textarea>, return the selection object
associated with it. Otherwise, return the current selection object.
Note: "element" must be provided as the first argument to
execute_script().
'''
if self.is_input_or_textarea(element):
# We must unwrap sel so that DOMRect could be returned to Python
# side.
return '''var sel = SpecialPowers.wrap(arguments[0]).editor.selection;
sel = SpecialPowers.unwrap(sel);'''
else:
return '''var sel = window.getSelection();'''
def caret_rect(self, element):
'''Return the caret's DOMRect object.
If the element is either <input> or <textarea>, return the caret's
DOMRect within the element. Otherwise, return the DOMRect of the
current selected caret.
'''
cmd = self.get_js_selection_cmd(element) +\
'''return sel.getRangeAt(0).getClientRects()[0];'''
return self.marionette.execute_script(cmd, script_args=[element])
def caret_location(self, element):
'''Return caret's center location by the number of characters offset
within the given element.
Return (x, y) coordinates of the caret's center by the number of
characters offset relative to the top left-hand corner of the given
element.
'''
rect = self.caret_rect(element)
x = rect['left'] + rect['width'] / 2.0 - element.location['x']
y = rect['top'] + rect['height'] / 2.0 - element.location['y']
return x, y
def touch_caret_location(self, element):
'''Return touch caret's location (based on current caret location).
Return (x, y) coordinates of the touch caret's tip relative to the top
left-hand corner of the given element.
'''
rect = self.caret_rect(element)
x = rect['left'] - element.location['x']
# Touch caret's tip is below the bottom of the caret. Add 5px to y
# should be sufficient to locate it.
y = rect['bottom'] + 5 - element.location['y']
return x, y
def move_caret_by_offset(self, element, offset, backward=False):
'''Move caret in the element by offset.'''
cmd = self.get_js_selection_cmd(element) +\
'''sel.modify("move", arguments[1], "character");'''
direction = 'backward' if backward else 'forward'
for i in range(offset):
self.marionette.execute_script(
cmd, script_args=[element, direction])
def move_caret_to_front(self, element):
if self.is_input_or_textarea(element):
cmd = '''arguments[0].setSelectionRange(0, 0);'''
else:
cmd = '''var sel = window.getSelection();
sel.collapse(arguments[0].firstChild, 0);'''
self.marionette.execute_script(cmd, script_args=[element])
def move_caret_to_end(self, element):
if self.is_input_or_textarea(element):
cmd = '''var len = arguments[0].value.length;
arguments[0].setSelectionRange(len, len);'''
else:
cmd = '''var sel = window.getSelection();
sel.collapse(arguments[0].lastChild, arguments[0].lastChild.length);'''
self.marionette.execute_script(cmd, script_args=[element])
def get_content(self, element):
'''Return the content of the element.'''
if self.is_input_or_textarea(element):
return element.get_attribute('value')
else:
return element.text
def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
content_to_add = '!'
target_content = self.get_content(el)
target_content = target_content[:1] + content_to_add + target_content[1:]
# Get touch caret (x, y) at position 1 and 2.
self.move_caret_to_front(el)
caret0_x, caret0_y = self.caret_location(el)
touch_caret0_x, touch_caret0_y = self.touch_caret_location(el)
self.move_caret_by_offset(el, 1)
touch_caret1_x, touch_caret1_y = self.touch_caret_location(el)
# Tap the front of the input to make touch caret appear.
el.tap(caret0_x, caret0_y)
# Move touch caret
self.actions.flick(el, touch_caret0_x, touch_caret0_y,
touch_caret1_x, touch_caret1_y).perform()
el.send_keys(content_to_add)
assertFunc(target_content, self.get_content(el))
def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
content_to_add = '!'
target_content = self.get_content(el) + content_to_add
# Tap the front of the input to make touch caret appear.
self.move_caret_to_front(el)
el.tap(*self.caret_location(el))
# Move touch caret to the bottom-right corner of the element.
src_x, src_y = self.touch_caret_location(el)
dest_x, dest_y = el.size['width'], el.size['height']
self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
el.send_keys(content_to_add)
assertFunc(target_content, self.get_content(el))
def _test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self, el, assertFunc):
content_to_add = '!'
target_content = content_to_add + self.get_content(el)
# Tap to make touch caret appear. Note: it's strange that when the caret
# is at the end, the rect of the caret in <textarea> cannot be obtained.
# A bug perhaps.
self.move_caret_to_end(el)
self.move_caret_by_offset(el, 1, backward=True)
el.tap(*self.caret_location(el))
# Move touch caret to the top-left corner of the input box.
src_x, src_y = self.touch_caret_location(el)
dest_x, dest_y = 0, 0
self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
el.send_keys(content_to_add)
assertFunc(target_content, self.get_content(el))
def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
content_to_add = '!'
non_target_content = content_to_add + self.get_content(el)
# Get touch caret timeout in millisecond, and convert it to second.
timeout = self.marionette.execute_script(
'return SpecialPowers.getIntPref("touchcaret.expiration.time");')
timeout /= 1000.0
# Tap to make touch caret appear. Note: it's strange that when the caret
# is at the end, the rect of the caret in <textarea> cannot be obtained.
# A bug perhaps.
self.move_caret_to_end(el)
self.move_caret_by_offset(el, 1, backward=True)
el.tap(*self.caret_location(el))
# Wait until touch caret disappears, then pretend to move it to the
# top-left corner of the input box.
src_x, src_y = self.touch_caret_location(el)
dest_x, dest_y = 0, 0
self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()
el.send_keys(content_to_add)
assertFunc(non_target_content, self.get_content(el))
########################################################################
# <input> test cases with touch caret enabled
########################################################################
def test_input_move_caret_to_the_right_by_one_character(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)
def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)
def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertEqual)
def test_input_touch_caret_timeout(self):
self.openTestHtml(enabled=True)
self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._input, self.assertNotEqual)
########################################################################
# <input> test cases with touch caret disabled
########################################################################
def test_input_move_caret_to_the_right_by_one_character_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)
def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertNotEqual)
########################################################################
# <textarea> test cases with touch caret enabled
########################################################################
def test_textarea_move_caret_to_the_right_by_one_character(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)
def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)
def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertEqual)
def test_textarea_touch_caret_timeout(self):
self.openTestHtml(enabled=True)
self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._textarea, self.assertNotEqual)
########################################################################
# <textarea> test cases with touch caret disabled
########################################################################
def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)
def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertNotEqual)
########################################################################
# <div> contenteditable test cases with touch caret enabled
########################################################################
def test_contenteditable_move_caret_to_the_right_by_one_character(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)
def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._contenteditable, self.assertEqual)
def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
self.openTestHtml(enabled=True)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertEqual)
def test_contenteditable_touch_caret_timeout(self):
self.openTestHtml(enabled=True)
self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._contenteditable, self.assertNotEqual)
########################################################################
# <div> contenteditable test cases with touch caret disabled
########################################################################
def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)
def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
self.openTestHtml(enabled=False)
self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertNotEqual)

View File

@ -825,7 +825,6 @@ pref("browser.snippets.statsUrl", "https://snippets-stats.mozilla.org/mobile");
pref("browser.snippets.enabled", true);
pref("browser.snippets.syncPromo.enabled", true);
#ifdef MOZ_ANDROID_SYNTHAPKS
// The URL of the APK factory from which we obtain APKs for webapps.
pref("browser.webapps.apkFactoryUrl", "https://controller.apk.firefox.com/application.apk");
@ -850,8 +849,6 @@ pref("browser.webapps.checkForUpdates", 1);
// which is a test server that always reports all apps as having updates.
pref("browser.webapps.updateCheckUrl", "https://controller.apk.firefox.com/app_updates");
#endif
// The mode of home provider syncing.
// 0: Sync always
// 1: Sync only when on wifi

View File

@ -188,7 +188,6 @@
</intent-filter>
</activity>
#ifdef MOZ_ANDROID_SYNTHAPKS
<activity android:name="org.mozilla.gecko.webapp.Dispatcher"
android:noHistory="true" >
<intent-filter>
@ -212,7 +211,6 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
#endif
<activity android:name=".Webapp"
android:label="@string/webapp_generic_name"

View File

@ -149,13 +149,6 @@ public class AppConstants {
false;
#endif
public static final boolean MOZ_ANDROID_SYNTHAPKS =
#ifdef MOZ_ANDROID_SYNTHAPKS
true;
#else
false;
#endif
// See this wiki page for more details about channel specific build defines:
// https://wiki.mozilla.org/Platform/Channel-specific_build_defines
public static final boolean RELEASE_BUILD =

View File

@ -793,41 +793,19 @@ public class GeckoAppShell
public static Intent getWebappIntent(String aURI, String aOrigin, String aTitle, Bitmap aIcon) {
Intent intent;
if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
Allocator slots = Allocator.getInstance(getContext());
int index = slots.getIndexForOrigin(aOrigin);
Allocator slots = Allocator.getInstance(getContext());
int index = slots.getIndexForOrigin(aOrigin);
if (index == -1) {
return null;
}
String packageName = slots.getAppForIndex(index);
intent = getContext().getPackageManager().getLaunchIntentForPackage(packageName);
if (aURI != null) {
intent.setData(Uri.parse(aURI));
}
} else {
int index;
if (aIcon != null && !TextUtils.isEmpty(aTitle))
index = WebappAllocator.getInstance(getContext()).findAndAllocateIndex(aOrigin, aTitle, aIcon);
else
index = WebappAllocator.getInstance(getContext()).getIndexForApp(aOrigin);
if (index == -1)
return null;
intent = getWebappIntent(index, aURI);
if (index == -1) {
return null;
}
return intent;
}
String packageName = slots.getAppForIndex(index);
intent = getContext().getPackageManager().getLaunchIntentForPackage(packageName);
if (aURI != null) {
intent.setData(Uri.parse(aURI));
}
// The old implementation of getWebappIntent. Not used by MOZ_ANDROID_SYNTHAPKS.
public static Intent getWebappIntent(int aIndex, String aURI) {
Intent intent = new Intent();
intent.setAction(GeckoApp.ACTION_WEBAPP_PREFIX + aIndex);
intent.setData(Uri.parse(aURI));
intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
AppConstants.ANDROID_PACKAGE_NAME + ".WebApps$WebApp" + aIndex);
return intent;
}

View File

@ -5,24 +5,28 @@
package org.mozilla.gecko;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.util.ThreadUtils;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import java.lang.ref.SoftReference;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.util.ThreadUtils;
import android.database.Cursor;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
class GlobalHistory {
private static final String LOGTAG = "GeckoGlobalHistory";
private static GlobalHistory sInstance = new GlobalHistory();
private static final String TELEMETRY_HISTOGRAM_ADD = "FENNEC_GLOBALHISTORY_ADD_MS";
private static final String TELEMETRY_HISTOGRAM_UPDATE = "FENNEC_GLOBALHISTORY_UPDATE_MS";
private static final String TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK = "FENNEC_GLOBALHISTORY_VISITED_BUILD_MS";
private static final GlobalHistory sInstance = new GlobalHistory();
static GlobalHistory getInstance() {
return sInstance;
@ -48,35 +52,38 @@ class GlobalHistory {
public void run() {
Set<String> visitedSet = mVisitedCache.get();
if (visitedSet == null) {
// the cache was wiped away, repopulate it
// The cache was wiped. Repopulate it.
Log.w(LOGTAG, "Rebuilding visited link set...");
visitedSet = new HashSet<String>();
Cursor c = null;
try {
c = BrowserDB.getAllVisitedHistory(GeckoAppShell.getContext().getContentResolver());
if (c == null) {
return;
}
final long start = SystemClock.uptimeMillis();
final Cursor c = BrowserDB.getAllVisitedHistory(GeckoAppShell.getContext().getContentResolver());
if (c == null) {
return;
}
try {
visitedSet = new HashSet<String>();
if (c.moveToFirst()) {
do {
visitedSet.add(c.getString(0));
} while (c.moveToNext());
}
mVisitedCache = new SoftReference<Set<String>>(visitedSet);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Telemetry.HistogramAdd(TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK, (int) Math.min(took, Integer.MAX_VALUE));
} finally {
if (c != null)
c.close();
c.close();
}
}
// this runs on the same handler thread as the checkUriVisited code,
// so no synchronization needed
// This runs on the same handler thread as the checkUriVisited code,
// so no synchronization is needed.
while (true) {
String uri = mPendingUris.poll();
final String uri = mPendingUris.poll();
if (uri == null) {
break;
}
if (visitedSet.contains(uri)) {
GeckoAppShell.notifyUriVisited(uri);
}
@ -95,12 +102,23 @@ class GlobalHistory {
}
public void add(String uri) {
final long start = SystemClock.uptimeMillis();
BrowserDB.updateVisitedHistory(GeckoAppShell.getContext().getContentResolver(), uri);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Log.d(LOGTAG, "GlobalHistory.add took " + took + "msec.");
Telemetry.HistogramAdd(TELEMETRY_HISTOGRAM_ADD, (int) Math.min(took, Integer.MAX_VALUE));
addToGeckoOnly(uri);
}
@SuppressWarnings("static-method")
public void update(String uri, String title) {
final long start = SystemClock.uptimeMillis();
BrowserDB.updateHistoryTitle(GeckoAppShell.getContext().getContentResolver(), uri, title);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Log.d(LOGTAG, "GlobalHistory.update took " + took + "msec.");
Telemetry.HistogramAdd(TELEMETRY_HISTOGRAM_UPDATE, (int) Math.min(took, Integer.MAX_VALUE));
}
public void checkUriVisited(final String uri) {

View File

@ -6,11 +6,7 @@
#filter substitution
package @ANDROID_PACKAGE_NAME@;
#ifdef MOZ_ANDROID_SYNTHAPKS
import org.mozilla.gecko.webapp.WebappImpl;
#else
import org.mozilla.gecko.WebappImpl;
#endif
/**
* This class serves only as a namespace wrapper for WebappImpl.

View File

@ -1,132 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
import android.util.Log;
import java.util.ArrayList;
public class WebappAllocator {
private final String LOGTAG = "GeckoWebappAllocator";
// The number of Webapp# and WEBAPP# activites/apps/intents
private final static int MAX_WEB_APPS = 100;
protected static WebappAllocator sInstance = null;
public static WebappAllocator getInstance() {
return getInstance(GeckoAppShell.getContext());
}
public static synchronized WebappAllocator getInstance(Context cx) {
if (sInstance == null) {
sInstance = new WebappAllocator(cx);
}
return sInstance;
}
SharedPreferences mPrefs;
protected WebappAllocator(Context context) {
mPrefs = context.getSharedPreferences("webapps", Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
}
public static String appKey(int index) {
return "app" + index;
}
static public String iconKey(int index) {
return "icon" + index;
}
public synchronized int findAndAllocateIndex(String app, String name, String aIconData) {
Bitmap icon = (aIconData != null) ? BitmapUtils.getBitmapFromDataURI(aIconData) : null;
return findAndAllocateIndex(app, name, icon);
}
public synchronized int findAndAllocateIndex(final String app, final String name, final Bitmap aIcon) {
int index = getIndexForApp(app);
if (index != -1)
return index;
for (int i = 0; i < MAX_WEB_APPS; ++i) {
if (!mPrefs.contains(appKey(i))) {
// found unused index i
updateAppAllocation(app, i, aIcon);
return i;
}
}
// no more apps!
return -1;
}
public synchronized void updateAppAllocation(final String app,
final int index,
final Bitmap aIcon) {
if (aIcon != null) {
ThreadUtils.getBackgroundHandler().post(new Runnable() {
@Override
public void run() {
int color = 0;
try {
color = BitmapUtils.getDominantColor(aIcon);
} catch (Exception e) {
Log.e(LOGTAG, "Exception during getDominantColor", e);
}
mPrefs.edit()
.putString(appKey(index), app)
.putInt(iconKey(index), color).commit();
}
});
} else {
mPrefs.edit()
.putString(appKey(index), app)
.putInt(iconKey(index), 0).commit();
}
}
public synchronized int getIndexForApp(String app) {
for (int i = 0; i < MAX_WEB_APPS; ++i) {
if (mPrefs.getString(appKey(i), "").equals(app)) {
return i;
}
}
return -1;
}
public synchronized String getAppForIndex(int index) {
return mPrefs.getString(appKey(index), null);
}
public synchronized int releaseIndexForApp(String app) {
int index = getIndexForApp(app);
if (index == -1)
return -1;
releaseIndex(index);
return index;
}
public synchronized void releaseIndex(final int index) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
mPrefs.edit()
.remove(appKey(index))
.remove(iconKey(index))
.commit();
}
});
}
}

View File

@ -1,221 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.RelativeLayout;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.view.Display;
import java.io.File;
import java.net.URI;
public class WebappImpl extends GeckoApp {
private static final String LOGTAG = "GeckoWebappImpl";
private URI mOrigin;
private TextView mTitlebarText = null;
private View mTitlebar = null;
private View mSplashscreen;
protected int getIndex() { return 0; }
@Override
public int getLayout() { return R.layout.web_app; }
@Override
public boolean hasTabsSideBar() { return false; }
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mSplashscreen = (RelativeLayout) findViewById(R.id.splashscreen);
if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
overridePendingTransition(R.anim.grow_fade_in_center, android.R.anim.fade_out);
showSplash();
}
String action = getIntent().getAction();
Bundle extras = getIntent().getExtras();
String title = extras != null ? extras.getString(Intent.EXTRA_SHORTCUT_NAME) : null;
setTitle(title != null ? title : "Web App");
mTitlebarText = (TextView)findViewById(R.id.webapp_title);
mTitlebar = findViewById(R.id.webapp_titlebar);
if (!action.startsWith(ACTION_WEBAPP_PREFIX)) {
Log.e(LOGTAG, "Webapp launch, but intent action is " + action + "!");
return;
}
// Try to use the origin stored in the WebappAllocator first
String origin = WebappAllocator.getInstance(this).getAppForIndex(getIndex());
try {
mOrigin = new URI(origin);
} catch (java.net.URISyntaxException ex) {
// If we can't parse the this is an app protocol, just settle for not having an origin
if (!origin.startsWith("app://")) {
return;
}
// If that failed fall back to the origin stored in the shortcut
Log.i(LOGTAG, "Webapp is not registered with allocator");
try {
mOrigin = new URI(getIntent().getData().toString());
} catch (java.net.URISyntaxException ex2) {
Log.e(LOGTAG, "Unable to parse intent url: ", ex);
}
}
}
@Override
protected void loadStartupTab(String uri) {
String action = getIntent().getAction();
if (GeckoApp.ACTION_WEBAPP_PREFIX.equals(action)) {
// This action assumes the uri is not an installed Webapp. We will
// use the WebappAllocator to register the uri with an Android
// process so it can run chromeless.
int index = WebappAllocator.getInstance(this).findAndAllocateIndex(uri, "App", (Bitmap) null);
Intent appIntent = GeckoAppShell.getWebappIntent(index, uri);
startActivity(appIntent);
finish();
}
}
private void showSplash() {
SharedPreferences prefs = getSharedPreferences("webapps", Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
// get the favicon dominant color, stored when the app was installed
int[] colors = new int[2];
int dominantColor = prefs.getInt(WebappAllocator.iconKey(getIndex()), -1);
// now lighten it, to ensure that the icon stands out in the center
float[] f = new float[3];
Color.colorToHSV(dominantColor, f);
f[2] = Math.min(f[2]*2, 1.0f);
colors[0] = Color.HSVToColor(255, f);
// now generate a second, slightly darker version of the same color
f[2] *= 0.75;
colors[1] = Color.HSVToColor(255, f);
// Draw the background gradient
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TL_BR, colors);
gd.setGradientType(GradientDrawable.RADIAL_GRADIENT);
Display display = getWindowManager().getDefaultDisplay();
gd.setGradientCenter(0.5f, 0.5f);
gd.setGradientRadius(Math.max(display.getWidth()/2, display.getHeight()/2));
mSplashscreen.setBackgroundDrawable((Drawable)gd);
// look for a logo.png in the profile dir and show it. If we can't find a logo show nothing
File profile = getProfile().getDir();
File logoFile = new File(profile, "logo.png");
if (logoFile.exists()) {
ImageView image = (ImageView)findViewById(R.id.splashscreen_icon);
Drawable d = Drawable.createFromPath(logoFile.getPath());
image.setImageDrawable(d);
Animation fadein = AnimationUtils.loadAnimation(this, R.anim.grow_fade_in_center);
fadein.setStartOffset(500);
fadein.setDuration(1000);
image.startAnimation(fadein);
}
}
@Override
protected String getDefaultProfileName() {
String action = getIntent().getAction();
if (!action.startsWith(ACTION_WEBAPP_PREFIX)) {
Log.e(LOGTAG, "Webapp launch, but intent action is " + action + "!");
return null;
}
return "webapp" + action.substring(ACTION_WEBAPP_PREFIX.length());
}
@Override
protected boolean getSessionRestoreState(Bundle savedInstanceState) {
// for now webapps never restore your session
return false;
}
@Override
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
switch(msg) {
case SELECTED:
case LOCATION_CHANGE:
if (Tabs.getInstance().isSelectedTab(tab)) {
final String urlString = tab.getURL();
final URI uri;
try {
uri = new URI(urlString);
} catch (java.net.URISyntaxException ex) {
mTitlebarText.setText(urlString);
// If we can't parse the url, and its an app protocol hide
// the titlebar and return, otherwise show the titlebar
// and the full url
if (!urlString.startsWith("app://")) {
mTitlebar.setVisibility(View.VISIBLE);
} else {
mTitlebar.setVisibility(View.GONE);
}
return;
}
if (mOrigin != null && mOrigin.getHost().equals(uri.getHost())) {
mTitlebar.setVisibility(View.GONE);
} else {
mTitlebarText.setText(uri.getScheme() + "://" + uri.getHost());
mTitlebar.setVisibility(View.VISIBLE);
}
}
break;
case LOADED:
if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
Animation fadeout = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
fadeout.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
mSplashscreen.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) { }
@Override
public void onAnimationStart(Animation animation) { }
});
mSplashscreen.startAnimation(fadeout);
}
break;
case START:
if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
View area = findViewById(R.id.splashscreen_progress);
area.setVisibility(View.VISIBLE);
Animation fadein = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
fadein.setDuration(1000);
area.startAnimation(fadein);
}
break;
}
super.onTabChanged(tab, msg, data);
}
};

View File

@ -4,19 +4,6 @@
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:process=":@ANDROID_PACKAGE_NAME@.Webapp@APPNUM@"
android:theme="@style/Gecko.App"
#ifdef MOZ_ANDROID_SYNTHAPKS
android:launchMode="singleTop"
android:exported="true"
/>
#else
android:launchMode="singleTask"
android:taskAffinity="org.mozilla.gecko.WEBAPP@APPNUM@"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="org.mozilla.gecko.WEBAPP@APPNUM@" />
</intent-filter>
<intent-filter>
<action android:name="org.mozilla.gecko.ACTION_ALERT_CALLBACK" />
</intent-filter>
</activity>
#endif

View File

@ -5,20 +5,24 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.db.BrowserDB;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.Log;
/**
* Encapsulates the implementation of the search cursor loader.
*/
class SearchLoader {
// Key for search terms
public static final String LOGTAG = "GeckoSearchLoader";
private static final String KEY_SEARCH_TERM = "search_term";
private SearchLoader() {
@ -53,6 +57,8 @@ class SearchLoader {
}
public static class SearchCursorLoader extends SimpleCursorLoader {
private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_SEARCH_LOADER_TIME_MS";
// Max number of search results
private static final int SEARCH_LIMIT = 100;
@ -66,7 +72,12 @@ class SearchLoader {
@Override
public Cursor loadCursor() {
return BrowserDB.filter(getContext().getContentResolver(), mSearchTerm, SEARCH_LIMIT);
final long start = SystemClock.uptimeMillis();
final Cursor cursor = BrowserDB.filter(getContext().getContentResolver(), mSearchTerm, SEARCH_LIMIT);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Telemetry.HistogramAdd(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
return cursor;
}
public String getSearchTerm() {

View File

@ -31,9 +31,9 @@ import android.content.Context;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
@ -430,8 +430,9 @@ public class TopSitesPanel extends HomeFragment {
}
private static class TopSitesLoader extends SimpleCursorLoader {
// Max number of search results
// Max number of search results.
private static final int SEARCH_LIMIT = 30;
private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_TOPSITES_LOADER_TIME_MS";
private int mMaxGridEntries;
public TopSitesLoader(Context context) {
@ -441,8 +442,12 @@ public class TopSitesPanel extends HomeFragment {
@Override
public Cursor loadCursor() {
trace("TopSitesLoader.loadCursor()");
return BrowserDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
final long start = SystemClock.uptimeMillis();
final Cursor cursor = BrowserDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
final long end = SystemClock.uptimeMillis();
final long took = end - start;
Telemetry.HistogramAdd(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
return cursor;
}
}

View File

@ -404,8 +404,6 @@ gbjar.sources += [
'webapp/TaskKiller.java',
'webapp/UninstallListener.java',
'webapp/WebappImpl.java',
'WebappAllocator.java',
'WebappImpl.java',
'widget/ActivityChooserModel.java',
'widget/AllCapsTextView.java',
'widget/AnimatedHeightLayout.java',

View File

@ -5,33 +5,6 @@
package org.mozilla.gecko.webapp;
import org.mozilla.gecko.ActivityHandlerHelper;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.WebappAllocator;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Log;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
@ -40,6 +13,25 @@ import java.util.Set;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.ActivityHandlerHelper;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.util.Log;
public class EventListener implements NativeEventListener {
@ -68,18 +60,10 @@ public class EventListener implements NativeEventListener {
@Override
public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
try {
if (AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("Webapps:InstallApk")) {
if (event.equals("Webapps:InstallApk")) {
installApk(GeckoAppShell.getGeckoInterface().getActivity(), message, callback);
} else if (event.equals("Webapps:Postinstall")) {
if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
postInstallWebapp(message.getString("apkPackageName"), message.getString("origin"));
} else {
postInstallWebapp(message.getString("name"),
message.getString("manifestURL"),
message.getString("origin"),
message.getString("iconURL"),
message.getString("originalOrigin"));
}
postInstallWebapp(message.getString("apkPackageName"), message.getString("origin"));
} else if (event.equals("Webapps:Open")) {
Intent intent = GeckoAppShell.getWebappIntent(message.getString("manifestURL"),
message.getString("origin"),
@ -88,16 +72,6 @@ public class EventListener implements NativeEventListener {
return;
}
GeckoAppShell.getGeckoInterface().getActivity().startActivity(intent);
} else if (!AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("Webapps:Uninstall")) {
uninstallWebapp(message.getString("origin"));
} else if (!AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("Webapps:Preinstall")) {
String name = message.getString("name");
String manifestURL = message.getString("manifestURL");
String origin = message.getString("origin");
JSONObject obj = new JSONObject();
obj.put("profile", preInstallWebapp(name, manifestURL, origin).toString());
callback.sendSuccess(obj);
} else if (event.equals("Webapps:GetApkVersions")) {
JSONObject obj = new JSONObject();
obj.put("versions", getApkVersions(GeckoAppShell.getGeckoInterface().getActivity(),
@ -109,30 +83,6 @@ public class EventListener implements NativeEventListener {
}
}
// Not used by MOZ_ANDROID_SYNTHAPKS.
public static File preInstallWebapp(String aTitle, String aURI, String aOrigin) {
int index = WebappAllocator.getInstance(GeckoAppShell.getContext()).findAndAllocateIndex(aOrigin, aTitle, (String) null);
GeckoProfile profile = GeckoProfile.get(GeckoAppShell.getContext(), "webapp" + index);
return profile.getDir();
}
// Not used by MOZ_ANDROID_SYNTHAPKS.
public static void postInstallWebapp(String aTitle, String aURI, String aOrigin, String aIconURL, String aOriginalOrigin) {
WebappAllocator allocator = WebappAllocator.getInstance(GeckoAppShell.getContext());
int index = allocator.getIndexForApp(aOriginalOrigin);
assert aIconURL != null;
final int preferredSize = GeckoAppShell.getPreferredIconSize();
Bitmap icon = FaviconDecoder.getMostSuitableBitmapFromDataURI(aIconURL, preferredSize);
assert aOrigin != null && index != -1;
allocator.updateAppAllocation(aOrigin, index, icon);
GeckoAppShell.createShortcut(aTitle, aURI, aOrigin, icon, "webapp");
}
// Used by MOZ_ANDROID_SYNTHAPKS.
public static void postInstallWebapp(String aPackageName, String aOrigin) {
Allocator allocator = Allocator.getInstance(GeckoAppShell.getContext());
int index = allocator.findOrAllocatePackage(aPackageName);

View File

@ -147,9 +147,7 @@ public class UninstallListener extends BroadcastReceiver {
ThreadUtils.assertOnBackgroundThread();
// Perform webapp uninstalls as appropiate.
if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
UninstallListener.initUninstallPackageScan(mApp.getApplicationContext());
}
UninstallListener.initUninstallPackageScan(mApp.getApplicationContext());
}
}
}

View File

@ -11,11 +11,9 @@ Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
Cu.import("resource://gre/modules/ContactService.jsm");
#ifdef MOZ_ANDROID_SYNTHAPKS
Cu.import("resource://gre/modules/AppsUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
#endif
function pref(name, value) {
return {
@ -70,14 +68,12 @@ let WebappRT = {
});
}
#ifdef MOZ_ANDROID_SYNTHAPKS
// If the app is in debug mode, configure and enable the remote debugger.
sendMessageToJava({ type: "NativeApp:IsDebuggable" }, (response) => {
if (response.isDebuggable) {
this._enableRemoteDebugger(aUrl);
}
});
#endif
this.findManifestUrlFor(aUrl, aCallback);
},
@ -163,7 +159,6 @@ let WebappRT = {
}
},
#ifdef MOZ_ANDROID_SYNTHAPKS
_enableRemoteDebugger: function(aUrl) {
// Skip the connection prompt in favor of notifying the user below.
Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false);
@ -195,7 +190,6 @@ let WebappRT = {
});
});
},
#endif
handleEvent: function(event) {
let target = event.target;

View File

@ -12,9 +12,7 @@ Cu.import("resource://gre/modules/Services.jsm")
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
#ifdef MOZ_ANDROID_SYNTHAPKS
XPCOMUtils.defineLazyModuleGetter(this, "WebappManager", "resource://gre/modules/WebappManager.jsm");
#endif
const DEFAULT_ICON = "chrome://browser/skin/images/default-app-icon.png";
@ -45,51 +43,9 @@ function openLink(aEvent) {
} catch (ex) {}
}
#ifdef MOZ_ANDROID_SYNTHAPKS
function checkForUpdates(aEvent) {
WebappManager.checkForUpdates(true);
}
#endif
#ifndef MOZ_ANDROID_SYNTHAPKS
var ContextMenus = {
target: null,
init: function() {
document.addEventListener("contextmenu", this, false);
document.getElementById("addToHomescreenLabel").addEventListener("click", this.addToHomescreen, false);
document.getElementById("uninstallLabel").addEventListener("click", this.uninstall, false);
},
handleEvent: function(event) {
// store the target of context menu events so that we know which app to act on
this.target = event.target;
while (!this.target.hasAttribute("contextmenu")) {
this.target = this.target.parentNode;
}
},
addToHomescreen: function() {
let manifest = this.target.manifest;
gChromeWin.WebappsUI.createShortcut(manifest.name, manifest.fullLaunchPath(), manifest.biggestIconURL || DEFAULT_ICON, "webapp");
this.target = null;
},
uninstall: function() {
navigator.mozApps.mgmt.uninstall(this.target.app);
let manifest = this.target.manifest;
gChromeWin.sendMessageToJava({
type: "Shortcut:Remove",
title: manifest.name,
url: manifest.fullLaunchPath(),
origin: this.target.app.origin,
shortcutType: "webapp"
});
this.target = null;
}
}
#endif
function onLoad(aEvent) {
let elmts = document.querySelectorAll("[pref]");
@ -97,17 +53,14 @@ function onLoad(aEvent) {
elmts[i].addEventListener("click", openLink, false);
}
#ifdef MOZ_ANDROID_SYNTHAPKS
document.getElementById("update-item").addEventListener("click", checkForUpdates, false);
#endif
navigator.mozApps.mgmt.oninstall = onInstall;
navigator.mozApps.mgmt.onuninstall = onUninstall;
updateList();
#ifndef MOZ_ANDROID_SYNTHAPKS
ContextMenus.init();
#endif
// XXX - Hack to fix bug 985867 for now
document.addEventListener("touchstart", function() { });
}
function updateList() {
@ -131,9 +84,6 @@ function addApplication(aApp) {
let container = document.createElement("div");
container.className = "app list-item";
#ifndef MOZ_ANDROID_SYNTHAPKS
container.setAttribute("contextmenu", "appmenu");
#endif
container.setAttribute("id", "app-" + aApp.origin);
container.setAttribute("mozApp", aApp.origin);
container.setAttribute("title", manifest.name);

View File

@ -28,14 +28,6 @@
</head>
<body dir="&locale.dir;">
#ifndef MOZ_ANDROID_SYNTHAPKS
<menu type="context" id="appmenu">
<menuitem id="addToHomescreenLabel" label="&aboutApps.addToHomescreen;"></menuitem>
<menuitem id="uninstallLabel" label="&aboutApps.uninstall;"></menuitem>
</menu>
#endif
<div class="header">
<div>&aboutApps.header;</div>
<div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL"/>
@ -55,13 +47,11 @@
<div id="browse-title" class="title">&aboutApps.browseMarketplace;</div>
</div>
</div>
#ifdef MOZ_ANDROID_SYNTHAPKS
<div class="list-item" id="update-item" role="button">
<img class="icon" src="chrome://browser/skin/images/update.png" />
<div class="inner">
<div id="browse-title" class="title">&aboutApps.checkForUpdates;</div>
</div>
</div>
#endif
</body>
</html>

View File

@ -77,10 +77,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils",
"resource://shumway/ShumwayUtils.jsm");
#endif
#ifdef MOZ_ANDROID_SYNTHAPKS
XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
"resource://gre/modules/WebappManager.jsm");
#endif
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
"resource://gre/modules/CharsetMenu.jsm");
@ -326,7 +324,6 @@ var BrowserApp = {
Services.obs.addObserver(this, "FormHistory:Init", false);
Services.obs.addObserver(this, "gather-telemetry", false);
Services.obs.addObserver(this, "keyword-search", false);
#ifdef MOZ_ANDROID_SYNTHAPKS
Services.obs.addObserver(this, "webapps-runtime-install", false);
Services.obs.addObserver(this, "webapps-runtime-install-package", false);
Services.obs.addObserver(this, "webapps-ask-install", false);
@ -335,7 +332,6 @@ var BrowserApp = {
Services.obs.addObserver(this, "Webapps:AutoInstall", false);
Services.obs.addObserver(this, "Webapps:Load", false);
Services.obs.addObserver(this, "Webapps:AutoUninstall", false);
#endif
Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
function showFullScreenWarning() {
@ -377,13 +373,9 @@ var BrowserApp = {
XPInstallObserver.init();
CharacterEncoding.init();
ActivityObserver.init();
#ifdef MOZ_ANDROID_SYNTHAPKS
// TODO: replace with Android implementation of WebappOSUtils.isLaunchable.
Cu.import("resource://gre/modules/Webapps.jsm");
DOMApplicationRegistry.allAppsLaunchable = true;
#else
WebappsUI.init();
#endif
RemoteDebugger.init();
Reader.init();
UserAgentOverrides.init();
@ -770,9 +762,6 @@ var BrowserApp = {
HealthReportStatusListener.uninit();
CharacterEncoding.uninit();
SearchEngines.uninit();
#ifndef MOZ_ANDROID_SYNTHAPKS
WebappsUI.uninit();
#endif
RemoteDebugger.uninit();
Reader.uninit();
UserAgentOverrides.uninit();
@ -961,7 +950,6 @@ var BrowserApp = {
sendMessageToJava(message);
},
#ifdef MOZ_ANDROID_SYNTHAPKS
_loadWebapp: function(aMessage) {
this._initRuntime(this._startupStatus, aMessage.url, aUrl => {
@ -969,7 +957,6 @@ var BrowserApp = {
this.addTab(aUrl, { title: aMessage.name });
});
},
#endif
// Calling this will update the state in BrowserApp after a tab has been
// closed in the Java UI.
@ -1627,7 +1614,6 @@ var BrowserApp = {
this.notifyPrefObservers(aData);
break;
#ifdef MOZ_ANDROID_SYNTHAPKS
case "webapps-runtime-install":
WebappManager.install(JSON.parse(aData), aSubject);
break;
@ -1661,7 +1647,6 @@ var BrowserApp = {
case "Webapps:AutoUninstall":
WebappManager.autoUninstall(JSON.parse(aData));
break;
#endif
case "Locale:Changed":
if (aData) {
@ -1676,6 +1661,14 @@ var BrowserApp = {
}
Services.prefs.setBoolPref("intl.locale.matchOS", !aData);
// Ensure that this choice is immediately persisted, because
// Gecko won't be told again if it forgets.
Services.prefs.savePrefFile(null);
// Blow away the string cache so that future lookups get the
// correct locale.
Services.strings.flushBundles();
break;
default:
@ -2290,16 +2283,46 @@ var NativeWindow = {
return res;
},
_findTarget: function(x, y) {
let isDescendant = function(parent, child) {
let node = child;
while (node) {
if (node === parent) {
return true;
}
node = node.parentNode;
}
return false;
};
let target = BrowserEventHandler._highlightElement;
let touchTarget = ElementTouchHelper.anyElementFromPoint(x, y);
// If we have a highlighted element that has a click handler, we want to ensure our target is inside it
if (isDescendant(target, touchTarget)) {
target = touchTarget;
} else if (!target) {
// Otherwise, let's try to find something clickable
target = ElementTouchHelper.elementFromPoint(x, y);
// If that failed, we'll just fall back to anything under the user's finger
if (!target) {
target = touchTarget;
}
}
return target;
},
/* Checks if there are context menu items to show, and if it finds them
* sends a contextmenu event to content. We also send showing events to
* any html5 context menus we are about to show, and fire some local notifications
* for chrome consumers to do lazy menuitem construction
*/
_sendToContent: function(x, y) {
let target = BrowserEventHandler._highlightElement || ElementTouchHelper.elementFromPoint(x, y);
if (!target)
target = ElementTouchHelper.anyElementFromPoint(x, y);
let target = this._findTarget(x, y);
if (!target)
return;
@ -7030,246 +7053,6 @@ var ActivityObserver = {
}
};
#ifndef MOZ_ANDROID_SYNTHAPKS
var WebappsUI = {
init: function init() {
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
DOMApplicationRegistry.allAppsLaunchable = true;
Services.obs.addObserver(this, "webapps-ask-install", false);
Services.obs.addObserver(this, "webapps-launch", false);
Services.obs.addObserver(this, "webapps-uninstall", false);
Services.obs.addObserver(this, "webapps-install-error", false);
},
uninit: function unint() {
Services.obs.removeObserver(this, "webapps-ask-install");
Services.obs.removeObserver(this, "webapps-launch");
Services.obs.removeObserver(this, "webapps-uninstall");
Services.obs.removeObserver(this, "webapps-install-error");
},
DEFAULT_ICON: "chrome://browser/skin/images/default-app-icon.png",
DEFAULT_PREFS_FILENAME: "default-prefs.js",
observe: function observe(aSubject, aTopic, aData) {
let data = {};
try {
data = JSON.parse(aData);
data.mm = aSubject;
} catch(ex) { }
switch (aTopic) {
case "webapps-install-error":
let msg = "";
switch (aData) {
case "INVALID_MANIFEST":
case "MANIFEST_PARSE_ERROR":
msg = Strings.browser.GetStringFromName("webapps.manifestInstallError");
break;
case "NETWORK_ERROR":
case "MANIFEST_URL_ERROR":
msg = Strings.browser.GetStringFromName("webapps.networkInstallError");
break;
default:
msg = Strings.browser.GetStringFromName("webapps.installError");
}
NativeWindow.toast.show(msg, "short");
console.log("Error installing app: " + aData);
break;
case "webapps-ask-install":
this.doInstall(data);
break;
case "webapps-launch":
this.openURL(data.manifestURL, data.origin);
break;
case "webapps-uninstall":
sendMessageToJava({
type: "Webapps:Uninstall",
origin: data.origin
});
break;
}
},
doInstall: function doInstall(aData) {
let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
if (Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) {
// Get a profile for the app to be installed in. We'll download everything before creating the icons.
let origin = aData.app.origin;
sendMessageToJava({
type: "Webapps:Preinstall",
name: manifest.name,
manifestURL: aData.app.manifestURL,
origin: origin
}, (data) => {
let profilePath = data.profile;
if (!profilePath)
return;
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(profilePath);
let self = this;
DOMApplicationRegistry.confirmInstall(aData, file,
function (aManifest) {
let localeManifest = new ManifestHelper(aManifest, aData.app.origin);
// the manifest argument is the manifest from within the zip file,
// TODO so now would be a good time to ask about permissions.
self.makeBase64Icon(localeManifest.biggestIconURL || this.DEFAULT_ICON,
function(scaledIcon, fullsizeIcon) {
// if java returned a profile path to us, try to use it to pre-populate the app cache
// also save the icon so that it can be used in the splash screen
try {
let iconFile = file.clone();
iconFile.append("logo.png");
let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist);
persist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
persist.persistFlags |= Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
let source = Services.io.newURI(fullsizeIcon, "UTF8", null);
persist.saveURI(source, null, null, null, null, iconFile, null);
// aData.app.origin may now point to the app: url that hosts this app
sendMessageToJava({
type: "Webapps:Postinstall",
name: localeManifest.name,
manifestURL: aData.app.manifestURL,
originalOrigin: origin,
origin: aData.app.origin,
iconURL: fullsizeIcon
});
if (!!aData.isPackage) {
// For packaged apps, put a notification in the notification bar.
let message = Strings.browser.GetStringFromName("webapps.alertSuccess");
let alerts = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
alerts.showAlertNotification("drawable://alert_app", localeManifest.name, message, true, "", {
observe: function () {
self.openURL(aData.app.manifestURL, aData.app.origin);
}
}, "webapp");
}
// Create a system notification allowing the user to launch the app
let observer = {
observe: function (aSubject, aTopic) {
if (aTopic == "alertclickcallback") {
WebappsUI.openURL(aData.app.manifestURL, origin);
}
}
};
let message = Strings.browser.GetStringFromName("webapps.alertSuccess");
let alerts = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService);
alerts.showAlertNotification("drawable://alert_app", localeManifest.name, message, true, "", observer, "webapp");
} catch(ex) {
console.log(ex);
}
self.writeDefaultPrefs(file, localeManifest);
}
);
}
);
});
} else {
DOMApplicationRegistry.denyInstall(aData);
}
},
writeDefaultPrefs: function webapps_writeDefaultPrefs(aProfile, aManifest) {
// build any app specific default prefs
let prefs = [];
if (aManifest.orientation) {
prefs.push({name:"app.orientation.default", value: aManifest.orientation.join(",") });
}
// write them into the app profile
let defaultPrefsFile = aProfile.clone();
defaultPrefsFile.append(this.DEFAULT_PREFS_FILENAME);
this._writeData(defaultPrefsFile, prefs);
},
_writeData: function(aFile, aPrefs) {
if (aPrefs.length > 0) {
let array = new TextEncoder().encode(JSON.stringify(aPrefs));
OS.File.writeAtomic(aFile.path, array, { tmpPath: aFile.path + ".tmp" }).then(null, function onError(reason) {
console.log("Error writing default prefs: " + reason);
});
}
},
openURL: function openURL(aManifestURL, aOrigin) {
sendMessageToJava({
type: "Webapps:Open",
manifestURL: aManifestURL,
origin: aOrigin
});
},
get iconSize() {
let iconSize = 64;
try {
let jni = new JNI();
let cls = jni.findClass("org/mozilla/gecko/GeckoAppShell");
let method = jni.getStaticMethodID(cls, "getPreferredIconSize", "()I");
iconSize = jni.callStaticIntMethod(cls, method);
jni.close();
} catch(ex) {
console.log(ex);
}
delete this.iconSize;
return this.iconSize = iconSize;
},
makeBase64Icon: function loadAndMakeBase64Icon(aIconURL, aCallbackFunction) {
let size = this.iconSize;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = canvas.height = size;
let ctx = canvas.getContext("2d");
let favicon = new Image();
favicon.onload = function() {
ctx.drawImage(favicon, 0, 0, size, size);
let scaledIcon = canvas.toDataURL("image/png", "");
canvas.width = favicon.width;
canvas.height = favicon.height;
ctx.drawImage(favicon, 0, 0, favicon.width, favicon.height);
let fullsizeIcon = canvas.toDataURL("image/png", "");
canvas = null;
aCallbackFunction.call(null, scaledIcon, fullsizeIcon);
};
favicon.onerror = function() {
Cu.reportError("CreateShortcut: favicon image load error");
// if the image failed to load, and it was not our default icon, attempt to
// use our default as a fallback
if (favicon.src != WebappsUI.DEFAULT_ICON) {
favicon.src = WebappsUI.DEFAULT_ICON;
}
};
favicon.src = aIconURL;
},
createShortcut: function createShortcut(aTitle, aURL, aIconURL, aType) {
this.makeBase64Icon(aIconURL, function _createShortcut(icon) {
try {
let shell = Cc["@mozilla.org/browser/shell-service;1"].createInstance(Ci.nsIShellService);
shell.createShortcut(aTitle, aURL, icon, aType);
} catch(e) {
Cu.reportError(e);
}
});
}
}
#endif
var RemoteDebugger = {
init: function rd_init() {
Services.prefs.addObserver("devtools.debugger.", this, false);

View File

@ -113,12 +113,10 @@ contract @mozilla.org/snippets;1 {a78d7e59-b558-4321-a3d6-dffe2f1e76dd}
category profile-after-change Snippets @mozilla.org/snippets;1
category update-timer Snippets @mozilla.org/snippets;1,getService,snippets-update-timer,browser.snippets.updateInterval,86400
#ifdef MOZ_ANDROID_SYNTHAPKS
# WebappsUpdateTimer.js
component {8f7002cb-e959-4f0a-a2e8-563232564385} WebappsUpdateTimer.js
contract @mozilla.org/webapps-update-timer;1 {8f7002cb-e959-4f0a-a2e8-563232564385}
category update-timer WebappsUpdateTimer @mozilla.org/webapps-update-timer;1,getService,webapp-background-update-timer,browser.webapps.updateInterval,86400
#endif
# ColorPicker.js
component {430b987f-bb9f-46a3-99a5-241749220b29} ColorPicker.js

View File

@ -67,8 +67,5 @@ MOZ_DATA_REPORTING=1
# Enable runtime locale switching.
MOZ_LOCALE_SWITCHER=1
# Enable the "synthetic APKs" implementation of Open Web Apps.
MOZ_ANDROID_SYNTHAPKS=1
# Enable second screen and casting support for external devices.
MOZ_DEVICES=1

View File

@ -621,10 +621,7 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
@BINPATH@/components/marionettecomponent.js
#endif
#ifdef MOZ_ANDROID_SYNTHAPKS
@BINPATH@/components/WebappsUpdateTimer.js
#endif
@BINPATH@/components/DataStore.manifest
@BINPATH@/components/DataStoreImpl.js
@BINPATH@/components/dom_datastore.xpt

View File

@ -242,16 +242,6 @@ timer.start=%S: timer started
# %1$S=name of timer, %2$S=number of milliseconds
timer.end=%1$S: %2$Sms
# Webapps
webapps.installTitle=Install Application
webapps.alertSuccess=Successfully installed
# Shown when there is a generic problem installing an app
webapps.installError=Error installing application
# Shown when there is something wrong with an apps manifest
webapps.manifestInstallError=Invalid application manifest
# Shown when a network error prevented installing an app
webapps.networkInstallError=Could not download manifest
# Click to play plugins
clickToPlayPlugins.message2=%S contains plugin content. Would you like to activate it?
clickToPlayPlugins.activate=Activate

View File

@ -37,9 +37,7 @@
locale/@AB_CD@/browser/phishing.dtd (%chrome/phishing.dtd)
locale/@AB_CD@/browser/payments.properties (%chrome/payments.properties)
locale/@AB_CD@/browser/handling.properties (%chrome/handling.properties)
#ifdef MOZ_ANDROID_SYNTHAPKS
locale/@AB_CD@/browser/webapp.properties (%chrome/webapp.properties)
#endif
# overrides for toolkit l10n, also for en-US
relativesrcdir toolkit/locales:

View File

@ -22,16 +22,10 @@ EXTRA_JS_MODULES += [
'SharedPreferences.jsm',
'SimpleServiceDiscovery.jsm',
'SSLExceptions.jsm',
'WebappManagerWorker.js',
]
EXTRA_PP_JS_MODULES += [
'RokuApp.jsm',
'WebappManager.jsm',
]
if CONFIG['MOZ_ANDROID_SYNTHAPKS']:
EXTRA_PP_JS_MODULES += [
'WebappManager.jsm',
]
EXTRA_JS_MODULES += [
'WebappManagerWorker.js',
]

View File

@ -649,6 +649,7 @@ FxAccountsInternal.prototype = {
data.kB = CommonUtils.bytesAsHex(kB_hex);
delete data.keyFetchToken;
delete data.unwrapBKey;
log.debug("Keys Obtained: kA=" + !!data.kA + ", kB=" + !!data.kB);
if (logPII) {

View File

@ -294,8 +294,9 @@ add_test(function test_getKeys() {
// Before getKeys, we have no keys
do_check_eq(!!user.kA, false);
do_check_eq(!!user.kB, false);
// And we still have a key-fetch token to use
// And we still have a key-fetch token and unwrapBKey to use
do_check_eq(!!user.keyFetchToken, true);
do_check_eq(!!user.unwrapBKey, true);
fxa.internal.getKeys().then(() => {
fxa.getSignedInUser().then((user) => {
@ -305,6 +306,7 @@ add_test(function test_getKeys() {
do_check_eq(user.kA, expandHex("11"));
do_check_eq(user.kB, expandHex("66"));
do_check_eq(user.keyFetchToken, undefined);
do_check_eq(user.unwrapBKey, undefined);
do_test_finished();
run_next_test();
});

View File

@ -28,8 +28,5 @@ skip = false
[include:../../../../../dom/events/test/marionette/manifest.ini]
[include:../../../../../dom/wifi/test/marionette/manifest.ini]
; layout tests
[include:../../../../../layout/base/tests/marionette/manifest.ini]
; loop tests
[include:../../../../../browser/components/loop/manifest.ini]

View File

@ -1,17 +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/. -->
<!DOCTYPE html>
<html id="html">
<head>
<title>Bug 960897: Marionette tests for touch caret</title>
</head>
<body>
<div><input id="input" value="ABCDEFGHI"></input></div>
<br />
<div><textarea name="textarea" id="textarea" rows="4" cols="6">ABCDEFGHI</textarea></div>
<br />
<div style="width: 10em; height: 2em; word-wrap: break-word; overflow: auto;" contenteditable="true" id="contenteditable">ABCDEFGHI</div>
</body>
</html>

View File

@ -63,8 +63,7 @@ function isFilenameWithSameDate(aSourceName, aTargetName) {
let targetMatches = aTargetName.match(filenamesRegex);
return sourceMatches && targetMatches &&
sourceMatches[1] == targetMatches[1] &&
sourceMatches[4] == targetMatches[4];
sourceMatches[1] == targetMatches[1];
}
/**
@ -438,6 +437,10 @@ this.PlacesBackups = {
this._backupFiles.shift();
this._entries.shift();
newBackupFile = mostRecentBackupFile;
// Ensure we retain the proper extension when renaming
// the most recent backup file.
if (/\.json$/.test(OS.Path.basename(mostRecentBackupFile)))
newBackupFilename = this.getFilenameForDate();
newFilenameWithMetaData = appendMetaDataToFilename(
newBackupFilename,
{ count: this.getBookmarkCountForFile(mostRecentBackupFile),

View File

@ -0,0 +1,103 @@
function run_test() {
run_next_test();
}
/* Bug 1016953 - When a previous bookmark backup exists with the same hash
regardless of date, an automatic backup should attempt to either rename it to
today's date if the backup was for an old date or leave it alone if it was for
the same date. However if the file ext was json it will accidentally rename it
to jsonlz4 while keeping the json contents
*/
add_task(function* test_same_date_same_hash() {
// If old file has been created on the same date and has the same hash
// the file should be left alone
let backupFolder = yield PlacesBackups.getBackupFolder();
// Save to profile dir to obtain hash and nodeCount to append to filename
let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
"bug10169583_bookmarks.json");
let {count, hash} = yield BookmarkJSONUtils.exportToFile(tempPath);
// Save JSON file in backup folder with hash appended
let dateObj = new Date();
let filename = "bookmarks-" + dateObj.toLocaleFormat("%Y-%m-%d") + "_" +
count + "_" + hash + ".json";
let backupFile = OS.Path.join(backupFolder, filename);
yield OS.File.move(tempPath, backupFile);
// Force a compressed backup which fallbacks to rename
yield PlacesBackups.create();
let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
// check to ensure not renamed to jsonlz4
Assert.equal(mostRecentBackupFile, backupFile);
// inspect contents and check if valid json
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let result = yield OS.File.read(mostRecentBackupFile);
let jsonString = converter.convertFromByteArray(result, result.length);
do_log_info("Check is valid JSON");
JSON.parse(jsonString);
// Cleanup
yield OS.File.remove(backupFile);
yield OS.File.remove(tempPath);
PlacesBackups._backupFiles = null; // To force re-cache of backupFiles
});
add_task(function* test_same_date_diff_hash() {
// If the old file has been created on the same date, but has a different hash
// the existing file should be overwritten with the newer compressed version
let backupFolder = yield PlacesBackups.getBackupFolder();
let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
"bug10169583_bookmarks.json");
let {count, hash} = yield BookmarkJSONUtils.exportToFile(tempPath);
let dateObj = new Date();
let filename = "bookmarks-" + dateObj.toLocaleFormat("%Y-%m-%d") + "_" +
count + "_" + "differentHash==" + ".json";
let backupFile = OS.Path.join(backupFolder, filename);
yield OS.File.move(tempPath, backupFile);
yield PlacesBackups.create(); // Force compressed backup
mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
// Decode lz4 compressed file to json and check if json is valid
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let result = yield OS.File.read(mostRecentBackupFile, { compression: "lz4" });
let jsonString = converter.convertFromByteArray(result, result.length);
do_log_info("Check is valid JSON");
JSON.parse(jsonString);
// Cleanup
yield OS.File.remove(mostRecentBackupFile);
yield OS.File.remove(tempPath);
PlacesBackups._backupFiles = null; // To force re-cache of backupFiles
});
add_task(function* test_diff_date_same_hash() {
// If the old file has been created on an older day but has the same hash
// it should be renamed with today's date without altering the contents.
let backupFolder = yield PlacesBackups.getBackupFolder();
let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
"bug10169583_bookmarks.json");
let {count, hash} = yield BookmarkJSONUtils.exportToFile(tempPath);
let oldDate = new Date(2014, 1, 1);
let curDate = new Date();
let oldFilename = "bookmarks-" + oldDate.toLocaleFormat("%Y-%m-%d") + "_" +
count + "_" + hash + ".json";
let newFilename = "bookmarks-" + curDate.toLocaleFormat("%Y-%m-%d") + "_" +
count + "_" + hash + ".json";
let backupFile = OS.Path.join(backupFolder, oldFilename);
let newBackupFile = OS.Path.join(backupFolder, newFilename);
yield OS.File.move(tempPath, backupFile);
// Ensure file has been renamed correctly
yield PlacesBackups.create();
let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
Assert.equal(mostRecentBackupFile, newBackupFile);
// Cleanup
yield OS.File.remove(mostRecentBackupFile);
yield OS.File.remove(tempPath);
});

View File

@ -33,3 +33,4 @@ tail =
[test_818587_compress-bookmarks-backups.js]
[test_992901-backup-unsorted-hierarchy.js]
[test_997030-bookmarks-html-encode.js]
[test_1016953-renaming-uncompressed.js]

View File

@ -2948,7 +2948,7 @@
"n_buckets": 10,
"cpp_guard": "ANDROID",
"extended_statistics_ok": true,
"description": "FENNEC: (Places) Number of favicons stored"
"description": "Number of favicons stored in the browser DB"
},
"FENNEC_THUMBNAILS_COUNT": {
"expires_in_version": "never",
@ -2957,7 +2957,7 @@
"n_buckets": 10,
"cpp_guard": "ANDROID",
"extended_statistics_ok": true,
"description": "FENNEC: (Places) Number of thumbnails stored"
"description": "Number of thumbnails stored in the browser DB"
},
"PLACES_SORTED_BOOKMARKS_PERC": {
"expires_in_version": "never",
@ -4281,13 +4281,31 @@
"description": "Number of history entries in the original XUL places database",
"cpp_guard": "ANDROID"
},
"FENNEC_AWESOMEBAR_ALLPAGES_EMPTY_TIME": {
"FENNEC_GLOBALHISTORY_ADD_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 10,
"high": "20000",
"n_buckets": 20,
"description": "Fennec: Time for the Awesomebar Top Sites query to return with no filter set (ms)",
"description": "Time for a record to be added to history (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_GLOBALHISTORY_UPDATE_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 10,
"high": "20000",
"n_buckets": 20,
"description": "Time for a record to be updated in history (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_GLOBALHISTORY_VISITED_BUILD_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 10,
"high": "20000",
"n_buckets": 20,
"description": "Time to update the visited link set (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_LOWMEM_TAB_COUNT": {
@ -4304,13 +4322,20 @@
"description": "Fennec is starting up but the Gecko thread was still running",
"cpp_guard": "ANDROID"
},
"FENNEC_STARTUP_TIME_JAVAUI": {
"FENNEC_SEARCH_LOADER_TIME_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 100,
"high": "5000",
"low": 10,
"high": "20000",
"n_buckets": 20,
"description": "Time for the Java UI to load (ms)",
"description": "Time for a URL bar DB search to return (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_STARTUP_GECKOAPP_ACTION": {
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 4,
"description": "The way the GeckoApp was launched. (Normal, URL, Prefetch, Redirector)",
"cpp_guard": "ANDROID"
},
"FENNEC_STARTUP_TIME_ABOUTHOME": {
@ -4331,11 +4356,13 @@
"description": "Time for the Gecko:Ready message to arrive (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_STARTUP_GECKOAPP_ACTION": {
"FENNEC_STARTUP_TIME_JAVAUI": {
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 4,
"description": "The way the GeckoApp was launched. (Normal, URL, Prefetch, Redirector)",
"kind": "exponential",
"low": 100,
"high": "5000",
"n_buckets": 20,
"description": "Time for the Java UI to load (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_TAB_EXPIRED": {
@ -4358,6 +4385,15 @@
"description": "How long (in seconds) a tab was inactive when it was OOM-zombified",
"cpp_guard": "ANDROID"
},
"FENNEC_TOPSITES_LOADER_TIME_MS": {
"expires_in_version": "never",
"kind": "exponential",
"low": 10,
"high": "20000",
"n_buckets": 20,
"description": "Time for the home screen Top Sites query to return with no filter set (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_WAS_KILLED": {
"expires_in_version": "never",
"kind": "flag",

View File

@ -27,23 +27,30 @@
<content>
<xul:autorepeatbutton class="autorepeatbutton-up"
anonid="scrollbutton-up"
collapsed="true"
xbl:inherits="orient"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
oncommand="_autorepeatbuttonScroll(event);"/>
<xul:spacer class="arrowscrollbox-overflow-start-indicator"
xbl:inherits="collapsed=scrolledtostart"/>
<xul:scrollbox class="arrowscrollbox-scrollbox"
anonid="scrollbox"
flex="1"
xbl:inherits="orient,align,pack,dir">
<children/>
</xul:scrollbox>
<xul:spacer class="arrowscrollbox-overflow-end-indicator"
xbl:inherits="collapsed=scrolledtoend"/>
<xul:autorepeatbutton class="autorepeatbutton-down"
anonid="scrollbutton-down"
collapsed="true"
xbl:inherits="orient"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
oncommand="_autorepeatbuttonScroll(event);"/>
</content>
<implementation>
<constructor><![CDATA[
this.setAttribute("notoverflowing", "true");
this._updateScrollButtonsDisabledState();
]]></constructor>
<destructor><![CDATA[
this._stopSmoothScroll();
]]></destructor>
@ -454,28 +461,39 @@
<method name="_updateScrollButtonsDisabledState">
<body><![CDATA[
var disableUpButton = false;
var disableDownButton = false;
var scrolledToStart = false;
var scrolledToEnd = false;
if (this.scrollPosition == 0) {
if (this.hasAttribute("notoverflowing")) {
scrolledToStart = true;
scrolledToEnd = true;
}
else if (this.scrollPosition == 0) {
// In the RTL case, this means the _last_ element in the
// scrollbox is visible
if (this._isRTLScrollbox)
disableDownButton = true;
scrolledToEnd = true;
else
disableUpButton = true;
scrolledToStart = true;
}
else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) {
// In the RTL case, this means the _first_ element in the
// scrollbox is visible
if (this._isRTLScrollbox)
disableUpButton = true;
scrolledToStart = true;
else
disableDownButton = true;
scrolledToEnd = true;
}
this._scrollButtonUp.disabled = disableUpButton;
this._scrollButtonDown.disabled = disableDownButton;
if (scrolledToEnd)
this.setAttribute("scrolledtoend", "true");
else
this.removeAttribute("scrolledtoend");
if (scrolledToStart)
this.setAttribute("scrolledtostart", "true");
else
this.removeAttribute("scrolledtostart");
]]></body>
</method>
</implementation>
@ -535,8 +553,8 @@
return;
}
this._scrollButtonUp.collapsed = true;
this._scrollButtonDown.collapsed = true;
this.setAttribute("notoverflowing", "true");
try {
// See bug 341047 and comments in overflow handler as to why
// try..catch is needed here
@ -545,8 +563,7 @@
this.ensureElementIsVisible(childNodes[0], false);
}
catch(e) {
this._scrollButtonUp.collapsed = false;
this._scrollButtonDown.collapsed = false;
this.removeAttribute("notoverflowing");
}
]]></handler>
@ -569,20 +586,18 @@
return;
}
this._scrollButtonUp.collapsed = false;
this._scrollButtonDown.collapsed = false;
this.removeAttribute("notoverflowing");
try {
// See bug 341047, the overflow event is dispatched when the
// scrollbox already is mostly destroyed. This causes some code in
// _updateScrollButtonsDisabledState() to throw an error. It also
// means that the scrollbarbuttons were uncollapsed when that should
// not be happening, because the whole overflow event should not be
// happening in that case.
// means that the notoverflowing attribute was removed erroneously,
// as the whole overflow event should not be happening in that case.
this._updateScrollButtonsDisabledState();
}
catch(e) {
this._scrollButtonUp.collapsed = true;
this._scrollButtonDown.collapsed = true;
this.setAttribute("notoverflowing", "true");
}
]]></handler>
@ -598,22 +613,26 @@
<binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox">
<content>
<xul:toolbarbutton class="scrollbutton-up" collapsed="true"
xbl:inherits="orient"
<xul:toolbarbutton class="scrollbutton-up"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
anonid="scrollbutton-up"
onclick="_distanceScroll(event);"
onmousedown="if (event.button == 0) _startScroll(-1);"
onmouseup="if (event.button == 0) _stopScroll();"
onmouseover="_continueScroll(-1);"
onmouseout="_pauseScroll();"/>
<xul:spacer class="arrowscrollbox-overflow-start-indicator"
xbl:inherits="collapsed=scrolledtostart"/>
<xul:scrollbox class="arrowscrollbox-scrollbox"
anonid="scrollbox"
flex="1"
xbl:inherits="orient,align,pack,dir">
<children/>
</xul:scrollbox>
<xul:toolbarbutton class="scrollbutton-down" collapsed="true"
xbl:inherits="orient"
<xul:spacer class="arrowscrollbox-overflow-end-indicator"
xbl:inherits="collapsed=scrolledtoend"/>
<xul:toolbarbutton class="scrollbutton-down"
xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
anonid="scrollbutton-down"
onclick="_distanceScroll(event);"
onmousedown="if (event.button == 0) _startScroll(1);"

View File

@ -290,6 +290,12 @@ exports.hasSafeGetter = function hasSafeGetter(aDesc) {
* True if it is safe to read properties from aObj, or false otherwise.
*/
exports.isSafeJSObject = function isSafeJSObject(aObj) {
// If we are running on a worker thread, Cu is not available. In this case,
// we always return false, just to be on the safe side.
if (!Cu) {
return false;
}
if (Cu.getGlobalForObject(aObj) ==
Cu.getGlobalForObject(exports.isSafeJSObject)) {
return true; // aObj is not a cross-compartment wrapper.

View File

@ -6,19 +6,23 @@
* EventEmitter.
*/
(function (factory) { // Module boilerplate
if (this.module && module.id.indexOf("event-emitter") >= 0) { // require
factory.call(this, require, exports, module);
} else { // Cu.import
const Cu = Components.utils;
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
this.promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
factory.call(this, devtools.require, this, { exports: this });
this.EXPORTED_SYMBOLS = ["EventEmitter"];
}
}).call(this, function (require, exports, module) {
this.EventEmitter = function EventEmitter() {};
module.exports = EventEmitter;
if (typeof(require) === "function") {
module.exports = EventEmitter;
var {Cu, components} = require("chrome");
} else {
var EXPORTED_SYMBOLS = ["EventEmitter"];
var Cu = this["Components"].utils;
var components = Components;
}
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
const { Cu, components } = require("chrome");
const Services = require("Services");
/**
* Decorate an object with event emitter functionality.
@ -190,3 +194,5 @@ EventEmitter.prototype = {
}
},
};
});

View File

@ -54,18 +54,22 @@ let FramerateActor = exports.FramerateActor = protocol.ActorClass({
/**
* Stops monitoring framerate, returning the recorded values.
*/
stopRecording: method(function() {
stopRecording: method(function(beginAt = 0, endAt = Number.MAX_SAFE_INTEGER) {
if (!this._recording) {
return [];
}
this._recording = false;
// We don't need to store the ticks array for future use, release it.
let ticks = this._ticks;
let ticks = this._ticks.filter(e => e >= beginAt && e <= endAt);
this._ticks = null;
return ticks;
}, {
response: { timeline: RetVal("array:number") }
request: {
beginAt: Arg(0, "nullable:number"),
endAt: Arg(1, "nullable:number")
},
response: { ticks: RetVal("array:number") }
}),
/**

View File

@ -935,9 +935,12 @@ var WalkerActor = protocol.ActorClass({
},
destroy: function() {
this._hoveredNode = null;
this._destroyed = true;
this.clearPseudoClassLocks();
this._activePseudoClassLocks = null;
this._hoveredNode = null;
this.rootDoc = null;
this.reflowObserver.off("reflows", this._onReflows);
@ -1711,13 +1714,14 @@ var WalkerActor = protocol.ActorClass({
if (!node.writePseudoClassLocks()) {
this._activePseudoClassLocks.delete(node);
}
this._queuePseudoClassMutation(node);
return true;
},
/**
* Clear all the pseudo-classes on a given node
* or all nodes.
* Clear all the pseudo-classes on a given node or all nodes.
* @param {NodeActor} node Optional node to clear pseudo-classes on
*/
clearPseudoClassLocks: method(function(node) {
if (node) {
@ -1929,7 +1933,7 @@ var WalkerActor = protocol.ActorClass({
}),
queueMutation: function(mutation) {
if (!this.actorID) {
if (!this.actorID || this._destroyed) {
// We've been destroyed, don't bother queueing this mutation.
return;
}

View File

@ -21,6 +21,7 @@ support-files =
[test_device.html]
[test_framerate_01.html]
[test_framerate_02.html]
[test_framerate_03.html]
[test_inspector-changeattrs.html]
[test_inspector-changevalue.html]
[test_inspector-hide.html]

View File

@ -0,0 +1,81 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1023018 - Tests whether or not the framerate actor can handle time ranges.
-->
<head>
<meta charset="utf-8">
<title>Framerate actor test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script>
window.onload = function() {
var Cu = Components.utils;
var Cc = Components.classes;
var Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
// Always log packets when running tests.
Services.prefs.setBoolPref("devtools.debugger.log", true);
SimpleTest.registerCleanupFunction(function() {
Services.prefs.clearUserPref("devtools.debugger.log");
});
Cu.import("resource://gre/modules/devtools/Loader.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
SimpleTest.waitForExplicitFinish();
var {FramerateFront} = devtools.require("devtools/server/actors/framerate");
var START_TICK = 2000;
var STOP_TICK = 3000;
var TOTAL_TIME = 5000;
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(function onConnect() {
client.listTabs(function onListTabs(aResponse) {
var form = aResponse.tabs[aResponse.selected];
var front = FramerateFront(client, form);
front.startRecording().then(() => {
window.setTimeout(() => {
front.stopRecording(START_TICK, STOP_TICK).then(rawData => {
onRecordingStopped(front, rawData);
});
}, TOTAL_TIME);
});
});
});
function onRecordingStopped(front, rawData) {
ok(rawData, "There should be a recording available.");
ok(!rawData.find(e => e < START_TICK),
"There should be no tick before 2000ms.");
ok(!rawData.find(e => e > STOP_TICK),
"There should be no tick after 3000ms.");
for (var tick of rawData) {
info("Testing tick: " + tick);
is(typeof tick, "number", "All values should be numbers.");
}
client.close(() => {
DebuggerServer.destroy();
SimpleTest.finish()
});
}
}
</script>
</pre>
</body>
</html>

View File

@ -4,23 +4,32 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-grips");
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-grips", aServer);
gDebuggee.eval(function stopMe(arg1) {
debugger;
}.toString());
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_object_grip();
});
});
do_test_pending();
}
function test_object_grip()
@ -38,7 +47,7 @@ function test_object_grip()
do_check_eq(aResponse.ownPropertyNames[2], "c");
gThreadClient.resume(function() {
finishClient(gClient);
gClient.close(gCallback);
});
});

View File

@ -4,23 +4,32 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-grips");
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-grips", aServer);
gDebuggee.eval(function stopMe(arg1) {
debugger;
}.toString());
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_object_grip();
});
});
do_test_pending();
}
function test_object_grip()
@ -41,7 +50,7 @@ function test_object_grip()
do_check_eq(aResponse.ownPropertyNames[1], "c");
gThreadClient.resume(function() {
finishClient(gClient);
gClient.close(gCallback);
});
});
});

View File

@ -4,23 +4,32 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-grips");
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-grips", aServer);
gDebuggee.eval(function stopMe(arg1) {
debugger;
}.toString());
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_object_grip();
});
});
do_test_pending();
}
function test_object_grip()
@ -51,7 +60,7 @@ function test_object_grip()
do_check_eq(aResponse.descriptor.set.type, "undefined");
gThreadClient.resume(function() {
finishClient(gClient);
gClient.close(gCallback);
});
});
});

View File

@ -4,23 +4,32 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-grips");
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-grips", aServer);
gDebuggee.eval(function stopMe(arg1) {
debugger;
}.toString());
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_object_grip();
});
});
do_test_pending();
}
function test_object_grip()
@ -55,7 +64,7 @@ function test_object_grip()
do_check_true(aResponse.ownPropertyNames.toString != undefined);
gThreadClient.resume(function() {
finishClient(gClient);
gClient.close(gCallback);
});
});
});

View File

@ -9,23 +9,32 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-grips");
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-grips", aServer);
gDebuggee.eval(function stopMe(arg1, arg2) {
debugger;
}.toString());
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_object_grip();
});
});
do_test_pending();
}
function test_object_grip()
@ -44,7 +53,7 @@ function test_object_grip()
do_check_false(obj2Client.isFrozen);
gThreadClient.resume(_ => {
finishClient(gClient);
gClient.close(gCallback);
});
});

View File

@ -9,23 +9,32 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-grips");
run_test_with_server(DebuggerServer, function () {
run_test_with_server(WorkerDebuggerServer, do_test_finished);
});
do_test_pending();
};
function run_test_with_server(aServer, aCallback)
{
gCallback = aCallback;
initTestDebuggerServer(aServer);
gDebuggee = addTestGlobal("test-grips", aServer);
gDebuggee.eval(function stopMe(arg1, arg2) {
debugger;
}.toString());
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function() {
attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_object_grip();
});
});
do_test_pending();
}
function test_object_grip()
@ -44,7 +53,7 @@ function test_object_grip()
do_check_false(obj2Client.isSealed);
gThreadClient.resume(_ => {
finishClient(gClient);
gClient.close(gCallback);
});
});

Some files were not shown because too many files have changed in this diff Show More