diff options
Diffstat (limited to 'embed/mozilla/ContentHandler.cpp')
-rw-r--r-- | embed/mozilla/ContentHandler.cpp | 671 |
1 files changed, 671 insertions, 0 deletions
diff --git a/embed/mozilla/ContentHandler.cpp b/embed/mozilla/ContentHandler.cpp new file mode 100644 index 000000000..9476a2f72 --- /dev/null +++ b/embed/mozilla/ContentHandler.cpp @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2001 Philip Langdale + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * The functioning of the download architecture, as described by Philip + * on 28 May 2001 and updated on 28 June 2001: + * + * When mozilla runs into a file it cannot render internally or that it + * does not have a plugin for, it calls the + * nsIExternalHelperAppService. This service will then either attempt to + * save the file or run it with a helper app depending on what the + * mozilla mime database returns. + * + * nsIExternalHelperAppService then calls out to the nsIHelperAppDialog + * interface which handles the UI for the service. This is the interface + * which we have reimplemented. Therefore, with a major caveat, we have + * put a GNOME/GTK frontend on an unmodified mozilla backend. + * + * Now for the caveat. With respect to saving files to disk, the mozilla + * backend works exactly the same as it does in + * mozilla-the-browser. However, for dealing with helper apps, we do not + * use the mozilla backend at all. This is because we want to use the + * gnome-vfs database to retrieve helper-app info, rather than the + * mozilla helper app database. + * + * How it works: + * + * a) The user clicks on a link or follows a redirect to a file of a type + * that mozilla cannot handle. Mozilla passes the link to the + * ExternalHelperAppService which in turn calls the Show() method of + * nsIHelperAppDialog. + * + * b) In our implementation of Show() we first compare the passed mime + * type to epiphany's mime list. If the mime type is in the list, we then + * lookup the Action associated with the mime type. Currently, the + * possible mime-actions are: + * + * Save to Disk + * Run with Helper App + * Ask User + * + * The default action is Ask User, and if the mime-type is not in our + * list, this is what will be assumed. + * + * c) If Ask User is the chosen action, a dialog will be shown to the + * user allowing the user to choose from the other two possible actions + * as well as a checkbox to let the user set the default action to the + * chosen action for the future. + * + * d-1) The "Save to Disk" action. We first check epiphany preferences to + * see if the user wants to use the built-in mozilla downloader, gtm or + * a command-line executed downloader. + * + * d-2a) The built-in downloader. This action is handled by the mozilla + * backend. Our nsIHelperAppDialog does the same thing that the + * mozilla-the-browser one does, which is to call the SaveToDisk() method + * of nsIExternalHelperAppService. This in turn calls the + * PromptForSaveToFile() method of nsIHelperAppDialog putting the ball + * back in our court. + * + * d-2b) Now, if epiphany is configured to always ask for a download + * directory, it will pop up a file selector so that the user can select + * the directory and filename to save the file to. Otherwise, it will + * use epiphany's default download directory and proceed without + * interaction. + * + * d-2c) When PromptForSaveToFile() returns, nsIExternalHelperAppService + * will then call the ShowProgressDialog() method of + * nsIHelperAppDialog. This progress dialog, obviously, tracks the + * progress of the download. It is worth noting that mozilla starts the + * actual download as soon as the user clicks on the link or follows the + * redirect. While the user is deciding what action to take, the file is + * downloading. Often, for small files, the file is already downloaded + * when the user decides what directory to put it in. The progress dialog + * does not appear in these cases. Also, we currently have a small + * problem where our progress dialog times the download from the point + * the dialog appears, not from the time the download starts. This is due + * to the timestamp that is passed to us is just plain weird, and I + * haven't worked out how to turn it into a useable time. The fact that + * the download starts early means that the file is actually downloaded + * to a temp file and only at the end is it moved to it's final location. + * + * d-3a) The two external downloader options. These options are + * handled completely by epiphany. The first thing that we do is call the + * Cancel() method of nsIExternalHelperAppService to cancel the mozilla + * download. We then pass the url to our own LaunchExternalDownloader() + * method. This method will ask for a download directory as appropriate + * as with the "Save to disk" action. + * + * d-3b) Finally, depending on whether GTM or a command line handler was + * selected in prefs, the external handler will be called with the url + * passed and the directory selected. + * + * e-1) The "Run with Helper App" action. This action is currently only + * working with a minimal implementation. First, we explicitly call + * ShowProgressDialog() so the user knows that the file is being + * downloaded. We also need this so that we only run the helper after the + * file is completely downloaded. The file will download to temp location + * that it would be moved from if the action was "Save to Disk". We have + * to call ShowProgressDialog() ourselves because we are not using + * mozilla's helper mechanism which would usually make the call for us. + * + * e-2) If there is a default helper app in our mime database and alwaysAsk + * is false, epiphany will run the default helper automatically. Otherwise it + * will pop up a helper chooser dialog which lists the helpers that gnome-vfs + * knows about as well as providing a GnomeFileEntry to allow the user to + * select and arbitrary application. The default value of the GnomeFileEntry + * is the helper stored in our database if one exits. + * + * f) General notes. We cannot use this infrastructure to override + * native mozilla types. mozilla will attempt to render these types and + * never call out to us. We are at the end of the chain as the handler of + * last resort, so native and plugin types will never reach us. This also + * means that a file with an incorrect mime-type ( eg: .tar.bz2 marked as + * text/plain ) will be incorrectly rendered by mozilla. We cannot help + * this. + * + * Despite the apparent user-side similarity with explicit downloads by + * a shift-click or context-menu item, there is actually none at all. + * Explicit downloads are handled by the nsIStreamTransfer manager which + * we use as is. Currently the progress dialog for the stream transfer + * manager is un-overridable, so it appears in XUL. This will change in + * due course. + * + * Matt would like the modifiy the progress dialog so each file currently + * being downloaded becomes a clist entry in a master dialog rather than + * causing a separate progress dialog. a lot of progress dialogs gets + * really messy. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +extern "C" { +#include "libgnomevfs/gnome-vfs-mime-handlers.h" +} + +#include "ephy-embed-shell.h" +#include "ephy-prefs.h" +#include "eel-gconf-extensions.h" +#include "ephy-glade.h" +#include "ephy-string.h" +#include "ephy-gui.h" +#include "ephy-embed-utils.h" +#include "ephy-file-helpers.h" +#include "ProgressListener.h" +#include "ContentHandler.h" + +#include <gtk/gtkentry.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtkprogress.h> +#include <gtk/gtkoptionmenu.h> +#include <libgnome/gnome-exec.h> +#include <libgnome/gnome-i18n.h> +#include <libgnome/gnome-config.h> +#include <libgnome/gnome-util.h> +#include <libgnomevfs/gnome-vfs-mime.h> + +#include "FilePicker.h" +#include "MozillaPrivate.h" + +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsIFactory.h" +#include "nsISupportsArray.h" +#include "nsIServiceManager.h" +#include "nsWeakReference.h" +#include "nsXPComFactory.h" + +#include "nsString.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsIMIMEInfo.h" +#include "nsIChannel.h" +#include "nsIFTPChannel.h" +#include "nsILocalFile.h" +#include "nsIPrefService.h" +#include "nsIDOMWindow.h" +#include "nsIDOMWindowInternal.h" + +class GContentHandler; +class GDownloadProgressListener; +struct MimeAskActionDialog; +struct HelperAppChooserDialog; + +extern "C" +void mime_ask_dialog_save_clicked_cb (GtkButton *button, + MimeAskActionDialog *dialog); +extern "C" +void mime_ask_dialog_open_clicked_cb (GtkButton *button, + MimeAskActionDialog *dialog); +extern "C" +gint mime_ask_dialog_cancel_clicked_cb (GtkButton *button, + MimeAskActionDialog *dialog); + +/* + * MimeAskActionDialog: the representation of dialogs used to ask + * about actions on MIME types + */ +struct MimeAskActionDialog +{ + MimeAskActionDialog(GContentHandler *aContentHandler, + GtkWidget *aParentWidget, + const char *aMimeType); + ~MimeAskActionDialog(); + + GContentHandler *mContentHandler; + GladeXML *mGXml; + GtkWidget *mParent; + GtkWidget *mAppMenu; + + GnomeVFSMimeApplication *mDefaultApp; +}; + +/* Implementation file */ +NS_IMPL_ISUPPORTS1(GContentHandler, nsIHelperAppLauncherDialog) + +GContentHandler::GContentHandler() : mUri(nsnull), + mMimeType(nsnull), + mDownloadCanceled(PR_FALSE), + mHelperProgress(PR_FALSE) +{ + NS_INIT_ISUPPORTS(); + /* member initializers and constructor code */ +} + +GContentHandler::~GContentHandler() +{ + /* destructor code */ + g_free (mUri); + g_free (mMimeType); +} + +//////////////////////////////////////////////////////////////////////////////// +// begin nsIHelperAppLauncher impl +//////////////////////////////////////////////////////////////////////////////// + +/* void show (in nsIHelperAppLauncher aLauncher, in nsISupports aContext); */ +NS_IMETHODIMP GContentHandler::Show(nsIHelperAppLauncher *aLauncher, + nsISupports *aContext) +{ + nsresult rv; + + mLauncher = aLauncher; + mContext = aContext; + rv = Init (); + + MIMEAskAction (); + + return NS_OK; +} + +/* nsILocalFile promptForSaveToFile (in nsISupports aWindowContext, in wstring aDefaultFile, in wstring aSuggestedFileExtension); */ +NS_IMETHODIMP GContentHandler:: + PromptForSaveToFile(nsISupports *aWindowContext, + const PRUnichar *aDefaultFile, + const PRUnichar *aSuggestedFileExtension, + nsILocalFile **_retval) +{ + nsresult rv; + + mContext = aWindowContext; + + nsCOMPtr<nsIDOMWindowInternal> windowInternal = + do_QueryInterface (aWindowContext); + + nsCOMPtr<nsILocalFile> saveDir; + char *dirName; + + /* FIXME persist download dir */ + dirName = g_strdup (g_get_home_dir()); + + saveDir = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID); + saveDir->InitWithPath (NS_ConvertUTF8toUCS2(dirName)); + g_free (dirName); + + nsCOMPtr <nsILocalFile> saveFile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + + PRInt16 okToSave = nsIFilePicker::returnCancel; + + if (okToSave == nsIFilePicker::returnCancel) + { + nsCOMPtr<nsIFilePicker> filePicker = + do_CreateInstance (G_FILEPICKER_CONTRACTID); + + const nsAString &title = NS_ConvertUTF8toUCS2(_("Select the destination filename")); + + filePicker->Init (windowInternal, + PromiseFlatString(title).get(), + nsIFilePicker::modeSave); + filePicker->SetDefaultString (aDefaultFile); + filePicker->SetDisplayDirectory (saveDir); + + filePicker->Show (&okToSave); + + if (okToSave == nsIFilePicker::returnOK) + { + filePicker->GetFile (getter_AddRefs(saveFile)); + } + } + + if (okToSave == nsIFilePicker::returnCancel) + return NS_ERROR_FAILURE; + else + { + nsCOMPtr<nsIFile> directory; + rv = saveFile->GetParent (getter_AddRefs(directory)); + + NS_IF_ADDREF (*_retval = saveFile); + return NS_OK; + } +} + +/* void showProgressDialog (in nsIHelperAppLauncher aLauncher, in nsISupports aContext); */ +NS_METHOD GContentHandler::ShowProgressDialog(nsIHelperAppLauncher *aLauncher, + nsISupports *aContext) +{ + g_print ("GContentHandler::ShowProgressDialog is depreciated!\n"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +//////////////////////////////////////////////////////////////////////////////// +// begin local public methods impl +//////////////////////////////////////////////////////////////////////////////// + +NS_METHOD GContentHandler::FindHelperApp (void) +{ + if (mUrlHelper) + { + return LaunchHelperApp (); + } + else + { + if (NS_SUCCEEDED(SynchroniseMIMEInfo())) + { + return mLauncher->LaunchWithApplication(nsnull, PR_FALSE); + } + else + { + return NS_ERROR_FAILURE; + } + } +} + +NS_METHOD GContentHandler::LaunchHelperApp (void) +{ + if (mMimeType) + { + nsresult rv; + nsCOMPtr<nsIExternalHelperAppService> helperService = + do_GetService (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID); + + nsCOMPtr<nsPIExternalAppLauncher> appLauncher = + do_QueryInterface (helperService, &rv); + if (NS_SUCCEEDED(rv)) + { + appLauncher->DeleteTemporaryFileOnExit(mTempFile); + } + + nsString uFileName; + mTempFile->GetPath(uFileName); + const nsCString &aFileName = NS_ConvertUCS2toUTF8(uFileName); + + const nsCString &document = (mUrlHelper) ? mUrl : aFileName; + + char *param = g_strdup (document.get()); + ephy_file_launch_application (mHelperApp->command, + param, + mHelperApp->requires_terminal); + + if(mUrlHelper) mLauncher->Cancel(); + + g_free (param); + } + else + { + mLauncher->Cancel (); + } + + return NS_OK; +} + +NS_METHOD GContentHandler::ShowHelperProgressDialog (void) +{ + mHelperProgress = PR_TRUE; + return ShowProgressDialog (mLauncher,mContext); +} + +NS_METHOD GContentHandler::GetLauncher (nsIHelperAppLauncher * *_retval) +{ + NS_IF_ADDREF (*_retval = mLauncher); + return NS_OK; +} + +NS_METHOD GContentHandler::GetContext (nsISupports * *_retval) +{ + NS_IF_ADDREF (*_retval = mContext); + return NS_OK; +} + +static gboolean +application_support_scheme (GnomeVFSMimeApplication *app, const nsCString &aScheme) +{ + GList *l; + + g_return_val_if_fail (app != NULL, FALSE); + g_return_val_if_fail (!aScheme.IsEmpty(), FALSE); + + if (app->expects_uris != GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS) + return FALSE; + + for (l = app->supported_uri_schemes; l != NULL; l = l->next) + { + char *uri_scheme = (char *)l->data; + g_return_val_if_fail (uri_scheme != NULL, FALSE); + if (aScheme.Equals(uri_scheme)) return TRUE; + } + + return FALSE; +} + +NS_METHOD GContentHandler::SetHelperApp(GnomeVFSMimeApplication *aHelperApp, + PRBool alwaysUse) +{ + mHelperApp = aHelperApp; + mUrlHelper = application_support_scheme (aHelperApp, mScheme); + + return NS_OK; +} + +NS_METHOD GContentHandler::SynchroniseMIMEInfo (void) +{ + nsresult rv; + nsCOMPtr<nsIMIMEInfo> mimeInfo; + rv = mLauncher->GetMIMEInfo(getter_AddRefs(mimeInfo)); + if(NS_FAILED(rv)) return NS_ERROR_FAILURE; + + nsCOMPtr<nsILocalFile> helperFile; + rv = NS_NewNativeLocalFile(nsDependentCString(mHelperApp->command), + PR_TRUE, + getter_AddRefs(helperFile)); + if(NS_FAILED(rv)) return NS_ERROR_FAILURE; + + rv = mimeInfo->SetPreferredApplicationHandler(helperFile); + if(NS_FAILED(rv)) return NS_ERROR_FAILURE; + + nsMIMEInfoHandleAction mimeInfoAction; + mimeInfoAction = nsIMIMEInfo::alwaysAsk; + + if(mHelperApp->requires_terminal) //Information passing kludge! + { + rv = mimeInfo->SetApplicationDescription + (NS_LITERAL_STRING("runInTerminal").get()); + if(NS_FAILED(rv)) return NS_ERROR_FAILURE; + } + + rv = mimeInfo->SetPreferredAction(mimeInfoAction); + if(NS_FAILED(rv)) return NS_ERROR_FAILURE; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// begin local private methods impl +//////////////////////////////////////////////////////////////////////////////// +NS_METHOD GContentHandler::Init (void) +{ + nsresult rv; + + nsCOMPtr<nsIMIMEInfo> MIMEInfo; + rv = mLauncher->GetMIMEInfo (getter_AddRefs(MIMEInfo)); + rv = MIMEInfo->GetMIMEType (&mMimeType); + + rv = mLauncher->GetDownloadInfo(getter_AddRefs(mUri), + &mTimeDownloadStarted, + getter_AddRefs(mTempFile)); + rv = mUri->GetSpec (mUrl); + rv = mUri->GetScheme (mScheme); +#if 0 + /* GetSource seems redundant and isn't in 0.9 This code is here while + it remains unclear what GetSource is for. --phil */ + nsCOMPtr<nsIURI> uri; + rv = mLauncher->GetSource(getter_AddRefs(uri)); + rv = uri->GetSpec (mUrl); +#endif + ProcessMimeInfo (); + + return NS_OK; +} + +NS_METHOD GContentHandler::ProcessMimeInfo (void) +{ + if (mMimeType == NULL || + !nsCRT::strcmp(mMimeType, "application/octet-stream")) + { + nsresult rv; + nsCOMPtr<nsIURL> url = do_QueryInterface(mUri, &rv); + if (NS_SUCCEEDED(rv) && url) + { + nsCAutoString uriFileName; + url->GetFileName(uriFileName); + mMimeType = g_strdup + (gnome_vfs_mime_type_from_name + (uriFileName.get())); + } + else + mMimeType = g_strdup ("application/octet-stream"); + } + + return NS_OK; +} + +NS_METHOD GContentHandler::MIMEAskAction (void) +{ + nsCOMPtr<nsIDOMWindow> parent = do_QueryInterface (mContext); + GtkWidget *parentWidget = MozillaFindGtkParent (parent); + + new MimeAskActionDialog(this, parentWidget, mMimeType); + + return NS_OK; +} + +//------------------------------------------------------------------------------ + +NS_DEF_FACTORY (GContentHandler, GContentHandler); + +/** + * NS_NewContentHandlerFactory: + */ +nsresult NS_NewContentHandlerFactory(nsIFactory** aFactory) +{ + NS_ENSURE_ARG_POINTER(aFactory); + *aFactory = nsnull; + + nsGContentHandlerFactory *result = new nsGContentHandlerFactory; + if (result == NULL) + { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(result); + *aFactory = result; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// begin MIMEAskActionDialog methods. +//////////////////////////////////////////////////////////////////////////////// + +MimeAskActionDialog::MimeAskActionDialog(GContentHandler *aContentHandler, + GtkWidget *aParentWidget, + const char *aMimeType) : + mContentHandler(aContentHandler), + mParent(aParentWidget) +{ + GtkWidget *label; + GtkWidget *dialogWidget; + const char *description; + char ltext[255]; //philipl: Fixed length buffer == potential security problem... + + mGXml = ephy_glade_widget_new ("epiphany.glade", "mime_ask_action_dialog", + &dialogWidget, this); + mAppMenu = glade_xml_get_widget (mGXml, "mime_ask_dialog_app_menu"); + + mDefaultApp = gnome_vfs_mime_get_default_application(aMimeType); + + GtkWidget *aMimeIcon = glade_xml_get_widget (mGXml, + "mime_ask_action_icon"); + gtk_image_set_from_file(GTK_IMAGE(aMimeIcon), + gnome_vfs_mime_get_icon(aMimeType)); + + description = gnome_vfs_mime_get_description (aMimeType); + if (!description) description = aMimeType; + + g_snprintf (ltext, 255, "<b>%s</b>", description); + label = glade_xml_get_widget (mGXml, "mime_ask_action_description"); + gtk_label_set_markup (GTK_LABEL (label), ltext); + + gtk_window_set_transient_for (GTK_WINDOW (dialogWidget), + GTK_WINDOW (aParentWidget)); + + gtk_widget_show(dialogWidget); +} + +MimeAskActionDialog::~MimeAskActionDialog() +{ +#if 0 + if(mApps) + gnome_vfs_mime_application_list_free(mApps); +#endif + + gtk_widget_destroy(glade_xml_get_widget(mGXml, "mime_ask_action_dialog")); + g_object_unref(G_OBJECT(mGXml)); +} + +//////////////////////////////////////////////////////////////////////////////// +// begin MIMEAskActionDialog callbacks. +//////////////////////////////////////////////////////////////////////////////// + +extern "C" void +mime_ask_dialog_save_clicked_cb (GtkButton *button, MimeAskActionDialog *dialog) +{ + gtk_widget_hide (glade_xml_get_widget (dialog->mGXml, + "mime_ask_action_dialog")); + + nsresult rv; + nsCOMPtr<nsIHelperAppLauncher> launcher; + rv = dialog->mContentHandler->GetLauncher (getter_AddRefs(launcher)); + + launcher->SaveToDisk (nsnull,PR_FALSE); + + delete dialog; +} + +static void +mime_ask_dialog_download_cancel (MimeAskActionDialog *dialog) +{ + nsresult rv; + nsCOMPtr<nsIHelperAppLauncher> launcher; + rv = dialog->mContentHandler->GetLauncher (getter_AddRefs(launcher)); + + launcher->Cancel (); + + delete dialog; +} + +extern "C" void +mime_ask_dialog_open_clicked_cb (GtkButton *button, MimeAskActionDialog *dialog) +{ + nsresult rv; + nsCOMPtr<nsIHelperAppLauncher> launcher; + rv = dialog->mContentHandler->GetLauncher (getter_AddRefs(launcher)); + GnomeVFSMimeApplication *app = dialog->mDefaultApp; + + if (app) + { + dialog->mContentHandler->SetHelperApp (app, FALSE); + dialog->mContentHandler->FindHelperApp (); + delete dialog; + } + else + { + mime_ask_dialog_download_cancel (dialog); + ephy_embed_utils_nohandler_dialog_run (dialog->mParent); + } +} + +extern "C" gint +mime_ask_dialog_cancel_clicked_cb (GtkButton *button, + MimeAskActionDialog *dialog) +{ + mime_ask_dialog_download_cancel (dialog); + return 0; /* FIXME: philipl, is this the right thing to return? */ +} |