mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to mozilla-inbound
This commit is contained in:
commit
9f6bd57bb5
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "9f480d1c52a46cac8e9a0be04bdb0d73810bc596",
|
||||
"revision": "2eae294604eb70e0e6eaee76b17e155ffd031107",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
|
889
dom/apps/src/InterAppCommService.jsm
Normal file
889
dom/apps/src/InterAppCommService.jsm
Normal 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();
|
@ -27,6 +27,7 @@ EXTRA_JS_MODULES += [
|
||||
'AppDownloadManager.jsm',
|
||||
'AppsServiceChild.jsm',
|
||||
'FreeSpaceWatcher.jsm',
|
||||
'InterAppCommService.jsm',
|
||||
'OfflineCacheInstaller.jsm',
|
||||
'PermissionsInstaller.jsm',
|
||||
'PermissionsTable.jsm',
|
||||
|
457
dom/apps/tests/unit/test_inter_app_comm_service.js
Normal file
457
dom/apps/tests/unit/test_inter_app_comm_service.js
Normal 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();
|
||||
}
|
@ -2,4 +2,5 @@
|
||||
head =
|
||||
tail =
|
||||
|
||||
[test_inter_app_comm_service.js]
|
||||
[test_manifestSanitizer.js]
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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() {
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
194
mobile/android/base/home/TabMenuStripLayout.java
Normal file
194
mobile/android/base/home/TabMenuStripLayout.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
|
@ -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">
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user