Merge fx-team to m-c a=merge

This commit is contained in:
Wes Kocher 2014-06-10 18:47:36 -07:00
commit 1ce168a40f
79 changed files with 931 additions and 1210 deletions

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

@ -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

@ -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);
});
});

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, arg3, arg4) {
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()
@ -48,7 +57,7 @@ function test_object_grip()
do_check_true(eClient.isExtensible);
gThreadClient.resume(_ => {
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()
@ -53,7 +62,7 @@ function test_object_grip()
do_check_eq(aResponse.ownProperties.d.value.type, "-0");
gThreadClient.resume(function() {
finishClient(gClient);
gClient.close(gCallback);
});
});
});

View File

@ -8,23 +8,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()
@ -54,7 +63,7 @@ function test_object_grip()
do_check_true(obj2.prototype != undefined);
gThreadClient.resume(function() {
finishClient(gClient);
gClient.close(gCallback);
});
});

View File

@ -8,19 +8,28 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
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-stack", aServer);
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_simple_stepping();
});
});
do_test_pending();
}
function test_simple_stepping()
@ -55,7 +64,7 @@ function test_simple_stepping()
do_check_eq(gDebuggee.b, 2);
gThreadClient.resume(function () {
finishClient(gClient);
gClient.close(gCallback);
});
});
gThreadClient.stepOver();

View File

@ -8,19 +8,28 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
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-stack", aServer);
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_simple_stepping();
});
});
do_test_pending();
}
function test_simple_stepping()
@ -55,7 +64,7 @@ function test_simple_stepping()
do_check_eq(gDebuggee.b, 2);
gThreadClient.resume(function () {
finishClient(gClient);
gClient.close(gCallback);
});
});
gThreadClient.stepIn();

View File

@ -8,19 +8,28 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
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-stack", aServer);
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_simple_stepping();
});
});
do_test_pending();
}
function test_simple_stepping()
@ -36,7 +45,7 @@ function test_simple_stepping()
do_check_eq(gDebuggee.b, 2);
gThreadClient.resume(function () {
finishClient(gClient);
gClient.close(gCallback);
});
});
gThreadClient.stepOut();

View File

@ -8,19 +8,28 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
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-stack", aServer);
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_simple_stepping();
});
});
do_test_pending();
}
function test_simple_stepping()
@ -45,7 +54,7 @@ function test_simple_stepping()
do_check_eq(gDebuggee.b, undefined);
gThreadClient.resume(function () {
finishClient(gClient);
gClient.close(gCallback);
});
});
gThreadClient.stepOver();

View File

@ -10,19 +10,28 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
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-stack", aServer);
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
test_stepping_last();
});
});
do_test_pending();
}
function test_stepping_last()
@ -84,7 +93,7 @@ function test_next_pause()
do_check_eq(aPacket.why.type, "debuggerStatement");
gThreadClient.resume(function () {
finishClient(gClient);
gClient.close(gCallback);
});
});

View File

@ -8,12 +8,22 @@
var gDebuggee;
var gClient;
var gThreadClient;
var gCallback;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-stack");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
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-stack", aServer);
gClient = new DebuggerClient(aServer.connectPipe());
gClient.connect(function () {
attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
gThreadClient = aThreadClient;
@ -25,7 +35,6 @@ function run_test()
}, Ci.nsIThread.DISPATCH_NORMAL);
});
});
do_test_pending();
}
function test_simple_stepping()
@ -55,7 +64,7 @@ function test_simple_stepping()
do_check_eq(aPacket.why.frameFinished.throw, "ah");
gThreadClient.resume(function () {
finishClient(gClient);
gClient.close(gCallback);
});
});
gThreadClient.stepOut();

View File

@ -260,8 +260,7 @@ DebuggerTransport.prototype = {
try {
this._currentOutgoing.write(stream);
} catch(e if e.result == Cr.NS_BASE_STREAM_CLOSED ||
e.result == Cr.NS_ERROR_NET_RESET) {
} catch(e if e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
this.close(e.result);
return;
}
@ -338,9 +337,7 @@ DebuggerTransport.prototype = {
while(stream.available() && this._incomingEnabled &&
this._processIncoming(stream, stream.available())) {}
this._waitForIncoming();
} catch(e if e.result == Cr.NS_BASE_STREAM_CLOSED ||
e.result == Cr.NS_ERROR_CONNECTION_REFUSED ||
e.result == Cr.NS_ERROR_OFFLINE) {
} catch(e if e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
this.close(e.result);
}
}, "DebuggerTransport.prototype.onInputStreamReady"),

View File

@ -94,18 +94,6 @@ function createModule(id) {
});
};
// A whitelist of modules from which the built-in chrome module may be
// required. The idea is add all modules that depend on chrome to the whitelist
// initially, and then remove them one by one, fixing any errors as we go along.
// Once the whitelist is empty, we can remove the built-in chrome module from
// the loader entirely.
//
// TODO: Remove this when the whitelist becomes empty
let chromeWhitelist = [
"devtools/toolkit/DevToolsUtils",
"devtools/toolkit/event-emitter",
];
// Create a CommonJS loader with the following options:
// - createSandbox:
// A function that will be used to create sandboxes. It takes the name and
@ -188,17 +176,6 @@ function WorkerDebuggerLoader(options) {
throw new Error("can't require module without id!");
}
// If the module to be required is the built-in chrome module, and the
// requirer is not in the whitelist, return a vacuous object as if the
// module was unavailable.
//
// TODO: Remove this when the whitelist becomes empty
if (id === "chrome" && chromeWhitelist.indexOf(requirer.id) < 0) {
return { CC: undefined, Cc: undefined,
ChromeWorker: undefined, Cm: undefined, Ci: undefined, Cu: undefined,
Cr: undefined, components: undefined };
}
// Built-in modules are cached by id rather than URL, so try to find the
// module to be required by id first.
let module = modules[id];
@ -332,9 +309,9 @@ if (typeof Components === "object") {
let SourceMap = {};
Cu.import("resource://gre/modules/devtools/SourceMap.jsm", SourceMap);
const Timer = Cu.import("resource://gre/modules/Timer.jsm", {});
const chrome = { CC: Function.bind.call(CC, Components), Cc: Cc,
ChromeWorker: ChromeWorker, Cm: Cm, Ci: Ci, Cu: Cu,
Cr: Cr, components: Components };
const chrome = { CC: undefined, Cc: undefined, ChromeWorker: undefined,
Cm: undefined, Ci: undefined, Cu: undefined,
Cr: undefined, components: undefined };
const xpcInspector = Cc["@mozilla.org/jsinspector;1"].
getService(Ci.nsIJSInspector);

View File

@ -917,58 +917,55 @@ let Links = {
return;
let { sortedLinks, linkMap } = links;
// Nothing to do if the list is full and the link isn't in it and shouldn't
// be in it.
if (!linkMap.has(aLink.url) &&
sortedLinks.length &&
sortedLinks.length == aProvider.maxNumLinks) {
let lastLink = sortedLinks[sortedLinks.length - 1];
if (this.compareLinks(lastLink, aLink) < 0)
return;
}
let existingLink = linkMap.get(aLink.url);
let insertionLink = null;
let updatePages = false;
// Update the title in O(1).
if ("title" in aLink) {
let link = linkMap.get(aLink.url);
if (link && link.title != aLink.title) {
link.title = aLink.title;
if (existingLink) {
// Update our copy's position in O(lg n) by first removing it from its
// list. It's important to do this before modifying its properties.
if (this._sortProperties.some(prop => prop in aLink)) {
let idx = this._indexOf(sortedLinks, existingLink);
if (idx < 0) {
throw new Error("Link should be in _sortedLinks if in _linkMap");
}
sortedLinks.splice(idx, 1);
// Update our copy's properties.
for (let prop of this._sortProperties) {
if (prop in aLink) {
existingLink[prop] = aLink[prop];
}
}
// Finally, reinsert our copy below.
insertionLink = existingLink;
}
// Update our copy's title in O(1).
if ("title" in aLink && aLink.title != existingLink.title) {
existingLink.title = aLink.title;
updatePages = true;
}
}
else if (this._sortProperties.every(prop => prop in aLink)) {
// Before doing the O(lg n) insertion below, do an O(1) check for the
// common case where the new link is too low-ranked to be in the list.
if (sortedLinks.length && sortedLinks.length == aProvider.maxNumLinks) {
let lastLink = sortedLinks[sortedLinks.length - 1];
if (this.compareLinks(lastLink, aLink) < 0) {
return;
}
}
// Copy the link object so that changes later made to it by the caller
// don't affect our copy.
insertionLink = {};
for (let prop in aLink) {
insertionLink[prop] = aLink[prop];
}
linkMap.set(aLink.url, insertionLink);
}
// Update the link's position in O(lg n).
if (this._sortProperties.some((prop) => prop in aLink)) {
let link = linkMap.get(aLink.url);
if (link) {
// The link is already in the list.
let idx = this._indexOf(sortedLinks, link);
if (idx < 0)
throw new Error("Link should be in _sortedLinks if in _linkMap");
sortedLinks.splice(idx, 1);
for (let prop of this._sortProperties) {
if (prop in aLink)
link[prop] = aLink[prop];
}
}
else {
// The link is new.
for (let prop of this._sortProperties) {
if (!(prop in aLink))
throw new Error("New link missing required sort property: " + prop);
}
// Copy the link object so that if the caller changes it, it doesn't
// screw up our bookkeeping.
link = {};
for (let [prop, val] of Iterator(aLink)) {
link[prop] = val;
}
linkMap.set(link.url, link);
}
let idx = this._insertionIndexOf(sortedLinks, link);
sortedLinks.splice(idx, 0, link);
if (insertionLink) {
let idx = this._insertionIndexOf(sortedLinks, insertionLink);
sortedLinks.splice(idx, 0, insertionLink);
if (sortedLinks.length > aProvider.maxNumLinks) {
let lastLink = sortedLinks.pop();
linkMap.delete(lastLink.url);

View File

@ -51,12 +51,7 @@ add_test(function changeLinks() {
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
// Notify of a new link.
let newLink = {
url: "http://example.com/19",
title: "My frecency is 19",
frecency: 19,
lastVisitDate: 0,
};
let newLink = makeLink(19);
expectedLinks.splice(1, 0, newLink);
provider.notifyLinkChanged(newLink);
do_check_links(NewTabUtils.links.getLinks(), expectedLinks);
@ -81,11 +76,7 @@ add_test(function changeLinks() {
// Notify of a new link again, but this time make it overflow maxNumLinks.
provider.maxNumLinks = expectedLinks.length;
newLink = {
url: "http://example.com/21",
frecency: 21,
lastVisitDate: 0,
};
newLink = makeLink(21);
expectedLinks.unshift(newLink);
expectedLinks.pop();
do_check_eq(expectedLinks.length, provider.maxNumLinks); // Sanity check.
@ -125,6 +116,34 @@ add_task(function oneProviderAlreadyCached() {
NewTabUtils.links.removeProvider(provider2);
});
add_task(function newLowRankedLink() {
// Init a provider with 10 links and make its maximum number also 10.
let links = makeLinks(0, 10, 1);
let provider = new TestProvider(done => done(links));
provider.maxNumLinks = links.length;
NewTabUtils.initWithoutProviders();
NewTabUtils.links.addProvider(provider);
// This is sync since the provider's getLinks is sync.
NewTabUtils.links.populateCache(function () {}, false);
do_check_links(NewTabUtils.links.getLinks(), links);
// Notify of a new link that's low-ranked enough not to make the list.
let newLink = makeLink(0);
provider.notifyLinkChanged(newLink);
do_check_links(NewTabUtils.links.getLinks(), links);
// Notify about the new link's title change.
provider.notifyLinkChanged({
url: newLink.url,
title: "a new title",
});
do_check_links(NewTabUtils.links.getLinks(), links);
NewTabUtils.links.removeProvider(provider);
});
function TestProvider(getLinksFn) {
this.getLinks = getLinksFn;
this._observers = new Set();
@ -165,12 +184,16 @@ function makeLinks(frecRangeStart, frecRangeEnd, step) {
let links = [];
// Remember, links are ordered by frecency descending.
for (let i = frecRangeEnd; i > frecRangeStart; i -= step) {
links.push({
url: "http://example.com/" + i,
title: "My frecency is " + i,
frecency: i,
lastVisitDate: 0,
});
links.push(makeLink(i));
}
return links;
}
function makeLink(frecency) {
return {
url: "http://example.com/" + frecency,
title: "My frecency is " + frecency,
frecency: frecency,
lastVisitDate: 0,
};
}