Merge m-c to inbound

This commit is contained in:
Wes Kocher 2014-02-21 18:55:53 -08:00
commit 04c6720e07
59 changed files with 1242 additions and 822 deletions

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
{
"revision": "5b157e823d42532a365d33cfe3a8bd9ba4b71152",
"revision": "2741c63d7513126ea0a7f033266a4230334dfc53",
"repo_path": "/integration/gaia-central"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

@ -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"].

View File

@ -101,6 +101,7 @@ const WebProgress = {
if (aTab == Browser.selectedTab) {
BrowserUI.updateURI();
BrowserUI.update();
BrowserUI.updateStartURIAttributes(aJson.location);
}
let locationHasChanged = (location != aTab.browser.lastLocation);

View File

@ -262,7 +262,6 @@ var BrowserUI = {
},
showContent: function showContent(aURI) {
this.updateStartURIAttributes(aURI);
ContextUI.dismissTabs();
ContextUI.dismissContextAppbar();
FlyoutPanelsUI.hide();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View 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;
}
}
}

View File

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

View File

@ -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;
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -154,7 +154,7 @@ gbjar.sources += [
'GeckoMessageReceiver.java',
'GeckoNetworkManager.java',
'GeckoProfile.java',
'GeckoScreenOrientationListener.java',
'GeckoScreenOrientation.java',
'GeckoSmsManager.java',
'GeckoThread.java',
'GeckoUpdateReceiver.java',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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