Bug 853901 - Add platform-specific completion operations for downloads. r=paolo

This commit is contained in:
Neil Deakin 2013-09-03 13:23:37 -04:00
parent 60440e2aa6
commit 64d3cff62b
16 changed files with 340 additions and 2 deletions

View File

@ -240,6 +240,7 @@
@BINPATH@/components/intl.xpt
@BINPATH@/components/jar.xpt
@BINPATH@/components/jsdebugger.xpt
@BINPATH@/components/jsdownloads.xpt
@BINPATH@/components/jsdservice.xpt
@BINPATH@/components/jsinspector.xpt
@BINPATH@/components/layout_base.xpt

View File

@ -248,6 +248,7 @@
@BINPATH@/components/jsdservice.xpt
#endif
@BINPATH@/components/jsdebugger.xpt
@BINPATH@/components/jsdownloads.xpt
@BINPATH@/components/jsinspector.xpt
@BINPATH@/components/layout_base.xpt
#ifdef NS_PRINTING

View File

@ -188,6 +188,7 @@
@BINPATH@/components/jar.xpt
@BINPATH@/components/jsdservice.xpt
@BINPATH@/components/jsdebugger.xpt
@BINPATH@/components/jsdownloads.xpt
@BINPATH@/components/jsinspector.xpt
@BINPATH@/components/layout_base.xpt
#ifdef NS_PRINTING

View File

@ -17,6 +17,7 @@ LOCAL_INCLUDES = \
-I$(srcdir)/../feeds \
-I$(srcdir)/../find \
-I$(srcdir)/../intl \
-I$(srcdir)/../jsdownloads/src \
-I$(srcdir)/../protobuf \
-I$(srcdir)/../startup \
-I$(srcdir)/../statusfilter \
@ -32,6 +33,7 @@ SHARED_LIBRARY_LIBS = \
../startup/$(LIB_PREFIX)appstartup_s.$(LIB_SUFFIX) \
../statusfilter/$(LIB_PREFIX)mozbrwsr_s.$(LIB_SUFFIX) \
../downloads/$(LIB_PREFIX)download_s.$(LIB_SUFFIX) \
../jsdownloads/src/$(LIB_PREFIX)jsdownloads_s.$(LIB_SUFFIX) \
../protobuf/$(LIB_PREFIX)protobuf_s.$(LIB_SUFFIX) \
../intl/$(LIB_PREFIX)intl_s.$(LIB_SUFFIX) \
$(NULL)

View File

@ -30,6 +30,9 @@
#define NS_DOWNLOADMANAGER_CONTRACTID \
"@mozilla.org/download-manager;1"
#define NS_DOWNLOADPLATFORM_CONTRACTID \
"@mozilla.org/toolkit/download-platform;1"
#define NS_FORMHISTORY_CONTRACTID \
"@mozilla.org/satchel/form-history;1"
@ -117,6 +120,9 @@
#define NS_DOWNLOADMANAGER_CID \
{ 0xedb0490e, 0x1dd1, 0x11b2, { 0x83, 0xb8, 0xdb, 0xf8, 0xd8, 0x59, 0x06, 0xa6 } }
#define NS_DOWNLOADPLATFORM_CID \
{ 0x649a14c9, 0xfe5c, 0x48ec, { 0x9c, 0x85, 0x00, 0xca, 0xd9, 0xcc, 0xf3, 0x2e } }
// {895DB6C7-DBDF-40ea-9F64-B175033243DC}
#define NS_FORMFILLCONTROLLER_CID \
{ 0x895db6c7, 0xdbdf, 0x40ea, { 0x9f, 0x64, 0xb1, 0x75, 0x3, 0x32, 0x43, 0xdc } }

View File

@ -18,6 +18,7 @@
#include "nsAlertsService.h"
#include "nsDownloadManager.h"
#include "DownloadPlatform.h"
#include "nsDownloadProxy.h"
#include "nsCharsetMenu.h"
#include "rdf.h"
@ -48,6 +49,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsAlertsService)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDownloadManager,
nsDownloadManager::GetSingleton)
NS_GENERIC_FACTORY_CONSTRUCTOR(DownloadPlatform)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadProxy)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsTypeAheadFind)
@ -94,6 +96,7 @@ NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
#endif
NS_DEFINE_NAMED_CID(NS_DOWNLOADMANAGER_CID);
NS_DEFINE_NAMED_CID(NS_DOWNLOADPLATFORM_CID);
NS_DEFINE_NAMED_CID(NS_DOWNLOAD_CID);
NS_DEFINE_NAMED_CID(NS_FIND_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_TYPEAHEADFIND_CID);
@ -119,6 +122,7 @@ static const mozilla::Module::CIDEntry kToolkitCIDs[] = {
{ &kNS_PARENTALCONTROLSSERVICE_CID, false, NULL, nsParentalControlsServiceWinConstructor },
#endif
{ &kNS_DOWNLOADMANAGER_CID, false, NULL, nsDownloadManagerConstructor },
{ &kNS_DOWNLOADPLATFORM_CID, false, NULL, DownloadPlatformConstructor },
{ &kNS_DOWNLOAD_CID, false, NULL, nsDownloadProxyConstructor },
{ &kNS_FIND_SERVICE_CID, false, NULL, nsFindServiceConstructor },
{ &kNS_TYPEAHEADFIND_CID, false, NULL, nsTypeAheadFindConstructor },
@ -146,6 +150,7 @@ static const mozilla::Module::ContractIDEntry kToolkitContracts[] = {
{ NS_PARENTALCONTROLSSERVICE_CONTRACTID, &kNS_PARENTALCONTROLSSERVICE_CID },
#endif
{ NS_DOWNLOADMANAGER_CONTRACTID, &kNS_DOWNLOADMANAGER_CID },
{ NS_DOWNLOADPLATFORM_CONTRACTID, &kNS_DOWNLOADPLATFORM_CID },
{ NS_TRANSFER_CONTRACTID, &kNS_DOWNLOAD_CID },
{ NS_FIND_SERVICE_CONTRACTID, &kNS_FIND_SERVICE_CID },
{ NS_TYPEAHEADFIND_CONTRACTID, &kNS_TYPEAHEADFIND_CID },

View File

@ -4,6 +4,6 @@
# 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/.
DIRS += ['src']
DIRS += ['public', 'src']
TEST_DIRS += ['test']

View File

@ -0,0 +1,9 @@
# 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/.
MODULE = 'jsdownloads'
XPIDL_SOURCES += [
'mozIDownloadPlatform.idl',
]

View File

@ -0,0 +1,39 @@
/* 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/. */
#include "nsISupports.idl"
interface nsIURI;
interface nsIFile;
[scriptable, uuid(314A0972-E6E3-413A-8BD4-2C052BCB2C74)]
interface mozIDownloadPlatform : nsISupports
{
/**
* Perform platform specific operations when a download is done.
*
* Windows:
* Add the download to the recent documents list
* Set the file to be indexed for searching
* Mac:
* Bounce the downloads dock icon
* GTK:
* Add the download to the recent documents list
* Save the source uri in the downloaded file's metadata
* Android:
* Scan media
*
* @param aSource
* Source URI of the download
* @param aTarget
* Downloaded file
* @param aContentType
* The source's content type
* @param aIsPrivate
* True for private downloads
* @return none
*/
void downloadDone(in nsIURI aSource, in nsIFile aTarget,
in ACString aContentType, in boolean aIsPrivate);
};

View File

@ -412,6 +412,8 @@ Download.prototype = {
this.speed = 0;
this._notifyChange();
if (this.succeeded) {
yield DownloadIntegration.downloadDone(this);
this._deferSucceeded.resolve();
if (this.launchWhenSucceeded) {

View File

@ -45,6 +45,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gDownloadPlatform",
"@mozilla.org/toolkit/download-platform;1",
"mozIDownloadPlatform");
XPCOMUtils.defineLazyServiceGetter(this, "gEnvironment",
"@mozilla.org/process/environment;1",
"nsIEnvironment");
@ -54,7 +57,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
"@mozilla.org/uriloader/external-protocol-service;1",
"nsIExternalProtocolService");
XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() {
if ("@mozilla.org/parental-controls-service;1" in Cc) {
return Cc["@mozilla.org/parental-controls-service;1"]
@ -116,6 +119,7 @@ this.DownloadIntegration = {
dontCheckParentalControls: false,
shouldBlockInTest: false,
dontOpenFileAndFolder: false,
downloadDoneCalled: false,
_deferTestOpenFile: null,
_deferTestShowDir: null,
@ -380,6 +384,28 @@ this.DownloadIntegration = {
return Promise.resolve(shouldBlock);
},
/**
* Performs platform-specific operations when a download is done.
*
* aParam aDownload
* The Download object.
*
* @return {Promise}
* @resolves When all the operations completed successfully.
* @rejects JavaScript exception if any of the operations failed.
*/
downloadDone: function(aDownload) {
try {
gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
new FileUtils.File(aDownload.target.path),
aDownload.contentType, aDownload.source.isPrivate);
this.downloadDoneCalled = true;
return Promise.resolve();
} catch(ex) {
return Promise.reject(ex);
}
},
/**
* Determines whether it's a Windows Metro app.
*/

View File

@ -0,0 +1,148 @@
/* 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/. */
#include "DownloadPlatform.h"
#include "nsString.h"
#include "nsIURI.h"
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
#include "mozilla/Preferences.h"
#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
#ifdef XP_WIN
#include <shlobj.h>
#include "nsILocalFileWin.h"
#endif
#ifdef XP_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#endif
#ifdef MOZ_WIDGET_ANDROID
#include "AndroidBridge.h"
#endif
#ifdef MOZ_WIDGET_GTK2
#include <gtk/gtk.h>
#endif
using namespace mozilla;
DownloadPlatform *DownloadPlatform::gDownloadPlatformService = nullptr;
NS_IMPL_ISUPPORTS1(DownloadPlatform, mozIDownloadPlatform);
DownloadPlatform* DownloadPlatform::GetDownloadPlatform()
{
if (!gDownloadPlatformService) {
gDownloadPlatformService = new DownloadPlatform();
}
NS_ADDREF(gDownloadPlatformService);
#if defined(MOZ_WIDGET_GTK2)
g_type_init();
#endif
return gDownloadPlatformService;
}
#ifdef MOZ_ENABLE_GIO
static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data)
{
GError *err = NULL;
g_file_set_attributes_finish(G_FILE(source_obj), res, NULL, &err);
if (err) {
#ifdef DEBUG
NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__);
#endif
g_error_free(err);
}
}
#endif
nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIFile* aTarget,
const nsACString& aContentType, bool aIsPrivate)
{
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK2)
nsAutoString path;
if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) {
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK2)
// On Windows and Gtk, add the download to the system's "recent documents"
// list, with a pref to disable.
{
bool addToRecentDocs = Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS);
if (addToRecentDocs && !aIsPrivate) {
#ifdef XP_WIN
::SHAddToRecentDocs(SHARD_PATHW, path.get());
#elif defined(MOZ_WIDGET_GTK2)
GtkRecentManager* manager = gtk_recent_manager_get_default();
gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
NULL, NULL);
if (uri) {
gtk_recent_manager_add_item(manager, uri);
g_free(uri);
}
#endif
}
#ifdef MOZ_ENABLE_GIO
// Use GIO to store the source URI for later display in the file manager.
GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
nsCString source_uri;
aSource->GetSpec(source_uri);
GFileInfo *file_info = g_file_info_new();
g_file_info_set_attribute_string(file_info, "metadata::download-uri", source_uri.get());
g_file_set_attributes_async(gio_file,
file_info,
G_FILE_QUERY_INFO_NONE,
G_PRIORITY_DEFAULT,
NULL, gio_set_metadata_done, NULL);
g_object_unref(file_info);
g_object_unref(gio_file);
#endif
}
#endif
#ifdef XP_MACOSX
// On OS X, make the downloads stack bounce.
CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
NS_ConvertUTF16toUTF8(path).get(),
kCFStringEncodingUTF8);
CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
observedObject, NULL, TRUE);
::CFRelease(observedObject);
#endif
#ifdef MOZ_WIDGET_ANDROID
if (!aContentType.IsEmpty()) {
mozilla::AndroidBridge::Bridge()->ScanMedia(path, aContentType);
}
#endif
}
#ifdef XP_WIN
// Adjust file attributes so that by default, new files are indexed by
// desktop search services. Skip off those that land in the temp folder.
nsCOMPtr<nsIFile> tempDir, fileDir;
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
NS_ENSURE_SUCCESS(rv, rv);
aTarget->GetParent(getter_AddRefs(fileDir));
bool isTemp = false;
if (fileDir) {
fileDir->Equals(tempDir, &isTemp);
}
nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(aTarget));
if (!isTemp && localFileWin) {
localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
}
#endif
#endif
return NS_OK;
}

View File

@ -0,0 +1,30 @@
/* 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/. */
#ifndef __DownloadPlatform_h__
#define __DownloadPlatform_h__
#include "mozIDownloadPlatform.h"
#include "nsCOMPtr.h"
class nsIURI;
class nsIFile;
class DownloadPlatform : public mozIDownloadPlatform
{
public:
NS_DECL_ISUPPORTS
NS_DECL_MOZIDOWNLOADPLATFORM
DownloadPlatform() { }
virtual ~DownloadPlatform() { }
static DownloadPlatform *gDownloadPlatformService;
static DownloadPlatform* GetDownloadPlatform();
};
#endif

View File

@ -0,0 +1,14 @@
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
CXXFLAGS += $(TK_CFLAGS)

View File

@ -4,6 +4,16 @@
# 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/.
MODULE = 'jsdownloads'
CPP_SOURCES += [
'DownloadPlatform.cpp',
]
LIBRARY_NAME = 'jsdownloads_s'
LIBXUL_LIBRARY = True
EXTRA_COMPONENTS += [
'DownloadLegacy.js',
'Downloads.manifest',

View File

@ -1623,3 +1623,47 @@ add_task(function test_contentType() {
do_check_eq("text/plain", download.contentType);
});
/**
* This test will call the platform specific operations within
* DownloadPlatform::DownloadDone. While there is no test to verify the
* specific behaviours, this at least ensures that there is no error or crash.
*/
add_task(function test_platform_integration()
{
for (let isPrivate of [false, true]) {
DownloadIntegration.downloadDoneCalled = false;
// Some platform specific operations only operate on files outside the
// temporary directory or in the Downloads directory (such as setting
// the Windows searchable attribute, and the Mac Downloads icon bouncing),
// so use the system Downloads directory for the target file.
let targetFile = yield DownloadIntegration.getSystemDownloadsDirectory();
targetFile = targetFile.clone();
targetFile.append("test" + (Math.floor(Math.random() * 1000000)));
let download;
if (gUseLegacySaver) {
download = yield promiseStartLegacyDownload(httpUrl("source.txt"),
{ targetFile: targetFile });
}
else {
download = yield Downloads.createDownload({
source: httpUrl("source.txt"),
target: targetFile,
});
download.start();
}
// Wait for the whenSucceeded promise to be resolved first.
// downloadDone should be called before the whenSucceeded promise is resolved.
yield download.whenSucceeded().then(function () {
do_check_true(DownloadIntegration.downloadDoneCalled);
});
// Then, wait for the promise returned by "start" to be resolved.
yield promiseDownloadStopped(download);
yield promiseVerifyContents(download.target.path, TEST_DATA_SHORT);
}
});