mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to inbound
This commit is contained in:
commit
04c6720e07
@ -12,7 +12,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
|
||||
|
@ -11,7 +11,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "5b157e823d42532a365d33cfe3a8bd9ba4b71152",
|
||||
"revision": "2741c63d7513126ea0a7f033266a4230334dfc53",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -11,7 +11,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="3d5c964015967ca8c86abe6dbbebee3cb82b1609"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a314508e397c8f1814228d36259ea8708034444e"/>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6894b915ee7d031a1f0ed16c422c48f293de1b84"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="7c320aa3291761196ccd6ed3131faffb255f3fb6"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="15e8982284c4560f9c74c2b9fe8bb361ebfe0cb6"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
|
@ -27,10 +27,6 @@ let gGestureSupport = {
|
||||
* True to add/init listeners and false to remove/uninit
|
||||
*/
|
||||
init: function GS_init(aAddListener) {
|
||||
// Bug 863514 - Make gesture support work in electrolysis
|
||||
if (gMultiProcessBrowser)
|
||||
return;
|
||||
|
||||
const gestureEvents = ["SwipeGestureStart",
|
||||
"SwipeGestureUpdate", "SwipeGestureEnd", "SwipeGesture",
|
||||
"MagnifyGestureStart", "MagnifyGestureUpdate", "MagnifyGesture",
|
||||
|
@ -57,7 +57,7 @@ let gDrop = {
|
||||
this._cancelDelayedArrange();
|
||||
|
||||
// Update the grid and move all sites to their new places.
|
||||
gUpdater.updateGrid();
|
||||
gUpdater.updateGrid(gDrag.draggedSite);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -145,6 +145,6 @@ let gDrop = {
|
||||
if (aCell)
|
||||
sites = gDropPreview.rearrange(aCell);
|
||||
|
||||
gTransformation.rearrangeSites(sites, {unfreeze: !aCell});
|
||||
gTransformation.rearrangeSites(sites, gDrag.draggedSite, {unfreeze: !aCell});
|
||||
}
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/NewTabUtils.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Rect",
|
||||
"resource://gre/modules/Geometry.jsm");
|
||||
|
@ -60,7 +60,7 @@ Site.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Unpins the site and calls the given callback when done.
|
||||
* Unpins the site.
|
||||
*/
|
||||
unpin: function Site_unpin() {
|
||||
if (this.isPinned()) {
|
||||
@ -79,8 +79,7 @@ Site.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Blocks the site (removes it from the grid) and calls the given callback
|
||||
* when done.
|
||||
* Blocks the site (removes it from the grid).
|
||||
*/
|
||||
block: function Site_block() {
|
||||
if (!gBlockedLinks.isBlocked(this._link)) {
|
||||
|
@ -38,46 +38,24 @@ let gTransformation = {
|
||||
return new Rect(left + scrollX, top + scrollY, width, height);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given node from zero to full opacity.
|
||||
* @param aNode The node to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
fadeNodeIn: function Transformation_fadeNodeIn(aNode, aCallback) {
|
||||
this._setNodeOpacity(aNode, 1, function () {
|
||||
// Clear the style property.
|
||||
aNode.style.opacity = "";
|
||||
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given node from full to zero opacity.
|
||||
* @param aNode The node to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
fadeNodeOut: function Transformation_fadeNodeOut(aNode, aCallback) {
|
||||
this._setNodeOpacity(aNode, 0, aCallback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given site from zero to full opacity.
|
||||
* @param aSite The site to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
showSite: function Transformation_showSite(aSite, aCallback) {
|
||||
this.fadeNodeIn(aSite.node, aCallback);
|
||||
showSite: function (aSite) {
|
||||
let node = aSite.node;
|
||||
return this._setNodeOpacity(node, 1).then(() => {
|
||||
// Clear the style property.
|
||||
node.style.opacity = "";
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades a given site from full to zero opacity.
|
||||
* @param aSite The site to fade.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
hideSite: function Transformation_hideSite(aSite, aCallback) {
|
||||
this.fadeNodeOut(aSite.node, aCallback);
|
||||
hideSite: function (aSite) {
|
||||
return this._setNodeOpacity(aSite.node, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -129,22 +107,11 @@ let gTransformation = {
|
||||
* @param aTarget The slide target.
|
||||
* @param aOptions Set of options (see below).
|
||||
* unfreeze - unfreeze the site after sliding
|
||||
* callback - the callback to call when finished
|
||||
*/
|
||||
slideSiteTo: function Transformation_slideSiteTo(aSite, aTarget, aOptions) {
|
||||
slideSiteTo: function (aSite, aTarget, aOptions) {
|
||||
let currentPosition = this.getNodePosition(aSite.node);
|
||||
let targetPosition = this.getNodePosition(aTarget.node)
|
||||
let callback = aOptions && aOptions.callback;
|
||||
|
||||
let self = this;
|
||||
|
||||
function finish() {
|
||||
if (aOptions && aOptions.unfreeze)
|
||||
self.unfreezeSitePosition(aSite);
|
||||
|
||||
if (callback)
|
||||
callback();
|
||||
}
|
||||
let promise;
|
||||
|
||||
// We need to take the width of a cell's border into account.
|
||||
targetPosition.left += this._cellBorderWidths.left;
|
||||
@ -153,68 +120,70 @@ let gTransformation = {
|
||||
// Nothing to do here if the positions already match.
|
||||
if (currentPosition.left == targetPosition.left &&
|
||||
currentPosition.top == targetPosition.top) {
|
||||
finish();
|
||||
promise = Promise.resolve();
|
||||
} else {
|
||||
this.setSitePosition(aSite, targetPosition);
|
||||
this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
|
||||
promise = this._whenTransitionEnded(aSite.node, ["left", "top"]);
|
||||
}
|
||||
|
||||
if (aOptions && aOptions.unfreeze) {
|
||||
promise = promise.then(() => this.unfreezeSitePosition(aSite));
|
||||
}
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges a given array of sites and moves them to their new positions or
|
||||
* fades in/out new/removed sites.
|
||||
* @param aSites An array of sites to rearrange.
|
||||
* @param aDraggedSite The currently dragged site, may be null.
|
||||
* @param aOptions Set of options (see below).
|
||||
* unfreeze - unfreeze the site after rearranging
|
||||
* callback - the callback to call when finished
|
||||
*/
|
||||
rearrangeSites: function Transformation_rearrangeSites(aSites, aOptions) {
|
||||
let batch = [];
|
||||
rearrangeSites: function (aSites, aDraggedSite, aOptions) {
|
||||
let self = this;
|
||||
let cells = gGrid.cells;
|
||||
let callback = aOptions && aOptions.callback;
|
||||
let unfreeze = aOptions && aOptions.unfreeze;
|
||||
|
||||
aSites.forEach(function (aSite, aIndex) {
|
||||
// Do not re-arrange empty cells or the dragged site.
|
||||
if (!aSite || aSite == gDrag.draggedSite)
|
||||
return;
|
||||
function* promises() {
|
||||
let index = 0;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
batch.push(deferred.promise);
|
||||
let cb = function () deferred.resolve();
|
||||
for (let site of aSites) {
|
||||
if (site && site !== aDraggedSite) {
|
||||
if (!cells[index]) {
|
||||
// The site disappeared from the grid, hide it.
|
||||
yield self.hideSite(site);
|
||||
} else if (self._getNodeOpacity(site.node) != 1) {
|
||||
// The site disappeared before but is now back, show it.
|
||||
yield self.showSite(site);
|
||||
} else {
|
||||
// The site's position has changed, move it around.
|
||||
yield self._moveSite(site, index, {unfreeze: unfreeze});
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cells[aIndex])
|
||||
// The site disappeared from the grid, hide it.
|
||||
this.hideSite(aSite, cb);
|
||||
else if (this._getNodeOpacity(aSite.node) != 1)
|
||||
// The site disappeared before but is now back, show it.
|
||||
this.showSite(aSite, cb);
|
||||
else
|
||||
// The site's position has changed, move it around.
|
||||
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
|
||||
}, this);
|
||||
|
||||
let wait = Promise.promised(function () callback && callback());
|
||||
wait.apply(null, batch);
|
||||
return Promise.all([p for (p of promises())]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listens for the 'transitionend' event on a given node and calls the given
|
||||
* callback.
|
||||
* Listens for the 'transitionend' event on a given node.
|
||||
* @param aNode The node that is transitioned.
|
||||
* @param aProperties The properties we'll wait to be transitioned.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_whenTransitionEnded:
|
||||
function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
|
||||
|
||||
_whenTransitionEnded: function (aNode, aProperties) {
|
||||
let deferred = Promise.defer();
|
||||
let props = new Set(aProperties);
|
||||
aNode.addEventListener("transitionend", function onEnd(e) {
|
||||
if (props.has(e.propertyName)) {
|
||||
aNode.removeEventListener("transitionend", onEnd);
|
||||
aCallback();
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -231,21 +200,14 @@ let gTransformation = {
|
||||
* Sets a given node's opacity.
|
||||
* @param aNode The node to set the opacity value for.
|
||||
* @param aOpacity The opacity value to set.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_setNodeOpacity:
|
||||
function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) {
|
||||
|
||||
_setNodeOpacity: function (aNode, aOpacity) {
|
||||
if (this._getNodeOpacity(aNode) == aOpacity) {
|
||||
if (aCallback)
|
||||
aCallback();
|
||||
} else {
|
||||
if (aCallback) {
|
||||
this._whenTransitionEnded(aNode, ["opacity"], aCallback);
|
||||
}
|
||||
|
||||
aNode.style.opacity = aOpacity;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
aNode.style.opacity = aOpacity;
|
||||
return this._whenTransitionEnded(aNode, ["opacity"]);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -254,9 +216,9 @@ let gTransformation = {
|
||||
* @param aIndex The target cell's index.
|
||||
* @param aOptions Options that are directly passed to slideSiteTo().
|
||||
*/
|
||||
_moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) {
|
||||
_moveSite: function (aSite, aIndex, aOptions) {
|
||||
this.freezeSitePosition(aSite);
|
||||
this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
|
||||
return this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -12,32 +12,30 @@ let gUpdater = {
|
||||
/**
|
||||
* Updates the current grid according to its pinned and blocked sites.
|
||||
* This removes old, moves existing and creates new sites to fill gaps.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
updateGrid: function Updater_updateGrid(aCallback) {
|
||||
updateGrid: function Updater_updateGrid(draggedSite = null) {
|
||||
let links = gLinks.getLinks().slice(0, gGrid.cells.length);
|
||||
|
||||
// Find all sites that remain in the grid.
|
||||
let sites = this._findRemainingSites(links);
|
||||
|
||||
let self = this;
|
||||
|
||||
// Remove sites that are no longer in the grid.
|
||||
this._removeLegacySites(sites, function () {
|
||||
this._removeLegacySites(sites).then(() => {
|
||||
// Freeze all site positions so that we can move their DOM nodes around
|
||||
// without any visual impact.
|
||||
self._freezeSitePositions(sites);
|
||||
this._freezeSitePositions(sites);
|
||||
|
||||
// Move the sites' DOM nodes to their new position in the DOM. This will
|
||||
// have no visual effect as all the sites have been frozen and will
|
||||
// remain in their current position.
|
||||
self._moveSiteNodes(sites);
|
||||
this._moveSiteNodes(sites);
|
||||
|
||||
// Now it's time to animate the sites actually moving to their new
|
||||
// positions.
|
||||
self._rearrangeSites(sites, function () {
|
||||
let opts = {unfreeze: true};
|
||||
gTransformation.rearrangeSites(sites, draggedSite, opts).then(() => {
|
||||
// Try to fill empty cells and finish.
|
||||
self._fillEmptyCells(links, aCallback);
|
||||
this._fillEmptyCells(links);
|
||||
|
||||
// Update other pages that might be open to keep them synced.
|
||||
gAllPages.update(gPage);
|
||||
@ -109,78 +107,52 @@ let gUpdater = {
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearranges the given sites and slides them to their new positions.
|
||||
* @param aSites The array of sites to re-arrange.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_rearrangeSites: function Updater_rearrangeSites(aSites, aCallback) {
|
||||
let options = {callback: aCallback, unfreeze: true};
|
||||
gTransformation.rearrangeSites(aSites, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all sites from the grid that are not in the given links array or
|
||||
* exceed the grid.
|
||||
* @param aSites The array of sites remaining in the grid.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_removeLegacySites: function Updater_removeLegacySites(aSites, aCallback) {
|
||||
let batch = [];
|
||||
_removeLegacySites: function (aSites) {
|
||||
let remainingSites = new Set(aSites);
|
||||
|
||||
// Delete sites that were removed from the grid.
|
||||
gGrid.sites.forEach(function (aSite) {
|
||||
// The site must be valid and not in the current grid.
|
||||
if (!aSite || aSites.indexOf(aSite) != -1)
|
||||
return;
|
||||
function* promises() {
|
||||
for (let site of gGrid.sites) {
|
||||
// The site must be valid and not in the current grid.
|
||||
if (site && !remainingSites.has(site)) {
|
||||
// Hide the site and remove it from the DOM.
|
||||
let remove = site.node.remove.bind(site.node);
|
||||
yield gTransformation.hideSite(site).then(remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
batch.push(deferred.promise);
|
||||
|
||||
// Fade out the to-be-removed site.
|
||||
gTransformation.hideSite(aSite, function () {
|
||||
let node = aSite.node;
|
||||
|
||||
// Remove the site from the DOM.
|
||||
node.parentNode.removeChild(node);
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
let wait = Promise.promised(aCallback);
|
||||
wait.apply(null, batch);
|
||||
return Promise.all([p for (p of promises())]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to fill empty cells with new links if available.
|
||||
* @param aLinks The array of links.
|
||||
* @param aCallback The callback to call when finished.
|
||||
*/
|
||||
_fillEmptyCells: function Updater_fillEmptyCells(aLinks, aCallback) {
|
||||
_fillEmptyCells: function (aLinks) {
|
||||
let {cells, sites} = gGrid;
|
||||
let batch = [];
|
||||
let index = 0;
|
||||
|
||||
// Find empty cells and fill them.
|
||||
sites.forEach(function (aSite, aIndex) {
|
||||
if (aSite || !aLinks[aIndex])
|
||||
return;
|
||||
for (let site of sites) {
|
||||
if (!site && aLinks[index]) {
|
||||
// Create the new site and fade it in.
|
||||
site = gGrid.createSite(aLinks[index], cells[index]);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
batch.push(deferred.promise);
|
||||
// Set the site's initial opacity to zero.
|
||||
site.node.style.opacity = 0;
|
||||
|
||||
// Create the new site and fade it in.
|
||||
let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
|
||||
// Flush all style changes for the dynamically inserted site to make
|
||||
// the fade-in transition work.
|
||||
window.getComputedStyle(site.node).opacity;
|
||||
gTransformation.showSite(site);
|
||||
}
|
||||
|
||||
// Set the site's initial opacity to zero.
|
||||
site.node.style.opacity = 0;
|
||||
|
||||
// Flush all style changes for the dynamically inserted site to make
|
||||
// the fade-in transition work.
|
||||
window.getComputedStyle(site.node).opacity;
|
||||
gTransformation.showSite(site, function () deferred.resolve());
|
||||
});
|
||||
|
||||
let wait = Promise.promised(aCallback);
|
||||
wait.apply(null, batch);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -532,11 +532,14 @@
|
||||
this.evictNode(node);
|
||||
this._isModifying = false;
|
||||
this._updateMigratedSet();
|
||||
// We will now have moved stuff around; kick off an aftercustomization event
|
||||
// We will now have moved stuff around; kick off some events
|
||||
// so add-ons know we've just moved their stuff:
|
||||
if (window.gCustomizeMode) {
|
||||
window.gCustomizeMode.dispatchToolboxEvent("aftercustomization");
|
||||
}
|
||||
// XXXgijs: only in this window. It's hard to know for sure what's the right
|
||||
// thing to do here - typically insertItem is used on each window, so
|
||||
// this seems to make the most sense, even if some of the effects of
|
||||
// evictNode might affect multiple windows.
|
||||
CustomizableUI.dispatchToolboxEvent("customizationchange", {}, window);
|
||||
CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
|
||||
return node;
|
||||
]]></body>
|
||||
</method>
|
||||
|
@ -1758,6 +1758,24 @@ let CustomizableUIInternal = {
|
||||
}
|
||||
},
|
||||
|
||||
_dispatchToolboxEventToWindow: function(aEventType, aDetails, aWindow) {
|
||||
let evt = new aWindow.CustomEvent(aEventType, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: aDetails
|
||||
});
|
||||
aWindow.gNavToolbox.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
dispatchToolboxEvent: function(aEventType, aDetails={}, aWindow=null) {
|
||||
if (aWindow) {
|
||||
return this._dispatchToolboxEventToWindow(aEventType, aDetails, aWindow);
|
||||
}
|
||||
for (let [win, ] of gBuildWindows) {
|
||||
this._dispatchToolboxEventToWindow(aEventType, aDetails, win);
|
||||
}
|
||||
},
|
||||
|
||||
createWidget: function(aProperties) {
|
||||
let widget = this.normalizeWidget(aProperties, CustomizableUI.SOURCE_EXTERNAL);
|
||||
//XXXunf This should probably throw.
|
||||
@ -3080,6 +3098,19 @@ this.CustomizableUI = {
|
||||
notifyEndCustomizing: function(aWindow) {
|
||||
CustomizableUIInternal.notifyListeners("onCustomizeEnd", aWindow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify toolbox(es) of a particular event. If you don't pass aWindow,
|
||||
* all toolboxes will be notified. For use from Customize Mode only,
|
||||
* do not use otherwise.
|
||||
* @param aEvent the name of the event to send.
|
||||
* @param aDetails optional, the details of the event.
|
||||
* @param aWindow optional, the window in which to send the event.
|
||||
*/
|
||||
dispatchToolboxEvent: function(aEvent, aDetails={}, aWindow=null) {
|
||||
CustomizableUIInternal.dispatchToolboxEvent(aEvent, aDetails, aWindow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether an area is overflowable.
|
||||
*
|
||||
|
@ -170,7 +170,7 @@ CustomizeMode.prototype = {
|
||||
if (this.document.documentElement._lightweightTheme)
|
||||
this.document.documentElement._lightweightTheme.disable();
|
||||
|
||||
this.dispatchToolboxEvent("beforecustomization");
|
||||
CustomizableUI.dispatchToolboxEvent("beforecustomization", {}, window);
|
||||
CustomizableUI.notifyStartCustomizing(this.window);
|
||||
|
||||
// Add a keypress listener to the document so that we can quickly exit
|
||||
@ -220,7 +220,7 @@ CustomizeMode.prototype = {
|
||||
yield this._doTransition(true);
|
||||
|
||||
// Let everybody in this window know that we're about to customize.
|
||||
this.dispatchToolboxEvent("customizationstarting");
|
||||
CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window);
|
||||
|
||||
this._mainViewContext = mainView.getAttribute("context");
|
||||
if (this._mainViewContext) {
|
||||
@ -266,7 +266,7 @@ CustomizeMode.prototype = {
|
||||
this._handler.isEnteringCustomizeMode = false;
|
||||
panelContents.removeAttribute("customize-transitioning");
|
||||
|
||||
this.dispatchToolboxEvent("customizationready");
|
||||
CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
|
||||
if (!this._wantToBeInCustomizeMode) {
|
||||
this.exit();
|
||||
}
|
||||
@ -359,7 +359,7 @@ CustomizeMode.prototype = {
|
||||
|
||||
// Let everybody in this window know that we're starting to
|
||||
// exit customization mode.
|
||||
this.dispatchToolboxEvent("customizationending");
|
||||
CustomizableUI.dispatchToolboxEvent("customizationending", {}, window);
|
||||
|
||||
window.PanelUI.setMainView(window.PanelUI.mainView);
|
||||
window.PanelUI.menuButton.disabled = false;
|
||||
@ -424,8 +424,8 @@ CustomizeMode.prototype = {
|
||||
this._changed = false;
|
||||
this._transitioning = false;
|
||||
this._handler.isExitingCustomizeMode = false;
|
||||
this.dispatchToolboxEvent("aftercustomization");
|
||||
CustomizableUI.notifyEndCustomizing(this.window);
|
||||
CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
|
||||
CustomizableUI.notifyEndCustomizing(window);
|
||||
|
||||
if (this._wantToBeInCustomizeMode) {
|
||||
this.enter();
|
||||
@ -477,7 +477,7 @@ CustomizeMode.prototype = {
|
||||
this.document.documentElement.setAttribute("customize-entered", true);
|
||||
this.document.documentElement.removeAttribute("customize-entering");
|
||||
}
|
||||
this.dispatchToolboxEvent("customization-transitionend", aEntering);
|
||||
CustomizableUI.dispatchToolboxEvent("customization-transitionend", aEntering, this.window);
|
||||
|
||||
deferred.resolve();
|
||||
}.bind(this), 0);
|
||||
@ -500,12 +500,6 @@ CustomizeMode.prototype = {
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
dispatchToolboxEvent: function(aEventType, aDetails={}) {
|
||||
let evt = this.document.createEvent("CustomEvent");
|
||||
evt.initCustomEvent(aEventType, true, true, {changed: this._changed});
|
||||
let result = this.window.gNavToolbox.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_getCustomizableChildForNode: function(aNode) {
|
||||
// NB: adjusted from _getCustomizableParent to keep that method fast
|
||||
// (it's used during drags), and avoid multiple DOM loops
|
||||
@ -544,7 +538,7 @@ CustomizeMode.prototype = {
|
||||
}
|
||||
CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_NAVBAR);
|
||||
if (!this._customizing) {
|
||||
this.dispatchToolboxEvent("customizationchange");
|
||||
CustomizableUI.dispatchToolboxEvent("customizationchange");
|
||||
}
|
||||
},
|
||||
|
||||
@ -555,7 +549,7 @@ CustomizeMode.prototype = {
|
||||
}
|
||||
CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_PANEL);
|
||||
if (!this._customizing) {
|
||||
this.dispatchToolboxEvent("customizationchange");
|
||||
CustomizableUI.dispatchToolboxEvent("customizationchange");
|
||||
}
|
||||
},
|
||||
|
||||
@ -566,7 +560,7 @@ CustomizeMode.prototype = {
|
||||
}
|
||||
CustomizableUI.removeWidgetFromArea(aNode.id);
|
||||
if (!this._customizing) {
|
||||
this.dispatchToolboxEvent("customizationchange");
|
||||
CustomizableUI.dispatchToolboxEvent("customizationchange");
|
||||
}
|
||||
},
|
||||
|
||||
@ -1017,7 +1011,7 @@ CustomizeMode.prototype = {
|
||||
this._updateUndoResetButton();
|
||||
this._updateEmptyPaletteNotice();
|
||||
}
|
||||
this.dispatchToolboxEvent("customizationchange");
|
||||
CustomizableUI.dispatchToolboxEvent("customizationchange");
|
||||
},
|
||||
|
||||
_updateEmptyPaletteNotice: function() {
|
||||
|
@ -68,4 +68,5 @@ skip-if = os == "linux"
|
||||
[browser_969427_recreate_destroyed_widget_after_reset.js]
|
||||
[browser_969661_character_encoding_navbar_disabled.js]
|
||||
[browser_970511_undo_restore_default.js]
|
||||
[browser_972267_customizationchange_events.js]
|
||||
[browser_panel_toggle.js]
|
||||
|
@ -0,0 +1,47 @@
|
||||
/* 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";
|
||||
|
||||
// Create a new window, then move the home button to the menu and check both windows have
|
||||
// customizationchange events fire on the toolbox:
|
||||
add_task(function() {
|
||||
let newWindow = yield openAndLoadWindow();
|
||||
let otherToolbox = newWindow.gNavToolbox;
|
||||
|
||||
let handlerCalledCount = 0;
|
||||
let handler = (ev) => {
|
||||
handlerCalledCount++;
|
||||
};
|
||||
|
||||
let homeButton = document.getElementById("home-button");
|
||||
|
||||
gNavToolbox.addEventListener("customizationchange", handler);
|
||||
otherToolbox.addEventListener("customizationchange", handler);
|
||||
|
||||
gCustomizeMode.addToPanel(homeButton);
|
||||
|
||||
is(handlerCalledCount, 2, "Should be called for both windows.");
|
||||
|
||||
// If the test is run in isolation and the panel has never been open,
|
||||
// the button will be in the palette. Deal with this case:
|
||||
if (homeButton.parentNode.id == "BrowserToolbarPalette") {
|
||||
yield PanelUI.ensureReady();
|
||||
isnot(homeButton.parentNode.id, "BrowserToolbarPalette", "Home button should now be in panel");
|
||||
}
|
||||
|
||||
handlerCalledCount = 0;
|
||||
gCustomizeMode.addToToolbar(homeButton);
|
||||
is(handlerCalledCount, 2, "Should be called for both windows.");
|
||||
|
||||
gNavToolbox.removeEventListener("customizationchange", handler);
|
||||
otherToolbox.removeEventListener("customizationchange", handler);
|
||||
|
||||
newWindow.close();
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
yield resetCustomization();
|
||||
});
|
||||
|
@ -185,7 +185,7 @@ let SessionHistoryInternal = {
|
||||
entry.isSrcdocEntry = shEntry.isSrcdocEntry;
|
||||
|
||||
if (shEntry.baseURI)
|
||||
entry.baseURI = shEntry.baseURI;
|
||||
entry.baseURI = shEntry.baseURI.spec;
|
||||
|
||||
if (shEntry.contentType)
|
||||
entry.contentType = shEntry.contentType;
|
||||
@ -332,7 +332,7 @@ let SessionHistoryInternal = {
|
||||
if (entry.isSrcdocEntry)
|
||||
shEntry.srcdocData = entry.srcdocData;
|
||||
if (entry.baseURI)
|
||||
shEntry.baseURI = entry.baseURI;
|
||||
shEntry.baseURI = Utils.makeURI(entry.baseURI);
|
||||
|
||||
if (entry.cacheKey) {
|
||||
var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
|
||||
|
@ -101,6 +101,7 @@ const WebProgress = {
|
||||
if (aTab == Browser.selectedTab) {
|
||||
BrowserUI.updateURI();
|
||||
BrowserUI.update();
|
||||
BrowserUI.updateStartURIAttributes(aJson.location);
|
||||
}
|
||||
|
||||
let locationHasChanged = (location != aTab.browser.lastLocation);
|
||||
|
@ -262,7 +262,6 @@ var BrowserUI = {
|
||||
},
|
||||
|
||||
showContent: function showContent(aURI) {
|
||||
this.updateStartURIAttributes(aURI);
|
||||
ContextUI.dismissTabs();
|
||||
ContextUI.dismissContextAppbar();
|
||||
FlyoutPanelsUI.hide();
|
||||
|
@ -927,6 +927,12 @@ this.UITour = {
|
||||
recreatePopup: function(aPanel) {
|
||||
// After changing popup attributes that relate to how the native widget is created
|
||||
// (e.g. @noautohide) we need to re-create the frame/widget for it to take effect.
|
||||
if (aPanel.hidden) {
|
||||
// If the panel is already hidden, we don't need to recreate it but flush
|
||||
// in case someone just hid it.
|
||||
aPanel.clientWidth; // flush
|
||||
return;
|
||||
}
|
||||
aPanel.hidden = true;
|
||||
aPanel.clientWidth; // flush
|
||||
aPanel.hidden = false;
|
||||
|
@ -250,12 +250,12 @@ toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-it
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"] {
|
||||
toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) {
|
||||
width: calc(@menuPanelButtonWidth@);
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-widget) {
|
||||
toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) {
|
||||
-moz-box-align: center;
|
||||
-moz-box-pack: center;
|
||||
}
|
||||
@ -264,6 +264,18 @@ toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"] > iframe {
|
||||
margin: 4px auto;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXXgijs: this is a workaround for a layout issue that was caused by these iframes,
|
||||
* which was affecting subview display. Because of this, we're hiding the iframe *only*
|
||||
* when displaying a subview. The discerning user might notice this, but it's not nearly
|
||||
* as bad as the brokenness.
|
||||
* This hack should be removed once https://bugzilla.mozilla.org/show_bug.cgi?id=975375
|
||||
* is addressed.
|
||||
*/
|
||||
#PanelUI-multiView[viewtype="subview"] toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > iframe {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > .toolbarbutton-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -110,14 +110,14 @@ XPCOMUtils.defineLazyGetter(this, "updateSvc", function() {
|
||||
});
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
const DIRECTORY_KEY = "webappsDir";
|
||||
const DIRECTORY_NAME = "webappsDir";
|
||||
#elifdef ANDROID
|
||||
const DIRECTORY_KEY = "webappsDir";
|
||||
const DIRECTORY_NAME = "webappsDir";
|
||||
#else
|
||||
// If we're executing in the context of the webapp runtime, the data files
|
||||
// are in a different directory (currently the Firefox profile that installed
|
||||
// the webapp); otherwise, they're in the current profile.
|
||||
const DIRECTORY_KEY = WEBAPP_RUNTIME ? "WebappRegD" : "ProfD";
|
||||
const DIRECTORY_NAME = WEBAPP_RUNTIME ? "WebappRegD" : "ProfD";
|
||||
#endif
|
||||
|
||||
// We'll use this to identify privileged apps that have been preinstalled
|
||||
@ -161,8 +161,8 @@ this.DOMApplicationRegistry = {
|
||||
|
||||
AppDownloadManager.registerCancelFunction(this.cancelDownload.bind(this));
|
||||
|
||||
this.appsFile = OS.Path.join(Services.dirsvc.get(DIRECTORY_KEY, Ci.nsIFile).path,
|
||||
"webapps", "webapps.json");
|
||||
this.appsFile = FileUtils.getFile(DIRECTORY_NAME,
|
||||
["webapps", "webapps.json"], true).path;
|
||||
|
||||
this.loadAndUpdateApps();
|
||||
},
|
||||
@ -171,10 +171,7 @@ this.DOMApplicationRegistry = {
|
||||
loadCurrentRegistry: function() {
|
||||
return this._loadJSONAsync(this.appsFile).then((aData) => {
|
||||
if (!aData) {
|
||||
// If _loadJSONAsync returns null, we're probably in the firstrun case
|
||||
// so we may need to create the "webapps" directory.
|
||||
return OS.File.makeDir(OS.Path.dirname(this.appsFile),
|
||||
{ ignoreExisting: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.webapps = aData;
|
||||
@ -410,13 +407,8 @@ this.DOMApplicationRegistry = {
|
||||
debug("Installing 3rd party app : " + aId +
|
||||
" from " + baseDir.path);
|
||||
|
||||
// We copy this app to DIRECTORY_KEY/$aId, and set the base path as needed.
|
||||
let destDir = this._getAppDir(aId);
|
||||
try {
|
||||
destDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
// Ignore the exception if the directory already exists.
|
||||
}
|
||||
// We copy this app to DIRECTORY_NAME/$aId, and set the base path as needed.
|
||||
let destDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
|
||||
|
||||
filesToMove.forEach(function(aFile) {
|
||||
let file = baseDir.clone();
|
||||
@ -1205,7 +1197,7 @@ this.DOMApplicationRegistry = {
|
||||
},
|
||||
|
||||
_getAppDir: function(aId) {
|
||||
return FileUtils.getDir(DIRECTORY_KEY, ["webapps", aId], false, true);
|
||||
return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
|
||||
},
|
||||
|
||||
_writeFile: function(aPath, aData) {
|
||||
@ -1345,8 +1337,11 @@ this.DOMApplicationRegistry = {
|
||||
|
||||
// We need to get the update manifest here, not the webapp manifest.
|
||||
// If this is an update, the update manifest is staged.
|
||||
let file = this._getAppDir(id);
|
||||
file.append(isUpdate ? "staged-update.webapp" : "update.webapp");
|
||||
let file = FileUtils.getFile(DIRECTORY_NAME,
|
||||
["webapps", id,
|
||||
isUpdate ? "staged-update.webapp"
|
||||
: "update.webapp"],
|
||||
true);
|
||||
|
||||
if (!file.exists()) {
|
||||
// This is a hosted app, let's check if it has an appcache
|
||||
@ -1436,13 +1431,13 @@ this.DOMApplicationRegistry = {
|
||||
// We need to get the old manifest to unregister web activities.
|
||||
this.getManifestFor(aManifestURL).then((aOldManifest) => {
|
||||
// Move the application.zip and manifest.webapp files out of TmpD
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], false, true);
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
|
||||
let manFile = tmpDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
let appFile = tmpDir.clone();
|
||||
appFile.append("application.zip");
|
||||
|
||||
let dir = this._getAppDir(id);
|
||||
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
|
||||
appFile.moveTo(dir, "application.zip");
|
||||
manFile.moveTo(dir, "manifest.webapp");
|
||||
|
||||
@ -2205,14 +2200,16 @@ this.DOMApplicationRegistry = {
|
||||
},
|
||||
|
||||
denyInstall: function(aData) {
|
||||
Task.spawn(function*() {
|
||||
let packageId = aData.app.packageId;
|
||||
if (packageId) {
|
||||
let dir = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", packageId);
|
||||
yield OS.File.removeDir(dir, { ignoreAbsent: true });
|
||||
let packageId = aData.app.packageId;
|
||||
if (packageId) {
|
||||
let dir = FileUtils.getDir("TmpD", ["webapps", packageId],
|
||||
true, true);
|
||||
try {
|
||||
dir.remove(true);
|
||||
} catch(e) {
|
||||
}
|
||||
aData.mm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
|
||||
}).then(null, Cu.reportError);
|
||||
}
|
||||
aData.mm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
|
||||
},
|
||||
|
||||
// This function is called after we called the onsuccess callback on the
|
||||
@ -2314,13 +2311,14 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
return appObject;
|
||||
},
|
||||
|
||||
_writeManifestFile: function(aDir, aIsPackage, aJsonManifest) {
|
||||
_writeManifestFile: function(aId, aIsPackage, aJsonManifest) {
|
||||
debug("_writeManifestFile");
|
||||
|
||||
// For packaged apps, keep the update manifest distinct from the app manifest.
|
||||
let manifestName = aIsPackage ? "update.webapp" : "manifest.webapp";
|
||||
|
||||
let manFile = OS.Path.join(aDir, manifestName);
|
||||
let dir = this._getAppDir(aId).path;
|
||||
let manFile = OS.Path.join(dir, manifestName);
|
||||
this._writeFile(manFile, JSON.stringify(aJsonManifest));
|
||||
},
|
||||
|
||||
@ -2346,18 +2344,10 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
localId = this._nextLocalId();
|
||||
}
|
||||
|
||||
// Create the app directory
|
||||
let dir = this._getAppDir(id);
|
||||
try {
|
||||
dir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
// Ignore the exception if the directory already exists.
|
||||
}
|
||||
|
||||
let app = this._setupApp(aData, id);
|
||||
|
||||
let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest;
|
||||
this._writeManifestFile(dir.path, aData.isPackage, jsonManifest);
|
||||
this._writeManifestFile(id, aData.isPackage, jsonManifest);
|
||||
|
||||
debug("app.origin: " + app.origin);
|
||||
let manifest = new ManifestHelper(jsonManifest, app.origin);
|
||||
@ -2477,14 +2467,11 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
debug("_onDownloadPackage");
|
||||
// Success! Move the zip out of TmpD.
|
||||
let app = this.webapps[aId];
|
||||
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], false, true);
|
||||
let zipFile = tmpDir.clone();
|
||||
zipFile.append("application.zip");
|
||||
|
||||
let zipFile =
|
||||
FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
|
||||
let dir = this._getAppDir(aId);
|
||||
zipFile.moveTo(dir, "application.zip");
|
||||
|
||||
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
|
||||
try {
|
||||
tmpDir.remove(true);
|
||||
} catch(e) { }
|
||||
@ -2577,7 +2564,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
if (!this._manifestCache[id]) {
|
||||
// the manifest file used to be named manifest.json, so fallback on this.
|
||||
let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath()
|
||||
? "coreAppsDir" : DIRECTORY_KEY;
|
||||
? "coreAppsDir" : DIRECTORY_NAME;
|
||||
|
||||
let dir = FileUtils.getDir(baseDir, ["webapps", id], false, true);
|
||||
|
||||
@ -2705,7 +2692,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
debug("No deviceStorage");
|
||||
// deviceStorage isn't available, so use FileUtils to find the size of
|
||||
// available storage.
|
||||
let dir = FileUtils.getDir(DIRECTORY_KEY, ["webapps"], false, true);
|
||||
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true);
|
||||
try {
|
||||
let sufficientStorage = this._checkDownloadSize(dir.diskSpaceAvailable,
|
||||
aNewApp);
|
||||
@ -2962,9 +2949,10 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
eventType: ["downloadsuccess", "downloadapplied"]
|
||||
});
|
||||
});
|
||||
|
||||
let appDir = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId);
|
||||
OS.File.removeDir(appDir, { ignoreAbsent: true });
|
||||
let file = FileUtils.getFile("TmpD", ["webapps", aId], false);
|
||||
if (file && file.exists()) {
|
||||
file.remove(true);
|
||||
}
|
||||
},
|
||||
|
||||
_openAndReadPackage: function(aZipFile, aOldApp, aNewApp, aIsLocalFileInstall,
|
||||
@ -3229,9 +3217,9 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
this.webapps[newId] = aOldApp;
|
||||
delete this.webapps[oldId];
|
||||
// Rename the directories where the files are installed.
|
||||
[DIRECTORY_KEY, "TmpD"].forEach(function(aDir) {
|
||||
let parent = FileUtils.getDir(aDir, ["webapps"], false, true);
|
||||
let dir = FileUtils.getDir(aDir, ["webapps", oldId], false, true);
|
||||
[DIRECTORY_NAME, "TmpD"].forEach(function(aDir) {
|
||||
let parent = FileUtils.getDir(aDir, ["webapps"], true, true);
|
||||
let dir = FileUtils.getDir(aDir, ["webapps", oldId], true, true);
|
||||
dir.moveTo(parent, newId);
|
||||
});
|
||||
// Signals that we need to swap the old id with the new app.
|
||||
@ -3308,7 +3296,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
// Removes the directory we created, and sends an error to the DOM side.
|
||||
_revertDownloadPackage: function(aId, aOldApp, aNewApp, aIsUpdate, aError) {
|
||||
debug("Cleanup: " + aError + "\n" + aError.stack);
|
||||
let dir = FileUtils.getDir("TmpD", ["webapps", aId], false, true);
|
||||
let dir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
|
||||
try {
|
||||
dir.remove(true);
|
||||
} catch (e) { }
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Mozilla Foundation
|
||||
* Copyright (C) 2013-2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -153,6 +153,10 @@ GonkCameraParameters::Initialize()
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
rv = GetListAsArray(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
return NS_OK;
|
||||
@ -401,16 +405,53 @@ GonkCameraParameters::GetTranslated(uint32_t aKey, int64_t& aValue)
|
||||
nsresult
|
||||
GonkCameraParameters::SetTranslated(uint32_t aKey, const double& aValue)
|
||||
{
|
||||
if (aKey == CAMERA_PARAM_EXPOSURECOMPENSATION) {
|
||||
/**
|
||||
* Convert from real value to a Gonk index, round
|
||||
* to the nearest step; index is 1-based.
|
||||
*/
|
||||
int index =
|
||||
(aValue - mExposureCompensationMin + mExposureCompensationStep / 2) /
|
||||
mExposureCompensationStep + 1;
|
||||
DOM_CAMERA_LOGI("Exposure compensation = %f --> index = %d\n", aValue, index);
|
||||
return SetImpl(CAMERA_PARAM_EXPOSURECOMPENSATION, index);
|
||||
int index;
|
||||
int value;
|
||||
|
||||
switch (aKey) {
|
||||
case CAMERA_PARAM_EXPOSURECOMPENSATION:
|
||||
/**
|
||||
* Convert from real value to a Gonk index, round
|
||||
* to the nearest step; index is 1-based.
|
||||
*/
|
||||
index =
|
||||
(aValue - mExposureCompensationMin + mExposureCompensationStep / 2) /
|
||||
mExposureCompensationStep + 1;
|
||||
DOM_CAMERA_LOGI("Exposure compensation = %f --> index = %d\n", aValue, index);
|
||||
return SetImpl(CAMERA_PARAM_EXPOSURECOMPENSATION, index);
|
||||
|
||||
case CAMERA_PARAM_ZOOM:
|
||||
{
|
||||
/**
|
||||
* Convert from a real zoom multipler (e.g. 2.5x) to
|
||||
* the index of the nearest supported value.
|
||||
*/
|
||||
value = aValue * 100.0;
|
||||
|
||||
// mZoomRatios is sorted, so we can binary search it
|
||||
unsigned int bottom = 0;
|
||||
unsigned int top = mZoomRatios.Length() - 1;
|
||||
unsigned int middle;
|
||||
|
||||
while (bottom != top) {
|
||||
middle = (top + bottom) / 2;
|
||||
if (value == mZoomRatios[middle]) {
|
||||
// exact match
|
||||
break;
|
||||
}
|
||||
if (value > mZoomRatios[middle] && value < mZoomRatios[middle + 1]) {
|
||||
// the specified zoom value lies in this interval
|
||||
break;
|
||||
}
|
||||
if (value > mZoomRatios[middle]) {
|
||||
bottom = middle + 1;
|
||||
} else {
|
||||
top = middle - 1;
|
||||
}
|
||||
}
|
||||
index = middle;
|
||||
}
|
||||
return SetImpl(CAMERA_PARAM_ZOOM, index);
|
||||
}
|
||||
|
||||
return SetImpl(aKey, aValue);
|
||||
@ -427,9 +468,9 @@ GonkCameraParameters::GetTranslated(uint32_t aKey, double& aValue)
|
||||
|
||||
switch (aKey) {
|
||||
case CAMERA_PARAM_ZOOM:
|
||||
rv = GetImpl(CAMERA_PARAM_ZOOM, val);
|
||||
rv = GetImpl(CAMERA_PARAM_ZOOM, index);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
val /= 100.0;
|
||||
val = mZoomRatios[index] / 100.0;
|
||||
} else {
|
||||
// return 1x when zooming is not supported
|
||||
val = 1.0;
|
||||
@ -557,6 +598,16 @@ ParseItem(const char* aStart, const char* aEnd, double* aItem)
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ParseItem(const char* aStart, const char* aEnd, int* aItem)
|
||||
{
|
||||
if (sscanf(aStart, "%d", aItem) == 1) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
template<class T> nsresult
|
||||
GonkCameraParameters::GetListAsArray(uint32_t aKey, nsTArray<T>& aArray)
|
||||
{
|
||||
@ -609,6 +660,14 @@ GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<nsString>& aValues)
|
||||
nsresult
|
||||
GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<double>& aValues)
|
||||
{
|
||||
if (aKey == CAMERA_PARAM_SUPPORTED_ZOOMRATIOS) {
|
||||
aValues.Clear();
|
||||
for (int i = 0; i < mZoomRatios.Length(); ++i) {
|
||||
*aValues.AppendElement() = mZoomRatios[i] / 100.0;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return GetListAsArray(aKey, aValues);
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,7 @@ protected:
|
||||
// Required internal properties
|
||||
double mExposureCompensationMin;
|
||||
double mExposureCompensationStep;
|
||||
nsTArray<int> mZoomRatios;
|
||||
|
||||
// This subclass of android::CameraParameters just gives
|
||||
// all of the AOSP getters and setters the same signature.
|
||||
|
@ -33,8 +33,8 @@ function onError(e) {
|
||||
}
|
||||
|
||||
var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
|
||||
'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
|
||||
'recorderProfiles'];
|
||||
'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
|
||||
'recorderProfiles', 'zoomRatios'];
|
||||
|
||||
var Camera = {
|
||||
cameraObj: null,
|
||||
@ -49,6 +49,7 @@ var Camera = {
|
||||
_zoomRatios: null,
|
||||
_sceneModes: null,
|
||||
_focusModes: null,
|
||||
_zoomRatios: null,
|
||||
_testsCompleted: 0,
|
||||
_shutter: 0,
|
||||
_config: {
|
||||
@ -67,6 +68,12 @@ var Camera = {
|
||||
setFocus: function camera_setfocus(mode) {
|
||||
this.cameraObj.focus = mode;
|
||||
},
|
||||
setZoom: function camera_setZoom(zoom) {
|
||||
this.cameraObj.zoom = zoom;
|
||||
},
|
||||
getZoom: function camera_getZoom() {
|
||||
return this.cameraObj.zoom;
|
||||
},
|
||||
getFileFormats: function camera_formats() {
|
||||
this._fileFormats = this.cameraObj.capabilities.fileFormats;
|
||||
},
|
||||
@ -91,6 +98,9 @@ var Camera = {
|
||||
getPreviewSizes: function camera_preview() {
|
||||
this._previewSizes = this.cameraObj.capabilities.previewSizes;
|
||||
},
|
||||
getZoomRatios: function camera_preview() {
|
||||
this._zoomRatios = this.cameraObj.capabilities.zoomRatios;
|
||||
},
|
||||
takePictureSuccess: function taken_foto(blob) {
|
||||
var img = new Image();
|
||||
var test = this._currentTest;
|
||||
@ -137,9 +147,35 @@ var Camera = {
|
||||
Camera.getPreviewSizes();
|
||||
Camera.getFileFormats();
|
||||
Camera.getFocusModes();
|
||||
Camera.getZoomRatios();
|
||||
ok(Camera._previewSizes.length > 0, "previewSizes length = " + Camera._previewSizes.length);
|
||||
ok(Camera._pictureSizes.length > 0, "picturesizes length = " + Camera._pictureSizes.length);
|
||||
ok(Camera._fileFormats.length > 0, "file formats length = " + Camera._fileFormats.length);
|
||||
info("zoom ratios length = " + Camera._zoomRatios.length);
|
||||
|
||||
if (Camera._zoomRatios.length > 0) {
|
||||
Camera._zoomRatios.forEach(function(element, index) {
|
||||
info("zoom[" + index + "] = " + element + "x");
|
||||
Camera.setZoom(element);
|
||||
ok(Camera.getZoom() === element, "zoom[" + index + "] = " + element + "x");
|
||||
});
|
||||
|
||||
var zoom = Camera._zoomRatios[0] - 0.1;
|
||||
Camera.setZoom(zoom);
|
||||
ok(Camera.getZoom() === Camera._zoomRatios[0],
|
||||
zoom + "x zoom clamps to minimum: " + Camera._zoomRatios[0]);
|
||||
zoom = Camera._zoomRatios.slice(-1)[0] + 1.0;
|
||||
Camera.setZoom(zoom);
|
||||
ok(Camera.getZoom() === Camera._zoomRatios[0],
|
||||
zoom + "x zoom clamps to maximum: " + Camera._zoomRatios.slice(-1)[0]);
|
||||
if (Camera._zoomRatios.length > 1) {
|
||||
zoom = (Camera._zoomRatios[0] + Camera._zoomRatios[1]) / 2;
|
||||
Camera.setZoom(zoom);
|
||||
ok(Camera.getZoom() === Camera._zoomRatios[0],
|
||||
zoom + "x zoom rounded down to maximum: " + Camera._zoomRatios.slice[0]);
|
||||
}
|
||||
}
|
||||
|
||||
Camera._tests = new Array();
|
||||
for (var i in Camera._pictureSizes) {
|
||||
for (var l in Camera._fileFormats) {
|
||||
|
@ -228,6 +228,7 @@ CheckPermission(PContentParent* aActor,
|
||||
uint32_t appPerm = nsIPermissionManager::UNKNOWN_ACTION;
|
||||
nsresult rv = pm->TestExactPermissionFromPrincipal(appPrincipal, aPermission, &appPerm);
|
||||
NS_ENSURE_SUCCESS(rv, nsIPermissionManager::UNKNOWN_ACTION);
|
||||
// Setting to "deny" in the settings UI should deny everywhere.
|
||||
if (appPerm == nsIPermissionManager::UNKNOWN_ACTION ||
|
||||
appPerm == nsIPermissionManager::DENY_ACTION) {
|
||||
return appPerm;
|
||||
@ -241,6 +242,15 @@ CheckPermission(PContentParent* aActor,
|
||||
return permission;
|
||||
}
|
||||
|
||||
// For browser content (and if the app hasn't explicitly denied this),
|
||||
// consider the requesting origin, not the app.
|
||||
if (appPerm == nsIPermissionManager::PROMPT_ACTION &&
|
||||
aPrincipal->GetIsInBrowserElement()) {
|
||||
return permission;
|
||||
}
|
||||
|
||||
// Setting to "prompt" in the settings UI should prompt everywhere in
|
||||
// non-browser content.
|
||||
if (appPerm == nsIPermissionManager::PROMPT_ACTION ||
|
||||
permission == nsIPermissionManager::PROMPT_ACTION) {
|
||||
return nsIPermissionManager::PROMPT_ACTION;
|
||||
|
@ -24,6 +24,8 @@ import org.mozilla.gecko.health.BrowserHealthReporter;
|
||||
import org.mozilla.gecko.health.HealthRecorder;
|
||||
import org.mozilla.gecko.health.SessionInformation;
|
||||
import org.mozilla.gecko.home.BrowserSearch;
|
||||
import org.mozilla.gecko.home.HomeBanner;
|
||||
import org.mozilla.gecko.home.HomeConfigInvalidator;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.SearchEngine;
|
||||
@ -1012,7 +1014,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
if (mMainLayoutAnimator != null)
|
||||
mMainLayoutAnimator.stop();
|
||||
|
||||
boolean isSideBar = (HardwareUtils.isTablet() && mOrientation == Configuration.ORIENTATION_LANDSCAPE);
|
||||
boolean isSideBar = (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
|
||||
final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width);
|
||||
|
||||
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams();
|
||||
@ -1421,7 +1423,8 @@ abstract public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
private boolean isHomePagerVisible() {
|
||||
return (mHomePager != null && mHomePager.isVisible());
|
||||
return (mHomePager != null && mHomePager.isLoaded()
|
||||
&& mHomePagerContainer != null && mHomePagerContainer.getVisibility() == View.VISIBLE);
|
||||
}
|
||||
|
||||
/* Favicon stuff. */
|
||||
@ -1641,9 +1644,7 @@ abstract public class BrowserApp extends GeckoApp
|
||||
public void onLocaleReady(final String locale) {
|
||||
super.onLocaleReady(locale);
|
||||
|
||||
if (mHomePager != null) {
|
||||
mHomePager.invalidate(getSupportLoaderManager(), getSupportFragmentManager());
|
||||
}
|
||||
HomeConfigInvalidator.getInstance().onLocaleReady(locale);
|
||||
|
||||
if (mMenu != null) {
|
||||
mMenu.clear();
|
||||
@ -1678,9 +1679,13 @@ abstract public class BrowserApp extends GeckoApp
|
||||
if (mHomePager == null) {
|
||||
final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
|
||||
mHomePager = (HomePager) homePagerStub.inflate();
|
||||
|
||||
HomeBanner homeBanner = (HomeBanner) findViewById(R.id.home_banner);
|
||||
mHomePager.setBanner(homeBanner);
|
||||
}
|
||||
|
||||
mHomePager.show(getSupportLoaderManager(),
|
||||
mHomePagerContainer.setVisibility(View.VISIBLE);
|
||||
mHomePager.load(getSupportLoaderManager(),
|
||||
getSupportFragmentManager(),
|
||||
pageId, animator);
|
||||
|
||||
@ -1740,9 +1745,10 @@ abstract public class BrowserApp extends GeckoApp
|
||||
|
||||
// Display the previously hidden web content (which prevented screen reader access).
|
||||
mLayerView.setVisibility(View.VISIBLE);
|
||||
mHomePagerContainer.setVisibility(View.GONE);
|
||||
|
||||
if (mHomePager != null) {
|
||||
mHomePager.hide();
|
||||
mHomePager.unload();
|
||||
}
|
||||
|
||||
mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
|
||||
|
@ -183,7 +183,6 @@ public abstract class GeckoApp
|
||||
protected MenuPanel mMenuPanel;
|
||||
protected Menu mMenu;
|
||||
protected GeckoProfile mProfile;
|
||||
public static int mOrientation;
|
||||
protected boolean mIsRestoringActivity;
|
||||
|
||||
private ContactService mContactService;
|
||||
@ -425,8 +424,12 @@ public abstract class GeckoApp
|
||||
|
||||
@Override
|
||||
public boolean onPreparePanel(int featureId, View view, Menu menu) {
|
||||
if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL)
|
||||
if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
|
||||
if (menu instanceof GeckoMenu) {
|
||||
((GeckoMenu) menu).refresh();
|
||||
}
|
||||
return onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
return super.onPreparePanel(featureId, view, menu);
|
||||
}
|
||||
@ -937,7 +940,7 @@ public abstract class GeckoApp
|
||||
|
||||
mFullScreenPluginView = null;
|
||||
|
||||
GeckoScreenOrientationListener.getInstance().unlockScreenOrientation();
|
||||
GeckoScreenOrientation.getInstance().unlock();
|
||||
setFullScreen(false);
|
||||
}
|
||||
|
||||
@ -1248,7 +1251,7 @@ public abstract class GeckoApp
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mOrientation = getResources().getConfiguration().orientation;
|
||||
GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
|
||||
|
||||
setContentView(getLayout());
|
||||
|
||||
@ -1920,6 +1923,10 @@ public abstract class GeckoApp
|
||||
return uri;
|
||||
}
|
||||
|
||||
protected int getOrientation() {
|
||||
return GeckoScreenOrientation.getInstance().getAndroidOrientation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
@ -1928,14 +1935,10 @@ public abstract class GeckoApp
|
||||
super.onResume();
|
||||
|
||||
int newOrientation = getResources().getConfiguration().orientation;
|
||||
|
||||
if (mOrientation != newOrientation) {
|
||||
mOrientation = newOrientation;
|
||||
if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
|
||||
refreshChrome();
|
||||
}
|
||||
|
||||
GeckoScreenOrientationListener.getInstance().start();
|
||||
|
||||
// User may have enabled/disabled accessibility.
|
||||
GeckoAccessibility.updateAccessibilitySettings(this);
|
||||
|
||||
@ -2011,8 +2014,6 @@ public abstract class GeckoApp
|
||||
}
|
||||
});
|
||||
|
||||
GeckoScreenOrientationListener.getInstance().stop();
|
||||
|
||||
if (mAppStateListeners != null) {
|
||||
for(GeckoAppShell.AppStateListener listener: mAppStateListeners) {
|
||||
listener.onPause();
|
||||
@ -2151,14 +2152,16 @@ public abstract class GeckoApp
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
|
||||
LocaleManager.correctLocale(getResources(), newConfig);
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
if (mOrientation != newConfig.orientation) {
|
||||
mOrientation = newConfig.orientation;
|
||||
// onConfigurationChanged is not called for 180 degree orientation changes,
|
||||
// we will miss such rotations and the screen orientation will not be
|
||||
// updated.
|
||||
if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) {
|
||||
if (mFormAssistPopup != null)
|
||||
mFormAssistPopup.hide();
|
||||
refreshChrome();
|
||||
}
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
public String getContentProcessName() {
|
||||
|
@ -2571,27 +2571,27 @@ public class GeckoAppShell
|
||||
|
||||
@WrapElementForJNI(stubName = "GetScreenOrientationWrapper")
|
||||
public static short getScreenOrientation() {
|
||||
return GeckoScreenOrientationListener.getInstance().getScreenOrientation();
|
||||
return GeckoScreenOrientation.getInstance().getScreenOrientation().value;
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void enableScreenOrientationNotifications() {
|
||||
GeckoScreenOrientationListener.getInstance().enableNotifications();
|
||||
GeckoScreenOrientation.getInstance().enableNotifications();
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void disableScreenOrientationNotifications() {
|
||||
GeckoScreenOrientationListener.getInstance().disableNotifications();
|
||||
GeckoScreenOrientation.getInstance().disableNotifications();
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void lockScreenOrientation(int aOrientation) {
|
||||
GeckoScreenOrientationListener.getInstance().lockScreenOrientation(aOrientation);
|
||||
GeckoScreenOrientation.getInstance().lock(aOrientation);
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static void unlockScreenOrientation() {
|
||||
GeckoScreenOrientationListener.getInstance().unlockScreenOrientation();
|
||||
GeckoScreenOrientation.getInstance().unlock();
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
|
376
mobile/android/base/GeckoScreenOrientation.java
Normal file
376
mobile/android/base/GeckoScreenOrientation.java
Normal file
@ -0,0 +1,376 @@
|
||||
/* -*- 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 android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.app.Activity;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Updates, locks and unlocks the screen orientation.
|
||||
*
|
||||
* Note: Replaces the OnOrientationChangeListener to avoid redundant rotation
|
||||
* event handling.
|
||||
*/
|
||||
public class GeckoScreenOrientation {
|
||||
private static final String LOGTAG = "GeckoScreenOrientation";
|
||||
|
||||
// Make sure that any change in dom/base/ScreenOrientation.h happens here too.
|
||||
public enum ScreenOrientation {
|
||||
NONE(0),
|
||||
PORTRAIT_PRIMARY(1 << 0),
|
||||
PORTRAIT_SECONDARY(1 << 1),
|
||||
LANDSCAPE_PRIMARY(1 << 2),
|
||||
LANDSCAPE_SECONDARY(1 << 3),
|
||||
DEFAULT(1 << 4);
|
||||
|
||||
public final short value;
|
||||
|
||||
private ScreenOrientation(int value) {
|
||||
this.value = (short)value;
|
||||
}
|
||||
|
||||
public static ScreenOrientation get(short value) {
|
||||
switch (value) {
|
||||
case (1 << 0): return PORTRAIT_PRIMARY;
|
||||
case (1 << 1): return PORTRAIT_SECONDARY;
|
||||
case (1 << 2): return LANDSCAPE_PRIMARY;
|
||||
case (1 << 3): return LANDSCAPE_SECONDARY;
|
||||
case (1 << 4): return DEFAULT;
|
||||
default: return NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance.
|
||||
private static GeckoScreenOrientation sInstance = null;
|
||||
// Default screen orientation, used for initialization and unlocking.
|
||||
private static final ScreenOrientation DEFAULT_SCREEN_ORIENTATION = ScreenOrientation.DEFAULT;
|
||||
// Default rotation, used when device rotation is unknown.
|
||||
private static final int DEFAULT_ROTATION = Surface.ROTATION_0;
|
||||
// Default orientation, used if screen orientation is unspecified.
|
||||
private ScreenOrientation mDefaultScreenOrientation;
|
||||
// Last updated screen orientation.
|
||||
private ScreenOrientation mScreenOrientation;
|
||||
// Whether the update should notify Gecko about screen orientation changes.
|
||||
private boolean mShouldNotify = true;
|
||||
// Configuration screen orientation preference path.
|
||||
private static final String DEFAULT_SCREEN_ORIENTATION_PREF = "app.orientation.default";
|
||||
|
||||
public GeckoScreenOrientation() {
|
||||
PrefsHelper.getPref(DEFAULT_SCREEN_ORIENTATION_PREF, new PrefsHelper.PrefHandlerBase() {
|
||||
@Override public void prefValue(String pref, String value) {
|
||||
// Read and update the configuration default preference.
|
||||
mDefaultScreenOrientation = screenOrientationFromArrayString(value);
|
||||
setRequestedOrientation(mDefaultScreenOrientation);
|
||||
}
|
||||
});
|
||||
|
||||
mDefaultScreenOrientation = DEFAULT_SCREEN_ORIENTATION;
|
||||
update();
|
||||
}
|
||||
|
||||
public static GeckoScreenOrientation getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new GeckoScreenOrientation();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable Gecko screen orientation events on update.
|
||||
*/
|
||||
public void enableNotifications() {
|
||||
update();
|
||||
mShouldNotify = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable Gecko screen orientation events on update.
|
||||
*/
|
||||
public void disableNotifications() {
|
||||
mShouldNotify = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update screen orientation.
|
||||
* Retrieve orientation and rotation via GeckoAppShell.
|
||||
*
|
||||
* @return Whether the screen orientation has changed.
|
||||
*/
|
||||
public boolean update() {
|
||||
Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
|
||||
if (activity == null) {
|
||||
return false;
|
||||
}
|
||||
Configuration config = activity.getResources().getConfiguration();
|
||||
return update(config.orientation);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update screen orientation given the android orientation.
|
||||
* Retrieve rotation via GeckoAppShell.
|
||||
*
|
||||
* @param aAndroidOrientation
|
||||
* Android screen orientation from Configuration.orientation.
|
||||
*
|
||||
* @return Whether the screen orientation has changed.
|
||||
*/
|
||||
public boolean update(int aAndroidOrientation) {
|
||||
return update(getScreenOrientation(aAndroidOrientation, getRotation()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Update screen orientation given the screen orientation.
|
||||
*
|
||||
* @param aScreenOrientation
|
||||
* Gecko screen orientation based on android orientation and rotation.
|
||||
*
|
||||
* @return Whether the screen orientation has changed.
|
||||
*/
|
||||
public boolean update(ScreenOrientation aScreenOrientation) {
|
||||
if (mScreenOrientation == aScreenOrientation) {
|
||||
return false;
|
||||
}
|
||||
mScreenOrientation = aScreenOrientation;
|
||||
Log.d(LOGTAG, "updating to new orientation " + mScreenOrientation);
|
||||
if (mShouldNotify) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenOrientationEvent(mScreenOrientation.value));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* @return The Android orientation (Configuration.orientation).
|
||||
*/
|
||||
public int getAndroidOrientation() {
|
||||
return screenOrientationToAndroidOrientation(getScreenOrientation());
|
||||
}
|
||||
|
||||
/*
|
||||
* @return The Gecko screen orientation derived from Android orientation and
|
||||
* rotation.
|
||||
*/
|
||||
public ScreenOrientation getScreenOrientation() {
|
||||
return mScreenOrientation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock screen orientation given the Android orientation.
|
||||
* Retrieve rotation via GeckoAppShell.
|
||||
*
|
||||
* @param aAndroidOrientation
|
||||
* The Android orientation provided by Configuration.orientation.
|
||||
*/
|
||||
public void lock(int aAndroidOrientation) {
|
||||
lock(getScreenOrientation(aAndroidOrientation, getRotation()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock screen orientation given the Gecko screen orientation.
|
||||
* Retrieve rotation via GeckoAppShell.
|
||||
*
|
||||
* @param aScreenOrientation
|
||||
* Gecko screen orientation derived from Android orientation and
|
||||
* rotation.
|
||||
*
|
||||
* @return Whether the locking was successful.
|
||||
*/
|
||||
public boolean lock(ScreenOrientation aScreenOrientation) {
|
||||
Log.d(LOGTAG, "locking to " + aScreenOrientation);
|
||||
update(aScreenOrientation);
|
||||
return setRequestedOrientation(aScreenOrientation);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unlock and update screen orientation.
|
||||
*
|
||||
* @return Whether the unlocking was successful.
|
||||
*/
|
||||
public boolean unlock() {
|
||||
Log.d(LOGTAG, "unlocking");
|
||||
setRequestedOrientation(mDefaultScreenOrientation);
|
||||
return update();
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the given requested orientation for the current activity.
|
||||
* This is essentially an unlock without an update.
|
||||
*
|
||||
* @param aScreenOrientation
|
||||
* Gecko screen orientation.
|
||||
*
|
||||
* @return Whether the requested orientation was set. This can only fail if
|
||||
* the current activity cannot be retrieved vie GeckoAppShell.
|
||||
*
|
||||
*/
|
||||
private boolean setRequestedOrientation(ScreenOrientation aScreenOrientation) {
|
||||
int activityOrientation = screenOrientationToActivityInfoOrientation(aScreenOrientation);
|
||||
Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
|
||||
if (activity == null) {
|
||||
Log.w(LOGTAG, "setRequestOrientation: failed to get activity");
|
||||
}
|
||||
if (activity.getRequestedOrientation() == activityOrientation) {
|
||||
return false;
|
||||
}
|
||||
activity.setRequestedOrientation(activityOrientation);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Combine the Android orientation and rotation to the Gecko orientation.
|
||||
*
|
||||
* @param aAndroidOrientation
|
||||
* Android orientation from Configuration.orientation.
|
||||
* @param aRotation
|
||||
* Device rotation from Display.getRotation().
|
||||
*
|
||||
* @return Gecko screen orientation.
|
||||
*/
|
||||
private ScreenOrientation getScreenOrientation(int aAndroidOrientation, int aRotation) {
|
||||
boolean isPrimary = aRotation == Surface.ROTATION_0 || aRotation == Surface.ROTATION_90;
|
||||
if (aAndroidOrientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
if (isPrimary) {
|
||||
// Non-rotated portrait device or landscape device rotated
|
||||
// to primary portrait mode counter-clockwise.
|
||||
return ScreenOrientation.PORTRAIT_PRIMARY;
|
||||
}
|
||||
return ScreenOrientation.PORTRAIT_SECONDARY;
|
||||
}
|
||||
if (aAndroidOrientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
if (isPrimary) {
|
||||
// Non-rotated landscape device or portrait device rotated
|
||||
// to primary landscape mode counter-clockwise.
|
||||
return ScreenOrientation.LANDSCAPE_PRIMARY;
|
||||
}
|
||||
return ScreenOrientation.LANDSCAPE_SECONDARY;
|
||||
}
|
||||
return ScreenOrientation.NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* @return Device rotation from Display.getRotation().
|
||||
*/
|
||||
private int getRotation() {
|
||||
Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
|
||||
if (activity == null) {
|
||||
Log.w(LOGTAG, "getRotation: failed to get activity");
|
||||
return DEFAULT_ROTATION;
|
||||
}
|
||||
return activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the screen orientation from an array string.
|
||||
*
|
||||
* @param aArray
|
||||
* String containing comma-delimited strings.
|
||||
*
|
||||
* @return First parsed Gecko screen orientation.
|
||||
*/
|
||||
public static ScreenOrientation screenOrientationFromArrayString(String aArray) {
|
||||
List<String> orientations = Arrays.asList(aArray.split(","));
|
||||
if (orientations.size() == 0) {
|
||||
// If nothing is listed, return default.
|
||||
Log.w(LOGTAG, "screenOrientationFromArrayString: no orientation in string");
|
||||
return DEFAULT_SCREEN_ORIENTATION;
|
||||
}
|
||||
|
||||
// We don't support multiple orientations yet. To avoid developer
|
||||
// confusion, just take the first one listed.
|
||||
return screenOrientationFromString(orientations.get(0));
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the scren orientation from a string.
|
||||
*
|
||||
* @param aStr
|
||||
* String hopefully containing a screen orientation name.
|
||||
* @return Gecko screen orientation if matched, DEFAULT_SCREEN_ORIENTATION
|
||||
* otherwise.
|
||||
*/
|
||||
public static ScreenOrientation screenOrientationFromString(String aStr) {
|
||||
if ("portrait".equals(aStr)) {
|
||||
return ScreenOrientation.PORTRAIT_PRIMARY;
|
||||
}
|
||||
else if ("landscape".equals(aStr)) {
|
||||
return ScreenOrientation.LANDSCAPE_PRIMARY;
|
||||
}
|
||||
else if ("portrait-primary".equals(aStr)) {
|
||||
return ScreenOrientation.PORTRAIT_PRIMARY;
|
||||
}
|
||||
else if ("portrait-secondary".equals(aStr)) {
|
||||
return ScreenOrientation.PORTRAIT_SECONDARY;
|
||||
}
|
||||
else if ("landscape-primary".equals(aStr)) {
|
||||
return ScreenOrientation.LANDSCAPE_PRIMARY;
|
||||
}
|
||||
else if ("landscape-secondary".equals(aStr)) {
|
||||
return ScreenOrientation.LANDSCAPE_SECONDARY;
|
||||
}
|
||||
Log.w(LOGTAG, "screenOrientationFromString: unknown orientation string");
|
||||
return DEFAULT_SCREEN_ORIENTATION;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert Gecko screen orientation to Android orientation.
|
||||
*
|
||||
* @param aScreenOrientation
|
||||
* Gecko screen orientation.
|
||||
* @return Android orientation. This conversion is lossy, the Android
|
||||
* orientation does not differentiate between primary and secondary
|
||||
* orientations.
|
||||
*/
|
||||
public static int screenOrientationToAndroidOrientation(ScreenOrientation aScreenOrientation) {
|
||||
switch (aScreenOrientation) {
|
||||
case PORTRAIT_PRIMARY:
|
||||
case PORTRAIT_SECONDARY:
|
||||
return Configuration.ORIENTATION_PORTRAIT;
|
||||
case LANDSCAPE_PRIMARY:
|
||||
case LANDSCAPE_SECONDARY:
|
||||
return Configuration.ORIENTATION_LANDSCAPE;
|
||||
case NONE:
|
||||
case DEFAULT:
|
||||
default:
|
||||
return Configuration.ORIENTATION_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert Gecko screen orientation to Android ActivityInfo orientation.
|
||||
* This is yet another orientation used by Android, but it's more detailed
|
||||
* than the Android orientation.
|
||||
* It is required for screen orientation locking and unlocking.
|
||||
*
|
||||
* @param aScreenOrientation
|
||||
* Gecko screen orientation.
|
||||
* @return Android ActivityInfo orientation.
|
||||
*/
|
||||
public static int screenOrientationToActivityInfoOrientation(ScreenOrientation aScreenOrientation) {
|
||||
switch (aScreenOrientation) {
|
||||
case PORTRAIT_PRIMARY:
|
||||
return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
||||
case PORTRAIT_SECONDARY:
|
||||
return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
|
||||
case LANDSCAPE_PRIMARY:
|
||||
return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
case LANDSCAPE_SECONDARY:
|
||||
return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
||||
case DEFAULT:
|
||||
case NONE:
|
||||
return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
default:
|
||||
return ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,229 +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 android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.util.Log;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
public class GeckoScreenOrientationListener {
|
||||
private static final String LOGTAG = "GeckoScreenOrientationListener";
|
||||
|
||||
static class OrientationEventListenerImpl extends OrientationEventListener {
|
||||
public OrientationEventListenerImpl(Context c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged(int aOrientation) {
|
||||
GeckoScreenOrientationListener.getInstance().updateScreenOrientation(aOrientation);
|
||||
}
|
||||
}
|
||||
|
||||
static private GeckoScreenOrientationListener sInstance = null;
|
||||
|
||||
// Make sure that any change in dom/base/ScreenOrientation.h happens here too.
|
||||
static public final short eScreenOrientation_None = 0;
|
||||
static public final short eScreenOrientation_PortraitPrimary = 1; // PR_BIT(0)
|
||||
static public final short eScreenOrientation_PortraitSecondary = 2; // PR_BIT(1)
|
||||
static public final short eScreenOrientation_LandscapePrimary = 4; // PR_BIT(2)
|
||||
static public final short eScreenOrientation_LandscapeSecondary = 8; // PR_BIT(3)
|
||||
static public final short eScreenOrientation_Default = 16;// PR_BIT(4)
|
||||
|
||||
static private final short DEFAULT_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
|
||||
private short mOrientation;
|
||||
private OrientationEventListenerImpl mListener = null;
|
||||
|
||||
// Whether the listener should be listening to changes.
|
||||
private boolean mShouldBeListening = false;
|
||||
// Whether the listener should notify Gecko that a change happened.
|
||||
private boolean mShouldNotify = false;
|
||||
// The default orientation to use if nothing is specified
|
||||
private short mDefaultOrientation;
|
||||
|
||||
private static final String DEFAULT_ORIENTATION_PREF = "app.orientation.default";
|
||||
|
||||
private GeckoScreenOrientationListener() {
|
||||
mListener = new OrientationEventListenerImpl(GeckoAppShell.getContext());
|
||||
|
||||
PrefsHelper.getPref(DEFAULT_ORIENTATION_PREF, new PrefsHelper.PrefHandlerBase() {
|
||||
@Override public void prefValue(String pref, String value) {
|
||||
mDefaultOrientation = orientationFromStringArray(value);
|
||||
unlockScreenOrientation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObserver() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mDefaultOrientation = DEFAULT_ORIENTATION;
|
||||
}
|
||||
|
||||
public static GeckoScreenOrientationListener getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new GeckoScreenOrientationListener();
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mShouldBeListening = true;
|
||||
updateScreenOrientation();
|
||||
|
||||
if (mShouldNotify) {
|
||||
startListening();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
mShouldBeListening = false;
|
||||
|
||||
if (mShouldNotify) {
|
||||
stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
public void enableNotifications() {
|
||||
updateScreenOrientation();
|
||||
mShouldNotify = true;
|
||||
|
||||
if (mShouldBeListening) {
|
||||
startListening();
|
||||
}
|
||||
}
|
||||
|
||||
public void disableNotifications() {
|
||||
mShouldNotify = false;
|
||||
|
||||
if (mShouldBeListening) {
|
||||
stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
private void startListening() {
|
||||
mListener.enable();
|
||||
}
|
||||
|
||||
private void stopListening() {
|
||||
mListener.disable();
|
||||
}
|
||||
|
||||
private short orientationFromStringArray(String val) {
|
||||
List<String> orientations = Arrays.asList(val.split(","));
|
||||
// if nothing is listed, return unspecified
|
||||
if (orientations.size() == 0)
|
||||
return DEFAULT_ORIENTATION;
|
||||
|
||||
// we dont' support multiple orientations yet. To avoid developer confusion,
|
||||
// just take the first one listed
|
||||
return orientationFromString(orientations.get(0));
|
||||
}
|
||||
|
||||
private short orientationFromString(String val) {
|
||||
if ("portrait".equals(val))
|
||||
return (short)ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
|
||||
else if ("landscape".equals(val))
|
||||
return (short)ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
|
||||
else if ("portrait-primary".equals(val))
|
||||
return (short)ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
||||
else if ("portrait-secondary".equals(val))
|
||||
return (short)ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
|
||||
else if ("landscape-primary".equals(val))
|
||||
return (short)ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
else if ("landscape-secondary".equals(val))
|
||||
return (short)ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
||||
return DEFAULT_ORIENTATION;
|
||||
}
|
||||
|
||||
private void updateScreenOrientation() {
|
||||
Context context = GeckoAppShell.getContext();
|
||||
int rotation = mDefaultOrientation;
|
||||
if (context instanceof Activity) {
|
||||
rotation = ((Activity)context).getWindowManager().getDefaultDisplay().getRotation();
|
||||
}
|
||||
updateScreenOrientation(rotation * 90);
|
||||
}
|
||||
|
||||
private void updateScreenOrientation(int aOrientation) {
|
||||
short previousOrientation = mOrientation;
|
||||
|
||||
if (aOrientation >= 315 || aOrientation < 45) {
|
||||
mOrientation = eScreenOrientation_PortraitPrimary;
|
||||
} else if (aOrientation >= 45 && aOrientation < 135) {
|
||||
mOrientation = eScreenOrientation_LandscapePrimary;
|
||||
} else if (aOrientation >= 135 && aOrientation < 225) {
|
||||
mOrientation = eScreenOrientation_PortraitSecondary;
|
||||
} else if (aOrientation >= 225 && aOrientation < 315) {
|
||||
mOrientation = eScreenOrientation_LandscapeSecondary;
|
||||
} else {
|
||||
Log.e(LOGTAG, "Unexpected value received! (" + aOrientation + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mShouldNotify && mOrientation != previousOrientation) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenOrientationEvent(mOrientation));
|
||||
}
|
||||
}
|
||||
|
||||
public short getScreenOrientation() {
|
||||
return mOrientation;
|
||||
}
|
||||
|
||||
public void lockScreenOrientation(int aOrientation) {
|
||||
int orientation = 0;
|
||||
|
||||
switch (aOrientation) {
|
||||
case eScreenOrientation_PortraitPrimary:
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
||||
break;
|
||||
case eScreenOrientation_PortraitSecondary:
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
|
||||
break;
|
||||
case eScreenOrientation_PortraitPrimary | eScreenOrientation_PortraitSecondary:
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
|
||||
break;
|
||||
case eScreenOrientation_LandscapePrimary:
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
break;
|
||||
case eScreenOrientation_LandscapeSecondary:
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
||||
break;
|
||||
case eScreenOrientation_LandscapePrimary | eScreenOrientation_LandscapeSecondary:
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
|
||||
break;
|
||||
case eScreenOrientation_Default:
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
break;
|
||||
default:
|
||||
Log.e(LOGTAG, "Unexpected value received! (" + aOrientation + ")");
|
||||
return;
|
||||
}
|
||||
if (GeckoAppShell.getContext() instanceof Activity)
|
||||
((Activity)GeckoAppShell.getContext()).setRequestedOrientation(orientation);
|
||||
updateScreenOrientation();
|
||||
}
|
||||
|
||||
public void unlockScreenOrientation() {
|
||||
if (!(GeckoAppShell.getContext() instanceof Activity))
|
||||
return;
|
||||
if (((Activity)GeckoAppShell.getContext()).getRequestedOrientation() == mDefaultOrientation)
|
||||
return;
|
||||
|
||||
((Activity)GeckoAppShell.getContext()).setRequestedOrientation(mDefaultOrientation);
|
||||
updateScreenOrientation();
|
||||
}
|
||||
}
|
@ -235,11 +235,11 @@ public class LocaleManager {
|
||||
|
||||
// Note that we don't tell Gecko about this. We notify Gecko when the
|
||||
// locale is set, not when we update Java.
|
||||
updateLocale(localeCode);
|
||||
final String resultant = updateLocale(localeCode);
|
||||
|
||||
final long t2 = android.os.SystemClock.uptimeMillis();
|
||||
Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
|
||||
return localeCode;
|
||||
return resultant;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,11 +202,11 @@ public class TabsTray extends TwoWayView
|
||||
// Updates the selected position in the list so that it will be scrolled to the right place.
|
||||
private void updateSelectedPosition() {
|
||||
int selected = getPositionForTab(Tabs.getInstance().getSelectedTab());
|
||||
updateSelectedStyle(selected);
|
||||
|
||||
if (selected != -1) {
|
||||
TabsTray.this.setSelection(selected);
|
||||
}
|
||||
|
||||
updateSelectedStyle(selected);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,6 +5,10 @@
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator.Property;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.R;
|
||||
@ -23,6 +27,7 @@ import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
@ -33,6 +38,22 @@ public class HomeBanner extends LinearLayout
|
||||
implements GeckoEventListener {
|
||||
private static final String LOGTAG = "GeckoHomeBanner";
|
||||
|
||||
// Used for tracking scroll length
|
||||
private float mTouchY = -1;
|
||||
|
||||
// Used to detect for upwards scroll to push banner all the way up
|
||||
private boolean mSnapBannerToTop;
|
||||
|
||||
// Tracks if the banner has been enabled by HomePager to avoid race conditions.
|
||||
private boolean mEnabled = false;
|
||||
|
||||
// The user is currently swiping between HomePager pages
|
||||
private boolean mScrollingPages = false;
|
||||
|
||||
// Tracks whether the user swiped the banner down, preventing us from autoshowing when the user
|
||||
// switches back to the default page.
|
||||
private boolean mUserSwipedDown = false;
|
||||
|
||||
public HomeBanner(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@ -82,9 +103,9 @@ public class HomeBanner extends LinearLayout
|
||||
GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
|
||||
}
|
||||
|
||||
public boolean isDismissed() {
|
||||
return (getVisibility() == View.GONE);
|
||||
}
|
||||
public void setScrollingPages(boolean scrollingPages) {
|
||||
mScrollingPages = scrollingPages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
@ -101,7 +122,8 @@ public class HomeBanner extends LinearLayout
|
||||
@Override
|
||||
public void run() {
|
||||
textView.setText(text);
|
||||
setVisibility(View.VISIBLE);
|
||||
setVisibility(VISIBLE);
|
||||
animateUp();
|
||||
}
|
||||
});
|
||||
} catch (JSONException e) {
|
||||
@ -137,4 +159,102 @@ public class HomeBanner extends LinearLayout
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
// No need to animate if not changing
|
||||
if (mEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
mEnabled = enabled;
|
||||
if (enabled) {
|
||||
animateUp();
|
||||
} else {
|
||||
animateDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void animateUp() {
|
||||
// Check to make sure that message has been received and the banner has been enabled.
|
||||
// Necessary to avoid race conditions between show() and handleMessage() calls.
|
||||
TextView textView = (TextView) findViewById(R.id.text);
|
||||
if (!mEnabled || TextUtils.isEmpty(textView.getText()) || mUserSwipedDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to animate if already translated.
|
||||
if (ViewHelper.getTranslationY(this) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PropertyAnimator animator = new PropertyAnimator(100);
|
||||
animator.attach(this, Property.TRANSLATION_Y, 0);
|
||||
animator.start();
|
||||
}
|
||||
|
||||
private void animateDown() {
|
||||
// No need to animate if already translated or gone.
|
||||
if (ViewHelper.getTranslationY(this) == getHeight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PropertyAnimator animator = new PropertyAnimator(100);
|
||||
animator.attach(this, Property.TRANSLATION_Y, getHeight());
|
||||
animator.start();
|
||||
}
|
||||
|
||||
public void handleHomeTouch(MotionEvent event) {
|
||||
if (!mEnabled || getVisibility() == GONE || mScrollingPages) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
// Track the beginning of the touch
|
||||
mTouchY = event.getRawY();
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final float curY = event.getRawY();
|
||||
final float delta = mTouchY - curY;
|
||||
mSnapBannerToTop = delta <= 0.0f;
|
||||
|
||||
final float height = getHeight();
|
||||
float newTranslationY = ViewHelper.getTranslationY(this) + delta;
|
||||
|
||||
// Clamp the values to be between 0 and height.
|
||||
if (newTranslationY < 0.0f) {
|
||||
newTranslationY = 0.0f;
|
||||
} else if (newTranslationY > height) {
|
||||
newTranslationY = height;
|
||||
}
|
||||
|
||||
// Don't change this value if it wasn't a significant movement
|
||||
if (delta >= 10 || delta <= -10) {
|
||||
mUserSwipedDown = newTranslationY == height;
|
||||
}
|
||||
|
||||
ViewHelper.setTranslationY(this, newTranslationY);
|
||||
mTouchY = curY;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
mTouchY = -1;
|
||||
final float y = ViewHelper.getTranslationY(this);
|
||||
final float height = getHeight();
|
||||
if (y > 0.0f && y < height) {
|
||||
if (mSnapBannerToTop) {
|
||||
animateUp();
|
||||
} else {
|
||||
animateDown();
|
||||
mUserSwipedDown = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -699,6 +699,7 @@ public final class HomeConfig {
|
||||
public interface HomeConfigBackend {
|
||||
public List<PanelConfig> load();
|
||||
public void save(List<PanelConfig> entries);
|
||||
public String getLocale();
|
||||
public void setOnChangeListener(OnChangeListener listener);
|
||||
}
|
||||
|
||||
@ -718,6 +719,10 @@ public final class HomeConfig {
|
||||
return mBackend.load();
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return mBackend.getLocale();
|
||||
}
|
||||
|
||||
public void save(List<PanelConfig> panelConfigs) {
|
||||
mBackend.save(panelConfigs);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
@ -38,22 +39,33 @@ public class HomeConfigInvalidator implements GeckoEventListener {
|
||||
private static final int PANEL_INFO_TIMEOUT_MSEC = 1000;
|
||||
|
||||
private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
|
||||
private static final String EVENT_HOMEPANELS_REMOVE = "HomePanels:Remove";
|
||||
private static final String EVENT_HOMEPANELS_REFRESH = "HomePanels:Refresh";
|
||||
private static final String EVENT_HOMEPANELS_UNINSTALL = "HomePanels:Uninstall";
|
||||
private static final String EVENT_HOMEPANELS_UPDATE = "HomePanels:Update";
|
||||
|
||||
private static final String JSON_KEY_PANEL = "panel";
|
||||
private static final String JSON_KEY_PANEL_ID = "id";
|
||||
|
||||
private enum ChangeType {
|
||||
REMOVE,
|
||||
UNINSTALL,
|
||||
INSTALL,
|
||||
UPDATE,
|
||||
REFRESH
|
||||
}
|
||||
|
||||
private enum InvalidationMode {
|
||||
DELAYED,
|
||||
IMMEDIATE
|
||||
}
|
||||
|
||||
private static class ConfigChange {
|
||||
private final ChangeType type;
|
||||
private final PanelConfig target;
|
||||
private final Object target;
|
||||
|
||||
public ConfigChange(ChangeType type, PanelConfig target) {
|
||||
public ConfigChange(ChangeType type) {
|
||||
this(type, null);
|
||||
}
|
||||
|
||||
public ConfigChange(ChangeType type, Object target) {
|
||||
this.type = type;
|
||||
this.target = target;
|
||||
}
|
||||
@ -74,35 +86,46 @@ public class HomeConfigInvalidator implements GeckoEventListener {
|
||||
mHomeConfig = HomeConfig.getDefault(context);
|
||||
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_INSTALL, this);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REMOVE, this);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REFRESH, this);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UNINSTALL, this);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_UPDATE, this);
|
||||
}
|
||||
|
||||
public void refreshAll() {
|
||||
handlePanelRefresh(null);
|
||||
public void onLocaleReady(final String locale) {
|
||||
ThreadUtils.getBackgroundHandler().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final String configLocale = mHomeConfig.getLocale();
|
||||
if (configLocale == null || !configLocale.equals(locale)) {
|
||||
handleLocaleChange();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
try {
|
||||
final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
|
||||
final PanelConfig panelConfig = new PanelConfig(json);
|
||||
|
||||
if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
|
||||
handlePanelInstall(panelConfig);
|
||||
} else if (event.equals(EVENT_HOMEPANELS_REMOVE)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_REMOVE);
|
||||
handlePanelRemove(panelConfig);
|
||||
} else if (event.equals(EVENT_HOMEPANELS_REFRESH)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_REFRESH);
|
||||
handlePanelRefresh(panelConfig);
|
||||
handlePanelInstall(createPanelConfigFromMessage(message));
|
||||
} else if (event.equals(EVENT_HOMEPANELS_UNINSTALL)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_UNINSTALL);
|
||||
final String panelId = message.getString(JSON_KEY_PANEL_ID);
|
||||
handlePanelUninstall(panelId);
|
||||
} else if (event.equals(EVENT_HOMEPANELS_UPDATE)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_UPDATE);
|
||||
handlePanelUpdate(createPanelConfigFromMessage(message));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Failed to handle event " + event, e);
|
||||
}
|
||||
}
|
||||
|
||||
private PanelConfig createPanelConfigFromMessage(JSONObject message) throws JSONException {
|
||||
final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
|
||||
return new PanelConfig(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko thread.
|
||||
*/
|
||||
@ -110,42 +133,54 @@ public class HomeConfigInvalidator implements GeckoEventListener {
|
||||
mPendingChanges.offer(new ConfigChange(ChangeType.INSTALL, panelConfig));
|
||||
Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation();
|
||||
scheduleInvalidation(InvalidationMode.DELAYED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko thread.
|
||||
*/
|
||||
private void handlePanelRemove(PanelConfig panelConfig) {
|
||||
mPendingChanges.offer(new ConfigChange(ChangeType.REMOVE, panelConfig));
|
||||
Log.d(LOGTAG, "handlePanelRemove: " + mPendingChanges.size());
|
||||
private void handlePanelUninstall(String panelId) {
|
||||
mPendingChanges.offer(new ConfigChange(ChangeType.UNINSTALL, panelId));
|
||||
Log.d(LOGTAG, "handlePanelUninstall: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation();
|
||||
scheduleInvalidation(InvalidationMode.DELAYED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a panel refresh in HomeConfig. Runs in the gecko thread.
|
||||
*
|
||||
* @param panelConfig the target PanelConfig instance or NULL to refresh
|
||||
* all HomeConfig entries.
|
||||
* Runs in the gecko thread.
|
||||
*/
|
||||
private void handlePanelRefresh(PanelConfig panelConfig) {
|
||||
mPendingChanges.offer(new ConfigChange(ChangeType.REFRESH, panelConfig));
|
||||
Log.d(LOGTAG, "handlePanelRefresh: " + mPendingChanges.size());
|
||||
private void handlePanelUpdate(PanelConfig panelConfig) {
|
||||
mPendingChanges.offer(new ConfigChange(ChangeType.UPDATE, panelConfig));
|
||||
Log.d(LOGTAG, "handlePanelUpdate: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation();
|
||||
scheduleInvalidation(InvalidationMode.DELAYED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private void handleLocaleChange() {
|
||||
mPendingChanges.offer(new ConfigChange(ChangeType.REFRESH));
|
||||
Log.d(LOGTAG, "handleLocaleChange: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation(InvalidationMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko or main thread.
|
||||
*/
|
||||
private void scheduleInvalidation() {
|
||||
private void scheduleInvalidation(InvalidationMode mode) {
|
||||
final Handler handler = ThreadUtils.getBackgroundHandler();
|
||||
|
||||
handler.removeCallbacks(mInvalidationRunnable);
|
||||
handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
|
||||
|
||||
Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation");
|
||||
if (mode == InvalidationMode.IMMEDIATE) {
|
||||
handler.post(mInvalidationRunnable);
|
||||
} else {
|
||||
handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
|
||||
}
|
||||
|
||||
Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation: " + mode);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,43 +199,59 @@ public class HomeConfigInvalidator implements GeckoEventListener {
|
||||
return false;
|
||||
}
|
||||
|
||||
private PanelConfig findPanelConfigWithId(List<PanelConfig> panelConfigs, String panelId) {
|
||||
for (PanelConfig panelConfig : panelConfigs) {
|
||||
if (panelConfig.getId().equals(panelId)) {
|
||||
return panelConfig;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private List<PanelConfig> executePendingChanges(List<PanelConfig> panelConfigs) {
|
||||
boolean shouldRefreshAll = false;
|
||||
boolean shouldRefresh = false;
|
||||
|
||||
while (!mPendingChanges.isEmpty()) {
|
||||
final ConfigChange pendingChange = mPendingChanges.poll();
|
||||
final PanelConfig panelConfig = pendingChange.target;
|
||||
|
||||
switch (pendingChange.type) {
|
||||
case REMOVE:
|
||||
if (panelConfigs.remove(panelConfig)) {
|
||||
case UNINSTALL: {
|
||||
final String panelId = (String) pendingChange.target;
|
||||
final PanelConfig panelConfig = findPanelConfigWithId(panelConfigs, panelId);
|
||||
if (panelConfig != null && panelConfigs.remove(panelConfig)) {
|
||||
Log.d(LOGTAG, "executePendingChanges: removed panel " + panelConfig.getId());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case INSTALL:
|
||||
case INSTALL: {
|
||||
final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
|
||||
if (!replacePanelConfig(panelConfigs, panelConfig)) {
|
||||
panelConfigs.add(panelConfig);
|
||||
Log.d(LOGTAG, "executePendingChanges: added panel " + panelConfig.getId());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REFRESH:
|
||||
if (panelConfig != null) {
|
||||
if (!replacePanelConfig(panelConfigs, panelConfig)) {
|
||||
Log.w(LOGTAG, "Tried to refresh non-existing panel " + panelConfig.getId());
|
||||
}
|
||||
} else {
|
||||
shouldRefreshAll = true;
|
||||
case UPDATE: {
|
||||
final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
|
||||
if (!replacePanelConfig(panelConfigs, panelConfig)) {
|
||||
Log.w(LOGTAG, "Tried to update non-existing panel " + panelConfig.getId());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REFRESH: {
|
||||
shouldRefresh = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRefreshAll) {
|
||||
if (shouldRefresh) {
|
||||
return executeRefresh(panelConfigs);
|
||||
} else {
|
||||
return panelConfigs;
|
||||
|
@ -29,11 +29,13 @@ import android.util.Log;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
private static final String LOGTAG = "GeckoHomeConfigBackend";
|
||||
|
||||
private static final String PREFS_KEY = "home_panels";
|
||||
private static final String PREFS_CONFIG_KEY = "home_panels";
|
||||
private static final String PREFS_LOCALE_KEY = "home_locale";
|
||||
|
||||
private final Context mContext;
|
||||
private PrefsListener mPrefsListener;
|
||||
@ -104,7 +106,7 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
@Override
|
||||
public List<PanelConfig> load() {
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
final String jsonString = prefs.getString(PREFS_KEY, null);
|
||||
final String jsonString = prefs.getString(PREFS_CONFIG_KEY, null);
|
||||
|
||||
final List<PanelConfig> panelConfigs;
|
||||
if (TextUtils.isEmpty(jsonString)) {
|
||||
@ -135,10 +137,36 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
final String jsonString = jsonPanelConfigs.toString();
|
||||
editor.putString(PREFS_KEY, jsonString);
|
||||
editor.putString(PREFS_CONFIG_KEY, jsonString);
|
||||
editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocale() {
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
|
||||
String locale = prefs.getString(PREFS_LOCALE_KEY, null);
|
||||
if (locale == null) {
|
||||
// Initialize config with the current locale
|
||||
final String currentLocale = Locale.getDefault().toString();
|
||||
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(PREFS_LOCALE_KEY, currentLocale);
|
||||
editor.commit();
|
||||
|
||||
// If the user has saved HomeConfig before, return null this
|
||||
// one time to trigger a refresh and ensure we use the
|
||||
// correct locale for the saved state. For more context,
|
||||
// see HomeConfigInvalidator.onLocaleReady().
|
||||
if (!prefs.contains(PREFS_CONFIG_KEY)) {
|
||||
locale = currentLocale;
|
||||
}
|
||||
}
|
||||
|
||||
return locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnChangeListener(OnChangeListener listener) {
|
||||
final SharedPreferences prefs = getSharedPreferences();
|
||||
@ -159,7 +187,7 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
||||
private class PrefsListener implements OnSharedPreferenceChangeListener {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
if (TextUtils.equals(key, PREFS_KEY)) {
|
||||
if (TextUtils.equals(key, PREFS_CONFIG_KEY)) {
|
||||
mChangeListener.onChange();
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ public class HomePager extends ViewPager {
|
||||
private volatile boolean mLoaded;
|
||||
private Decor mDecor;
|
||||
private View mTabStrip;
|
||||
private HomeBanner mHomeBanner;
|
||||
private int mDefaultPageIndex = -1;
|
||||
|
||||
private final OnAddPanelListener mAddPanelListener;
|
||||
|
||||
@ -49,9 +51,6 @@ public class HomePager extends ViewPager {
|
||||
|
||||
private String mInitialPanelId;
|
||||
|
||||
// Whether or not we need to restart the loader when we show the HomePager.
|
||||
private boolean mRestartLoader;
|
||||
|
||||
// Cached original ViewPager background.
|
||||
private final Drawable mOriginalBackground;
|
||||
|
||||
@ -128,6 +127,7 @@ public class HomePager extends ViewPager {
|
||||
setFocusableInTouchMode(true);
|
||||
|
||||
mOriginalBackground = getBackground();
|
||||
setOnPageChangeListener(new PageChangeListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -143,21 +143,6 @@ public class HomePager extends ViewPager {
|
||||
setCurrentItem(index, true);
|
||||
}
|
||||
});
|
||||
|
||||
setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
mDecor.onPageSelected(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) { }
|
||||
});
|
||||
} else if (child instanceof HomePagerTabStrip) {
|
||||
mTabStrip = child;
|
||||
}
|
||||
@ -165,41 +150,12 @@ public class HomePager extends ViewPager {
|
||||
super.addView(child, index, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the current configuration, redisplaying the HomePager if necessary.
|
||||
*/
|
||||
public void invalidate(LoaderManager lm, FragmentManager fm) {
|
||||
// We need to restart the loader to load the new strings.
|
||||
mRestartLoader = true;
|
||||
|
||||
// If the HomePager is currently visible, redisplay it with the new strings.
|
||||
if (isVisible()) {
|
||||
redisplay(lm, fm);
|
||||
}
|
||||
}
|
||||
|
||||
private void redisplay(LoaderManager lm, FragmentManager fm) {
|
||||
final HomeAdapter adapter = (HomeAdapter) getAdapter();
|
||||
|
||||
// If mInitialPanelId is non-null, this means the HomePager hasn't
|
||||
// finished loading its config yet. Simply re-show() with the
|
||||
// current target panel.
|
||||
final String currentPanelId;
|
||||
if (mInitialPanelId != null) {
|
||||
currentPanelId = mInitialPanelId;
|
||||
} else {
|
||||
currentPanelId = adapter.getPanelIdAtPosition(getCurrentItem());
|
||||
}
|
||||
|
||||
show(lm, fm, currentPanelId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and initializes the pager.
|
||||
*
|
||||
* @param fm FragmentManager for the adapter
|
||||
*/
|
||||
public void show(LoaderManager lm, FragmentManager fm, String panelId, PropertyAnimator animator) {
|
||||
public void load(LoaderManager lm, FragmentManager fm, String panelId, PropertyAnimator animator) {
|
||||
mLoaded = true;
|
||||
mInitialPanelId = panelId;
|
||||
|
||||
@ -211,19 +167,12 @@ public class HomePager extends ViewPager {
|
||||
adapter.setCanLoadHint(!shouldAnimate);
|
||||
setAdapter(adapter);
|
||||
|
||||
setVisibility(VISIBLE);
|
||||
|
||||
// Don't show the tabs strip until we have the
|
||||
// list of panels in place.
|
||||
mTabStrip.setVisibility(View.INVISIBLE);
|
||||
|
||||
// Load list of panels from configuration. Restart the loader if necessary.
|
||||
if (mRestartLoader) {
|
||||
lm.restartLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
|
||||
mRestartLoader = false;
|
||||
} else {
|
||||
lm.initLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
|
||||
}
|
||||
// Load list of panels from configuration
|
||||
lm.initLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
|
||||
|
||||
if (shouldAnimate) {
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@ -248,23 +197,22 @@ public class HomePager extends ViewPager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the pager and removes all child fragments.
|
||||
* Removes all child fragments to free memory.
|
||||
*/
|
||||
public void hide() {
|
||||
public void unload() {
|
||||
mLoaded = false;
|
||||
setVisibility(GONE);
|
||||
setAdapter(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the pager is visible.
|
||||
* Determines whether the pager is loaded.
|
||||
*
|
||||
* Unlike getVisibility(), this method does not need to be called on the UI
|
||||
* thread.
|
||||
*
|
||||
* @return Whether the pager and its fragments are being displayed
|
||||
* @return Whether the pager and its fragments are loaded
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
public boolean isLoaded() {
|
||||
return mLoaded;
|
||||
}
|
||||
|
||||
@ -287,6 +235,19 @@ public class HomePager extends ViewPager {
|
||||
return super.onInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
public void setBanner(HomeBanner banner) {
|
||||
mHomeBanner = banner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
if (mHomeBanner != null) {
|
||||
mHomeBanner.handleHomeTouch(event);
|
||||
}
|
||||
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
private void updateUiFromPanelConfigs(List<PanelConfig> panelConfigs) {
|
||||
// We only care about the adapter if HomePager is currently
|
||||
// loaded, which means it's visible in the activity.
|
||||
@ -333,7 +294,7 @@ public class HomePager extends ViewPager {
|
||||
setAdapter(adapter);
|
||||
|
||||
// Use the default panel as defined in the HomePager's configuration
|
||||
// if the initial panel wasn't explicitly set by the show() caller,
|
||||
// if the initial panel wasn't explicitly set by the load() caller,
|
||||
// or if the initial panel is not found in the adapter.
|
||||
final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
|
||||
if (itemPosition > -1) {
|
||||
@ -343,6 +304,7 @@ public class HomePager extends ViewPager {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final PanelConfig panelConfig = enabledPanels.get(i);
|
||||
if (panelConfig.isDefault()) {
|
||||
mDefaultPageIndex = i;
|
||||
setCurrentItem(i, false);
|
||||
break;
|
||||
}
|
||||
@ -365,4 +327,31 @@ public class HomePager extends ViewPager {
|
||||
public void onLoaderReset(Loader<List<PanelConfig>> loader) {
|
||||
}
|
||||
}
|
||||
|
||||
private class PageChangeListener implements ViewPager.OnPageChangeListener {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
if (mDecor != null) {
|
||||
mDecor.onPageSelected(position);
|
||||
}
|
||||
|
||||
if (mHomeBanner != null) {
|
||||
mHomeBanner.setEnabled(position == mDefaultPageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
if (mDecor != null) {
|
||||
mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
|
||||
}
|
||||
|
||||
if (mHomeBanner != null) {
|
||||
mHomeBanner.setScrollingPages(positionOffsetPixels != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) { }
|
||||
}
|
||||
}
|
||||
|
@ -87,15 +87,6 @@ public class TopSitesPanel extends HomeFragment {
|
||||
// Grid of top sites
|
||||
private TopSitesGridView mGrid;
|
||||
|
||||
// Banner to show snippets.
|
||||
private HomeBanner mBanner;
|
||||
|
||||
// Raw Y value of the last event that happened on the list view.
|
||||
private float mListTouchY = -1;
|
||||
|
||||
// Scrolling direction of the banner.
|
||||
private boolean mSnapBannerToTop;
|
||||
|
||||
// Callbacks used for the search and favicon cursor loaders
|
||||
private CursorLoaderCallbacks mCursorLoaderCallbacks;
|
||||
|
||||
@ -226,15 +217,6 @@ public class TopSitesPanel extends HomeFragment {
|
||||
|
||||
registerForContextMenu(mList);
|
||||
registerForContextMenu(mGrid);
|
||||
|
||||
mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
|
||||
mList.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
TopSitesPanel.this.handleListTouchEvent(event);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -473,60 +455,6 @@ public class TopSitesPanel extends HomeFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleListTouchEvent(MotionEvent event) {
|
||||
// Ignore the event if the banner is hidden for this session.
|
||||
if (mBanner.isDismissed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mListTouchY = event.getRawY();
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
// There is a chance that we won't receive ACTION_DOWN, if the touch event
|
||||
// actually started on the Grid instead of the List. Treat this as first event.
|
||||
if (mListTouchY == -1) {
|
||||
mListTouchY = event.getRawY();
|
||||
return;
|
||||
}
|
||||
|
||||
final float curY = event.getRawY();
|
||||
final float delta = mListTouchY - curY;
|
||||
mSnapBannerToTop = (delta > 0.0f) ? false : true;
|
||||
|
||||
final float height = mBanner.getHeight();
|
||||
float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
|
||||
|
||||
// Clamp the values to be between 0 and height.
|
||||
if (newTranslationY < 0.0f) {
|
||||
newTranslationY = 0.0f;
|
||||
} else if (newTranslationY > height) {
|
||||
newTranslationY = height;
|
||||
}
|
||||
|
||||
ViewHelper.setTranslationY(mBanner, newTranslationY);
|
||||
mListTouchY = curY;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
mListTouchY = -1;
|
||||
final float y = ViewHelper.getTranslationY(mBanner);
|
||||
final float height = mBanner.getHeight();
|
||||
if (y > 0.0f && y < height) {
|
||||
final PropertyAnimator animator = new PropertyAnimator(100);
|
||||
animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
|
||||
animator.start();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUiFromCursor(Cursor c) {
|
||||
mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import android.widget.ListView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -636,6 +637,18 @@ public class GeckoMenu extends ListView
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
for (Iterator<GeckoMenuItem> i = mPrimaryActionItems.keySet().iterator(); i.hasNext();) {
|
||||
GeckoMenuItem item = i.next();
|
||||
item.refreshIfChanged();
|
||||
}
|
||||
|
||||
for (Iterator<GeckoMenuItem> i = mSecondaryActionItems.keySet().iterator(); i.hasNext();) {
|
||||
GeckoMenuItem item = i.next();
|
||||
item.refreshIfChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// Adapter to bind menu items to the list.
|
||||
private class MenuItemsAdapter extends BaseAdapter {
|
||||
private static final int VIEW_TYPE_DEFAULT = 0;
|
||||
|
@ -222,6 +222,17 @@ public class GeckoMenuItem implements MenuItem {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void refreshIfChanged() {
|
||||
if (mActionProvider == null)
|
||||
return;
|
||||
|
||||
if (mActionProvider instanceof GeckoActionProvider) {
|
||||
if (((GeckoActionProvider) mActionProvider).hasChanged()) {
|
||||
mShowAsActionChangedListener.onShowAsActionChanged(GeckoMenuItem.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setActionView(int resId) {
|
||||
return this;
|
||||
|
@ -154,7 +154,7 @@ gbjar.sources += [
|
||||
'GeckoMessageReceiver.java',
|
||||
'GeckoNetworkManager.java',
|
||||
'GeckoProfile.java',
|
||||
'GeckoScreenOrientationListener.java',
|
||||
'GeckoScreenOrientation.java',
|
||||
'GeckoSmsManager.java',
|
||||
'GeckoThread.java',
|
||||
'GeckoUpdateReceiver.java',
|
||||
|
@ -11,8 +11,7 @@
|
||||
android:id="@+id/home_pager"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@android:color/white"
|
||||
android:visibility="gone">
|
||||
android:background="@android:color/white">
|
||||
|
||||
<org.mozilla.gecko.home.TabMenuStrip android:layout_width="fill_parent"
|
||||
android:layout_height="32dip"
|
||||
|
@ -35,6 +35,17 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"/>
|
||||
|
||||
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
|
||||
style="@style/Widget.HomeBanner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="@dimen/home_banner_height"
|
||||
android:background="@drawable/home_banner"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:translationY="@dimen/home_banner_height"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
@ -11,8 +11,7 @@
|
||||
android:id="@+id/home_pager"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@android:color/white"
|
||||
android:visibility="gone">
|
||||
android:background="@android:color/white">
|
||||
|
||||
<org.mozilla.gecko.home.HomePagerTabStrip android:layout_width="fill_parent"
|
||||
android:layout_height="32dip"
|
||||
|
@ -3,26 +3,8 @@
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.mozilla.gecko.home.HomeListView
|
||||
android:id="@+id/list"
|
||||
style="@style/Widget.TopSitesListView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"/>
|
||||
|
||||
<org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
|
||||
style="@style/Widget.HomeBanner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="@dimen/home_banner_height"
|
||||
android:background="@drawable/home_banner"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
|
||||
</FrameLayout>
|
||||
<org.mozilla.gecko.home.HomeListView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/list"
|
||||
style="@style/Widget.TopSitesListView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"/>
|
||||
|
@ -483,12 +483,12 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
|
||||
}
|
||||
|
||||
public final void verifyHomePagerHidden() {
|
||||
final View homePagerView = mSolo.getView(R.id.home_pager);
|
||||
final View homePagerContainer = mSolo.getView(R.id.home_pager_container);
|
||||
|
||||
boolean rc = waitForCondition(new Condition() {
|
||||
@Override
|
||||
public boolean isSatisfied() {
|
||||
return homePagerView.getVisibility() != View.VISIBLE;
|
||||
return homePagerContainer.getVisibility() != View.VISIBLE;
|
||||
}
|
||||
}, MAX_WAIT_HOME_PAGER_HIDDEN_MS);
|
||||
|
||||
|
@ -59,6 +59,10 @@ public class AboutHomeComponent extends BaseComponent {
|
||||
super(testContext);
|
||||
}
|
||||
|
||||
private View getHomePagerContainer() {
|
||||
return mSolo.getView(R.id.home_pager_container);
|
||||
}
|
||||
|
||||
private ViewPager getHomePagerView() {
|
||||
return (ViewPager) mSolo.getView(R.id.home_pager);
|
||||
}
|
||||
@ -77,26 +81,32 @@ public class AboutHomeComponent extends BaseComponent {
|
||||
}
|
||||
|
||||
public AboutHomeComponent assertNotVisible() {
|
||||
assertFalse("The HomePager is not visible",
|
||||
getHomePagerView().getVisibility() == View.VISIBLE);
|
||||
assertTrue("The HomePager is not visible",
|
||||
getHomePagerContainer().getVisibility() != View.VISIBLE ||
|
||||
getHomePagerView().getVisibility() != View.VISIBLE);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AboutHomeComponent assertVisible() {
|
||||
assertEquals("The HomePager is visible",
|
||||
View.VISIBLE, getHomePagerView().getVisibility());
|
||||
assertTrue("The HomePager is visible",
|
||||
getHomePagerContainer().getVisibility() == View.VISIBLE &&
|
||||
getHomePagerView().getVisibility() == View.VISIBLE);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AboutHomeComponent assertBannerNotVisible() {
|
||||
assertFalse("The HomeBanner is not visible",
|
||||
getHomeBannerView().getVisibility() == View.VISIBLE);
|
||||
View banner = getHomeBannerView();
|
||||
assertTrue("The HomeBanner is not visible",
|
||||
getHomePagerContainer().getVisibility() != View.VISIBLE ||
|
||||
banner.getVisibility() != View.VISIBLE ||
|
||||
banner.getTranslationY() == banner.getHeight());
|
||||
return this;
|
||||
}
|
||||
|
||||
public AboutHomeComponent assertBannerVisible() {
|
||||
assertEquals("The HomeBanner is visible",
|
||||
View.VISIBLE, getHomeBannerView().getVisibility());
|
||||
assertTrue("The HomeBanner is visible",
|
||||
getHomePagerContainer().getVisibility() == View.VISIBLE &&
|
||||
getHomeBannerView().getVisibility() == View.VISIBLE);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,9 @@ public class testHomeBanner extends UITest {
|
||||
|
||||
// These test methods depend on being run in this order.
|
||||
addBannerTest();
|
||||
removeBannerTest();
|
||||
// TODO: API doesn't actually support this but it used to work due to how the banner was
|
||||
// part of TopSitesPanel's lifecycle
|
||||
// removeBannerTest();
|
||||
|
||||
// Make sure to test dismissing the banner after everything else, since dismissing
|
||||
// the banner will prevent it from showing up again.
|
||||
@ -41,7 +43,8 @@ public class testHomeBanner extends UITest {
|
||||
// Load about:home again, and make sure the onshown handler is called.
|
||||
Actions.EventExpecter eventExpecter = getActions().expectGeckoEvent("TestHomeBanner:MessageShown");
|
||||
NavigationHelper.enterAndLoadUrl("about:home");
|
||||
eventExpecter.blockForEvent();
|
||||
// TODO: Add shown event passing from Java: bug 974723
|
||||
// eventExpecter.blockForEvent();
|
||||
|
||||
// Verify that the banner is visible with the correct text.
|
||||
mAboutHome.assertBannerText(TEXT);
|
||||
@ -54,10 +57,7 @@ public class testHomeBanner extends UITest {
|
||||
// Verify that the banner isn't visible after navigating away from about:home.
|
||||
NavigationHelper.enterAndLoadUrl("about:firefox");
|
||||
|
||||
// AboutHomeComponent calls mSolo.getView, which will fail an assertion if the
|
||||
// view is not present, so we need to use findViewById in this case.
|
||||
final View banner = getActivity().findViewById(R.id.home_banner);
|
||||
assertTrue("The HomeBanner is not visible", banner == null || banner.getVisibility() != View.VISIBLE);
|
||||
mAboutHome.assertBannerNotVisible();
|
||||
}
|
||||
|
||||
|
||||
|
@ -319,6 +319,8 @@ public class ActivityChooserModel extends DataSetObservable {
|
||||
*/
|
||||
private boolean mReloadActivities = false;
|
||||
|
||||
private long mLastChanged = 0;
|
||||
|
||||
/**
|
||||
* Policy for controlling how the model handles chosen activities.
|
||||
*/
|
||||
@ -745,6 +747,7 @@ public class ActivityChooserModel extends DataSetObservable {
|
||||
ResolveInfo resolveInfo = resolveInfos.get(i);
|
||||
mActivities.add(new ActivityResolveInfo(resolveInfo));
|
||||
}
|
||||
mLastChanged = System.currentTimeMillis();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -1220,7 +1223,11 @@ public class ActivityChooserModel extends DataSetObservable {
|
||||
}
|
||||
|
||||
mReloadActivities = true;
|
||||
mLastChanged = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getLastChanged() {
|
||||
return mLastChanged;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import android.view.View.OnClickListener;
|
||||
|
||||
public class GeckoActionProvider extends ActionProvider {
|
||||
private static int MAX_HISTORY_SIZE = 2;
|
||||
private long mLastChanged = 0;
|
||||
|
||||
/**
|
||||
* A listener to know when a target was selected.
|
||||
@ -79,6 +80,14 @@ public class GeckoActionProvider extends ActionProvider {
|
||||
return onCreateActionView();
|
||||
}
|
||||
|
||||
public boolean hasChanged() {
|
||||
ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
|
||||
long lastChanged = dataModel.getLastChanged();
|
||||
boolean ret = lastChanged != mLastChanged;
|
||||
mLastChanged = lastChanged;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubMenu() {
|
||||
return true;
|
||||
|
@ -42,9 +42,11 @@ var HelperApps = {
|
||||
|
||||
get defaultHtmlHandlers() {
|
||||
delete this.defaultHtmlHandlers;
|
||||
let handlers = this.getAppsForUri(Services.io.newURI("http://www.example.com/index.html", null, null));
|
||||
|
||||
this.defaultHtmlHandlers = {};
|
||||
let handlers = this.getAppsForUri(Services.io.newURI("http://www.example.com/index.html", null, null), {
|
||||
filterHtml: false
|
||||
});
|
||||
|
||||
handlers.forEach(function(app) {
|
||||
this.defaultHtmlHandlers[app.name] = app;
|
||||
}, this);
|
||||
|
@ -210,6 +210,12 @@ let HomePanels = (function () {
|
||||
return false;
|
||||
};
|
||||
|
||||
let _assertPanelExists = function(id) {
|
||||
if (!(id in _panels)) {
|
||||
throw "Home.panels: Panel doesn't exist: id = " + id;
|
||||
}
|
||||
};
|
||||
|
||||
return Object.freeze({
|
||||
// Valid layouts for a panel.
|
||||
Layout: Object.freeze({
|
||||
@ -222,12 +228,6 @@ let HomePanels = (function () {
|
||||
GRID: "grid"
|
||||
}),
|
||||
|
||||
// Valid actions for a panel.
|
||||
Action: Object.freeze({
|
||||
INSTALL: "install",
|
||||
REFRESH: "refresh"
|
||||
}),
|
||||
|
||||
// Valid item types for a panel view.
|
||||
Item: Object.freeze({
|
||||
ARTICLE: "article",
|
||||
@ -240,18 +240,16 @@ let HomePanels = (function () {
|
||||
INTENT: "intent"
|
||||
}),
|
||||
|
||||
add: function(options) {
|
||||
register: function(options) {
|
||||
let panel = new Panel(options);
|
||||
if (!panel.id || !panel.title) {
|
||||
throw "Home.panels: Can't create a home panel without an id and title!";
|
||||
|
||||
// Bail if the panel already exists
|
||||
if (panel.id in _panels) {
|
||||
throw "Home.panels: Panel already exists: id = " + panel.id;
|
||||
}
|
||||
|
||||
let action = options.action;
|
||||
|
||||
// Bail if the panel already exists, except when we're refreshing
|
||||
// an existing panel instance.
|
||||
if (panel.id in _panels && action != this.Action.REFRESH) {
|
||||
throw "Home.panels: Panel already exists: id = " + panel.id;
|
||||
if (!panel.id || !panel.title) {
|
||||
throw "Home.panels: Can't create a home panel without an id and title!";
|
||||
}
|
||||
|
||||
if (!_valueExists(this.Layout, panel.layout)) {
|
||||
@ -288,41 +286,38 @@ let HomePanels = (function () {
|
||||
}
|
||||
|
||||
_panels[panel.id] = panel;
|
||||
|
||||
if (action) {
|
||||
let messageType;
|
||||
|
||||
switch(action) {
|
||||
case this.Action.INSTALL:
|
||||
messageType = "HomePanels:Install";
|
||||
break;
|
||||
|
||||
case this.Action.REFRESH:
|
||||
messageType = "HomePanels:Refresh";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Home.panels: Invalid action for panel: panel.id = " + panel.id + ", action = " + action;
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
type: messageType,
|
||||
panel: _panelToJSON(panel)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
if (!(id in _panels)) {
|
||||
throw "Home.panels: Panel doesn't exist: id = " + id;
|
||||
}
|
||||
unregister: function(id) {
|
||||
_assertPanelExists(id);
|
||||
|
||||
let panel = _panels[id];
|
||||
delete _panels[id];
|
||||
},
|
||||
|
||||
install: function(id) {
|
||||
_assertPanelExists(id);
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Remove",
|
||||
panel: _panelToJSON(panel)
|
||||
type: "HomePanels:Install",
|
||||
panel: _panelToJSON(_panels[id])
|
||||
});
|
||||
},
|
||||
|
||||
uninstall: function(id) {
|
||||
_assertPanelExists(id);
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Uninstall",
|
||||
id: id
|
||||
});
|
||||
},
|
||||
|
||||
update: function(id) {
|
||||
_assertPanelExists(id);
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Update",
|
||||
panel: _panelToJSON(_panels[id])
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -275,12 +275,6 @@ WebappsActor.prototype = {
|
||||
// Move manifest.webapp to the destination directory.
|
||||
// The destination directory for this app.
|
||||
let installDir = DOMApplicationRegistry._getAppDir(aId);
|
||||
try {
|
||||
installDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
// Ignore the exception if the directory already exists.
|
||||
}
|
||||
|
||||
if (aManifest) {
|
||||
let manFile = OS.Path.join(installDir.path, "manifest.webapp");
|
||||
return DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest)).then(() => {
|
||||
@ -400,12 +394,6 @@ WebappsActor.prototype = {
|
||||
// we can move application.zip to the destination directory, and
|
||||
// extract manifest.webapp there.
|
||||
let installDir = DOMApplicationRegistry._getAppDir(id);
|
||||
try {
|
||||
installDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
// Ignore the exception if the directory already exists.
|
||||
}
|
||||
|
||||
let manFile = installDir.clone();
|
||||
manFile.append("manifest.webapp");
|
||||
zipReader.extract("manifest.webapp", manFile);
|
||||
|
Loading…
Reference in New Issue
Block a user