зеркало из https://github.com/mozilla/gecko-dev.git
Коммит
9e25960569
|
@ -15,9 +15,9 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
|
@ -134,9 +134,9 @@
|
|||
<project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
|
||||
<project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
|
||||
<project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
|
||||
<project name="kernel/common" path="kernel" revision="0b4249f88cb8faadfbf80be207af14198dec08e3"/>
|
||||
<project name="kernel/common" path="kernel" revision="1ef12a094a59d966a2576d0991a3618b591a7da9"/>
|
||||
<project name="platform/system/core" path="system/core" revision="53d584d4a4b4316e4de9ee5f210d662f89b44e7e"/>
|
||||
<project name="u-boot" path="u-boot" revision="6980cf8f8cf9c1d43ff92b7af13425a5ed531d12"/>
|
||||
<project name="u-boot" path="u-boot" revision="f1502910977ac88f43da7bf9277c3523ad4b0b2f"/>
|
||||
<project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="6974f8e771d4d8e910357a6739ab124768891e8f"/>
|
||||
<project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="c985699a5140b6dd5c7a43295e4d4397ce9b1267"/>
|
||||
<project name="vendor/sprd/partner" path="vendor/sprd/partner" revision="8649c7145972251af11b0639997edfecabfc7c2e"/>
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
<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="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
<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="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"git": {
|
||||
"git_revision": "7512026a377271a0cade12d70846557f0bc7781c",
|
||||
"git_revision": "f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "53713e514b92cccfd55404cebd2b627cb9804bc2",
|
||||
"revision": "c00390d70e15726ba3e4c50bd2353fbf991f25c9",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="f34ce82a840ad3c0aed3bfff18517b3f6a0eb37f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2e85143db5d5f6edc4c2b97263c02b558fee757e"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
|
@ -141,7 +141,7 @@
|
|||
<default remote="caf" revision="refs/tags/android-5.0.0_r6" sync-j="4"/>
|
||||
<!-- Nexus 5 specific things -->
|
||||
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="ba62cc8b78c30d36181b8060a2016cc8da166236"/>
|
||||
<project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="dc525eeb67301a44f514a7ac40a59ec592b34e31"/>
|
||||
<project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="a6f00b0d7519168329ca4a3f66d3be30d66e0c9e"/>
|
||||
<project name="device/qcom/common" path="device/qcom/common" remote="caf" revision="3697e5acf25629b82658334e3f8ee3b6df5becab"/>
|
||||
<project name="device_lge_hammerhead-kernel" path="device/lge/hammerhead-kernel" remote="b2g" revision="1268f640184df5ef759ada669f101a613451673a"/>
|
||||
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="0cb8574d338bf9f15b45ace7c08ad6deae9673ee"/>
|
||||
|
|
|
@ -21,6 +21,12 @@ XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
|
|||
"@mozilla.org/telephony/volume-service;1",
|
||||
"nsIVolumeService");
|
||||
|
||||
/**
|
||||
* The content process implementations of navigator.mozDownloadManager and its
|
||||
* DOMDownload download objects. Uses DownloadsIPC.jsm to communicate with
|
||||
* DownloadsAPI.jsm in the parent process.
|
||||
*/
|
||||
|
||||
function debug(aStr) {
|
||||
#ifdef MOZ_DEBUG
|
||||
dump("-*- DownloadsAPI.js : " + aStr + "\n");
|
||||
|
@ -40,6 +46,15 @@ DOMDownloadManagerImpl.prototype = {
|
|||
this.initDOMRequestHelper(aWindow,
|
||||
["Downloads:Added",
|
||||
"Downloads:Removed"]);
|
||||
|
||||
// Get the manifest URL if this is an installed app
|
||||
let appsService = Cc["@mozilla.org/AppsService;1"]
|
||||
.getService(Ci.nsIAppsService);
|
||||
let principal = aWindow.document.nodePrincipal;
|
||||
// This returns the empty string if we're not an installed app. Coerce to
|
||||
// null.
|
||||
this._manifestURL = appsService.getManifestURLByLocalId(principal.appId) ||
|
||||
null;
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
|
@ -79,23 +94,8 @@ DOMDownloadManagerImpl.prototype = {
|
|||
|
||||
clearAllDone: function() {
|
||||
debug("clearAllDone()");
|
||||
return this.createPromise(function (aResolve, aReject) {
|
||||
DownloadsIPC.clearAllDone().then(
|
||||
function(aDownloads) {
|
||||
// Turn the list of download objects into DOM objects and
|
||||
// send them.
|
||||
let array = new this._window.Array();
|
||||
for (let id in aDownloads) {
|
||||
let dom = createDOMDownloadObject(this._window, aDownloads[id]);
|
||||
array.push(this._prepareForContent(dom));
|
||||
}
|
||||
aResolve(array);
|
||||
}.bind(this),
|
||||
function() {
|
||||
aReject("ClearAllDoneError");
|
||||
}
|
||||
);
|
||||
}.bind(this));
|
||||
// This is a void function; we just kick it off. No promises, etc.
|
||||
DownloadsIPC.clearAllDone();
|
||||
},
|
||||
|
||||
remove: function(aDownload) {
|
||||
|
@ -121,6 +121,67 @@ DOMDownloadManagerImpl.prototype = {
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
adoptDownload: function(aAdoptDownloadDict) {
|
||||
// Our AdoptDownloadDict only includes simple types, which WebIDL enforces.
|
||||
// We have no object/any types so we do not need to worry about invoking
|
||||
// JSON.stringify (and it inheriting our security privileges).
|
||||
debug("adoptDownload");
|
||||
return this.createPromise(function (aResolve, aReject) {
|
||||
if (!aAdoptDownloadDict) {
|
||||
debug("Download dictionary is required!");
|
||||
aReject("InvalidDownload");
|
||||
return;
|
||||
}
|
||||
if (!aAdoptDownloadDict.storageName || !aAdoptDownloadDict.storagePath ||
|
||||
!aAdoptDownloadDict.contentType) {
|
||||
debug("Missing one of: storageName, storagePath, contentType");
|
||||
aReject("InvalidDownload");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert storageName/storagePath to a local filesystem path.
|
||||
let volume;
|
||||
// getVolumeByName throws if you give it something it doesn't like
|
||||
// because XPConnect converts the NS_ERROR_NOT_AVAILABLE to an
|
||||
// exception. So catch it.
|
||||
try {
|
||||
volume = volumeService.getVolumeByName(aAdoptDownloadDict.storageName);
|
||||
} catch (ex) {}
|
||||
if (!volume) {
|
||||
debug("Invalid storage name: " + aAdoptDownloadDict.storageName);
|
||||
aReject("InvalidDownload");
|
||||
return;
|
||||
}
|
||||
let computedPath = volume.mountPoint + '/' +
|
||||
aAdoptDownloadDict.storagePath;
|
||||
// We validate that there is actually a file at the given path in the
|
||||
// parent process in DownloadsAPI.js because that's where the file
|
||||
// access would actually occur either way.
|
||||
|
||||
// Create a DownloadsAPI.jsm 'jsonDownload' style representation.
|
||||
let jsonDownload = {
|
||||
url: aAdoptDownloadDict.url,
|
||||
path: computedPath,
|
||||
contentType: aAdoptDownloadDict.contentType,
|
||||
startTime: aAdoptDownloadDict.startTime.valueOf() || Date.now(),
|
||||
sourceAppManifestURL: this._manifestURL
|
||||
};
|
||||
|
||||
DownloadsIPC.adoptDownload(jsonDownload).then(
|
||||
function(aResult) {
|
||||
let domDownload = createDOMDownloadObject(this._window, aResult);
|
||||
aResolve(this._prepareForContent(domDownload));
|
||||
}.bind(this),
|
||||
function(aResult) {
|
||||
// This will be one of: AdoptError (generic catch-all),
|
||||
// AdoptNoSuchFile, AdoptFileIsDirectory
|
||||
aReject(aResult.error);
|
||||
}
|
||||
);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Turns a chrome download object into a content accessible one.
|
||||
* When we have __DOM_IMPL__ available we just use that, otherwise
|
||||
|
@ -295,6 +356,11 @@ DOMDownloadImpl.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize a DOMDownload instance for the given window using the
|
||||
* 'jsonDownload' serialized format of the download encoded by
|
||||
* DownloadsAPI.jsm.
|
||||
*/
|
||||
_init: function(aWindow, aDownload) {
|
||||
this._window = aWindow;
|
||||
this.id = aDownload.id;
|
||||
|
@ -314,12 +380,13 @@ DOMDownloadImpl.prototype = {
|
|||
}
|
||||
|
||||
let props = ["totalBytes", "currentBytes", "url", "path", "storageName",
|
||||
"storagePath", "state", "contentType", "startTime"];
|
||||
"storagePath", "state", "contentType", "startTime",
|
||||
"sourceAppManifestURL"];
|
||||
let changed = false;
|
||||
let changedProps = {};
|
||||
|
||||
props.forEach((prop) => {
|
||||
if (aDownload[prop] && (aDownload[prop] != this[prop])) {
|
||||
if (prop in aDownload && (aDownload[prop] != this[prop])) {
|
||||
this[prop] = aDownload[prop];
|
||||
changedProps[prop] = changed = true;
|
||||
}
|
||||
|
|
|
@ -13,11 +13,19 @@ this.EXPORTED_SYMBOLS = [];
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Downloads.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
||||
"@mozilla.org/parentprocessmessagemanager;1",
|
||||
"nsIMessageBroadcaster");
|
||||
|
||||
/**
|
||||
* Parent process logic that services download API requests from the
|
||||
* DownloadAPI.js instances in content processeses. The actual work of managing
|
||||
* downloads is done by Toolkit's Downloads.jsm. This module is loaded by B2G's
|
||||
* shell.js
|
||||
*/
|
||||
|
||||
function debug(aStr) {
|
||||
#ifdef MOZ_DEBUG
|
||||
dump("-*- DownloadsAPI.jsm : " + aStr + "\n");
|
||||
|
@ -49,7 +57,8 @@ let DownloadsAPI = {
|
|||
"Downloads:ClearAllDone",
|
||||
"Downloads:Remove",
|
||||
"Downloads:Pause",
|
||||
"Downloads:Resume"].forEach((msgName) => {
|
||||
"Downloads:Resume",
|
||||
"Downloads:Adopt"].forEach((msgName) => {
|
||||
ppmm.addMessageListener(msgName, this);
|
||||
});
|
||||
|
||||
|
@ -92,7 +101,9 @@ let DownloadsAPI = {
|
|||
url: aDownload.source.url,
|
||||
path: aDownload.target.path,
|
||||
contentType: aDownload.contentType,
|
||||
startTime: aDownload.startTime.getTime()
|
||||
startTime: aDownload.startTime.getTime(),
|
||||
sourceAppManifestURL: aDownload._unknownProperties &&
|
||||
aDownload._unknownProperties.sourceAppManifestURL
|
||||
};
|
||||
|
||||
if (aDownload.error) {
|
||||
|
@ -165,6 +176,9 @@ let DownloadsAPI = {
|
|||
case "Downloads:Resume":
|
||||
this.resume(aMessage.data, aMessage.target);
|
||||
break;
|
||||
case "Downloads:Adopt":
|
||||
this.adoptDownload(aMessage.data, aMessage.target);
|
||||
break;
|
||||
default:
|
||||
debug("Invalid message: " + aMessage.name);
|
||||
}
|
||||
|
@ -186,17 +200,9 @@ let DownloadsAPI = {
|
|||
|
||||
clearAllDone: function(aData, aMm) {
|
||||
debug("clearAllDone called!");
|
||||
let self = this;
|
||||
Task.spawn(function () {
|
||||
let list = yield Downloads.getList(Downloads.ALL);
|
||||
yield list.removeFinished();
|
||||
list = yield Downloads.getList(Downloads.ALL);
|
||||
let downloads = yield list.getAll();
|
||||
let res = [];
|
||||
downloads.forEach((aDownload) => {
|
||||
res.push(self.jsonDownload(aDownload));
|
||||
});
|
||||
aMm.sendAsyncMessage("Downloads:ClearAllDone:Return", res);
|
||||
list.removeFinished();
|
||||
}).then(null, Components.utils.reportError);
|
||||
},
|
||||
|
||||
|
@ -262,6 +268,97 @@ let DownloadsAPI = {
|
|||
aData, "ResumeError");
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Receive a download to adopt in the same representation we produce from
|
||||
* our "jsonDownload" normalizer and add it to the list of downloads.
|
||||
*/
|
||||
adoptDownload: function(aData, aMm) {
|
||||
let adoptJsonRep = aData.jsonDownload;
|
||||
debug("adoptDownload " + uneval(adoptJsonRep));
|
||||
|
||||
Task.spawn(function* () {
|
||||
// Verify that the file exists on disk. This will result in a rejection
|
||||
// if the file does not exist. We will also use this information for the
|
||||
// file size to avoid weird inconsistencies. We ignore the filesystem
|
||||
// timestamp in favor of whatever the caller is telling us.
|
||||
let fileInfo = yield OS.File.stat(adoptJsonRep.path);
|
||||
|
||||
// We also require that the file is not a directory.
|
||||
if (fileInfo.isDir) {
|
||||
throw new Error("AdoptFileIsDirectory");
|
||||
}
|
||||
|
||||
// We need to create a Download instance to add to the list. Create a
|
||||
// serialized representation and then from there the instance.
|
||||
let serializedRep = {
|
||||
// explicit initializations in toSerializable
|
||||
source: {
|
||||
url: adoptJsonRep.url
|
||||
// This is where isPrivate would go if adoption supported private
|
||||
// browsing.
|
||||
},
|
||||
target: {
|
||||
path: adoptJsonRep.path,
|
||||
},
|
||||
startTime: adoptJsonRep.startTime,
|
||||
// kPlainSerializableDownloadProperties propagations
|
||||
succeeded: true, // (all adopted downloads are required to be completed)
|
||||
totalBytes: fileInfo.size,
|
||||
contentType: adoptJsonRep.contentType,
|
||||
// unknown properties added/used by the DownloadsAPI
|
||||
currentBytes: fileInfo.size,
|
||||
sourceAppManifestURL: adoptJsonRep.sourceAppManifestURL
|
||||
};
|
||||
|
||||
let download = yield Downloads.createDownload(serializedRep);
|
||||
|
||||
// The ALL list is a DownloadCombinedList instance that combines the
|
||||
// PUBLIC (persisted to disk) and PRIVATE (ephemeral) download lists..
|
||||
// When we call add on it, it dispatches to the appropriate list based on
|
||||
// the 'isPrivate' field of the source. (Which we don't initialize and
|
||||
// defaults to false.)
|
||||
let allDownloadList = yield Downloads.getList(Downloads.ALL);
|
||||
|
||||
// This add will automatically notify all views of the added download,
|
||||
// including DownloadsAPI instances and the DownloadAutoSaveView that's
|
||||
// subscribed to the PUBLIC list and will save the download.
|
||||
yield allDownloadList.add(download);
|
||||
|
||||
debug("download adopted");
|
||||
// The notification above occurred synchronously, and so we will have
|
||||
// already dispatched an added notification for our download to the child
|
||||
// process in question. As such, we only need to relay the download id
|
||||
// since the download will already have been cached.
|
||||
return download;
|
||||
}.bind(this)).then(
|
||||
(download) => {
|
||||
sendPromiseMessage(aMm, "Downloads:Adopt:Return",
|
||||
{
|
||||
id: this.downloadId(download),
|
||||
promiseId: aData.promiseId
|
||||
});
|
||||
},
|
||||
(ex) => {
|
||||
let reportAs = "AdoptError";
|
||||
// Provide better error codes for expected errors.
|
||||
if (ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
|
||||
reportAs = "AdoptNoSuchFile";
|
||||
} else if (ex.message === "AdoptFileIsDirectory") {
|
||||
reportAs = ex.message;
|
||||
} else {
|
||||
// Anything else is unexpected and should be reported to help track
|
||||
// down what's going wrong.
|
||||
debug("unexpected download error: " + ex);
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
sendPromiseMessage(aMm, "Downloads:Adopt:Return",
|
||||
{
|
||||
promiseId: aData.promiseId
|
||||
},
|
||||
reportAs);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ const ipcMessages = ["Downloads:Added",
|
|||
"Downloads:Removed",
|
||||
"Downloads:Changed",
|
||||
"Downloads:GetList:Return",
|
||||
"Downloads:ClearAllDone:Return",
|
||||
"Downloads:Remove:Return",
|
||||
"Downloads:Pause:Return",
|
||||
"Downloads:Resume:Return"];
|
||||
"Downloads:Resume:Return",
|
||||
"Downloads:Adopt:Return"];
|
||||
|
||||
this.DownloadsIPC = {
|
||||
downloads: {},
|
||||
|
@ -54,7 +54,6 @@ this.DownloadsIPC = {
|
|||
// We need to get the list of current downloads.
|
||||
this.ready = false;
|
||||
this.getListPromises = [];
|
||||
this.clearAllPromises = [];
|
||||
this.downloadPromises = {};
|
||||
cpmm.sendAsyncMessage("Downloads:GetList", {});
|
||||
this._promiseId = 0;
|
||||
|
@ -93,12 +92,6 @@ this.DownloadsIPC = {
|
|||
}
|
||||
this.ready = true;
|
||||
break;
|
||||
case "Downloads:ClearAllDone:Return":
|
||||
this._updateDownloadsArray(download);
|
||||
this.clearAllPromises.forEach(aPromise =>
|
||||
aPromise.resolve(this.downloads));
|
||||
this.clearAllPromises.length = 0;
|
||||
break;
|
||||
case "Downloads:Added":
|
||||
this.downloads[download.id] = download;
|
||||
this.notifyChanges(download.id);
|
||||
|
@ -139,6 +132,7 @@ this.DownloadsIPC = {
|
|||
case "Downloads:Remove:Return":
|
||||
case "Downloads:Pause:Return":
|
||||
case "Downloads:Resume:Return":
|
||||
case "Downloads:Adopt:Return":
|
||||
if (this.downloadPromises[download.promiseId]) {
|
||||
if (!download.error) {
|
||||
this.downloadPromises[download.promiseId].resolve(download);
|
||||
|
@ -167,14 +161,11 @@ this.DownloadsIPC = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Returns a promise that is resolved with the list of current downloads.
|
||||
*/
|
||||
* Void function to trigger removal of completed downloads.
|
||||
*/
|
||||
clearAllDone: function() {
|
||||
debug("clearAllDone");
|
||||
let deferred = Promise.defer();
|
||||
this.clearAllPromises.push(deferred);
|
||||
cpmm.sendAsyncMessage("Downloads:ClearAllDone", {});
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
promiseId: function() {
|
||||
|
@ -211,6 +202,16 @@ this.DownloadsIPC = {
|
|||
return deferred.promise;
|
||||
},
|
||||
|
||||
adoptDownload: function(aJsonDownload) {
|
||||
debug("adoptDownload");
|
||||
let deferred = Promise.defer();
|
||||
let pId = this.promiseId();
|
||||
this.downloadPromises[pId] = deferred;
|
||||
cpmm.sendAsyncMessage("Downloads:Adopt",
|
||||
{ jsonDownload: aJsonDownload, promiseId: pId });
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic == "xpcom-shutdown") {
|
||||
ipcMessages.forEach((aMessage) => {
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* A helper to clear out the existing downloads known to the mozDownloadManager
|
||||
* / downloads.js.
|
||||
*
|
||||
* It exists because previously mozDownloadManager.clearAllDone() thought that
|
||||
* when it returned that all the completed downloads would be cleared out. It
|
||||
* was wrong and this led to various intermittent test failurse. In discussion
|
||||
* on https://bugzil.la/979446#c13 and onwards, it was decided that
|
||||
* clearAllDone() was in the wrong and that the jsdownloads API it depends on
|
||||
* was not going to change to make it be in the right.
|
||||
*
|
||||
* The existing uses of clearAllDone() in tests seemed to be about:
|
||||
* - Exploding if there was somehow still a download in progress
|
||||
* - Clearing out the download list at the start of a test so that calls to
|
||||
* getDownloads() wouldn't have to worry about existing downloads, etc.
|
||||
*
|
||||
* From discussion, the right way to handle clearing is to wait for the expected
|
||||
* removal events to occur for the existing downloads. So that's what we do.
|
||||
* We still generate a test failure if there are any in-progress downloads.
|
||||
*
|
||||
* @param {Boolean} [getDownloads=false]
|
||||
* If true, invoke getDownloads after clearing the download list and return
|
||||
* its value.
|
||||
*/
|
||||
function clearAllDoneHelper(getDownloads) {
|
||||
var clearedPromise = new Promise(function(resolve, reject) {
|
||||
function gotDownloads(downloads) {
|
||||
// If there are no downloads, we're already done.
|
||||
if (downloads.length === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Track the set of expected downloads that will be finalized.
|
||||
var expectedIds = new Set();
|
||||
function changeHandler(evt) {
|
||||
var download = evt.download;
|
||||
if (download.state === "finalized") {
|
||||
expectedIds.delete(download.id);
|
||||
if (expectedIds.size === 0) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
downloads.forEach(function(download) {
|
||||
if (download.state === "downloading") {
|
||||
ok(false, "A download is still active: " + download.path);
|
||||
reject("Active download");
|
||||
}
|
||||
download.onstatechange = changeHandler;
|
||||
expectedIds.add(download.id);
|
||||
});
|
||||
navigator.mozDownloadManager.clearAllDone();
|
||||
}
|
||||
function gotBadNews(err) {
|
||||
ok(false, "Problem clearing all downloads: " + err);
|
||||
reject(err);
|
||||
}
|
||||
navigator.mozDownloadManager.getDownloads().then(gotDownloads, gotBadNews);
|
||||
});
|
||||
if (!getDownloads) {
|
||||
return clearedPromise;
|
||||
}
|
||||
return clearedPromise.then(function() {
|
||||
return navigator.mozDownloadManager.getDownloads();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
function is(a, b, msg) {
|
||||
alert((a === b ? 'OK' : 'KO') + ' ' + a + ' should equal ' + b + ': ' + msg);
|
||||
}
|
||||
|
||||
function ok(a, msg) {
|
||||
alert((a ? 'OK' : 'KO')+ ' ' + msg);
|
||||
}
|
||||
|
||||
function info(msg) {
|
||||
alert('INFO ' + msg);
|
||||
}
|
||||
|
||||
function cbError() {
|
||||
alert('KO error');
|
||||
}
|
||||
|
||||
function finish() {
|
||||
alert('DONE');
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
var gBasePath = "tests/dom/downloads/tests/";
|
||||
var gTemplate = "file_app.template.webapp";
|
||||
|
||||
function handleRequest(request, response) {
|
||||
var query = getQuery(request);
|
||||
|
||||
var testToken = query.testToken || '';
|
||||
var appType = query.appType || 'web';
|
||||
|
||||
var template = gBasePath + gTemplate;
|
||||
response.setHeader("Content-Type", "application/x-web-app-manifest+json", false);
|
||||
var body = readTemplate(template)
|
||||
.replace(/TESTTOKEN/g, testToken)
|
||||
.replace(/APPTYPE/g, appType);
|
||||
response.write();
|
||||
}
|
||||
|
||||
// Copy-pasted incantations. There ought to be a better way to synchronously read
|
||||
// a file into a string, but I guess we're trying to discourage that.
|
||||
function readTemplate(path) {
|
||||
var file = Components.classes["@mozilla.org/file/directory_service;1"].
|
||||
getService(Components.interfaces.nsIProperties).
|
||||
get("CurWorkD", Components.interfaces.nsILocalFile);
|
||||
var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
|
||||
createInstance(Components.interfaces.nsIFileInputStream);
|
||||
var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
|
||||
createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
var split = path.split("/");
|
||||
for(var i = 0; i < split.length; ++i) {
|
||||
file.append(split[i]);
|
||||
}
|
||||
fis.init(file, -1, -1, false);
|
||||
cis.init(fis, "UTF-8", 0, 0);
|
||||
|
||||
var data = "";
|
||||
let (str = {}) {
|
||||
let read = 0;
|
||||
do {
|
||||
read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value
|
||||
data += str.value;
|
||||
} while (read != 0);
|
||||
}
|
||||
cis.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
function getQuery(request) {
|
||||
var query = {};
|
||||
request.queryString.split('&').forEach(function (val) {
|
||||
var [name, value] = val.split('=');
|
||||
query[name] = unescape(value);
|
||||
});
|
||||
return query;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "Really Rapid Release (hosted)",
|
||||
"description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
|
||||
"type": "APPTYPE",
|
||||
"launch_path": "/tests/dom/downloads/tests/TESTTOKEN",
|
||||
"icons": { "128": "default_icon" }
|
||||
}
|
|
@ -1,12 +1,24 @@
|
|||
[DEFAULT]
|
||||
skip-if = buildapp == 'mulet' || buildapp == 'b2g' # bug 979446, frequent failures
|
||||
# The actual requirement for mozDownloadManager is MOZ_GONK because of
|
||||
# the nsIVolumeService dependency. Until https://bugzil.la/1130264 is
|
||||
# addressed, there is no way for mulet to run these tests.
|
||||
run-if = toolkit == 'gonk'
|
||||
support-files =
|
||||
serve_file.sjs
|
||||
clear_all_done_helper.js
|
||||
file_app.template.webapp
|
||||
file_app.sjs
|
||||
common_app.js
|
||||
shim_app_as_test.js
|
||||
shim_app_as_test_chrome.js
|
||||
testapp_downloads_adopt_download.html
|
||||
testapp_downloads_adopt_download.js
|
||||
testapp_downloads_adopt_download.manifest
|
||||
|
||||
[test_downloads_navigator_object.html]
|
||||
[test_downloads_basic.html]
|
||||
[test_downloads_large.html]
|
||||
[test_downloads_adopt_download.html]
|
||||
[test_downloads_bad_file.html]
|
||||
[test_downloads_pause_remove.html]
|
||||
[test_downloads_pause_resume.html]
|
||||
skip-if = toolkit=='gonk' # b2g(bug 947167) b2g-debug(bug 947167)
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
/**
|
||||
* Support logic to run a test file as an installed app. This file is derived
|
||||
* from dom/requestsync/tests/test_basic_app.html but uses
|
||||
* DOMApplicationRegistry in a chrome script (shim_app_as_test_chrome.js) to
|
||||
* directly install the apps instead of mozApps.install because mozApps.install
|
||||
* can't install privileged/certified apps. (This is the same mechanism used by
|
||||
* the Firefox OS Gaia email app's backend test runner.)
|
||||
*
|
||||
* You really only want to do this if your test cares about the app's origin
|
||||
* or you REALLY want to double-check AvailableIn and other WebIDL-provided
|
||||
* security mechanisms.
|
||||
*
|
||||
* If you trust WebIDL, your life may be made significantly easier by just
|
||||
* setting the pref "dom.ignore_webidl_scope_checks" to true, which makes
|
||||
* BindingUtils.cpp's IsInPrivilegedApp and IsInCertifiedApp return true no
|
||||
* matter what *on the main thread*. You are potentially out of luck on
|
||||
* workers since at the time of writing this since the values stored on
|
||||
* WorkerPrivateParent are based on the app status and ignore the pref.
|
||||
*
|
||||
* TO USE THIS:
|
||||
*
|
||||
* Make sure you have the usual header boilerplate:
|
||||
* <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
* <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
*
|
||||
* You also want to add this file!
|
||||
* <script type="application/javascript" src="shim_app_as_test.js"></script>
|
||||
*
|
||||
* In your script body, issue a call like so:
|
||||
* runAppTest({
|
||||
* appFile: 'testapp_downloads_adopt_download.html',
|
||||
* appManifest: 'testapp_downloads_adopt_download.manifest',
|
||||
* appType: 'certified',
|
||||
* extraPrefs: {
|
||||
* set: [["dom.mozDownloads.enabled", true]]
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* You shouldn't be adding other stuff to that file. Instead, you want
|
||||
* everything in your testapp_*.html file. And you probably just want to copy
|
||||
* and paste from an existing one of those...
|
||||
*/
|
||||
|
||||
var gManifestURL;
|
||||
var gApp;
|
||||
var gOptions;
|
||||
|
||||
// Load the chrome script.
|
||||
var gChromeHelper = SpecialPowers.loadChromeScript(
|
||||
SimpleTest.getTestFileURL('shim_app_as_test_chrome.js'));
|
||||
|
||||
function installApp() {
|
||||
info("installing app");
|
||||
var useOrigin = document.location.origin;
|
||||
gChromeHelper.sendAsyncMessage(
|
||||
'install',
|
||||
{
|
||||
origin: useOrigin,
|
||||
manifestURL: SimpleTest.getTestFileURL(gOptions.appManifest),
|
||||
});
|
||||
}
|
||||
|
||||
function installedApp(appInfo) {
|
||||
gApp = appInfo;
|
||||
ok(!!appInfo, 'installed app');
|
||||
runTests();
|
||||
}
|
||||
gChromeHelper.addMessageListener('installed', installedApp);
|
||||
|
||||
function uninstallApp() {
|
||||
info('uninstalling app');
|
||||
gChromeHelper.sendAsyncMessage('uninstall', gApp);
|
||||
}
|
||||
|
||||
function uninstalledApp(success) {
|
||||
ok(success, 'uninstalled app');
|
||||
runTests();
|
||||
}
|
||||
gChromeHelper.addMessageListener('uninstalled', uninstalledApp);
|
||||
|
||||
function testApp() {
|
||||
var cleanupFrame;
|
||||
var handleTestMessage = function(message) {
|
||||
if (/^OK/.exec(message)) {
|
||||
ok(true, "Message from app: " + message);
|
||||
} else if (/^KO/.exec(message)) {
|
||||
ok(false, "Message from app: " + message);
|
||||
} else if (/^INFO/.exec(message)) {
|
||||
info("Message from app: " + message.substring(5));
|
||||
} else if (/^DONE$/.exec(message)) {
|
||||
ok(true, "Messaging from app complete");
|
||||
cleanupFrame();
|
||||
runTests();
|
||||
}
|
||||
};
|
||||
|
||||
// Bug 1097479 means that embed-webapps does not work if you are already
|
||||
// OOP, as we are for b2g. So we need to have the chrome script run our
|
||||
// app in a sibling iframe to the one we're living in. When that bug is
|
||||
// fixed or we are run in a non-b2g context, we can set this value to false
|
||||
// or otherwise conditionalize based on behaviour.
|
||||
var needSiblingIframeHack = true;
|
||||
|
||||
if (needSiblingIframeHack) {
|
||||
gChromeHelper.sendAsyncMessage('run', gApp);
|
||||
|
||||
gChromeHelper.addMessageListener('appMessage', handleTestMessage);
|
||||
gChromeHelper.addMessageListener('appError', function(data) {
|
||||
ok(false, "Error in app frame: " + data.message);
|
||||
});
|
||||
|
||||
cleanupFrame = function() {
|
||||
gChromeHelper.sendAsyncMessage('close', {});
|
||||
};
|
||||
} else {
|
||||
var ifr = document.createElement('iframe');
|
||||
ifr.setAttribute('mozbrowser', 'true');
|
||||
ifr.setAttribute('mozapp', gApp.manifestURL);
|
||||
|
||||
cleanupFrame = function() {
|
||||
ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
|
||||
domParent.removeChild(ifr);
|
||||
};
|
||||
|
||||
// Set us up to listen for messages from the app.
|
||||
var listener = function(e) {
|
||||
var message = e.detail.message; // e.detail.message;
|
||||
handleTestMessage(message);
|
||||
};
|
||||
|
||||
// This event is triggered when the app calls "alert".
|
||||
ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
|
||||
ifr.addEventListener('mozbrowsererror', function(evt) {
|
||||
ok(false, "Error in app frame: " + evt.detail);
|
||||
});
|
||||
|
||||
ifr.setAttribute('src', gApp.manifest.launch_path);
|
||||
var domParent = document.getElementById('content');
|
||||
if (!domParent) {
|
||||
document.createElement('div');
|
||||
document.body.insertBefore(domParent, document.body.firstChild);
|
||||
}
|
||||
domParent.appendChild(ifr);
|
||||
}
|
||||
}
|
||||
|
||||
var tests = [
|
||||
// Permissions
|
||||
function() {
|
||||
info("pushing permissions");
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "browser", "allow": 1, "context": document },
|
||||
{ "type": "embed-apps", "allow": 1, "context": document },
|
||||
{ "type": "webapps-manage", "allow": 1, "context": document }
|
||||
],
|
||||
runTests);
|
||||
},
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
info("pushing preferences: " + gOptions.extraPrefs.set);
|
||||
SpecialPowers.pushPrefEnv({
|
||||
"set": gOptions.extraPrefs.set
|
||||
}, runTests);
|
||||
},
|
||||
|
||||
function() {
|
||||
info("enabling use of mozbrowser");
|
||||
//SpecialPowers.setAllAppsLaunchable(true);
|
||||
SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
|
||||
runTests();
|
||||
},
|
||||
|
||||
// No confirmation needed when an app is installed
|
||||
function() {
|
||||
SpecialPowers.autoConfirmAppInstall(function() {
|
||||
SpecialPowers.autoConfirmAppUninstall(runTests);
|
||||
});
|
||||
},
|
||||
|
||||
// Installing the app
|
||||
installApp,
|
||||
|
||||
// Run tests in app
|
||||
testApp,
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp,
|
||||
];
|
||||
|
||||
function runTests() {
|
||||
if (!tests.length) {
|
||||
ok(true, 'DONE!');
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
test();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function runAppTest(options) {
|
||||
gOptions = options;
|
||||
var href = document.location.href;
|
||||
gManifestURL = href.substring(0, href.lastIndexOf('/') + 1) +
|
||||
options.appManifest;
|
||||
runTests();
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/**
|
||||
* This is the chrome helper for shim_app_as_test.js. Its load is triggered by
|
||||
* shim_app_as_test.js by a call to SpecialPowers.loadChromeScript and runs
|
||||
* in the parent process in a sandbox created with the system principal. (Which
|
||||
* seems like it can never get collected because it's reachable via the
|
||||
* apparently singleton SpecialPowersObserverAPI instance and there's no logic
|
||||
* to support reaping. Wuh-oh.)
|
||||
*
|
||||
* It exists to help install fake privileged/certified applications. It needs
|
||||
* to exist because:
|
||||
* - We need to poke at DOMApplicationRegistry directly.
|
||||
* - By using SpecialPowers.loadChromeScript we are able to ensure this file
|
||||
* is run in the parent process. This is important because
|
||||
* DOMApplicationRegistry only lives in the parent process!
|
||||
* - By running entirely in a chrome privileged compartment, we avoid crazy
|
||||
* wrapper problems that we would otherwise face with our shenanigans of
|
||||
* directly meddling with DOMApplicationRegistry. (And hopefully save
|
||||
* anyone changing DOMApplicationRegistry from frustration/hating us if
|
||||
* things were just barely working.)
|
||||
* - Bug 1097479 means that embed-webapps doesn't work when the content process
|
||||
* that is telling us to do things is itself OOP. So it falls upon us to
|
||||
* handle the running of the app by creating a sibling mozbrowser/mozapp
|
||||
* iframe to the one running the mochitests.
|
||||
*
|
||||
* Note that in this file we try to do *only* those things that can't otherwise
|
||||
* be cleanly done using SpecialPowers.
|
||||
*
|
||||
* Want to better understand our execution context? Check out
|
||||
* SpecialPowersObserverAPI.js and search on SPLoadChromeScript.
|
||||
*/
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const CC = Components.Constructor;
|
||||
|
||||
Cu.import('resource://gre/modules/Webapps.jsm'); // for DOMApplicationRegistry
|
||||
Cu.import('resource://gre/modules/AppsUtils.jsm'); // for AppUtils
|
||||
Cu.import('resource://gre/modules/Services.jsm'); // for AppUtils
|
||||
|
||||
// Yes, you would think there was something like this already exposed easily
|
||||
// in a JSM somewhere. No.
|
||||
function fetchManifest(manifestURL) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
xhr.open("GET", manifestURL, true);
|
||||
xhr.responseType = "json";
|
||||
|
||||
xhr.addEventListener("load", function() {
|
||||
if (xhr.status == 200) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener("error", function() {
|
||||
reject();
|
||||
});
|
||||
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an app using confirmInstall using pre-chewed data. This avoids the
|
||||
* check in the normal installApp flow that gets all judgemental about the
|
||||
* installation of privileged and certified apps.
|
||||
*/
|
||||
function installApp(req) {
|
||||
fetchManifest(req.manifestURL).then(function(manifestObj) {
|
||||
var data = {
|
||||
// cloneAppObj normalizes the representation for us
|
||||
app: AppsUtils.cloneAppObject({
|
||||
installOrigin: req.origin,
|
||||
origin: req.origin,
|
||||
manifestURL: req.manifestURL,
|
||||
appStatus: AppsUtils.getAppManifestStatus(manifestObj),
|
||||
receipts: [],
|
||||
categories: []
|
||||
}),
|
||||
|
||||
from: req.origin, // unused?
|
||||
oid: 0, // unused?
|
||||
requestID: 0, // unused-ish
|
||||
appId: 0, // unused
|
||||
isBrowser: false,
|
||||
isPackage: false, // used
|
||||
// magic to auto-ack... don't think we care about this...
|
||||
forceSuccessAck: false
|
||||
// stuff that probably doesn't matter: 'mm', 'apkInstall',
|
||||
};
|
||||
// cloneAppObject does not propagate the manifest
|
||||
data.app.manifest = manifestObj;
|
||||
|
||||
return DOMApplicationRegistry.confirmInstall(data).then(
|
||||
function() {
|
||||
var appId =
|
||||
DOMApplicationRegistry.getAppLocalIdByManifestURL(req.manifestURL);
|
||||
// act like this is a privileged app having all of its permissions
|
||||
// authorized at first run.
|
||||
DOMApplicationRegistry.updatePermissionsForApp(
|
||||
appId,
|
||||
/* preinstalled */ true,
|
||||
/* system update? */ true);
|
||||
|
||||
sendAsyncMessage(
|
||||
'installed',
|
||||
{
|
||||
appId: appId,
|
||||
manifestURL: req.manifestURL,
|
||||
manifest: manifestObj
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
sendAsyncMessage('installed', false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function uninstallApp(appInfo) {
|
||||
DOMApplicationRegistry.uninstall(appInfo.manifestURL).then(
|
||||
function() {
|
||||
sendAsyncMessage('uninstalled', true);
|
||||
},
|
||||
function() {
|
||||
sendAsyncMessage('uninstalled', false);
|
||||
});
|
||||
}
|
||||
|
||||
var activeIframe = null;
|
||||
|
||||
/**
|
||||
* Run our app in a sibling mozbrowser/mozapp iframe to the mochitest iframe.
|
||||
* This is needed because we can't nest mozbrowser/mozapp iframes inside our
|
||||
* already-OOP iframe until bug 1097479 is resolved.
|
||||
*/
|
||||
function runApp(appInfo) {
|
||||
let shellDomWindow = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
let sysAppFrame = shellDomWindow.document.body.querySelector('#systemapp');
|
||||
let sysAppDoc = sysAppFrame.contentDocument;
|
||||
|
||||
let siblingFrame = sysAppDoc.body.querySelector('#test-container');
|
||||
|
||||
let ifr = activeIframe = sysAppDoc.createElement('iframe');
|
||||
ifr.setAttribute('mozbrowser', 'true');
|
||||
ifr.setAttribute('remote', 'true');
|
||||
ifr.setAttribute('mozapp', appInfo.manifestURL);
|
||||
|
||||
ifr.addEventListener('mozbrowsershowmodalprompt', function(evt) {
|
||||
var message = evt.detail.message;
|
||||
// only send the message as long as we haven't been told to clean up.
|
||||
if (activeIframe) {
|
||||
sendAsyncMessage('appMessage', message);
|
||||
}
|
||||
}, false);
|
||||
ifr.addEventListener('mozbrowsererror', function(evt) {
|
||||
if (activeIframe) {
|
||||
sendAsyncMessage('appError', { message: '' + evt.detail });
|
||||
}
|
||||
});
|
||||
|
||||
ifr.setAttribute('src', appInfo.manifest.launch_path);
|
||||
siblingFrame.parentElement.appendChild(ifr);
|
||||
}
|
||||
|
||||
function closeApp() {
|
||||
if (activeIframe) {
|
||||
activeIframe.parentElement.removeChild(activeIframe);
|
||||
activeIframe = null;
|
||||
}
|
||||
}
|
||||
|
||||
addMessageListener('install', installApp);
|
||||
addMessageListener('uninstall', uninstallApp);
|
||||
addMessageListener('run', runApp);
|
||||
addMessageListener('close', closeApp);
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=825318
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 825318 mozDownloadManager.adoptDownload</title>
|
||||
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript;version=1.7" src="shim_app_as_test.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=825318">Mozilla Bug 825318</a>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript;version=1.7">
|
||||
|
||||
runAppTest({
|
||||
appFile: 'testapp_downloads_adopt_download.html',
|
||||
appManifest: 'testapp_downloads_adopt_download.manifest',
|
||||
appType: 'certified',
|
||||
extraPrefs: {
|
||||
set: [["dom.mozDownloads.enabled", true]]
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -72,6 +72,10 @@ function downloadChange(evt) {
|
|||
is(download.currentBytes, 1024, "Download current size is 1024 bytes");
|
||||
SimpleTest.finish();
|
||||
} else if (download.state === "downloading") {
|
||||
// Note that this case may or may not trigger, depending on whether the
|
||||
// download is initially reported with 0 bytes (we should happen) or with
|
||||
// 1024 bytes (we should not happen). If we do happen, an additional 8
|
||||
// TEST-PASS events should be logged.
|
||||
ok(download.currentBytes > lastKnownCurrentBytes,
|
||||
"Download current size is larger than last download change event");
|
||||
lastKnownCurrentBytes = download.currentBytes;
|
||||
|
@ -84,7 +88,8 @@ function downloadStart(evt) {
|
|||
var download = evt.download;
|
||||
checkConsistentDownloadAttributes(download);
|
||||
|
||||
is(download.currentBytes, 0, "Download current size is zero");
|
||||
// We used to check that the currentBytes was 0. This was incorrect. It
|
||||
// is very common to first hear about the download already at 1024 bytes.
|
||||
is(download.state, "downloading", "Download state is downloading");
|
||||
|
||||
download.onstatechange = downloadChange;
|
||||
|
|
|
@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=938023
|
|||
<title>Test for Bug 938023 Downloads API</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="clear_all_done_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -46,7 +47,7 @@ function error() {
|
|||
|
||||
function getDownloads(downloads) {
|
||||
ok(downloads.length == 1, "One downloads after getDownloads");
|
||||
navigator.mozDownloadManager.clearAllDone().then(clearAllDone, error);
|
||||
clearAllDoneHelper(true).then(clearAllDone, error);
|
||||
}
|
||||
|
||||
function clearAllDone(downloads) {
|
||||
|
@ -76,7 +77,7 @@ var steps = [
|
|||
SpecialPowers.pushPermissions([
|
||||
{type: "downloads", allow: true, context: document}
|
||||
], function() {
|
||||
navigator.mozDownloadManager.clearAllDone().then(next, error);
|
||||
clearAllDoneHelper(true).then(next, error);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=938023
|
|||
<title>Test for Bug 938023 Downloads API</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="clear_all_done_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -83,7 +84,7 @@ var steps = [
|
|||
SpecialPowers.pushPermissions([
|
||||
{type: "downloads", allow: true, context: document}
|
||||
], function() {
|
||||
navigator.mozDownloadManager.clearAllDone().then(next, error);
|
||||
clearAllDoneHelper(true).then(next, error);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=938023
|
|||
<title>Test for Bug 938023 Downloads API</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="clear_all_done_helper.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -54,8 +55,7 @@ function checkDownloadList(downloads) {
|
|||
|
||||
function checkResumeSucceeded(download) {
|
||||
ok(download.state == "succeeded", "Download resumed successfully.");
|
||||
navigator.mozDownloadManager.clearAllDone()
|
||||
.then(checkDownloadList, error);
|
||||
clearAllDoneHelper(true).then(checkDownloadList, error);
|
||||
}
|
||||
|
||||
function downloadChange(evt) {
|
||||
|
@ -85,7 +85,7 @@ var steps = [
|
|||
SpecialPowers.pushPermissions([
|
||||
{type: "downloads", allow: true, context: document}
|
||||
], function() {
|
||||
navigator.mozDownloadManager.clearAllDone().then(next, error);
|
||||
clearAllDoneHelper(true).then(next, error);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="application/javascript" src="common_app.js"></script>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<div id="blah">initial text</div>
|
||||
<pre id="test">
|
||||
<!-- because of certified CSP, this code must NOT be inline -->
|
||||
<script class="testbody" type="text/javascript;version=1.7" src="testapp_downloads_adopt_download.js"></script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* Test the adoptDownload API. Specifically, we expect that when we call
|
||||
* adoptDownload with a valid payload that:
|
||||
* - The method will be resolved with a valid, fully populated DOMDownload
|
||||
* instance, including an id.
|
||||
* - An ondownloadstart notification will be generated and the DOMDownload
|
||||
* instance it receives will be logically equivalent.
|
||||
*
|
||||
* We also explicitly verify that invalid adoptDownload payloads result in a
|
||||
* rejection and that no download is added.
|
||||
*
|
||||
* This test explicitly does not test that the download is correctly persisted
|
||||
* to the database. This is done because Downloads.jsm does not provide a means
|
||||
* of safely restarting itself, so Firefox would need to be restarted. Because
|
||||
* the adoptDownload code is using the Downloads API in a straightforward
|
||||
* manner, it's not considered likely this would regress, and certainly not
|
||||
* considered worth the automated testing overhead of a restart.
|
||||
*/
|
||||
|
||||
function checkInvalidResult(dict, expectedErr, explanation) {
|
||||
navigator.mozDownloadManager.ondownloadstart = function() {
|
||||
ok(false, "No download should have been added!");
|
||||
};
|
||||
navigator.mozDownloadManager.adoptDownload(dict).then(
|
||||
function() {
|
||||
ok(false, "Invalid adoptDownload did not reject!");
|
||||
runTests();
|
||||
},
|
||||
function(rejectedWith) {
|
||||
is(rejectedWith, expectedErr, explanation + " rejection value");
|
||||
runTests();
|
||||
});
|
||||
}
|
||||
|
||||
// Pick a date that Date.now() could not possibly return by picking a date in
|
||||
// the past. (We want to make sure the date we provide works.)
|
||||
var arbitraryDate = new Date(Date.now() - 60000);
|
||||
|
||||
var blobContents = new Uint8Array(256);
|
||||
var memBlob = new Blob([blobContents], { type: 'application/octet-stream' });
|
||||
var blobStorageName;
|
||||
var blobStoragePath = 'blobby.blob';
|
||||
|
||||
function checkAdoptedDownload(download, validPayload) {
|
||||
is(download.totalBytes, memBlob.size, 'size');
|
||||
is(download.url, validPayload.url, 'url');
|
||||
// The filesystem path is not practical to check since we can't hard-code it
|
||||
// and the only way to check is to effectively duplicate the logic in
|
||||
// DownloadsAPI.js. The good news, however, is that the value is
|
||||
// round-tripped from storageName/storagePath to path and back again, and we
|
||||
// also verify the file exists on disk, so we can be reasonably confident this
|
||||
// is correct. We output it to aid in debugging if things should break,
|
||||
// of course.
|
||||
info('path (not checked): ' + download.path);
|
||||
is(download.storageName, validPayload.storageName, 'storageName');
|
||||
is(download.storagePath, validPayload.storagePath, 'storagePath');
|
||||
is(download.state, 'succeeded', 'state');
|
||||
is(download.contentType, validPayload.contentType, 'contentType');
|
||||
is(download.startTime.valueOf(), arbitraryDate.valueOf(), 'startTime');
|
||||
is(download.sourceAppManifestURL,
|
||||
'http://mochi.test:8888/' +
|
||||
'tests/dom/downloads/tests/testapp_downloads_adopt_download.manifest',
|
||||
'app manifest');
|
||||
};
|
||||
|
||||
var tests = [
|
||||
function saveBlobToDeviceStorage() {
|
||||
// Only sdcard can handle arbitrary MIME types and is guaranteed to be a
|
||||
// thing.
|
||||
var storage = navigator.getDeviceStorage('sdcard');
|
||||
// We used the non-array helper, so the name we get may be different than
|
||||
// what we asked for.
|
||||
blobStorageName = storage.storageName;
|
||||
ok(!!storage, 'have storage');
|
||||
var req = storage.addNamed(memBlob, blobStoragePath);
|
||||
req.onerror = function() {
|
||||
ok(false, 'problem saving blob to storage: ' + req.error.name);
|
||||
};
|
||||
req.onsuccess = function(evt) {
|
||||
ok(true, 'saved blob: ' + evt.target.result);
|
||||
runTests();
|
||||
};
|
||||
},
|
||||
function addValid() {
|
||||
var validPayload = {
|
||||
// All currently expected consumers are unable to provide a valid URL, and
|
||||
// as a result need to provide an empty string.
|
||||
url: "",
|
||||
storageName: blobStorageName,
|
||||
storagePath: blobStoragePath,
|
||||
contentType: memBlob.type,
|
||||
startTime: arbitraryDate
|
||||
};
|
||||
// Wrap the notification in a check so we can force our logic to be
|
||||
// consistently ordered in the test even if it's not in reality.
|
||||
var notifiedPromise = new Promise(function(resolve, reject) {
|
||||
navigator.mozDownloadManager.ondownloadstart = function(evt) {
|
||||
resolve(evt.download);
|
||||
};
|
||||
});
|
||||
|
||||
// Start the download
|
||||
navigator.mozDownloadManager.adoptDownload(validPayload).then(
|
||||
function(apiDownload) {
|
||||
checkAdoptedDownload(apiDownload, validPayload);
|
||||
ok(!!apiDownload.id, "Need a download id!");
|
||||
notifiedPromise.then(function(notifiedDownload) {
|
||||
checkAdoptedDownload(notifiedDownload, validPayload);
|
||||
is(apiDownload.id, notifiedDownload.id,
|
||||
"Notification should be for the download we adopted");
|
||||
runTests();
|
||||
});
|
||||
},
|
||||
function() {
|
||||
ok(false, "adoptDownload should not have rejected");
|
||||
runTests();
|
||||
});
|
||||
},
|
||||
|
||||
function dictionaryNotProvided() {
|
||||
checkInvalidResult(undefined, "InvalidDownload");
|
||||
},
|
||||
// Missing fields immediately result in rejection with InvalidDownload
|
||||
function missingStorageName() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
// no storageName
|
||||
storagePath: "relpath/filename.txt",
|
||||
contentType: "text/plain",
|
||||
startTime: arbitraryDate
|
||||
}, "InvalidDownload", "missing storage name");
|
||||
},
|
||||
function nullStorageName() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
storageName: null,
|
||||
storagePath: "relpath/filename.txt",
|
||||
contentType: "text/plain",
|
||||
startTime: arbitraryDate
|
||||
}, "InvalidDownload", "null storage name");
|
||||
},
|
||||
function missingStoragePath() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
storageName: blobStorageName,
|
||||
// no storagePath
|
||||
contentType: "text/plain",
|
||||
startTime: arbitraryDate
|
||||
}, "InvalidDownload", "missing storage path");
|
||||
},
|
||||
function nullStoragePath() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
storageName: blobStorageName,
|
||||
storagePath: null,
|
||||
contentType: "text/plain",
|
||||
startTime: arbitraryDate
|
||||
}, "InvalidDownload", "null storage path");
|
||||
},
|
||||
function missingContentType() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
storageName: "sdcard",
|
||||
storagePath: "relpath/filename.txt",
|
||||
// no contentType
|
||||
startTime: arbitraryDate
|
||||
}, "InvalidDownload", "missing content type");
|
||||
},
|
||||
function nullContentType() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
storageName: "sdcard",
|
||||
storagePath: "relpath/filename.txt",
|
||||
contentType: null,
|
||||
startTime: arbitraryDate
|
||||
}, "InvalidDownload", "null content type");
|
||||
},
|
||||
// Incorrect storage names are likewise immediately invalidated
|
||||
function invalidStorageName() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
storageName: "ALMOST CERTAINLY DOES NOT EXIST",
|
||||
storagePath: "relpath/filename.txt",
|
||||
contentType: "text/plain",
|
||||
startTime: arbitraryDate
|
||||
}, "InvalidDownload", "invalid storage name");
|
||||
},
|
||||
// The existence of the file is validated in the parent process
|
||||
function legitStorageInvalidPath() {
|
||||
checkInvalidResult({
|
||||
url: "",
|
||||
storageName: blobStorageName,
|
||||
storagePath: "ALMOST CERTAINLY DOES NOT EXIST",
|
||||
contentType: "text/plain",
|
||||
startTime: arbitraryDate
|
||||
}, "AdoptNoSuchFile", "invalid path");
|
||||
},
|
||||
function allDone() {
|
||||
// Just in case, make sure no other mochitest could mess with us after we've
|
||||
// finished.
|
||||
navigator.mozDownloadManager.ondownloadstart = null;
|
||||
runTests();
|
||||
}
|
||||
];
|
||||
|
||||
function runTests() {
|
||||
if (!tests.length) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
if (test.name) {
|
||||
info('starting test: ' + test.name);
|
||||
}
|
||||
test();
|
||||
}
|
||||
runTests();
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "Downloads certified test fake app",
|
||||
"description": "Test",
|
||||
"launch_path": "http://mochi.test:8888/tests/dom/downloads/tests/testapp_downloads_adopt_download.html",
|
||||
"type": "certified",
|
||||
"permissions": {
|
||||
"device-storage:sdcard":{ "access": "readcreate" },
|
||||
"downloads": {}
|
||||
}
|
||||
}
|
|
@ -36,10 +36,32 @@ interface DOMDownloadManager : EventTarget {
|
|||
[UnsafeInPrerendering]
|
||||
Promise<DOMDownload> remove(DOMDownload download);
|
||||
|
||||
// Removes all the completed downloads from the set. Returns an
|
||||
// array of the completed downloads that were removed.
|
||||
// Removes all completed downloads. This kicks off an asynchronous process
|
||||
// that will eventually complete, but will not have completed by the time this
|
||||
// method returns. If you care about the side-effects of this method, know
|
||||
// that each existing download will have its onstatechange method invoked and
|
||||
// will have a new state of "finalized". (After the download is finalized, no
|
||||
// further events will be generated on it.)
|
||||
[UnsafeInPrerendering]
|
||||
Promise<sequence<DOMDownload>> clearAllDone();
|
||||
void clearAllDone();
|
||||
|
||||
// Add completed downloads from applications that must perform the download
|
||||
// process themselves. For example, email. The method is resolved with a
|
||||
// fully populated DOMDownload instance on success, or rejected in the
|
||||
// event all required options were not provided.
|
||||
//
|
||||
// The adopted download will also be reported via the ondownloadstart event
|
||||
// handler.
|
||||
//
|
||||
// Applications must currently be certified to use this, but it could be
|
||||
// widened at a later time.
|
||||
//
|
||||
// Note that "download" is not actually optional, but WebIDL requires that it
|
||||
// be marked as such because it is not followed by a required argument. The
|
||||
// promise will be rejected if the dictionary is omitted or the specified
|
||||
// file does not exist on disk.
|
||||
[AvailableIn=CertifiedApps]
|
||||
Promise<DOMDownload> adoptDownload(optional AdoptDownloadDict download);
|
||||
|
||||
// Fires when a new download starts.
|
||||
attribute EventHandler ondownloadstart;
|
||||
|
@ -59,7 +81,9 @@ interface DOMDownload : EventTarget {
|
|||
readonly attribute DOMString url;
|
||||
|
||||
// The full path in local storage where the file will end up once the download
|
||||
// is complete.
|
||||
// is complete. This is equivalent to the concatenation of the 'storagePath'
|
||||
// to the 'mountPoint' of the nsIVolume associated with the 'storageName'
|
||||
// (with delimiter).
|
||||
readonly attribute DOMString path;
|
||||
|
||||
// The DeviceStorage volume name on which the file is being downloaded.
|
||||
|
@ -69,7 +93,10 @@ interface DOMDownload : EventTarget {
|
|||
// downloaded.
|
||||
readonly attribute DOMString storagePath;
|
||||
|
||||
// The state of the download.
|
||||
// The state of the download. One of: downloading, stopped, succeeded, or
|
||||
// finalized. A finalized download is a download that has been removed /
|
||||
// cleared and is no longer tracked by the download manager and will not
|
||||
// receive any further onstatechange updates.
|
||||
readonly attribute DownloadState state;
|
||||
|
||||
// The mime type for this resource.
|
||||
|
@ -82,6 +109,10 @@ interface DOMDownload : EventTarget {
|
|||
// download (eg. in different windows) will have the same id.
|
||||
readonly attribute DOMString id;
|
||||
|
||||
// The manifestURL of the application that added this download. Only used for
|
||||
// downloads added via the adoptDownload API call.
|
||||
readonly attribute DOMString? sourceAppManifestURL;
|
||||
|
||||
// A DOM error object, that will be not null when a download is stopped
|
||||
// because something failed.
|
||||
readonly attribute DOMError? error;
|
||||
|
@ -100,3 +131,41 @@ interface DOMDownload : EventTarget {
|
|||
// - when the state and/or error attributes change.
|
||||
attribute EventHandler onstatechange;
|
||||
};
|
||||
|
||||
// Used to initialize the DOMDownload object for adopted downloads.
|
||||
// fields directly maps to the DOMDownload fields.
|
||||
dictionary AdoptDownloadDict {
|
||||
// The URL of this resource if there is one available. An empty string if
|
||||
// the download is not accessible via URL. An empty string is chosen over
|
||||
// null so that existinc code does not need to null-check but the value is
|
||||
// still falsey. (Note: If you do have a usable URL, you should probably not
|
||||
// be using the adoptDownload API and instead be initiating downloads the
|
||||
// normal way.)
|
||||
DOMString url;
|
||||
|
||||
// The storageName of the DeviceStorage instance the file was saved to.
|
||||
// Required but marked as optional so the bindings don't auto-coerce the value
|
||||
// null to "null".
|
||||
DOMString? storageName;
|
||||
// The path of the file within the DeviceStorage instance named by
|
||||
// 'storageName'. This is used to automatically compute the 'path' of the
|
||||
// download. Note that when DeviceStorage gives you a path to a file, the
|
||||
// first path segment is the name of the specific device storage and you do
|
||||
// *not* want to include this. For example, if DeviceStorage tells you the
|
||||
// file has a path of '/sdcard1/actual/path/file.ext', then the storageName
|
||||
// should be 'sdcard1' and the storagePath should be 'actual/path/file.ext'.
|
||||
//
|
||||
// The existence of the file will be validated will be validated with stat()
|
||||
// and the size the file-system tells us will be what we use.
|
||||
//
|
||||
// Required but marked as optional so the bindings don't auto-coerce the value
|
||||
// null to "null".
|
||||
DOMString? storagePath;
|
||||
|
||||
// The mime type for this resource. Required, but marked as optional because
|
||||
// WebIDL otherwise auto-coerces the value null to "null".
|
||||
DOMString? contentType;
|
||||
|
||||
// The time the download was started. If omitted, the current time is used.
|
||||
Date? startTime;
|
||||
};
|
||||
|
|
|
@ -309,6 +309,16 @@ public:
|
|||
return EventRegions();
|
||||
}
|
||||
|
||||
bool HasTransformAnimation() const
|
||||
{
|
||||
MOZ_ASSERT(IsValid());
|
||||
|
||||
if (AtBottomLayer()) {
|
||||
return mLayer->HasTransformAnimation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RefLayer* AsRefLayer() const
|
||||
{
|
||||
MOZ_ASSERT(IsValid());
|
||||
|
|
|
@ -762,6 +762,17 @@ Layer::GetLocalTransform()
|
|||
return transform;
|
||||
}
|
||||
|
||||
bool
|
||||
Layer::HasTransformAnimation() const
|
||||
{
|
||||
for (uint32_t i = 0; i < mAnimations.Length(); i++) {
|
||||
if (mAnimations[i].property() == eCSSProperty_transform) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
Layer::ApplyPendingUpdatesForThisTransaction()
|
||||
{
|
||||
|
|
|
@ -1284,6 +1284,8 @@ public:
|
|||
uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
|
||||
void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; }
|
||||
|
||||
bool HasTransformAnimation() const;
|
||||
|
||||
/**
|
||||
* Returns the local transform for this layer: either mTransform or,
|
||||
* for shadow layers, GetShadowTransform()
|
||||
|
|
|
@ -91,11 +91,14 @@ GetTransformToAncestorsParentLayer(Layer* aStart, const LayerMetricsWrapper& aAn
|
|||
|
||||
void
|
||||
ClientTiledPaintedLayer::GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor,
|
||||
LayerMetricsWrapper* aOutDisplayPortAncestor)
|
||||
LayerMetricsWrapper* aOutDisplayPortAncestor,
|
||||
bool* aOutHasTransformAnimation)
|
||||
{
|
||||
LayerMetricsWrapper scrollAncestor;
|
||||
LayerMetricsWrapper displayPortAncestor;
|
||||
bool hasTransformAnimation = false;
|
||||
for (LayerMetricsWrapper ancestor(this, LayerMetricsWrapper::StartAt::BOTTOM); ancestor; ancestor = ancestor.GetParent()) {
|
||||
hasTransformAnimation |= ancestor.HasTransformAnimation();
|
||||
const FrameMetrics& metrics = ancestor.Metrics();
|
||||
if (!scrollAncestor && metrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID) {
|
||||
scrollAncestor = ancestor;
|
||||
|
@ -113,6 +116,9 @@ ClientTiledPaintedLayer::GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncest
|
|||
if (aOutDisplayPortAncestor) {
|
||||
*aOutDisplayPortAncestor = displayPortAncestor;
|
||||
}
|
||||
if (aOutHasTransformAnimation) {
|
||||
*aOutHasTransformAnimation = hasTransformAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -134,7 +140,8 @@ ClientTiledPaintedLayer::BeginPaint()
|
|||
// with a displayport.
|
||||
LayerMetricsWrapper scrollAncestor;
|
||||
LayerMetricsWrapper displayPortAncestor;
|
||||
GetAncestorLayers(&scrollAncestor, &displayPortAncestor);
|
||||
bool hasTransformAnimation;
|
||||
GetAncestorLayers(&scrollAncestor, &displayPortAncestor, &hasTransformAnimation);
|
||||
|
||||
if (!displayPortAncestor || !scrollAncestor) {
|
||||
// No displayport or scroll ancestor, so we can't do progressive rendering.
|
||||
|
@ -146,8 +153,8 @@ ClientTiledPaintedLayer::BeginPaint()
|
|||
return;
|
||||
}
|
||||
|
||||
TILING_LOG("TILING %p: Found scrollAncestor %p and displayPortAncestor %p\n", this,
|
||||
scrollAncestor.GetLayer(), displayPortAncestor.GetLayer());
|
||||
TILING_LOG("TILING %p: Found scrollAncestor %p, displayPortAncestor %p, transform %d\n", this,
|
||||
scrollAncestor.GetLayer(), displayPortAncestor.GetLayer(), hasTransformAnimation);
|
||||
|
||||
const FrameMetrics& scrollMetrics = scrollAncestor.Metrics();
|
||||
const FrameMetrics& displayportMetrics = displayPortAncestor.Metrics();
|
||||
|
@ -159,12 +166,17 @@ ClientTiledPaintedLayer::BeginPaint()
|
|||
transformDisplayPortToLayer.Invert();
|
||||
|
||||
// Compute the critical display port that applies to this layer in the
|
||||
// LayoutDevice space of this layer.
|
||||
ParentLayerRect criticalDisplayPort =
|
||||
(displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom())
|
||||
+ displayportMetrics.mCompositionBounds.TopLeft();
|
||||
mPaintData.mCriticalDisplayPort = RoundedOut(
|
||||
ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort));
|
||||
// LayoutDevice space of this layer, but only if there is no OMT animation
|
||||
// on this layer. If there is an OMT animation then we need to draw the whole
|
||||
// visible region of this layer as determined by layout, because we don't know
|
||||
// what parts of it might move into view in the compositor.
|
||||
if (!hasTransformAnimation) {
|
||||
ParentLayerRect criticalDisplayPort =
|
||||
(displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom())
|
||||
+ displayportMetrics.mCompositionBounds.TopLeft();
|
||||
mPaintData.mCriticalDisplayPort = RoundedOut(
|
||||
ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort));
|
||||
}
|
||||
TILING_LOG("TILING %p: Critical displayport %s\n", this, Stringify(mPaintData.mCriticalDisplayPort).c_str());
|
||||
|
||||
// Store the resolution from the displayport ancestor layer. Because this is Gecko-side,
|
||||
|
@ -217,51 +229,48 @@ ClientTiledPaintedLayer::IsScrollingOnCompositor(const FrameMetrics& aParentMetr
|
|||
}
|
||||
|
||||
bool
|
||||
ClientTiledPaintedLayer::UseFastPath()
|
||||
{
|
||||
// The fast path doesn't allow rendering at low resolution. It will draw the low-res
|
||||
// area at full resolution and cause OOM.
|
||||
if (gfxPrefs::UseLowPrecisionBuffer()) {
|
||||
ClientTiledPaintedLayer::UseProgressiveDraw() {
|
||||
if (!gfxPlatform::GetPlatform()->UseProgressivePaint()) {
|
||||
// pref is disabled, so never do progressive
|
||||
return false;
|
||||
}
|
||||
|
||||
LayerMetricsWrapper scrollAncestor;
|
||||
GetAncestorLayers(&scrollAncestor, nullptr);
|
||||
if (!scrollAncestor) {
|
||||
return true;
|
||||
if (ClientManager()->HasShadowTarget()) {
|
||||
// This condition is true when we are in a reftest scenario. We don't want
|
||||
// to draw progressively here because it can cause intermittent reftest
|
||||
// failures because the harness won't wait for all the tiles to be drawn.
|
||||
return false;
|
||||
}
|
||||
const FrameMetrics& parentMetrics = scrollAncestor.Metrics();
|
||||
|
||||
bool multipleTransactionsNeeded = gfxPlatform::GetPlatform()->UseProgressivePaint()
|
||||
|| !parentMetrics.GetCriticalDisplayPort().IsEmpty();
|
||||
bool isFixed = GetIsFixedPosition() || GetParent()->GetIsFixedPosition();
|
||||
bool isScrollable = parentMetrics.IsScrollable();
|
||||
if (mPaintData.mCriticalDisplayPort.IsEmpty()) {
|
||||
// This catches three scenarios:
|
||||
// 1) This layer doesn't have a scrolling ancestor
|
||||
// 2) This layer is subject to OMTA transforms
|
||||
// 3) Low-precision painting is disabled
|
||||
// In all of these cases, we don't want to draw this layer progressively.
|
||||
return false;
|
||||
}
|
||||
|
||||
return !multipleTransactionsNeeded || isFixed || !isScrollable;
|
||||
}
|
||||
|
||||
bool
|
||||
ClientTiledPaintedLayer::UseProgressiveDraw() {
|
||||
// Don't draw progressively in a reftest scenario (that's what the HasShadowTarget() check is for).
|
||||
if (!gfxPlatform::GetPlatform()->UseProgressivePaint() || ClientManager()->HasShadowTarget()) {
|
||||
if (GetIsFixedPosition() || GetParent()->GetIsFixedPosition()) {
|
||||
// This layer is fixed-position and so even if it does have a scrolling
|
||||
// ancestor it will likely be entirely on-screen all the time, so we
|
||||
// should draw it all at once
|
||||
return false;
|
||||
}
|
||||
|
||||
// XXX We probably want to disable progressive drawing for non active APZ layers in the future
|
||||
// but we should wait for a proper test case before making this change.
|
||||
|
||||
#if 0 //!defined(MOZ_WIDGET_ANDROID) || defined(MOZ_ANDROID_APZ)
|
||||
LayerMetricsWrapper scrollAncestor;
|
||||
GetAncestorLayers(&scrollAncestor, nullptr);
|
||||
if (!scrollAncestor) {
|
||||
return true;
|
||||
}
|
||||
GetAncestorLayers(&scrollAncestor, nullptr, nullptr);
|
||||
MOZ_ASSERT(scrollAncestor); // because mPaintData.mCriticalDisplayPort is non-empty
|
||||
const FrameMetrics& parentMetrics = scrollAncestor.Metrics();
|
||||
|
||||
return !IsScrollingOnCompositor(parentMetrics);
|
||||
#else
|
||||
return true;
|
||||
if (!IsScrollingOnCompositor(parentMetrics)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -441,16 +450,6 @@ ClientTiledPaintedLayer::RenderLayer()
|
|||
ToClientLayer(GetMaskLayer())->RenderLayer();
|
||||
}
|
||||
|
||||
// In some cases we can take a fast path and just be done with it.
|
||||
if (UseFastPath()) {
|
||||
TILING_LOG("TILING %p: Taking fast-path\n", this);
|
||||
mValidRegion = neededRegion;
|
||||
mContentClient->mTiledBuffer.PaintThebes(mValidRegion, invalidRegion, callback, data);
|
||||
ClientManager()->Hold(this);
|
||||
mContentClient->UseTiledLayerBuffer(TiledContentClient::TILED_BUFFER);
|
||||
return;
|
||||
}
|
||||
|
||||
// For more complex cases we need to calculate a bunch of metrics before we
|
||||
// can do the paint.
|
||||
BeginPaint();
|
||||
|
|
|
@ -81,7 +81,8 @@ public:
|
|||
* which hold the return values; the values passed in may be null.
|
||||
*/
|
||||
void GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor,
|
||||
LayerMetricsWrapper* aOutDisplayPortAncestor);
|
||||
LayerMetricsWrapper* aOutDisplayPortAncestor,
|
||||
bool* aOutHasTransformAnimation);
|
||||
|
||||
private:
|
||||
ClientLayerManager* ClientManager()
|
||||
|
@ -95,12 +96,6 @@ private:
|
|||
*/
|
||||
void BeginPaint();
|
||||
|
||||
/**
|
||||
* Determine if we can use a fast path to just do a single high-precision,
|
||||
* non-progressive paint.
|
||||
*/
|
||||
bool UseFastPath();
|
||||
|
||||
/**
|
||||
* Check if the layer is being scrolled by APZ on the compositor.
|
||||
*/
|
||||
|
|
|
@ -1360,7 +1360,7 @@ ClientTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInval
|
|||
TILING_LOG("TILING %p: Progressive update stale region %s\n", mPaintedLayer, Stringify(staleRegion).c_str());
|
||||
|
||||
LayerMetricsWrapper scrollAncestor;
|
||||
mPaintedLayer->GetAncestorLayers(&scrollAncestor, nullptr);
|
||||
mPaintedLayer->GetAncestorLayers(&scrollAncestor, nullptr, nullptr);
|
||||
|
||||
// Find out the current view transform to determine which tiles to draw
|
||||
// first, and see if we should just abort this paint. Aborting is usually
|
||||
|
|
|
@ -3239,7 +3239,7 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram
|
|||
if (gfxPrefs::DumpClientLayers()) {
|
||||
std::stringstream ss;
|
||||
FrameLayerBuilder::DumpRetainedLayerTree(layerManager, ss, false);
|
||||
printf_stderr("%s", ss.str().c_str());
|
||||
print_stderr(ss);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче