bug 11632 - save page with images, stylesheets, objects and applets.
r=hewitt, brade, sr=hyatt git-svn-id: svn://10.0.0.236/trunk@110364 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
@@ -62,6 +62,7 @@ var gBrowser = null;
|
||||
|
||||
// focused frame URL
|
||||
var gFocusedURL = null;
|
||||
var gFocusedDocument = null;
|
||||
|
||||
/**
|
||||
* We can avoid adding multiple load event listeners and save some time by adding
|
||||
@@ -98,6 +99,7 @@ function contentAreaFrameFocus()
|
||||
var focusedWindow = document.commandDispatcher.focusedWindow;
|
||||
if (isDocumentFrame(focusedWindow)) {
|
||||
gFocusedURL = focusedWindow.location.href;
|
||||
gFocusedDocument = focusedWindow.document;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<command id="cmd_newEditorTemplate"/>
|
||||
<command id="cmd_newEditorDraft"/> -->
|
||||
<command id="Browser:OpenFile" oncommand="BrowserOpenFileWindow();"/>
|
||||
<command id="Browser:SavePage" oncommand="savePage();"/>
|
||||
<command id="Browser:SavePage" oncommand="saveDocument(window._content.document);"/>
|
||||
<command id="Browser:EditPage" oncommand="editPage(window._content.location.href,window, false);"/>
|
||||
<command id="Browser:Open" oncommand="BrowserOpenWindow();"/>
|
||||
<command id="cmd_printSetup" oncommand="goPageSetup();"/>
|
||||
@@ -186,7 +186,7 @@
|
||||
<menuitem id="menu_closeWindow" hidden="true" command="cmd_closeWindow" key="key_closeWindow" label="&closeWindow.label;"/>
|
||||
<menuseparator id="menu_closeSeparator" hidden="true"/>
|
||||
<menuitem label="&savePageCmd.label;" accesskey="&savePageCmd.accesskey;" key="key_savePage" command="Browser:SavePage"/>
|
||||
<menuitem id="savepage" label="&saveFrameCmd.label;" accesskey="&saveFrameCmd.accesskey;" oncommand="savePage(gFocusedURL);" hidden="true"/>
|
||||
<menuitem id="savepage" label="&saveFrameCmd.label;" accesskey="&saveFrameCmd.accesskey;" oncommand="savePage(gFocusedURL, gFocusedDocument);" hidden="true"/>
|
||||
<menuseparator/>
|
||||
<menuitem label="&editPageCmd.label;" accesskey="&editPageCmd.accesskey;" key="key_editPage" command="Browser:EditPage" />
|
||||
<menuseparator/>
|
||||
|
||||
@@ -39,6 +39,12 @@
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
/*
|
||||
* - [ Dependencies ] ---------------------------------------------------------
|
||||
* utilityOverlay.js:
|
||||
* - gatherTextUnder
|
||||
*/
|
||||
|
||||
var pref = null;
|
||||
pref = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
@@ -205,7 +211,7 @@
|
||||
saveModifier = saveModifier ? event.shiftKey : event.metaKey;
|
||||
|
||||
if (saveModifier) { // if saveModifier is down
|
||||
savePage(href); // save the link
|
||||
saveURL(href, gatherTextUnder(event.target));
|
||||
return true;
|
||||
}
|
||||
if (event.altKey) // if alt is down
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
<menuitem id="context-savepage"
|
||||
label="&savePageCmd.label;"
|
||||
accesskey="&savePageCmd.accesskey;"
|
||||
oncommand="gContextMenu.savePage();"/>
|
||||
oncommand="saveDocument(window._content.document);"/>
|
||||
<menuitem id="context-saveframe"
|
||||
label="&saveFrameCmd.label;"
|
||||
accesskey="&saveFrameCmd.accesskey;"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Ben Goodger <ben@netscape.com> (Save File)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
@@ -39,123 +40,440 @@
|
||||
* Determine whether or not a given focused DOMWindow is in the content
|
||||
* area.
|
||||
**/
|
||||
function isDocumentFrame(aFocusedWindow)
|
||||
function isDocumentFrame(aFocusedWindow)
|
||||
{
|
||||
var contentFrames = _content.frames;
|
||||
if (contentFrames.length) {
|
||||
for (var i = 0; i < contentFrames.length; ++i) {
|
||||
if (aFocusedWindow == contentFrames[i])
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function urlSecurityCheck(url, doc)
|
||||
{
|
||||
// URL Loading Security Check
|
||||
var focusedWindow = doc.commandDispatcher.focusedWindow;
|
||||
var sourceWin = isDocumentFrame(focusedWindow) ? focusedWindow.location.href : focusedWindow._content.location.href;
|
||||
const nsIScriptSecurityManager = Components.interfaces.nsIScriptSecurityManager;
|
||||
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"].getService().
|
||||
QueryInterface(nsIScriptSecurityManager);
|
||||
try {
|
||||
secMan.checkLoadURIStr(sourceWin, url, nsIScriptSecurityManager.STANDARD);
|
||||
} catch (e) {
|
||||
throw "Load of " + url + " denied.";
|
||||
}
|
||||
}
|
||||
|
||||
function openNewWindowWith(url)
|
||||
{
|
||||
urlSecurityCheck(url, document);
|
||||
var newWin;
|
||||
var wintype = document.firstChild.getAttribute('windowtype');
|
||||
|
||||
// if and only if the current window is a browser window and it has a document with a character
|
||||
// set, then extract the current charset menu setting from the current document and use it to
|
||||
// initialize the new browser window...
|
||||
if (window && (wintype == "navigator:browser") &&
|
||||
window._content && window._content.document) {
|
||||
var DocCharset = window._content.document.characterSet;
|
||||
var charsetArg = "charset="+DocCharset;
|
||||
|
||||
//we should "inherit" the charset menu setting in a new window
|
||||
newWin = window.openDialog( getBrowserURL(), "_blank", "chrome,all,dialog=no", url, charsetArg, true );
|
||||
}
|
||||
else { // forget about the charset information.
|
||||
newWin = window.openDialog( getBrowserURL(), "_blank", "chrome,all,dialog=no", url, null, true );
|
||||
}
|
||||
|
||||
// Fix new window.
|
||||
newWin.saveFileAndPos = true;
|
||||
}
|
||||
|
||||
function openNewTabWith(url)
|
||||
{
|
||||
urlSecurityCheck(url, document);
|
||||
var wintype = document.firstChild.getAttribute('windowtype');
|
||||
|
||||
// if and only if the current window is a browser window and it has a document with a character
|
||||
// set, then extract the current charset menu setting from the current document and use it to
|
||||
// initialize the new browser window...
|
||||
if (window && (wintype == "navigator:browser")) {
|
||||
var browser=getBrowser();
|
||||
var t = browser.addTab(url); // open link in new tab
|
||||
if (pref && !pref.getBoolPref("browser.tabs.loadInBackground"))
|
||||
browser.selectedTab = t;
|
||||
}
|
||||
|
||||
// Fix new window.
|
||||
newWin.saveFileAndPos = true;
|
||||
}
|
||||
|
||||
function findParentNode(node, parentNode)
|
||||
{
|
||||
if (node && node.nodeType == Node.TEXT_NODE) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
while (node) {
|
||||
var nodeName = node.localName;
|
||||
if (!nodeName)
|
||||
return null;
|
||||
nodeName = nodeName.toLowerCase();
|
||||
if (nodeName == "body" || nodeName == "html" ||
|
||||
nodeName == "#document") {
|
||||
return null;
|
||||
}
|
||||
if (nodeName == parentNode)
|
||||
return node;
|
||||
node = node.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clientelle: (Make sure you don't break any of these)
|
||||
// - File -> Save Page/Frame As...
|
||||
// - Context -> Save Page/Frame As...
|
||||
// - Context -> Save Link As...
|
||||
// - Context -> Save Image As...
|
||||
// - Shift-Click Save Link As
|
||||
//
|
||||
// Try saving each of these types:
|
||||
// - A complete webpage using File->Save Page As, and Context->Save Page As
|
||||
// - A webpage as HTML only using the above methods
|
||||
// - A webpage as Text only using the above methods
|
||||
// - An image with an extension (e.g. .jpg) in its file name, using
|
||||
// Context->Save Image As...
|
||||
// - An image without an extension (e.g. a banner ad on cnn.com) using
|
||||
// the above method.
|
||||
// - A linked document using Save Link As...
|
||||
// - A linked document using shift-click Save Link As...
|
||||
//
|
||||
function saveURL(aURL, aFileName, aFilePickerTitleKey)
|
||||
{
|
||||
var data = {
|
||||
fileName: aFileName,
|
||||
filePickerTitle: aFilePickerTitleKey
|
||||
};
|
||||
var sniffer = new nsHeaderSniffer(aURL, foundHeaderInfo, data);
|
||||
}
|
||||
|
||||
function foundHeaderInfo(aSniffer, aData)
|
||||
{
|
||||
var contentType = aSniffer.contentType;
|
||||
|
||||
var fp = makeFilePicker();
|
||||
var titleKey = aData.filePickerTitle || "SaveLinkTitle";
|
||||
var bundle = getStringBundle();
|
||||
fp.init(window, bundle.GetStringFromName(titleKey),
|
||||
Components.interfaces.nsIFilePicker.modeSave);
|
||||
|
||||
appendFiltersForContentType(fp, aSniffer.contentType);
|
||||
|
||||
// Determine what the 'default' string to display in the File Picker dialog
|
||||
// should be.
|
||||
var defaultFileName = getDefaultFileName(aData.fileName,
|
||||
aSniffer.suggestedFileName,
|
||||
aSniffer.uri);
|
||||
fp.defaultString = getNormalizedLeafName(defaultFileName, contentType);
|
||||
|
||||
if (fp.show() == Components.interfaces.nsIFilePicker.returnCancel || !fp.file)
|
||||
return;
|
||||
|
||||
fp.file.leafName = validateFileName(fp.file.leafName);
|
||||
fp.file.leafName = getNormalizedLeafName(fp.file.leafName, contentType);
|
||||
|
||||
var persistArgs = {
|
||||
source : aSniffer.uri,
|
||||
target : fp.file,
|
||||
postData : getPostData()
|
||||
};
|
||||
|
||||
openDialog("chrome://global/content/helperAppDldProgress.xul", "",
|
||||
"chrome,titlebar,minizable,dialog=yes",
|
||||
makeWebBrowserPersist(), persistArgs);
|
||||
}
|
||||
|
||||
function nsHeaderSniffer(aURL, aCallback, aData)
|
||||
{
|
||||
this.mPersist = makeWebBrowserPersist();
|
||||
this.mCallback = aCallback;
|
||||
this.mData = aData;
|
||||
|
||||
this.mPersist.progressListener = this;
|
||||
|
||||
this.mTempFile = makeTempFile();
|
||||
while (this.mTempFile.exists())
|
||||
this.mTempFile = makeTempFile();
|
||||
|
||||
const stdURLContractID = "@mozilla.org/network/standard-url;1";
|
||||
const stdURLIID = Components.interfaces.nsIURI;
|
||||
this.uri = Components.classes[stdURLContractID].createInstance(stdURLIID);
|
||||
this.uri.spec = aURL;
|
||||
|
||||
this.mPersist.saveURI(this.uri, null, this.mTempFile);
|
||||
}
|
||||
|
||||
nsHeaderSniffer.prototype = {
|
||||
onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus)
|
||||
{
|
||||
var contentFrames = _content.frames;
|
||||
if (contentFrames.length) {
|
||||
for (var i = 0; i < contentFrames.length; ++i) {
|
||||
if (aFocusedWindow == contentFrames[i])
|
||||
return true;
|
||||
if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_START) {
|
||||
try {
|
||||
var channel = aRequest.QueryInterface(Components.interfaces.nsIChannel);
|
||||
this.contentType = channel.contentType;
|
||||
try {
|
||||
var httpChannel = aRequest.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||||
this.mContentDisposition = httpChannel.getResponseHeader("content-disposition");
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
this.mPersist.cancelSave();
|
||||
if (this.mTempFile.exists())
|
||||
this.mTempFile.remove(false);
|
||||
this.mCallback(this, this.mData);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function urlSecurityCheck(url, doc) {
|
||||
// URL Loading Security Check
|
||||
var focusedWindow = doc.commandDispatcher.focusedWindow;
|
||||
var sourceWin = isDocumentFrame(focusedWindow) ? focusedWindow.location.href : focusedWindow._content.location.href;
|
||||
const nsIScriptSecurityManager = Components.interfaces.nsIScriptSecurityManager;
|
||||
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"].getService().
|
||||
QueryInterface(nsIScriptSecurityManager);
|
||||
try {
|
||||
secMan.checkLoadURIStr(sourceWin, url, nsIScriptSecurityManager.STANDARD);
|
||||
} catch (e) {
|
||||
throw "Load of " + url + " denied.";
|
||||
}
|
||||
}
|
||||
|
||||
function openNewWindowWith(url) {
|
||||
|
||||
urlSecurityCheck(url, document);
|
||||
var newWin;
|
||||
var wintype = document.firstChild.getAttribute('windowtype');
|
||||
|
||||
// if and only if the current window is a browser window and it has a document with a character
|
||||
// set, then extract the current charset menu setting from the current document and use it to
|
||||
// initialize the new browser window...
|
||||
if (window && (wintype == "navigator:browser") &&
|
||||
window._content && window._content.document) {
|
||||
var DocCharset = window._content.document.characterSet;
|
||||
var charsetArg = "charset="+DocCharset;
|
||||
|
||||
//we should "inherit" the charset menu setting in a new window
|
||||
newWin = window.openDialog( getBrowserURL(), "_blank", "chrome,all,dialog=no", url, charsetArg, true );
|
||||
}
|
||||
else { // forget about the charset information.
|
||||
newWin = window.openDialog( getBrowserURL(), "_blank", "chrome,all,dialog=no", url, null, true );
|
||||
}
|
||||
|
||||
// Fix new window.
|
||||
newWin.saveFileAndPos = true;
|
||||
}
|
||||
|
||||
function openNewTabWith(url) {
|
||||
|
||||
urlSecurityCheck(url, document);
|
||||
var wintype = document.firstChild.getAttribute('windowtype');
|
||||
|
||||
// if and only if the current window is a browser window and it has a document with a character
|
||||
// set, then extract the current charset menu setting from the current document and use it to
|
||||
// initialize the new browser window...
|
||||
if (window && (wintype == "navigator:browser")) {
|
||||
var browser=getBrowser();
|
||||
var t = browser.addTab(url); // open link in new tab
|
||||
if (pref && !pref.getBoolPref("browser.tabs.loadInBackground"))
|
||||
browser.selectedTab = t;
|
||||
}
|
||||
|
||||
// Fix new window.
|
||||
newWin.saveFileAndPos = true;
|
||||
}
|
||||
|
||||
function savePage(url)
|
||||
},
|
||||
onLocationChange: function (aWebProgress, aRequest, aLocation) { },
|
||||
onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { },
|
||||
onSecurityChange: function (aWebProgress, aRequest, aState) { },
|
||||
onProgressChange: function (aWebProgress, aRequest, aCurSelfProgress,
|
||||
aMaxSelfProgress, aCurTotalProgress,
|
||||
aMaxTotalProgress) { },
|
||||
|
||||
get suggestedFileName()
|
||||
{
|
||||
var postData = null; // No post data, usually.
|
||||
var cacheKey = null;
|
||||
// Default is to save current page.
|
||||
if ( !url )
|
||||
url = window._content.location.href;
|
||||
|
||||
try {
|
||||
var sessionHistory = getWebNavigation().sessionHistory;
|
||||
var entry = sessionHistory.getEntryAtIndex(sessionHistory.index, false).QueryInterface(Components.interfaces.nsISHEntry);
|
||||
postData = entry.postData;
|
||||
cacheKey = entry.cacheKey;
|
||||
} catch(e) {
|
||||
}
|
||||
|
||||
// Use stream xfer component to prompt for destination and save.
|
||||
var xfer = Components.classes["@mozilla.org/appshell/component/xfer;1"].getService(Components.interfaces["nsIStreamTransfer"]);
|
||||
try {
|
||||
xfer.SelectFileAndTransferLocationSpec( url, window, "", "", true, postData, cacheKey );
|
||||
} catch( exception ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//Note: "function editPage(url)" was moved to utilityOverlay.js
|
||||
|
||||
function findParentNode(node, parentNode)
|
||||
{
|
||||
if (node && node.nodeType == Node.TEXT_NODE) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
while (node) {
|
||||
var nodeName = node.localName;
|
||||
if (!nodeName)
|
||||
return null;
|
||||
nodeName = nodeName.toLowerCase();
|
||||
if (nodeName == "body" || nodeName == "html" ||
|
||||
nodeName == "#document") {
|
||||
return null;
|
||||
var filename = "";
|
||||
var name = this.mContentDisposition;
|
||||
if (name) {
|
||||
var ix = name.indexOf("filename=");
|
||||
if (ix > 0) {
|
||||
filename = name.substr(ix, name.length);
|
||||
if (filename != "") {
|
||||
ix = filename.lastIndexOf(";");
|
||||
if (ix > 0)
|
||||
filename = filename.substr(0, ix);
|
||||
// XXX strip out quotes;
|
||||
}
|
||||
}
|
||||
if (nodeName == parentNode)
|
||||
return node;
|
||||
node = node.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function saveDocument(aDocument)
|
||||
{
|
||||
// When this function is called, we assume we're saving text/html or
|
||||
// compatible.
|
||||
var fp = makeFilePicker();
|
||||
var bundle = getStringBundle();
|
||||
fp.init(window, bundle.GetStringFromName("SavePageTitle"),
|
||||
Components.interfaces.nsIFilePicker.modeSave);
|
||||
|
||||
appendFiltersForContentType(fp, "text/html", MODE_COMPLETE);
|
||||
|
||||
// Determine what the 'default' string to display in the File Picker dialog
|
||||
// should be.
|
||||
const stdURLContractID = "@mozilla.org/network/standard-url;1";
|
||||
const stdURLIID = Components.interfaces.nsIURI;
|
||||
var uri = Components.classes[stdURLContractID].createInstance(stdURLIID);
|
||||
uri.spec = aDocument.URL;
|
||||
|
||||
var defaultFileName = getDefaultFileName(null, null, uri, aDocument);
|
||||
fp.defaultString = getNormalizedLeafName(defaultFileName, "text/html");
|
||||
|
||||
if (fp.show() == Components.interfaces.nsIFilePicker.returnCancel || !fp.file)
|
||||
return;
|
||||
|
||||
var contentType = fp.filterIndex == 2 ? "text/unicode" : "text/html";
|
||||
fp.file.leafName = validateFileName(fp.file.leafName);
|
||||
fp.file.leafName = getNormalizedLeafName(fp.file.URL, contentType);
|
||||
|
||||
var persistArgs = {
|
||||
source : fp.filterIndex == 0 ? aDocument : uri,
|
||||
// XXX turn this on when Adam lands the ability to save as a specific content
|
||||
// type
|
||||
// contentType : fp.filterIndex == 2 ? "text/unicode" : "text/html";
|
||||
target : fp.file,
|
||||
postData : getPostData()
|
||||
};
|
||||
|
||||
openDialog("chrome://global/content/helperAppDldProgress.xul", "",
|
||||
"chrome,titlebar,minizable,dialog=yes",
|
||||
makeWebBrowserPersist(), persistArgs);
|
||||
|
||||
}
|
||||
|
||||
const MODE_COMPLETE = 0;
|
||||
const MODE_HTMLONLY = 1;
|
||||
|
||||
function appendFiltersForContentType(aFilePicker, aContentType, aSaveMode)
|
||||
{
|
||||
var bundle = getStringBundle();
|
||||
|
||||
switch (aContentType) {
|
||||
case "text/html":
|
||||
if (aSaveMode == MODE_COMPLETE)
|
||||
aFilePicker.appendFilter(bundle.GetStringFromName("WebPageCompleteFilter"), "*.htm; *.html");
|
||||
aFilePicker.appendFilter(bundle.GetStringFromName("WebPageHTMLOnlyFilter"), "*.htm; *.html");
|
||||
aFilePicker.appendFilter(bundle.GetStringFromName("TextOnlyFilter"), "*.txt");
|
||||
break;
|
||||
default:
|
||||
var mimeInfo = getMIMEInfoForType(aContentType);
|
||||
if (mimeInfo) {
|
||||
var extCount = { };
|
||||
var extList = { };
|
||||
mimeInfo.GetFileExtensions(extCount, extList);
|
||||
|
||||
var extString = "";
|
||||
for (var i = 0; i < extCount.value; ++i) {
|
||||
if (i > 0)
|
||||
extString += "; "; // If adding more than one extension, separate by semi-colon
|
||||
extString += "*." + extList.value[i];
|
||||
}
|
||||
|
||||
aFilePicker.appendFilter(mimeInfo.Description, extString);
|
||||
}
|
||||
else
|
||||
aFilePicker.appendFilter(bundle.GetStringFromName("AllFilesFilter"), "*.*");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function getPostData()
|
||||
{
|
||||
try {
|
||||
var sessionHistory = getWebNavigation().sessionHistory;
|
||||
entry = sessionHistory.getEntryAtIndex(sessionHistory.index, false);
|
||||
entry = entry.QueryInterface(Components.interfaces.nsISHEntry);
|
||||
return entry.postData;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getStringBundle()
|
||||
{
|
||||
const bundleURL = "chrome://communicator/locale/contentAreaCommands.properties";
|
||||
|
||||
const sbsContractID = "@mozilla.org/intl/stringbundle;1";
|
||||
const sbsIID = Components.interfaces.nsIStringBundleService;
|
||||
const sbs = Components.classes[sbsContractID].getService(sbsIID);
|
||||
|
||||
const lsContractID = "@mozilla.org/intl/nslocaleservice;1";
|
||||
const lsIID = Components.interfaces.nsILocaleService;
|
||||
const ls = Components.classes[lsContractID].getService(lsIID);
|
||||
var appLocale = ls.GetApplicationLocale();
|
||||
return sbs.createBundle(bundleURL, appLocale);
|
||||
}
|
||||
|
||||
function makeWebBrowserPersist()
|
||||
{
|
||||
const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
|
||||
const persistIID = Components.interfaces.nsIWebBrowserPersist;
|
||||
return Components.classes[persistContractID].createInstance(persistIID);
|
||||
}
|
||||
|
||||
function makeFilePicker()
|
||||
{
|
||||
const fpContractID = "@mozilla.org/filepicker;1";
|
||||
const fpIID = Components.interfaces.nsIFilePicker;
|
||||
return Components.classes[fpContractID].createInstance(fpIID);
|
||||
}
|
||||
|
||||
function makeTempFile()
|
||||
{
|
||||
const mimeTypes = "TmpD";
|
||||
const flContractID = "@mozilla.org/file/directory_service;1";
|
||||
const flIID = Components.interfaces.nsIProperties;
|
||||
var fileLocator = Components.classes[flContractID].getService(flIID);
|
||||
var tempFile = fileLocator.get(mimeTypes, Components.interfaces.nsIFile);
|
||||
tempFile.append("~sav" + Math.floor(Math.random() * 1000) + ".tmp");
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
function getMIMEInfoForType(aMIMEType)
|
||||
{
|
||||
const mimeSvcCID = "{03af31da-3109-11d3-8cd0-0060b0fc14a3}";
|
||||
const mimeSvcIID = Components.interfaces.nsIMIMEService;
|
||||
const mimeSvc = Components.classesByID[mimeSvcCID].getService(mimeSvcIID);
|
||||
|
||||
try {
|
||||
return mimeSvc.GetFromMIMEType(aMIMEType);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDefaultFileName(aDefaultFileName, aNameFromHeaders, aDocumentURI, aDocument)
|
||||
{
|
||||
if (aDefaultFileName)
|
||||
return validateFileName(aDefaultFileName); // 1) Use the caller-provided name, if any
|
||||
|
||||
if (aNameFromHeaders)
|
||||
return validateFileName(aNameFromHeaders); // 2) Use the name suggested by the HTTP headers
|
||||
|
||||
if (aDocument && aDocument.title != "")
|
||||
return validateFileName(aDocument.title) // 3) Use the document title
|
||||
|
||||
var url = aDocumentURI.QueryInterface(Components.interfaces.nsIURL);
|
||||
if (url.fileName != "")
|
||||
return url.fileName; // 4) Use the actual file name, if present
|
||||
|
||||
return aDocumentURI.host; // 5) Use the host.
|
||||
}
|
||||
|
||||
function validateFileName(aFileName)
|
||||
{
|
||||
var re = /[\/]+/g;
|
||||
if (navigator.appVersion.indexOf("Windows") != -1) {
|
||||
re = /[\\\/\|]+/g;
|
||||
aFileName = aFileName.replace(/[\"]+/g, "'");
|
||||
aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
|
||||
aFileName = aFileName.replace(/[\<]+/g, "(");
|
||||
aFileName = aFileName.replace(/[\>]+/g, ")");
|
||||
}
|
||||
else if (navigator.appVersion.indexOf("Macintosh") != -1)
|
||||
re = /[\:\/]+/g;
|
||||
|
||||
return aFileName.replace(re, "_");
|
||||
}
|
||||
|
||||
function getNormalizedLeafName(aFile, aContentType)
|
||||
{
|
||||
// Fix up the file name we're saving to so that if the user enters
|
||||
// no extension, an appropriate one is appended.
|
||||
var leafName = aFile;
|
||||
|
||||
var mimeInfo = getMIMEInfoForType(aContentType);
|
||||
if (mimeInfo) {
|
||||
var extCount = { };
|
||||
var extList = { };
|
||||
mimeInfo.GetFileExtensions(extCount, extList);
|
||||
|
||||
const stdURLContractID = "@mozilla.org/network/standard-url;1";
|
||||
const stdURLIID = Components.interfaces.nsIURI;
|
||||
var uri = Components.classes[stdURLContractID].createInstance(stdURLIID);
|
||||
uri.spec = "file:///" + aFile;
|
||||
var url = uri.QueryInterface(Components.interfaces.nsIURL);
|
||||
|
||||
if (aContentType == "text/html") {
|
||||
if ((url.fileExtension &&
|
||||
url.fileExtension != "htm" && url.fileExtension != "html") ||
|
||||
(!url.fileExtension))
|
||||
return leafName + ".html";
|
||||
}
|
||||
|
||||
if (!url.fileExtension)
|
||||
return leafName + "." + extList.value[0];
|
||||
}
|
||||
|
||||
return leafName;
|
||||
}
|
||||
|
||||
|
||||
@@ -467,15 +467,15 @@ nsContextMenu.prototype = {
|
||||
},
|
||||
// Save URL of clicked-on frame.
|
||||
saveFrame : function () {
|
||||
this.savePage( this.target.ownerDocument.location.href, true );
|
||||
saveDocument( this.target.ownerDocument );
|
||||
},
|
||||
// Save URL of clicked-on link.
|
||||
saveLink : function () {
|
||||
this.savePage( this.linkURL(), false );
|
||||
saveURL( this.linkURL(), this.linkText() );
|
||||
},
|
||||
// Save URL of clicked-on image.
|
||||
saveImage : function () {
|
||||
this.savePage( this.imageURL, true );
|
||||
saveURL( this.imageURL, null, "SaveImageTitle" );
|
||||
},
|
||||
// Generate email address and put it on clipboard.
|
||||
copyEmail : function () {
|
||||
@@ -578,53 +578,9 @@ nsContextMenu.prototype = {
|
||||
},
|
||||
// Get text of link (if possible).
|
||||
linkText : function () {
|
||||
var text = this.gatherTextUnder( this.link );
|
||||
var text = gatherTextUnder( this.link );
|
||||
return text;
|
||||
},
|
||||
// Gather all descendent text under given document node.
|
||||
gatherTextUnder : function ( root ) {
|
||||
var text = "";
|
||||
var node = root.firstChild;
|
||||
var depth = 1;
|
||||
while ( node && depth > 0 ) {
|
||||
// See if this node is text.
|
||||
if ( node.nodeName == "#text" ) {
|
||||
// Add this text to our collection.
|
||||
text += " " + node.data;
|
||||
} else if ( node.nodeType == Node.ELEMENT_NODE
|
||||
&& node.localName.toUpperCase() == "IMG" ) {
|
||||
// If it has an alt= attribute, use that.
|
||||
var altText = node.getAttribute( "alt" );
|
||||
if ( altText && altText != "" ) {
|
||||
text = altText;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Find next node to test.
|
||||
// First, see if this node has children.
|
||||
if ( node.hasChildNodes() ) {
|
||||
// Go to first child.
|
||||
node = node.firstChild;
|
||||
depth++;
|
||||
} else {
|
||||
// No children, try next sibling.
|
||||
if ( node.nextSibling ) {
|
||||
node = node.nextSibling;
|
||||
} else {
|
||||
// Last resort is our next oldest uncle/aunt.
|
||||
node = node.parentNode.nextSibling;
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Strip leading whitespace.
|
||||
text = text.replace( /^\s+/, "" );
|
||||
// Strip trailing whitespace.
|
||||
text = text.replace( /\s+$/, "" );
|
||||
// Compress remaining whitespace.
|
||||
text = text.replace( /\s+/g, " " );
|
||||
return text;
|
||||
},
|
||||
// Returns "true" if there's no text selected, null otherwise.
|
||||
isNoTextSelected : function ( event ) {
|
||||
// Not implemented so all text-selected-based options are disabled.
|
||||
@@ -686,34 +642,6 @@ nsContextMenu.prototype = {
|
||||
|
||||
return ioService.newURI(baseURI.resolve(url), null).spec;
|
||||
},
|
||||
// Save specified URL in user-selected file.
|
||||
savePage : function ( url, doNotValidate ) {
|
||||
var postData = null; // No post data, usually.
|
||||
var cacheKey = null;
|
||||
|
||||
// Default is to save current page.
|
||||
if ( !url ) {
|
||||
url = window._content.location.href;
|
||||
|
||||
try {
|
||||
var sessionHistory = getWebNavigation().sessionHistory;
|
||||
var entry = sessionHistory.getEntryAtIndex(sessionHistory.index, false).QueryInterface(Components.interfaces.nsISHEntry);
|
||||
postData = entry.postData;
|
||||
cacheKey = entry.cacheKey;
|
||||
} catch(e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Use stream xfer component to prompt for destination and save.
|
||||
var xfer = this.getService( "@mozilla.org/appshell/component/xfer;1",
|
||||
"nsIStreamTransfer" );
|
||||
try {
|
||||
xfer.SelectFileAndTransferLocationSpec( url, window, "", "", doNotValidate, postData, cacheKey );
|
||||
} catch( exception ) {
|
||||
// Failed (or cancelled), give them another chance.
|
||||
}
|
||||
return;
|
||||
},
|
||||
// Parse coords= attribute and return array.
|
||||
parseCoords : function ( area ) {
|
||||
return [];
|
||||
|
||||
@@ -381,6 +381,52 @@ function extractFileNameFromUrl(urlstr)
|
||||
return urlstr.slice(urlstr.lastIndexOf( "/" )+1);
|
||||
}
|
||||
|
||||
// Gather all descendent text under given document node.
|
||||
function gatherTextUnder ( root )
|
||||
{
|
||||
var text = "";
|
||||
var node = root.firstChild;
|
||||
var depth = 1;
|
||||
while ( node && depth > 0 ) {
|
||||
// See if this node is text.
|
||||
if ( node.nodeName == "#text" ) {
|
||||
// Add this text to our collection.
|
||||
text += " " + node.data;
|
||||
} else if ( node.nodeType == Node.ELEMENT_NODE
|
||||
&& node.localName.toUpperCase() == "IMG" ) {
|
||||
// If it has an alt= attribute, use that.
|
||||
var altText = node.getAttribute( "alt" );
|
||||
if ( altText && altText != "" ) {
|
||||
text = altText;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Find next node to test.
|
||||
// First, see if this node has children.
|
||||
if ( node.hasChildNodes() ) {
|
||||
// Go to first child.
|
||||
node = node.firstChild;
|
||||
depth++;
|
||||
} else {
|
||||
// No children, try next sibling.
|
||||
if ( node.nextSibling ) {
|
||||
node = node.nextSibling;
|
||||
} else {
|
||||
// Last resort is our next oldest uncle/aunt.
|
||||
node = node.parentNode.nextSibling;
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Strip leading whitespace.
|
||||
text = text.replace( /^\s+/, "" );
|
||||
// Strip trailing whitespace.
|
||||
text = text.replace( /\s+$/, "" );
|
||||
// Compress remaining whitespace.
|
||||
text = text.replace( /\s+/g, " " );
|
||||
return text;
|
||||
}
|
||||
|
||||
var offlineObserver = {
|
||||
observe: function(subject, topic, state) {
|
||||
// sanity checks
|
||||
|
||||
@@ -2,3 +2,12 @@
|
||||
|
||||
saveImageAs=Save Image (%S)...
|
||||
searchText=Search for '%S'
|
||||
|
||||
SavePageTitle=Save Web Page
|
||||
SaveImageTitle=Save Picture
|
||||
SaveLinkTitle=Save As
|
||||
WebPageCompleteFilter=Web Page, complete (*.htm;*.html)
|
||||
WebPageHTMLOnlyFilter=Web Page, HTML only (*.htm;*.html)
|
||||
TextOnlyFilter=Text File (*.txt)
|
||||
AllFilesFilter=All Files (*.*)
|
||||
|
||||
|
||||
@@ -93,6 +93,8 @@ function filepickerLoad() {
|
||||
filterMenuList.selectedIndex = 0;
|
||||
var filterBox = document.getElementById("filterBox");
|
||||
filterBox.removeAttribute("hidden");
|
||||
|
||||
filterMenuList.selectedIndex = o.filterIndex;
|
||||
} else if (filePickerMode == nsIFilePicker.modeGetFolder) {
|
||||
outlinerView.showOnlyDirectories = true;
|
||||
}
|
||||
@@ -166,6 +168,10 @@ function openOnOK()
|
||||
gotoDirectory(dir);
|
||||
retvals.file = dir;
|
||||
retvals.buttonStatus = nsIFilePicker.returnCancel;
|
||||
|
||||
var filterMenuList = document.getElementById("filterMenuList");
|
||||
retvals.filterIndex = filterMenuList.selectedIndex;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ function nsFilePicker()
|
||||
|
||||
/* attributes */
|
||||
this.mDefaultString = "";
|
||||
this.mFilterIndex = 0;
|
||||
if (lastDirectory) {
|
||||
this.mDisplayDirectory = Components.classes[LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile);
|
||||
this.mDisplayDirectory.initWithUnicodePath(lastDirectory);
|
||||
@@ -99,6 +100,10 @@ nsFilePicker.prototype = {
|
||||
set defaultString(a) { this.mDefaultString = a; },
|
||||
get defaultString() { return this.mDefaultString; },
|
||||
|
||||
/* attribute long filterIndex; */
|
||||
set filterIndex(a) { this.mFilterIndex = a; },
|
||||
get filterInrex() { return this.mFilterIndex; },
|
||||
|
||||
/* methods */
|
||||
init: function(parent, title, mode) {
|
||||
this.mParentWindow = parent;
|
||||
@@ -151,6 +156,7 @@ nsFilePicker.prototype = {
|
||||
o.mode = this.mMode;
|
||||
o.displayDirectory = this.mDisplayDirectory;
|
||||
o.defaultString = this.mDefaultString;
|
||||
o.filterIndex = this.mFilterIndex;</pre>
|
||||
o.filters = new Object();
|
||||
o.filters.titles = this.mFilterTitles;
|
||||
o.filters.types = this.mFilters;
|
||||
@@ -179,6 +185,7 @@ nsFilePicker.prototype = {
|
||||
"chrome,modal,titlebar,resizable=yes,dependent=yes",
|
||||
o);
|
||||
this.mFile = o.retvals.file;
|
||||
this.mFilterIndex = o.retvals.filterindex;</pre>
|
||||
lastDirectory = o.retvals.directory;
|
||||
return o.retvals.buttonStatus;
|
||||
} catch(ex) { dump("unable to open file picker\n" + ex + "\n"); }
|
||||
|
||||
@@ -63,6 +63,7 @@ function nsFilePicker()
|
||||
|
||||
/* attributes */
|
||||
this.mDefaultString = "";
|
||||
this.mFilterIndex = 0;
|
||||
if (lastDirectory) {
|
||||
this.mDisplayDirectory = Components.classes[LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile);
|
||||
this.mDisplayDirectory.initWithUnicodePath(lastDirectory);
|
||||
@@ -99,6 +100,10 @@ nsFilePicker.prototype = {
|
||||
set defaultString(a) { this.mDefaultString = a; },
|
||||
get defaultString() { return this.mDefaultString; },
|
||||
|
||||
/* attribute long filterIndex; */
|
||||
set filterIndex(a) { this.mFilterIndex = a; },
|
||||
get filterInrex() { return this.mFilterIndex; },
|
||||
|
||||
/* methods */
|
||||
init: function(parent, title, mode) {
|
||||
this.mParentWindow = parent;
|
||||
@@ -151,6 +156,7 @@ nsFilePicker.prototype = {
|
||||
o.mode = this.mMode;
|
||||
o.displayDirectory = this.mDisplayDirectory;
|
||||
o.defaultString = this.mDefaultString;
|
||||
o.filterIndex = this.mFilterIndex;</pre>
|
||||
o.filters = new Object();
|
||||
o.filters.titles = this.mFilterTitles;
|
||||
o.filters.types = this.mFilters;
|
||||
@@ -179,6 +185,7 @@ nsFilePicker.prototype = {
|
||||
"chrome,modal,titlebar,resizable=yes,dependent=yes",
|
||||
o);
|
||||
this.mFile = o.retvals.file;
|
||||
this.mFilterIndex = o.retvals.filterindex;</pre>
|
||||
lastDirectory = o.retvals.directory;
|
||||
return o.retvals.buttonStatus;
|
||||
} catch(ex) { dump("unable to open file picker\n" + ex + "\n"); }
|
||||
|
||||
@@ -31,6 +31,8 @@ var dialog;
|
||||
|
||||
// the helperAppLoader is a nsIHelperAppLauncher object
|
||||
var helperAppLoader;
|
||||
var webBrowserPersist;
|
||||
var persistArgs;
|
||||
|
||||
// random global variables...
|
||||
var completed = false;
|
||||
@@ -67,13 +69,15 @@ var progressListener = {
|
||||
percentMsg = replaceInsert( percentMsg, 1, 100 );
|
||||
dialog.progressText.setAttribute("label", percentMsg);
|
||||
|
||||
processEndOfDownload();
|
||||
const nsIWebBrowserPersist = Components.interfaces.nsIWebBrowserPersist;
|
||||
if (helperAppLoader || webBrowserPersist &&
|
||||
webBrowserPersist.currentState == nsIWebBrowserPersist.PERSIST_STATE_FINISHED)
|
||||
setTimeout("processEndOfDownload()", 300);
|
||||
}
|
||||
},
|
||||
|
||||
onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
|
||||
{
|
||||
|
||||
if (!gRestartChecked)
|
||||
{
|
||||
gRestartChecked = true;
|
||||
@@ -281,11 +285,29 @@ function loadDialog()
|
||||
{
|
||||
var sourceUrlValue = {};
|
||||
var initialDownloadTimeValue = {};
|
||||
// targetFile is global because we are going to want re-use later one...
|
||||
targetFile = helperAppLoader.getDownloadInfo(sourceUrlValue, initialDownloadTimeValue);
|
||||
var sourceUrl = null;
|
||||
|
||||
// targetFile is global because we are going to want re-use later one...
|
||||
if (helperAppLoader) {
|
||||
targetFile = helperAppLoader.getDownloadInfo(sourceUrlValue, initialDownloadTimeValue);
|
||||
|
||||
var sourceUrl = sourceUrlValue.value;
|
||||
startTime = initialDownloadTimeValue.value / 1000;
|
||||
sourceUrl = sourceUrlValue.value;
|
||||
startTime = initialDownloadTimeValue.value / 1000;
|
||||
}
|
||||
else if (webBrowserPersist) {
|
||||
// When saving web pages, the file we're saving into is passed to us as a parameter.
|
||||
try {
|
||||
persistArgs.source.QueryInterface(Components.interfaces.nsIURI);
|
||||
sourceUrl = persistArgs.source;
|
||||
}
|
||||
catch (e) {
|
||||
sourceUrl = { spec: persistArgs.source.URL };
|
||||
}
|
||||
|
||||
// When saving web pages, we don't need to do anything special to receive the time
|
||||
// at which the transfer started, so just assume it started 'now'.
|
||||
startTime = ( new Date() ).getTime();
|
||||
}
|
||||
|
||||
// set the elapsed time on the first pass...
|
||||
var now = ( new Date() ).getTime();
|
||||
@@ -312,10 +334,15 @@ function replaceInsert( text, index, value ) {
|
||||
|
||||
function onLoad() {
|
||||
// Set global variables.
|
||||
helperAppLoader = window.arguments[0].QueryInterface( Components.interfaces.nsIHelperAppLauncher );
|
||||
|
||||
if ( !helperAppLoader ) {
|
||||
dump( "Invalid argument to downloadProgress.xul\n" );
|
||||
try {
|
||||
helperAppLoader = window.arguments[0].QueryInterface( Components.interfaces.nsIHelperAppLauncher );
|
||||
}
|
||||
catch (e) {
|
||||
webBrowserPersist = window.arguments[0].QueryInterface( Components.interfaces.nsIWebBrowserPersist );
|
||||
}
|
||||
|
||||
if ( !helperAppLoader && !webBrowserPersist ) {
|
||||
dump( "Invalid argument to helperAppDldProgress.xul\n" );
|
||||
window.close()
|
||||
return;
|
||||
}
|
||||
@@ -342,12 +369,44 @@ function onLoad() {
|
||||
var object = this;
|
||||
doSetOKCancel("", function () { return object.onCancel();});
|
||||
|
||||
// set our web progress listener on the helper app launcher
|
||||
if (helperAppLoader)
|
||||
helperAppLoader.setWebProgressListener(progressListener);
|
||||
else if (webBrowserPersist) {
|
||||
webBrowserPersist.progressListener = progressListener;
|
||||
|
||||
persistArgs = window.arguments[1];
|
||||
|
||||
targetFile = persistArgs.target;
|
||||
|
||||
try {
|
||||
var uri = persistArgs.source.QueryInterface(Components.interfaces.nsIURI);
|
||||
webBrowserPersist.saveURI(uri, persistArgs.postData, targetFile);
|
||||
}
|
||||
catch (e) {
|
||||
// Saving a Document, not a URI:
|
||||
|
||||
// Create the local directory into which to save associated files.
|
||||
const lfContractID = "@mozilla.org/file/local;1";
|
||||
const lfIID = Components.interfaces.nsILocalFile;
|
||||
var filesFolder = Components .classes[lfContractID].createInstance(lfIID);
|
||||
filesFolder.initWithUnicodePath(persistArgs.target.unicodePath);
|
||||
|
||||
var nameWithoutExtension = filesFolder.leafName;
|
||||
nameWithoutExtension = nameWithoutExtension.substring(0, nameWithoutExtension.lastIndexOf("."));
|
||||
var filesFolderLeafName = getString("filesFolder");
|
||||
filesFolderLeafName = filesFolderLeafName.replace(/\^BASE\^/, nameWithoutExtension);
|
||||
|
||||
filesFolder.leafName = filesFolderLeafName;
|
||||
|
||||
filesFolder.create(lfIID.DIRECTORY_TYPE, 0644);
|
||||
webBrowserPersist.saveDocument(persistArgs.source, targetFile, filesFolder);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill dialog.
|
||||
loadDialog();
|
||||
|
||||
// set our web progress listener on the helper app launcher
|
||||
helperAppLoader.setWebProgressListener(progressListener);
|
||||
|
||||
if ( window.opener ) {
|
||||
moveToAlertPosition();
|
||||
} else {
|
||||
@@ -396,6 +455,10 @@ function onCancel ()
|
||||
|
||||
catch( exception ) {}
|
||||
}
|
||||
else if (webBrowserPersist)
|
||||
{
|
||||
webBrowserPersist.cancelSave();
|
||||
}
|
||||
|
||||
// Close up dialog by returning true.
|
||||
return true;
|
||||
@@ -445,8 +508,10 @@ function setupPostProgressUI()
|
||||
// and enable the open and open folder buttons on the dialog.
|
||||
function processEndOfDownload()
|
||||
{
|
||||
if (!keepProgressWindowUpBox.checked)
|
||||
if (!keepProgressWindowUpBox.checked) {
|
||||
closeWindow(); // shut down, we are all done.
|
||||
return;
|
||||
}
|
||||
|
||||
// o.t the user has asked the window to stay open so leave it open and enable the open and open new folder buttons
|
||||
setupPostProgressUI();
|
||||
|
||||
@@ -57,6 +57,7 @@ Contributor(s):
|
||||
<data id="dialog.strings.longTimeFormat">&longTimeFormat;</data>
|
||||
<data id="dialog.strings.unknownTime">&unknownTime;</data>
|
||||
<data id="dialog.strings.pausedMsg">&pausedMsg;</data>
|
||||
<data id="dialog.strings.filesFolder">&filesFolder;</data>
|
||||
|
||||
<grid flex="1">
|
||||
<columns>
|
||||
|
||||
@@ -85,3 +85,11 @@
|
||||
<!ENTITY keepProgressDialogUpMsg.label "Keep this window open after the download is complete.">
|
||||
<!ENTITY openFolder.label "Reveal Location">
|
||||
<!ENTITY open.label "Launch File">
|
||||
|
||||
<!-- LOCALIZATION NOTE (filesFolder):
|
||||
This is the name of the folder that is created parallel to a HTML file
|
||||
when it is saved "With Images". The ^BASE^ section is replaced with the
|
||||
leaf name of the file being saved (minus extension). -->
|
||||
<!ENTITY filesFolder "^BASE^_files">
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user