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(aDocument) : 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(aDocument)
644 var sessionHistory = aDocument.defaultView
645 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
646 .getInterface(Components.interfaces.nsIWebNavigation)
648 return sessionHistory.getEntryAtIndex(sessionHistory.index, false)
649 .QueryInterface(Components.interfaces.nsISHEntry)
657 function getStringBundle()
659 const bundleURL = "chrome://global/locale/contentAreaCommands.properties";
661 const sbsContractID = "@mozilla.org/intl/stringbundle;1";
662 const sbsIID = Components.interfaces.nsIStringBundleService;
663 const sbs = Components.classes[sbsContractID].getService(sbsIID);
665 const lsContractID = "@mozilla.org/intl/nslocaleservice;1";
666 const lsIID = Components.interfaces.nsILocaleService;
667 const ls = Components.classes[lsContractID].getService(lsIID);
668 var appLocale = ls.getApplicationLocale();
669 return sbs.createBundle(bundleURL, appLocale);
672 function makeWebBrowserPersist()
674 const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
675 const persistIID = Components.interfaces.nsIWebBrowserPersist;
676 return Components.classes[persistContractID].createInstance(persistIID);
680 * Constructs a new URI, using nsIIOService.
681 * @param aURL The URI spec.
682 * @param aOriginCharset The charset of the URI.
683 * @param aBaseURI Base URI to resolve aURL, or null.
684 * @return an nsIURI object based on aURL.
686 function makeURI(aURL, aOriginCharset, aBaseURI)
688 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
689 .getService(Components.interfaces.nsIIOService);
690 return ioService.newURI(aURL, aOriginCharset, aBaseURI);
693 function makeFileURI(aFile)
695 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
696 .getService(Components.interfaces.nsIIOService);
697 return ioService.newFileURI(aFile);
700 function makeFilePicker()
702 const fpContractID = "@mozilla.org/filepicker;1";
703 const fpIID = Components.interfaces.nsIFilePicker;
704 return Components.classes[fpContractID].createInstance(fpIID);
707 function getMIMEService()
709 const mimeSvcContractID = "@mozilla.org/mime;1";
710 const mimeSvcIID = Components.interfaces.nsIMIMEService;
711 const mimeSvc = Components.classes[mimeSvcContractID].getService(mimeSvcIID);
715 // Given aFileName, find the fileName without the extension on the end.
716 function getFileBaseName(aFileName)
718 // Remove the file extension from aFileName:
719 return aFileName.replace(/\.[^.]*$/, "");
722 function getMIMETypeForURI(aURI)
725 return getMIMEService().getTypeFromURI(aURI);
732 function getMIMEInfoForType(aMIMEType, aExtension)
734 if (aMIMEType || aExtension) {
736 return getMIMEService().getFromTypeAndExtension(aMIMEType, aExtension);
744 function getDefaultFileName(aDefaultFileName, aURI, aDocument,
747 // 1) look for a filename in the content-disposition header, if any
748 if (aContentDisposition) {
749 const mhpContractID = "@mozilla.org/network/mime-hdrparam;1";
750 const mhpIID = Components.interfaces.nsIMIMEHeaderParam;
751 const mhp = Components.classes[mhpContractID].getService(mhpIID);
752 var dummy = { value: null }; // Need an out param...
753 var charset = getCharsetforSave(aDocument);
757 fileName = mhp.getParameter(aContentDisposition, "filename", charset,
762 fileName = mhp.getParameter(aContentDisposition, "name", charset, true,
773 var url = aURI.QueryInterface(Components.interfaces.nsIURL);
774 if (url.fileName != "") {
775 // 2) Use the actual file name, if present
776 var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
777 .getService(Components.interfaces.nsITextToSubURI);
778 return validateFileName(textToSubURI.unEscapeURIForUI(url.originCharset || "UTF-8", url.fileName));
781 // This is something like a data: and so forth URI... no filename here.
785 var docTitle = validateFileName(aDocument.title).replace(/^\s+|\s+$/g, "");
787 // 3) Use the document title
792 if (aDefaultFileName)
793 // 4) Use the caller-provided name, if any
794 return validateFileName(aDefaultFileName);
796 // 5) If this is a directory, use the last directory name
797 var path = aURI.path.match(/\/([^\/]+)\/$/);
798 if (path && path.length > 1)
799 return validateFileName(path[1]);
806 // Some files have no information at all, like Javascript generated pages
809 // 7) Use the default file name
810 return getStringBundle().GetStringFromName("DefaultSaveFileName");
812 //in case localized string cannot be found
814 // 8) If all else fails, use "index"
818 function validateFileName(aFileName)
821 if (navigator.appVersion.indexOf("Windows") != -1) {
823 aFileName = aFileName.replace(/[\"]+/g, "'");
824 aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
825 aFileName = aFileName.replace(/[\<]+/g, "(");
826 aFileName = aFileName.replace(/[\>]+/g, ")");
828 else if (navigator.appVersion.indexOf("Macintosh") != -1)
831 return aFileName.replace(re, "_");
834 function getNormalizedLeafName(aFile, aDefaultExtension)
836 if (!aDefaultExtension)
840 // Remove trailing dots and spaces on windows
841 aFile = aFile.replace(/[\s.]+$/, "");
844 // Remove leading dots
845 aFile = aFile.replace(/^\.+/, "");
847 // Fix up the file name we're saving to to include the default extension
848 var i = aFile.lastIndexOf(".");
849 if (aFile.substr(i + 1) != aDefaultExtension)
850 return aFile + "." + aDefaultExtension;
855 function getDefaultExtension(aFilename, aURI, aContentType)
857 if (aContentType == "text/plain" || aContentType == "application/octet-stream" || aURI.scheme == "ftp")
858 return ""; // temporary fix for bug 120327
860 // First try the extension from the filename
861 const stdURLContractID = "@mozilla.org/network/standard-url;1";
862 const stdURLIID = Components.interfaces.nsIURL;
863 var url = Components.classes[stdURLContractID].createInstance(stdURLIID);
864 url.filePath = aFilename;
866 var ext = url.fileExtension;
868 // This mirrors some code in nsExternalHelperAppService::DoContent
869 // Use the filename first and then the URI if that fails
871 var mimeInfo = getMIMEInfoForType(aContentType, ext);
873 if (ext && mimeInfo && mimeInfo.extensionExists(ext))
876 // Well, that failed. Now try the extension from the URI
879 url = aURI.QueryInterface(Components.interfaces.nsIURL);
880 urlext = url.fileExtension;
884 if (urlext && mimeInfo && mimeInfo.extensionExists(urlext)) {
890 return mimeInfo.primaryExtension;
894 // Fall back on the extensions in the filename and URI for lack
895 // of anything better.
896 return ext || urlext;
900 function GetSaveModeForContentType(aContentType)
902 var saveMode = SAVEMODE_FILEONLY;
903 switch (aContentType) {
905 case "application/xhtml+xml":
906 case "image/svg+xml":
907 saveMode |= SAVEMODE_COMPLETE_TEXT;
910 case "application/xml":
911 saveMode |= SAVEMODE_COMPLETE_DOM;
918 function getCharsetforSave(aDocument)
921 return aDocument.characterSet;
923 if (document.commandDispatcher.focusedWindow)
924 return document.commandDispatcher.focusedWindow.document.characterSet;
926 return window.content.document.characterSet;
930 * Open a URL from chrome, determining if we can handle it internally or need to
931 * launch an external application to handle it.
932 * @param aURL The URL to be opened
934 function openURL(aURL)
936 var ios = Components.classes["@mozilla.org/network/io-service;1"]
937 .getService(Components.interfaces.nsIIOService);
938 var uri = ios.newURI(aURL, null, null);
940 var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
941 .getService(Components.interfaces.nsIExternalProtocolService);
943 if (!protocolSvc.isExposedProtocol(uri.scheme)) {
944 // If we're not a browser, use the external protocol service to load the URI.
945 protocolSvc.loadUrl(uri);
948 var loadgroup = Components.classes["@mozilla.org/network/load-group;1"]
949 .createInstance(Components.interfaces.nsILoadGroup);
950 var appstartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
951 .getService(Components.interfaces.nsIAppStartup);
954 onStartRequest: function ll_start(aRequest, aContext) {
955 appstartup.enterLastWindowClosingSurvivalArea();
957 onStopRequest: function ll_stop(aRequest, aContext, aStatusCode) {
958 appstartup.exitLastWindowClosingSurvivalArea();
960 QueryInterface: function ll_QI(iid) {
961 if (iid.equals(Components.interfaces.nsISupports) ||
962 iid.equals(Components.interfaces.nsIRequestObserver) ||
963 iid.equals(Components.interfaces.nsISupportsWeakReference))
965 throw Components.results.NS_ERROR_NO_INTERFACE;
968 loadgroup.groupObserver = loadListener;
971 onStartURIOpen: function(uri) { return false; },
972 doContent: function(ctype, preferred, request, handler) { return false; },
973 isPreferred: function(ctype, desired) { return false; },
974 canHandleContent: function(ctype, preferred, desired) { return false; },
976 parentContentListener: null,
977 getInterface: function(iid) {
978 if (iid.equals(Components.interfaces.nsIURIContentListener))
980 if (iid.equals(Components.interfaces.nsILoadGroup))
982 throw Components.results.NS_ERROR_NO_INTERFACE;
986 var channel = ios.newChannelFromURI(uri);
987 var uriLoader = Components.classes["@mozilla.org/uriloader;1"]
988 .getService(Components.interfaces.nsIURILoader);
989 uriLoader.openURI(channel, true, uriListener);