Merge m-c to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-04-24 13:20:37 +02:00
commit 9f6bd57bb5
50 changed files with 1948 additions and 1413 deletions

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="0292e64ef8451df104dcf9ac3b2c6749b81684dd"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
@ -128,7 +128,7 @@
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9a00b3db898a83ef0766baa93e935e525572899e"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bb11a0417efa7e6a08ed1cb2d5d6a13a3100abd3"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="72e3a520e3c700839f07ba0113fd527b923c3330"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="baaf899afb158b9530690002f3656e958e3eb047"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="65fba428f8d76336b33ddd9e15900357953600ba">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="0292e64ef8451df104dcf9ac3b2c6749b81684dd"/>

View File

@ -18,7 +18,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "9f480d1c52a46cac8e9a0be04bdb0d73810bc596",
"revision": "2eae294604eb70e0e6eaee76b17e155ffd031107",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -39,7 +39,6 @@ let CustomizationHandler = {
UpdateUrlbarSearchSplitterState();
CombinedStopReload.uninit();
CombinedBackForward.uninit();
PlacesToolbarHelper.customizeStart();
DownloadsButton.customizeStart();
@ -87,7 +86,6 @@ let CustomizationHandler = {
// The url bar splitter state is dependent on whether stop/reload
// and the location bar are combined, so we need this ordering
CombinedStopReload.init();
CombinedBackForward.init();
UpdateUrlbarSearchSplitterState();
// Update the urlbar

View File

@ -260,15 +260,7 @@ function UpdateBackForwardCommands(aWebNavigation) {
backBroadcaster.setAttribute("disabled", true);
}
let canGoForward = aWebNavigation.canGoForward;
if (forwardDisabled) {
// Force the button to either be hidden (if we are already disabled,
// and should be), or to show if we're about to un-disable it:
// otherwise no transition will occur and it'll never show:
CombinedBackForward.setForwardButtonOcclusion(!canGoForward);
}
if (forwardDisabled == canGoForward) {
if (forwardDisabled == aWebNavigation.canGoForward) {
if (forwardDisabled)
forwardBroadcaster.removeAttribute("disabled");
else
@ -920,7 +912,6 @@ var gBrowserInit = {
// Misc. inits.
CombinedStopReload.init();
CombinedBackForward.init();
gPrivateBrowsingUI.init();
TabsInTitlebar.init();
@ -1267,7 +1258,6 @@ var gBrowserInit = {
// uninit methods don't depend on the services having been initialized).
CombinedStopReload.uninit();
CombinedBackForward.uninit();
gGestureSupport.init(false);
@ -3799,7 +3789,6 @@ var XULBrowserWindow = {
onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
if (FullZoom.updateBackgroundTabs)
FullZoom.onLocationChange(gBrowser.currentURI, true);
CombinedBackForward.setForwardButtonOcclusion(!gBrowser.webProgress.canGoForward);
var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
// use a pseudo-object instead of a (potentially nonexistent) channel for getting
@ -3874,43 +3863,6 @@ var LinkTargetDisplay = {
}
};
let CombinedBackForward = {
init: function() {
this.forwardButton = document.getElementById("forward-button");
// Add a transition listener to the url bar to hide the forward button
// when necessary
if (gURLBar)
gURLBar.addEventListener("transitionend", this);
// On startup, or if the user customizes, our listener isn't attached,
// and no transitions fire anyway, so we need to make sure we've hidden the
// button if necessary:
if (this.forwardButton && this.forwardButton.hasAttribute("disabled")) {
this.setForwardButtonOcclusion(true);
}
},
uninit: function() {
if (gURLBar)
gURLBar.removeEventListener("transitionend", this);
},
handleEvent: function(aEvent) {
if (aEvent.type == "transitionend" &&
(aEvent.propertyName == "margin-left" || aEvent.propertyName == "margin-right") &&
this.forwardButton.hasAttribute("disabled")) {
this.setForwardButtonOcclusion(true);
}
},
setForwardButtonOcclusion: function(shouldBeOccluded) {
if (!this.forwardButton)
return;
let hasAttribute = this.forwardButton.hasAttribute("occluded-by-urlbar");
if (shouldBeOccluded && !hasAttribute)
this.forwardButton.setAttribute("occluded-by-urlbar", "true");
else if (!shouldBeOccluded && hasAttribute)
this.forwardButton.removeAttribute("occluded-by-urlbar");
}
}
var CombinedStopReload = {
init: function () {
if (this._initialized)

View File

@ -656,21 +656,14 @@
onclick="checkForMiddleClick(this, event);"
tooltip="back-button-tooltip"
context="backForwardMenu"/>
<toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
label="&forwardCmd.label;"
command="Browser:ForwardOrForwardDuplicate"
cui-areatype="toolbar"
onclick="checkForMiddleClick(this, event);"
tooltip="forward-button-tooltip"
context="backForwardMenu"/>
<dummyobservertarget hidden="true"
onbroadcast="if (this.getAttribute('disabled') == 'true')
this.parentNode.setAttribute('forwarddisabled', 'true');
else
this.parentNode.removeAttribute('forwarddisabled');">
<observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/>
</dummyobservertarget>
<hbox id="urlbar-wrapper" flex="1" align="center">
<hbox id="urlbar-wrapper" flex="1">
<toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
label="&forwardCmd.label;"
command="Browser:ForwardOrForwardDuplicate"
cui-areatype="toolbar"
onclick="checkForMiddleClick(this, event);"
tooltip="forward-button-tooltip"
context="backForwardMenu"/>
<textbox id="urlbar" flex="1"
placeholder="&urlbar.placeholder2;"
type="autocomplete"
@ -1188,22 +1181,13 @@
<svg:svg height="0">
#include tab-shape.inc.svg
#ifndef XP_MACOSX
<svg:clipPath id="keyhole-forward-clip-path" clipPathUnits="objectBoundingBox">
<svg:path d="m 0,0 c .3,.25 .3,.75, 0,1 l 1,0 0,-1 z"/>
</svg:clipPath>
<svg:clipPath id="urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
<svg:path d="m 0,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22l10000,0 l 0,-50 l -10000,0 z"/>
</svg:clipPath>
#ifndef XP_MACOSX
<svg:path d="m 1,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22l10000,0 l 0,-50 l -10000,0 z"/>
#else
<svg:clipPath id="osx-keyhole-forward-clip-path" clipPathUnits="userSpaceOnUse">
<svg:path d="M 0,0 a 16 16 0 0 1 0,24 l 10000,0 l 0,-24 l -10000,0 z"/>
</svg:clipPath>
<svg:clipPath id="osx-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
<svg:path d="M -12,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
</svg:clipPath>
<svg:path d="M -11,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
#endif
</svg:clipPath>
</svg:svg>
</vbox>

View File

@ -1012,12 +1012,12 @@
this.mCurrentTab = this.tabContainer.selectedItem;
this.showTab(this.mCurrentTab);
var backForwardContainer = document.getElementById("urlbar-container");
if (backForwardContainer) {
backForwardContainer.setAttribute("switchingtabs", "true");
var forwardButtonContainer = document.getElementById("urlbar-wrapper");
if (forwardButtonContainer) {
forwardButtonContainer.setAttribute("switchingtabs", "true");
window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
backForwardContainer.removeAttribute("switchingtabs");
forwardButtonContainer.removeAttribute("switchingtabs");
});
}

View File

@ -90,15 +90,11 @@ let gTests = [
},
// Disabled on Linux for intermittent issues with FHR, see Bug 945667.
// Disabled always due to bug 992485
{
desc: "Check that performing a search fires a search event and records to " +
"Firefox Health Report.",
setup: function () { },
run: function () {
// Skip this test always for now since it loads google.com and that causes bug 992485
return;
// Skip this test on Linux.
if (navigator.platform.indexOf("Linux") == 0) { return; }
@ -111,7 +107,7 @@ let gTests = [
}
let numSearchesBefore = 0;
let deferred = Promise.defer();
let searchEventDeferred = Promise.defer();
let doc = gBrowser.contentDocument;
let engineName = doc.documentElement.getAttribute("searchEngineName");
@ -125,22 +121,27 @@ let gTests = [
executeSoon(function () {
getNumberOfSearches(engineName).then(num => {
is(num, numSearchesBefore + 1, "One more search recorded.");
deferred.resolve();
searchEventDeferred.resolve();
});
});
}, true, true);
// Get the current number of recorded searches.
let searchStr = "a search";
getNumberOfSearches(engineName).then(num => {
numSearchesBefore = num;
info("Perform a search.");
doc.getElementById("searchText").value = "a search";
doc.getElementById("searchText").value = searchStr;
doc.getElementById("searchSubmit").click();
gBrowser.stop();
});
return deferred.promise;
let expectedURL = Services.search.currentEngine.
getSubmission(searchStr, null, "homepage").
uri.spec;
let loadPromise = waitForDocLoadAndStopIt(expectedURL);
return Promise.all([searchEventDeferred.promise, loadPromise]);
}
},

View File

@ -292,6 +292,38 @@ function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
return deferred.promise;
}
/**
* Waits for the next top-level document load in the current browser. The URI
* of the document is compared against aExpectedURL. The load is then stopped
* before it actually starts.
*
* @param aExpectedURL
* The URL of the document that is expected to load.
* @return promise
*/
function waitForDocLoadAndStopIt(aExpectedURL) {
let deferred = Promise.defer();
let progressListener = {
onStateChange: function (webProgress, req, flags, status) {
info("waitForDocLoadAndStopIt: onStateChange: " + req.name);
let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
Ci.nsIWebProgressListener.STATE_START;
if ((flags & docStart) && webProgress.isTopLevel) {
info("waitForDocLoadAndStopIt: Document start: " +
req.QueryInterface(Ci.nsIChannel).URI.spec);
is(req.originalURI.spec, aExpectedURL,
"waitForDocLoadAndStopIt: The expected URL was loaded");
req.cancel(Components.results.NS_ERROR_FAILURE);
gBrowser.removeProgressListener(progressListener);
deferred.resolve();
}
},
};
gBrowser.addProgressListener(progressListener);
info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
return deferred.promise;
}
let FullZoomHelper = {
selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {

View File

@ -104,6 +104,10 @@
onmouseout="DownloadsView.onDownloadMouseOut(event);"
oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
ondragstart="DownloadsView.onDownloadDragStart(event);"/>
<description id="emptyDownloads"
mousethrough="always">
&downloadsPanelEmpty.label;
</description>
<vbox id="downloadsFooter">
<hbox id="downloadsSummary"

View File

@ -84,6 +84,11 @@
-->
<!ENTITY downloadsListEmpty.label "There are no downloads.">
<!-- LOCALIZATION NOTE (downloadsPanelEmpty.label):
This string is shown when there are no items in the Downloads Panel.
-->
<!ENTITY downloadsPanelEmpty.label "No downloads for this session.">
<!-- LOCALIZATION NOTE (downloadsListNoMatch.label):
This string is shown when some search terms are specified, but there are no
results in the Downloads view.

View File

@ -15,7 +15,7 @@
%filter substitution
%define forwardTransitionLength 150ms
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
%define conditionalForwardWithUrlbarWidth 30
#menubar-items {
@ -735,35 +735,39 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
transition: none;
}
#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
#forward-button:-moz-locale-dir(rtl) {
#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
transform: scaleX(-1);
}
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
opacity: 0;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
transition: opacity @forwardTransitionLength@ ease-out;
}
@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
visibility: hidden;
}
#forward-button {
padding: 0;
}
#forward-button > .toolbarbutton-icon {
background-clip: padding-box;
clip-path: url("chrome://browser/content/browser.xul#keyhole-forward-clip-path");
margin-left: -6px;
padding-left: 9px;
padding-right: 3px;
border: 1px solid #9a9a9a;
border-left-style: none;
border-radius: 0;
padding: 2px 3px 2px 9px;
border: 1px solid #9a9a9a;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
transition: margin-left @forwardTransitionLength@ ease-out;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
/* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
/* when not hovered anymore, trigger a new transition to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
}
/* tabview menu item */
@ -903,59 +907,36 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
-moz-box-align: center;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
padding-left: @conditionalForwardWithUrlbarWidth@px;
-moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
position: relative;
pointer-events: none;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
@conditionalForwardWithUrlbar@ > #urlbar {
-moz-border-start: none;
margin-left: 0;
pointer-events: all;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
transition: margin-left @forwardTransitionLength@ ease-out;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
@conditionalForwardWithUrlbar@ {
clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-moz-margin-start: -5px;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}
@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
/* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
/* when switching tabs, or when not hovered anymore, trigger a new transition
* to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
/* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
/* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
transform: scaleX(-1);
}
@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
-moz-box-direction: reverse;
}
#urlbar-icons {
-moz-box-align: center;
}
@ -1015,33 +996,33 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
border-radius: 0;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
border-radius: 0;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
transition: padding-left, padding-right;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
padding-left: 5px;
transition: padding-left;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
padding-right: 5px;
transition: padding-right;
}
@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
/* forward button hiding is delayed when hovered */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
/* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
padding-left: 5.01px;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
/* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
padding-right: 5.01px;
}
@ -1071,12 +1052,8 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
margin-top: 1px;
margin-bottom: 1px;
-moz-margin-start: 3px;
-moz-margin-end: 2px;
-moz-image-region: rect(0, 16px, 16px, 0);
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box > #page-proxy-favicon {
-moz-margin-end: 1px;
-moz-image-region: rect(0, 16px, 16px, 0);
}
#identity-box:hover > #page-proxy-favicon {

View File

@ -18,13 +18,22 @@
display: none;
}
#downloadsPanel[hasdownloads] > #emptyDownloads {
display: none;
}
#emptyDownloads {
padding: 10px 20px;
max-width: 40ch;
}
#downloadsHistory {
background: transparent;
color: -moz-nativehyperlinktext;
cursor: pointer;
}
#downloadsPanel[hasdownloads] > #downloadsFooter {
#downloadsFooter {
border-top: 1px solid ThreeDShadow;
background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
}

View File

@ -7,8 +7,8 @@
%include shared.inc
%filter substitution
%define forwardTransitionLength 150ms
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
%define conditionalForwardWithUrlbarWidth 30
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
%define conditionalForwardWithUrlbarWidth 32
%define spaceAboveTabbar 9px
%define toolbarButtonPressed :hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"])
%define windowButtonMarginTop 11px
@ -1420,20 +1420,17 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
border-color: rgba(0,0,0,0.2);
}
#back-button:-moz-locale-dir(rtl),
#forward-button:-moz-locale-dir(rtl) {
#back-button:-moz-locale-dir(rtl) {
transform: scaleX(-1);
}
/* Back button styles */
#back-button {
-moz-margin-end: -7px;
position: relative;
z-index: 1;
width: 32px;
height: 32px;
padding: 4px 5px 4px 3px;
-moz-margin-end: 0;
border-radius: 10000px;
}
@ -1463,10 +1460,10 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
/* Forward button styles */
#forward-button {
-moz-margin-start: 0;
-moz-margin-end: 0;
margin-left: -2px;
margin-right: 0;
padding-left: 2px;
width: 32px;
clip-path: url(chrome://browser/content/browser.xul#osx-keyhole-forward-clip-path);
}
#forward-button > .toolbarbutton-icon {
@ -1475,13 +1472,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
margin-right: -1px;
}
#forward-button:-moz-lwtheme {
-moz-padding-start: 2px;
-moz-padding-end: 0;
}
#forward-button:not(:-moz-lwtheme) {
-moz-padding-start: 2px;
background: linear-gradient(hsl(0,0%,99%), hsl(0,0%,67%)) padding-box;
border: 1px solid;
border-color: hsl(0,0%,31%) hsla(0,0%,29%,.6) hsl(0,0%,27%);
@ -1489,10 +1480,6 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
0 1px 0 hsla(0,0%,100%,.2);
}
#urlbar-container:not([switchingtabs]) > #forward-button {
transition: opacity @forwardTransitionLength@ ease-out;
}
#forward-button:hover:active:not(:-moz-lwtheme) {
background-image: linear-gradient(hsl(0,0%,74%), hsl(0,0%,61%));
box-shadow: inset rgba(0,0,0,.3) 0 -6px 10px,
@ -1507,14 +1494,6 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
box-shadow: inset 0 1px 0 hsla(0,0%,100%,.35);
}
#urlbar-container:not(:hover) > #forward-button[disabled] {
opacity: 0;
}
@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
visibility: hidden;
}
@media (-moz-mac-lion-theme) {
#forward-button:not(:-moz-lwtheme) {
background-image: linear-gradient(hsla(0,0%,100%,.73), hsla(0,0%,100%,.05) 85%);
@ -1538,6 +1517,24 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
transition: margin-left @forwardTransitionLength@ ease-out;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
/* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
/* when not hovered anymore, trigger a new transition to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
}
.unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
list-style-image: url("chrome://browser/skin/menu-back.png") !important;
@ -1721,59 +1718,36 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
border-radius: @toolbarbuttonCornerRadius@;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
padding-left: @conditionalForwardWithUrlbarWidth@px;
-moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
position: relative;
pointer-events: none;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
@conditionalForwardWithUrlbar@ > #urlbar {
-moz-border-start: none;
margin-left: 0;
pointer-events: all;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
transition: margin-left @forwardTransitionLength@ ease-out;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
clip-path: url("chrome://browser/content/browser.xul#osx-urlbar-back-button-clip-path");
@conditionalForwardWithUrlbar@ {
clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-moz-margin-start: -6px;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}
@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
/* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
/* when switching tabs, or when not hovered anymore, trigger a new transition
* to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
/* let osx-urlbar-back-button-clip-path clip the urlbar's right side for RTL */
@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
/* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
transform: scaleX(-1);
}
@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
-moz-box-direction: reverse;
}
#identity-box {
-moz-margin-end: 3px;
padding-top: 1px;
@ -1798,32 +1772,32 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
border-radius: 0;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
border-radius: 0;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
transition: 0s padding-left;
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
transition: padding-left, padding-right;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
padding-left: 10px;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
transition: 0s padding-right;
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
padding-right: 10px;
}
@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
/* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
/* forward button hiding is delayed when hovered */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
padding-left: 10.01px;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
padding-right: 10.01px;
}
@ -3400,7 +3374,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box {
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar > #notification-popup-box {
padding-left: 7px;
}

View File

@ -22,6 +22,15 @@
display: none;
}
#downloadsPanel[hasdownloads] > #emptyDownloads {
display: none;
}
#emptyDownloads {
padding: 10px 20px;
max-width: 40ch;
}
#downloadsFooter {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
@ -33,7 +42,7 @@
cursor: pointer;
}
#downloadsPanel[hasdownloads] > #downloadsFooter {
#downloadsFooter {
background: #e5e5e5;
border-top: 1px solid hsla(0,0%,0%,.1);
box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
@ -52,11 +61,6 @@
-moz-outline-radius-bottomright: 4px;
}
#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory:focus {
-moz-outline-radius-topleft: 4px;
-moz-outline-radius-topright: 4px;
}
/*** Downloads Summary and List items ***/
#downloadsSummary,

View File

@ -14,7 +14,7 @@
%define toolbarShadowColor hsla(209,67%,12%,0.35)
%define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
%define forwardTransitionLength 150ms
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
%define conditionalForwardWithUrlbarWidth 30
#menubar-items {
@ -886,36 +886,28 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
#forward-button > .toolbarbutton-icon {
background-clip: padding-box !important;
/*mask: url(keyhole-forward-mask.svg#mask); XXX: this regresses twinopen */
clip-path: url(chrome://browser/content/browser.xul#keyhole-forward-clip-path) !important;
margin-left: -6px !important;
border-left-style: none !important;
border-radius: 0 !important;
padding-left: 9px !important;
padding-right: 3px !important;
}
%ifdef WINDOWS_AERO
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
%endif
#forward-button > .toolbarbutton-icon {
margin-left: -6px !important;
}
%ifdef WINDOWS_AERO
}
%endif
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
transition: opacity @forwardTransitionLength@ ease-out;
transition: margin-left @forwardTransitionLength@ ease-out;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
/* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
opacity: 0;
}
@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
visibility: hidden;
/* when not hovered anymore, trigger a new transition to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
}
#back-button {
@ -999,8 +991,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
%endif
#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
#forward-button:-moz-locale-dir(rtl) {
#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
transform: scaleX(-1);
}
@ -1172,59 +1163,36 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
background-color: white;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
padding-left: @conditionalForwardWithUrlbarWidth@px;
-moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
position: relative;
pointer-events: none;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
@conditionalForwardWithUrlbar@ > #urlbar {
-moz-border-start: none;
margin-left: 0;
pointer-events: all;
}
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
transition: margin-left @forwardTransitionLength@ ease-out;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
@conditionalForwardWithUrlbar@ {
clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-moz-margin-start: -5px;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
margin-left: -@conditionalForwardWithUrlbarWidth@px;
}
@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
/* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
/* when switching tabs, or when not hovered anymore, trigger a new transition
* to hide the forward button immediately */
margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
/* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
/* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
transform: scaleX(-1);
}
@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
-moz-box-direction: reverse;
}
html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
.searchbar-textbox:-moz-lwtheme > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::-moz-placeholder {
opacity: 1.0;
@ -1314,33 +1282,33 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
border-radius: 0;
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
border-radius: 0;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
transition: padding-left, padding-right;
}
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
padding-left: 5px;
transition: padding-left;
}
@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
padding-right: 5px;
transition: padding-right;
}
@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
/* forward button hiding is delayed when hovered */
transition-delay: 100s;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
/* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
padding-left: 5.01px;
}
@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
/* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
padding-right: 5.01px;
}
@ -1395,12 +1363,8 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
margin-top: 1px;
margin-bottom: 1px;
-moz-margin-start: 3px;
-moz-margin-end: 2px;
-moz-image-region: rect(0, 16px, 16px, 0);
}
@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box > #page-proxy-favicon {
-moz-margin-end: 1px;
-moz-image-region: rect(0, 16px, 16px, 0);
}
#identity-box:hover > #page-proxy-favicon {

View File

@ -18,6 +18,15 @@
display: none;
}
#downloadsPanel[hasdownloads] > #emptyDownloads {
display: none;
}
#emptyDownloads {
padding: 10px 20px;
max-width: 40ch;
}
#downloadsHistory {
background: transparent;
cursor: pointer;
@ -44,18 +53,18 @@
margin: 1em;
}
#downloadsPanel[hasdownloads] > #downloadsFooter {
#downloadsFooter {
background-color: hsla(210,4%,10%,.04);
box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
transition-duration: 150ms;
transition-property: background-color;
}
#downloadsPanel[hasdownloads] > #downloadsFooter:hover {
#downloadsFooter:hover {
background-color: hsla(210,4%,10%,.05);
}
#downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
#downloadsFooter:hover:active {
background-color: hsla(210,4%,10%,.1);
box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
}
@ -65,15 +74,15 @@
(-moz-os-version: windows-win7) {
%endif
@media (-moz-windows-default-theme) {
#downloadsPanel[hasdownloads] > #downloadsFooter {
#downloadsFooter {
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
transition-duration: 0s;
}
#downloadsPanel[hasdownloads] > #downloadsFooter,
#downloadsPanel[hasdownloads] > #downloadsFooter:hover,
#downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
#downloadsFooter,
#downloadsFooter:hover,
#downloadsFooter:hover:active {
%ifdef WINDOWS_AERO
background-color: #f1f5fb;
%else

View File

@ -6,869 +6,23 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/InterAppCommService.jsm");
const DEBUG = false;
function debug(aMsg) {
dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
function InterAppCommServiceProxy() {
}
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "messenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
const kMessages =["Webapps:Connect",
"Webapps:GetConnections",
"InterAppConnection:Cancel",
"InterAppMessagePort:PostMessage",
"InterAppMessagePort:Register",
"InterAppMessagePort:Unregister",
"child-process-shutdown"];
function InterAppCommService() {
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
kMessages.forEach(function(aMsg) {
ppmm.addMessageListener(aMsg, this);
}, this);
// This matrix is used for saving the inter-app connection info registered in
// the app manifest. The object literal is defined as below:
//
// {
// "keyword1": {
// "subAppManifestURL1": {
// /* subscribed info */
// },
// "subAppManifestURL2": {
// /* subscribed info */
// },
// ...
// },
// "keyword2": {
// "subAppManifestURL3": {
// /* subscribed info */
// },
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://subApp1.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp1.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// },
// "app://subApp2.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp2.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// },
// "bar": {
// "app://subApp3.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp3.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// }
// }
//
// TODO Bug 908999 - Update registered connections when app gets uninstalled.
this._registeredConnections = {};
// This matrix is used for saving the permitted connections, which allows
// the messaging between publishers and subscribers. The object literal is
// defined as below:
//
// {
// "keyword1": {
// "pubAppManifestURL1": [
// "subAppManifestURL1",
// "subAppManifestURL2",
// ...
// ],
// "pubAppManifestURL2": [
// "subAppManifestURL3",
// "subAppManifestURL4",
// ...
// ],
// ...
// },
// "keyword2": {
// "pubAppManifestURL3": [
// "subAppManifestURL5",
// ...
// ],
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://pubApp1.gaiamobile.org/manifest.webapp": [
// "app://subApp1.gaiamobile.org/manifest.webapp",
// "app://subApp2.gaiamobile.org/manifest.webapp"
// ],
// "app://pubApp2.gaiamobile.org/manifest.webapp": [
// "app://subApp3.gaiamobile.org/manifest.webapp",
// "app://subApp4.gaiamobile.org/manifest.webapp"
// ]
// },
// "bar": {
// "app://pubApp3.gaiamobile.org/manifest.webapp": [
// "app://subApp5.gaiamobile.org/manifest.webapp",
// ]
// }
// }
//
// TODO Bug 908999 - Update allowed connections when app gets uninstalled.
this._allowedConnections = {};
// This matrix is used for saving the caller info from the content process,
// which is indexed by a random UUID, to know where to return the promise
// resolvser's callback when the prompt UI for allowing connections returns.
// An example of the object literal is shown as below:
//
// {
// "fooID": {
// outerWindowID: 12,
// requestID: 34,
// target: pubAppTarget1
// },
// "barID": {
// outerWindowID: 56,
// requestID: 78,
// target: pubAppTarget2
// }
// }
//
// where |outerWindowID| is the ID of the window requesting the connection,
// |requestID| is the ID specifying the promise resolver to return,
// |target| is the target of the process requesting the connection.
this._promptUICallers = {};
// This matrix is used for saving the pair of message ports, which is indexed
// by a random UUID, so that each port can know whom it should talk to.
// An example of the object literal is shown as below:
//
// {
// "UUID1": {
// keyword: "keyword1",
// publisher: {
// manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
// target: pubAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
// target: subAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// },
// "UUID2": {
// keyword: "keyword2",
// publisher: {
// manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
// target: pubAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
// target: subAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// }
// }
this._messagePortPairs = {};
}
InterAppCommService.prototype = {
InterAppCommServiceProxy.prototype = {
registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
aDescription, aRules) {
let manifestURL = aManifestURI.spec;
let pageURL = aHandlerPageURI.spec;
if (DEBUG) {
debug("registerConnection: aKeyword: " + aKeyword +
" manifestURL: " + manifestURL + " pageURL: " + pageURL +
" aDescription: " + aDescription +
" aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
" aRules.manifestURLs: " + aRules.manifestURLs +
" aRules.installOrigins: " + aRules.installOrigins);
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
subAppManifestURLs = this._registeredConnections[aKeyword] = {};
}
subAppManifestURLs[manifestURL] = {
pageURL: pageURL,
description: aDescription,
rules: aRules,
manifestURL: manifestURL
};
},
_matchMinimumAccessLevel: function(aRules, aAppStatus) {
if (!aRules || !aRules.minimumAccessLevel) {
if (DEBUG) {
debug("rules.minimumAccessLevel is not available. No need to match.");
}
return true;
}
let minAccessLevel = aRules.minimumAccessLevel;
switch (minAccessLevel) {
case "web":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "privileged":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "certified":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
}
if (DEBUG) {
debug("rules.minimumAccessLevel is not matched!" +
" minAccessLevel: " + minAccessLevel +
" aAppStatus : " + aAppStatus);
}
return false;
},
_matchManifestURLs: function(aRules, aManifestURL) {
if (!aRules || !Array.isArray(aRules.manifestURLs)) {
if (DEBUG) {
debug("rules.manifestURLs is not available. No need to match.");
}
return true;
}
let manifestURLs = aRules.manifestURLs;
if (manifestURLs.indexOf(aManifestURL) != -1) {
return true;
}
if (DEBUG) {
debug("rules.manifestURLs is not matched!" +
" manifestURLs: " + manifestURLs +
" aManifestURL : " + aManifestURL);
}
return false;
},
_matchInstallOrigins: function(aRules, aInstallOrigin) {
if (!aRules || !Array.isArray(aRules.installOrigins)) {
if (DEBUG) {
debug("rules.installOrigins is not available. No need to match.");
}
return true;
}
let installOrigins = aRules.installOrigins;
if (installOrigins.indexOf(aInstallOrigin) != -1) {
return true;
}
if (DEBUG) {
debug("rules.installOrigins is not matched!" +
" installOrigins: " + installOrigins +
" installOrigin : " + aInstallOrigin);
}
return false;
},
_matchRules: function(aPubAppManifestURL, aPubRules,
aSubAppManifestURL, aSubRules) {
let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL);
let subApp = appsService.getAppByManifestURL(aSubAppManifestURL);
// TODO Bug 907068 In the initiative step, we only expose this API to
// certified apps to meet the time line. Eventually, we need to make
// it available for the non-certified apps as well. For now, only the
// certified apps can match the rules.
if (pubApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
subApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
if (DEBUG) {
debug("Only certified apps are allowed to do connections.");
}
return false;
}
if (!aPubRules && !aSubRules) {
if (DEBUG) {
debug("No rules for publisher and subscriber. No need to match.");
}
return true;
}
// Check minimumAccessLevel.
if (!this._matchMinimumAccessLevel(aPubRules, subApp.appStatus) ||
!this._matchMinimumAccessLevel(aSubRules, pubApp.appStatus)) {
return false;
}
// Check manifestURLs.
if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
!this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
return false;
}
// Check installOrigins.
if (!this._matchInstallOrigins(aPubRules, subApp.installOrigin) ||
!this._matchInstallOrigins(aSubRules, pubApp.installOrigin)) {
return false;
}
// Check developers.
// TODO Do we really want to check this? This one seems naive.
if (DEBUG) debug("All rules are matched.");
return true;
},
_dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
aAllowedSubAppManifestURLs,
aTarget, aOuterWindowID, aRequestID) {
if (DEBUG) {
debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
" aPubAppManifestURL: " + aPubAppManifestURL +
" aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
}
if (aAllowedSubAppManifestURLs.length == 0) {
if (DEBUG) debug("No apps are allowed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let messagePortIDs = [];
aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) {
let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL];
if (!subscribedInfo) {
if (DEBUG) {
debug("The sunscribed info is not available. Skipping: " +
aAllowedSubAppManifestURL);
}
return;
}
// The message port ID is aimed for identifying the coupling targets
// to deliver messages with each other. This ID is centrally generated
// by the parent and dispatched to both the sender and receiver ends
// for creating their own message ports respectively.
let messagePortID = UUIDGenerator.generateUUID().toString();
this._messagePortPairs[messagePortID] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubAppManifestURL
},
subscriber: {
manifestURL: aAllowedSubAppManifestURL
}
};
// Fire system message to deliver the message port to the subscriber.
messenger.sendMessage("connection",
{ keyword: aKeyword,
messagePortID: messagePortID },
Services.io.newURI(subscribedInfo.pageURL, null, null),
Services.io.newURI(subscribedInfo.manifestURL, null, null));
messagePortIDs.push(messagePortID);
}, this);
if (messagePortIDs.length == 0) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
// Return the message port IDs to open the message ports for the publisher.
if (DEBUG) debug("messagePortIDs: " + messagePortIDs);
aTarget.sendAsyncMessage("Webapps:Connect:Return:OK",
{ keyword: aKeyword,
messagePortIDs: messagePortIDs,
oid: aOuterWindowID, requestID: aRequestID });
},
_connect: function(aMessage, aTarget) {
let keyword = aMessage.keyword;
let pubRules = aMessage.rules;
let pubAppManifestURL = aMessage.manifestURL;
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let subAppManifestURLs = this._registeredConnections[keyword];
if (!subAppManifestURLs) {
if (DEBUG) {
debug("No apps are subscribed for this connection. Returning.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
aTarget, outerWindowID, requestID);
return;
}
// Fetch the apps that used to be allowed to connect before, so that
// users don't need to select/allow them again. That is, we only pop up
// the prompt UI for the *new* connections.
let allowedSubAppManifestURLs = [];
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (allowedPubAppManifestURLs &&
allowedPubAppManifestURLs[pubAppManifestURL]) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL];
}
// Check rules to see if a subscribed app is allowed to connect.
let appsToSelect = [];
for (let subAppManifestURL in subAppManifestURLs) {
if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) {
if (DEBUG) {
debug("Don't need to select again. Skipping: " + subAppManifestURL);
}
continue;
}
// Only rule-matched publishers/subscribers are allowed to connect.
let subscribedInfo = subAppManifestURLs[subAppManifestURL];
let subRules = subscribedInfo.rules;
let matched =
this._matchRules(pubAppManifestURL, pubRules,
subAppManifestURL, subRules);
if (!matched) {
if (DEBUG) {
debug("Rules are not matched. Skipping: " + subAppManifestURL);
}
continue;
}
appsToSelect.push({
manifestURL: subAppManifestURL,
description: subscribedInfo.description
});
}
if (appsToSelect.length == 0) {
if (DEBUG) {
debug("No additional apps need to be selected for this connection. " +
"Just dispatch message ports for the existing connections.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL,
allowedSubAppManifestURLs,
aTarget, outerWindowID, requestID);
return;
}
// Remember the caller info with an UUID so that we can know where to
// return the promise resolver's callback when the prompt UI returns.
let callerID = UUIDGenerator.generateUUID().toString();
this._promptUICallers[callerID] = {
outerWindowID: outerWindowID,
requestID: requestID,
target: aTarget
};
// TODO Bug 897169 Temporarily disable the notification for popping up
// the prompt until the UX/UI for the prompt is confirmed.
//
// TODO Bug 908191 We need to change the way of interaction between API and
// run-time prompt from observer notification to xpcom-interface caller.
//
/*
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, "inter-app-comm-select-app",
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
appsToSelect: appsToSelect }));
*/
// TODO Bug 897169 Simulate the return of the app-selected result by
// the prompt, which always allows the connection. This dummy codes
// will be removed when the UX/UI for the prompt is ready.
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
selectedApps: appsToSelect }));
},
_getConnections: function(aMessage, aTarget) {
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let connections = [];
for (let keyword in this._allowedConnections) {
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) {
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[allowedPubAppManifestURL];
allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) {
connections.push({ keyword: keyword,
pubAppManifestURL: allowedPubAppManifestURL,
subAppManifestURL: allowedSubAppManifestURL });
});
}
}
aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK",
{ connections: connections,
oid: outerWindowID, requestID: requestID });
},
_cancelConnection: function(aMessage) {
let keyword = aMessage.keyword;
let pubAppManifestURL = aMessage.pubAppManifestURL;
let subAppManifestURL = aMessage.subAppManifestURL;
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
if (DEBUG) debug("keyword is not found: " + keyword);
return;
}
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[pubAppManifestURL];
if (!allowedSubAppManifestURLs) {
if (DEBUG) debug("publisher is not found: " + pubAppManifestURL);
return;
}
let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL);
if (index == -1) {
if (DEBUG) debug("subscriber is not found: " + subAppManifestURL);
return;
}
if (DEBUG) debug("Cancelling the connection.");
allowedSubAppManifestURLs.splice(index, 1);
// Clean up the parent entries if needed.
if (allowedSubAppManifestURLs.length == 0) {
delete allowedPubAppManifestURLs[pubAppManifestURL];
if (Object.keys(allowedPubAppManifestURLs).length == 0) {
delete this._allowedConnections[keyword];
}
}
if (DEBUG) debug("Unregistering message ports based on this connection.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.keyword == keyword &&
pair.publisher.manifestURL == pubAppManifestURL &&
pair.subscriber.manifestURL == subAppManifestURL) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_identifyMessagePort: function(aMessagePortID, aManifestURL) {
let pair = this._messagePortPairs[aMessagePortID];
if (!pair) {
if (DEBUG) {
debug("Error! The message port ID is invalid: " + aMessagePortID +
", which should have been generated by parent.");
}
return null;
}
// Check it the message port is for publisher.
if (pair.publisher.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: true };
}
// Check it the message port is for subscriber.
if (pair.subscriber.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: false };
}
if (DEBUG) {
debug("Error! The manifest URL is invalid: " + aManifestURL +
", which might be a hacked app.");
}
return null;
},
_registerMessagePort: function(aMessage, aTarget) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let pageURL = aMessage.pageURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to register.");
}
return;
}
if (DEBUG) debug("Registering message port for " + manifestURL);
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.target = aTarget;
sender.pageURL = pageURL;
sender.messageQueue = [];
// Check if the other port has queued messages. Deliver them if needed.
if (DEBUG) {
debug("Checking if the other port used to send messages but queued.");
}
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (receiver.messageQueue) {
while (receiver.messageQueue.length) {
let message = receiver.messageQueue.shift();
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ message: message,
manifestURL: sender.manifestURL,
pageURL: sender.pageURL,
messagePortID: messagePortID });
}
}
},
_unregisterMessagePort: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to unregister.");
}
return;
}
if (DEBUG) {
debug("Unregistering message port for " + manifestURL);
}
delete this._messagePortPairs[messagePortID];
},
_removeTarget: function(aTarget) {
if (!aTarget) {
if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way.");
return
}
if (DEBUG) debug("Unregistering message ports based on this target.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.publisher.target === aTarget ||
pair.subscriber.target === aTarget) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_postMessage: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let message = aMessage.message;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) debug("Cannot identify the message port. Failed to post.");
return;
}
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (!receiver.target) {
if (DEBUG) {
debug("The receiver's target is not ready yet. Queuing the message.");
}
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.messageQueue.push(message);
return;
}
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ manifestURL: receiver.manifestURL,
pageURL: receiver.pageURL,
messagePortID: messagePortID,
message: message });
},
_handleSelectcedApps: function(aData) {
let callerID = aData.callerID;
let caller = this._promptUICallers[callerID];
if (!caller) {
if (DEBUG) debug("Error! Cannot find the caller.");
return;
}
delete this._promptUICallers[callerID];
let outerWindowID = caller.outerWindowID;
let requestID = caller.requestID;
let target = caller.target;
let manifestURL = aData.manifestURL;
let keyword = aData.keyword;
let selectedApps = aData.selectedApps;
if (selectedApps.length == 0) {
if (DEBUG) debug("No apps are selected to connect.")
this._dispatchMessagePorts(keyword, manifestURL, [],
target, outerWindowID, requestID);
return;
}
// Find the entry of allowed connections to add the selected apps.
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
allowedPubAppManifestURLs = this._allowedConnections[keyword] = {};
}
let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL];
if (!allowedSubAppManifestURLs) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = [];
}
// Add the selected app into the existing set of allowed connections.
selectedApps.forEach(function(aSelectedApp) {
let allowedSubAppManifestURL = aSelectedApp.manifestURL;
if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) {
allowedSubAppManifestURLs.push(allowedSubAppManifestURL);
}
});
// Finally, dispatch the message ports for the allowed connections,
// including the old connections and the newly selected connection.
this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
target, outerWindowID, requestID);
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
let message = aMessage.json;
let target = aMessage.target;
// To prevent the hacked child process from sending commands to parent
// to do illegal connections, we need to check its manifest URL.
if (aMessage.name !== "child-process-shutdown" &&
// TODO: fix bug 988142 to re-enable "InterAppMessagePort:Unregister".
aMessage.name !== "InterAppMessagePort:Unregister" &&
kMessages.indexOf(aMessage.name) != -1) {
if (!target.assertContainApp(message.manifestURL)) {
if (DEBUG) {
debug("Got message from a process carrying illegal manifest URL.");
}
return null;
}
}
switch (aMessage.name) {
case "Webapps:Connect":
this._connect(message, target);
break;
case "Webapps:GetConnections":
this._getConnections(message, target);
break;
case "InterAppConnection:Cancel":
this._cancelConnection(message);
break;
case "InterAppMessagePort:PostMessage":
this._postMessage(message);
break;
case "InterAppMessagePort:Register":
this._registerMessagePort(message, target);
break;
case "InterAppMessagePort:Unregister":
this._unregisterMessagePort(message);
break;
case "child-process-shutdown":
this._removeTarget(target);
break;
}
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "xpcom-shutdown":
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.obs.removeObserver(this, "inter-app-comm-select-app-result");
kMessages.forEach(function(aMsg) {
ppmm.removeMessageListener(aMsg, this);
}, this);
ppmm = null;
break;
case "inter-app-comm-select-app-result":
if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
this._handleSelectcedApps(JSON.parse(aData));
break;
}
InterAppCommService.
registerConnection(aKeyword, aHandlerPageURI, aManifestURI,
aDescription, aRules);
},
classID: Components.ID("{3dd15ce6-e7be-11e2-82bc-77967e7a63e6}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService,
Ci.nsIObserver])
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService])
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommService]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommServiceProxy]);

View File

@ -0,0 +1,889 @@
/* 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";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
this.EXPORTED_SYMBOLS = ["InterAppCommService"];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
const DEBUG = false;
function debug(aMsg) {
dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
}
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "messenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
const kMessages =["Webapps:Connect",
"Webapps:GetConnections",
"InterAppConnection:Cancel",
"InterAppMessagePort:PostMessage",
"InterAppMessagePort:Register",
"InterAppMessagePort:Unregister",
"child-process-shutdown"];
/**
* This module contains helpers for Inter-App Communication API [1] related
* purposes, which plays the role of the central service receiving messages
* from and interacting with the content processes.
*
* [1] https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal
*/
this.InterAppCommService = {
init: function() {
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
kMessages.forEach(function(aMsg) {
ppmm.addMessageListener(aMsg, this);
}, this);
// This matrix is used for saving the inter-app connection info registered in
// the app manifest. The object literal is defined as below:
//
// {
// "keyword1": {
// "subAppManifestURL1": {
// /* subscribed info */
// },
// "subAppManifestURL2": {
// /* subscribed info */
// },
// ...
// },
// "keyword2": {
// "subAppManifestURL3": {
// /* subscribed info */
// },
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://subApp1.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp1.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// },
// "app://subApp2.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp2.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// },
// "bar": {
// "app://subApp3.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp3.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// }
// }
//
// TODO Bug 908999 - Update registered connections when app gets uninstalled.
this._registeredConnections = {};
// This matrix is used for saving the permitted connections, which allows
// the messaging between publishers and subscribers. The object literal is
// defined as below:
//
// {
// "keyword1": {
// "pubAppManifestURL1": [
// "subAppManifestURL1",
// "subAppManifestURL2",
// ...
// ],
// "pubAppManifestURL2": [
// "subAppManifestURL3",
// "subAppManifestURL4",
// ...
// ],
// ...
// },
// "keyword2": {
// "pubAppManifestURL3": [
// "subAppManifestURL5",
// ...
// ],
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://pubApp1.gaiamobile.org/manifest.webapp": [
// "app://subApp1.gaiamobile.org/manifest.webapp",
// "app://subApp2.gaiamobile.org/manifest.webapp"
// ],
// "app://pubApp2.gaiamobile.org/manifest.webapp": [
// "app://subApp3.gaiamobile.org/manifest.webapp",
// "app://subApp4.gaiamobile.org/manifest.webapp"
// ]
// },
// "bar": {
// "app://pubApp3.gaiamobile.org/manifest.webapp": [
// "app://subApp5.gaiamobile.org/manifest.webapp",
// ]
// }
// }
//
// TODO Bug 908999 - Update allowed connections when app gets uninstalled.
this._allowedConnections = {};
// This matrix is used for saving the caller info from the content process,
// which is indexed by a random UUID, to know where to return the promise
// resolvser's callback when the prompt UI for allowing connections returns.
// An example of the object literal is shown as below:
//
// {
// "fooID": {
// outerWindowID: 12,
// requestID: 34,
// target: pubAppTarget1
// },
// "barID": {
// outerWindowID: 56,
// requestID: 78,
// target: pubAppTarget2
// }
// }
//
// where |outerWindowID| is the ID of the window requesting the connection,
// |requestID| is the ID specifying the promise resolver to return,
// |target| is the target of the process requesting the connection.
this._promptUICallers = {};
// This matrix is used for saving the pair of message ports, which is indexed
// by a random UUID, so that each port can know whom it should talk to.
// An example of the object literal is shown as below:
//
// {
// "UUID1": {
// keyword: "keyword1",
// publisher: {
// manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
// target: pubAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
// target: subAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// },
// "UUID2": {
// keyword: "keyword2",
// publisher: {
// manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
// target: pubAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
// target: subAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// }
// }
this._messagePortPairs = {};
},
/**
* Registration of a page that wants to be connected to other apps through
* the Inter-App Communication API.
*
* @param aKeyword The connection's keyword.
* @param aHandlerPageURI The URI of the handler's page.
* @param aManifestURI The webapp's manifest URI.
* @param aDescription The connection's description.
* @param aRules The connection's rules.
*/
registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
aDescription, aRules) {
let manifestURL = aManifestURI.spec;
let pageURL = aHandlerPageURI.spec;
if (DEBUG) {
debug("registerConnection: aKeyword: " + aKeyword +
" manifestURL: " + manifestURL + " pageURL: " + pageURL +
" aDescription: " + aDescription +
" aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
" aRules.manifestURLs: " + aRules.manifestURLs +
" aRules.installOrigins: " + aRules.installOrigins);
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
subAppManifestURLs = this._registeredConnections[aKeyword] = {};
}
subAppManifestURLs[manifestURL] = {
pageURL: pageURL,
description: aDescription,
rules: aRules,
manifestURL: manifestURL
};
},
_matchMinimumAccessLevel: function(aRules, aAppStatus) {
if (!aRules || !aRules.minimumAccessLevel) {
if (DEBUG) {
debug("rules.minimumAccessLevel is not available. No need to match.");
}
return true;
}
let minAccessLevel = aRules.minimumAccessLevel;
switch (minAccessLevel) {
case "web":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "privileged":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "certified":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
}
if (DEBUG) {
debug("rules.minimumAccessLevel is not matched!" +
" minAccessLevel: " + minAccessLevel +
" aAppStatus : " + aAppStatus);
}
return false;
},
_matchManifestURLs: function(aRules, aManifestURL) {
if (!aRules || !Array.isArray(aRules.manifestURLs)) {
if (DEBUG) {
debug("rules.manifestURLs is not available. No need to match.");
}
return true;
}
let manifestURLs = aRules.manifestURLs;
if (manifestURLs.indexOf(aManifestURL) != -1) {
return true;
}
if (DEBUG) {
debug("rules.manifestURLs is not matched!" +
" manifestURLs: " + manifestURLs +
" aManifestURL : " + aManifestURL);
}
return false;
},
_matchInstallOrigins: function(aRules, aInstallOrigin) {
if (!aRules || !Array.isArray(aRules.installOrigins)) {
if (DEBUG) {
debug("rules.installOrigins is not available. No need to match.");
}
return true;
}
let installOrigins = aRules.installOrigins;
if (installOrigins.indexOf(aInstallOrigin) != -1) {
return true;
}
if (DEBUG) {
debug("rules.installOrigins is not matched!" +
" installOrigins: " + installOrigins +
" installOrigin : " + aInstallOrigin);
}
return false;
},
_matchRules: function(aPubAppManifestURL, aPubRules,
aSubAppManifestURL, aSubRules) {
let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL);
let subApp = appsService.getAppByManifestURL(aSubAppManifestURL);
// TODO Bug 907068 In the initiative step, we only expose this API to
// certified apps to meet the time line. Eventually, we need to make
// it available for the non-certified apps as well. For now, only the
// certified apps can match the rules.
if (pubApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
subApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
if (DEBUG) {
debug("Only certified apps are allowed to do connections.");
}
return false;
}
if (!aPubRules && !aSubRules) {
if (DEBUG) {
debug("No rules for publisher and subscriber. No need to match.");
}
return true;
}
// Check minimumAccessLevel.
if (!this._matchMinimumAccessLevel(aPubRules, subApp.appStatus) ||
!this._matchMinimumAccessLevel(aSubRules, pubApp.appStatus)) {
return false;
}
// Check manifestURLs.
if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
!this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
return false;
}
// Check installOrigins.
if (!this._matchInstallOrigins(aPubRules, subApp.installOrigin) ||
!this._matchInstallOrigins(aSubRules, pubApp.installOrigin)) {
return false;
}
// Check developers.
// TODO Do we really want to check this? This one seems naive.
if (DEBUG) debug("All rules are matched.");
return true;
},
_dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
aAllowedSubAppManifestURLs,
aTarget, aOuterWindowID, aRequestID) {
if (DEBUG) {
debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
" aPubAppManifestURL: " + aPubAppManifestURL +
" aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
}
if (aAllowedSubAppManifestURLs.length == 0) {
if (DEBUG) debug("No apps are allowed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let messagePortIDs = [];
aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) {
let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL];
if (!subscribedInfo) {
if (DEBUG) {
debug("The sunscribed info is not available. Skipping: " +
aAllowedSubAppManifestURL);
}
return;
}
// The message port ID is aimed for identifying the coupling targets
// to deliver messages with each other. This ID is centrally generated
// by the parent and dispatched to both the sender and receiver ends
// for creating their own message ports respectively.
let messagePortID = UUIDGenerator.generateUUID().toString();
this._messagePortPairs[messagePortID] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubAppManifestURL
},
subscriber: {
manifestURL: aAllowedSubAppManifestURL
}
};
// Fire system message to deliver the message port to the subscriber.
messenger.sendMessage("connection",
{ keyword: aKeyword,
messagePortID: messagePortID },
Services.io.newURI(subscribedInfo.pageURL, null, null),
Services.io.newURI(subscribedInfo.manifestURL, null, null));
messagePortIDs.push(messagePortID);
}, this);
if (messagePortIDs.length == 0) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
// Return the message port IDs to open the message ports for the publisher.
if (DEBUG) debug("messagePortIDs: " + messagePortIDs);
aTarget.sendAsyncMessage("Webapps:Connect:Return:OK",
{ keyword: aKeyword,
messagePortIDs: messagePortIDs,
oid: aOuterWindowID, requestID: aRequestID });
},
_connect: function(aMessage, aTarget) {
let keyword = aMessage.keyword;
let pubRules = aMessage.rules;
let pubAppManifestURL = aMessage.manifestURL;
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let subAppManifestURLs = this._registeredConnections[keyword];
if (!subAppManifestURLs) {
if (DEBUG) {
debug("No apps are subscribed for this connection. Returning.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
aTarget, outerWindowID, requestID);
return;
}
// Fetch the apps that used to be allowed to connect before, so that
// users don't need to select/allow them again. That is, we only pop up
// the prompt UI for the *new* connections.
let allowedSubAppManifestURLs = [];
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (allowedPubAppManifestURLs &&
allowedPubAppManifestURLs[pubAppManifestURL]) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL];
}
// Check rules to see if a subscribed app is allowed to connect.
let appsToSelect = [];
for (let subAppManifestURL in subAppManifestURLs) {
if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) {
if (DEBUG) {
debug("Don't need to select again. Skipping: " + subAppManifestURL);
}
continue;
}
// Only rule-matched publishers/subscribers are allowed to connect.
let subscribedInfo = subAppManifestURLs[subAppManifestURL];
let subRules = subscribedInfo.rules;
let matched =
this._matchRules(pubAppManifestURL, pubRules,
subAppManifestURL, subRules);
if (!matched) {
if (DEBUG) {
debug("Rules are not matched. Skipping: " + subAppManifestURL);
}
continue;
}
appsToSelect.push({
manifestURL: subAppManifestURL,
description: subscribedInfo.description
});
}
if (appsToSelect.length == 0) {
if (DEBUG) {
debug("No additional apps need to be selected for this connection. " +
"Just dispatch message ports for the existing connections.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL,
allowedSubAppManifestURLs,
aTarget, outerWindowID, requestID);
return;
}
// Remember the caller info with an UUID so that we can know where to
// return the promise resolver's callback when the prompt UI returns.
let callerID = UUIDGenerator.generateUUID().toString();
this._promptUICallers[callerID] = {
outerWindowID: outerWindowID,
requestID: requestID,
target: aTarget
};
// TODO Bug 897169 Temporarily disable the notification for popping up
// the prompt until the UX/UI for the prompt is confirmed.
//
// TODO Bug 908191 We need to change the way of interaction between API and
// run-time prompt from observer notification to xpcom-interface caller.
//
/*
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, "inter-app-comm-select-app",
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
appsToSelect: appsToSelect }));
*/
// TODO Bug 897169 Simulate the return of the app-selected result by
// the prompt, which always allows the connection. This dummy codes
// will be removed when the UX/UI for the prompt is ready.
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
selectedApps: appsToSelect }));
},
_getConnections: function(aMessage, aTarget) {
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let connections = [];
for (let keyword in this._allowedConnections) {
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) {
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[allowedPubAppManifestURL];
allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) {
connections.push({ keyword: keyword,
pubAppManifestURL: allowedPubAppManifestURL,
subAppManifestURL: allowedSubAppManifestURL });
});
}
}
aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK",
{ connections: connections,
oid: outerWindowID, requestID: requestID });
},
_cancelConnection: function(aMessage) {
let keyword = aMessage.keyword;
let pubAppManifestURL = aMessage.pubAppManifestURL;
let subAppManifestURL = aMessage.subAppManifestURL;
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
if (DEBUG) debug("keyword is not found: " + keyword);
return;
}
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[pubAppManifestURL];
if (!allowedSubAppManifestURLs) {
if (DEBUG) debug("publisher is not found: " + pubAppManifestURL);
return;
}
let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL);
if (index == -1) {
if (DEBUG) debug("subscriber is not found: " + subAppManifestURL);
return;
}
if (DEBUG) debug("Cancelling the connection.");
allowedSubAppManifestURLs.splice(index, 1);
// Clean up the parent entries if needed.
if (allowedSubAppManifestURLs.length == 0) {
delete allowedPubAppManifestURLs[pubAppManifestURL];
if (Object.keys(allowedPubAppManifestURLs).length == 0) {
delete this._allowedConnections[keyword];
}
}
if (DEBUG) debug("Unregistering message ports based on this connection.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.keyword == keyword &&
pair.publisher.manifestURL == pubAppManifestURL &&
pair.subscriber.manifestURL == subAppManifestURL) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_identifyMessagePort: function(aMessagePortID, aManifestURL) {
let pair = this._messagePortPairs[aMessagePortID];
if (!pair) {
if (DEBUG) {
debug("Error! The message port ID is invalid: " + aMessagePortID +
", which should have been generated by parent.");
}
return null;
}
// Check it the message port is for publisher.
if (pair.publisher.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: true };
}
// Check it the message port is for subscriber.
if (pair.subscriber.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: false };
}
if (DEBUG) {
debug("Error! The manifest URL is invalid: " + aManifestURL +
", which might be a hacked app.");
}
return null;
},
_registerMessagePort: function(aMessage, aTarget) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let pageURL = aMessage.pageURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to register.");
}
return;
}
if (DEBUG) debug("Registering message port for " + manifestURL);
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.target = aTarget;
sender.pageURL = pageURL;
sender.messageQueue = [];
// Check if the other port has queued messages. Deliver them if needed.
if (DEBUG) {
debug("Checking if the other port used to send messages but queued.");
}
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (receiver.messageQueue) {
while (receiver.messageQueue.length) {
let message = receiver.messageQueue.shift();
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ message: message,
manifestURL: sender.manifestURL,
pageURL: sender.pageURL,
messagePortID: messagePortID });
}
}
},
_unregisterMessagePort: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to unregister.");
}
return;
}
if (DEBUG) {
debug("Unregistering message port for " + manifestURL);
}
delete this._messagePortPairs[messagePortID];
},
_removeTarget: function(aTarget) {
if (!aTarget) {
if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way.");
return
}
if (DEBUG) debug("Unregistering message ports based on this target.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.publisher.target === aTarget ||
pair.subscriber.target === aTarget) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_postMessage: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let message = aMessage.message;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) debug("Cannot identify the message port. Failed to post.");
return;
}
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (!receiver.target) {
if (DEBUG) {
debug("The receiver's target is not ready yet. Queuing the message.");
}
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.messageQueue.push(message);
return;
}
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ manifestURL: receiver.manifestURL,
pageURL: receiver.pageURL,
messagePortID: messagePortID,
message: message });
},
_handleSelectcedApps: function(aData) {
let callerID = aData.callerID;
let caller = this._promptUICallers[callerID];
if (!caller) {
if (DEBUG) debug("Error! Cannot find the caller.");
return;
}
delete this._promptUICallers[callerID];
let outerWindowID = caller.outerWindowID;
let requestID = caller.requestID;
let target = caller.target;
let manifestURL = aData.manifestURL;
let keyword = aData.keyword;
let selectedApps = aData.selectedApps;
if (selectedApps.length == 0) {
if (DEBUG) debug("No apps are selected to connect.")
this._dispatchMessagePorts(keyword, manifestURL, [],
target, outerWindowID, requestID);
return;
}
// Find the entry of allowed connections to add the selected apps.
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
allowedPubAppManifestURLs = this._allowedConnections[keyword] = {};
}
let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL];
if (!allowedSubAppManifestURLs) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = [];
}
// Add the selected app into the existing set of allowed connections.
selectedApps.forEach(function(aSelectedApp) {
let allowedSubAppManifestURL = aSelectedApp.manifestURL;
if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) {
allowedSubAppManifestURLs.push(allowedSubAppManifestURL);
}
});
// Finally, dispatch the message ports for the allowed connections,
// including the old connections and the newly selected connection.
this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
target, outerWindowID, requestID);
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
let message = aMessage.json;
let target = aMessage.target;
// To prevent the hacked child process from sending commands to parent
// to do illegal connections, we need to check its manifest URL.
if (aMessage.name !== "child-process-shutdown" &&
// TODO: fix bug 988142 to re-enable "InterAppMessagePort:Unregister".
aMessage.name !== "InterAppMessagePort:Unregister" &&
kMessages.indexOf(aMessage.name) != -1) {
if (!target.assertContainApp(message.manifestURL)) {
if (DEBUG) {
debug("Got message from a process carrying illegal manifest URL.");
}
return null;
}
}
switch (aMessage.name) {
case "Webapps:Connect":
this._connect(message, target);
break;
case "Webapps:GetConnections":
this._getConnections(message, target);
break;
case "InterAppConnection:Cancel":
this._cancelConnection(message);
break;
case "InterAppMessagePort:PostMessage":
this._postMessage(message);
break;
case "InterAppMessagePort:Register":
this._registerMessagePort(message, target);
break;
case "InterAppMessagePort:Unregister":
this._unregisterMessagePort(message);
break;
case "child-process-shutdown":
this._removeTarget(target);
break;
}
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "xpcom-shutdown":
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.obs.removeObserver(this, "inter-app-comm-select-app-result");
kMessages.forEach(function(aMsg) {
ppmm.removeMessageListener(aMsg, this);
}, this);
ppmm = null;
break;
case "inter-app-comm-select-app-result":
if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
this._handleSelectcedApps(JSON.parse(aData));
break;
}
}
};
InterAppCommService.init();

View File

@ -27,6 +27,7 @@ EXTRA_JS_MODULES += [
'AppDownloadManager.jsm',
'AppsServiceChild.jsm',
'FreeSpaceWatcher.jsm',
'InterAppCommService.jsm',
'OfflineCacheInstaller.jsm',
'PermissionsInstaller.jsm',
'PermissionsTable.jsm',

View File

@ -0,0 +1,457 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/InterAppCommService.jsm");
let UUIDGenerator = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
const MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
const FAKE_MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
const OUTER_WINDOW_ID = UUIDGenerator.generateUUID().toString();
const REQUEST_ID = UUIDGenerator.generateUUID().toString();
const PUB_APP_MANIFEST_URL = "app://pubApp.gaiamobile.org/manifest.webapp";
const SUB_APP_MANIFEST_URL = "app://subApp.gaiamobile.org/manifest.webapp";
const PUB_APP_PAGE_URL = "app://pubApp.gaiamobile.org/handler.html";
const SUB_APP_PAGE_URL = "app://subApp.gaiamobile.org/handler.html";
const KEYWORD = "test";
function create_message_port_pair(aMessagePortId,
aKeyword,
aPubManifestURL,
aSubManifestURL) {
InterAppCommService._messagePortPairs[aMessagePortId] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubManifestURL
},
subscriber: {
manifestURL: aSubManifestURL
}
};
}
function clear_message_port_pairs() {
InterAppCommService._messagePortPairs = {};
}
function register_message_port(aMessagePortId,
aManifestURL,
aPageURL,
aTargetSendAsyncMessage) {
let message = {
name: "InterAppMessagePort:Register",
json: {
messagePortID: aMessagePortId,
manifestURL: aManifestURL,
pageURL: aPageURL
},
target: {
sendAsyncMessage: function(aName, aData) {
if (aTargetSendAsyncMessage) {
aTargetSendAsyncMessage(aName, aData);
}
},
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
return message.target;
}
function register_message_ports(aMessagePortId,
aPubTargetSendAsyncMessage,
aSubTargetSendAsyncMessage) {
let pubTarget = register_message_port(aMessagePortId,
PUB_APP_MANIFEST_URL,
PUB_APP_PAGE_URL,
aPubTargetSendAsyncMessage);
let subTarget = register_message_port(aMessagePortId,
SUB_APP_MANIFEST_URL,
SUB_APP_PAGE_URL,
aSubTargetSendAsyncMessage);
return { pubTarget: pubTarget, subTarget: subTarget };
}
function unregister_message_port(aMessagePortId,
aManifestURL) {
let message = {
name: "InterAppMessagePort:Unregister",
json: {
messagePortID: aMessagePortId,
manifestURL: aManifestURL
},
target: {
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
function remove_target(aTarget) {
let message = {
name: "child-process-shutdown",
target: aTarget
};
InterAppCommService.receiveMessage(message);
}
function post_message(aMessagePortId,
aManifestURL,
aMessage) {
let message = {
name: "InterAppMessagePort:PostMessage",
json: {
messagePortID: aMessagePortId,
manifestURL: aManifestURL,
message: aMessage
},
target: {
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
function create_allowed_connections(aKeyword,
aPubManifestURL,
aSubManifestURL) {
let allowedPubAppManifestURLs =
InterAppCommService._allowedConnections[aKeyword] = {};
allowedPubAppManifestURLs[aPubManifestURL] = [aSubManifestURL];
}
function clear_allowed_connections() {
InterAppCommService._allowedConnections = {};
}
function get_connections(aManifestURL,
aOuterWindowID,
aRequestID,
aTargetSendAsyncMessage) {
let message = {
name: "Webapps:GetConnections",
json: {
manifestURL: aManifestURL,
outerWindowID: aOuterWindowID,
requestID: aRequestID
},
target: {
sendAsyncMessage: function(aName, aData) {
if (aTargetSendAsyncMessage) {
aTargetSendAsyncMessage(aName, aData);
}
},
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
function cancel_connections(aManifestURL,
aKeyword,
aPubManifestURL,
aSubManifestURL) {
let message = {
name: "InterAppConnection:Cancel",
json: {
manifestURL: aManifestURL,
keyword: aKeyword,
pubAppManifestURL: aPubManifestURL,
subAppManifestURL: aSubManifestURL
},
target: {
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
add_test(function test_registerMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let targets = register_message_ports(MESSAGE_PORT_ID);
let messagePortPair = InterAppCommService._messagePortPairs[MESSAGE_PORT_ID];
do_check_eq(PUB_APP_PAGE_URL, messagePortPair.publisher.pageURL);
do_check_eq(SUB_APP_PAGE_URL, messagePortPair.subscriber.pageURL);
do_check_true(targets.pubTarget === messagePortPair.publisher.target);
do_check_true(targets.subTarget === messagePortPair.subscriber.target);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_failToRegisterMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let targets = register_message_ports(FAKE_MESSAGE_PORT_ID);
let messagePortPair = InterAppCommService._messagePortPairs[MESSAGE_PORT_ID];
// Because it failed to register, the page URLs and targets don't exist.
do_check_true(messagePortPair.publisher.pageURL === undefined);
do_check_true(messagePortPair.subscriber.pageURL === undefined);
do_check_true(messagePortPair.publisher.target === undefined);
do_check_true(messagePortPair.subscriber.target === undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_unregisterMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_ports(MESSAGE_PORT_ID);
unregister_message_port(MESSAGE_PORT_ID, PUB_APP_MANIFEST_URL);
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
=== undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_failToUnregisterMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_ports(MESSAGE_PORT_ID);
unregister_message_port(FAKE_MESSAGE_PORT_ID, PUB_APP_MANIFEST_URL);
// Because it failed to unregister, the entry still exists.
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
!== undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_removeTarget() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let targets = register_message_ports(MESSAGE_PORT_ID);
remove_target(targets.pubTarget);
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
=== undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_postMessage() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let countPubAppOnMessage = 0;
function pubAppOnMessage(aName, aData) {
countPubAppOnMessage++;
do_check_eq(aName, "InterAppMessagePort:OnMessage");
do_check_eq(aData.manifestURL, PUB_APP_MANIFEST_URL);
do_check_eq(aData.pageURL, PUB_APP_PAGE_URL);
do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
if (countPubAppOnMessage == 1) {
do_check_eq(aData.message.text, "sub app says world");
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello again" });
} else if (countPubAppOnMessage == 2) {
do_check_eq(aData.message.text, "sub app says world again");
clear_message_port_pairs();
run_next_test();
} else {
do_throw("pub app receives an unexpected message")
}
};
let countSubAppOnMessage = 0;
function subAppOnMessage(aName, aData) {
countSubAppOnMessage++;
do_check_eq(aName, "InterAppMessagePort:OnMessage");
do_check_eq(aData.manifestURL, SUB_APP_MANIFEST_URL);
do_check_eq(aData.pageURL, SUB_APP_PAGE_URL);
do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
if (countSubAppOnMessage == 1) {
do_check_eq(aData.message.text, "pub app says hello");
post_message(MESSAGE_PORT_ID,
SUB_APP_MANIFEST_URL,
{ text: "sub app says world" });
} else if (countSubAppOnMessage == 2) {
do_check_eq(aData.message.text, "pub app says hello again");
post_message(MESSAGE_PORT_ID,
SUB_APP_MANIFEST_URL,
{ text: "sub app says world again" });
} else {
do_throw("sub app receives an unexpected message")
}
};
register_message_ports(MESSAGE_PORT_ID, pubAppOnMessage, subAppOnMessage);
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello" });
});
add_test(function test_registerMessagePort_with_queued_messages() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_port(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
PUB_APP_PAGE_URL);
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello" });
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello again" });
let countSubAppOnMessage = 0;
function subAppOnMessage(aName, aData) {
countSubAppOnMessage++;
do_check_eq(aName, "InterAppMessagePort:OnMessage");
do_check_eq(aData.manifestURL, SUB_APP_MANIFEST_URL);
do_check_eq(aData.pageURL, SUB_APP_PAGE_URL);
do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
if (countSubAppOnMessage == 1) {
do_check_eq(aData.message.text, "pub app says hello");
} else if (countSubAppOnMessage == 2) {
do_check_eq(aData.message.text, "pub app says hello again");
clear_message_port_pairs();
run_next_test();
} else {
do_throw("sub app receives an unexpected message")
}
};
register_message_port(MESSAGE_PORT_ID,
SUB_APP_MANIFEST_URL,
SUB_APP_PAGE_URL,
subAppOnMessage);
});
add_test(function test_getConnections() {
create_allowed_connections(KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
function onGetConnections(aName, aData) {
do_check_eq(aName, "Webapps:GetConnections:Return:OK");
do_check_eq(aData.oid, OUTER_WINDOW_ID);
do_check_eq(aData.requestID, REQUEST_ID);
let connections = aData.connections;
do_check_eq(connections.length, 1);
do_check_eq(connections[0].keyword, KEYWORD);
do_check_eq(connections[0].pubAppManifestURL, PUB_APP_MANIFEST_URL);
do_check_eq(connections[0].subAppManifestURL, SUB_APP_MANIFEST_URL);
clear_allowed_connections();
run_next_test();
};
get_connections(PUB_APP_MANIFEST_URL,
OUTER_WINDOW_ID,
REQUEST_ID,
onGetConnections);
});
add_test(function test_cancelConnection() {
create_allowed_connections(KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_ports(MESSAGE_PORT_ID);
cancel_connections(PUB_APP_MANIFEST_URL,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
do_check_true(InterAppCommService._allowedConnections[KEYWORD]
=== undefined);
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
=== undefined);
clear_allowed_connections();
clear_message_port_pairs();
run_next_test();
});
function run_test() {
do_get_profile();
run_next_test();
}

View File

@ -2,4 +2,5 @@
head =
tail =
[test_inter_app_comm_service.js]
[test_manifestSanitizer.js]

View File

@ -1629,32 +1629,11 @@ int64_t GetPluginLastModifiedTime(const nsCOMPtr<nsIFile>& localfile)
bool
GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile,
const nsCOMPtr<nsISimpleEnumerator>& extensionDirs)
const nsCOMArray<nsIFile>& extensionDirs)
{
if (!extensionDirs) {
return false;
}
bool hasMore;
while (NS_SUCCEEDED(extensionDirs->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> supports;
nsresult rv = extensionDirs->GetNext(getter_AddRefs(supports));
if (NS_FAILED(rv)) {
continue;
}
nsCOMPtr<nsIFile> extDir(do_QueryInterface(supports, &rv));
if (NS_FAILED(rv)) {
continue;
}
nsCOMPtr<nsIFile> dir;
if (NS_FAILED(extDir->Clone(getter_AddRefs(dir)))) {
continue;
}
for (uint32_t i = 0; i < extensionDirs.Length(); ++i) {
bool contains;
if (NS_FAILED(dir->Contains(pluginFile, true, &contains)) || !contains) {
if (NS_FAILED(extensionDirs[i]->Contains(pluginFile, true, &contains)) || !contains) {
continue;
}
@ -1664,12 +1643,12 @@ GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile,
return false;
}
nsCOMPtr<nsISimpleEnumerator>
GetExtensionDirectories()
void
GetExtensionDirectories(nsCOMArray<nsIFile>& dirs)
{
nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
if (!dirService) {
return nullptr;
return;
}
nsCOMPtr<nsISimpleEnumerator> list;
@ -1677,10 +1656,21 @@ GetExtensionDirectories()
NS_GET_IID(nsISimpleEnumerator),
getter_AddRefs(list));
if (NS_FAILED(rv)) {
return nullptr;
return;
}
return list;
bool more;
while (NS_SUCCEEDED(list->HasMoreElements(&more)) && more) {
nsCOMPtr<nsISupports> next;
if (NS_FAILED(list->GetNext(getter_AddRefs(next)))) {
break;
}
nsCOMPtr<nsIFile> file = do_QueryInterface(next);
if (file) {
file->Normalize();
dirs.AppendElement(file);
}
}
}
struct CompareFilesByTime
@ -1746,10 +1736,8 @@ nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir,
pluginFiles.Sort(CompareFilesByTime());
nsCOMPtr<nsISimpleEnumerator> extensionDirs = GetExtensionDirectories();
if (!extensionDirs) {
PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("Could not get extension directories."));
}
nsCOMArray<nsIFile> extensionDirs;
GetExtensionDirectories(extensionDirs);
bool warnOutdated = false;

View File

@ -5,10 +5,10 @@
include $(topsrcdir)/config/rules.mk
ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
plugin_file_name = Test.plugin
plugin_file_names = Test.plugin SecondTest.plugin
addon_file_name = testaddon_$(TARGET_XPCOM_ABI).xpi
else
plugin_file_name = $(DLL_PREFIX)nptest$(DLL_SUFFIX)
plugin_file_names = $(DLL_PREFIX)nptest$(DLL_SUFFIX) $(DLL_PREFIX)npsecondtest$(DLL_SUFFIX)
addon_file_name = testaddon.xpi
endif
@ -20,4 +20,4 @@ libs::
$(NSINSTALL) -D $(testdir)
rm -f $(addonpath)
cd $(srcdir) && zip -rD $(addonpath) install.rdf
cd $(DIST) && zip -rD $(addonpath) plugins/$(plugin_file_name)
cd $(DIST) && zip -rD $(addonpath) $(foreach name,$(plugin_file_names),plugins/$(name))

View File

@ -14,27 +14,13 @@ const gIsLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc) ||
const gDirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
// Finds the test plugin library
function get_test_plugin() {
function get_test_plugin(secondplugin=false) {
var pluginEnum = gDirSvc.get("APluginsDL", Ci.nsISimpleEnumerator);
while (pluginEnum.hasMoreElements()) {
let dir = pluginEnum.getNext().QueryInterface(Ci.nsILocalFile);
let name = get_platform_specific_plugin_name(secondplugin);
let plugin = dir.clone();
// OSX plugin
plugin.append("Test.plugin");
if (plugin.exists()) {
plugin.normalize();
return plugin;
}
plugin = dir.clone();
// *nix plugin
plugin.append("libnptest.so");
if (plugin.exists()) {
plugin.normalize();
return plugin;
}
// Windows plugin
plugin = dir.clone();
plugin.append("nptest.dll");
plugin.append(name);
if (plugin.exists()) {
plugin.normalize();
return plugin;
@ -93,11 +79,17 @@ function do_get_profile_startup() {
return file.clone();
}
function get_platform_specific_plugin_name() {
if (gIsWindows) return "nptest.dll";
else if (gIsOSX) return "Test.plugin";
else if (gIsLinux) return "libnptest.so";
else return null;
function get_platform_specific_plugin_name(secondplugin=false) {
if (secondplugin) {
if (gIsWindows) return "npsecondtest.dll";
if (gIsOSX) return "SecondTest.plugin";
if (gIsLinux) return "libnpsecondtest.so";
} else {
if (gIsWindows) return "nptest.dll";
if (gIsOSX) return "Test.plugin";
if (gIsLinux) return "libnptest.so";
}
return null;
}
function get_platform_specific_plugin_suffix() {

View File

@ -41,6 +41,11 @@ add_task(function* test_state() {
// xpcshell tests have plugins in per-test profiles, so that's fine.
let file = get_test_plugin();
file.remove(true);
file = get_test_plugin(true);
file.remove(true);
Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_CLICKTOPLAY);
Services.prefs.setIntPref("plugin.defaultXpi.state", Ci.nsIPluginTag.STATE_ENABLED);
let success = yield installAddon(getTestaddonFilename());
Assert.ok(success, "Should have installed addon.");
@ -105,4 +110,8 @@ add_task(function* test_state() {
pluginDir.append(testPlugin.filename);
Assert.ok(pluginDir.exists(), "Plugin file should exist in addon directory: " + pluginDir.path);
testPlugin = get_test_plugintag("Second Test Plug-in");
Assert.notEqual(testPlugin, null, "Second test plugin should have been found");
Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Second test plugin from addon should have state enabled");
});

View File

@ -64,7 +64,8 @@ class B2GDesktopReftest(RefTest):
log.info("%s | Running tests: start.", os.path.basename(__file__))
cmd, args = self.build_command_line(options.app,
ignore_window_size=options.ignoreWindowSize)
ignore_window_size=options.ignoreWindowSize,
browser_arg=options.browser_arg)
self.runner = FirefoxRunner(profile=self.profile,
binary=cmd,
cmdargs=args,
@ -123,10 +124,14 @@ class B2GDesktopReftest(RefTest):
profile.set_preferences(prefs)
return profile
def build_command_line(self, app, ignore_window_size=False):
def build_command_line(self, app, ignore_window_size=False,
browser_arg=None):
cmd = os.path.abspath(app)
args = ['-marionette']
if browser_arg:
args += [browser_arg]
if not ignore_window_size:
args.extend(['--screen', '800x1000'])
return cmd, args

View File

@ -31,6 +31,11 @@ class B2GOptions(ReftestOptions):
ReftestOptions.__init__(self, automation)
self.add_option("--browser-arg", action="store",
type = "string", dest = "browser_arg",
help = "Optional command-line arg to pass to the browser")
defaults["browser_arg"] = None
self.add_option("--b2gpath", action="store",
type = "string", dest = "b2gPath",
help = "path to B2G repo or qemu dir")

View File

@ -1920,7 +1920,7 @@ abstract public class BrowserApp extends GeckoApp
*/
private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
info.added = true;
final Menu destination;
if (info.parent == 0) {
destination = menu;
@ -2056,7 +2056,7 @@ abstract public class BrowserApp extends GeckoApp
// Sets mMenu = menu.
super.onCreateOptionsMenu(menu);
// Inform the menu about the action-items bar.
// Inform the menu about the action-items bar.
if (menu instanceof GeckoMenu &&
HardwareUtils.isTablet()) {
((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
@ -2470,7 +2470,7 @@ abstract public class BrowserApp extends GeckoApp
/*
* If the app has been launched a certain number of times, and we haven't asked for feedback before,
* open a new tab with about:feedback when launching the app from the icon shortcut.
*/
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@ -2572,7 +2572,7 @@ abstract public class BrowserApp extends GeckoApp
@Override
public void onNewTabs(String[] urls) {
final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB);
for (String url : urls) {
if (!maybeSwitchToTab(url, flags)) {
openUrlAndStopEditing(url, true);

View File

@ -254,6 +254,8 @@ final class TouchEventHandler implements Tabs.OnTabsChangedListener {
if (event != null) {
dispatchEvent(event, allowDefaultAction);
event.recycle();
event = null;
}
if (mEventQueue.isEmpty()) {
// we have processed the backlog of events, and are all caught up.

View File

@ -78,6 +78,9 @@ class PanelItemView extends LinearLayout {
private ArticleItemView(Context context) {
super(context, R.layout.panel_article_item);
setOrientation(LinearLayout.HORIZONTAL);
final int padding = getResources().getDimensionPixelSize(R.dimen.article_item_view_padding);
setPadding(0, padding, 0, padding);
}
}

View File

@ -16,186 +16,85 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class TabMenuStrip extends LinearLayout
implements HomePager.Decor,
View.OnFocusChangeListener {
private static final String LOGTAG = "GeckoTabMenuStrip";
/**
* {@code TabMenuStrip} is the view used to display {@code HomePager} tabs
* on tablets. See {@code TabMenuStripLayout} for details about how the
* tabs are created and updated.
*/
public class TabMenuStrip extends HorizontalScrollView
implements HomePager.Decor {
private HomePager.OnTitleClickListener mOnTitleClickListener;
private Drawable mStrip;
private View mSelectedView;
// Offset between the selected tab title and the edge of the screen,
// except for the first and last tab in the tab strip.
private static final int TITLE_OFFSET_DIPS = 24;
// Data associated with the scrolling of the strip drawable.
private View toTab;
private View fromTab;
private float progress;
// This variable is used to predict the direction of scroll.
private float mPrevProgress;
private final int titleOffset;
private final TabMenuStripLayout layout;
public TabMenuStrip(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
a.recycle();
// Disable the scroll bar.
setHorizontalScrollBarEnabled(false);
if (stripResId != -1) {
mStrip = getResources().getDrawable(stripResId);
}
titleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
setWillNotDraw(false);
layout = new TabMenuStripLayout(context, attrs);
addView(layout, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
}
@Override
public void onAddPagerView(String title) {
final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
button.setText(title.toUpperCase());
addView(button);
button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
button.setOnFocusChangeListener(this);
layout.onAddPagerView(title);
}
@Override
public void removeAllPagerViews() {
removeAllViews();
layout.removeAllViews();
}
@Override
public void onPageSelected(final int position) {
mSelectedView = getChildAt(position);
// Callback to measure and draw the strip after the view is visible.
ViewTreeObserver vto = mSelectedView.getViewTreeObserver();
if (vto.isAlive()) {
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mSelectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (mStrip != null) {
mStrip.setBounds(mSelectedView.getLeft(),
mSelectedView.getTop(),
mSelectedView.getRight(),
mSelectedView.getBottom());
}
mPrevProgress = position;
}
});
}
layout.onPageSelected(position);
}
// Page scroll animates the drawable and its bounds from the previous to next child view.
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mStrip == null) {
layout.onPageScrolled(position, positionOffset, positionOffsetPixels);
final View selectedTitle = layout.getChildAt(position);
if (selectedTitle == null) {
return;
}
setScrollingData(position, positionOffset);
final int selectedTitleOffset = (int) (positionOffset * selectedTitle.getWidth());
if (fromTab == null || toTab == null) {
return;
int titleLeft = selectedTitle.getLeft() + selectedTitleOffset;
if (position > 0) {
titleLeft -= titleOffset;
}
final int fromTabLeft = fromTab.getLeft();
final int fromTabRight = fromTab.getRight();
final int toTabLeft = toTab.getLeft();
final int toTabRight = toTab.getRight();
mStrip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
0,
(int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
getHeight());
invalidate();
}
/*
* position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
* Normalized progress is relative to the the direction the page is being scrolled towards.
* For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
*/
private void setScrollingData(int position, float positionOffset) {
if (position >= getChildCount() - 1) {
return;
int titleRight = selectedTitle.getRight() + selectedTitleOffset;
if (position < layout.getChildCount() - 1) {
titleRight += titleOffset;
}
final float currProgress = position + positionOffset;
if (mPrevProgress > currProgress) {
toTab = getChildAt(position);
fromTab = getChildAt(position + 1);
progress = 1 - positionOffset;
} else {
toTab = getChildAt(position + 1);
fromTab = getChildAt(position);
progress = positionOffset;
}
mPrevProgress = currProgress;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mStrip != null) {
mStrip.draw(canvas);
}
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (v == this && hasFocus && getChildCount() > 0) {
mSelectedView.requestFocus();
return;
}
if (!hasFocus) {
return;
}
int i = 0;
final int numTabs = getChildCount();
while (i < numTabs) {
View view = getChildAt(i);
if (view == v) {
view.requestFocus();
if (isShown()) {
// A view is focused so send an event to announce the menu strip state.
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
break;
}
i++;
final int scrollX = getScrollX();
if (titleLeft < scrollX) {
// Tab strip overflows to the left.
scrollTo(titleLeft, 0);
} else if (titleRight > scrollX + getWidth()) {
// Tab strip overflows to the right.
scrollTo(titleRight - getWidth(), 0);
}
}
@Override
public void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
mOnTitleClickListener = onTitleClickListener;
}
private class ViewClickListener implements OnClickListener {
private final int mIndex;
public ViewClickListener(int index) {
mIndex = index;
}
@Override
public void onClick(View view) {
if (mOnTitleClickListener != null) {
mOnTitleClickListener.onTitleClicked(mIndex);
}
}
layout.setOnTitleClickListener(onTitleClickListener);
}
}

View File

@ -0,0 +1,194 @@
/* -*- 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.home;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* {@code TabMenuStripLayout} is the view that draws the {@code HomePager}
* tabs that are displayed in {@code TabMenuStrip}.
*/
class TabMenuStripLayout extends LinearLayout
implements View.OnFocusChangeListener {
private HomePager.OnTitleClickListener onTitleClickListener;
private Drawable strip;
private View selectedView;
// Data associated with the scrolling of the strip drawable.
private View toTab;
private View fromTab;
private float progress;
// This variable is used to predict the direction of scroll.
private float prevProgress;
TabMenuStripLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
a.recycle();
if (stripResId != -1) {
strip = getResources().getDrawable(stripResId);
}
setWillNotDraw(false);
}
void onAddPagerView(String title) {
final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
button.setText(title.toUpperCase());
addView(button);
button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
button.setOnFocusChangeListener(this);
}
void onPageSelected(final int position) {
selectedView = getChildAt(position);
// Callback to measure and draw the strip after the view is visible.
ViewTreeObserver vto = selectedView.getViewTreeObserver();
if (vto.isAlive()) {
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
selectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (strip != null) {
strip.setBounds(selectedView.getLeft(),
selectedView.getTop(),
selectedView.getRight(),
selectedView.getBottom());
}
prevProgress = position;
}
});
}
}
// Page scroll animates the drawable and its bounds from the previous to next child view.
void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (strip == null) {
return;
}
setScrollingData(position, positionOffset);
if (fromTab == null || toTab == null) {
return;
}
final int fromTabLeft = fromTab.getLeft();
final int fromTabRight = fromTab.getRight();
final int toTabLeft = toTab.getLeft();
final int toTabRight = toTab.getRight();
strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
0,
(int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
getHeight());
invalidate();
}
/*
* position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
* Normalized progress is relative to the the direction the page is being scrolled towards.
* For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
*/
void setScrollingData(int position, float positionOffset) {
if (position >= getChildCount() - 1) {
return;
}
final float currProgress = position + positionOffset;
if (prevProgress > currProgress) {
toTab = getChildAt(position);
fromTab = getChildAt(position + 1);
progress = 1 - positionOffset;
} else {
toTab = getChildAt(position + 1);
fromTab = getChildAt(position);
progress = positionOffset;
}
prevProgress = currProgress;
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (strip != null) {
strip.draw(canvas);
}
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (v == this && hasFocus && getChildCount() > 0) {
selectedView.requestFocus();
return;
}
if (!hasFocus) {
return;
}
int i = 0;
final int numTabs = getChildCount();
while (i < numTabs) {
View view = getChildAt(i);
if (view == v) {
view.requestFocus();
if (isShown()) {
// A view is focused so send an event to announce the menu strip state.
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
break;
}
i++;
}
}
void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
this.onTitleClickListener = onTitleClickListener;
}
private class ViewClickListener implements OnClickListener {
private final int mIndex;
public ViewClickListener(int index) {
mIndex = index;
}
@Override
public void onClick(View view) {
if (onTitleClickListener != null) {
onTitleClickListener.onTitleClicked(mIndex);
}
}
}
}

View File

@ -279,6 +279,7 @@ gbjar.sources += [
'home/SimpleCursorLoader.java',
'home/SuggestClient.java',
'home/TabMenuStrip.java',
'home/TabMenuStripLayout.java',
'home/TopSitesGridItemView.java',
'home/TopSitesGridView.java',
'home/TopSitesPanel.java',

View File

@ -8,19 +8,14 @@
<ImageView android:id="@+id/image"
android:layout_width="54dp"
android:layout_height="44dp"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
android:layout_marginLeft="10dip"
android:scaleType="centerCrop"/>
<LinearLayout android:id="@+id/title_desc_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="15dip"
android:paddingBottom="15dip"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:minHeight="@dimen/page_row_height"
android:gravity="center_vertical"
android:orientation="vertical">

View File

@ -107,4 +107,7 @@
<!-- PanelGridView dimensions -->
<dimen name="panel_grid_view_column_width">150dp</dimen>
<!-- ArticleItemView dimensions -->
<dimen name="article_item_view_padding">15dp</dimen>
</resources>

View File

@ -438,9 +438,8 @@ abstract class BaseTest extends BaseRobocopTest {
if (listLength > 1) {
for (int i = 1; i < listLength; i++) {
String itemName = "^" + listItems[i] + "$";
if (!waitForPreferencesText(itemName)) {
mSolo.scrollDown();
}
mAsserter.ok(waitForPreferencesText(itemName), "Waiting for and scrolling once to find item " + itemName, itemName + " found");
mAsserter.ok(waitForEnabledText(itemName), "Waiting for enabled text " + itemName, itemName + " option is present and enabled");
mSolo.clickOnText(itemName);
}
}

View File

@ -26,7 +26,12 @@ class MotionEventHelper {
Log.d(LOGTAG, "Triggering down at (" + x + "," + y + ")");
long downTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
mInstrumentation.sendPointerSync(event);
try {
mInstrumentation.sendPointerSync(event);
} finally {
event.recycle();
event = null;
}
return downTime;
}
@ -37,7 +42,12 @@ class MotionEventHelper {
public long move(long downTime, long moveTime, float x, float y) {
Log.d(LOGTAG, "Triggering move to (" + x + "," + y + ")");
MotionEvent event = MotionEvent.obtain(downTime, moveTime, MotionEvent.ACTION_MOVE, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
mInstrumentation.sendPointerSync(event);
try {
mInstrumentation.sendPointerSync(event);
} finally {
event.recycle();
event = null;
}
return downTime;
}
@ -48,7 +58,12 @@ class MotionEventHelper {
public long up(long downTime, long upTime, float x, float y) {
Log.d(LOGTAG, "Triggering up at (" + x + "," + y + ")");
MotionEvent event = MotionEvent.obtain(downTime, upTime, MotionEvent.ACTION_UP, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
mInstrumentation.sendPointerSync(event);
try {
mInstrumentation.sendPointerSync(event);
} finally {
event.recycle();
event = null;
}
return -1L;
}

View File

@ -203,8 +203,13 @@ class MotionEventReplayer {
eventTime * 1000000, action, pointerCount, pointerIds, (float[])pointerData,
metaState, xPrecision, yPrecision, deviceId, edgeFlags);
}
Log.v(LOGTAG, "Injecting " + event.toString());
mInstrumentation.sendPointerSync(event);
try {
Log.v(LOGTAG, "Injecting " + event.toString());
mInstrumentation.sendPointerSync(event);
} finally {
event.recycle();
event = null;
}
eventProperties.clear();
}

View File

@ -50,6 +50,7 @@ import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputMethodManager;
@ -190,7 +191,10 @@ public class BrowserToolbar extends ThemedRelativeLayout
urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
urlBarEntryDefaultLayoutParams = (RelativeLayout.LayoutParams) urlBarEntry.getLayoutParams();
urlBarEntryShrunkenLayoutParams = new RelativeLayout.LayoutParams(urlBarEntryDefaultLayoutParams);
// API level 19 adds a RelativeLayout.LayoutParams copy constructor, so we explicitly cast
// to ViewGroup.MarginLayoutParams to ensure consistency across platforms.
urlBarEntryShrunkenLayoutParams = new RelativeLayout.LayoutParams(
(ViewGroup.MarginLayoutParams) urlBarEntryDefaultLayoutParams);
urlBarEntryShrunkenLayoutParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.edit_layout);
urlBarEntryShrunkenLayoutParams.rightMargin = 0;
@ -397,7 +401,7 @@ public class BrowserToolbar extends ThemedRelativeLayout
public boolean onBackPressed() {
if (isEditing()) {
stopEditing();
cancelEdit();
return true;
}

View File

@ -8,12 +8,13 @@ config = {
"--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
"--profile=%(gaia_profile)s", "--app=%(application)s", "--desktop",
"--utility-path=%(utility_path)s", "--certificate-path=%(cert_path)s",
"--symbols-path=%(symbols_path)s",
"--symbols-path=%(symbols_path)s", "--browser-arg=%(browser_arg)s",
"--quiet"
],
"reftest_options": [
"--desktop", "--profile=%(gaia_profile)s", "--appname=%(application)s",
"--symbols-path=%(symbols_path)s", "%(test_manifest)s",
"--browser-arg=%(browser_arg)s", "--symbols-path=%(symbols_path)s",
"%(test_manifest)s"
]
}