1 # -*- Mode
: Java
; tab
-width
: 2; indent
-tabs
-mode
: nil
; c
-basic
-offset
: 2 -*-
2 # ***** BEGIN LICENSE BLOCK
*****
3 # Version
: MPL
1.1/GPL 2.0/LGPL
2.1
5 # The contents
of this file are subject to the Mozilla Public License Version
6 # 1.1 (the
"License"); you may not
use this file except
in compliance
with
7 # the License
. You may obtain a copy
of the License at
8 # http
://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an
"AS IS" basis
,
11 # WITHOUT WARRANTY OF ANY KIND
, either express or implied
. See the License
12 # for the specific language governing rights and limitations under the
15 # The Original Code is mozilla
.org code
.
17 # The Initial Developer
of the Original Code is
18 # Netscape Communications Corporation
.
19 # Portions created by the Initial Developer are
Copyright (C
) 1998
20 # the Initial Developer
. All Rights Reserved
.
23 # Ben Goodger
<ben
@netscape
.com
> (Save File
)
24 # Fredrik Holmqvist
<thesuckiestemail
@yahoo
.se
>
25 # Asaf Romano
<mozilla
.mano
@sent
.com
>
27 # Alternatively
, the contents
of this file may be used under the terms
of
28 # either the GNU General Public License Version
2 or
later (the
"GPL"), or
29 # the GNU Lesser General Public License Version
2.1 or
later (the
"LGPL"),
30 # in which
case the provisions
of the GPL or the LGPL are applicable instead
31 # of those above
. If you wish to allow
use of your version
of this file only
32 # under the terms
of either the GPL or the LGPL
, and not to allow others to
33 # use your version
of this file under the terms
of the MPL
, indicate your
34 # decision by deleting the provisions above and replace them
with the notice
35 # and other provisions required by the GPL or the LGPL
. If you
do not
delete
36 # the provisions above
, a recipient may
use your version
of this file under
37 # the terms
of any one
of the MPL
, the GPL or the LGPL
.
39 # ***** END LICENSE BLOCK
*****
42 * urlSecurityCheck: JavaScript wrapper for checkLoadURIWithPrincipal
43 * and checkLoadURIStrWithPrincipal.
44 * If |aPrincipal| is not allowed to link to |aURL|, this function throws with
48 * The URL a page has linked to. This could be passed either as a string
49 * or as a nsIURI object.
51 * The principal of the document from which aURL came.
53 * Flags to be passed to checkLoadURIStr. If undefined,
54 * nsIScriptSecurityManager.STANDARD will be passed.
56 function urlSecurityCheck(aURL
, aPrincipal
, aFlags
)
58 const nsIScriptSecurityManager
=
59 Components
.interfaces
.nsIScriptSecurityManager
;
60 var secMan
= Components
.classes
["@mozilla.org/scriptsecuritymanager;1"]
61 .getService(nsIScriptSecurityManager
);
62 if (aFlags
=== undefined)
63 aFlags
= nsIScriptSecurityManager
.STANDARD
;
66 if (aURL
instanceof Components
.interfaces
.nsIURI
)
67 secMan
.checkLoadURIWithPrincipal(aPrincipal
, aURL
, aFlags
);
69 secMan
.checkLoadURIStrWithPrincipal(aPrincipal
, aURL
, aFlags
);
71 // XXXmano: dump the principal url here too
72 throw "Load of " + aURL
+ " denied.";
77 * Determine whether or not a given focused DOMWindow is in the content area.
79 function isContentFrame(aFocusedWindow
)
84 return (aFocusedWindow
.top
== window
.content
);
88 // Clientelle: (Make sure you don't break any of these)
89 // - File -> Save Page/Frame As...
90 // - Context -> Save Page/Frame As...
91 // - Context -> Save Link As...
92 // - Alt-Click links in web pages
93 // - Alt-Click links in the UI
95 // Try saving each of these types:
96 // - A complete webpage using File->Save Page As, and Context->Save Page As
97 // - A webpage as HTML only using the above methods
98 // - A webpage as Text only using the above methods
99 // - An image with an extension (e.g. .jpg) in its file name, using
100 // Context->Save Image As...
101 // - An image without an extension (e.g. a banner ad on cnn.com) using
103 // - A linked document using Save Link As...
104 // - A linked document using Alt-click Save Link As...
106 function saveURL(aURL
, aFileName
, aFilePickerTitleKey
, aShouldBypassCache
,
107 aSkipPrompt
, aReferrer
)
109 internalSave(aURL
, null, aFileName
, null, null, aShouldBypassCache
,
110 aFilePickerTitleKey
, null, aReferrer
, aSkipPrompt
);
113 // Just like saveURL, but will get some info off the image before
114 // calling internalSave
115 // Clientelle: (Make sure you don't break any of these)
116 // - Context -> Save Image As...
117 const imgICache
= Components
.interfaces
.imgICache
;
118 const nsISupportsCString
= Components
.interfaces
.nsISupportsCString
;
120 function saveImageURL(aURL
, aFileName
, aFilePickerTitleKey
, aShouldBypassCache
,
121 aSkipPrompt
, aReferrer
)
123 var contentType
= null;
124 var contentDisposition
= null;
125 if (!aShouldBypassCache
) {
127 var imageCache
= Components
.classes
["@mozilla.org/image/cache;1"]
128 .getService(imgICache
);
130 imageCache
.findEntryProperties(makeURI(aURL
, getCharsetforSave(null)));
132 contentType
= props
.get("type", nsISupportsCString
);
133 contentDisposition
= props
.get("content-disposition",
137 // Failure to get type and content-disposition off the image is non-fatal
140 internalSave(aURL
, null, aFileName
, contentDisposition
, contentType
,
141 aShouldBypassCache
, aFilePickerTitleKey
, null, aReferrer
, aSkipPrompt
);
144 function saveFrameDocument()
146 var focusedWindow
= document
.commandDispatcher
.focusedWindow
;
147 if (isContentFrame(focusedWindow
))
148 saveDocument(focusedWindow
.document
);
151 function saveDocument(aDocument
, aSkipPrompt
)
154 throw "Must have a document when calling saveDocument";
156 // We want to use cached data because the document is currently visible.
157 var contentDisposition
= null;
160 aDocument
.defaultView
161 .QueryInterface(Components
.interfaces
.nsIInterfaceRequestor
)
162 .getInterface(Components
.interfaces
.nsIDOMWindowUtils
)
163 .getDocumentMetadata("content-disposition");
165 // Failure to get a content-disposition is ok
167 internalSave(aDocument
.location
.href
, aDocument
, null, contentDisposition
,
168 aDocument
.contentType
, false, null, null,
169 aDocument
.referrer
? makeURI(aDocument
.referrer
) : null,
173 function DownloadListener(win
, transfer
) {
174 function makeClosure(name
) {
176 transfer
[name
].apply(transfer
, arguments
);
182 // Now... we need to forward all calls to our transfer
183 for (var i
in transfer
) {
184 if (i
!= "QueryInterface")
185 this[i
] = makeClosure(i
);
189 DownloadListener
.prototype = {
190 QueryInterface
: function dl_qi(aIID
)
192 if (aIID
.equals(Components
.interfaces
.nsIInterfaceRequestor
) ||
193 aIID
.equals(Components
.interfaces
.nsIWebProgressListener
) ||
194 aIID
.equals(Components
.interfaces
.nsIWebProgressListener2
) ||
195 aIID
.equals(Components
.interfaces
.nsISupports
)) {
198 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
201 getInterface
: function dl_gi(aIID
)
203 if (aIID
.equals(Components
.interfaces
.nsIAuthPrompt
) ||
204 aIID
.equals(Components
.interfaces
.nsIAuthPrompt2
)) {
206 Components
.classes
["@mozilla.org/embedcomp/window-watcher;1"]
207 .getService(Components
.interfaces
.nsIPromptFactory
);
208 return ww
.getPrompt(this.window
, aIID
);
211 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
215 const kSaveAsType_Complete
= 0; // Save document with attached objects.
216 // const kSaveAsType_URL = 1; // Save document or URL by itself.
217 const kSaveAsType_Text
= 2; // Save document, converting to plain text.
220 * internalSave: Used when saving a document or URL. This method:
221 * - Determines a local target filename to use (unless parameter
222 * aChosenData is non-null)
223 * - Determines content-type if possible
224 * - Prompts the user to confirm the destination filename and save mode
225 * (content-type affects this)
226 * - Creates a 'Persist' object (which will perform the saving in the
227 * background) and then starts it.
229 * @param aURL The String representation of the URL of the document being saved
230 * @param aDocument The document to be saved
231 * @param aDefaultFileName The caller-provided suggested filename if we don't
233 * @param aContentDisposition The caller-provided content-disposition header
235 * @param aContentType The caller-provided content-type to use
236 * @param aShouldBypassCache If true, the document will always be refetched
238 * @param aFilePickerTitleKey Alternate title for the file picker
239 * @param aChosenData If non-null this contains an instance of object AutoChosen
240 * (see below) which holds pre-determined data so that the user does not
241 * need to be prompted for a target filename.
242 * @param aReferrer the referrer URI object (not URL string) to use, or null
243 if no referrer should be sent.
244 * @param aSkipPrompt If true, the file will be saved to the default download folder.
246 function internalSave(aURL
, aDocument
, aDefaultFileName
, aContentDisposition
,
247 aContentType
, aShouldBypassCache
, aFilePickerTitleKey
,
248 aChosenData
, aReferrer
, aSkipPrompt
)
250 if (aSkipPrompt
== undefined)
253 // Note: aDocument == null when this code is used by save-link-as...
254 var saveMode
= GetSaveModeForContentType(aContentType
);
255 var isDocument
= aDocument
!= null && saveMode
!= SAVEMODE_FILEONLY
;
256 var saveAsType
= kSaveAsType_Complete
;
259 // Find the URI object for aURL and the FileName/Extension to use when saving.
260 // FileName/Extension will be ignored if aChosenData supplied.
261 var fileInfo
= new FileInfo(aDefaultFileName
);
263 file
= aChosenData
.file
;
267 charset
= aDocument
.characterSet
;
269 charset
= aReferrer
.originCharset
;
270 initFileInfo(fileInfo
, aURL
, charset
, aDocument
,
271 aContentType
, aContentDisposition
);
273 fpTitleKey
: aFilePickerTitleKey
,
274 isDocument
: isDocument
,
276 contentType
: aContentType
,
278 saveAsType
: saveAsType
,
283 if (!getTargetFile(fpParams
, aSkipPrompt
))
284 // If the method returned false this is because the user cancelled from
285 // the save file picker dialog.
288 saveAsType
= fpParams
.saveAsType
;
289 saveMode
= fpParams
.saveMode
;
290 file
= fpParams
.file
;
291 fileURL
= fpParams
.fileURL
;
295 fileURL
= makeFileURI(file
);
297 // XXX We depend on the following holding true in appendFiltersForContentType():
298 // If we should save as a complete page, the saveAsType is kSaveAsType_Complete.
299 // If we should save as text, the saveAsType is kSaveAsType_Text.
300 var useSaveDocument
= isDocument
&&
301 (((saveMode
& SAVEMODE_COMPLETE_DOM
) && (saveAsType
== kSaveAsType_Complete
)) ||
302 ((saveMode
& SAVEMODE_COMPLETE_TEXT
) && (saveAsType
== kSaveAsType_Text
)));
303 // If we're saving a document, and are saving either in complete mode or
304 // as converted text, pass the document to the web browser persist component.
305 // If we're just saving the HTML (second option in the list), send only the URI.
306 var source
= useSaveDocument
? aDocument
: fileInfo
.uri
;
309 contentType
: (!aChosenData
&& useSaveDocument
&&
310 saveAsType
== kSaveAsType_Text
) ?
313 postData
: isDocument
? getPostData() : null,
314 bypassCache
: aShouldBypassCache
317 var persist
= makeWebBrowserPersist();
319 // Calculate persist flags.
320 const nsIWBP
= Components
.interfaces
.nsIWebBrowserPersist
;
321 const flags
= nsIWBP
.PERSIST_FLAGS_REPLACE_EXISTING_FILES
;
322 if (aShouldBypassCache
)
323 persist
.persistFlags
= flags
| nsIWBP
.PERSIST_FLAGS_BYPASS_CACHE
;
325 persist
.persistFlags
= flags
| nsIWBP
.PERSIST_FLAGS_FROM_CACHE
;
327 // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof):
328 persist
.persistFlags
|= nsIWBP
.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION
;
330 // Create download and initiate it (below)
331 var tr
= Components
.classes
["@mozilla.org/transfer;1"].createInstance(Components
.interfaces
.nsITransfer
);
333 if (useSaveDocument
) {
334 // Saving a Document, not a URI:
335 var filesFolder
= null;
336 if (persistArgs
.contentType
!= "text/plain") {
337 // Create the local directory into which to save associated files.
338 filesFolder
= file
.clone();
340 var nameWithoutExtension
= getFileBaseName(filesFolder
.leafName
);
341 var filesFolderLeafName
= getStringBundle().formatStringFromName("filesFolder",
342 [nameWithoutExtension
],
345 filesFolder
.leafName
= filesFolderLeafName
;
348 var encodingFlags
= 0;
349 if (persistArgs
.contentType
== "text/plain") {
350 encodingFlags
|= nsIWBP
.ENCODE_FLAGS_FORMATTED
;
351 encodingFlags
|= nsIWBP
.ENCODE_FLAGS_ABSOLUTE_LINKS
;
352 encodingFlags
|= nsIWBP
.ENCODE_FLAGS_NOFRAMES_CONTENT
;
355 encodingFlags
|= nsIWBP
.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES
;
358 const kWrapColumn
= 80;
359 tr
.init((aChosenData
? aChosenData
.uri
: fileInfo
.uri
),
360 persistArgs
.target
, "", null, null, null, persist
);
361 persist
.progressListener
= new DownloadListener(window
, tr
);
362 persist
.saveDocument(persistArgs
.source
, persistArgs
.target
, filesFolder
,
363 persistArgs
.contentType
, encodingFlags
, kWrapColumn
);
365 tr
.init((aChosenData
? aChosenData
.uri
: source
),
366 persistArgs
.target
, "", null, null, null, persist
);
367 persist
.progressListener
= new DownloadListener(window
, tr
);
368 persist
.saveURI((aChosenData
? aChosenData
.uri
: source
),
369 null, aReferrer
, persistArgs
.postData
, null,
375 * Structure for holding info about automatically supplied parameters for
376 * internalSave(...). This allows parameters to be supplied so the user does not
377 * need to be prompted for file info.
378 * @param aFileAutoChosen This is an nsILocalFile object that has been
379 * pre-determined as the filename for the target to save to
380 * @param aUriAutoChosen This is the nsIURI object for the target
382 function AutoChosen(aFileAutoChosen
, aUriAutoChosen
) {
383 this.file
= aFileAutoChosen
;
384 this.uri
= aUriAutoChosen
;
388 * Structure for holding info about a URL and the target filename it should be
389 * saved to. This structure is populated by initFileInfo(...).
390 * @param aSuggestedFileName This is used by initFileInfo(...) when it
391 * cannot 'discover' the filename from the url
392 * @param aFileName The target filename
393 * @param aFileBaseName The filename without the file extension
394 * @param aFileExt The extension of the filename
395 * @param aUri An nsIURI object for the url that is being saved
397 function FileInfo(aSuggestedFileName
, aFileName
, aFileBaseName
, aFileExt
, aUri
) {
398 this.suggestedFileName
= aSuggestedFileName
;
399 this.fileName
= aFileName
;
400 this.fileBaseName
= aFileBaseName
;
401 this.fileExt
= aFileExt
;
406 * Determine what the 'default' filename string is, its file extension and the
407 * filename without the extension. This filename is used when prompting the user
408 * for confirmation in the file picker dialog.
409 * @param aFI A FileInfo structure into which we'll put the results of this method.
410 * @param aURL The String representation of the URL of the document being saved
411 * @param aURLCharset The charset of aURL.
412 * @param aDocument The document to be saved
413 * @param aContentType The content type we're saving, if it could be
414 * determined by the caller.
415 * @param aContentDisposition The content-disposition header for the object
416 * we're saving, if it could be determined by the caller.
418 function initFileInfo(aFI
, aURL
, aURLCharset
, aDocument
,
419 aContentType
, aContentDisposition
)
422 // Get an nsIURI object from aURL if possible:
424 aFI
.uri
= makeURI(aURL
, aURLCharset
);
425 // Assuming nsiUri is valid, calling QueryInterface(...) on it will
426 // populate extra object fields (eg filename and file extension).
427 var url
= aFI
.uri
.QueryInterface(Components
.interfaces
.nsIURL
);
428 aFI
.fileExt
= url
.fileExtension
;
432 // Get the default filename:
433 aFI
.fileName
= getDefaultFileName((aFI
.suggestedFileName
|| aFI
.fileName
),
434 aFI
.uri
, aDocument
, aContentDisposition
);
435 // If aFI.fileExt is still blank, consider: aFI.suggestedFileName is supplied
436 // if saveURL(...) was the original caller (hence both aContentType and
437 // aDocument are blank). If they were saving a link to a website then make
438 // the extension .htm .
439 if (!aFI
.fileExt
&& !aDocument
&& !aContentType
&& (/^http(s?):\/\//i.test(aURL
))) {
441 aFI
.fileBaseName
= aFI
.fileName
;
443 aFI
.fileExt
= getDefaultExtension(aFI
.fileName
, aFI
.uri
, aContentType
);
444 aFI
.fileBaseName
= getFileBaseName(aFI
.fileName
);
450 function getTargetFile(aFpP
, aSkipPrompt
)
452 const prefSvcContractID
= "@mozilla.org/preferences-service;1";
453 const prefSvcIID
= Components
.interfaces
.nsIPrefService
;
454 var prefs
= Components
.classes
[prefSvcContractID
]
455 .getService(prefSvcIID
).getBranch("browser.download.");
457 const nsILocalFile
= Components
.interfaces
.nsILocalFile
;
459 // For information on download folder preferences, see
460 // mozilla/browser/components/preferences/main.js
462 var useDownloadDir
= prefs
.getBoolPref("useDownloadDir");
465 // Default to lastDir if useDownloadDir is false, and lastDir
466 // is configured and valid. Otherwise, use the user's default
467 // downloads directory configured through download prefs.
468 var dnldMgr
= Components
.classes
["@mozilla.org/download-manager;1"]
469 .getService(Components
.interfaces
.nsIDownloadManager
);
471 var lastDir
= prefs
.getComplexValue("lastDir", nsILocalFile
);
472 if ((!aSkipPrompt
|| !useDownloadDir
) && lastDir
.exists())
475 dir
= dnldMgr
.userDownloadsDirectory
;
477 dir
= dnldMgr
.userDownloadsDirectory
;
480 if (!aSkipPrompt
|| !useDownloadDir
|| !dir
|| (dir
&& !dir
.exists())) {
481 if (!dir
|| (dir
&& !dir
.exists())) {
482 // Default to desktop.
483 var fileLocator
= Components
.classes
["@mozilla.org/file/directory_service;1"]
484 .getService(Components
.interfaces
.nsIProperties
);
485 dir
= fileLocator
.get("Desk", nsILocalFile
);
488 var fp
= makeFilePicker();
489 var titleKey
= aFpP
.fpTitleKey
|| "SaveLinkTitle";
490 var bundle
= getStringBundle();
491 fp
.init(window
, bundle
.GetStringFromName(titleKey
),
492 Components
.interfaces
.nsIFilePicker
.modeSave
);
494 fp
.defaultExtension
= aFpP
.fileInfo
.fileExt
;
495 fp
.defaultString
= getNormalizedLeafName(aFpP
.fileInfo
.fileName
,
496 aFpP
.fileInfo
.fileExt
);
497 appendFiltersForContentType(fp
, aFpP
.contentType
, aFpP
.fileInfo
.fileExt
,
501 fp
.displayDirectory
= dir
;
503 if (aFpP
.isDocument
) {
505 fp
.filterIndex
= prefs
.getIntPref("save_converter_index");
511 if (fp
.show() == Components
.interfaces
.nsIFilePicker
.returnCancel
|| !fp
.file
)
514 var directory
= fp
.file
.parent
.QueryInterface(nsILocalFile
);
515 prefs
.setComplexValue("lastDir", nsILocalFile
, directory
);
517 fp
.file
.leafName
= validateFileName(fp
.file
.leafName
);
518 aFpP
.saveAsType
= fp
.filterIndex
;
520 aFpP
.fileURL
= fp
.fileURL
;
523 prefs
.setIntPref("save_converter_index", aFpP
.saveAsType
);
526 dir
.append(getNormalizedLeafName(aFpP
.fileInfo
.fileName
,
527 aFpP
.fileInfo
.fileExt
));
530 // Since we're automatically downloading, we don't get the file picker's
531 // logic to check for existing files, so we need to do that here.
533 // Note - this code is identical to that in
534 // mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
535 // If you are updating this code, update that code too! We can't share code
536 // here since that code is called in a js component.
537 var collisionCount
= 0;
538 while (file
.exists()) {
540 if (collisionCount
== 1) {
541 // Append "(2)" before the last dot in (or at the end of) the filename
542 // special case .ext.gz etc files so we don't wind up with .tar(2).gz
543 if (file
.leafName
.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
544 file
.leafName
= file
.leafName
.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
546 file
.leafName
= file
.leafName
.replace(/(\.[^\.]*)?$/, "(2)$&");
549 // replace the last (n) in the filename with (n+1)
550 file
.leafName
= file
.leafName
.replace(/^(.*\()\d+\)/, "$1" + (collisionCount
+1) + ")");
559 // We have no DOM, and can only save the URL as is.
560 const SAVEMODE_FILEONLY
= 0x00;
561 // We have a DOM and can save as complete.
562 const SAVEMODE_COMPLETE_DOM
= 0x01;
563 // We have a DOM which we can serialize as text.
564 const SAVEMODE_COMPLETE_TEXT
= 0x02;
566 // If we are able to save a complete DOM, the 'save as complete' filter
567 // must be the first filter appended. The 'save page only' counterpart
568 // must be the second filter appended. And the 'save as complete text'
569 // filter must be the third filter appended.
570 function appendFiltersForContentType(aFilePicker
, aContentType
, aFileExtension
, aSaveMode
)
572 var bundle
= getStringBundle();
573 // The bundle name for saving only a specific content type.
575 // The corresponding filter string for a specific content type.
578 // XXX all the cases that are handled explicitly here MUST be handled
579 // in GetSaveModeForContentType to return a non-fileonly filter.
580 switch (aContentType
) {
582 bundleName
= "WebPageHTMLOnlyFilter";
583 filterString
= "*.htm; *.html";
586 case "application/xhtml+xml":
587 bundleName
= "WebPageXHTMLOnlyFilter";
588 filterString
= "*.xht; *.xhtml";
591 case "image/svg+xml":
592 bundleName
= "WebPageSVGOnlyFilter";
593 filterString
= "*.svg; *.svgz";
597 case "application/xml":
598 bundleName
= "WebPageXMLOnlyFilter";
599 filterString
= "*.xml";
603 if (aSaveMode
!= SAVEMODE_FILEONLY
)
604 throw "Invalid save mode for type '" + aContentType
+ "'";
606 var mimeInfo
= getMIMEInfoForType(aContentType
, aFileExtension
);
609 var extEnumerator
= mimeInfo
.getFileExtensions();
612 while (extEnumerator
.hasMore()) {
613 var extension
= extEnumerator
.getNext();
615 extString
+= "; "; // If adding more than one extension,
616 // separate by semi-colon
617 extString
+= "*." + extension
;
621 aFilePicker
.appendFilter(mimeInfo
.description
, extString
);
627 if (aSaveMode
& SAVEMODE_COMPLETE_DOM
) {
628 aFilePicker
.appendFilter(bundle
.GetStringFromName("WebPageCompleteFilter"), filterString
);
629 // We should always offer a choice to save document only if
630 // we allow saving as complete.
631 aFilePicker
.appendFilter(bundle
.GetStringFromName(bundleName
), filterString
);
634 if (aSaveMode
& SAVEMODE_COMPLETE_TEXT
)
635 aFilePicker
.appendFilters(Components
.interfaces
.nsIFilePicker
.filterText
);
637 // Always append the all files (*) filter
638 aFilePicker
.appendFilters(Components
.interfaces
.nsIFilePicker
.filterAll
);
641 function getPostData()
644 var sessionHistory
= getWebNavigation().sessionHistory
;
645 return sessionHistory
.getEntryAtIndex(sessionHistory
.index
, false)
646 .QueryInterface(Components
.interfaces
.nsISHEntry
)
654 function getStringBundle()
656 return Components
.classes
["@mozilla.org/intl/stringbundle;1"]
657 .getService(Components
.interfaces
.nsIStringBundleService
)
658 .createBundle("chrome://global/locale/contentAreaCommands.properties");
661 function makeWebBrowserPersist()
663 const persistContractID
= "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
664 const persistIID
= Components
.interfaces
.nsIWebBrowserPersist
;
665 return Components
.classes
[persistContractID
].createInstance(persistIID
);
669 * Constructs a new URI, using nsIIOService.
670 * @param aURL The URI spec.
671 * @param aOriginCharset The charset of the URI.
672 * @param aBaseURI Base URI to resolve aURL, or null.
673 * @return an nsIURI object based on aURL.
675 function makeURI(aURL
, aOriginCharset
, aBaseURI
)
677 var ioService
= Components
.classes
["@mozilla.org/network/io-service;1"]
678 .getService(Components
.interfaces
.nsIIOService
);
679 return ioService
.newURI(aURL
, aOriginCharset
, aBaseURI
);
682 function makeFileURI(aFile
)
684 var ioService
= Components
.classes
["@mozilla.org/network/io-service;1"]
685 .getService(Components
.interfaces
.nsIIOService
);
686 return ioService
.newFileURI(aFile
);
689 function makeFilePicker()
691 const fpContractID
= "@mozilla.org/filepicker;1";
692 const fpIID
= Components
.interfaces
.nsIFilePicker
;
693 return Components
.classes
[fpContractID
].createInstance(fpIID
);
696 function getMIMEService()
698 const mimeSvcContractID
= "@mozilla.org/mime;1";
699 const mimeSvcIID
= Components
.interfaces
.nsIMIMEService
;
700 const mimeSvc
= Components
.classes
[mimeSvcContractID
].getService(mimeSvcIID
);
704 // Given aFileName, find the fileName without the extension on the end.
705 function getFileBaseName(aFileName
)
707 // Remove the file extension from aFileName:
708 return aFileName
.replace(/\.[^.]*$/, "");
711 function getMIMETypeForURI(aURI
)
714 return getMIMEService().getTypeFromURI(aURI
);
721 function getMIMEInfoForType(aMIMEType
, aExtension
)
723 if (aMIMEType
|| aExtension
) {
725 return getMIMEService().getFromTypeAndExtension(aMIMEType
, aExtension
);
733 function getDefaultFileName(aDefaultFileName
, aURI
, aDocument
,
736 // 1) look for a filename in the content-disposition header, if any
737 if (aContentDisposition
) {
738 const mhpContractID
= "@mozilla.org/network/mime-hdrparam;1";
739 const mhpIID
= Components
.interfaces
.nsIMIMEHeaderParam
;
740 const mhp
= Components
.classes
[mhpContractID
].getService(mhpIID
);
741 var dummy
= { value
: null }; // Need an out param...
742 var charset
= getCharsetforSave(aDocument
);
746 fileName
= mhp
.getParameter(aContentDisposition
, "filename", charset
,
751 fileName
= mhp
.getParameter(aContentDisposition
, "name", charset
, true,
762 var url
= aURI
.QueryInterface(Components
.interfaces
.nsIURL
);
763 if (url
.fileName
!= "") {
764 // 2) Use the actual file name, if present
765 var textToSubURI
= Components
.classes
["@mozilla.org/intl/texttosuburi;1"]
766 .getService(Components
.interfaces
.nsITextToSubURI
);
767 return validateFileName(textToSubURI
.unEscapeURIForUI(url
.originCharset
|| "UTF-8", url
.fileName
));
770 // This is something like a data: and so forth URI... no filename here.
774 var docTitle
= validateFileName(aDocument
.title
).replace(/^\s+|\s+$/g, "");
776 // 3) Use the document title
781 if (aDefaultFileName
)
782 // 4) Use the caller-provided name, if any
783 return validateFileName(aDefaultFileName
);
785 // 5) If this is a directory, use the last directory name
786 var path
= aURI
.path
.match(/\/([^\/]+)\/$/);
787 if (path
&& path
.length
> 1)
788 return validateFileName(path
[1]);
795 // Some files have no information at all, like Javascript generated pages
798 // 7) Use the default file name
799 return getStringBundle().GetStringFromName("DefaultSaveFileName");
801 //in case localized string cannot be found
803 // 8) If all else fails, use "index"
807 function validateFileName(aFileName
)
810 if (navigator
.appVersion
.indexOf("Windows") != -1) {
812 aFileName
= aFileName
.replace(/[\"]+/g, "'");
813 aFileName
= aFileName
.replace(/[\*\:\?]+/g, " ");
814 aFileName
= aFileName
.replace(/[\<]+/g, "(");
815 aFileName
= aFileName
.replace(/[\>]+/g, ")");
817 else if (navigator
.appVersion
.indexOf("Macintosh") != -1)
820 return aFileName
.replace(re
, "_");
823 function getNormalizedLeafName(aFile
, aDefaultExtension
)
825 if (!aDefaultExtension
)
829 // Remove trailing dots and spaces on windows
830 aFile
= aFile
.replace(/[\s.]+$/, "");
833 // Remove leading dots
834 aFile
= aFile
.replace(/^\.+/, "");
836 // Fix up the file name we're saving to to include the default extension
837 var i
= aFile
.lastIndexOf(".");
838 if (aFile
.substr(i
+ 1) != aDefaultExtension
)
839 return aFile
+ "." + aDefaultExtension
;
844 function getDefaultExtension(aFilename
, aURI
, aContentType
)
846 if (aContentType
== "text/plain" || aContentType
== "application/octet-stream" || aURI
.scheme
== "ftp")
847 return ""; // temporary fix for bug 120327
849 // First try the extension from the filename
850 const stdURLContractID
= "@mozilla.org/network/standard-url;1";
851 const stdURLIID
= Components
.interfaces
.nsIURL
;
852 var url
= Components
.classes
[stdURLContractID
].createInstance(stdURLIID
);
853 url
.filePath
= aFilename
;
855 var ext
= url
.fileExtension
;
857 // This mirrors some code in nsExternalHelperAppService::DoContent
858 // Use the filename first and then the URI if that fails
860 var mimeInfo
= getMIMEInfoForType(aContentType
, ext
);
862 if (ext
&& mimeInfo
&& mimeInfo
.extensionExists(ext
))
865 // Well, that failed. Now try the extension from the URI
868 url
= aURI
.QueryInterface(Components
.interfaces
.nsIURL
);
869 urlext
= url
.fileExtension
;
873 if (urlext
&& mimeInfo
&& mimeInfo
.extensionExists(urlext
)) {
879 return mimeInfo
.primaryExtension
;
883 // Fall back on the extensions in the filename and URI for lack
884 // of anything better.
885 return ext
|| urlext
;
889 function GetSaveModeForContentType(aContentType
)
891 var saveMode
= SAVEMODE_FILEONLY
;
892 switch (aContentType
) {
894 case "application/xhtml+xml":
895 case "image/svg+xml":
896 saveMode
|= SAVEMODE_COMPLETE_TEXT
;
899 case "application/xml":
900 saveMode
|= SAVEMODE_COMPLETE_DOM
;
907 function getCharsetforSave(aDocument
)
910 return aDocument
.characterSet
;
912 if (document
.commandDispatcher
.focusedWindow
)
913 return document
.commandDispatcher
.focusedWindow
.document
.characterSet
;
915 return window
.content
.document
.characterSet
;
919 * Open a URL from chrome, determining if we can handle it internally or need to
920 * launch an external application to handle it.
921 * @param aURL The URL to be opened
923 function openURL(aURL
)
925 var ios
= Components
.classes
["@mozilla.org/network/io-service;1"]
926 .getService(Components
.interfaces
.nsIIOService
);
927 var uri
= ios
.newURI(aURL
, null, null);
929 var protocolSvc
= Components
.classes
["@mozilla.org/uriloader/external-protocol-service;1"]
930 .getService(Components
.interfaces
.nsIExternalProtocolService
);
932 if (!protocolSvc
.isExposedProtocol(uri
.scheme
)) {
933 // If we're not a browser, use the external protocol service to load the URI.
934 protocolSvc
.loadUrl(uri
);
937 var loadgroup
= Components
.classes
["@mozilla.org/network/load-group;1"]
938 .createInstance(Components
.interfaces
.nsILoadGroup
);
939 var appstartup
= Components
.classes
["@mozilla.org/toolkit/app-startup;1"]
940 .getService(Components
.interfaces
.nsIAppStartup
);
943 onStartRequest
: function ll_start(aRequest
, aContext
) {
944 appstartup
.enterLastWindowClosingSurvivalArea();
946 onStopRequest
: function ll_stop(aRequest
, aContext
, aStatusCode
) {
947 appstartup
.exitLastWindowClosingSurvivalArea();
949 QueryInterface
: function ll_QI(iid
) {
950 if (iid
.equals(Components
.interfaces
.nsISupports
) ||
951 iid
.equals(Components
.interfaces
.nsIRequestObserver
) ||
952 iid
.equals(Components
.interfaces
.nsISupportsWeakReference
))
954 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
957 loadgroup
.groupObserver
= loadListener
;
960 onStartURIOpen: function(uri
) { return false; },
961 doContent: function(ctype
, preferred
, request
, handler
) { return false; },
962 isPreferred: function(ctype
, desired
) { return false; },
963 canHandleContent: function(ctype
, preferred
, desired
) { return false; },
965 parentContentListener
: null,
966 getInterface: function(iid
) {
967 if (iid
.equals(Components
.interfaces
.nsIURIContentListener
))
969 if (iid
.equals(Components
.interfaces
.nsILoadGroup
))
971 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
975 var channel
= ios
.newChannelFromURI(uri
);
976 var uriLoader
= Components
.classes
["@mozilla.org/uriloader;1"]
977 .getService(Components
.interfaces
.nsIURILoader
);
978 uriLoader
.openURI(channel
, true, uriListener
);