Bug 299372 - Content-Disposition headers no longer looked at for Save Link As filename. original-patch=dmose, r=mconnor, r=biesi, a=blocking-ff3+

This commit is contained in:
dolske@mozilla.com 2008-04-02 20:02:08 -07:00
Родитель f7a5027ea5
Коммит bd40f61468
14 изменённых файлов: 219 добавлений и 43 удалений

Просмотреть файл

@ -224,6 +224,12 @@ pref("browser.urlbar.maxRichResults", 12);
pref("browser.urlbar.search.chunkSize", 1000); pref("browser.urlbar.search.chunkSize", 1000);
pref("browser.urlbar.search.timeout", 100); pref("browser.urlbar.search.timeout", 100);
// Number of milliseconds to wait for the http headers (and thus
// the Content-Disposition filename) before giving up and falling back to
// picking a filename without that info in hand so that the user sees some
// feedback from their action.
pref("browser.download.saveLinkAsFilenameTimeout", 1000);
pref("browser.download.useDownloadDir", true); pref("browser.download.useDownloadDir", true);
pref("browser.download.folderList", 0); pref("browser.download.folderList", 0);
pref("browser.download.manager.showAlertOnComplete", true); pref("browser.download.manager.showAlertOnComplete", true);

Просмотреть файл

@ -41,6 +41,7 @@
# Simon Bünzli <zeniko@gmail.com> # Simon Bünzli <zeniko@gmail.com>
# Gijs Kruitbosch <gijskruitbosch@gmail.com> # Gijs Kruitbosch <gijskruitbosch@gmail.com>
# Ehsan Akhgari <ehsan.akhgari@gmail.com> # Ehsan Akhgari <ehsan.akhgari@gmail.com>
# Dan Mosedale <dmose@mozilla.org>
# #
# Alternatively, the contents of this file may be used under the terms of # Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or # either the GNU General Public License Version 2 or later (the "GPL"), or
@ -826,10 +827,123 @@ nsContextMenu.prototype = {
// Save URL of clicked-on link. // Save URL of clicked-on link.
saveLink: function() { saveLink: function() {
// canonical def in nsURILoader.h
const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
var doc = this.target.ownerDocument; var doc = this.target.ownerDocument;
urlSecurityCheck(this.linkURL, doc.nodePrincipal); urlSecurityCheck(this.linkURL, doc.nodePrincipal);
saveURL(this.linkURL, this.linkText(), null, true, false, var linkText = this.linkText();
doc.documentURIObject); var linkURL = this.linkURL;
// an object to proxy the data through to
// nsIExternalHelperAppService.doContent, which will wait for the
// appropriate MIME-type headers and then prompt the user with a
// file picker
function saveAsListener() {}
saveAsListener.prototype = {
extListener: null,
onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
// if the timer fired, the error status will have been caused by that,
// and we'll be restarting in onStopRequest, so no reason to notify
// the user
if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
return;
timer.cancel();
// some other error occured; notify the user...
if (!Components.isSuccessCode(aRequest.status)) {
try {
const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
const bundle = sbs.createBundle(
"chrome://mozapps/locale/downloads/downloads.properties");
const title = bundle.GetStringFromName("downloadErrorAlertTitle");
const msg = bundle.GetStringFromName("downloadErrorGeneric");
const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService);
promptSvc.alert(doc.defaultView, title, msg);
} catch (ex) {}
return;
}
var extHelperAppSvc =
Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
getService(Ci.nsIExternalHelperAppService);
var channel = aRequest.QueryInterface(Ci.nsIChannel);
this.extListener =
extHelperAppSvc.doContent(channel.contentType, aRequest,
doc.defaultView, true);
this.extListener.onStartRequest(aRequest, aContext);
},
onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
aStatusCode) {
if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
// do it the old fashioned way, which will pick the best filename
// it can without waiting.
saveURL(linkURL, linkText, null, true, false, doc.documentURIObject);
}
if (this.extListener)
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
},
onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
aInputStream,
aOffset, aCount) {
this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
aOffset, aCount);
}
}
// in case we need to prompt the user for authentication
function callbacks() {}
callbacks.prototype = {
getInterface: function sLA_callbacks_getInterface(aIID) {
if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIPromptFactory);
return ww.getPrompt(doc.defaultView, aIID);
}
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
// if it we don't have the headers after a short time, the user
// won't have received any feedback from their click. that's bad. so
// we give up waiting for the filename.
function timerCallback() {}
timerCallback.prototype = {
notify: function sLA_timer_notify(aTimer) {
channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
return;
}
}
// set up a channel to do the saving
var ioService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var channel = ioService.newChannelFromURI(this.getLinkURI());
channel.notificationCallbacks = new callbacks();
channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
if (channel instanceof Ci.nsIHttpChannel)
channel.referrer = doc.documentURIObject;
// fallback to the old way if we don't see the headers quickly
var timeToWait =
gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(new timerCallback(), timeToWait,
timer.TYPE_ONE_SHOT);
// kick off the channel with our proxy object as the listener
channel.asyncOpen(new saveAsListener(), null);
}, },
sendLink: function() { sendLink: function() {

Просмотреть файл

@ -1893,7 +1893,9 @@ SessionStoreService.prototype = {
this._dirty = aUpdateAll; this._dirty = aUpdateAll;
var oState = this._getCurrentState(); var oState = this._getCurrentState();
oState.session = { state: ((this._loadState == STATE_RUNNING) ? STATE_RUNNING_STR : STATE_STOPPED_STR) }; oState.session = { state: ((this._loadState == STATE_RUNNING) ? STATE_RUNNING_STR : STATE_STOPPED_STR) };
this._writeFile(this._sessionFile, oState.toSource()); //var oStateString = this._toJSONString(oState);
var oStateString = oState.toSource();
this._writeFile(this._sessionFile, oStateString);
this._lastSaveTime = Date.now(); this._lastSaveTime = Date.now();
}, },

Просмотреть файл

@ -474,6 +474,7 @@ CHelperAppLauncherDlg::PromptForSaveToFile(nsIHelperAppLauncher *aLauncher,
nsISupports *aWindowContext, nsISupports *aWindowContext,
const PRUnichar *aDefaultFile, const PRUnichar *aDefaultFile,
const PRUnichar *aSuggestedFileExtension, const PRUnichar *aSuggestedFileExtension,
PRBool aForcePrompt,
nsILocalFile **_retval) nsILocalFile **_retval)
{ {
NS_ENSURE_ARG_POINTER(_retval); NS_ENSURE_ARG_POINTER(_retval);

Просмотреть файл

@ -212,6 +212,7 @@ NS_IMETHODIMP EmbedDownloadMgr::PromptForSaveToFile(nsIHelperAppLauncher *aLaunc
nsISupports *aWindowContext, nsISupports *aWindowContext,
const PRUnichar *aDefaultFile, const PRUnichar *aDefaultFile,
const PRUnichar *aSuggestedFileExtension, const PRUnichar *aSuggestedFileExtension,
PRBool aForcePrompt,
nsILocalFile **_retval) nsILocalFile **_retval)
{ {
*_retval = nsnull; *_retval = nsnull;

Просмотреть файл

@ -70,6 +70,7 @@ NS_IMETHODIMP nsUnknownContentTypeHandler::PromptForSaveToFile( nsIHelperAppLaun
nsISupports *aWindowContext, nsISupports *aWindowContext,
const PRUnichar *aDefaultFile, const PRUnichar *aDefaultFile,
const PRUnichar *aSuggestedFileExtension, const PRUnichar *aSuggestedFileExtension,
PRBool aForcePrompt,
nsILocalFile **_retval ) nsILocalFile **_retval )
{ {
///* ATENTIE */ printf("PromptForSaveToFile!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n\n"); ///* ATENTIE */ printf("PromptForSaveToFile!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n\n");

Просмотреть файл

@ -119,7 +119,7 @@ nsHelperAppDialog.prototype = {
}, },
// promptForSaveToFile: Display file picker dialog and return selected file. // promptForSaveToFile: Display file picker dialog and return selected file.
promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension) { promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
var result = ""; var result = "";
const prefSvcContractID = "@mozilla.org/preferences-service;1"; const prefSvcContractID = "@mozilla.org/preferences-service;1";
@ -142,7 +142,7 @@ nsHelperAppDialog.prototype = {
var autoDownload = branch.getBoolPref("autoDownload"); var autoDownload = branch.getBoolPref("autoDownload");
// If the autoDownload pref is set then just download to default download directory // If the autoDownload pref is set then just download to default download directory
if (autoDownload && dir && dir.exists()) { if (!aForcePrompt && autoDownload && dir && dir.exists()) {
if (aDefaultFile == "") if (aDefaultFile == "")
aDefaultFile = bundle.GetStringFromName("noDefaultFile") + (aSuggestedFileExtension || ""); aDefaultFile = bundle.GetStringFromName("noDefaultFile") + (aSuggestedFileExtension || "");
dir.append(aDefaultFile); dir.append(aDefaultFile);

Просмотреть файл

@ -141,33 +141,35 @@ nsUnknownContentTypeDialog.prototype = {
// //
// Note - this function is called without a dialog, so it cannot access any part // Note - this function is called without a dialog, so it cannot access any part
// of the dialog XUL as other functions on this object do. // of the dialog XUL as other functions on this object do.
promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension) { promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
var result = null; var result = null;
this.mLauncher = aLauncher; this.mLauncher = aLauncher;
// Check to see if the user wishes to auto save to the default download
// folder without prompting. This preferences may not be set, so default
// to not prompting.
let prefs = Components.classes["@mozilla.org/preferences-service;1"] let prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch); .getService(Components.interfaces.nsIPrefBranch);
let autodownload = true;
try { if (!aForcePrompt) {
autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); // Check to see if the user wishes to auto save to the default download
} catch (e) { } // folder without prompting. Note that preference might not be set.
let autodownload = false;
try {
autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
} catch (e) { }
if (autodownload) { if (autodownload) {
// Retrieve the user's default download directory // Retrieve the user's default download directory
var dnldMgr = Components.classes["@mozilla.org/download-manager;1"] let dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager); .getService(Components.interfaces.nsIDownloadManager);
var defaultFolder = dnldMgr.userDownloadsDirectory; let defaultFolder = dnldMgr.userDownloadsDirectory;
result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension); result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
// Check to make sure we have a valid directory, otherwise, prompt
if (result)
return result;
}
} }
// Check to make sure we have a valid directory, otherwise, prompt
if (result)
return result;
// Use file picker to show dialog. // Use file picker to show dialog.
var nsIFilePicker = Components.interfaces.nsIFilePicker; var nsIFilePicker = Components.interfaces.nsIFilePicker;
var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);

Просмотреть файл

@ -594,6 +594,7 @@ nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports *
rv = helperAppService->DoContent(mContentType, rv = helperAppService->DoContent(mContentType,
request, request,
m_originalContext, m_originalContext,
PR_FALSE,
getter_AddRefs(m_targetStreamListener)); getter_AddRefs(m_targetStreamListener));
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
request->SetLoadFlags(loadFlags); request->SetLoadFlags(loadFlags);

Просмотреть файл

@ -99,4 +99,10 @@ protected:
#define NS_ERROR_MALWARE_URI NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_URILOADER, 30) #define NS_ERROR_MALWARE_URI NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_URILOADER, 30)
#define NS_ERROR_PHISHING_URI NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_URILOADER, 31) #define NS_ERROR_PHISHING_URI NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_URILOADER, 31)
/**
* Used when "Save Link As..." doesn't see the headers quickly enough to choose
* a filename. See nsContextMenu.js.
*/
#define NS_ERROR_SAVE_LINK_AS_TIMEOUT NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_URILOADER, 32);
#endif /* nsURILoader_h__ */ #endif /* nsURILoader_h__ */

Просмотреть файл

@ -524,6 +524,7 @@ nsExternalHelperAppService::~nsExternalHelperAppService()
NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType, NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeContentType,
nsIRequest *aRequest, nsIRequest *aRequest,
nsIInterfaceRequestor *aWindowContext, nsIInterfaceRequestor *aWindowContext,
PRBool aForceSave,
nsIStreamListener ** aStreamListener) nsIStreamListener ** aStreamListener)
{ {
nsAutoString fileName; nsAutoString fileName;
@ -640,7 +641,8 @@ NS_IMETHODIMP nsExternalHelperAppService::DoContent(const nsACString& aMimeConte
buf, buf,
aWindowContext, aWindowContext,
fileName, fileName,
reason); reason,
aForceSave);
if (!handler) if (!handler)
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aStreamListener = handler); NS_ADDREF(*aStreamListener = handler);
@ -991,11 +993,12 @@ nsExternalAppHandler::nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo,
const nsCSubstring& aTempFileExtension, const nsCSubstring& aTempFileExtension,
nsIInterfaceRequestor* aWindowContext, nsIInterfaceRequestor* aWindowContext,
const nsAString& aSuggestedFilename, const nsAString& aSuggestedFilename,
PRUint32 aReason) PRUint32 aReason, PRBool aForceSave)
: mMimeInfo(aMIMEInfo) : mMimeInfo(aMIMEInfo)
, mWindowContext(aWindowContext) , mWindowContext(aWindowContext)
, mWindowToClose(nsnull) , mWindowToClose(nsnull)
, mSuggestedFileName(aSuggestedFilename) , mSuggestedFileName(aSuggestedFilename)
, mForceSave(aForceSave)
, mCanceled(PR_FALSE) , mCanceled(PR_FALSE)
, mShouldCloseWindow(PR_FALSE) , mShouldCloseWindow(PR_FALSE)
, mReceivedDispositionInfo(PR_FALSE) , mReceivedDispositionInfo(PR_FALSE)
@ -1470,6 +1473,13 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo
alwaysAsk = (action != nsIMIMEInfo::saveToDisk); alwaysAsk = (action != nsIMIMEInfo::saveToDisk);
} }
// if we were told that we _must_ save to disk without asking, all the stuff
// before this is irrelevant; override it
if (mForceSave) {
alwaysAsk = PR_FALSE;
action = nsIMIMEInfo::saveToDisk;
}
if (alwaysAsk) if (alwaysAsk)
{ {
// do this first! make sure we don't try to take an action until the user tells us what they want to do // do this first! make sure we don't try to take an action until the user tells us what they want to do
@ -1904,7 +1914,7 @@ nsresult nsExternalAppHandler::PromptForSaveToFile(nsILocalFile ** aNewFile, con
mWindowContext, mWindowContext,
aDefaultFile.get(), aDefaultFile.get(),
aFileExtension.get(), aFileExtension.get(),
aNewFile); mForceSave, aNewFile);
return rv; return rv;
} }

Просмотреть файл

@ -251,7 +251,7 @@ public:
nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo, const nsCSubstring& aFileExtension, nsExternalAppHandler(nsIMIMEInfo * aMIMEInfo, const nsCSubstring& aFileExtension,
nsIInterfaceRequestor * aWindowContext, nsIInterfaceRequestor * aWindowContext,
const nsAString& aFilename, const nsAString& aFilename,
PRUint32 aReason); PRUint32 aReason, PRBool aForceSave);
~nsExternalAppHandler(); ~nsExternalAppHandler();
@ -280,6 +280,13 @@ protected:
*/ */
nsString mSuggestedFileName; nsString mSuggestedFileName;
/**
* If set, this handler should forcibly save the file to disk regardless of
* MIME info settings or anything else, without ever popping up the
* unknown content type handling dialog.
*/
PRPackedBool mForceSave;
/** /**
* The canceled flag is set if the user canceled the launching of this * The canceled flag is set if the user canceled the launching of this
* application before we finished saving the data to a temp file. * application before we finished saving the data to a temp file.

Просмотреть файл

@ -51,7 +51,7 @@ interface nsIInterfaceRequestor;
* The external helper app service is used for finding and launching * The external helper app service is used for finding and launching
* platform specific external applications for a given mime content type. * platform specific external applications for a given mime content type.
*/ */
[scriptable, uuid(0ea90cf3-2dd9-470f-8f76-f141743c5678)] [scriptable, uuid(9e456297-ba3e-42b1-92bd-b7db014268cb)]
interface nsIExternalHelperAppService : nsISupports interface nsIExternalHelperAppService : nsISupports
{ {
/** /**
@ -64,10 +64,13 @@ interface nsIExternalHelperAppService : nsISupports
* @param aWindowContext Use GetInterface to retrieve properties like the * @param aWindowContext Use GetInterface to retrieve properties like the
* dom window or parent window... * dom window or parent window...
* The service might need this in order to bring up dialogs. * The service might need this in order to bring up dialogs.
* @param aForceSave True to always save this content to disk, regardless of
* nsIMIMEInfo and other such influences.
* @return A nsIStreamListener which the caller should pump the data into. * @return A nsIStreamListener which the caller should pump the data into.
*/ */
nsIStreamListener doContent (in ACString aMimeContentType, in nsIRequest aRequest, nsIStreamListener doContent (in ACString aMimeContentType, in nsIRequest aRequest,
in nsIInterfaceRequestor aWindowContext); in nsIInterfaceRequestor aWindowContext,
in boolean aForceSave);
/** /**
* Returns true if data from a URL with this extension combination * Returns true if data from a URL with this extension combination

Просмотреть файл

@ -53,7 +53,7 @@ interface nsILocalFile;
* will access methods of the nsIHelperAppLauncher passed in to show() * will access methods of the nsIHelperAppLauncher passed in to show()
* in order to cause a "save to disk" or "open using" action. * in order to cause a "save to disk" or "open using" action.
*/ */
[scriptable, uuid(64355793-988d-40a5-ba8e-fcde78cac631)] [scriptable, uuid(f3704fdc-8ae6-4eba-a3c3-f02958ac0649)]
interface nsIHelperAppLauncherDialog : nsISupports { interface nsIHelperAppLauncherDialog : nsISupports {
/** /**
* This request is passed to the helper app dialog because Gecko can not * This request is passed to the helper app dialog because Gecko can not
@ -72,23 +72,45 @@ interface nsIHelperAppLauncherDialog : nsISupports {
*/ */
const unsigned long REASON_TYPESNIFFED = 2; const unsigned long REASON_TYPESNIFFED = 2;
// Show confirmation dialog for launching application (or "save to /**
// disk") for content specified by aLauncher. * Show confirmation dialog for launching application (or "save to
// aReason is one of the constants from above. It indicates why the dialog is * disk") for content specified by aLauncher.
// shown. *
// Implementors should treat unknown reasons like REASON_CANTHANDLE. * @param aLauncher
* A nsIHelperAppLauncher to be invoked when a file is selected.
* @param aWindowContext
* Window associated with action.
* @param aReason
* One of the constants from above. It indicates why the dialog is
* shown. Implementors should treat unknown reasons like
* REASON_CANTHANDLE.
*/
void show(in nsIHelperAppLauncher aLauncher, void show(in nsIHelperAppLauncher aLauncher,
in nsISupports aContext, in nsISupports aWindowContext,
in unsigned long aReason); in unsigned long aReason);
// invoke a save to file dialog instead of the full fledged helper app dialog. /**
// aDefaultFileName --> default file name to provide (can be null) * Invoke a save-to-file dialog instead of the full fledged helper app dialog.
// aSuggestedFileExtension --> sugested file extension * Returns the a nsILocalFile for the file name/location selected.
// aFileLocation --> return value for the file location *
* @param aLauncher
* A nsIHelperAppLauncher to be invoked when a file is selected.
* @param aWindowContext
* Window associated with action.
* @param aDefaultFileName
* Default file name to provide (can be null)
* @param aSuggestedFileExtension
* Sugested file extension
* @param aForcePrompt
* Set to true to force prompting the user for thet file
* name/location, otherwise perferences may control if the user is
* prompted.
*/
nsILocalFile promptForSaveToFile(in nsIHelperAppLauncher aLauncher, nsILocalFile promptForSaveToFile(in nsIHelperAppLauncher aLauncher,
in nsISupports aWindowContext, in nsISupports aWindowContext,
in wstring aDefaultFile, in wstring aDefaultFileName,
in wstring aSuggestedFileExtension); in wstring aSuggestedFileExtension,
in boolean aForcePrompt);
}; };