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>
26 # Ehsan Akhgari <ehsan.akhgari@gmail.com>
28 # Alternatively, the contents of this file may be used under the terms of
29 # either the GNU General Public License Version 2 or later (the "GPL"), or
30 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 # in which case the provisions of the GPL or the LGPL are applicable instead
32 # of those above. If you wish to allow use of your version of this file only
33 # under the terms of either the GPL or the LGPL, and not to allow others to
34 # use your version of this file under the terms of the MPL, indicate your
35 # decision by deleting the provisions above and replace them with the notice
36 # and other provisions required by the GPL or the LGPL. If you do not delete
37 # the provisions above, a recipient may use your version of this file under
38 # the terms of any one of the MPL, the GPL or the LGPL.
40 # ***** END LICENSE BLOCK *****
43 * urlSecurityCheck: JavaScript wrapper for checkLoadURIWithPrincipal
44 * and checkLoadURIStrWithPrincipal.
45 * If |aPrincipal| is not allowed to link to |aURL|, this function throws with
49 * The URL a page has linked to. This could be passed either as a string
50 * or as a nsIURI object.
52 * The principal of the document from which aURL came.
54 * Flags to be passed to checkLoadURIStr. If undefined,
55 * nsIScriptSecurityManager.STANDARD will be passed.
57 function urlSecurityCheck(aURL, aPrincipal, aFlags)
59 const nsIScriptSecurityManager =
60 Components.interfaces.nsIScriptSecurityManager;
61 var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
62 .getService(nsIScriptSecurityManager);
63 if (aFlags === undefined)
64 aFlags = nsIScriptSecurityManager.STANDARD;
67 if (aURL instanceof Components.interfaces.nsIURI)
68 secMan.checkLoadURIWithPrincipal(aPrincipal, aURL, aFlags);
70 secMan.checkLoadURIStrWithPrincipal(aPrincipal, aURL, aFlags);
72 // XXXmano: dump the principal url here too
73 throw "Load of " + aURL + " denied.";
78 * Determine whether or not a given focused DOMWindow is in the content area.
80 function isContentFrame(aFocusedWindow)
85 return (aFocusedWindow.top == window.content);
89 // Clientelle: (Make sure you don't break any of these)
90 // - File -> Save Page/Frame As...
91 // - Context -> Save Page/Frame As...
92 // - Context -> Save Link As...
93 // - Alt-Click links in web pages
94 // - Alt-Click links in the UI
96 // Try saving each of these types:
97 // - A complete webpage using File->Save Page As, and Context->Save Page As
98 // - A webpage as HTML only using the above methods
99 // - A webpage as Text only using the above methods
100 // - An image with an extension (e.g. .jpg) in its file name, using
101 // Context->Save Image As...
102 // - An image without an extension (e.g. a banner ad on cnn.com) using
104 // - A linked document using Save Link As...
105 // - A linked document using Alt-click Save Link As...
107 function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
108 aSkipPrompt, aReferrer)
110 internalSave(aURL, null, aFileName, null, null, aShouldBypassCache,
111 aFilePickerTitleKey, null, aReferrer, aSkipPrompt, null);
114 // Just like saveURL, but will get some info off the image before
115 // calling internalSave
116 // Clientelle: (Make sure you don't break any of these)
117 // - Context -> Save Image As...
118 const imgICache = Components.interfaces.imgICache;
119 const nsISupportsCString = Components.interfaces.nsISupportsCString;
121 function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
122 aSkipPrompt, aReferrer)
124 var contentType = null;
125 var contentDisposition = null;
126 if (!aShouldBypassCache) {
128 var imageCache = Components.classes["@mozilla.org/image/cache;1"]
129 .getService(imgICache);
131 imageCache.findEntryProperties(makeURI(aURL, getCharsetforSave(null)));
133 contentType = props.get("type", nsISupportsCString);
134 contentDisposition = props.get("content-disposition",
138 // Failure to get type and content-disposition off the image is non-fatal
141 internalSave(aURL, null, aFileName, contentDisposition, contentType,
142 aShouldBypassCache, aFilePickerTitleKey, null, aReferrer,
146 function saveFrameDocument()
148 var focusedWindow = document.commandDispatcher.focusedWindow;
149 if (isContentFrame(focusedWindow))
150 saveDocument(focusedWindow.document);
153 function saveDocument(aDocument, aSkipPrompt)
156 throw "Must have a document when calling saveDocument";
158 // We want to use cached data because the document is currently visible.
160 aDocument.defaultView
161 .QueryInterface(Components.interfaces.nsIInterfaceRequestor);
163 var contentDisposition = null;
166 ifreq.getInterface(Components.interfaces.nsIDOMWindowUtils)
167 getDocumentMetadata("content-disposition");
169 // Failure to get a content-disposition is ok
175 ifreq.getInterface(Components.interfaces.nsIWebNavigation)
176 .QueryInterface(Components.interfaces.nsIWebPageDescriptor);
178 // We might not find it in the cache. Oh, well.
181 internalSave(aDocument.location.href, aDocument, null, contentDisposition,
182 aDocument.contentType, false, null, null,
183 aDocument.referrer ? makeURI(aDocument.referrer) : null,
184 aSkipPrompt, cacheKey);
187 function DownloadListener(win, transfer) {
188 function makeClosure(name) {
190 transfer[name].apply(transfer, arguments);
196 // Now... we need to forward all calls to our transfer
197 for (var i in transfer) {
198 if (i != "QueryInterface")
199 this[i] = makeClosure(i);
203 DownloadListener.prototype = {
204 QueryInterface: function dl_qi(aIID)
206 if (aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
207 aIID.equals(Components.interfaces.nsIWebProgressListener) ||
208 aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
209 aIID.equals(Components.interfaces.nsISupports)) {
212 throw Components.results.NS_ERROR_NO_INTERFACE;
215 getInterface: function dl_gi(aIID)
217 if (aIID.equals(Components.interfaces.nsIAuthPrompt) ||
218 aIID.equals(Components.interfaces.nsIAuthPrompt2)) {
220 Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
221 .getService(Components.interfaces.nsIPromptFactory);
222 return ww.getPrompt(this.window, aIID);
225 throw Components.results.NS_ERROR_NO_INTERFACE;
229 const kSaveAsType_Complete = 0; // Save document with attached objects.
230 // const kSaveAsType_URL = 1; // Save document or URL by itself.
231 const kSaveAsType_Text = 2; // Save document, converting to plain text.
234 * internalSave: Used when saving a document or URL. This method:
235 * - Determines a local target filename to use (unless parameter
236 * aChosenData is non-null)
237 * - Determines content-type if possible
238 * - Prompts the user to confirm the destination filename and save mode
239 * (content-type affects this)
240 * - Creates a 'Persist' object (which will perform the saving in the
241 * background) and then starts it.
244 * The String representation of the URL of the document being saved
246 * The document to be saved
247 * @param aDefaultFileName
248 * The caller-provided suggested filename if we don't
250 * @param aContentDisposition
251 * The caller-provided content-disposition header to use.
252 * @param aContentType
253 * The caller-provided content-type to use
254 * @param aShouldBypassCache
255 * If true, the document will always be refetched from the server
256 * @param aFilePickerTitleKey
257 * Alternate title for the file picker
259 * If non-null this contains an instance of object AutoChosen (see below)
260 * which holds pre-determined data so that the user does not need to be
261 * prompted for a target filename.
263 * the referrer URI object (not URL string) to use, or null
264 * if no referrer should be sent.
265 * @param aSkipPrompt [optional]
266 * If set to true, we will attempt to save the file to the
267 * default downloads folder without prompting.
268 * @param aCacheKey [optional]
269 * If set will be passed to saveURI. See nsIWebBrowserPersist for
272 function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
273 aContentType, aShouldBypassCache, aFilePickerTitleKey,
274 aChosenData, aReferrer, aSkipPrompt, aCacheKey)
276 if (aSkipPrompt == undefined)
279 if (aCacheKey == undefined)
282 // Note: aDocument == null when this code is used by save-link-as...
283 var saveMode = GetSaveModeForContentType(aContentType);
284 var isDocument = aDocument != null && saveMode != SAVEMODE_FILEONLY;
285 var saveAsType = kSaveAsType_Complete;
288 // Find the URI object for aURL and the FileName/Extension to use when saving.
289 // FileName/Extension will be ignored if aChosenData supplied.
290 var fileInfo = new FileInfo(aDefaultFileName);
292 file = aChosenData.file;
296 charset = aDocument.characterSet;
298 charset = aReferrer.originCharset;
299 initFileInfo(fileInfo, aURL, charset, aDocument,
300 aContentType, aContentDisposition);
302 fpTitleKey: aFilePickerTitleKey,
303 isDocument: isDocument,
305 contentType: aContentType,
307 saveAsType: saveAsType,
312 if (!getTargetFile(fpParams, aSkipPrompt))
313 // If the method returned false this is because the user cancelled from
314 // the save file picker dialog.
317 saveAsType = fpParams.saveAsType;
318 saveMode = fpParams.saveMode;
319 file = fpParams.file;
320 fileURL = fpParams.fileURL;
324 fileURL = makeFileURI(file);
326 // XXX We depend on the following holding true in appendFiltersForContentType():
327 // If we should save as a complete page, the saveAsType is kSaveAsType_Complete.
328 // If we should save as text, the saveAsType is kSaveAsType_Text.
329 var useSaveDocument = isDocument &&
330 (((saveMode & SAVEMODE_COMPLETE_DOM) && (saveAsType == kSaveAsType_Complete)) ||
331 ((saveMode & SAVEMODE_COMPLETE_TEXT) && (saveAsType == kSaveAsType_Text)));
332 // If we're saving a document, and are saving either in complete mode or
333 // as converted text, pass the document to the web browser persist component.
334 // If we're just saving the HTML (second option in the list), send only the URI.
335 var source = useSaveDocument ? aDocument : fileInfo.uri;
338 contentType : (!aChosenData && useSaveDocument &&
339 saveAsType == kSaveAsType_Text) ?
342 postData : isDocument ? getPostData() : null,
343 bypassCache : aShouldBypassCache
346 var persist = makeWebBrowserPersist();
348 // Calculate persist flags.
349 const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
350 const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
351 if (aShouldBypassCache)
352 persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
354 persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
356 // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof):
357 persist.persistFlags |= nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
359 // Create download and initiate it (below)
360 var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer);
362 if (useSaveDocument) {
363 // Saving a Document, not a URI:
364 var filesFolder = null;
365 if (persistArgs.contentType != "text/plain") {
366 // Create the local directory into which to save associated files.
367 filesFolder = file.clone();
369 var nameWithoutExtension = getFileBaseName(filesFolder.leafName);
370 var filesFolderLeafName = getStringBundle().formatStringFromName("filesFolder",
371 [nameWithoutExtension],
374 filesFolder.leafName = filesFolderLeafName;
377 var encodingFlags = 0;
378 if (persistArgs.contentType == "text/plain") {
379 encodingFlags |= nsIWBP.ENCODE_FLAGS_FORMATTED;
380 encodingFlags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS;
381 encodingFlags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT;
384 encodingFlags |= nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
387 const kWrapColumn = 80;
388 tr.init((aChosenData ? aChosenData.uri : fileInfo.uri),
389 persistArgs.target, "", null, null, null, persist);
390 persist.progressListener = new DownloadListener(window, tr);
391 persist.saveDocument(persistArgs.source, persistArgs.target, filesFolder,
392 persistArgs.contentType, encodingFlags, kWrapColumn);
394 tr.init((aChosenData ? aChosenData.uri : source),
395 persistArgs.target, "", null, null, null, persist);
396 persist.progressListener = new DownloadListener(window, tr);
397 persist.saveURI((aChosenData ? aChosenData.uri : source),
398 aCacheKey, aReferrer, persistArgs.postData, null,
404 * Structure for holding info about automatically supplied parameters for
405 * internalSave(...). This allows parameters to be supplied so the user does not
406 * need to be prompted for file info.
407 * @param aFileAutoChosen This is an nsILocalFile object that has been
408 * pre-determined as the filename for the target to save to
409 * @param aUriAutoChosen This is the nsIURI object for the target
411 function AutoChosen(aFileAutoChosen, aUriAutoChosen) {
412 this.file = aFileAutoChosen;
413 this.uri = aUriAutoChosen;
417 * Structure for holding info about a URL and the target filename it should be
418 * saved to. This structure is populated by initFileInfo(...).
419 * @param aSuggestedFileName This is used by initFileInfo(...) when it
420 * cannot 'discover' the filename from the url
421 * @param aFileName The target filename
422 * @param aFileBaseName The filename without the file extension
423 * @param aFileExt The extension of the filename
424 * @param aUri An nsIURI object for the url that is being saved
426 function FileInfo(aSuggestedFileName, aFileName, aFileBaseName, aFileExt, aUri) {
427 this.suggestedFileName = aSuggestedFileName;
428 this.fileName = aFileName;
429 this.fileBaseName = aFileBaseName;
430 this.fileExt = aFileExt;
435 * Determine what the 'default' filename string is, its file extension and the
436 * filename without the extension. This filename is used when prompting the user
437 * for confirmation in the file picker dialog.
438 * @param aFI A FileInfo structure into which we'll put the results of this method.
439 * @param aURL The String representation of the URL of the document being saved
440 * @param aURLCharset The charset of aURL.
441 * @param aDocument The document to be saved
442 * @param aContentType The content type we're saving, if it could be
443 * determined by the caller.
444 * @param aContentDisposition The content-disposition header for the object
445 * we're saving, if it could be determined by the caller.
447 function initFileInfo(aFI, aURL, aURLCharset, aDocument,
448 aContentType, aContentDisposition)
451 // Get an nsIURI object from aURL if possible:
453 aFI.uri = makeURI(aURL, aURLCharset);
454 // Assuming nsiUri is valid, calling QueryInterface(...) on it will
455 // populate extra object fields (eg filename and file extension).
456 var url = aFI.uri.QueryInterface(Components.interfaces.nsIURL);
457 aFI.fileExt = url.fileExtension;
461 // Get the default filename:
462 aFI.fileName = getDefaultFileName((aFI.suggestedFileName || aFI.fileName),
463 aFI.uri, aDocument, aContentDisposition);
464 // If aFI.fileExt is still blank, consider: aFI.suggestedFileName is supplied
465 // if saveURL(...) was the original caller (hence both aContentType and
466 // aDocument are blank). If they were saving a link to a website then make
467 // the extension .htm .
468 if (!aFI.fileExt && !aDocument && !aContentType && (/^http(s?):\/\//i.test(aURL))) {
470 aFI.fileBaseName = aFI.fileName;
472 aFI.fileExt = getDefaultExtension(aFI.fileName, aFI.uri, aContentType);
473 aFI.fileBaseName = getFileBaseName(aFI.fileName);
479 Components.utils.import("resource://gre/modules/DownloadLastDir.jsm");
481 function getTargetFile(aFpP, /* optional */ aSkipPrompt)
483 const prefSvcContractID = "@mozilla.org/preferences-service;1";
484 const prefSvcIID = Components.interfaces.nsIPrefService;
485 var prefs = Components.classes[prefSvcContractID]
486 .getService(prefSvcIID).getBranch("browser.download.");
488 const nsILocalFile = Components.interfaces.nsILocalFile;
490 var inPrivateBrowsing = false;
492 var pbs = Components.classes["@mozilla.org/privatebrowsing;1"]
493 .getService(Components.interfaces.nsIPrivateBrowsingService);
494 inPrivateBrowsing = pbs.privateBrowsingEnabled;
499 // For information on download folder preferences, see
500 // mozilla/browser/components/preferences/main.js
502 var useDownloadDir = prefs.getBoolPref("useDownloadDir");
505 // Default to lastDir if useDownloadDir is false, and lastDir
506 // is configured and valid. Otherwise, use the user's default
507 // downloads directory configured through download prefs.
508 var dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
509 .getService(Components.interfaces.nsIDownloadManager);
511 var lastDir = prefs.getComplexValue("lastDir", nsILocalFile);
512 if (inPrivateBrowsing && gDownloadLastDir.path)
513 lastDir = gDownloadLastDir.path;
514 if ((!aSkipPrompt || !useDownloadDir) && lastDir.exists())
517 dir = dnldMgr.userDownloadsDirectory;
519 dir = dnldMgr.userDownloadsDirectory;
522 if (!aSkipPrompt || !useDownloadDir || !dir || (dir && !dir.exists())) {
523 if (!dir || (dir && !dir.exists())) {
524 // Default to desktop.
525 var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
526 .getService(Components.interfaces.nsIProperties);
527 dir = fileLocator.get("Desk", nsILocalFile);
530 var fp = makeFilePicker();
531 var titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
532 var bundle = getStringBundle();
533 fp.init(window, bundle.GetStringFromName(titleKey),
534 Components.interfaces.nsIFilePicker.modeSave);
536 fp.defaultExtension = aFpP.fileInfo.fileExt;
537 fp.defaultString = getNormalizedLeafName(aFpP.fileInfo.fileName,
538 aFpP.fileInfo.fileExt);
539 appendFiltersForContentType(fp, aFpP.contentType, aFpP.fileInfo.fileExt,
543 fp.displayDirectory = dir;
545 if (aFpP.isDocument) {
547 fp.filterIndex = prefs.getIntPref("save_converter_index");
553 if (fp.show() == Components.interfaces.nsIFilePicker.returnCancel || !fp.file)
556 // Do not store the last save directory as a pref inside the private browsing mode
557 var directory = fp.file.parent.QueryInterface(nsILocalFile);
558 if (inPrivateBrowsing)
559 gDownloadLastDir.path = directory;
561 prefs.setComplexValue("lastDir", nsILocalFile, directory);
563 fp.file.leafName = validateFileName(fp.file.leafName);
564 aFpP.saveAsType = fp.filterIndex;
566 aFpP.fileURL = fp.fileURL;
569 prefs.setIntPref("save_converter_index", aFpP.saveAsType);
572 dir.append(getNormalizedLeafName(aFpP.fileInfo.fileName,
573 aFpP.fileInfo.fileExt));
576 // Since we're automatically downloading, we don't get the file picker's
577 // logic to check for existing files, so we need to do that here.
579 // Note - this code is identical to that in
580 // mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
581 // If you are updating this code, update that code too! We can't share code
582 // here since that code is called in a js component.
583 var collisionCount = 0;
584 while (file.exists()) {
586 if (collisionCount == 1) {
587 // Append "(2)" before the last dot in (or at the end of) the filename
588 // special case .ext.gz etc files so we don't wind up with .tar(2).gz
589 if (file.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
590 file.leafName = file.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
592 file.leafName = file.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
595 // replace the last (n) in the filename with (n+1)
596 file.leafName = file.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
605 // We have no DOM, and can only save the URL as is.
606 const SAVEMODE_FILEONLY = 0x00;
607 // We have a DOM and can save as complete.
608 const SAVEMODE_COMPLETE_DOM = 0x01;
609 // We have a DOM which we can serialize as text.
610 const SAVEMODE_COMPLETE_TEXT = 0x02;
612 // If we are able to save a complete DOM, the 'save as complete' filter
613 // must be the first filter appended. The 'save page only' counterpart
614 // must be the second filter appended. And the 'save as complete text'
615 // filter must be the third filter appended.
616 function appendFiltersForContentType(aFilePicker, aContentType, aFileExtension, aSaveMode)
618 var bundle = getStringBundle();
619 // The bundle name for saving only a specific content type.
621 // The corresponding filter string for a specific content type.
624 // XXX all the cases that are handled explicitly here MUST be handled
625 // in GetSaveModeForContentType to return a non-fileonly filter.
626 switch (aContentType) {
628 bundleName = "WebPageHTMLOnlyFilter";
629 filterString = "*.htm; *.html";
632 case "application/xhtml+xml":
633 bundleName = "WebPageXHTMLOnlyFilter";
634 filterString = "*.xht; *.xhtml";
637 case "image/svg+xml":
638 bundleName = "WebPageSVGOnlyFilter";
639 filterString = "*.svg; *.svgz";
643 case "application/xml":
644 bundleName = "WebPageXMLOnlyFilter";
645 filterString = "*.xml";
649 if (aSaveMode != SAVEMODE_FILEONLY)
650 throw "Invalid save mode for type '" + aContentType + "'";
652 var mimeInfo = getMIMEInfoForType(aContentType, aFileExtension);
655 var extEnumerator = mimeInfo.getFileExtensions();
658 while (extEnumerator.hasMore()) {
659 var extension = extEnumerator.getNext();
661 extString += "; "; // If adding more than one extension,
662 // separate by semi-colon
663 extString += "*." + extension;
667 aFilePicker.appendFilter(mimeInfo.description, extString);
673 if (aSaveMode & SAVEMODE_COMPLETE_DOM) {
674 aFilePicker.appendFilter(bundle.GetStringFromName("WebPageCompleteFilter"), filterString);
675 // We should always offer a choice to save document only if
676 // we allow saving as complete.
677 aFilePicker.appendFilter(bundle.GetStringFromName(bundleName), filterString);
680 if (aSaveMode & SAVEMODE_COMPLETE_TEXT)
681 aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterText);
683 // Always append the all files (*) filter
684 aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
687 function getPostData()
690 var sessionHistory = getWebNavigation().sessionHistory;
691 return sessionHistory.getEntryAtIndex(sessionHistory.index, false)
692 .QueryInterface(Components.interfaces.nsISHEntry)
700 function getStringBundle()
702 return Components.classes["@mozilla.org/intl/stringbundle;1"]
703 .getService(Components.interfaces.nsIStringBundleService)
704 .createBundle("chrome://global/locale/contentAreaCommands.properties");
707 function makeWebBrowserPersist()
709 const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
710 const persistIID = Components.interfaces.nsIWebBrowserPersist;
711 return Components.classes[persistContractID].createInstance(persistIID);
715 * Constructs a new URI, using nsIIOService.
716 * @param aURL The URI spec.
717 * @param aOriginCharset The charset of the URI.
718 * @param aBaseURI Base URI to resolve aURL, or null.
719 * @return an nsIURI object based on aURL.
721 function makeURI(aURL, aOriginCharset, aBaseURI)
723 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
724 .getService(Components.interfaces.nsIIOService);
725 return ioService.newURI(aURL, aOriginCharset, aBaseURI);
728 function makeFileURI(aFile)
730 var ioService = Components.classes["@mozilla.org/network/io-service;1"]
731 .getService(Components.interfaces.nsIIOService);
732 return ioService.newFileURI(aFile);
735 function makeFilePicker()
737 const fpContractID = "@mozilla.org/filepicker;1";
738 const fpIID = Components.interfaces.nsIFilePicker;
739 return Components.classes[fpContractID].createInstance(fpIID);
742 function getMIMEService()
744 const mimeSvcContractID = "@mozilla.org/mime;1";
745 const mimeSvcIID = Components.interfaces.nsIMIMEService;
746 const mimeSvc = Components.classes[mimeSvcContractID].getService(mimeSvcIID);
750 // Given aFileName, find the fileName without the extension on the end.
751 function getFileBaseName(aFileName)
753 // Remove the file extension from aFileName:
754 return aFileName.replace(/\.[^.]*$/, "");
757 function getMIMETypeForURI(aURI)
760 return getMIMEService().getTypeFromURI(aURI);
767 function getMIMEInfoForType(aMIMEType, aExtension)
769 if (aMIMEType || aExtension) {
771 return getMIMEService().getFromTypeAndExtension(aMIMEType, aExtension);
779 function getDefaultFileName(aDefaultFileName, aURI, aDocument,
782 // 1) look for a filename in the content-disposition header, if any
783 if (aContentDisposition) {
784 const mhpContractID = "@mozilla.org/network/mime-hdrparam;1";
785 const mhpIID = Components.interfaces.nsIMIMEHeaderParam;
786 const mhp = Components.classes[mhpContractID].getService(mhpIID);
787 var dummy = { value: null }; // Need an out param...
788 var charset = getCharsetforSave(aDocument);
792 fileName = mhp.getParameter(aContentDisposition, "filename", charset,
797 fileName = mhp.getParameter(aContentDisposition, "name", charset, true,
808 var url = aURI.QueryInterface(Components.interfaces.nsIURL);
809 if (url.fileName != "") {
810 // 2) Use the actual file name, if present
811 var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
812 .getService(Components.interfaces.nsITextToSubURI);
813 return validateFileName(textToSubURI.unEscapeURIForUI(url.originCharset || "UTF-8", url.fileName));
816 // This is something like a data: and so forth URI... no filename here.
820 var docTitle = validateFileName(aDocument.title).replace(/^\s+|\s+$/g, "");
822 // 3) Use the document title
827 if (aDefaultFileName)
828 // 4) Use the caller-provided name, if any
829 return validateFileName(aDefaultFileName);
831 // 5) If this is a directory, use the last directory name
832 var path = aURI.path.match(/\/([^\/]+)\/$/);
833 if (path && path.length > 1)
834 return validateFileName(path[1]);
841 // Some files have no information at all, like Javascript generated pages
844 // 7) Use the default file name
845 return getStringBundle().GetStringFromName("DefaultSaveFileName");
847 //in case localized string cannot be found
849 // 8) If all else fails, use "index"
853 function validateFileName(aFileName)
856 if (navigator.appVersion.indexOf("Windows") != -1) {
858 aFileName = aFileName.replace(/[\"]+/g, "'");
859 aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
860 aFileName = aFileName.replace(/[\<]+/g, "(");
861 aFileName = aFileName.replace(/[\>]+/g, ")");
863 else if (navigator.appVersion.indexOf("Macintosh") != -1)
866 return aFileName.replace(re, "_");
869 function getNormalizedLeafName(aFile, aDefaultExtension)
871 if (!aDefaultExtension)
875 // Remove trailing dots and spaces on windows
876 aFile = aFile.replace(/[\s.]+$/, "");
879 // Remove leading dots
880 aFile = aFile.replace(/^\.+/, "");
882 // Fix up the file name we're saving to to include the default extension
883 var i = aFile.lastIndexOf(".");
884 if (aFile.substr(i + 1) != aDefaultExtension)
885 return aFile + "." + aDefaultExtension;
890 function getDefaultExtension(aFilename, aURI, aContentType)
892 if (aContentType == "text/plain" || aContentType == "application/octet-stream" || aURI.scheme == "ftp")
893 return ""; // temporary fix for bug 120327
895 // First try the extension from the filename
896 const stdURLContractID = "@mozilla.org/network/standard-url;1";
897 const stdURLIID = Components.interfaces.nsIURL;
898 var url = Components.classes[stdURLContractID].createInstance(stdURLIID);
899 url.filePath = aFilename;
901 var ext = url.fileExtension;
903 // This mirrors some code in nsExternalHelperAppService::DoContent
904 // Use the filename first and then the URI if that fails
906 var mimeInfo = getMIMEInfoForType(aContentType, ext);
908 if (ext && mimeInfo && mimeInfo.extensionExists(ext))
911 // Well, that failed. Now try the extension from the URI
914 url = aURI.QueryInterface(Components.interfaces.nsIURL);
915 urlext = url.fileExtension;
919 if (urlext && mimeInfo && mimeInfo.extensionExists(urlext)) {
925 return mimeInfo.primaryExtension;
929 // Fall back on the extensions in the filename and URI for lack
930 // of anything better.
931 return ext || urlext;
935 function GetSaveModeForContentType(aContentType)
937 var saveMode = SAVEMODE_FILEONLY;
938 switch (aContentType) {
940 case "application/xhtml+xml":
941 case "image/svg+xml":
942 saveMode |= SAVEMODE_COMPLETE_TEXT;
945 case "application/xml":
946 saveMode |= SAVEMODE_COMPLETE_DOM;
953 function getCharsetforSave(aDocument)
956 return aDocument.characterSet;
958 if (document.commandDispatcher.focusedWindow)
959 return document.commandDispatcher.focusedWindow.document.characterSet;
961 return window.content.document.characterSet;
965 * Open a URL from chrome, determining if we can handle it internally or need to
966 * launch an external application to handle it.
967 * @param aURL The URL to be opened
969 function openURL(aURL)
971 var ios = Components.classes["@mozilla.org/network/io-service;1"]
972 .getService(Components.interfaces.nsIIOService);
973 var uri = ios.newURI(aURL, null, null);
975 var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
976 .getService(Components.interfaces.nsIExternalProtocolService);
978 if (!protocolSvc.isExposedProtocol(uri.scheme)) {
979 // If we're not a browser, use the external protocol service to load the URI.
980 protocolSvc.loadUrl(uri);
983 var loadgroup = Components.classes["@mozilla.org/network/load-group;1"]
984 .createInstance(Components.interfaces.nsILoadGroup);
985 var appstartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
986 .getService(Components.interfaces.nsIAppStartup);
989 onStartRequest: function ll_start(aRequest, aContext) {
990 appstartup.enterLastWindowClosingSurvivalArea();
992 onStopRequest: function ll_stop(aRequest, aContext, aStatusCode) {
993 appstartup.exitLastWindowClosingSurvivalArea();
995 QueryInterface: function ll_QI(iid) {
996 if (iid.equals(Components.interfaces.nsISupports) ||
997 iid.equals(Components.interfaces.nsIRequestObserver) ||
998 iid.equals(Components.interfaces.nsISupportsWeakReference))
1000 throw Components.results.NS_ERROR_NO_INTERFACE;
1003 loadgroup.groupObserver = loadListener;
1006 onStartURIOpen: function(uri) { return false; },
1007 doContent: function(ctype, preferred, request, handler) { return false; },
1008 isPreferred: function(ctype, desired) { return false; },
1009 canHandleContent: function(ctype, preferred, desired) { return false; },
1011 parentContentListener: null,
1012 getInterface: function(iid) {
1013 if (iid.equals(Components.interfaces.nsIURIContentListener))
1015 if (iid.equals(Components.interfaces.nsILoadGroup))
1017 throw Components.results.NS_ERROR_NO_INTERFACE;
1021 var channel = ios.newChannelFromURI(uri);
1022 var uriLoader = Components.classes["@mozilla.org/uriloader;1"]
1023 .getService(Components.interfaces.nsIURILoader);
1024 uriLoader.openURI(channel, true, uriListener);